@samitouri / QOS-React-2 / commits / 2c1ac9fe1d

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;