diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 941247574f..257dcd65a2 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -41,10 +41,13 @@ if (__DEV__) { var hasBadMapPolyfill = false; try { var nonExtensibleObject = Object.preventExtensions({}); - /* eslint-disable no-new */ - new Map([[nonExtensibleObject, null]]); - new Set([nonExtensibleObject]); - /* eslint-enable no-new */ + var testMap = new Map([[nonExtensibleObject, null]]); + var testSet = new Set([nonExtensibleObject]); + // This is necessary for Rollup to not consider these unused. + // https://github.com/rollup/rollup/issues/1771 + // TODO: we can remove these if Rollup fixes the bug. + testMap.set(0, 0); + testSet.add(0); } catch (e) { // TODO: Consider warning about bad polyfills hasBadMapPolyfill = true; diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js index ce98b306cd..72ba93fc09 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js @@ -2715,4 +2715,77 @@ describe('ReactIncremental', () => { expect(ReactNoop.flush()).toEqual([]); }); + + it('does not break with a bad Map polyfill', () => { + const realMapSet = Map.prototype.set; + + function triggerCodePathThatUsesFibersAsMapKeys() { + function Thing() { + throw new Error('No.'); + } + class Boundary extends React.Component { + state = {didError: false}; + componentDidCatch() { + this.setState({didError: true}); + } + render() { + return this.state.didError ? null : ; + } + } + ReactNoop.render(); + ReactNoop.flush(); + } + + // First, verify that this code path normally receives Fibers as keys, + // and that they're not extensible. + jest.resetModules(); + let receivedNonExtensibleObjects; + // eslint-disable-next-line no-extend-native + Map.prototype.set = function(key) { + if (typeof key === 'object' && key !== null) { + if (!Object.isExtensible(key)) { + receivedNonExtensibleObjects = true; + } + } + return realMapSet.apply(this, arguments); + }; + React = require('react'); + ReactNoop = require('react-noop-renderer'); + try { + receivedNonExtensibleObjects = false; + triggerCodePathThatUsesFibersAsMapKeys(); + } finally { + // eslint-disable-next-line no-extend-native + Map.prototype.set = realMapSet; + } + // If this fails, find another code path in Fiber + // that passes Fibers as keys to Maps. + // Note that we only expect them to be non-extensible + // in development. + expect(receivedNonExtensibleObjects).toBe(__DEV__); + + // Next, verify that a Map polyfill that "writes" to keys + // doesn't cause a failure. + jest.resetModules(); + // eslint-disable-next-line no-extend-native + Map.prototype.set = function(key, value) { + if (typeof key === 'object' && key !== null) { + // A polyfill could do something like this. + // It would throw if an object is not extensible. + key.__internalValueSlot = value; + } + return realMapSet.apply(this, arguments); + }; + React = require('react'); + ReactNoop = require('react-noop-renderer'); + try { + triggerCodePathThatUsesFibersAsMapKeys(); + } finally { + // eslint-disable-next-line no-extend-native + Map.prototype.set = realMapSet; + } + // If we got this far, our feature detection worked. + // We knew that Map#set() throws for non-extensible objects, + // so we didn't set them as non-extensible for that reason. + }); });