| <!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> |