blob: 13d83a1b01dc6fd477403d3b9767941704222095 [file] [log] [blame]
<!doctype html>
<html>
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script src="resources/log-active-stack-trace.js"></script>
<script>
const timerDelay = 20;
function pauseThenFinishTest() {
debugger;
TestPage.dispatchEventToFrontend("AfterTestFunction");
}
function testRequestAnimationFrame() {
requestAnimationFrame(pauseThenFinishTest);
}
function testSetTimeout() {
setTimeout(pauseThenFinishTest, timerDelay);
}
function testChainedRequestAnimationFrame() {
requestAnimationFrame(testRequestAnimationFrame);
}
function testSetInterval(repeatCount) {
let pauses = 0;
let timerIdentifier = setInterval(function intervalFired() {
debugger;
if (++pauses === repeatCount) {
clearInterval(timerIdentifier);
TestPage.dispatchEventToFrontend("AfterTestFunction");
}
}, timerDelay);
}
function testReferenceCounting() {
let interval = setInterval(function intervalFired() {
clearInterval(interval);
setTimeout(pauseThenFinishTest, timerDelay * 2);
}, timerDelay);
}
function testAddEventListener() {
document.body.addEventListener("click", function clickFired() {
document.body.removeEventListener("click", clickFired);
pauseThenFinishTest();
});
document.body.click();
}
function testPostMessage(targetOrigin = "*") {
let childFrame = document.getElementById("postMessageTestFrame");
childFrame.contentWindow.postMessage("<message>", targetOrigin);
window.addEventListener("message", function postMessageFired() {
window.removeEventListener("message", postMessageFired);
pauseThenFinishTest();
});
}
function testFailPostMessage() {
testPostMessage("http://example.com");
setTimeout(() => {
TestPage.dispatchEventToFrontend("AfterTestFunction");
}, 0);
}
function recursiveCallThenTest(testFunction, depth) {
if (depth) {
recursiveCallThenTest(testFunction, depth - 1);
return;
}
testFunction();
}
function repeatingRequestAnimationFrame(repeatCount) {
let count = 0;
function repeat() {
if (count++ === repeatCount) {
pauseThenFinishTest();
return;
}
requestAnimationFrame(repeat);
}
requestAnimationFrame(repeat);
}
function test()
{
let suite = InspectorTest.createAsyncSuite("AsyncStackTrace");
function addSimpleTestCase(name, expression) {
suite.addTestCase({
name: `CheckAsyncStackTrace.${name}`,
test(resolve, reject) {
let pauseCount = 0;
function debuggerPaused() {
InspectorTest.log(`PAUSE #${++pauseCount}`);
logActiveStackTrace();
WI.debuggerManager.resume();
}
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
InspectorTest.awaitEvent("AfterTestFunction")
.then(() => {
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
resolve();
});
InspectorTest.evaluateInPage(expression);
}
});
}
addSimpleTestCase("RequestAnimationFrame", "testRequestAnimationFrame()");
addSimpleTestCase("SetTimeout", "testSetTimeout()");
addSimpleTestCase("SetInterval", "testSetInterval(3)");
addSimpleTestCase("ChainedRequestAnimationFrame", "testChainedRequestAnimationFrame()");
addSimpleTestCase("ReferenceCounting", "testReferenceCounting()");
addSimpleTestCase("AddEventListener", "testAddEventListener()");
addSimpleTestCase("PostMessage", "testPostMessage()");
suite.addTestCase({
name: "ShouldNotPauseForFailedPostMessage",
test(resolve, reject) {
function debuggerPaused() {
WI.debuggerManager.resume();
InspectorTest.fail("Should not pause.");
reject();
}
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
InspectorTest.awaitEvent("AfterTestFunction")
.then(() => {
WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
InspectorTest.pass("Should not pause for failed post message.");
resolve();
});
InspectorTest.evaluateInPage("testFailPostMessage()");
}
});
function setup(resolve) {
InspectorTest.log("Save DebuggerManager.asyncStackTraceDepth");
this.savedCallStackDepth = WI.debuggerManager.asyncStackTraceDepth;
resolve();
}
function teardown(resolve) {
InspectorTest.log("Restore DebuggerManager.asyncStackTraceDepth");
WI.debuggerManager.asyncStackTraceDepth = this.savedCallStackDepth;
resolve();
}
suite.addTestCase({
name: "AsyncStackTrace.DisableStackTrace",
setup,
teardown,
test(resolve, reject) {
WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused)
.then((event) => {
let stackTrace = getActiveStackTrace();
let asyncStackTrace = stackTrace.parentStackTrace;
InspectorTest.expectNull(asyncStackTrace, "Async stack trace should be null.");
WI.debuggerManager.resume().then(resolve, reject);
});
WI.debuggerManager.asyncStackTraceDepth = 0;
InspectorTest.evaluateInPage("testRequestAnimationFrame()");
}
});
function addTruncateTestCase(name, asyncStackTraceDepth) {
suite.addTestCase({
name: `AsyncStackTrace.${name}`,
setup,
teardown,
test(resolve, reject) {
// When repeatingRequestAnimationFrame calls rAF, the backend will store a call stack with length 2:
// one frame for the caller and one for the asynchronous boundary. As a result, the parity of
// Debugger.asyncStackTraceDepth determines whether the trace is truncated on a call stack boundary
// (even) or call frame boundary (odd). Since truncation doesn't remove individual call frames,
// the depth of the resulting stack trace may exceed the depth setting:
// S = { sšŸ¶, sšŸ·, ā€¦, sš‘˜ }
// T = truncate(S)
// depth(T) ā‰¤ d + depth(tš‘˜)
const framesPerCallStack = 2;
const expectedStackTraceDepth = asyncStackTraceDepth + (asyncStackTraceDepth % framesPerCallStack);
WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused)
.then((event) => {
let stackTrace = getActiveStackTrace();
InspectorTest.assert(stackTrace && stackTrace.callFrames);
if (!stackTrace || !stackTrace.callFrames)
reject();
let stackTraceDepth = 0;
while (stackTrace.parentStackTrace) {
InspectorTest.expectFalse(stackTrace.truncated, "Non-root StackTrace should not be truncated.");
stackTrace = stackTrace.parentStackTrace;
stackTraceDepth += stackTrace.callFrames.length;
}
InspectorTest.expectThat(stackTrace.truncated, "StackTrace root should be truncated.");
InspectorTest.expectEqual(stackTraceDepth, expectedStackTraceDepth, "StackTrace should be truncated to the nearest call stack.");
logActiveStackTrace();
WI.debuggerManager.resume().then(resolve, reject);
});
InspectorTest.log(`Set Debugger.asyncStackTraceDepth equal to ${asyncStackTraceDepth}`);
WI.debuggerManager.asyncStackTraceDepth = asyncStackTraceDepth;
let repeatCount = Math.floor(asyncStackTraceDepth / framesPerCallStack) + 1;
InspectorTest.evaluateInPage(`repeatingRequestAnimationFrame(${repeatCount})`);
}
});
}
addTruncateTestCase("ForceTruncationOnCallStackBoundary", 4);
addTruncateTestCase("ForceTruncationWithinCallStack", 5);
suite.runTestCasesAndFinish();
}
</script>
</head>
<body onload="runTest()">
<p>Tests for async stack traces.</p>
<iframe id="postMessageTestFrame" src="resources/postMessage-echo.html"></iframe>
</body>
</html>