Title: [209062] trunk
Revision
209062
Author
mattba...@apple.com
Date
2016-11-28 23:08:09 -0800 (Mon, 28 Nov 2016)

Log Message

Web Inspector: Debugger should have an option for showing asynchronous call stacks
https://bugs.webkit.org/show_bug.cgi?id=163230
<rdar://problem/28698683>

Reviewed by Joseph Pecoraro.

Source/_javascript_Core:

* inspector/ScriptCallFrame.cpp:
(Inspector::ScriptCallFrame::isNative):
Encapsulate check for native code source URL.

* inspector/ScriptCallFrame.h:
* inspector/ScriptCallStack.cpp:
(Inspector::ScriptCallStack::firstNonNativeCallFrame):
(Inspector::ScriptCallStack::buildInspectorArray):
* inspector/ScriptCallStack.h:
Replace use of Console::StackTrace with Array<Console::CallFrame>.

* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::disable):
(Inspector::InspectorDebuggerAgent::setAsyncStackTraceDepth):
Set number of async frames to store (including boundary frames).
A value of zero disables recording of async call stacks.

(Inspector::InspectorDebuggerAgent::buildAsyncStackTrace):
Helper function for building a linked list StackTraces.
(Inspector::InspectorDebuggerAgent::didScheduleAsyncCall):
Store a call stack for the script that scheduled the async call.
If the call repeats (e.g. setInterval), the starting reference count is
set to 1. This ensures that dereffing after dispatch won't clear the stack.
If another async call is currently being dispatched, increment the
AsyncCallData reference count for that call.

(Inspector::InspectorDebuggerAgent::didCancelAsyncCall):
Decrement the reference count for the canceled call.

(Inspector::InspectorDebuggerAgent::willDispatchAsyncCall):
Set the identifier for the async callback currently being dispatched,
so that if the debugger pauses during dispatch a stack trace can be
associated with the pause location. If an async call is already being
dispatched, which could be the case when a script schedules an async
call in a nested runloop, do nothing.

(Inspector::InspectorDebuggerAgent::didDispatchAsyncCall):
Decrement the reference count for the canceled call.
(Inspector::InspectorDebuggerAgent::didPause):
If a stored stack trace exists for this location, convert to a protocol
object and send to the frontend.

(Inspector::InspectorDebuggerAgent::didClearGlobalObject):
(Inspector::InspectorDebuggerAgent::clearAsyncStackTraceData):
(Inspector::InspectorDebuggerAgent::refAsyncCallData):
Increment AsyncCallData reference count.
(Inspector::InspectorDebuggerAgent::derefAsyncCallData):
Decrement AsyncCallData reference count. If zero, deref its parent
(if it exists) and remove the AsyncCallData entry.

* inspector/agents/InspectorDebuggerAgent.h:

* inspector/protocol/Console.json:
* inspector/protocol/Network.json:
Replace use of Console.StackTrace with array of Console.CallFrame.

* inspector/protocol/Debugger.json:
New protocol command and event data.

Source/WebCore:

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

* inspector/InspectorInstrumentation.cpp:
(WebCore::didScheduleAsyncCall):
Helper function used by by instrumentation hooks. Informs the debugger
agent that an asynchronous call was scheduled for the current script
execution state.

(WebCore::InspectorInstrumentation::didInstallTimerImpl):
(WebCore::InspectorInstrumentation::didRemoveTimerImpl):
(WebCore::InspectorInstrumentation::willFireTimerImpl):
(WebCore::InspectorInstrumentation::didFireTimerImpl):
Asynchronous stack trace plumbing for timers (setTimeout, setInterval).
(WebCore::InspectorInstrumentation::didRequestAnimationFrameImpl):
(WebCore::InspectorInstrumentation::didCancelAnimationFrameImpl):
(WebCore::InspectorInstrumentation::willFireAnimationFrameImpl):
(WebCore::InspectorInstrumentation::didFireAnimationFrameImpl):
Asynchronous stack trace plumbing for requestAnimationFrame.

Source/WebInspectorUI:

* Localizations/en.lproj/localizedStrings.js:
New string for generic async call stack boundary label: "(async)".

* UserInterface/Controllers/DebuggerManager.js:
Create async stack depth setting and set default depth.
(WebInspector.DebuggerManager.prototype.get asyncStackTraceDepth):
(WebInspector.DebuggerManager.prototype.set asyncStackTraceDepth):
Make async stack depth setting accessible to the frontend.
(WebInspector.DebuggerManager.prototype.initializeTarget):
Set async stack depth value on the target.
(WebInspector.DebuggerManager.prototype.debuggerDidPause):
Plumbing for the async stack trace payload.

* UserInterface/Models/ConsoleMessage.js:
(WebInspector.ConsoleMessage):
Updated for new StackTrace.fromPayload use.

* UserInterface/Models/DebuggerData.js:
(WebInspector.DebuggerData):
(WebInspector.DebuggerData.prototype.get asyncStackTrace):
(WebInspector.DebuggerData.prototype.updateForPause):
(WebInspector.DebuggerData.prototype.updateForResume):
More plumbing.

* UserInterface/Models/StackTrace.js:
Update frontend model for use as new protocol object Console.StackTrace,
which was previously an alias for a simple array of Console.CallFrames.

(WebInspector.StackTrace):
(WebInspector.StackTrace.fromPayload):
(WebInspector.StackTrace.fromString):
(WebInspector.StackTrace.prototype.get topCallFrameIsBoundary):
(WebInspector.StackTrace.prototype.get parentStackTrace):

* UserInterface/Protocol/DebuggerObserver.js:
(WebInspector.DebuggerObserver.prototype.paused):
More plumbing.

* UserInterface/Views/CallFrameTreeElement.css:
(.tree-outline .item.call-frame.async-boundary):
Use default cursor since boundary element is not selectable.
(.tree-outline .item.call-frame.async-boundary .icon):
(.tree-outline .item.call-frame.async-boundary::before,):
(.tree-outline .item.call-frame.async-boundary::after):
(.tree-outline .item.call-frame.async-boundary::before):
Dimmed text and divider line styles for boundary element.

* UserInterface/Views/CallFrameTreeElement.js:
(WebInspector.CallFrameTreeElement):
Add a flag denoting whether the call frame is an async call trace
boundary, and set styles accordingly.

* UserInterface/Views/DebuggerSidebarPanel.js:
Set async stack trace depth, if supported.
(WebInspector.DebuggerSidebarPanel.prototype._updateSingleThreadCallStacks):
Add call frames for async stack traces to the call stack TreeOutline.
(WebInspector.DebuggerSidebarPanel.prototype._treeSelectionDidChange):
Ensure that async call frames cannot become the active call frame.

* UserInterface/Views/Variables.css:
(:root):
Add --text-color-gray-medium, for dimmed text in async boundary element.

LayoutTests:

Add basic tests for async stack trace data included in Debugger.paused, and
check that requestAnimationFrame, setTimeout, and setInterval are supported.

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

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (209061 => 209062)


--- trunk/LayoutTests/ChangeLog	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/LayoutTests/ChangeLog	2016-11-29 07:08:09 UTC (rev 209062)
@@ -1,3 +1,17 @@
+2016-11-28  Matt Baker  <mattba...@apple.com>
+
+        Web Inspector: Debugger should have an option for showing asynchronous call stacks
+        https://bugs.webkit.org/show_bug.cgi?id=163230
+        <rdar://problem/28698683>
+
+        Reviewed by Joseph Pecoraro.
+
+        Add basic tests for async stack trace data included in Debugger.paused, and
+        check that requestAnimationFrame, setTimeout, and setInterval are supported.
+
+        * inspector/debugger/async-stack-trace-expected.txt: Added.
+        * inspector/debugger/async-stack-trace.html: Added.
+
 2016-11-28  Ryan Haddad  <ryanhad...@apple.com>
 
         Unreviewed, rolling out r209008.

Added: trunk/LayoutTests/inspector/debugger/async-stack-trace-expected.txt (0 => 209062)


