conclusion

FYI: ApI-monitor is a library that automatically reports server interface time, interface error information, error codes, and HTTP status codes to monitor interface quality.

Before we start, let’s look at a little bit of code and ask, can I get into the for loop?

var origin = {};

Object.defineProperty(origin, 'username', {

    get() {

        return 'mbj';

    },

    set() {},

    configurable: true.enumerable: true});var obj = Object.create(origin);

Object.defineProperty(obj, 'username', {

    get: function () {

        return 'wmy';

    },

    set: function () {}});console.log('obj', obj);



for (var attr in obj) {

    console.log('attr', attr);

    console.log(

        `obj.${attr}Attribute descriptor '.Object.getOwnPropertyDescriptor(obj, attr)

    );

    console.log(

        `obj.__proto__.${attr}Attribute descriptor '.Object.getOwnPropertyDescriptor(obj.__proto__, attr)

    );

}
Copy the code

The results of

Mobile ios14.5beta The mobile ios version is not 14.5 Android PC Chrome
Whether the for loop can be entered Can’t can can Can’t

If prototype has an attribute that is not enumerable, then does this attribute use “for in”?

The result is:

Mobile ios14.5beta and PC Chrome Mobile ios is not 14.5 or Android
Whether for in is enumerable no is

Back to XMLHttprequest, first of all, before the API-monitor proxy XMLHttprequest, there were libraries that proxied XMLHttprequest, and there are

window. __real=window.XMLHttpRequest

window.XMLHttpRequest= function {

    this._xhr = new window.__real();

    for (var attr in this._xhr) {

        var type = ' ';

        try {

            type = typeof this._xhr[attr];

        } catch (e) {}

        if (type === 'function') {

            this[attr] = hookfun(attr);

        } else {

            Object.defineProperty(this, attr, {

                get: genGetter(attr),

                set: genSetter(attr), }); }}}window.XMLHttpRequest.prototype = window.__real.prototype
Copy the code

Enumerable and Enumerable are false, and the XMLHttpRequest Object can be proxying without any additional information, regardless of whether it is an Object or a controller.

At this time

var xml=new window.XMLHttpRequest()
Copy the code

So that fits the code above, like an onReadyStatechange attribute on XML, actually

The descriptor
xml.onreadystatechange Enumerable and Different are false
xml.__proto__.onreadystatechange Enumerable and different are true

Therefore, when apI-monitor traverses the XML for in to realize the proxy function, onreadyStatechange and responseText cannot be traversed on ios 14.5, resulting in no data response.

conclusion

If an object has an attribute that is not enumerable, and the object’s prototype has an attribute that is enumerable

  1. Mobile ios14.5beta version and PC Chrome, not enumerable
  2. Mobile ios, not 14.5 or Android, can be enumerated

As you can see from this incident, XML has a lot of uncertainty and legacy issues, and proxies for real XML by traversing XML instances are not reliable.

The following is an analysis of the specific process of locating this problem, for those who are interested

Api-monitor intercepts the XMLHttprequest mode

Api-monitor overwrites window.XMLHttprequest with its own implementation of XMLHttprequest. When using XML, the user is actually using the XML provided by api-monitor. An XML instance proxies a real XML instance. If you call xml.open, the open method of the real XML instance is called, and if you assign xml.onreadyStatechange, the real XML instance is assigned.

The implementation code is mainly based on Ajax-hook

Non-ios 14.5 beta

You can iterate through all the properties and methods from an instance of XMLHttprequest with a for in, and that’s fine. The XML provided by apI-monitor can proxy all the methods and properties of a full-line XML instance.

The code is as follows:

// Iterate over the actual XML instance

var xhr = new window.XMLHttpRequest();

console.log('enter???? '.Object.keys(xhr));

