IOS 9 compatibility problem solving: attempting to change configurable attribute


Problems arise

Information from the front test of express: IOS version cannot request and display the advertisement in the article under IOS 9 system!

Troubleshooting and positioning

  • First, confirm the environment in which the bug occurs: IOS 9 of the old model and IOS models of other higher versions are normal
  • Exclusion method to narrow the scope of the problem: please check whether our jssdk has been pulled and whether the ad pull request has been initiated by the test student far away in Beijing through HTTP proxy packet capturing. The result is: JS has been pulled, but the next Ajax request has not been issued; this means that the problem must have occurred during the loading or execution of jssdk.
  • After checking the Babel compilation configuration, the target browser wrote “latest 3 Safari version”. After checking, Safari 9 was not included, so it was changed to “last 10 Safari version”. However, it did not work after the test.
  • Ask the IOS terminal students to check the terminal log to find the cause of JS error.

    In this case, the default packaging method of Web pack will package the module as an eval () execution block, which is not conducive to locating the specific location of the code. Therefore, I changed the devtool of the web pack configuration to “source map”, so that the packaged JS is basically the same as the source code.

    Finally, the terminal students give the error log as follows:

IOS 9 compatibility problem solving: attempting to change configurable attribute

The error message is: attempting to change configured attribute. But because it’s the code after Polyfill after compilation, it’s difficult to determine who caused it. The function that only sees the error is: “define property”

Analysis problem

After reading the error message carefully, we can conclude that this is because we have modified an unconfigurable attribute.

As we know, in Es5, JavaScript provides a method of object.defineproperty to define the descriptor of a property. For a property defined as “configured: false”, it cannot be modified (especially through object.defineproperty to modify the description again, or through the delete operator), while for a property defined as “writebale: false” For the property of, it means that it cannot be modified by the assignment operator “=”.

Then, it is clear that our error alert indicates that we have done an operation of object.defineproperty or delete an unalterable property in our code. So, let’s see who called the function “define property” and finally found the following two lines of code in bundle.js:

_defineProperty(KbArticleCenter, "name", 'kb-article-center');

_defineProperty(KbArticleCenter, "instances", []);

Among them, kbarticlecenter is a class in my source code, while name and instances are static members of two classes. The source code is as follows:

class KbArticleCenter {
  static name = 'kb-article-center'
  static instances = []
//...... Omit the member definition code of a class

Is it possible to say that the static members of a class are not compatible with ios9 after Babel compilation? With questions, I searched the issue of plugin – proposal – class – properties plug-in, but it didn’t pay off.

Solve the problem

Finally, I’ll go back to the compiled code to check it. Suddenly, we realize that a class class will actually be converted into a normal JavaScript function after Babel is compiled, as follows:

function KbArticleCenter(options) {
   //Omit a bunch of constructor code

Our static members will be added directly to the function itself through object.defineproperty. For example, the static name attribute we defined in the type is converted to:_defineProperty(KbArticleCenter, "name", 'kb-article-center');

However, don’t forget that for a JavaScript function, it has a name property with the same name. If we rewrite it in the way of defineproperty here, it means that the original name property must be configurable (i.e. configurable: true).

In a normal modern browser, the name attribute of a JavaScript function is actually true by default. For example, the output result of the following code shows that name is configurable:

var foo = function() {}
Object.getOwnPropertyDescriptor(foo, 'name')

// configurable: true
// enumerable: false
// value: "foo"
// writable: false

However, I deeply doubt that in Safari 9, the name attribute is uncofigurable. Because there is no tester, change the name attribute to compname directly, and repackage it for test verification!

Another problem

After the test and verification, the terminal sees the log and presents a new error message:“Unhandled Promise Rejection: NotSupportedError (DOM Exception 9)

IOS 9 compatibility problem solving: attempting to change configurable attribute

Carefully observe the error stack and find that the problem occurs at the createcontextualfragment location of the source initdom function. We post the code here:

const frag = this.adEl = document.createRange().createContextualFragment(renderedHtml).firstElementChild

The function of the code here is to generate a native DOM node based on the DOM string rendered by arttemplate. The idea here is to use the createcontextualfragment method of range type. The range interface represents a document fragment that contains a part of the node and the text node. By creating contextualfragment, an HTML content can be converted into a document fragment.

Why not use document.createdocumentfragment to create document fragments? Because we create DOM based on strings, not directly.

However, looking up MDN, it is found that createcontextualfragment is an experimental API and should not be used in the production environment. In fact, we found that the entire range API is not available in ios9:

IOS 9 compatibility problem solving: attempting to change configurable attribute

Therefore, it is necessary to change the implementation idea decisively: convert the DOM string into a child DOM node of the parent div through innerHTML, and then take out the DOM node through the firstelementchild method of the parent div:

const tmp = document.createElement('div')
tmp.innerHTML = renderedHtml
const frag = this.adEl = tmp.firstElementChild

The compatibility of firstelementchild is much better:

IOS 9 compatibility problem solving: attempting to change configurable attribute
So far, the problem has been solved.


Different versions of browsers do have different implementations in many details. When we write code, we’d better pay more attention to:

*For known differences, do a good job in feature detection and compatibility

*For the unknown, try to write the code in advance. For example, in the scenario of this article, you should remember not to use attribute names that conflict with some reserved words. Obviously, if you have more solid basic knowledge, you will not make mistakes.

*For some partial APIs (especially those copied from the Internet), it is better to check the specification and the support of can I use