Web Inspector: capture async stack trace when workers/main context posts a message
authormattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 16 Aug 2017 23:15:32 +0000 (23:15 +0000)
committermattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 16 Aug 2017 23:15:32 +0000 (23:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167084
<rdar://problem/30033673>

Reviewed by Brian Burg.

Source/JavaScriptCore:

* inspector/agents/InspectorDebuggerAgent.h:
Add `PostMessage` async call type.

Source/WebCore:

Add instrumentation to DOMWindow to support showing asynchronous
stack traces when the debugger pauses in a MessageEvent handler.

Test: inspector/debugger/async-stack-trace.html

* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::didPostMessageImpl):
(WebCore::InspectorInstrumentation::didFailPostMessageImpl):
(WebCore::InspectorInstrumentation::willDispatchPostMessageImpl):
(WebCore::InspectorInstrumentation::didDispatchPostMessageImpl):

* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::didPostMessage):
(WebCore::InspectorInstrumentation::didFailPostMessage):
(WebCore::InspectorInstrumentation::willDispatchPostMessage):
(WebCore::InspectorInstrumentation::didDispatchPostMessage):

* inspector/PageDebuggerAgent.cpp:
(WebCore::PageDebuggerAgent::didClearAsyncStackTraceData):
(WebCore::PageDebuggerAgent::didPostMessage):
(WebCore::PageDebuggerAgent::didFailPostMessage):
(WebCore::PageDebuggerAgent::willDispatchPostMessage):
(WebCore::PageDebuggerAgent::didDispatchPostMessage):
* inspector/PageDebuggerAgent.h:

* page/DOMWindow.cpp:
(WebCore::DOMWindow::postMessage):
(WebCore::DOMWindow::postMessageTimerFired):

LayoutTests:

Add a test to check for asynchronous stack trace data when the debugger
pauses inside a MessageEvent handler.

* inspector/debugger/async-stack-trace-expected.txt:
* inspector/debugger/async-stack-trace.html:
* inspector/debugger/resources/postMessage-echo.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220815 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/async-stack-trace-expected.txt
LayoutTests/inspector/debugger/async-stack-trace.html
LayoutTests/inspector/debugger/resources/postMessage-echo.html [new file with mode: 0644]
LayoutTests/inspector/dom-debugger/dom-breakpoints.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/WebCore/ChangeLog
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/PageDebuggerAgent.cpp
Source/WebCore/inspector/PageDebuggerAgent.h
Source/WebCore/page/DOMWindow.cpp

index 03461aa..6c45c1a 100644 (file)
@@ -1,3 +1,18 @@
+2017-08-16  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture async stack trace when workers/main context posts a message
+        https://bugs.webkit.org/show_bug.cgi?id=167084
+        <rdar://problem/30033673>
+
+        Reviewed by Brian Burg.
+
+        Add a test to check for asynchronous stack trace data when the debugger
+        pauses inside a MessageEvent handler.
+
+        * inspector/debugger/async-stack-trace-expected.txt:
+        * inspector/debugger/async-stack-trace.html:
+        * inspector/debugger/resources/postMessage-echo.html: Added.
+
 2017-08-16  Sam Weinig  <sam@webkit.org>
 
         [WebIDL] Remove the need for JSSubtleCryptoCustom.cpp
index 6d42ded..003fbf4 100644 (file)
@@ -1,6 +1,9 @@
+CONSOLE MESSAGE: line 55: Unable to post message to http://example.com. Recipient has origin .
+
 Tests for async stack traces.
 
 
+
 == Running test suite: AsyncStackTrace
 -- Running test case: CheckAsyncStackTrace.RequestAnimationFrame
 PAUSE #1
