From bbebea0fa57217a59d958715f315c890ed2b0c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cs=C3=A1sz=C3=A1r?= Date: Sun, 1 Sep 2024 09:31:52 +0200 Subject: [PATCH 1/4] Refactor: extract postMessage --- src/core/ClearStack.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js index c8b2ed21..a9f645ed 100644 --- a/src/core/ClearStack.js +++ b/src/core/ClearStack.js @@ -25,6 +25,23 @@ getJasmineRequireObj().clearStack = function(j$) { } function messageChannelImpl(global) { + const { setTimeout } = global; + const postMessage = getPostMessage(global); + + let currentCallCount = 0; + return function clearStack(fn) { + currentCallCount++; + + if (currentCallCount < maxInlineCallCount) { + postMessage(fn); + } else { + currentCallCount = 0; + setTimeout(fn); + } + }; + } + + function getPostMessage(global) { const { MessageChannel, setTimeout } = global; const channel = new MessageChannel(); let head = {}; @@ -48,17 +65,9 @@ getJasmineRequireObj().clearStack = function(j$) { } }; - let currentCallCount = 0; - return function clearStack(fn) { - currentCallCount++; - - if (currentCallCount < maxInlineCallCount) { - tail = tail.next = { task: fn }; - channel.port2.postMessage(0); - } else { - currentCallCount = 0; - setTimeout(fn); - } + return function postMessage(fn) { + tail = tail.next = { task: fn }; + channel.port2.postMessage(0); }; } From 1c743566913f9b2451153de488775fccbf73b821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cs=C3=A1sz=C3=A1r?= Date: Sun, 1 Sep 2024 09:37:47 +0200 Subject: [PATCH 2/4] Add unclampedSetTimeout helper --- src/core/ClearStack.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js index a9f645ed..29ec3688 100644 --- a/src/core/ClearStack.js +++ b/src/core/ClearStack.js @@ -41,6 +41,18 @@ getJasmineRequireObj().clearStack = function(j$) { }; } + function getUnclampedSetTimeout(global) { + const { setTimeout } = global; + if (j$.util.isUndefined(global.MessageChannel)) return setTimeout; + + const postMessage = getPostMessage(global); + return function unclampedSetTimeout(fn) { + postMessage(function() { + setTimeout(fn); + }); + }; + } + function getPostMessage(global) { const { MessageChannel, setTimeout } = global; const channel = new MessageChannel(); From acc777c2678746681de37c06df617bd9f5f29f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cs=C3=A1sz=C3=A1r?= Date: Sun, 1 Sep 2024 09:41:41 +0200 Subject: [PATCH 3/4] Unclamp setTimeout in Safari Fixes #2008 wrapping setTimeout in postMessage is a trade-off between * slowdown due to postMessage (slow on Safari) * speedup due to no setTimeout clamping (can be severe on Safari) --- src/core/ClearStack.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js index 29ec3688..3ae237f0 100644 --- a/src/core/ClearStack.js +++ b/src/core/ClearStack.js @@ -2,7 +2,8 @@ getJasmineRequireObj().clearStack = function(j$) { const maxInlineCallCount = 10; function browserQueueMicrotaskImpl(global) { - const { setTimeout, queueMicrotask } = global; + const unclampedSetTimeout = getUnclampedSetTimeout(global); + const { queueMicrotask } = global; let currentCallCount = 0; return function clearStack(fn) { currentCallCount++; @@ -11,7 +12,7 @@ getJasmineRequireObj().clearStack = function(j$) { queueMicrotask(fn); } else { currentCallCount = 0; - setTimeout(fn); + unclampedSetTimeout(fn); } }; } From 35f16e812509826a2b94b282a6b1532073662bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cs=C3=A1sz=C3=A1r?= Date: Sun, 1 Sep 2024 10:22:16 +0200 Subject: [PATCH 4/4] Add test for unclamping setTimeout in Safari --- spec/core/ClearStackSpec.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/core/ClearStackSpec.js b/spec/core/ClearStackSpec.js index b60aee7e..2e846ffc 100644 --- a/spec/core/ClearStackSpec.js +++ b/spec/core/ClearStackSpec.js @@ -20,6 +20,32 @@ describe('ClearStack', function() { MessageChannel: fakeMessageChannel }; }); + + it('uses MessageChannel to reduce setTimeout clamping', function() { + const fakeChannel = fakeMessageChannel(); + spyOn(fakeChannel.port2, 'postMessage'); + const queueMicrotask = jasmine.createSpy('queueMicrotask'); + const global = { + navigator: { + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.0.8 (KHTML, like Gecko) Version/15.1 Safari/605.0.8' + }, + MessageChannel: function() { + return fakeChannel; + }, + queueMicrotask + }; + + const clearStack = jasmineUnderTest.getClearStack(global); + + for (let i = 0; i < 9; i++) clearStack(function() {}); + + expect(fakeChannel.port2.postMessage).not.toHaveBeenCalled(); + + clearStack(function() {}); + + expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(1); + }); }); describe("in WebKit (Playwright's build for Windows)", function() {