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%);