Inline fbjs/lib/EventListener dependency (#11402)
* Drop fbjs/lib/EventListener from tests Assert on the underlying native event listener instead. This test file still needs to be rewritten in terms of public APIs. * Inline fbjs/lib/EventListener dependency We explicitly don't want to shim this and we don't use the return value. We can probably even drop the IE path now since we don't support it. Not sure if that'll be a true breaking change though. * Wrap event listeners and the callback passed to requestIdleCallback This is a FBism. This uses the injection model in ReactErrorUtils. This isn't technically going to used for errors but close enough. This really wants to be eager but we can't because of dynamic injection.
Sebastian Markbåge committed
Oct 31, 2017 at 11:23 UTC
2c1ac9fe1d124e474f0cfb24625930023b92b46b
4 files changed
+65
-23
packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js
+9
-13
@@ -9,7 +9,6 @@
9
10
'use strict';
11
12
-var EventListener;
12
var EventPluginHub;
13
var EventPluginRegistry;
14
var React;
@@ -57,7 +56,7 @@ describe('ReactBrowserEventEmitter', () => {
56
beforeEach(() => {
57
jest.resetModules();
58
LISTENER.mockClear();
60
- EventListener = require('fbjs/lib/EventListener');
59
+
60
// TODO: can we express this test with only public API?
61
EventPluginHub = require('events/EventPluginHub');
62
EventPluginRegistry = require('events/EventPluginRegistry');
@@ -374,35 +373,32 @@ describe('ReactBrowserEventEmitter', () => {
373
});
374
375
it('should listen to events only once', () => {
377
- spyOn(EventListener, 'listen');
376
+ spyOn(EventTarget.prototype, 'addEventListener');
377
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
378
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
380
- expect(EventListener.listen.calls.count()).toBe(1);
379
+ expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1);
380
});
381
382
it('should work with event plugins without dependencies', () => {
384
- spyOn(EventListener, 'listen');
383
+ spyOn(EventTarget.prototype, 'addEventListener');
384
385
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
386
388
- expect(EventListener.listen.calls.argsFor(0)[1]).toBe('click');
387
+ expect(EventTarget.prototype.addEventListener.calls.argsFor(0)[0]).toBe(
388
+ 'click',
389
+ );
390
});
391
392
it('should work with event plugins with dependencies', () => {
392
- spyOn(EventListener, 'listen');
393
- spyOn(EventListener, 'capture');
393
+ spyOn(EventTarget.prototype, 'addEventListener');
394
395
ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);
396
397
var setEventListeners = [];
398
- var listenCalls = EventListener.listen.calls.allArgs();
399
- var captureCalls = EventListener.capture.calls.allArgs();
398
+ var listenCalls = EventTarget.prototype.addEventListener.calls.allArgs();
399
for (var i = 0; i < listenCalls.length; i++) {
400
setEventListeners.push(listenCalls[i][1]);
401
}
403
- for (i = 0; i < captureCalls.length; i++) {
404
- setEventListeners.push(captureCalls[i][1]);
405
- }
402
403
var module = EventPluginRegistry.registrationNameModules[ON_CHANGE_KEY];
404
var dependencies = module.eventTypes.change.dependencies;
packages/react-dom/src/events/ReactDOMEventListener.js
+28
-8
@@ -8,9 +8,9 @@
8
'use strict';
9
10
var ReactGenericBatching = require('events/ReactGenericBatching');
11
+var ReactErrorUtils = require('shared/ReactErrorUtils');
12
var ReactFiberTreeReflection = require('shared/ReactFiberTreeReflection');
13
var ReactTypeOfWork = require('shared/ReactTypeOfWork');
13
-var EventListener = require('fbjs/lib/EventListener');
14
var {HostRoot} = ReactTypeOfWork;
15
16
var getEventTarget = require('./getEventTarget');
@@ -127,11 +127,18 @@ var ReactDOMEventListener = {
127
if (!element) {
128
return null;
129
}
130
- return EventListener.listen(
131
- element,
130
+ // TODO: Once we have static injection we should just wrap
131
+ // ReactDOMEventListener.dispatchEvent statically so we don't have to do
132
+ // it for every event type.
133
+ var callback = ReactErrorUtils.wrapEventListener(
134
handlerBaseName,
135
ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
136
);
137
+ if (element.addEventListener) {
138
+ element.addEventListener(handlerBaseName, callback, false);
139
+ } else if (element.attachEvent) {
140
+ element.attachEvent('on' + handlerBaseName, callback);
141
+ }
142
},
143
144
/**
@@ -148,11 +155,24 @@ var ReactDOMEventListener = {
155
if (!element) {
156
return null;
157
}
151
- return EventListener.capture(
152
- element,
153
- handlerBaseName,
154
- ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
155
- );
158
+ if (element.addEventListener) {
159
+ // TODO: Once we have static injection we should just wrap
160
+ // ReactDOMEventListener.dispatchEvent statically so we don't have to do
161
+ // it for every event type.
162
+ var callback = ReactErrorUtils.wrapEventListener(
163
+ handlerBaseName,
164
+ ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
165
+ );
166
+ element.addEventListener(handlerBaseName, callback, true);
167
+ } else {
168
+ if (__DEV__) {
169
+ console.error(
170
+ 'Attempted to listen to events during the capture phase on a ' +
171
+ 'browser that does not support the capture phase. Your application ' +
172
+ 'will not receive some events.',
173
+ );
174
+ }
175
+ }
176
},
177
178
dispatchEvent: function(topLevelType, nativeEvent) {
packages/react-reconciler/src/ReactFiberScheduler.js
+16
-2
@@ -19,6 +19,7 @@ var {
19
getStackAddendumByWorkInProgressFiber,
20
} = require('shared/ReactFiberComponentTreeHook');
21
var {
22
+ wrapEventListener,
23
invokeGuardedCallback,
24
hasCaughtError,
25
clearCaughtError,
@@ -1285,6 +1286,17 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1286
// TODO: Everything below this is written as if it has been lifted to the
1287
// renderers. I'll do this in a follow-up.
1288
1289
+ // Ensure performAsyncWork gets wrapped. This currently needs to be lazy because
1290
+ // we use dynamic injection. TODO: Make this eagerly wrapping this callback once
1291
+ // we have static injection.
1292
+ let hasCallbackBeenScheduled: boolean = false;
1293
+ function ensureCallbackWrapped() {
1294
+ if (!hasCallbackBeenScheduled) {
1295
+ performAsyncWork = wrapEventListener('reactAsyncWork', performAsyncWork);
1296
+ hasCallbackBeenScheduled = true;
1297
+ }
1298
+ }
1299
+
1300
// Linked-list of roots
1301
let firstScheduledRoot: FiberRoot | null = null;
1302
let lastScheduledRoot: FiberRoot | null = null;
@@ -1366,6 +1378,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1378
performWork(Sync, null);
1379
} else if (!isCallbackScheduled) {
1380
isCallbackScheduled = true;
1381
+ ensureCallbackWrapped();
1382
scheduleDeferredCallback(performAsyncWork);
1383
}
1384
}
@@ -1446,9 +1459,9 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1459
nextFlushedExpirationTime = highestPriorityWork;
1460
}
1461
1449
- function performAsyncWork(dl) {
1462
+ let performAsyncWork = function(dl) {
1463
performWork(NoWork, dl);
1451
- }
1464
+ };
1465
1466
function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
1467
deadline = dl;
@@ -1478,6 +1491,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1491
// If there's work left over, schedule a new callback.
1492
if (nextFlushedRoot !== null && !isCallbackScheduled) {
1493
isCallbackScheduled = true;
1494
+ ensureCallbackWrapped();
1495
scheduleDeferredCallback(performAsyncWork);
1496
}
1497
packages/shared/ReactErrorUtils.js
+12
@@ -27,6 +27,7 @@ const ReactErrorUtils = {
27
'Injected invokeGuardedCallback() must be a function.',
28
);
29
invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback;
30
+ wrapEventListener = injectedErrorUtils.wrapEventListener;
31
},
32
},
33
@@ -57,6 +58,13 @@ const ReactErrorUtils = {
58
invokeGuardedCallback.apply(ReactErrorUtils, arguments);
59
},
60
61
+ /**
62
+ * Wrap a callback in whatever logic it needs. Used in FB to polyfill Promises.
63
+ */
64
+ wrapEventListener: function<T>(name: string, callback: T): T {
65
+ return wrapEventListener(name, callback);
66
+ },
67
+
68
/**
69
* Same as invokeGuardedCallback, but instead of returning an error, it stores
70
* it in a global so it can be rethrown by `rethrowCaughtError` later.
@@ -116,6 +124,10 @@ const ReactErrorUtils = {
124
},
125
};
126
127
+let wrapEventListener = function<T>(name: string, callback: T): T {
128
+ return callback;
129
+};
130
+
131
let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) {
132
ReactErrorUtils._hasCaughtError = false;
133
ReactErrorUtils._caughtError = null;