Xstate: XState 4 doesn't work with polyfilled IE11 browser

Created on 21 Mar 2019  路  7Comments  路  Source: davidkpiano/xstate

Bug or feature request?

Bug

Description:

[email protected] doesn't work in IE11 with polyfills. Please note that version 3.x.x works with IE11. The root cause seems to be some interaction with polyfills and: Symbol, Symbol.iterator, Arrays, Object.defineProperty, and prototypes.

The error surfaces in machine.initialState property not containing the array of actions from the state configs onEntry (and presumably onExit) action arrays, and instead containing an empty array.

I created a static HTML file that demonstrates the issue. A strange thing to note is that when the same file is reloaded and executed several times, the error varies.

The error message varies. I have seen a variety of error messages with a complex hierarchichal state machine (rather than the simplified case in my repro below). Here are some that I have seen where the same page is reloaded and executed, but reports different errors:

  1. Invalid descriptor for property __kind__
  2. Unable to get property @@CLOSE of undefined or null reference (where CLOSE is an event name).
  3. Unable to get @@actions of undefined or null reference

(Bug) Expected result:

Arrays are created correctly in IE11.

(Bug) Actual result:

Arrays are not created correctly in IE11.

(Bug) Potential fix:

Since polyfilled features are not guaranteed to work the same way as native code, I think that the change in version 4 to use many more ES6 features makes IE11 break, even when those features are polyfilled.

Link to reproduction or proof-of-concept:

Instructions:
Since codepen doesn't work in IE11, you can go to the pen in a different browser and save the HTML content as a static file and load that into your IE11 browser.

  1. Please open in a non-IE11 browser and create a static HTML file from the HTML content in the pen: https://codepen.io/anon/pen/EMOdxd?editors=1111 .
  2. Load the page in your IE11 browser.
  3. Use the console and execute: machine.initialState.
help wanted

Most helpful comment

