diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js index 278a153060179..792b9611f29f2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js @@ -12,7 +12,6 @@ // Polyfills for test environment global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream; global.TextEncoder = require('util').TextEncoder; -global.AbortController = require('abort-controller'); let React; let ReactDOMFizzServer; diff --git a/packages/react-reconciler/src/ReactAbortController.js b/packages/react-reconciler/src/ReactAbortController.js new file mode 100644 index 0000000000000..b81bd5209981f --- /dev/null +++ b/packages/react-reconciler/src/ReactAbortController.js @@ -0,0 +1,138 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {enableCache} from 'shared/ReactFeatureFlags'; + +const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; + +const signals: Map< + AbortControllerShim, + AbortSignalShim, +> = (new PossiblyWeakMap(): any); + +const abortedFlags: Map< + AbortSignalShim, + boolean, +> = (new PossiblyWeakMap(): any); + +type Listener = { + listener: any => void, + next: Listener | null, +}; + +const listenersMap: Map< + AbortSignalShim, + Map, +> = (new PossiblyWeakMap(): any); + +function getListeners(target): Map { + const listeners = listenersMap.get(target); + if (listeners == null) { + // eslint-disable-next-line react-internal/prod-error-codes + throw new Error( + 'Listeners should already be registered. This is a bug in React.', + ); + } + return listeners; +} + +class AbortSignalShim { + constructor() { + listenersMap.set(this, new Map()); + } + get aborted() { + return !!abortedFlags.get(this); + } + + addEventListener(type, listener) { + const listeners = getListeners(this); + let node = listeners.get(type); + + const newNode = {listener, next: null}; + if (node === undefined) { + listeners.set(type, newNode); + return; + } + + let prev = null; + while (node != null) { + prev = node; + node = node.next; + } + if (prev != null) { + prev.next = newNode; + } + } + + removeEventListener(eventName, listener) { + const listeners = getListeners(this); + let node = listeners.get(eventName); + + let prev = null; + while (node != null) { + if (node.listener === listener) { + if (prev !== null) { + prev.next = node.next; + } else if (node.next !== null) { + listeners.set(eventName, node.next); + } else { + listeners.delete(eventName); + } + return; + } + + prev = node; + node = node.next; + } + } + + dispatchEvent(event) { + const listeners = getListeners(this); + let node = listeners.get(event.type); + while (node != null) { + node.listener(event); + node = node.next; + } + } +} + +class AbortControllerShim { + constructor() { + const signal = new AbortSignalShim(); + abortedFlags.set(signal, false); + signals.set(this, signal); + } + + get signal() { + return signals.get(this); + } + + abort() { + const signal = signals.get(this); + if (signal == null) { + // eslint-disable-next-line react-internal/prod-error-codes + throw new Error( + 'Aborted signal was not registered. This is a bug in React.', + ); + } + if (abortedFlags.get(signal) !== false) { + return; + } + abortedFlags.set(signal, true); + signal.dispatchEvent({type: 'abort'}); + } +} + +// In environments without AbortController (e.g. tests) +// replace it with a lightweight shim that only has the features we use. +export default enableCache + ? typeof AbortController !== 'undefined' + ? AbortController + : (AbortControllerShim: AbortController) + : (null: any); diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.new.js b/packages/react-reconciler/src/ReactFiberCacheComponent.new.js index 67588c3219a92..fc2b232d47ffe 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.new.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.new.js @@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes'; import {enableCache} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import AbortController from './ReactAbortController'; import {pushProvider, popProvider} from './ReactFiberNewContext.new'; import * as Scheduler from 'scheduler'; diff --git a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js index e530619d41664..0b76584629c38 100644 --- a/packages/react-reconciler/src/ReactFiberCacheComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberCacheComponent.old.js @@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes'; import {enableCache} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import AbortController from './ReactAbortController'; import {pushProvider, popProvider} from './ReactFiberNewContext.old'; import * as Scheduler from 'scheduler'; diff --git a/scripts/jest/setupEnvironment.js b/scripts/jest/setupEnvironment.js index d2d510088c45e..2ba88b156169d 100644 --- a/scripts/jest/setupEnvironment.js +++ b/scripts/jest/setupEnvironment.js @@ -1,7 +1,5 @@ /* eslint-disable */ -const AbortController = require('abort-controller'); - const NODE_ENV = process.env.NODE_ENV; if (NODE_ENV !== 'development' && NODE_ENV !== 'production') { throw new Error('NODE_ENV must either be set to development or production.'); @@ -23,8 +21,6 @@ global.__EXPERIMENTAL__ = global.__VARIANT__ = !!process.env.VARIANT; -global.AbortController = AbortController; - if (typeof window !== 'undefined') { global.requestIdleCallback = function(callback) { return setTimeout(() => {