@@ -76,6 +79,21 @@ ASYNC CALL STACK:
 4: --- addEventListener ---
 5: [F] testAddEventListener
 6: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.PostMessage
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+1: [F] postMessageFired
+ASYNC CALL STACK:
+2: --- postMessage ---
+3: [F] messageReceived
+4: --- postMessage ---
+5: [F] testPostMessage
+6: [P] Global Code
+
+-- Running test case: ShouldNotPauseForFailedPostMessage
+PASS: Should not pause for failed post message.
 -- Running test setup.
 Save DebuggerManager.asyncStackTraceDepth
 
index 4829403..13d83a1 100644 (file)
@@ -50,6 +50,23 @@ function testAddEventListener() {
     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);
@@ -79,16 +96,17 @@ function test()
             name: `CheckAsyncStackTrace.${name}`,
             test(resolve, reject) {
                 let pauseCount = 0;
-                function handlePaused() {
+                function debuggerPaused() {
                     InspectorTest.log(`PAUSE #${++pauseCount}`);
                     logActiveStackTrace();
                     WI.debuggerManager.resume();
                 }
 
-                WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, handlePaused);
+                WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
 
-                InspectorTest.singleFireEventListener("AfterTestFunction", () => {
-                    WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, handlePaused);
+                InspectorTest.awaitEvent("AfterTestFunction")
+                .then(() => {
+                    WI.debuggerManager.removeEventListener(WI.DebuggerManager.Event.Paused, debuggerPaused);
                     resolve();
                 });
 
@@ -103,6 +121,29 @@ function test()
     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");
@@ -190,5 +231,6 @@ function test()
 </head>
 <body onload="runTest()">
 <p>Tests for async stack traces.</p>
+<iframe id="postMessageTestFrame" src="resources/postMessage-echo.html"></iframe>
 </body>
 </html>
diff --git a/LayoutTests/inspector/debugger/resources/postMessage-echo.html b/LayoutTests/inspector/debugger/resources/postMessage-echo.html
new file mode 100644 (file)
index 0000000..2982c86
--- /dev/null
@@ -0,0 +1,6 @@
+<script>
+window.addEventListener("message", function messageReceived(event) {
+    let echo = `iFrame postMessage echo: ${event.data}`;
+    parent.postMessage(echo, "*");
+});
+</script>
index 4c53099..0b129ac 100644 (file)
@@ -36,6 +36,17 @@ function test()
         resolve();
     }
 
+    function rejectOnPause() {
+        return new Promise((resolve, reject) => {
+            WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused)
+            .then((event) => {
+                WI.debuggerManager.resume();
+                InspectorTest.fail("Should not pause.");
+                reject();
+            });
+        });
+    }
+
     function awaitEvaluateInPage(expression) {
         return new Promise((resolve, reject) => {
             InspectorTest.log("Wait for evaluate in page to return.");
@@ -48,17 +59,6 @@ function test()
         });
     }
 