I've dug into this a little bit too and it really seems like a problem with polyfill.io service (at least with the version posted by @Zenwolf ), because I've run the example machine right now with their recent polyfills and it works OK (at least when it comes to reading the .initialState.

In the version posted here originally there was a broken "setDescriptor" implementation:

    setDescriptor = function (o, key, descriptor) {
        var protoDescriptor = gOPD(ObjectProto, key);
        delete ObjectProto[key];
        defineProperty(o, key, descriptor);
        if (o !== ObjectProto) {
            defineProperty(ObjectProto, key, protoDescriptor);
        }
    };

It has crashed because protoDescriptor was undefined for one of the cases and that couldn't be set on ObjectProto, when I've fixed the condition to if (o !== ObjectProto && protoDescriptor) the issue went away.

To sum it up - this really shouldn't be reported here and I believe the issue can be closed as XState can be successfully ran in IE11, it just needs proper polyfills.

All 7 comments

I would gladly accept a PR for this - I don't have the environment to test this in IE11 but if you can figure out exactly what doesn't work in IE11, that'd be very helpful.

This bug was really hard to diagnose. Unfortunately, I have not yet figured out the particular bad interaction causing this bug, even after quite a few hours of debugging. If I do manage to figure it out, I will be sure to update this issue.

Just took a brief look at this in an IE11 Dev Image in VirtualBox VM.

I suspect the first error may be related to the spread on this Set:

https://github.com/davidkpiano/xstate/blob/master/src/StateTree.ts#L174

and an incompatibility between the resulting compiled code:

Object.defineProperty(t.prototype,"nextEvents",{get:function(){var t=this,e=this.stateNode.ownEvents,n=b(f(this.nodes).map(function(e){return t.nodes[e].nextEvents}));return s(new Set(n.concat(e)))},enumerable:!0,configurable:!0})

and the custom Object.defineProperty() polyfill added (as part of the Symbol polyfill) via that polyfill.io service.

(Other uses of the Object.defineProperty() and Set polyfills throughout seem to be working fine.)

I'll dig into this further when I have little more time.

Amazing. If I have a breakpoint in just the right spot it works perfectly.

Capturing some notes for when I have time to take a look at this again:

After simplifying the scripts portion of the example down to the following:

<script src="https://polyfill.io/v3/polyfill.js?features=es5,Array.from,Object.assign,Object.create,Object.getOwnPropertyDescriptor"></script>
<script src="https://unpkg.com/xstate@4/dist/xstate.js"></script>
<script src="https://unpkg.com/xstate@4/dist/xstate.interpreter.js"></script>
<script>
window.machine = XState.Machine({
    key: 'testmachine',
    initial: 'initializing',
    states: {
        ended: {},
        initializing: {
            on: {
                start: 'started'
            },
            onEntry: ['create']
        },
        started: {
            on: {
                end: 'ended'
            }
        }
    }
});
</script>

(and executing machine.initialState in the Console as above).

The first error I encounter is:

Invalid descriptor for property `_values`.

  • It always occurs when the Set is instantiated in the StateNode::ownEvents or StateTree::nextEvents getters (even if I neuter the implementations of both to simply create an empty Set and return an unrelated empty Array).
  • When it occurs, it ends up going into this branch in the Symbol polyfill instead of this branch.
  • If I put a breakpoint here - which is called as part of new Set(), it enters the correct branch and successfully returns the machine's initialState.

This appears to be a bug in the interaction between the Set and Symbol polyfills under IE11, rather than an XState bug, per se.

It also could well be influenced by the other bundled polyfills - worth checking if polyfill.io returns them in a consistent order. If not, that could explain the odd and varying behavior described in the initial report.

The Invalid descriptor for property `_values`. error Heisenbug I encountered above appears to be related to the IE11 developer tools, rather than the web app itself. When you type machine.initialState, the error occurs while the IE developer tools REPL traverses the resulting object in an attempt to render an interactive inspectable result row in the IE11 Console.

If I execute JSON.stringify(machine.initialState) (rather than machine.initialState) in the IE11 Console instead, that error disappears.

Just tried the following, and it appears to address the original complaint for the supplied example - the "actions" array is populated.

<script src="https://polyfill.io/v3/polyfill.js?features=es5,Array.from,Array.isArray,Array.prototype.@@iterator,Array.prototype.find,Array.prototype.includes,matchMedia,Intl,Object.assign,Object.create,Object.values,Object.entries,Number.isNaN,Map,Set,Symbol,Symbol.iterator"></script>
<script src="https://unpkg.com/xstate@4/dist/xstate.js"></script>
<script src="https://unpkg.com/xstate@4/dist/xstate.interpreter.js"></script>

<script>
window.machine = XState.Machine({
    key: 'testmachine',
    initial: 'initializing',
    states: {
        ended: {},
        initializing: {
            on: {
                start: 'started'
            },
            onEntry: ['create']
        },
        started: {
            on: {
                end: 'ended'
            }
        }
    }
});
var element = document.createElement('pre');
element.textContent = JSON.stringify(machine.initialState, null, 2);
document.body.appendChild(element);
</script>

Before (using the polyfills from the supplied CodePen):

{
  "actions": [],
  "activities": {},
  "meta": {},
  "events": [],
  "value": "initializing",
  "event": {
    "type": "xstate.init"
  }
}

After (with the updated polyfills referenced above):

{
  "actions": [
    {
      "type": "create"
    }
  ],
  "activities": {},
  "meta": {},
  "events": [],
  "value": "initializing",
  "event": {
    "type": "xstate.init"
  }
}

I tried to derive the polyfill selection from the selection listed in the comments from the original CodePen, but:

  • html5shiv does not appear to be a valid option currently; and
  • adding Object.create appeared to be important.

Hope that helps!

Great analysis! I'll see if I can avoid using Set, but other than that, not much I can do with regard to IE11 behavior.

I've dug into this a little bit too and it really seems like a problem with polyfill.io service (at least with the version posted by @Zenwolf ), because I've run the example machine right now with their recent polyfills and it works OK (at least when it comes to reading the .initialState.

In the version posted here originally there was a broken "setDescriptor" implementation:

    setDescriptor = function (o, key, descriptor) {
        var protoDescriptor = gOPD(ObjectProto, key);
        delete ObjectProto[key];
        defineProperty(o, key, descriptor);
        if (o !== ObjectProto) {
            defineProperty(ObjectProto, key, protoDescriptor);
        }
    };

It has crashed because protoDescriptor was undefined for one of the cases and that couldn't be set on ObjectProto, when I've fixed the condition to if (o !== ObjectProto && protoDescriptor) the issue went away.

To sum it up - this really shouldn't be reported here and I believe the issue can be closed as XState can be successfully ran in IE11, it just needs proper polyfills.

Was this page helpful?
0 / 5 - 0 ratings