Skip to content

Commit 646ebad

Browse files
committed
Add fallback shim for AbortController
1 parent ebd7ff6 commit 646ebad

File tree

5 files changed

+140
-5
lines changed

5 files changed

+140
-5
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
// Polyfills for test environment
1313
global.ReadableStream = require('web-streams-polyfill/ponyfill/es6').ReadableStream;
1414
global.TextEncoder = require('util').TextEncoder;
15-
global.AbortController = require('abort-controller');
1615

1716
let React;
1817
let ReactDOMFizzServer;
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {enableCache} from 'shared/ReactFeatureFlags';
11+
12+
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
13+
14+
const signals: Map<
15+
AbortControllerShim,
16+
AbortSignalShim,
17+
> = (new PossiblyWeakMap(): any);
18+
19+
const abortedFlags: Map<
20+
AbortSignalShim,
21+
boolean,
22+
> = (new PossiblyWeakMap(): any);
23+
24+
type Listener = {
25+
listener: any => void,
26+
next: Listener | null,
27+
};
28+
29+
const listenersMap: Map<
30+
AbortSignalShim,
31+
Map<string, Listener>,
32+
> = (new PossiblyWeakMap(): any);
33+
34+
function getListeners(target): Map<string, Listener> {
35+
const listeners = listenersMap.get(target);
36+
if (listeners == null) {
37+
// eslint-disable-next-line react-internal/prod-error-codes
38+
throw new Error(
39+
'Listeners should already be registered. This is a bug in React.',
40+
);
41+
}
42+
return listeners;
43+
}
44+
45+
class AbortSignalShim {
46+
constructor() {
47+
listenersMap.set(this, new Map<string, any>());
48+
}
49+
get aborted() {
50+
return !!abortedFlags.get(this);
51+
}
52+
53+
addEventListener(type, listener) {
54+
const listeners = getListeners(this);
55+
let node = listeners.get(type);
56+
57+
const newNode = {listener, next: null};
58+
if (node === undefined) {
59+
listeners.set(type, newNode);
60+
return;
61+
}
62+
63+
let prev = null;
64+
while (node != null) {
65+
prev = node;
66+
node = node.next;
67+
}
68+
if (prev != null) {
69+
prev.next = newNode;
70+
}
71+
}
72+
73+
removeEventListener(eventName, listener) {
74+
const listeners = getListeners(this);
75+
let node = listeners.get(eventName);
76+
77+
let prev = null;
78+
while (node != null) {
79+
if (node.listener === listener) {
80+
if (prev !== null) {
81+
prev.next = node.next;
82+
} else if (node.next !== null) {
83+
listeners.set(eventName, node.next);
84+
} else {
85+
listeners.delete(eventName);
86+
}
87+
return;
88+
}
89+
90+
prev = node;
91+
node = node.next;
92+
}
93+
}
94+
95+
dispatchEvent(event) {
96+
const listeners = getListeners(this);
97+
let node = listeners.get(event.type);
98+
while (node != null) {
99+
node.listener(event);
100+
node = node.next;
101+
}
102+
}
103+
}
104+
105+
class AbortControllerShim {
106+
constructor() {
107+
const signal = new AbortSignalShim();
108+
abortedFlags.set(signal, false);
109+
signals.set(this, signal);
110+
}
111+
112+
get signal() {
113+
return signals.get(this);
114+
}
115+
116+
abort() {
117+
const signal = signals.get(this);
118+
if (signal == null) {
119+
// eslint-disable-next-line react-internal/prod-error-codes
120+
throw new Error(
121+
'Aborted signal was not registered. This is a bug in React.',
122+
);
123+
}
124+
if (abortedFlags.get(signal) !== false) {
125+
return;
126+
}
127+
abortedFlags.set(signal, true);
128+
signal.dispatchEvent({type: 'abort'});
129+
}
130+
}
131+
132+
// In environments without AbortController (e.g. tests)
133+
// replace it with a lightweight shim that only has the features we use.
134+
export default enableCache
135+
? typeof AbortController !== 'undefined'
136+
? AbortController
137+
: (AbortControllerShim: AbortController)
138+
: (null: any);

packages/react-reconciler/src/ReactFiberCacheComponent.new.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes';
1111

1212
import {enableCache} from 'shared/ReactFeatureFlags';
1313
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
14+
import AbortController from './ReactAbortController';
1415

1516
import {pushProvider, popProvider} from './ReactFiberNewContext.new';
1617
import * as Scheduler from 'scheduler';

packages/react-reconciler/src/ReactFiberCacheComponent.old.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {ReactContext} from 'shared/ReactTypes';
1111

1212
import {enableCache} from 'shared/ReactFeatureFlags';
1313
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
14+
import AbortController from './ReactAbortController';
1415

1516
import {pushProvider, popProvider} from './ReactFiberNewContext.old';
1617
import * as Scheduler from 'scheduler';

scripts/jest/setupEnvironment.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
/* eslint-disable */
22

3-
const AbortController = require('abort-controller');
4-
53
const NODE_ENV = process.env.NODE_ENV;
64
if (NODE_ENV !== 'development' && NODE_ENV !== 'production') {
75
throw new Error('NODE_ENV must either be set to development or production.');
@@ -23,8 +21,6 @@ global.__EXPERIMENTAL__ =
2321

2422
global.__VARIANT__ = !!process.env.VARIANT;
2523

26-
global.AbortController = AbortController;
27-
2824
if (typeof window !== 'undefined') {
2925
global.requestIdleCallback = function(callback) {
3026
return setTimeout(() => {

0 commit comments

Comments
 (0)