-    function rejectOnPause() {
-        return new Promise((resolve, reject) => {
-            WI.debuggerManager.awaitEvent(WI.DebuggerManager.Event.Paused)
-            .then((event) => {
-                WI.debuggerManager.resume();
-                InspectorTest.fail("Should not pause.");
-                reject();
-            });
-        });
-    }
-
     function awaitQuerySelector(selector) {
         return new Promise((resolve, reject) => {
             WI.domTreeManager.requestDocument((documentNode) => {
index 03e9d3d..8353ef3 100644 (file)
@@ -1,3 +1,14 @@
+2017-08-16  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture async stack trace when workers/main context posts a message
+        https://bugs.webkit.org/show_bug.cgi?id=167084
+        <rdar://problem/30033673>
+
+        Reviewed by Brian Burg.
+
+        * inspector/agents/InspectorDebuggerAgent.h:
+        Add `PostMessage` async call type.
+
 2017-08-16  Mark Lam  <mark.lam@apple.com>
 
         Enhance MacroAssembler::probe() to support an initializeStackFunction callback.
index 0b073f5..2292dbb 100644 (file)
@@ -94,6 +94,7 @@ public:
     enum class AsyncCallType {
         DOMTimer,
         EventListener,
+        PostMessage,
         RequestAnimationFrame,
     };
 
index 01a186b..28a3a10 100644 (file)
@@ -1,3 +1,40 @@
+2017-08-16  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: capture async stack trace when workers/main context posts a message
+        https://bugs.webkit.org/show_bug.cgi?id=167084
+        <rdar://problem/30033673>
+
+        Reviewed by Brian Burg.
+
+        Add instrumentation to DOMWindow to support showing asynchronous
+        stack traces when the debugger pauses in a MessageEvent handler.
+
+        Test: inspector/debugger/async-stack-trace.html
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::didPostMessageImpl):
+        (WebCore::InspectorInstrumentation::didFailPostMessageImpl):
+        (WebCore::InspectorInstrumentation::willDispatchPostMessageImpl):
+        (WebCore::InspectorInstrumentation::didDispatchPostMessageImpl):
+
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::didPostMessage):
+        (WebCore::InspectorInstrumentation::didFailPostMessage):
+        (WebCore::InspectorInstrumentation::willDispatchPostMessage):
+        (WebCore::InspectorInstrumentation::didDispatchPostMessage):
+
+        * inspector/PageDebuggerAgent.cpp:
+        (WebCore::PageDebuggerAgent::didClearAsyncStackTraceData):
+        (WebCore::PageDebuggerAgent::didPostMessage):
+        (WebCore::PageDebuggerAgent::didFailPostMessage):
+        (WebCore::PageDebuggerAgent::willDispatchPostMessage):
+        (WebCore::PageDebuggerAgent::didDispatchPostMessage):
+        * inspector/PageDebuggerAgent.h:
+
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::postMessage):
+        (WebCore::DOMWindow::postMessageTimerFired):
+
 2017-08-16  Timothy Horton  <timothy_horton@apple.com>
 
         Try to fix the build
index 1a37670..13569b5 100644 (file)
@@ -363,6 +363,30 @@ void InspectorInstrumentation::willRemoveEventListenerImpl(InstrumentingAgents&
         pageDebuggerAgent->willRemoveEventListener(target, eventType, listener, capture);
 }
 
+void InspectorInstrumentation::didPostMessageImpl(InstrumentingAgents& instrumentingAgents, const TimerBase& timer, JSC::ExecState& state)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->didPostMessage(timer, state);
+}
+
+void InspectorInstrumentation::didFailPostMessageImpl(InstrumentingAgents& instrumentingAgents, const TimerBase& timer)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->didFailPostMessage(timer);
+}
+
+void InspectorInstrumentation::willDispatchPostMessageImpl(InstrumentingAgents& instrumentingAgents, const TimerBase& timer)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->willDispatchPostMessage(timer);
+}
+
+void InspectorInstrumentation::didDispatchPostMessageImpl(InstrumentingAgents& instrumentingAgents, const TimerBase& timer)
+{
+    if (PageDebuggerAgent* pageDebuggerAgent = instrumentingAgents.pageDebuggerAgent())
+        pageDebuggerAgent->didDispatchPostMessage(timer);
+}
+
 InspectorInstrumentationCookie InspectorInstrumentation::willCallFunctionImpl(InstrumentingAgents& instrumentingAgents, const String& scriptName, int scriptLine, ScriptExecutionContext* context)
 {
     int timelineAgentId = 0;
index 49e099d..d94a2b2 100644 (file)
@@ -85,6 +85,7 @@ class ResourceResponse;
 class ScriptExecutionContext;
 class SecurityOrigin;
 class ShadowRoot;
+class TimerBase;
 class URL;
 #if ENABLE(WEBGL)
 class WebGLProgram;
@@ -136,6 +137,11 @@ public:
     static void didInstallTimer(ScriptExecutionContext&, int timerId, Seconds timeout, bool singleShot);
     static void didRemoveTimer(ScriptExecutionContext&, int timerId);
 
+    static void didPostMessage(Frame&, TimerBase&, JSC::ExecState&);
+    static void didFailPostMessage(Frame&, TimerBase&);
+    static void willDispatchPostMessage(Frame&, TimerBase&);
+    static void didDispatchPostMessage(Frame&, TimerBase&);
+
     static InspectorInstrumentationCookie willCallFunction(ScriptExecutionContext*, const String& scriptName, int scriptLine);
     static void didCallFunction(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
     static void didAddEventListener(EventTarget&, const AtomicString& eventType);
@@ -302,6 +308,11 @@ private:
     static void didInstallTimerImpl(InstrumentingAgents&, int timerId, Seconds timeout, bool singleShot, ScriptExecutionContext&);
     static void didRemoveTimerImpl(InstrumentingAgents&, int timerId, ScriptExecutionContext&);
 
+    static void didPostMessageImpl(InstrumentingAgents&, const TimerBase&, JSC::ExecState&);
+    static void didFailPostMessageImpl(InstrumentingAgents&, const TimerBase&);
+    static void willDispatchPostMessageImpl(InstrumentingAgents&, const TimerBase&);
+    static void didDispatchPostMessageImpl(InstrumentingAgents&, const TimerBase&);
+
     static InspectorInstrumentationCookie willCallFunctionImpl(InstrumentingAgents&, const String& scriptName, int scriptLine, ScriptExecutionContext*);
     static void didCallFunctionImpl(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
     static void didAddEventListenerImpl(InstrumentingAgents&, EventTarget&, const AtomicString& eventType);
@@ -669,6 +680,34 @@ inline void InspectorInstrumentation::willRemoveEventListener(EventTarget& targe
         willRemoveEventListenerImpl(*instrumentingAgents, target, eventType, listener, capture);
 }
 
+inline void InspectorInstrumentation::didPostMessage(Frame& frame, TimerBase& timer, JSC::ExecState& state)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        didPostMessageImpl(*instrumentingAgents, timer, state);
+}
+
+inline void InspectorInstrumentation::didFailPostMessage(Frame& frame, TimerBase& timer)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        didFailPostMessageImpl(*instrumentingAgents, timer);
+}
+
+inline void InspectorInstrumentation::willDispatchPostMessage(Frame& frame, TimerBase& timer)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        willDispatchPostMessageImpl(*instrumentingAgents, timer);
+}
+
+inline void InspectorInstrumentation::didDispatchPostMessage(Frame& frame, TimerBase& timer)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForFrame(frame))
+        didDispatchPostMessageImpl(*instrumentingAgents, timer);
+}
+
 inline InspectorInstrumentationCookie InspectorInstrumentation::willCallFunction(ScriptExecutionContext* context, const String& scriptName, int scriptLine)
 {
     FAST_RETURN_IF_NO_FRONTENDS(InspectorInstrumentationCookie());
index 6a06ba3..09ba8dd 100644 (file)
@@ -45,6 +45,7 @@
 #include "PageScriptDebugServer.h"
 #include "ScriptExecutionContext.h"
 #include "ScriptState.h"
+#include "Timer.h"
 #include <inspector/InjectedScript.h>
 #include <inspector/InjectedScriptManager.h>
 #include <inspector/ScriptCallStack.h>
@@ -99,7 +100,9 @@ String PageDebuggerAgent::sourceMapURLForScript(const Script& script)
 void PageDebuggerAgent::didClearAsyncStackTraceData()
 {
     m_registeredEventListeners.clear();
+    m_postMessageTimers.clear();
     m_nextEventListenerIdentifier = 1;
+    m_nextPostMessageIdentifier = 1;
 }
 
 void PageDebuggerAgent::muteConsole()
@@ -227,4 +230,51 @@ void PageDebuggerAgent::didCancelAnimationFrame(int callbackId)
     didCancelAsyncCall(InspectorDebuggerAgent::AsyncCallType::RequestAnimationFrame, callbackId);
 }
 
