Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions packages/react-dom/src/__tests__/ReactBrowserEventEmitter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

'use strict';

var EventListener;
var EventPluginHub;
var EventPluginRegistry;
var React;
Expand Down Expand Up @@ -57,7 +56,7 @@ describe('ReactBrowserEventEmitter', () => {
beforeEach(() => {
jest.resetModules();
LISTENER.mockClear();
EventListener = require('fbjs/lib/EventListener');

// TODO: can we express this test with only public API?
EventPluginHub = require('events/EventPluginHub');
EventPluginRegistry = require('events/EventPluginRegistry');
Expand Down Expand Up @@ -374,35 +373,32 @@ describe('ReactBrowserEventEmitter', () => {
});

it('should listen to events only once', () => {
spyOn(EventListener, 'listen');
spyOn(EventTarget.prototype, 'addEventListener');
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
expect(EventListener.listen.calls.count()).toBe(1);
expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1);
});

it('should work with event plugins without dependencies', () => {
spyOn(EventListener, 'listen');
spyOn(EventTarget.prototype, 'addEventListener');

ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);

expect(EventListener.listen.calls.argsFor(0)[1]).toBe('click');
expect(EventTarget.prototype.addEventListener.calls.argsFor(0)[0]).toBe(
'click',
);
});

it('should work with event plugins with dependencies', () => {
spyOn(EventListener, 'listen');
spyOn(EventListener, 'capture');
spyOn(EventTarget.prototype, 'addEventListener');

ReactBrowserEventEmitter.listenTo(ON_CHANGE_KEY, document);

var setEventListeners = [];
var listenCalls = EventListener.listen.calls.allArgs();
var captureCalls = EventListener.capture.calls.allArgs();
var listenCalls = EventTarget.prototype.addEventListener.calls.allArgs();
for (var i = 0; i < listenCalls.length; i++) {
setEventListeners.push(listenCalls[i][1]);
}
for (i = 0; i < captureCalls.length; i++) {
setEventListeners.push(captureCalls[i][1]);
}

var module = EventPluginRegistry.registrationNameModules[ON_CHANGE_KEY];
var dependencies = module.eventTypes.change.dependencies;
Expand Down
36 changes: 28 additions & 8 deletions packages/react-dom/src/events/ReactDOMEventListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
'use strict';

var ReactGenericBatching = require('events/ReactGenericBatching');
var ReactErrorUtils = require('shared/ReactErrorUtils');
var ReactFiberTreeReflection = require('shared/ReactFiberTreeReflection');
var ReactTypeOfWork = require('shared/ReactTypeOfWork');
var EventListener = require('fbjs/lib/EventListener');
var {HostRoot} = ReactTypeOfWork;

var getEventTarget = require('./getEventTarget');
Expand Down Expand Up @@ -127,11 +127,18 @@ var ReactDOMEventListener = {
if (!element) {
return null;
}
return EventListener.listen(
element,
// TODO: Once we have static injection we should just wrap
// ReactDOMEventListener.dispatchEvent statically so we don't have to do
// it for every event type.
var callback = ReactErrorUtils.wrapEventListener(
handlerBaseName,
ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
);
if (element.addEventListener) {
element.addEventListener(handlerBaseName, callback, false);
} else if (element.attachEvent) {
element.attachEvent('on' + handlerBaseName, callback);
}
},

/**
Expand All @@ -148,11 +155,24 @@ var ReactDOMEventListener = {
if (!element) {
return null;
}
return EventListener.capture(
element,
handlerBaseName,
ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
);
if (element.addEventListener) {
// TODO: Once we have static injection we should just wrap
// ReactDOMEventListener.dispatchEvent statically so we don't have to do
// it for every event type.
var callback = ReactErrorUtils.wrapEventListener(
handlerBaseName,
ReactDOMEventListener.dispatchEvent.bind(null, topLevelType),
);
element.addEventListener(handlerBaseName, callback, true);
} else {
if (__DEV__) {
console.error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning(false, ?

'Attempted to listen to events during the capture phase on a ' +
'browser that does not support the capture phase. Your application ' +
'will not receive some events.',
);
}
}
},

dispatchEvent: function(topLevelType, nativeEvent) {
Expand Down
18 changes: 16 additions & 2 deletions packages/react-reconciler/src/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var {
getStackAddendumByWorkInProgressFiber,
} = require('shared/ReactFiberComponentTreeHook');
var {
wrapEventListener,
invokeGuardedCallback,
hasCaughtError,
clearCaughtError,
Expand Down Expand Up @@ -1273,6 +1274,17 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
// TODO: Everything below this is written as if it has been lifted to the
// renderers. I'll do this in a follow-up.

// Ensure performAsyncWork gets wrapped. This currently needs to be lazy because
// we use dynamic injection. TODO: Make this eagerly wrapping this callback once
// we have static injection.
let hasCallbackBeenScheduled: boolean = false;
function ensureCallbackWrapped() {
if (!hasCallbackBeenScheduled) {
performAsyncWork = wrapEventListener('reactAsyncWork', performAsyncWork);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty unfortunate but we have to make these lazy. I also don't want to keep wrapping each individual call to rIC because this wrapper is pretty heavy.

hasCallbackBeenScheduled = true;
}
}

// Linked-list of roots
let firstScheduledRoot: FiberRoot | null = null;
let lastScheduledRoot: FiberRoot | null = null;
Expand Down Expand Up @@ -1354,6 +1366,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
performWork(Sync, null);
} else if (!isCallbackScheduled) {
isCallbackScheduled = true;
ensureCallbackWrapped();
scheduleDeferredCallback(performAsyncWork);
}
}
Expand Down Expand Up @@ -1434,9 +1447,9 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
nextFlushedExpirationTime = highestPriorityWork;
}

function performAsyncWork(dl) {
let performAsyncWork = function(dl) {
performWork(NoWork, dl);
}
};

function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
deadline = dl;
Expand Down Expand Up @@ -1466,6 +1479,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
// If there's work left over, schedule a new callback.
if (nextFlushedRoot !== null && !isCallbackScheduled) {
isCallbackScheduled = true;
ensureCallbackWrapped();
scheduleDeferredCallback(performAsyncWork);
}

Expand Down
12 changes: 12 additions & 0 deletions packages/shared/ReactErrorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const ReactErrorUtils = {
'Injected invokeGuardedCallback() must be a function.',
);
invokeGuardedCallback = injectedErrorUtils.invokeGuardedCallback;
wrapEventListener = injectedErrorUtils.wrapEventListener;
},
},

Expand Down Expand Up @@ -57,6 +58,13 @@ const ReactErrorUtils = {
invokeGuardedCallback.apply(ReactErrorUtils, arguments);
},

/**
* Wrap a callback in whatever logic it needs. Used in FB to polyfill Promises.
*/
wrapEventListener: function<T>(name: string, callback: T): T {
return wrapEventListener(name, callback);
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaearon I built this the same way as the other injection since they're related. Feel free to split them out when we move to static injection. At the very least it seems like we could move to something like what fbjs/lib/EventListener used to be.


/**
* Same as invokeGuardedCallback, but instead of returning an error, it stores
* it in a global so it can be rethrown by `rethrowCaughtError` later.
Expand Down Expand Up @@ -116,6 +124,10 @@ const ReactErrorUtils = {
},
};

let wrapEventListener = function<T>(name: string, callback: T): T {
return callback;
};

let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) {
ReactErrorUtils._hasCaughtError = false;
ReactErrorUtils._caughtError = null;
Expand Down