Skip to content

Commit 1736f33

Browse files
committed
[scheduler] Post to MessageChannel instead of window
Scheduler needs to schedule a task that fires after paint. To do this, it currently posts a message event to `window`. This happens on every frame until the queue is empty. An unfortunate consequence is that every other message event handler also gets called on every frame; even if they exit immediately, this adds up to significant per-frame overhead. Instead, we'll create a MessageChannel and post to that, with a fallback to the old behavior if MessageChannel does not exist.
1 parent d7fd679 commit 1736f33

File tree

2 files changed

+27
-16
lines changed

2 files changed

+27
-16
lines changed

packages/scheduler/src/Scheduler.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,14 @@ if (typeof window !== 'undefined' && window._schedMock) {
533533
};
534534

535535
// We use the postMessage trick to defer idle work until after the repaint.
536+
var port = null;
536537
var messageKey =
537538
'__reactIdleCallback$' +
538539
Math.random()
539540
.toString(36)
540541
.slice(2);
541542
var idleTick = function(event) {
542-
if (event.source !== window || event.data !== messageKey) {
543+
if (event.source !== port || event.data !== messageKey) {
543544
return;
544545
}
545546

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

590588
var animationTick = function(rafTime) {
591589
if (scheduledHostCallback !== null) {
@@ -629,7 +627,7 @@ if (typeof window !== 'undefined' && window._schedMock) {
629627
frameDeadline = rafTime + activeFrameTime;
630628
if (!isMessageEventScheduled) {
631629
isMessageEventScheduled = true;
632-
window.postMessage(messageKey, '*');
630+
port.postMessage(messageKey, '*');
633631
}
634632
};
635633

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

650+
if (typeof MessageChannel === 'function') {
651+
// Use a MessageChannel, if support exists
652+
var channel = new MessageChannel();
653+
channel.port1.onmessage = idleTick;
654+
port = channel.port2;
655+
} else {
656+
// Otherwise post a message to the window. This isn't ideal because
657+
// message handlers will on every frame until the queue is empty, including
658+
// some browser extensions.
659+
window.addEventListener('message', idleTick, false);
660+
port = window;
661+
}
662+
652663
cancelHostCallback = function() {
653664
scheduledHostCallback = null;
654665
isMessageEventScheduled = false;

packages/scheduler/src/__tests__/SchedulerDOM-test.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,21 @@ describe('SchedulerDOM', () => {
7676
cb(startOfLatestFrame);
7777
});
7878
};
79-
const originalAddEventListener = global.addEventListener;
8079
postMessageCallback = null;
8180
postMessageEvents = [];
8281
postMessageErrors = [];
83-
global.addEventListener = function(eventName, callback, useCapture) {
84-
if (eventName === 'message') {
85-
postMessageCallback = callback;
86-
} else {
87-
originalAddEventListener(eventName, callback, useCapture);
88-
}
82+
const port1 = {};
83+
const port2 = {
84+
postMessage(messageKey) {
85+
const postMessageEvent = {source: port2, data: messageKey};
86+
postMessageEvents.push(postMessageEvent);
87+
},
8988
};
90-
global.postMessage = function(messageKey, targetOrigin) {
91-
const postMessageEvent = {source: window, data: messageKey};
92-
postMessageEvents.push(postMessageEvent);
89+
global.MessageChannel = function MessageChannel() {
90+
this.port1 = port1;
91+
this.port2 = port2;
9392
};
93+
postMessageCallback = event => port1.onmessage(event);
9494
global.Date.now = function() {
9595
return currentTime;
9696
};

0 commit comments

Comments
 (0)