+void PageDebuggerAgent::didPostMessage(const TimerBase& timer, JSC::ExecState& state)
+{
+    if (!breakpointsActive())
+        return;
+
+    if (m_postMessageTimers.contains(&timer)) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    int postMessageIdentifier = m_nextPostMessageIdentifier++;
+    m_postMessageTimers.set(&timer, postMessageIdentifier);
+
+    didScheduleAsyncCall(&state, InspectorDebuggerAgent::AsyncCallType::PostMessage, postMessageIdentifier, true);
+}
+
+void PageDebuggerAgent::didFailPostMessage(const TimerBase& timer)
+{
+    auto it = m_postMessageTimers.find(&timer);
+    if (it == m_postMessageTimers.end())
+        return;
+
+    didCancelAsyncCall(InspectorDebuggerAgent::AsyncCallType::PostMessage, it->value);
+
+    m_postMessageTimers.remove(it);
+}
+
+void PageDebuggerAgent::willDispatchPostMessage(const TimerBase& timer)
+{
+    auto it = m_postMessageTimers.find(&timer);
+    if (it == m_postMessageTimers.end())
+        return;
+
+    willDispatchAsyncCall(InspectorDebuggerAgent::AsyncCallType::PostMessage, it->value);
+}
+
+void PageDebuggerAgent::didDispatchPostMessage(const TimerBase& timer)
+{
+    auto it = m_postMessageTimers.find(&timer);
+    if (it == m_postMessageTimers.end())
+        return;
+
+    didDispatchAsyncCall();
+
+    m_postMessageTimers.remove(it);
+}
+
 } // namespace WebCore
