diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 86d6ada7..6aaf5989 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -2876,7 +2876,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++; @@ -2885,7 +2886,7 @@ getJasmineRequireObj().clearStack = function(j$) { queueMicrotask(fn); } else { currentCallCount = 0; - setTimeout(fn); + unclampedSetTimeout(fn); } }; } @@ -2899,6 +2900,35 @@ 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 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(); let head = {}; @@ -2922,17 +2952,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); }; } 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() { diff --git a/src/core/ClearStack.js b/src/core/ClearStack.js index c8b2ed21..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); } }; } @@ -25,6 +26,35 @@ 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 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(); let head = {}; @@ -48,17 +78,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); }; }