Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[scheduler] Post to MessageChannel instead of window #14234

Merged
merged 1 commit into from
Nov 14, 2018
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
23 changes: 17 additions & 6 deletions packages/scheduler/src/Scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -533,13 +533,14 @@ if (typeof window !== 'undefined' && window._schedMock) {
};

// We use the postMessage trick to defer idle work until after the repaint.
var port = null;
var messageKey =
Copy link
Collaborator

Choose a reason for hiding this comment

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

The overhead of this is no longer necessary in this mechanism since it's a unique channel.

'__reactIdleCallback$' +
Math.random()
.toString(36)
.slice(2);
var idleTick = function(event) {
if (event.source !== window || event.data !== messageKey) {
if (event.source !== port || event.data !== messageKey) {
return;
}

Expand Down Expand Up @@ -583,9 +584,6 @@ if (typeof window !== 'undefined' && window._schedMock) {
}
}
};
// Assumes that we have addEventListener in this environment. Might need
// something better for old IE.
window.addEventListener('message', idleTick, false);

var animationTick = function(rafTime) {
if (scheduledHostCallback !== null) {
Expand Down Expand Up @@ -629,7 +627,7 @@ if (typeof window !== 'undefined' && window._schedMock) {
frameDeadline = rafTime + activeFrameTime;
if (!isMessageEventScheduled) {
isMessageEventScheduled = true;
window.postMessage(messageKey, '*');
port.postMessage(messageKey, '*');
}
};

Expand All @@ -638,7 +636,7 @@ if (typeof window !== 'undefined' && window._schedMock) {
timeoutTime = absoluteTimeout;
if (isFlushingHostCallback || absoluteTimeout < 0) {
// Don't wait for the next frame. Continue working ASAP, in a new event.
window.postMessage(messageKey, '*');
port.postMessage(messageKey, '*');
} else if (!isAnimationFrameScheduled) {
// If rAF didn't already schedule one, we need to schedule a frame.
// TODO: If this rAF doesn't materialize because the browser throttles, we
Expand All @@ -649,6 +647,19 @@ if (typeof window !== 'undefined' && window._schedMock) {
}
};

if (typeof MessageChannel === 'function') {
// Use a MessageChannel, if support exists
var channel = new MessageChannel();
channel.port1.onmessage = idleTick;
port = channel.port2;
} else {
// Otherwise post a message to the window. This isn't ideal because message
// handlers will fire on every frame until the queue is empty, including
// some browser extensions.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need this fallback? IE9? Do we even support that anyway?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Idk, for some reason I thought FB did but I guess we don't any longer

window.addEventListener('message', idleTick, false);
port = window;
}

cancelHostCallback = function() {
scheduledHostCallback = null;
isMessageEventScheduled = false;
Expand Down
27 changes: 10 additions & 17 deletions packages/scheduler/src/__tests__/SchedulerDOM-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,33 +64,26 @@ describe('SchedulerDOM', () => {
let currentTime = 0;

beforeEach(() => {
// TODO pull this into helper method, reduce repetition.
// mock the browser APIs which are used in schedule:
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
// - calling 'window.postMessage' should actually fire postmessage handlers
// - Date.now should return the correct thing
// - test with native performance.now()
delete global.performance;
global.requestAnimationFrame = function(cb) {
return rAFCallbacks.push(() => {
cb(startOfLatestFrame);
});
};
const originalAddEventListener = global.addEventListener;
postMessageCallback = null;
postMessageEvents = [];
postMessageErrors = [];
global.addEventListener = function(eventName, callback, useCapture) {
if (eventName === 'message') {
postMessageCallback = callback;
} else {
originalAddEventListener(eventName, callback, useCapture);
}
const port1 = {};
const port2 = {
postMessage(messageKey) {
const postMessageEvent = {source: port2, data: messageKey};
postMessageEvents.push(postMessageEvent);
},
};
global.postMessage = function(messageKey, targetOrigin) {
const postMessageEvent = {source: window, data: messageKey};
postMessageEvents.push(postMessageEvent);
global.MessageChannel = function MessageChannel() {
this.port1 = port1;
this.port2 = port2;
};
postMessageCallback = event => port1.onmessage(event);
global.Date.now = function() {
return currentTime;
};
Expand Down