index 166f1dd..671b3a4 100644 (file)
@@ -42,6 +42,7 @@ class InspectorOverlay;
 class InspectorPageAgent;
 class Page;
 class RegisteredEventListener;
+class TimerBase;
 
 class PageDebuggerAgent final : public WebDebuggerAgent {
     WTF_MAKE_NONCOPYABLE(PageDebuggerAgent);
@@ -64,6 +65,11 @@ public:
     void willRemoveEventListener(EventTarget&, const AtomicString& eventType, EventListener&, bool capture);
     void willHandleEvent(const RegisteredEventListener&);
 
+    void didPostMessage(const TimerBase&, JSC::ExecState&);
+    void didFailPostMessage(const TimerBase&);
+    void willDispatchPostMessage(const TimerBase&);
+    void didDispatchPostMessage(const TimerBase&);
+
 protected:
     void enable() override;
     void disable(bool isBeingDestroyed) override;
@@ -87,7 +93,9 @@ private:
     InspectorOverlay* m_overlay { nullptr };
 
     HashMap<const RegisteredEventListener*, int> m_registeredEventListeners;
+    HashMap<const TimerBase*, int> m_postMessageTimers;
     int m_nextEventListenerIdentifier { 1 };
+    int m_nextPostMessageIdentifier { 1 };
 };
 
 } // namespace WebCore
index a64f08a..017eb24 100644 (file)
@@ -956,6 +956,8 @@ ExceptionOr<void> DOMWindow::postMessage(JSC::ExecState& state, DOMWindow& incum
     auto* timer = new PostMessageTimer(*this, message.releaseReturnValue(), sourceOrigin, incumbentWindow, channels.releaseReturnValue(), WTFMove(target), WTFMove(stackTrace));
     timer->startOneShot(0_s);
 
+    InspectorInstrumentation::didPostMessage(*m_frame, *timer, state);
+
     return { };
 }
 
@@ -974,11 +976,17 @@ void DOMWindow::postMessageTimerFired(PostMessageTimer& timer)
                 else
                     pageConsole->addMessage(MessageSource::Security, MessageLevel::Error, message);
             }
+
+            InspectorInstrumentation::didFailPostMessage(*m_frame, timer);
             return;
         }
     }
 
+    InspectorInstrumentation::willDispatchPostMessage(*m_frame, timer);
+
     dispatchEvent(timer.event(*document()));
+
+    InspectorInstrumentation::didDispatchPostMessage(*m_frame, timer);
 }
 
 DOMSelection* DOMWindow::getSelection()