for (var attr in xhr) {

    console.log('attr ', attr);

    var type = ' ';

    if (attr === 'xhr') continue;

    try {

        type = typeof xhr[attr]; // May cause exception on some browser

    } catch (e) {}

    if (type === 'function') {

        // Proxy for real XML methods

        this[attr] = hookFunction(attr);

    } else {

        // Proxy real XML attributes

        Object.defineProperty(this, attr, {

            get: getterFactory(attr),

            set: setterFactory(attr),

            enumerable: true}); }}Copy the code

The following log is for in traversing the output of a real XML instance:

Ios 14.5 beta

When traversing XHR instances through for in, ios 14.5 cannot traverse onReadyStatechange, onTimeout, timeout, etc. Therefore, when assigning onReadyStatechange to XML, The onReadyStatechange method cannot be mounted to a real XML instance, resulting in an inability to respond to data.

As above, the following log shows the output of a real XML instance iterated with for in:

Traverse the contrast

Let’s walk through real XML objects in three ways to compare the results in ios14.5beta and non-14.5

Traverse the way Mobile ios14.5beta The mobile ios version is not 14.5 PC Chrome
Object.keys All the ways All the ways empty
for in All the ways All the waysAnd attributes All the methods and properties
Object.getOwnPropertyNames() All the methods and properties All the methods and properties empty

From the above results, we can guess that the attributes and methods are directly mounted on the XML instance on the mobile side, but the attributes are not traversable. I use the following code to get the attribute descriptor

console.log(

            'descriptor '.Object.getOwnPropertyDescriptor(xhr, 'responseType'));console.log(

            'descriptor '.Object.getOwnPropertyDescriptor(xhr, 'send'));Copy the code

On the mobile end of ios, the results are all

// responseType

{

    configurable:false.enumerable:false.get(){},

    set(){},}// send

{

    configurable: true.enumerable: true.value:function(){}

    w​ritable: true

}
Copy the code

This result confirms our hypothesis, which contradicts why mobile ios can iterate over non-enumerable properties other than the 14.5 version. The hypothesis is that Apple dad fixed this bug in the 14.5 beta, let’s read on.

Another village

Print the xmlHttprequest obtained in api-monitor, and find that the XMLHttprequest is overridden by other services. The real XML instance I mentioned above is an XML instance of someone else’s proxy, not a native XML instance. From the printed implementation below, methods are mounted on the instance and properties are defined on the instance as follows

Object.defineProperty(this, attr, {

                get: genGetter(attr),

                set: genSetter(attr),

            });
Copy the code

The default value of Enumerable and cis is false, and therefore it is not possible to iterate for in, without any additional version 14.5 or Android. PC Chrome and ios 14.5 do not. And found that

window.__real.prototype === window.XMLHttpRequest.prototype   // true
Copy the code

__real. Prototype property descriptors are different, and enumerable is true. If any property is different, no additional control system can be configured. Enumerable is false based on the property descriptor of the XML instance above, and the initial conclusion is made.

My print code and someone else’s implementation of XMLHttpRequest:

if (typeof window! = ='undefined') {

  console.log('fake'.window.XMLHttpRequest);

  const foo = new window.XMLHttpRequest();

  console.log('open', foo.open);

  console.log('send', foo.send);

  console.log('real '.window.__real);

  console.log(

    'isEqual'.window.__real,

    window.__real.prototype === window.XMLHttpRequest.prototype

  );  // It prints true

}



// Print out window.xmlHttprequest

window.__real=window.XMLHttpRequest

window.XMLHttpRequest= function {

    this._xhr = new window.__real();

    for (var attr in this._xhr) {

        var type = ' ';

        try {

            type = typeof this._xhr[attr];

        } catch (e) {}

        if (type === 'function') {

            this[attr] = hookfun(attr);

        } else {

            Object.defineProperty(this, attr, {

                get: genGetter(attr),

                set: genSetter(attr), }); }}}window.XMLHttpRequest.prototype = window.__real.prototype
Copy the code

❤️ Thank you

That is all the content of this sharing. I hope it will help you

Don’t forget to share, like and bookmark your favorite things.

Welcome to pay attention to the public number ELab team receiving factory good article ~