--- trunk/LayoutTests/inspector/debugger/async-stack-trace-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/debugger/async-stack-trace-expected.txt	2016-11-29 07:08:09 UTC (rev 209062)
@@ -0,0 +1,74 @@
+Tests for async stack traces.
+
+
+== Running test suite: AsyncStackTrace
+-- Running test case: CheckAsyncStackTrace.RequestAnimationFrame
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+-- [N] requestAnimationFrame ----
+1: [F] testRequestAnimationFrame
+2: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.SetTimeout
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+-- [N] setTimeout ----
+1: [F] testSetTimeout
+2: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.SetInterval
+PAUSE #1
+CALL STACK:
+0: [F] intervalFired
+-- [N] setInterval ----
+1: [F] testSetInterval
+2: [P] Global Code
+PAUSE #2
+CALL STACK:
+0: [F] intervalFired
+-- [N] setInterval ----
+1: [F] testSetInterval
+2: [P] Global Code
+PAUSE #3
+CALL STACK:
+0: [F] intervalFired
+-- [N] setInterval ----
+1: [F] testSetInterval
+2: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.ChainedRequestAnimationFrame
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+-- [N] requestAnimationFrame ----
+1: [F] testRequestAnimationFrame
+-- [N] requestAnimationFrame ----
+2: [F] testChainedRequestAnimationFrame
+3: [P] Global Code
+
+-- Running test case: CheckAsyncStackTrace.ReferenceCounting
+PAUSE #1
+CALL STACK:
+0: [F] pauseThenFinishTest
+-- [N] setTimeout ----
+1: [F] intervalFired
+-- [N] setInterval ----
+2: [F] testReferenceCounting
+3: [P] Global Code
+-- Running test setup.
+Save DebuggerManager.asyncStackTraceDepth
+
+-- Running test case: AsyncStackTrace.DisableStackTrace
+PASS: Async stack trace should be null.
+-- Running test teardown.
+Restore DebuggerManager.asyncStackTraceDepth
+-- Running test setup.
+Save DebuggerManager.asyncStackTraceDepth
+
+-- Running test case: AsyncStackTrace.SetStackTraceDepth
+PASS: Number of call frames should be equal to the depth setting.
+-- Running test teardown.
+Restore DebuggerManager.asyncStackTraceDepth
+

Added: trunk/LayoutTests/inspector/debugger/async-stack-trace.html (0 => 209062)


