307 lines
8.8 KiB
JavaScript
307 lines
8.8 KiB
JavaScript
describe('StackClearer', function() {
|
|
it('works in an integrationy way', function(done) {
|
|
const { clearStack } = privateUnderTest.getStackClearer(
|
|
jasmineUnderTest.getGlobal()
|
|
);
|
|
|
|
clearStack(function() {
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('in Safari', function() {
|
|
usesQueueMicrotaskWithSetTimeout(function() {
|
|
return {
|
|
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'
|
|
},
|
|
// queueMicrotask should be used even though MessageChannel is present
|
|
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 } = privateUnderTest.getStackClearer(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() {
|
|
usesQueueMicrotaskWithSetTimeout(function() {
|
|
return {
|
|
navigator: {
|
|
userAgent:
|
|
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)'
|
|
},
|
|
// queueMicrotask should be used even though MessageChannel is present
|
|
MessageChannel: fakeMessageChannel
|
|
};
|
|
});
|
|
});
|
|
|
|
describe('in browsers other than Safari', function() {
|
|
usesMessageChannel(function() {
|
|
return {
|
|
navigator: {
|
|
// Chrome's user agent string contains "Safari" so it's a good test case
|
|
userAgent:
|
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
|
|
}
|
|
};
|
|
});
|
|
});
|
|
|
|
describe('in Node', function() {
|
|
usesQueueMicrotaskWithoutSetTimeout(function() {
|
|
return {
|
|
process: {
|
|
versions: {
|
|
node: '3.1415927'
|
|
}
|
|
}
|
|
};
|
|
});
|
|
});
|
|
|
|
function usesMessageChannel(makeGlobal) {
|
|
it('uses MessageChannel', function() {
|
|
const global = {
|
|
...makeGlobal(),
|
|
MessageChannel: fakeMessageChannel
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
let called = false;
|
|
|
|
clearStack(function() {
|
|
called = true;
|
|
});
|
|
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
it('uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU', function() {
|
|
const fakeChannel = fakeMessageChannel();
|
|
spyOn(fakeChannel.port2, 'postMessage');
|
|
const setTimeout = jasmine.createSpy('setTimeout');
|
|
const global = {
|
|
...makeGlobal(),
|
|
setTimeout,
|
|
MessageChannel: function() {
|
|
return fakeChannel;
|
|
}
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
|
|
for (let i = 0; i < 9; i++) {
|
|
clearStack(function() {});
|
|
}
|
|
|
|
expect(fakeChannel.port2.postMessage).toHaveBeenCalled();
|
|
expect(setTimeout).not.toHaveBeenCalled();
|
|
|
|
clearStack(function() {});
|
|
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(9);
|
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
|
|
clearStack(function() {});
|
|
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(10);
|
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('calls setTimeout when onmessage is called recursively', function() {
|
|
const setTimeout = jasmine.createSpy('setTimeout');
|
|
const global = {
|
|
...makeGlobal(),
|
|
setTimeout,
|
|
MessageChannel: fakeMessageChannel
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
const fn = jasmine.createSpy('second clearStack function');
|
|
|
|
clearStack(function() {
|
|
clearStack(fn);
|
|
});
|
|
|
|
expect(fn).not.toHaveBeenCalled();
|
|
expect(setTimeout).toHaveBeenCalledWith(fn, 0);
|
|
});
|
|
}
|
|
|
|
function usesQueueMicrotaskWithSetTimeout(makeGlobal) {
|
|
it('uses queueMicrotask', function() {
|
|
const global = {
|
|
...makeGlobal(),
|
|
queueMicrotask: function(fn) {
|
|
fn();
|
|
}
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
let called = false;
|
|
|
|
clearStack(function() {
|
|
called = true;
|
|
});
|
|
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
function hasSetTimeoutBehavior(configure) {
|
|
it('uses setTimeout instead of queueMicrotask every 10 calls', function() {
|
|
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
|
const setTimeout = jasmine.createSpy('setTimeout');
|
|
const global = {
|
|
...makeGlobal(),
|
|
queueMicrotask,
|
|
setTimeout
|
|
};
|
|
const stackClearer = privateUnderTest.getStackClearer(global);
|
|
|
|
if (configure) {
|
|
configure(stackClearer);
|
|
}
|
|
|
|
for (let i = 0; i < 9; i++) {
|
|
stackClearer.clearStack(function() {});
|
|
}
|
|
|
|
expect(queueMicrotask).toHaveBeenCalled();
|
|
expect(setTimeout).not.toHaveBeenCalled();
|
|
|
|
stackClearer.clearStack(function() {});
|
|
expect(queueMicrotask).toHaveBeenCalledTimes(9);
|
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
|
|
stackClearer.clearStack(function() {});
|
|
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
});
|
|
}
|
|
|
|
hasSetTimeoutBehavior();
|
|
|
|
describe('With yield strategy explicitly set to count', function() {
|
|
hasSetTimeoutBehavior(function(stackClearer) {
|
|
stackClearer.setSafariYieldStrategy('count');
|
|
});
|
|
});
|
|
|
|
describe('With yield strategy set to time', function() {
|
|
beforeEach(function() {
|
|
jasmine.clock().install();
|
|
jasmine.clock().mockDate();
|
|
});
|
|
|
|
afterEach(function() {
|
|
jasmine.clock().uninstall();
|
|
});
|
|
|
|
it('uses setTimeout instead of queueMicrotask every 25 milliseconds', function() {
|
|
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
|
const setTimeout = jasmine.createSpy('setTimeout');
|
|
const global = {
|
|
...makeGlobal(),
|
|
queueMicrotask,
|
|
setTimeout
|
|
};
|
|
const stackClearer = privateUnderTest.getStackClearer(global);
|
|
stackClearer.setSafariYieldStrategy('time');
|
|
|
|
// 10+ counts should not trigger a setTimeout if they happen fast enough
|
|
jasmine.clock().tick(24);
|
|
for (let i = 0; i < 11; i++) {
|
|
stackClearer.clearStack(function() {});
|
|
}
|
|
|
|
expect(queueMicrotask).toHaveBeenCalled();
|
|
expect(setTimeout).not.toHaveBeenCalled();
|
|
|
|
queueMicrotask.calls.reset();
|
|
jasmine.clock().tick(1);
|
|
stackClearer.clearStack(function() {});
|
|
expect(queueMicrotask).not.toHaveBeenCalled();
|
|
expect(setTimeout).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
}
|
|
|
|
function usesQueueMicrotaskWithoutSetTimeout(makeGlobal) {
|
|
it('uses queueMicrotask', function() {
|
|
const global = {
|
|
...makeGlobal(),
|
|
queueMicrotask: function(fn) {
|
|
fn();
|
|
}
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
let called = false;
|
|
|
|
clearStack(function() {
|
|
called = true;
|
|
});
|
|
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
it('does not use setTimeout', function() {
|
|
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
|
const setTimeout = jasmine.createSpy('setTimeout');
|
|
const global = {
|
|
...makeGlobal(),
|
|
queueMicrotask,
|
|
setTimeout
|
|
};
|
|
const { clearStack } = privateUnderTest.getStackClearer(global);
|
|
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
clearStack(function() {});
|
|
|
|
expect(queueMicrotask).toHaveBeenCalledTimes(10);
|
|
expect(setTimeout).not.toHaveBeenCalled();
|
|
});
|
|
}
|
|
|
|
function fakeMessageChannel() {
|
|
const channel = {
|
|
port1: {},
|
|
port2: {
|
|
postMessage: function() {
|
|
channel.port1.onmessage();
|
|
}
|
|
}
|
|
};
|
|
return channel;
|
|
}
|
|
});
|