Repository: facebook/react
Pull request: Add fallback shim for AbortController

Adds a lightweight fallback shim for environments that don't have an AbortController (e.g. tests running in Node < 15).

Changed files:
filename: packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
patch: @@ -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;

filename: packages/react-reconciler/src/ReactFiberCacheComponent.new.js
patch: @@ -15,8 +15,29 @@ import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
 import {pushProvider, popProvider} from './ReactFiberNewContext.new';
 import * as Scheduler from 'scheduler';
 
+// In environments without AbortController (e.g. tests)
+// replace it with a lightweight shim that only has the features we use.
+const AbortControllerLocal = enableCache
+  ? typeof AbortController !== 'undefined'
+    ? AbortController
+    : (function AbortControllerShim() {
+        const listeners = [];
+        const signal = (this.signal = {
+          aborted: false,
+          addEventListener: (type, listener) => {
+            listeners.push(listener);
+          },
+        });
+
+        this.abort = () => {
+          signal.aborted = true;
+          listeners.forEach(listener => listener());
+        };
+      }: AbortController)
+  : (null: any);
+
 export type Cache = {|
-  controller: AbortController,
+  controller: AbortControllerLocal,
   data: Map<() => mixed, mixed>,
   refCount: number,
 |};
@@ -66,7 +87,7 @@ export function createCache(): Cache {
     return (null: any);
   }
   const cache: Cache = {
-    controller: new AbortController(),
+    controller: new AbortControllerLocal(),
     data: new Map(),
     refCount: 0,
   };

filename: packages/react-reconciler/src/ReactFiberCacheComponent.old.js
patch: @@ -15,8 +15,29 @@ import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
 import {pushProvider, popProvider} from './ReactFiberNewContext.old';
 import * as Scheduler from 'scheduler';
 
+// In environments without AbortController (e.g. tests)
+// replace it with a lightweight shim that only has the features we use.
+const AbortControllerLocal = enableCache
+  ? typeof AbortController !== 'undefined'
+    ? AbortController
+    : (function AbortControllerShim() {
+        const listeners = [];
+        const signal = (this.signal = {
+          aborted: false,
+          addEventListener: (type, listener) => {
+            listeners.push(listener);
+          },
+        });
+
+        this.abort = () => {
+          signal.aborted = true;
+          listeners.forEach(listener => listener());
+        };
+      }: AbortController)
+  : (null: any);
+
 export type Cache = {|
-  controller: AbortController,
+  controller: AbortControllerLocal,
   data: Map<() => mixed, mixed>,
   refCount: number,
 |};
@@ -66,7 +87,7 @@ export function createCache(): Cache {
     return (null: any);
   }
   const cache: Cache = {
-    controller: new AbortController(),
+    controller: new AbortControllerLocal(),
     data: new Map(),
     refCount: 0,
   };

filename: scripts/jest/setupEnvironment.js
patch: @@ -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(() => {