--- trunk/LayoutTests/inspector/debugger/async-stack-trace.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/debugger/async-stack-trace.html	2016-11-29 07:08:09 UTC (rev 209062)
@@ -0,0 +1,189 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<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 recursiveCallThenTest(testFunction, depth) {
+    if (depth) {
+        recursiveCallThenTest(testFunction, depth - 1);
+        return;
+    }
+    testFunction();
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("AsyncStackTrace");
+
+    function activeTargetData() {
+        InspectorTest.assert(WebInspector.debuggerManager.activeCallFrame, "Active call frame should exist.");
+        if (!WebInspector.debuggerManager.activeCallFrame)
+            return null;
+
+        let targetData = WebInspector.debuggerManager.dataForTarget(WebInspector.debuggerManager.activeCallFrame.target);
+        InspectorTest.assert(targetData, "Data for active call frame target should exist.");
+        return targetData;
+    }
+
+    function logCallStack() {
+        function callFrameString(callFrame) {
+            let code = callFrame.nativeCode ? "N" : (callFrame.programCode ? "P" : "F");
+            return `[${code}] ${callFrame.functionName}`;
+        }
+
+        function logCallFrames(callFrames) {
+            for (let callFrame of callFrames) {
+                InspectorTest.log(`${callFrameIndex++}: ${callFrameString(callFrame)}`);
+                // Skip remaining call frames after the test harness entry point.
+                if (callFrame.programCode)
+                    break;
+            }
+        }
+
+        let {callFrames, asyncStackTrace} = activeTargetData();
+        InspectorTest.assert(callFrames);
+        InspectorTest.assert(asyncStackTrace);
+
+        let callFrameIndex = 0;
+        logCallFrames(callFrames);
+
+        while (asyncStackTrace) {
+            let callFrames = asyncStackTrace.callFrames;
+            let topCallFrameIsBoundary = asyncStackTrace.topCallFrameIsBoundary;
+            asyncStackTrace = asyncStackTrace.parentStackTrace;
+            if (!callFrames || !callFrames.length)
+                continue;
+
+            let boundaryLabel = topCallFrameIsBoundary ? callFrameString(callFrames.shift()) : "(async)";
+            InspectorTest.log(`-- ${boundaryLabel} ----`);
+            logCallFrames(callFrames);
+        }
+    }
+
+    function addSimpleTestCase(name, _expression_) {
+        suite.addTestCase({
+            name: `CheckAsyncStackTrace.${name}`,
+            test(resolve, reject) {
+                let pauseCount = 0;
+                function handlePaused() {
+                    InspectorTest.log(`PAUSE #${++pauseCount}`);
+                    InspectorTest.log("CALL STACK:");
+                    logCallStack();
+                    WebInspector.debuggerManager.resume();
+                }
+
+                WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, handlePaused);
+
+                InspectorTest.singleFireEventListener("AfterTestFunction", () => {
+                    WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.Paused, handlePaused);
+                    resolve();
+                });
+
+                InspectorTest.evaluateInPage(_expression_);
+            }
+        });
+    }
+
+    addSimpleTestCase("RequestAnimationFrame", "testRequestAnimationFrame()");
+    addSimpleTestCase("SetTimeout", "testSetTimeout()");
+    addSimpleTestCase("SetInterval", "testSetInterval(3)");
+    addSimpleTestCase("ChainedRequestAnimationFrame", "testChainedRequestAnimationFrame()");
+    addSimpleTestCase("ReferenceCounting", "testReferenceCounting()");
+
+    function setup(resolve) {
+        InspectorTest.log("Save DebuggerManager.asyncStackTraceDepth");
+        this.savedCallStackDepth = WebInspector.debuggerManager.asyncStackTraceDepth;
+        resolve();
+    }
+
+    function teardown(resolve) {
+        InspectorTest.log("Restore DebuggerManager.asyncStackTraceDepth");
+        WebInspector.debuggerManager.asyncStackTraceDepth = this.savedCallStackDepth;
+        resolve();
+    }
+
+    suite.addTestCase({
+        name: "AsyncStackTrace.DisableStackTrace",
+        setup,
+        teardown,
+        test(resolve, reject) {
+            WebInspector.debuggerManager.awaitEvent(WebInspector.DebuggerManager.Event.Paused)
+            .then((event) => {
+                let stackTrace = activeTargetData().asyncStackTrace;
+                InspectorTest.expectNull(stackTrace, "Async stack trace should be null.");
+                WebInspector.debuggerManager.resume().then(resolve, reject);
+            });
+
+            WebInspector.debuggerManager.asyncStackTraceDepth = 0;
+            InspectorTest.evaluateInPage("testRequestAnimationFrame()");
+        }
+    });
+
+    suite.addTestCase({
+        name: "AsyncStackTrace.SetStackTraceDepth",
+        setup,
+        teardown,
+        test(resolve, reject) {
+            WebInspector.debuggerManager.awaitEvent(WebInspector.DebuggerManager.Event.Paused)
+            .then((event) => {
+                let stackTrace = activeTargetData().asyncStackTrace;
+                InspectorTest.assert(stackTrace && stackTrace.callFrames);
+                if (!stackTrace || !stackTrace.callFrames)
+                    reject();
+
+                InspectorTest.expectEqual(stackTrace.callFrames.length, maxStackDepth, "Number of call frames should be equal to the depth setting.");
+                WebInspector.debuggerManager.resume().then(resolve, reject);
+            });
+
+            const maxStackDepth = 2;
+            const functionCallCount = maxStackDepth * 2;
+            WebInspector.debuggerManager.asyncStackTraceDepth = maxStackDepth;
+            InspectorTest.evaluateInPage(`recursiveCallThenTest(testRequestAnimationFrame, ${functionCallCount})`);
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Tests for async stack traces.</p>
+</body>
+</html>

Modified: trunk/Source/_javascript_Core/ChangeLog (209061 => 209062)


--- trunk/Source/_javascript_Core/ChangeLog	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-11-29 07:08:09 UTC (rev 209062)
@@ -1,3 +1,70 @@
+2016-11-28  Matt Baker  <mattba...@apple.com>
+
+        Web Inspector: Debugger should have an option for showing asynchronous call stacks
+        https://bugs.webkit.org/show_bug.cgi?id=163230
+        <rdar://problem/28698683>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/ScriptCallFrame.cpp:
+        (Inspector::ScriptCallFrame::isNative):
+        Encapsulate check for native code source URL.
+
+        * inspector/ScriptCallFrame.h:
+        * inspector/ScriptCallStack.cpp:
+        (Inspector::ScriptCallStack::firstNonNativeCallFrame):
+        (Inspector::ScriptCallStack::buildInspectorArray):
+        * inspector/ScriptCallStack.h:
+        Replace use of Console::StackTrace with Array<Console::CallFrame>.
+
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::disable):
+        (Inspector::InspectorDebuggerAgent::setAsyncStackTraceDepth):
+        Set number of async frames to store (including boundary frames).
+        A value of zero disables recording of async call stacks.
+
+        (Inspector::InspectorDebuggerAgent::buildAsyncStackTrace):
+        Helper function for building a linked list StackTraces.
+        (Inspector::InspectorDebuggerAgent::didScheduleAsyncCall):
+        Store a call stack for the script that scheduled the async call.
+        If the call repeats (e.g. setInterval), the starting reference count is
+        set to 1. This ensures that dereffing after dispatch won't clear the stack.
+        If another async call is currently being dispatched, increment the
+        AsyncCallData reference count for that call.
+
+        (Inspector::InspectorDebuggerAgent::didCancelAsyncCall):
+        Decrement the reference count for the canceled call.
+
+        (Inspector::InspectorDebuggerAgent::willDispatchAsyncCall):
+        Set the identifier for the async callback currently being dispatched,
+        so that if the debugger pauses during dispatch a stack trace can be
+        associated with the pause location. If an async call is already being
+        dispatched, which could be the case when a script schedules an async
+        call in a nested runloop, do nothing.
+
+        (Inspector::InspectorDebuggerAgent::didDispatchAsyncCall):
+        Decrement the reference count for the canceled call.
+        (Inspector::InspectorDebuggerAgent::didPause):
+        If a stored stack trace exists for this location, convert to a protocol
+        object and send to the frontend.
+
+        (Inspector::InspectorDebuggerAgent::didClearGlobalObject):
+        (Inspector::InspectorDebuggerAgent::clearAsyncStackTraceData):
+        (Inspector::InspectorDebuggerAgent::refAsyncCallData):
+        Increment AsyncCallData reference count.
+        (Inspector::InspectorDebuggerAgent::derefAsyncCallData):
+        Decrement AsyncCallData reference count. If zero, deref its parent
+        (if it exists) and remove the AsyncCallData entry.
+
+        * inspector/agents/InspectorDebuggerAgent.h:
+
+        * inspector/protocol/Console.json:
+        * inspector/protocol/Network.json:
+        Replace use of Console.StackTrace with array of Console.CallFrame.
+
+        * inspector/protocol/Debugger.json:
+        New protocol command and event data.
+
 2016-11-28  Darin Adler  <da...@apple.com>
 
         Streamline and speed up tokenizer and segmented string classes

Modified: trunk/Source/_javascript_Core/inspector/ScriptCallFrame.cpp (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/ScriptCallFrame.cpp	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/ScriptCallFrame.cpp	2016-11-29 07:08:09 UTC (rev 209062)
@@ -59,6 +59,11 @@
         && m_column == o.m_column;
 }
 
+bool ScriptCallFrame::isNative() const
+{
+    return m_scriptName == "[native code]";
+}
+
 Ref<Inspector::Protocol::Console::CallFrame> ScriptCallFrame::buildInspectorObject() const
 {
     return Inspector::Protocol::Console::CallFrame::create()

Modified: trunk/Source/_javascript_Core/inspector/ScriptCallFrame.h (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/ScriptCallFrame.h	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/ScriptCallFrame.h	2016-11-29 07:08:09 UTC (rev 209062)
@@ -50,6 +50,7 @@
     JSC::SourceID sourceID() const { return m_sourceID; }
 
     bool isEqual(const ScriptCallFrame&) const;
+    bool isNative() const;
 
     Ref<Inspector::Protocol::Console::CallFrame> buildInspectorObject() const;
 

Modified: trunk/Source/_javascript_Core/inspector/ScriptCallStack.cpp (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/ScriptCallStack.cpp	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/ScriptCallStack.cpp	2016-11-29 07:08:09 UTC (rev 209062)
@@ -77,7 +77,7 @@
 
     for (size_t i = 0; i < m_frames.size(); ++i) {
         const ScriptCallFrame& frame = m_frames[i];
-        if (frame.sourceURL() != "[native code]")
+        if (!frame.isNative())
             return &frame;
     }
 
@@ -106,9 +106,9 @@
     return true;
 }
 
-Ref<Inspector::Protocol::Console::StackTrace> ScriptCallStack::buildInspectorArray() const
+Ref<Inspector::Protocol::Array<Inspector::Protocol::Console::CallFrame>> ScriptCallStack::buildInspectorArray() const
 {
-    auto frames = Inspector::Protocol::Console::StackTrace::create();
+    auto frames = Inspector::Protocol::Array<Inspector::Protocol::Console::CallFrame>::create();
     for (size_t i = 0; i < m_frames.size(); i++)
         frames->addItem(m_frames.at(i).buildInspectorObject());
     return frames;

Modified: trunk/Source/_javascript_Core/inspector/ScriptCallStack.h (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/ScriptCallStack.h	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/ScriptCallStack.h	2016-11-29 07:08:09 UTC (rev 209062)
@@ -57,7 +57,7 @@
 
     bool isEqual(ScriptCallStack*) const;
 
-    Ref<Inspector::Protocol::Console::StackTrace> buildInspectorArray() const;
+    Ref<Inspector::Protocol::Array<Inspector::Protocol::Console::CallFrame>> buildInspectorArray() const;
 
 private:
     ScriptCallStack();

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.cpp	2016-11-29 07:08:09 UTC (rev 209062)
@@ -37,6 +37,7 @@
 #include "InspectorValues.h"
 #include "JSCInlines.h"
 #include "RegularExpression.h"
+#include "ScriptCallStackFactory.h"
 #include "ScriptDebugServer.h"
 #include "ScriptObject.h"
 #include "ScriptValue.h"
@@ -113,6 +114,8 @@
     if (m_listener)
         m_listener->debuggerWasDisabled();
 
+    clearAsyncStackTraceData();
+
     m_pauseOnAssertionFailures = false;
 
     m_enabled = false;
@@ -133,6 +136,22 @@
     return m_scriptDebugServer.breakpointsActive();
 }
 
+void InspectorDebuggerAgent::setAsyncStackTraceDepth(ErrorString& errorString, int depth)
+{
+    if (m_asyncStackTraceDepth == depth)
+        return;
+
+    if (depth < 0) {
+        errorString = ASCIILiteral("depth must be a positive number.");
+        return;
+    }
+
+    m_asyncStackTraceDepth = depth;
+
+    if (!m_asyncStackTraceDepth)
+        clearAsyncStackTraceData();
+}
+
 void InspectorDebuggerAgent::setBreakpointsActive(ErrorString&, bool active)
 {
     if (active)
@@ -193,6 +212,40 @@
     return injectedScript.wrapObject(exception, InspectorDebuggerAgent::backtraceObjectGroup)->openAccessors();
 }
 
+RefPtr<Inspector::Protocol::Console::StackTrace> InspectorDebuggerAgent::buildAsyncStackTrace(const AsyncCallIdentifier& identifier)
+{
+    RefPtr<Inspector::Protocol::Console::StackTrace> topStackTrace;
+    RefPtr<Inspector::Protocol::Console::StackTrace> previousStackTrace;
+
+    auto iterator = m_asyncCallIdentifierToData.find(identifier);
+    auto end = m_asyncCallIdentifierToData.end();
+    while (iterator != end) {
+        const auto& callData = iterator->value;
+        ASSERT(callData.callStack && callData.callStack->size());
+        if (!callData.callStack || !callData.callStack->size())
+            break;
+
+        RefPtr<Inspector::Protocol::Console::StackTrace> stackTrace = Inspector::Protocol::Console::StackTrace::create()
+            .setCallFrames(callData.callStack->buildInspectorArray())
+            .release();
+
+        if (callData.callStack->at(0).isNative())
+            stackTrace->setTopCallFrameIsBoundary(true);
+        if (!topStackTrace)
+            topStackTrace = stackTrace;
+        if (previousStackTrace)
+            previousStackTrace->setParentStackTrace(stackTrace);
+
+        if (!callData.parentAsyncCallIdentifier)
+            break;
+
+        previousStackTrace = stackTrace;
+        iterator = m_asyncCallIdentifierToData.find(callData.parentAsyncCallIdentifier.value());
+    }
+
+    return topStackTrace;
+}
+
 void InspectorDebuggerAgent::handleConsoleAssert(const String& message)
 {
     if (m_pauseOnAssertionFailures)
@@ -199,6 +252,64 @@
         breakProgram(DebuggerFrontendDispatcher::Reason::Assert, buildAssertPauseReason(message));
 }
 
+void InspectorDebuggerAgent::didScheduleAsyncCall(JSC::ExecState* exec, int asyncCallType, int callbackIdentifier, bool singleShot)
+{
+    if (!m_asyncStackTraceDepth)
+        return;
+
+    if (!m_scriptDebugServer.breakpointsActive())
+        return;
+
+    RefPtr<ScriptCallStack> callStack = createScriptCallStack(exec, m_asyncStackTraceDepth);
+    ASSERT(callStack && callStack->size());
+    if (!callStack || !callStack->size())
+        return;
+
+    if (m_currentAsyncCallIdentifier)
+        refAsyncCallData(m_currentAsyncCallIdentifier.value());
+
+    m_asyncCallIdentifierToData.set(std::make_pair(asyncCallType, callbackIdentifier), AsyncCallData(callStack, m_currentAsyncCallIdentifier, singleShot));
+}
+
+void InspectorDebuggerAgent::didCancelAsyncCall(int asyncCallType, int callbackIdentifier)
+{
+    if (!m_asyncStackTraceDepth)
+        return;
+
+    const auto asyncCallIdentifier = std::make_pair(asyncCallType, callbackIdentifier);
+    derefAsyncCallData(asyncCallIdentifier);
+}
+
+void InspectorDebuggerAgent::willDispatchAsyncCall(int asyncCallType, int callbackIdentifier)
+{
+    if (!m_asyncStackTraceDepth)
+        return;
+
+    if (m_currentAsyncCallIdentifier)
+        return;
+
+    // A call can be scheduled before the Inspector is opened, or while async stack
+    // traces are disabled. If no call data exists, do nothing.
+    auto asyncCallIdentifier = std::make_pair(asyncCallType, callbackIdentifier);
+    if (!m_asyncCallIdentifierToData.contains(asyncCallIdentifier))
+        return;
+
+    m_currentAsyncCallIdentifier = WTFMove(asyncCallIdentifier);
+    refAsyncCallData(asyncCallIdentifier);
+}
+
+void InspectorDebuggerAgent::didDispatchAsyncCall()
+{
+    if (!m_asyncStackTraceDepth)
+        return;
+
+    if (!m_currentAsyncCallIdentifier)
+        return;
+
+    derefAsyncCallData(m_currentAsyncCallIdentifier.value());
+    m_currentAsyncCallIdentifier = std::nullopt;
+}
+
 static Ref<InspectorObject> buildObjectForBreakpointCookie(const String& url, int lineNumber, int columnNumber, const String& condition, RefPtr<InspectorArray>& actions, bool isRegex, bool autoContinue, unsigned ignoreCount)
 {
     Ref<InspectorObject> breakpointObject = InspectorObject::create();
@@ -882,8 +993,12 @@
     m_conditionToDispatchResumed = ShouldDispatchResumed::No;
     m_enablePauseWhenIdle = false;
 
-    m_frontendDispatcher->paused(currentCallFrames(injectedScript), m_breakReason, m_breakAuxData);
+    RefPtr<Inspector::Protocol::Console::StackTrace> asyncStackTrace;
+    if (m_currentAsyncCallIdentifier)
+        asyncStackTrace = buildAsyncStackTrace(m_currentAsyncCallIdentifier.value());
 
+    m_frontendDispatcher->paused(currentCallFrames(injectedScript), m_breakReason, m_breakAuxData, asyncStackTrace);
+
     m_javaScriptPauseScheduled = false;
 
     if (m_continueToLocationBreakpointID != JSC::noBreakpointID) {
@@ -985,6 +1100,8 @@
     // pages have what breakpoints, as the mapping is only sent to DebuggerAgent once.
     clearDebuggerBreakpointState();
 
+    clearAsyncStackTraceData();
+
     m_frontendDispatcher->globalObjectCleared();
 }
 
@@ -1012,4 +1129,37 @@
     }
 }
 
+void InspectorDebuggerAgent::clearAsyncStackTraceData()
+{
+    m_asyncCallIdentifierToData.clear();
+    m_currentAsyncCallIdentifier = std::nullopt;
+}
+
+void InspectorDebuggerAgent::refAsyncCallData(const AsyncCallIdentifier& identifier)
+{
+    auto iterator = m_asyncCallIdentifierToData.find(identifier);
+    ASSERT(iterator != m_asyncCallIdentifierToData.end());
+    if (iterator == m_asyncCallIdentifierToData.end())
+        return;
+
+    iterator->value.referenceCount++;
+}
+
+void InspectorDebuggerAgent::derefAsyncCallData(const AsyncCallIdentifier& identifier)
+{
+    auto iterator = m_asyncCallIdentifierToData.find(identifier);
+    ASSERT(iterator != m_asyncCallIdentifierToData.end());
+    if (iterator == m_asyncCallIdentifierToData.end())
+        return;
+
+    auto& asyncCallData = iterator->value;
+    asyncCallData.referenceCount--;
+    if (asyncCallData.referenceCount)
+        return;
+
+    if (asyncCallData.parentAsyncCallIdentifier)
+        derefAsyncCallData(asyncCallData.parentAsyncCallIdentifier.value());
+    m_asyncCallIdentifierToData.remove(identifier);
+}
+
 } // namespace Inspector

Modified: trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/agents/InspectorDebuggerAgent.h	2016-11-29 07:08:09 UTC (rev 209062)
@@ -35,6 +35,7 @@
 #include "debugger/Debugger.h"
 #include "inspector/InspectorAgentBase.h"
 #include "inspector/ScriptBreakpoint.h"
+#include "inspector/ScriptCallStack.h"
 #include "inspector/ScriptDebugListener.h"
 #include <wtf/Forward.h>
 #include <wtf/HashMap.h>
@@ -48,6 +49,7 @@
 class InspectorArray;
 class InspectorObject;
 class ScriptDebugServer;
+struct AsyncCallData;
 typedef String ErrorString;
 
 class JS_EXPORT_PRIVATE InspectorDebuggerAgent : public InspectorAgentBase, public ScriptDebugListener, public DebuggerBackendDispatcherHandler {
@@ -63,6 +65,7 @@
 
     void enable(ErrorString&) final;
     void disable(ErrorString&) final;
+    void setAsyncStackTraceDepth(ErrorString&, int depth) final;
     void setBreakpointsActive(ErrorString&, bool active) final;
     void setBreakpointByUrl(ErrorString&, int lineNumber, const String* optionalURL, const String* optionalURLRegex, const int* optionalColumnNumber, const Inspector::InspectorObject* options, Inspector::Protocol::Debugger::BreakpointId*, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::Debugger::Location>>& locations) final;
     void setBreakpoint(ErrorString&, const Inspector::InspectorObject& location, const Inspector::InspectorObject* options, Inspector::Protocol::Debugger::BreakpointId*, RefPtr<Inspector::Protocol::Debugger::Location>& actualLocation) final;
@@ -89,6 +92,11 @@
 
     void handleConsoleAssert(const String& message);
 
+    void didScheduleAsyncCall(JSC::ExecState*, int asyncCallType, int callbackIdentifier, bool singleShot);
+    void didCancelAsyncCall(int asyncCallType, int callbackIdentifier);
+    void willDispatchAsyncCall(int asyncCallType, int callbackIdentifier);
+    void didDispatchAsyncCall();
+
     void schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<InspectorObject>&& data);
     void cancelPauseOnNextStatement();
     bool pauseOnNextStatementEnabled() const { return m_javaScriptPauseScheduled; }
@@ -142,6 +150,7 @@
     void clearInspectorBreakpointState();
     void clearBreakDetails();
     void clearExceptionValue();
+    void clearAsyncStackTraceData();
 
     enum class ShouldDispatchResumed { No, WhenIdle, WhenContinued };
     void registerIdleHandler();
@@ -153,11 +162,32 @@
 
     bool breakpointActionsFromProtocol(ErrorString&, RefPtr<InspectorArray>& actions, BreakpointActions* result);
 
+    typedef std::pair<int, int> AsyncCallIdentifier;
+
+    RefPtr<Inspector::Protocol::Console::StackTrace> buildAsyncStackTrace(const AsyncCallIdentifier&);
+    void refAsyncCallData(const AsyncCallIdentifier&);
+    void derefAsyncCallData(const AsyncCallIdentifier&);
+
     typedef HashMap<JSC::SourceID, Script> ScriptsMap;
     typedef HashMap<String, Vector<JSC::BreakpointID>> BreakpointIdentifierToDebugServerBreakpointIDsMap;
     typedef HashMap<String, RefPtr<InspectorObject>> BreakpointIdentifierToBreakpointMap;
     typedef HashMap<JSC::BreakpointID, String> DebugServerBreakpointIDToBreakpointIdentifier;
 
+    struct AsyncCallData {
+        AsyncCallData(RefPtr<ScriptCallStack> callStack, std::optional<AsyncCallIdentifier> parentAsyncCallIdentifier, bool singleShot)
+            : callStack(callStack)
+            , parentAsyncCallIdentifier(parentAsyncCallIdentifier)
+            , referenceCount(singleShot ? 0 : 1)
+        {
+        }
+
+        AsyncCallData() = default;
+
+        RefPtr<ScriptCallStack> callStack;
+        std::optional<AsyncCallIdentifier> parentAsyncCallIdentifier { std::nullopt };
+        unsigned referenceCount { 0 };
+    };
+
     InjectedScriptManager& m_injectedScriptManager;
     std::unique_ptr<DebuggerFrontendDispatcher> m_frontendDispatcher;
     RefPtr<DebuggerBackendDispatcher> m_backendDispatcher;
@@ -174,6 +204,8 @@
     RefPtr<InspectorObject> m_breakAuxData;
     ShouldDispatchResumed m_conditionToDispatchResumed { ShouldDispatchResumed::No };
     bool m_enablePauseWhenIdle { false };
+    HashMap<AsyncCallIdentifier, AsyncCallData> m_asyncCallIdentifierToData;
+    std::optional<AsyncCallIdentifier> m_currentAsyncCallIdentifier { std::nullopt };
     bool m_enabled { false };
     bool m_javaScriptPauseScheduled { false };
     bool m_hasExceptionValue { false };
@@ -180,6 +212,7 @@
     bool m_didPauseStopwatch { false };
     bool m_pauseOnAssertionFailures { false };
     bool m_registeredIdleCallback { false };
+    int m_asyncStackTraceDepth { 0 };
 };
 
 } // namespace Inspector

Modified: trunk/Source/_javascript_Core/inspector/protocol/Console.json (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/protocol/Console.json	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/protocol/Console.json	2016-11-29 07:08:09 UTC (rev 209062)
@@ -16,7 +16,7 @@
                 { "name": "column", "type": "integer", "optional": true, "description": "Column number on the line in the resource that generated this message." },
                 { "name": "repeatCount", "type": "integer", "optional": true, "description": "Repeat count for repeated messages." },
                 { "name": "parameters", "type": "array", "items": { "$ref": "Runtime.RemoteObject" }, "optional": true, "description": "Message parameters in case of the formatted message." },
-                { "name": "stackTrace", "$ref": "StackTrace", "optional": true, "description": "_javascript_ stack trace for assertions and error messages." },
+                { "name": "stackTrace", "type": "array", "items": { "$ref": "CallFrame" }, "optional": true, "description": "_javascript_ stack trace for assertions and error messages." },
                 { "name": "networkRequestId", "$ref": "Network.RequestId", "optional": true, "description": "Identifier of the network request associated with this message." }
             ]
         },
@@ -34,9 +34,13 @@
         },
         {
             "id": "StackTrace",
-            "type": "array",
-            "items": { "$ref": "CallFrame" },
-            "description": "Call frames for assertions or error messages."
+            "description": "Call frames for async function calls, console assertions, and error messages.",
+            "type": "object",
+            "properties": [
+                { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" } },
+                { "name": "topCallFrameIsBoundary", "type": "boolean", "optional": true, "description": "Whether the first item in <code>callFrames</code> is the native function that scheduled the asynchronous operation (e.g. setTimeout)." },
+                { "name": "parentStackTrace", "$ref": "StackTrace", "optional": true, "description": "Parent StackTrace." }
+            ]
         }
     ],
     "commands": [

Modified: trunk/Source/_javascript_Core/inspector/protocol/Debugger.json (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/protocol/Debugger.json	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/protocol/Debugger.json	2016-11-29 07:08:09 UTC (rev 209062)
@@ -136,6 +136,13 @@
             "description": "Disables debugger for given page."
         },
         {
+            "name": "setAsyncStackTraceDepth",
+            "description": "Set the async stack trace depth for the page. A value of zero disables recording of async stack traces.",
+            "parameters": [
+                { "name": "depth", "type": "integer", "description": "Async stack trace depth." }
+            ]
+        },
+        {
             "name": "setBreakpointsActive",
             "parameters": [
                 { "name": "active", "type": "boolean", "description": "New value for breakpoints active state." }
@@ -325,7 +332,8 @@
             "parameters": [
                 { "name": "callFrames", "type": "array", "items": { "$ref": "CallFrame" }, "description": "Call stack the virtual machine stopped on." },
                 { "name": "reason", "type": "string", "enum": ["XHR", "DOM", "EventListener", "exception", "assert", "CSPViolation", "DebuggerStatement", "Breakpoint", "PauseOnNextStatement", "other"], "description": "Pause reason." },
-                { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." }
+                { "name": "data", "type": "object", "optional": true, "description": "Object containing break-specific auxiliary properties." },
+                { "name": "asyncStackTrace", "$ref": "Console.StackTrace", "optional": true, "description": "Linked list of asynchronous StackTraces." }
             ],
             "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria."
         },

Modified: trunk/Source/_javascript_Core/inspector/protocol/Network.json (209061 => 209062)


--- trunk/Source/_javascript_Core/inspector/protocol/Network.json	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/_javascript_Core/inspector/protocol/Network.json	2016-11-29 07:08:09 UTC (rev 209062)
@@ -117,7 +117,7 @@
             "description": "Information about the request initiator.",
             "properties": [
                 { "name": "type", "type": "string", "enum": ["parser", "script", "other"], "description": "Type of this initiator." },
-                { "name": "stackTrace", "$ref": "Console.StackTrace", "optional": true, "description": "Initiator _javascript_ stack trace, set for Script only." },
+                { "name": "stackTrace", "type": "array", "items": { "$ref": "Console.CallFrame" }, "optional": true, "description": "Initiator _javascript_ stack trace, set for Script only." },
                 { "name": "url", "type": "string", "optional": true, "description": "Initiator URL, set for Parser type only." },
                 { "name": "lineNumber", "type": "number", "optional": true, "description": "Initiator line number, set for Parser type only." }
             ]

Modified: trunk/Source/WebCore/ChangeLog (209061 => 209062)


--- trunk/Source/WebCore/ChangeLog	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebCore/ChangeLog	2016-11-29 07:08:09 UTC (rev 209062)
@@ -1,3 +1,30 @@
+2016-11-28  Matt Baker  <mattba...@apple.com>
+
+        Web Inspector: Debugger should have an option for showing asynchronous call stacks
+        https://bugs.webkit.org/show_bug.cgi?id=163230
+        <rdar://problem/28698683>
+
+        Reviewed by Joseph Pecoraro.
+
+        Test: inspector/debugger/async-stack-trace.html
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::didScheduleAsyncCall):
+        Helper function used by by instrumentation hooks. Informs the debugger
+        agent that an asynchronous call was scheduled for the current script
+        execution state.
+
+        (WebCore::InspectorInstrumentation::didInstallTimerImpl):
+        (WebCore::InspectorInstrumentation::didRemoveTimerImpl):
+        (WebCore::InspectorInstrumentation::willFireTimerImpl):
+        (WebCore::InspectorInstrumentation::didFireTimerImpl):
+        Asynchronous stack trace plumbing for timers (setTimeout, setInterval).
+        (WebCore::InspectorInstrumentation::didRequestAnimationFrameImpl):
+        (WebCore::InspectorInstrumentation::didCancelAnimationFrameImpl):
+        (WebCore::InspectorInstrumentation::willFireAnimationFrameImpl):
+        (WebCore::InspectorInstrumentation::didFireAnimationFrameImpl):
+        Asynchronous stack trace plumbing for requestAnimationFrame.
+
 2016-11-28  Jiewen Tan  <jiewen_...@apple.com>
 
         Unreviewed, followup patch after r209059.

Modified: trunk/Source/WebCore/inspector/InspectorInstrumentation.cpp (209061 => 209062)


--- trunk/Source/WebCore/inspector/InspectorInstrumentation.cpp	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebCore/inspector/InspectorInstrumentation.cpp	2016-11-29 07:08:09 UTC (rev 209062)
@@ -83,6 +83,11 @@
 static const char* const clearTimerEventName = "clearTimer";
 static const char* const timerFiredEventName = "timerFired";
 
+enum AsyncCallType {
+    AsyncCallTypeRequestAnimationFrame,
+    AsyncCallTypeTimer,
+};
+
 namespace {
 static HashSet<InstrumentingAgents*>* s_instrumentingAgentsSet = nullptr;
 }
@@ -89,6 +94,17 @@
 
 int InspectorInstrumentation::s_frontendCounter = 0;
 
+static void didScheduleAsyncCall(InstrumentingAgents& instrumentingAgents, AsyncCallType type, int callbackId, ScriptExecutionContext& context, bool singleShot)
+{
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent()) {
+        JSC::ExecState* scriptState = context.execState();
+        if (!scriptState)
+            return;
+
+        debuggerAgent->didScheduleAsyncCall(scriptState, type, callbackId, singleShot);
+    }
+}
+
 static Frame* frameForScriptExecutionContext(ScriptExecutionContext* context)
 {
     Frame* frame = nullptr;
@@ -323,6 +339,8 @@
 void InspectorInstrumentation::didInstallTimerImpl(InstrumentingAgents& instrumentingAgents, int timerId, std::chrono::milliseconds timeout, bool singleShot, ScriptExecutionContext& context)
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, setTimerEventName, true);
+    didScheduleAsyncCall(instrumentingAgents, AsyncCallTypeTimer, timerId, context, singleShot);
+
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent())
         timelineAgent->didInstallTimer(timerId, timeout, singleShot, frameForScriptExecutionContext(context));
 }
@@ -330,6 +348,9 @@
 void InspectorInstrumentation::didRemoveTimerImpl(InstrumentingAgents& instrumentingAgents, int timerId, ScriptExecutionContext& context)
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, clearTimerEventName, true);
+
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
+        debuggerAgent->didCancelAsyncCall(AsyncCallTypeTimer, timerId);
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent())
         timelineAgent->didRemoveTimer(timerId, frameForScriptExecutionContext(context));
 }
@@ -423,6 +444,9 @@
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, timerFiredEventName, false);
 
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
+        debuggerAgent->willDispatchAsyncCall(AsyncCallTypeTimer, timerId);
+
     int timelineAgentId = 0;
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent()) {
         timelineAgent->willFireTimer(timerId, frameForScriptExecutionContext(context));
@@ -433,6 +457,8 @@
 
 void InspectorInstrumentation::didFireTimerImpl(const InspectorInstrumentationCookie& cookie)
 {
+    if (InspectorDebuggerAgent* debuggerAgent = cookie.instrumentingAgents()->inspectorDebuggerAgent())
+        debuggerAgent->didDispatchAsyncCall();
     if (InspectorTimelineAgent* timelineAgent = retrieveTimelineAgent(cookie))
         timelineAgent->didFireTimer();
 }
@@ -1139,6 +1165,7 @@
 void InspectorInstrumentation::didRequestAnimationFrameImpl(InstrumentingAgents& instrumentingAgents, int callbackId, Frame* frame)
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, requestAnimationFrameEventName, true);
+    didScheduleAsyncCall(instrumentingAgents, AsyncCallTypeRequestAnimationFrame, callbackId, *frame->document(), true);
 
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent())
         timelineAgent->didRequestAnimationFrame(callbackId, frame);
@@ -1148,6 +1175,8 @@
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, cancelAnimationFrameEventName, true);
 
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
+        debuggerAgent->didCancelAsyncCall(AsyncCallTypeRequestAnimationFrame, callbackId);
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent())
         timelineAgent->didCancelAnimationFrame(callbackId, frame);
 }
@@ -1156,6 +1185,9 @@
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, animationFrameFiredEventName, false);
 
+    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
+        debuggerAgent->willDispatchAsyncCall(AsyncCallTypeRequestAnimationFrame, callbackId);
+
     int timelineAgentId = 0;
     if (InspectorTimelineAgent* timelineAgent = instrumentingAgents.inspectorTimelineAgent()) {
         timelineAgent->willFireAnimationFrame(callbackId, frame);
@@ -1166,6 +1198,8 @@
 
 void InspectorInstrumentation::didFireAnimationFrameImpl(const InspectorInstrumentationCookie& cookie)
 {
+    if (InspectorDebuggerAgent* debuggerAgent = cookie.instrumentingAgents()->inspectorDebuggerAgent())
+        debuggerAgent->didDispatchAsyncCall();
     if (InspectorTimelineAgent* timelineAgent = retrieveTimelineAgent(cookie))
         timelineAgent->didFireAnimationFrame();
 }

Modified: trunk/Source/WebInspectorUI/ChangeLog (209061 => 209062)


--- trunk/Source/WebInspectorUI/ChangeLog	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/ChangeLog	2016-11-29 07:08:09 UTC (rev 209062)
@@ -1,3 +1,74 @@
+2016-11-28  Matt Baker  <mattba...@apple.com>
+
+        Web Inspector: Debugger should have an option for showing asynchronous call stacks
+        https://bugs.webkit.org/show_bug.cgi?id=163230
+        <rdar://problem/28698683>
+
+        Reviewed by Joseph Pecoraro.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New string for generic async call stack boundary label: "(async)".
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        Create async stack depth setting and set default depth.
+        (WebInspector.DebuggerManager.prototype.get asyncStackTraceDepth):
+        (WebInspector.DebuggerManager.prototype.set asyncStackTraceDepth):
+        Make async stack depth setting accessible to the frontend.
+        (WebInspector.DebuggerManager.prototype.initializeTarget):
+        Set async stack depth value on the target.
+        (WebInspector.DebuggerManager.prototype.debuggerDidPause):
+        Plumbing for the async stack trace payload.
+
+        * UserInterface/Models/ConsoleMessage.js:
+        (WebInspector.ConsoleMessage):
+        Updated for new StackTrace.fromPayload use.
+
+        * UserInterface/Models/DebuggerData.js:
+        (WebInspector.DebuggerData):
+        (WebInspector.DebuggerData.prototype.get asyncStackTrace):
+        (WebInspector.DebuggerData.prototype.updateForPause):
+        (WebInspector.DebuggerData.prototype.updateForResume):
+        More plumbing.
+
+        * UserInterface/Models/StackTrace.js:
+        Update frontend model for use as new protocol object Console.StackTrace,
+        which was previously an alias for a simple array of Console.CallFrames.
+
+        (WebInspector.StackTrace):
+        (WebInspector.StackTrace.fromPayload):
+        (WebInspector.StackTrace.fromString):
+        (WebInspector.StackTrace.prototype.get topCallFrameIsBoundary):
+        (WebInspector.StackTrace.prototype.get parentStackTrace):
+
+        * UserInterface/Protocol/DebuggerObserver.js:
+        (WebInspector.DebuggerObserver.prototype.paused):
+        More plumbing.
+
+        * UserInterface/Views/CallFrameTreeElement.css:
+        (.tree-outline .item.call-frame.async-boundary):
+        Use default cursor since boundary element is not selectable.
+        (.tree-outline .item.call-frame.async-boundary .icon):
+        (.tree-outline .item.call-frame.async-boundary::before,):
+        (.tree-outline .item.call-frame.async-boundary::after):
+        (.tree-outline .item.call-frame.async-boundary::before):
+        Dimmed text and divider line styles for boundary element.
+
+        * UserInterface/Views/CallFrameTreeElement.js:
+        (WebInspector.CallFrameTreeElement):
+        Add a flag denoting whether the call frame is an async call trace
+        boundary, and set styles accordingly.
+
+        * UserInterface/Views/DebuggerSidebarPanel.js:
+        Set async stack trace depth, if supported.
+        (WebInspector.DebuggerSidebarPanel.prototype._updateSingleThreadCallStacks):
+        Add call frames for async stack traces to the call stack TreeOutline.
+        (WebInspector.DebuggerSidebarPanel.prototype._treeSelectionDidChange):
+        Ensure that async call frames cannot become the active call frame.
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        Add --text-color-gray-medium, for dimmed text in async boundary element.
+
 2016-11-18  Matt Baker  <mattba...@apple.com>
 
         Web Inspector: TimelineDataGridNode assertions when refreshing page

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -42,6 +42,7 @@
 localizedStrings["(Index)"] = "(Index)";
 localizedStrings["(Tail Call)"] = "(Tail Call)";
 localizedStrings["(anonymous function)"] = "(anonymous function)";
+localizedStrings["(async)"] = "(async)";
 localizedStrings["(many)"] = "(many)";
 localizedStrings["(modify the boxes below to add a value)"] = "(modify the boxes below to add a value)";
 localizedStrings["(multiple)"] = "(multiple)";

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -52,6 +52,7 @@
         this._allExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-exceptions", false);
         this._allUncaughtExceptionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-all-uncaught-exceptions", false);
         this._assertionsBreakpointEnabledSetting = new WebInspector.Setting("break-on-assertions", false);
+        this._asyncStackTraceDepthSetting = new WebInspector.Setting("async-stack-trace-depth", 200);
 
         let specialBreakpointLocation = new WebInspector.SourceCodeLocation(null, Infinity, Infinity);
 
@@ -94,6 +95,10 @@
         if (DebuggerAgent.setPauseOnAssertions)
             DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
 
+        // COMPATIBILITY (iOS 10): Debugger.setAsyncStackTraceDepth did not exist yet.
+        if (DebuggerAgent.setAsyncStackTraceDepth)
+            DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+
         this._ignoreBreakpointDisplayLocationDidChangeEvent = false;
 
         function restoreBreakpointsSoon() {
@@ -277,6 +282,22 @@
         return knownScripts;
     }
 
+    get asyncStackTraceDepth()
+    {
+        return this._asyncStackTraceDepthSetting.value;
+    }
+
+    set asyncStackTraceDepth(x)
+    {
+        if (this._asyncStackTraceDepthSetting.value === x)
+            return;
+
+        this._asyncStackTraceDepthSetting.value = x;
+
+        for (let target of WebInspector.targets)
+            target.DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
+    }
+
     pause()
     {
         if (this.paused)
@@ -484,6 +505,7 @@
         DebuggerAgent.setBreakpointsActive(this._breakpointsEnabledSetting.value);
         DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
         DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
+        DebuggerAgent.setAsyncStackTraceDepth(this._asyncStackTraceDepthSetting.value);
 
         if (this.paused)
             targetData.pauseIfNeeded();
@@ -550,7 +572,7 @@
             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
     }
 
-    debuggerDidPause(target, callFramesPayload, reason, data)
+    debuggerDidPause(target, callFramesPayload, reason, data, asyncStackTracePayload)
     {
         // Called from WebInspector.DebuggerObserver.
 
@@ -599,7 +621,8 @@
             return;
         }
 
-        targetData.updateForPause(callFrames, pauseReason, pauseData);
+        let asyncStackTrace = WebInspector.StackTrace.fromPayload(target, asyncStackTracePayload);
+        targetData.updateForPause(callFrames, pauseReason, pauseData, asyncStackTrace);
 
         // Pause other targets because at least one target has paused.
         // FIXME: Should this be done on the backend?

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -25,7 +25,7 @@
 
 WebInspector.ConsoleMessage = class ConsoleMessage extends WebInspector.Object
 {
-    constructor(target, source, level, message, type, url, line, column, repeatCount, parameters, stackTrace, request)
+    constructor(target, source, level, message, type, url, line, column, repeatCount, parameters, callFrames, request)
     {
         super();
 
@@ -49,7 +49,8 @@
         this._repeatCount = repeatCount || 0;
         this._parameters = parameters;
 
-        this._stackTrace = WebInspector.StackTrace.fromPayload(this._target, stackTrace || []);
+        callFrames = callFrames || [];
+        this._stackTrace = WebInspector.StackTrace.fromPayload(this._target, {callFrames});
 
         this._request = request;
     }

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -38,6 +38,7 @@
         this._pauseReason = null;
         this._pauseData = null;
         this._callFrames = [];
+        this._asyncStackTrace = null;
 
         this._scriptIdMap = new Map;
         this._scriptContentIdentifierMap = new Map;
@@ -53,6 +54,7 @@
     get pauseReason() { return this._pauseReason; }
     get pauseData() { return this._pauseData; }
     get callFrames() { return this._callFrames; }
+    get asyncStackTrace() { return this._asyncStackTrace; }
 
     get scripts()
     {
@@ -122,7 +124,7 @@
         return this._target.DebuggerAgent.continueUntilNextRunLoop();
     }
 
-    updateForPause(callFrames, pauseReason, pauseData)
+    updateForPause(callFrames, pauseReason, pauseData, asyncStackTrace)
     {
         this._paused = true;
         this._pausing = false;
@@ -129,6 +131,7 @@
         this._pauseReason = pauseReason;
         this._pauseData = pauseData;
         this._callFrames = callFrames;
+        this._asyncStackTrace = asyncStackTrace;
 
         // We paused, no need for auto-pausing.
         this._makePausingAfterNextResume = false;
@@ -141,6 +144,7 @@
         this._pauseReason = null;
         this._pauseData = null;
         this._callFrames = [];
+        this._asyncStackTrace = null;
 
         // We resumed, but may be auto-pausing.
         if (this._makePausingAfterNextResume) {

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/StackTrace.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Models/StackTrace.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/StackTrace.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -25,7 +25,7 @@
 
 WebInspector.StackTrace = class StackTrace extends WebInspector.Object
 {
-    constructor(callFrames)
+    constructor(callFrames, topCallFrameIsBoundary)
     {
         super();
 
@@ -32,6 +32,8 @@
         console.assert(callFrames && callFrames.every((callFrame) => callFrame instanceof WebInspector.CallFrame));
 
         this._callFrames = callFrames;
+        this._topCallFrameIsBoundary = topCallFrameIsBoundary || false;
+        this._parentStackTrace = null;
     }
 
     // Static
@@ -38,14 +40,28 @@
 
     static fromPayload(target, payload)
     {
-        let callFrames = payload.map((x) => WebInspector.CallFrame.fromPayload(target, x));
-        return new WebInspector.StackTrace(callFrames);
+        let result = null;
+        let previousStackTrace = null;
+
+        while (payload) {
+            let callFrames = payload.callFrames.map((x) => WebInspector.CallFrame.fromPayload(target, x));
+            let stackTrace = new WebInspector.StackTrace(callFrames, payload.topCallFrameIsBoundary);
+            if (!result)
+                result = stackTrace;
+            if (previousStackTrace)
+                previousStackTrace._parentStackTrace = stackTrace;
+
+            previousStackTrace = stackTrace;
+            payload = payload.parentStackTrace;
+        }
+
+        return result;
     }
 
     static fromString(target, stack)
     {
-        let payload = WebInspector.StackTrace._parseStackTrace(stack);
-        return WebInspector.StackTrace.fromPayload(target, payload);
+        let callFrames = WebInspector.StackTrace._parseStackTrace(stack);
+        return WebInspector.StackTrace.fromPayload(target, {callFrames});
     }
 
     // May produce false negatives; must not produce any false positives.
@@ -151,4 +167,7 @@
 
         return null;
     }
+
+    get topCallFrameIsBoundary() { return this._topCallFrameIsBoundary; }
+    get parentStackTrace() { return this._parentStackTrace; }
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/DebuggerObserver.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Protocol/DebuggerObserver.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/DebuggerObserver.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -63,9 +63,9 @@
         WebInspector.debuggerManager.breakpointResolved(this.target, breakpointId, location);
     }
 
-    paused(callFrames, reason, data)
+    paused(callFrames, reason, data, asyncStackTrace)
     {
-        WebInspector.debuggerManager.debuggerDidPause(this.target, callFrames, reason, data);
+        WebInspector.debuggerManager.debuggerDidPause(this.target, callFrames, reason, data, asyncStackTrace);
     }
 
     resumed()

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.css (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.css	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.css	2016-11-29 07:08:09 UTC (rev 209062)
@@ -43,3 +43,35 @@
 .tree-outline:matches(:focus, .force-focus) .item.call-frame.selected .status > .status-image {
     fill: var(--selected-foreground-color);
 }
+
+.tree-outline .item.call-frame.async-boundary {
+    cursor: default;
+    color: var(--text-color-gray-medium);
+    padding-left: 0;
+}
+
+.tree-outline .item.call-frame.async-boundary .icon {
+    float: none;
+    display: inline-block;
+    margin-left: 0 !important;
+}
+
+.tree-outline .item.call-frame.async-boundary::before,
+.tree-outline .item.call-frame.async-boundary::after {
+    content: "";
+    display: inline-block;
+    height: 0;
+    margin-top: 2px;
+    vertical-align: middle;
+    border-bottom: solid 0.5px var(--border-color);
+}
+
+.tree-outline .item.call-frame.async-boundary::after {
+    width: 100%;
+    margin-left: 2px;
+}
+
+.tree-outline .item.call-frame.async-boundary::before {
+    width: 20px;
+    margin-right: 2px;
+}

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -25,7 +25,7 @@
 
 WebInspector.CallFrameTreeElement = class CallFrameTreeElement extends WebInspector.GeneralTreeElement
 {
-    constructor(callFrame)
+    constructor(callFrame, isAsyncBoundaryCallFrame)
     {
         console.assert(callFrame instanceof WebInspector.CallFrame);
 
@@ -34,18 +34,26 @@
 
         super(["call-frame", className], title, null, callFrame, false);
 
-        if (!callFrame.nativeCode && callFrame.sourceCodeLocation) {
-            let displayScriptURL = callFrame.sourceCodeLocation.displaySourceCode.url;
-            if (displayScriptURL) {
-                this.subtitle = document.createElement("span");
-                callFrame.sourceCodeLocation.populateLiveDisplayLocationString(this.subtitle, "textContent");
-                // Set the tooltip on the entire tree element in onattach, once the element is created.
-                this.tooltipHandledSeparately = true;
-            }
+        this._callFrame = callFrame;
+        this._isActiveCallFrame = false;
+
+         if (isAsyncBoundaryCallFrame) {
+            this.addClassName("async-boundary");
+            this.selectable = false;
+         }
+
+        if (this._callFrame.nativeCode || !this._callFrame.sourceCodeLocation) {
+            this.subtitle = "";
+            return;
         }
 
-        this._callFrame = callFrame;
-        this._isActiveCallFrame = false;
+        let displayScriptURL = this._callFrame.sourceCodeLocation.displaySourceCode.url;
+        if (displayScriptURL) {
+            this.subtitle = document.createElement("span");
+            this._callFrame.sourceCodeLocation.populateLiveDisplayLocationString(this.subtitle, "textContent");
+            // Set the tooltip on the entire tree element in onattach, once the element is created.
+            this.tooltipHandledSeparately = true;
+        }
     }
 
     // Public

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js	2016-11-29 07:08:09 UTC (rev 209062)
@@ -628,6 +628,36 @@
             if (this._showingSingleThreadCallStack)
                 activeCallFrameTreeElement.select(true, true);
         }
+
+        if (!targetData.asyncStackTrace)
+            return;
+
+        let currentStackTrace = targetData.asyncStackTrace;
+        while (currentStackTrace) {
+            console.assert(currentStackTrace.callFrames.length, "StackTrace should have non-empty call frames array.");
+            if (!currentStackTrace.callFrames.length)
+                break;
+
+            let boundaryCallFrame;
+            if (currentStackTrace.topCallFrameIsBoundary) {
+                boundaryCallFrame = currentStackTrace.callFrames[0];
+                console.assert(boundaryCallFrame.nativeCode && !boundaryCallFrame.sourceCodeLocation);
+            } else {
+                // Create a generic native CallFrame for the asynchronous boundary.
+                const functionName = WebInspector.UIString("(async)");
+                const nativeCode = true;
+                boundaryCallFrame = new WebInspector.CallFrame(null, null, null, functionName, null, null, nativeCode);
+            }
+
+            const isAsyncBoundaryCallFrame = true;
+            this._singleThreadCallStackTreeOutline.appendChild(new WebInspector.CallFrameTreeElement(boundaryCallFrame, isAsyncBoundaryCallFrame));
+
+            let startIndex = currentStackTrace.topCallFrameIsBoundary ? 1 : 0;
+            for (let i = startIndex; i < currentStackTrace.callFrames.length; ++i)
+                this._singleThreadCallStackTreeOutline.appendChild(new WebInspector.CallFrameTreeElement(currentStackTrace.callFrames[i]));
+
+            currentStackTrace = currentStackTrace.parentStackTrace;
+        }
     }
 
     _selectActiveCallFrameTreeElement(treeOutline)
@@ -859,8 +889,11 @@
 
         if (treeElement instanceof WebInspector.CallFrameTreeElement) {
             let callFrame = treeElement.callFrame;
-            WebInspector.debuggerManager.activeCallFrame = callFrame;
-            WebInspector.showSourceCodeLocation(callFrame.sourceCodeLocation);
+            if (callFrame.id)
+                WebInspector.debuggerManager.activeCallFrame = callFrame;
+
+            if (callFrame.sourceCodeLocation)
+                WebInspector.showSourceCodeLocation(callFrame.sourceCodeLocation);
             return;
         }
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css (209061 => 209062)


--- trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css	2016-11-29 05:49:36 UTC (rev 209061)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css	2016-11-29 07:08:09 UTC (rev 209062)
@@ -53,6 +53,7 @@
     --console-secondary-text-color: hsla(0, 0%, 0%, 0.33);
     --console-prompt-min-height: 30px;
 
+    --text-color-gray-medium: hsl(0, 0%, 50%);
     --error-text-color: hsl(0, 86%, 47%);
 
     --syntax-highlight-number-color: hsl(248, 100%, 40%);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to