Web Inspector: Worker debugging should pause all targets and view call frames in...
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Nov 2016 04:02:59 +0000 (04:02 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Nov 2016 04:02:59 +0000 (04:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=164305
<rdar://problem/29056192>

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/InjectedScriptSource.js:
(InjectedScript.prototype._propertyDescriptors):
Accessing __proto__ does a ToThis(...) conversion on the receiver.
In the case of GlobalObjects (such as WorkerGlobalScope when paused)
this would return undefined and throw an exception. We can use
Object.getPrototypeOf to avoid that conversion and possible error.

* inspector/protocol/Debugger.json:
Provide a new way to effectively `resume` + `pause` immediately.
This must be implemented on the backend to correctly synchronize
the resuming and pausing.

* inspector/agents/InspectorDebuggerAgent.h:
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::continueUntilNextRunLoop):
Treat this as `resume` and `pause`. Resume now, and trigger
a pause if the VM becomes idle and we didn't pause before then
(such as hitting a breakpoint after we resumed).

(Inspector::InspectorDebuggerAgent::pause):
(Inspector::InspectorDebuggerAgent::resume):
(Inspector::InspectorDebuggerAgent::schedulePauseOnNextStatement):
(Inspector::InspectorDebuggerAgent::cancelPauseOnNextStatement):
Clean up and correct pause on next statement logic.

(Inspector::InspectorDebuggerAgent::registerIdleHandler):
(Inspector::InspectorDebuggerAgent::willStepAndMayBecomeIdle):
(Inspector::InspectorDebuggerAgent::didBecomeIdle):
(Inspector::InspectorDebuggerAgent::didBecomeIdleAfterStepping): Deleted.
The idle handler may now also trigger a pause in the case
where continueUntilNextRunLoop resumed and wants to pause.

(Inspector::InspectorDebuggerAgent::didPause):
Eliminate the useless didPause. The DOMDebugger was keeping track
of its own state that was worse then the state in DebuggerAgent.

Source/WebCore:

Tests: inspector/debugger/continueUntilNextRunLoop
       inspector/worker/debugger-multiple-targets-pause

* workers/WorkerMessagingProxy.cpp:
(WebCore::WorkerMessagingProxy::postMessageToPageInspector):
Switch from postTask (callOnMainThread) to RunLoop::main().dispatch so
that a paused Worker can send Inspector protocol messages responses
back through the Main Page's InspectorWorkerAgent even if the Page
itself is paused and MainThread callbacks are paused.

* workers/WorkerRunLoop.h:
(WebCore::WorkerRunLoop::isNested):
* workers/WorkerRunLoop.cpp:
(WebCore::WorkerRunLoop::runInMode):
When running a nested WorkerRunLoop, running inspector debugger
commands, we should not fire timers on the Worker. Timers would
then be happening out of order and would not be debuggable.

* dom/EventTarget.cpp:
(WebCore::EventTarget::fireEventListeners):
* inspector/InspectorDOMDebuggerAgent.cpp:
(WebCore::InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded):
(WebCore::InspectorDOMDebuggerAgent::clear):
(WebCore::InspectorDOMDebuggerAgent::didPause): Deleted.
* inspector/InspectorDOMDebuggerAgent.h:
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::willHandleEventImpl):
(WebCore::InspectorInstrumentation::didFireTimerImpl):
(WebCore::InspectorInstrumentation::didHandleEventImpl): Deleted.
(WebCore::InspectorInstrumentation::cancelPauseOnNativeEvent): Deleted.
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::willHandleEvent):
(WebCore::InspectorInstrumentation::didHandleEvent): Deleted.
Remove unnecessary code where WebCore is trying to keep track
of pause on next statement but that state is already more
accurately provided by InspectorDebuggerAgent.

Source/WebInspectorUI:

This implements a policy where, when one Target ("Thread") pauses
the frontend triggers a pause in all other Targets. The intended
user experience is "all threads pause" whenever the frontend shows
the debugger paused UI.

DebuggerManager has a few straight forward changes:

    - The paused state reflects if any target is paused.
    - The Paused Event is fired when going from !paused -> paused.
      This means when the first target pauses.
    - The Resumed Event is fired when going from paused -> !paused.
      This means only after all targets have resumed.
    - The CallFrameDidChange Event now includes the Target that updated.

When a Target first pauses the frontend then immediately pauses all
other Targets. This puts them into a "pausing" state (we display as
Idle) and they will pause as soon as they start executing JavaScript.

When a Target steps the "paused" state isn't changing. So this is
just a CallFramesDidChange update.

When clicking Resume we resume all targets. This is will be the normal,
expected way users resume execution. Note that one of the threads may
then hit a breakpoint and re-pause all threads.

Sometimes when multiple threads are paused you may want to run an
individual thread to completion but keep other threads paused. There
is a context menu on the ThreadTreeElement to resume just that
single thread. It will continue and pause for its next run loop.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Images/Thread.svg: Added.
* UserInterface/Images/gtk/Thread.svg: Added.
* UserInterface/Main.html:
New strings and files.

* UserInterface/Base/Main.js:
(WebInspector.loaded):
* UserInterface/Test/Test.js:
(WebInspector.loaded):
Place the TargetManager first since other managers may want to listen
for TargetAdded / TargetRemoved events.

* UserInterface/Controllers/DebuggerManager.js:
(WebInspector.DebuggerManager.prototype.get paused):
This is now a computed state.

(WebInspector.DebuggerManager.prototype.pause):
(WebInspector.DebuggerManager.prototype.resume):
Affect all targets.

(WebInspector.DebuggerManager.prototype.stepOver):
(WebInspector.DebuggerManager.prototype.stepInto):
(WebInspector.DebuggerManager.prototype.stepOut):
(WebInspector.DebuggerManager.prototype.reset):
Update to use the paused computed property.

(WebInspector.DebuggerManager.prototype.continueUntilNextRunLoop):
Issue the new Debugger.continueUntilNextRunLoop command
on a given target.

(WebInspector.DebuggerManager.prototype.initializeTarget):
When a new Target is created and we were already paused,
then start that Worker in a paused state.

(WebInspector.DebuggerManager.prototype.debuggerDidPause):
Recover from bad cases where the backend informs the frontend about
internal JavaScript that it shouldn't know about. Legacy backend do
this but also there are corner cases we need to handle.
Dispatch events appropriately now that multiple targets may be paused.

(WebInspector.DebuggerManager.prototype._didResumeInternal):
Dispatch events appropriately now that multiple targets may be paused.

(WebInspector.DebuggerManager.prototype._targetRemoved):
Remove debugger data for targets that go away to avoid leaks.

* UserInterface/Models/DebuggerData.js:
(WebInspector.DebuggerData):
(WebInspector.DebuggerData.prototype.get paused):
(WebInspector.DebuggerData.prototype.get pausing):
Move some more per-Target state into DebuggerData.

(WebInspector.DebuggerData.prototype.pauseIfNeeded):
(WebInspector.DebuggerData.prototype.resumeIfNeeded):
(WebInspector.DebuggerData.prototype.continueUntilNextRunLoop):
These should only be called by DebuggerManager. They correctly
update the state of the DebuggerData for this Target, and also
issue the underlying command to the target.

(WebInspector.DebuggerData.prototype.updateForPause):
(WebInspector.DebuggerData.prototype.updateForResume):
Handle a special case where continueUntilNextRunLoop triggers
an invisible "pause" on the backend that we should mirror.

* UserInterface/Protocol/Target.js:
(WebInspector.MainTarget):
(WebInspector.MainTarget.prototype.get displayName):
(WebInspector.MainTarget.prototype.initialize):
Better display names.

* UserInterface/Views/DebuggerSidebarPanel.js:
(WebInspector.DebuggerSidebarPanel):
(WebInspector.DebuggerSidebarPanel.prototype._debuggerDidPause):
(WebInspector.DebuggerSidebarPanel.prototype._debuggerDidResume):
(WebInspector.DebuggerSidebarPanel.prototype._updateSingleThreadCallStacks):
(WebInspector.DebuggerSidebarPanel.prototype._selectActiveCallFrameTreeElement):
(WebInspector.DebuggerSidebarPanel.prototype._showSingleThreadCallStacks):
(WebInspector.DebuggerSidebarPanel.prototype._showMultipleThreadCallStacks):
(WebInspector.DebuggerSidebarPanel.prototype._findThreadTreeElementForTarget):
(WebInspector.DebuggerSidebarPanel.prototype._targetAdded):
(WebInspector.DebuggerSidebarPanel.prototype._targetRemoved):
(WebInspector.DebuggerSidebarPanel.prototype._debuggerCallFramesDidChange):
(WebInspector.DebuggerSidebarPanel.prototype._debuggerActiveCallFrameDidChange):
The DebuggerSidebar still has a single "Call Stacks" section, but maintains
two TreeOutlines and only shows one at a time. The Single Thread view shows
a flat list of the call frames for the Main Target when it is the only target.
The Multiple Threads view shows a list of Threads and their call frames.
We always keep both up to date, because we may need to swap between them
purely as Targets are added / removed. There is a bit of extra logic to
ensure we select elements properly based only on the visible tree outline.

* UserInterface/Views/LogContentView.js:
(WebInspector.LogContentView.prototype.didAppendConsoleMessageView):
When evaluating in a particular target, "runAfterPendingDispatches"
must wait for all other commands in that particular target to have
completed. So use the target specific version.

* UserInterface/Views/NavigationSidebarPanel.js:
(WebInspector.NavigationSidebarPanel.prototype._isTreeElementWithoutRepresentedObject):
Gracefully handle a few more TreeElements without a represented object.

* UserInterface/Views/IdleTreeElement.css: Added.
(.details-section.call-stack .idle .icon):
* UserInterface/Views/IdleTreeElement.js: Added.
(WebInspector.IdleTreeElement):
Very basic tree element to encapsulate an Idle call frame with an
empty represented object.

* UserInterface/Views/ThreadTreeElement.css: Added.
(.details-section.call-stack .thread .icon):
* UserInterface/Views/ThreadTreeElement.js: Added.
(WebInspector.ThreadTreeElement):
(WebInspector.ThreadTreeElement.prototype.get target):
(WebInspector.ThreadTreeElement.prototype.refresh):
(WebInspector.ThreadTreeElement.prototype.onattach):
(WebInspector.ThreadTreeElement.prototype.oncontextmenu):
ThreadTreeElement has no represented object, but makes it easy
to refresh a list of CallFrameTreeElements for a given target.

LayoutTests:

* inspector/debugger/continueUntilNextRunLoop-expected.txt: Added.
* inspector/debugger/continueUntilNextRunLoop.html: Added.
New test for new Debugger.continueUntilNextRunLoop protocol method.

* inspector/worker/debugger-multiple-targets-pause-expected.txt: Added.
* inspector/worker/debugger-multiple-targets-pause.html: Added.
* inspector/worker/resources/worker-debugger-thread-1.js: Added.
* inspector/worker/resources/worker-debugger-thread-2.js: Added.
This tests uses a 250ms timeout because we have to have the worker thread
evaluate some work and trigger a pause on other threads before their work
starts. On debug builds, shorter times, like 100ms, would not be enough.

* inspector/worker/debugger-pause-expected.txt:
* inspector/worker/debugger-pause.html:
Now that all threads pause, the first InspectorTest.log evaluates JavaScript on
the page and causes a pause. So make the first log empty to keep the test unchanged.

* inspector/worker/runtime-basic-expected.txt:
* inspector/unit-tests/target-manager-expected.txt:
Updated display name of the mainTarget.

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

43 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/continueUntilNextRunLoop-expected.txt [new file with mode: 0644]
LayoutTests/inspector/debugger/continueUntilNextRunLoop.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/target-manager-expected.txt
LayoutTests/inspector/worker/debugger-multiple-targets-pause-expected.txt [new file with mode: 0644]
LayoutTests/inspector/worker/debugger-multiple-targets-pause.html [new file with mode: 0644]
LayoutTests/inspector/worker/debugger-pause-expected.txt
LayoutTests/inspector/worker/debugger-pause.html
LayoutTests/inspector/worker/resources-in-worker.html
LayoutTests/inspector/worker/resources/worker-debugger-thread-1.js [new file with mode: 0644]
LayoutTests/inspector/worker/resources/worker-debugger-thread-2.js [new file with mode: 0644]
LayoutTests/inspector/worker/runtime-basic-expected.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/JavaScriptCore/inspector/protocol/Debugger.json
Source/WebCore/ChangeLog
Source/WebCore/dom/EventTarget.cpp
Source/WebCore/inspector/InspectorDOMDebuggerAgent.cpp
Source/WebCore/inspector/InspectorDOMDebuggerAgent.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/workers/WorkerMessagingProxy.cpp
Source/WebCore/workers/WorkerRunLoop.cpp
Source/WebCore/workers/WorkerRunLoop.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Images/Thread.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Images/gtk/Thread.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/DebuggerData.js
Source/WebInspectorUI/UserInterface/Protocol/Target.js
Source/WebInspectorUI/UserInterface/Test/Test.js
Source/WebInspectorUI/UserInterface/Views/DebuggerSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/LogContentView.js
Source/WebInspectorUI/UserInterface/Views/NavigationSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.js [new file with mode: 0644]

index 729725b..a991b9b 100644 (file)
@@ -1,3 +1,32 @@
+2016-11-14  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Worker debugging should pause all targets and view call frames in all targets
+        https://bugs.webkit.org/show_bug.cgi?id=164305
+        <rdar://problem/29056192>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/debugger/continueUntilNextRunLoop-expected.txt: Added.
+        * inspector/debugger/continueUntilNextRunLoop.html: Added.
+        New test for new Debugger.continueUntilNextRunLoop protocol method.
+
+        * inspector/worker/debugger-multiple-targets-pause-expected.txt: Added.
+        * inspector/worker/debugger-multiple-targets-pause.html: Added.
+        * inspector/worker/resources/worker-debugger-thread-1.js: Added.
+        * inspector/worker/resources/worker-debugger-thread-2.js: Added.
+        This tests uses a 250ms timeout because we have to have the worker thread
+        evaluate some work and trigger a pause on other threads before their work
+        starts. On debug builds, shorter times, like 100ms, would not be enough.
+
+        * inspector/worker/debugger-pause-expected.txt:
+        * inspector/worker/debugger-pause.html:
+        Now that all threads pause, the first InspectorTest.log evaluates JavaScript on
+        the page and causes a pause. So make the first log empty to keep the test unchanged.
+
+        * inspector/worker/runtime-basic-expected.txt:
+        * inspector/unit-tests/target-manager-expected.txt:
+        Updated display name of the mainTarget.
+
 2016-11-14  Myles C. Maxfield  <mmaxfield@apple.com>
 
         [WebGL2] Teach WebGLRenderingContextBase about new texture internal formats
diff --git a/LayoutTests/inspector/debugger/continueUntilNextRunLoop-expected.txt b/LayoutTests/inspector/debugger/continueUntilNextRunLoop-expected.txt
new file mode 100644 (file)
index 0000000..fa3c22f
--- /dev/null
@@ -0,0 +1,14 @@
+Debugger.continueUntilNextRunLoop
+
+
+== Running test suite: Debugger.continueUntilNextRunLoop
+-- Running test case: Debugger.Unpaused.continueUntilNextRunLoop
+PASS: Should produce an error if not paused.
+Can only perform operation while paused.
+
+-- Running test case: Debugger.Unpaused.continueUntilNextRunLoop
+PASS: Received First Pause Event.
+PASS: Should be paused in pause1.
+PASS: Received Second Pause Event.
+PASS: Should be paused in pause2.
+
diff --git a/LayoutTests/inspector/debugger/continueUntilNextRunLoop.html b/LayoutTests/inspector/debugger/continueUntilNextRunLoop.html
new file mode 100644 (file)
index 0000000..46663db
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function triggerMultiplePauses() {
+    setTimeout(pause1, 0);
+    setTimeout(pause2, 0);
+}
+
+function pause1() {
+    debugger;
+}
+
+function pause2() {
+    debugger;
+}
+
+function test()
+{
+    InspectorTest.debug();
+
+    function topCallFrameName() {
+        let targetData = WebInspector.debuggerManager.dataForTarget(WebInspector.mainTarget);
+        return targetData.callFrames[0].functionName;
+    }
+
+    let suite = InspectorTest.createAsyncSuite("Debugger.continueUntilNextRunLoop");
+
+    suite.addTestCase({
+        name: "Debugger.Unpaused.continueUntilNextRunLoop",
+        description: "Debugger.continueUntilNextRunLoop should only work when paused.",
+        test(resolve, reject) {
+            DebuggerAgent.continueUntilNextRunLoop((error) => {
+                InspectorTest.expectThat(error, "Should produce an error if not paused.");
+                InspectorTest.log(error);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Debugger.Unpaused.continueUntilNextRunLoop",
+        description: "Debugger.continueUntilNextRunLoop should only work when paused.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage("triggerMultiplePauses()");
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                InspectorTest.pass("Received First Pause Event.");
+                InspectorTest.expectEqual(topCallFrameName(), "pause1", "Should be paused in pause1.");
+                WebInspector.debuggerManager.continueUntilNextRunLoop(WebInspector.mainTarget);
+                WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                    InspectorTest.pass("Received Second Pause Event.");
+                    InspectorTest.expectEqual(topCallFrameName(), "pause2", "Should be paused in pause2.");
+                    WebInspector.debuggerManager.resume().then(resolve, reject);
+                });
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Debugger.continueUntilNextRunLoop</p>
+</body>
+</html>
index 14de4fe..bc6f9ed 100644 (file)
@@ -7,17 +7,17 @@ PASS: Targets list should always start out with the main target.
 PASS: Target list should always contain the main target.
 PASS: Main target should have an ExecutionContext.
 PASS: Main target should have the global RuntimeAgent.
-Target - Main - Main Frame
+Target - Main - Page
 
 -- Running test case: TargetManager.WorkerTarget.Create
 PASS: Added Target should have Worker type.
 PASS: Added Target should have an ExecutionContext.
 PASS: Added Target should have a RuntimeAgent.
 PASS: Added Target RuntimeAgent should not be the global RuntimeAgent.
-Target - Main - Main Frame
+Target - Main - Page
 Target - Worker - worker-1.js
 
 -- Running test case: TargetManager.WorkerTarget.Remove
 PASS: Removed Target should have Worker type.
-Target - Main - Main Frame
+Target - Main - Page
 
diff --git a/LayoutTests/inspector/worker/debugger-multiple-targets-pause-expected.txt b/LayoutTests/inspector/worker/debugger-multiple-targets-pause-expected.txt
new file mode 100644 (file)
index 0000000..68da3ba
--- /dev/null
@@ -0,0 +1,58 @@
+Ensure we can pause in multiple targets and evaluate in each.
+
+
+== Running test suite: Worker.Debugger.Threads
+-- Running test case: Worker.Debugger.Threads.CreateThreads
+PASS: Created Worker 1
+PASS: Created Worker 2
+
+-- Running test case: Worker.Debugger.Threads.Pause
+PASS: Paused event should happen before CallFramesDidChange event.
+PASS: In Paused event all other Targets should be pausing.
+PASS: Worker 1 should be the first to pause.
+PASS: All Targets should eventually pause.
+
+TARGET: Page
+   CALL FRAME #1: workOnMainThread
+TARGET: worker-debugger-thread-1.js
+ * CALL FRAME #1: foo
+   CALL FRAME #2: workInThread1
+TARGET: worker-debugger-thread-2.js
+   CALL FRAME #1: workInThread2
+
+-- Running test case: Worker.Debugger.Threads.Paused.Worker1Evaluate
+PASS: Evaluated result in worker-debugger-thread-1.js should be "worker thread 1".
+
+-- Running test case: Worker.Debugger.Threads.Paused.Worker2Evaluate
+PASS: Evaluated result in worker-debugger-thread-2.js should be "worker thread 2".
+
+-- Running test case: Worker.Debugger.Threads.Paused.MainEvaluate
+PASS: Evaluated result in Page should be "main thread".
+
+-- Running test case: Worker.Debugger.Threads.Paused.Worker1.StepOut
+PASS: Should receive CallFramesDidChange for Worker 1.
+
+TARGET: Page
+   CALL FRAME #1: workOnMainThread
+TARGET: worker-debugger-thread-1.js
+ * CALL FRAME #1: workInThread1
+TARGET: worker-debugger-thread-2.js
+   CALL FRAME #1: workInThread2
+
+-- Running test case: Worker.Debugger.Threads.Paused.Worker2.ResumeThread
+PASS: Should Receive CallFramesDidChange for Worker 2.
+PASS: Worker 2 should be pausing.
+PASS: Should Receive CallFramesDidChange for Worker 2.
+PASS: Worker 2 should be paused.
+
+TARGET: Page
+   CALL FRAME #1: workOnMainThread
+TARGET: worker-debugger-thread-1.js
+ * CALL FRAME #1: workInThread1
+TARGET: worker-debugger-thread-2.js
+   CALL FRAME #1: laterWorkInThread2
+
+-- Running test case: Worker.Debugger.Threads.Complete
+PASS: Received Resume event.
+PASS: All Targets should be unpaused.
+
diff --git a/LayoutTests/inspector/worker/debugger-multiple-targets-pause.html b/LayoutTests/inspector/worker/debugger-multiple-targets-pause.html
new file mode 100644 (file)
index 0000000..6f4cd39
--- /dev/null
@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="../debugger/resources/log-pause-location.js"></script>
+<script>
+let worker1, worker2;
+let testVariable = "main thread";
+
+function triggerStartWorkers() {
+    worker1 = new Worker("resources/worker-debugger-thread-1.js");
+    worker2 = new Worker("resources/worker-debugger-thread-2.js");
+}
+
+function triggerWork() {
+    worker1.postMessage("doWork");
+    worker2.postMessage("doWork");
+    setTimeout(workOnMainThread, 250);
+}
+
+function workOnMainThread() {
+    TestPage.dispatchEventToFrontend("MainThreadDidWork");
+    Date.now();
+}
+
+function test()
+{
+    let mainTarget = WebInspector.mainTarget;
+    let workerTarget1;
+    let workerTarget2;
+
+    function areAllTargetsPaused() {
+        for (let target of WebInspector.targets) {
+            let targetData = WebInspector.debuggerManager.dataForTarget(target);
+            if (!targetData.paused)
+                return false;
+        }
+        return true;
+    }
+
+    function areAllTargetsUnpaused() {
+        for (let target of WebInspector.targets) {
+            let targetData = WebInspector.debuggerManager.dataForTarget(target);
+            if (targetData.paused)
+                return false;
+        }
+        return true;
+    }
+
+    function dumpCallFrames() {
+        InspectorTest.log("");
+        for (let target of WebInspector.targets) {
+            InspectorTest.log(`TARGET: ${target.displayName}`);
+            let targetData = WebInspector.debuggerManager.dataForTarget(target);
+            let callFrames = targetData.callFrames;
+            for (let i = 0; i < callFrames.length; ++i) {
+                let callFrame = callFrames[i];
+                let active = callFrame === WebInspector.debuggerManager.activeCallFrame;
+                InspectorTest.log(` ${active ? "*" : " "} CALL FRAME #${i + 1}: ${callFrame.functionName}`);
+            }
+        }
+    }
+
+    let okayToReceiveMainThreadEvent = false;
+    InspectorTest.singleFireEventListener("MainThreadDidWork", (event) => {
+        if (!okayToReceiveMainThreadEvent) {
+            InspectorTest.fail("Main Thread's work fired before it could pause. Failing early.");
+            InspectorTest.completeTest();
+        }
+    });
+
+    let suite = InspectorTest.createAsyncSuite("Worker.Debugger.Threads");
+
+    suite.addTestCase({
+        name: "Worker.Debugger.Threads.CreateThreads",
+        description: "Spawn multiple targets.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`triggerStartWorkers()`);
+            WebInspector.targetManager.singleFireEventListener(WebInspector.TargetManager.Event.TargetAdded, (event) => {
+                InspectorTest.pass("Created Worker 1");
+                workerTarget1 = event.data.target;
+                WebInspector.targetManager.singleFireEventListener(WebInspector.TargetManager.Event.TargetAdded, (event) => {
+                    InspectorTest.pass("Created Worker 2");
+                    workerTarget2 = event.data.target;
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Worker.Debugger.Threads.Pause",
+        description: "Should be able to pause in multiple targets.",
+        test(resolve, reject) {
+            InspectorTest.evaluateInPage(`triggerWork()`);
+
+            let receivedCallFramesDidChange = false;
+            let receivedPauseBeforeCallFramesDidChange = false;
+            let otherTargetsPausing = false;
+
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                receivedPauseBeforeCallFramesDidChange = !receivedCallFramesDidChange;
+                otherTargetsPausing = WebInspector.debuggerManager.dataForTarget(mainTarget).pausing && WebInspector.debuggerManager.dataForTarget(workerTarget2).pausing;
+            });
+
+            let listener = WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                receivedCallFramesDidChange = true;
+                if (!areAllTargetsPaused())
+                    return;
+
+                WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, listener);
+
+                let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+                InspectorTest.assert(activeCallFrame.target === workerTarget1);
+
+                InspectorTest.expectThat(receivedPauseBeforeCallFramesDidChange, "Paused event should happen before CallFramesDidChange event.");
+                InspectorTest.expectThat(otherTargetsPausing, "In Paused event all other Targets should be pausing.");
+                InspectorTest.expectEqual(activeCallFrame.target.displayName, "worker-debugger-thread-1.js", "Worker 1 should be the first to pause.");
+                InspectorTest.pass("All Targets should eventually pause.");
+                dumpCallFrames();
+                resolve();
+            });
+        }
+    });
+
+    function addEvaluateTestCase({name, targetResolver, expected}) {
+        suite.addTestCase({
+            name, description: "Should be able to evaluate in different threads while paused.",
+            test(resolve, reject) {
+                let target = targetResolver();
+                let targetData = WebInspector.debuggerManager.dataForTarget(target);
+                WebInspector.debuggerManager.activeCallFrame = targetData.callFrames[0];
+                WebInspector.runtimeManager.evaluateInInspectedWindow("testVariable", {objectGroup: "test", includeCommandLineAPI: true}, (remoteObject) => {
+                    InspectorTest.expectEqual(remoteObject.description, expected, `Evaluated result in ${target.displayName} should be ${JSON.stringify(expected)}.`)
+                    resolve();
+                });
+            }
+        });
+    }
+
+    addEvaluateTestCase({
+        name: "Worker.Debugger.Threads.Paused.Worker1Evaluate",
+        targetResolver: () => workerTarget1,
+        expected: "worker thread 1",
+    });
+
+    addEvaluateTestCase({
+        name: "Worker.Debugger.Threads.Paused.Worker2Evaluate",
+        targetResolver: () => workerTarget2,
+        expected: "worker thread 2",
+    });
+
+    addEvaluateTestCase({
+        name: "Worker.Debugger.Threads.Paused.MainEvaluate",
+        targetResolver: () => mainTarget,
+        expected: "main thread",
+    });
+
+    suite.addTestCase({
+        name: "Worker.Debugger.Threads.Paused.Worker1.StepOut",
+        description: "Should be able to step in individual threads and not affect the others.",
+        test(resolve, reject) {
+            let targetData = WebInspector.debuggerManager.dataForTarget(workerTarget1);
+            WebInspector.debuggerManager.activeCallFrame = targetData.callFrames[0];
+            WebInspector.debuggerManager.stepOut();
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                InspectorTest.expectEqual(event.data.target, workerTarget1, "Should receive CallFramesDidChange for Worker 1.");
+                dumpCallFrames();
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Worker.Debugger.Threads.Paused.Worker2.ResumeThread",
+        description: "Should be able to resume an individual thread and not affect the others.",
+        test(resolve, reject) {
+            let targetData = WebInspector.debuggerManager.dataForTarget(workerTarget2);
+            WebInspector.debuggerManager.continueUntilNextRunLoop(workerTarget2);
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                InspectorTest.expectEqual(event.data.target, workerTarget2, "Should Receive CallFramesDidChange for Worker 2.");
+                InspectorTest.expectThat(targetData.pausing, "Worker 2 should be pausing.");
+                WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, (event) => {
+                    InspectorTest.expectEqual(event.data.target, workerTarget2, "Should Receive CallFramesDidChange for Worker 2.");
+                    InspectorTest.expectThat(targetData.paused, "Worker 2 should be paused.");
+                    dumpCallFrames();
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Worker.Debugger.Threads.Complete",
+        description: "Resume all threads for the test to complete.",
+        test(resolve, reject) {
+            okayToReceiveMainThreadEvent = true;
+            WebInspector.debuggerManager.resume();
+            WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Resumed, () => {
+                InspectorTest.pass("Received Resume event.");
+                InspectorTest.expectThat(areAllTargetsUnpaused(), "All Targets should be unpaused.");
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Ensure we can pause in multiple targets and evaluate in each.</p>
+</body>
+</html>
index a6fcd60..ee6b5fd 100644 (file)
@@ -16,6 +16,7 @@ PAUSE AT triggerDebuggerStatement:3:5
       5    
 
 
+
 -- Running test case: Worker.Debugger.Pause.Breakpoint
 PASS: Paused
 PASS: Should be paused in a Worker CallFrame.
@@ -30,6 +31,7 @@ PAUSE AT triggerBreakpoint:9:5
      11    
 
 
+
 -- Running test case: Worker.Debugger.Pause.Exception
 PASS: Paused
 PASS: Should be paused in a Worker CallFrame.
@@ -43,6 +45,7 @@ PAUSE AT triggerException:14:9
      15    
      16    function triggerAssertion() {
 
+
 Uncaught exception in test page: TypeError: undefined is not an object (evaluating '[].x.x') [worker-debugger-pause.js:14]
 
 -- Running test case: Worker.Debugger.Pause.Assert
@@ -59,3 +62,4 @@ PAUSE AT triggerAssertion:18:19
      20    onmessage = function(event) {
 
 
+
index 1d156f7..1549e60 100644 (file)
@@ -31,6 +31,7 @@ function test()
         test(resolve, reject) {
             InspectorTest.evaluateInPage(`worker.postMessage("triggerDebuggerStatement")`);
             WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                InspectorTest.log(""); // This evaluation will pause the MainThread.
                 InspectorTest.pass("Paused");
                 InspectorTest.expectEqual(WebInspector.debuggerManager.activeCallFrame.target, workerTarget, "Should be paused in a Worker CallFrame.");
                 InspectorTest.expectEqual(workerDebuggerData.pauseReason, WebInspector.DebuggerManager.PauseReason.DebuggerStatement, "Pause reason should be a debugger statement.");
@@ -49,6 +50,7 @@ function test()
             WebInspector.debuggerManager.addBreakpoint(breakpoint);
             InspectorTest.evaluateInPage(`worker.postMessage("triggerBreakpoint")`);
             WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                InspectorTest.log(""); // This evaluation will pause the MainThread.
                 InspectorTest.pass("Paused");
                 InspectorTest.expectEqual(WebInspector.debuggerManager.activeCallFrame.target, workerTarget, "Should be paused in a Worker CallFrame.");
                 InspectorTest.expectEqual(workerDebuggerData.pauseReason, WebInspector.DebuggerManager.PauseReason.Breakpoint, "Pause reason should be a breakpoint.");
@@ -64,6 +66,7 @@ function test()
         test(resolve, reject) {
             InspectorTest.evaluateInPage(`worker.postMessage("triggerException")`);
             WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                InspectorTest.log(""); // This evaluation will pause the MainThread.
                 InspectorTest.pass("Paused");
                 InspectorTest.expectEqual(WebInspector.debuggerManager.activeCallFrame.target, workerTarget, "Should be paused in a Worker CallFrame.");
                 InspectorTest.expectEqual(workerDebuggerData.pauseReason, WebInspector.DebuggerManager.PauseReason.Exception, "Pause reason should be an exception.");
@@ -79,6 +82,7 @@ function test()
         test(resolve, reject) {
             InspectorTest.evaluateInPage(`worker.postMessage("triggerAssertion")`);
             WebInspector.debuggerManager.singleFireEventListener(WebInspector.DebuggerManager.Event.Paused, (event) => {
+                InspectorTest.log(""); // This evaluation will pause the MainThread.
                 InspectorTest.pass("Paused");
                 InspectorTest.expectEqual(WebInspector.debuggerManager.activeCallFrame.target, workerTarget, "Should be paused in a Worker CallFrame.");
                 InspectorTest.expectEqual(workerDebuggerData.pauseReason, WebInspector.DebuggerManager.PauseReason.Assertion, "Pause reason should be an exception.");
index 415eefe..0ef2a53 100644 (file)
@@ -36,8 +36,6 @@ function triggerWorkerImportScript() {
 
 function test()
 {
-    InspectorTest.debug();
-
     let workerTarget = null;
     let mainTarget = WebInspector.mainTarget;
 
diff --git a/LayoutTests/inspector/worker/resources/worker-debugger-thread-1.js b/LayoutTests/inspector/worker/resources/worker-debugger-thread-1.js
new file mode 100644 (file)
index 0000000..d8e181d
--- /dev/null
@@ -0,0 +1,14 @@
+let testVariable = "worker thread 1";
+
+onmessage = function(event) {
+    if (event.data === "doWork")
+        setTimeout(workInThread1, 0)
+}
+
+function workInThread1() {
+    foo();
+}
+
+function foo() {
+    debugger;
+}
diff --git a/LayoutTests/inspector/worker/resources/worker-debugger-thread-2.js b/LayoutTests/inspector/worker/resources/worker-debugger-thread-2.js
new file mode 100644 (file)
index 0000000..d47d93c
--- /dev/null
@@ -0,0 +1,16 @@
+let testVariable = "worker thread 2";
+
+onmessage = function(event) {
+    if (event.data === "doWork") {
+        setTimeout(workInThread2, 250);
+        setTimeout(laterWorkInThread2, 250);
+    }
+}
+
+function workInThread2() {
+    Date.now();
+}
+
+function laterWorkInThread2() {
+    Date.now();
+}
index eb2ada7..1f14458 100644 (file)
@@ -3,13 +3,13 @@ Test for RuntimeAgent in a Worker.
 
 == Running test suite: Worker.Runtime.basic
 -- Running test case: Main.Runtime.evaluate
-Target - Main Frame - passphrase - page-passphrase
+Target - Page - passphrase - page-passphrase
 
 -- Running test case: Worker.Runtime.evaluate
 Target - worker-1.js - passphrase - worker-passphrase
 
 -- Running test case: Main.Runtime.RemoteObjectAndPropertyDescriptor.
-Target - Main Frame - location and href - Location: inspector/worker/runtime-basic.html
+Target - Page - location and href - Location: inspector/worker/runtime-basic.html
 
 -- Running test case: Worker.Runtime.RemoteObjectAndPropertyDescriptor.
 Target - worker-1.js - location and href - WorkerLocation: inspector/worker/resources/worker-1.js
index c926b8f..1996f0f 100644 (file)
@@ -1,3 +1,47 @@
+2016-11-14  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Worker debugging should pause all targets and view call frames in all targets
+        https://bugs.webkit.org/show_bug.cgi?id=164305
+        <rdar://problem/29056192>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.prototype._propertyDescriptors):
+        Accessing __proto__ does a ToThis(...) conversion on the receiver.
+        In the case of GlobalObjects (such as WorkerGlobalScope when paused)
+        this would return undefined and throw an exception. We can use
+        Object.getPrototypeOf to avoid that conversion and possible error.
+
+        * inspector/protocol/Debugger.json:
+        Provide a new way to effectively `resume` + `pause` immediately.
+        This must be implemented on the backend to correctly synchronize
+        the resuming and pausing.
+
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::continueUntilNextRunLoop):
+        Treat this as `resume` and `pause`. Resume now, and trigger
+        a pause if the VM becomes idle and we didn't pause before then
+        (such as hitting a breakpoint after we resumed).
+
+        (Inspector::InspectorDebuggerAgent::pause):
+        (Inspector::InspectorDebuggerAgent::resume):
+        (Inspector::InspectorDebuggerAgent::schedulePauseOnNextStatement):
+        (Inspector::InspectorDebuggerAgent::cancelPauseOnNextStatement):
+        Clean up and correct pause on next statement logic.
+
+        (Inspector::InspectorDebuggerAgent::registerIdleHandler):
+        (Inspector::InspectorDebuggerAgent::willStepAndMayBecomeIdle):
+        (Inspector::InspectorDebuggerAgent::didBecomeIdle):
+        (Inspector::InspectorDebuggerAgent::didBecomeIdleAfterStepping): Deleted.
+        The idle handler may now also trigger a pause in the case
+        where continueUntilNextRunLoop resumed and wants to pause.
+
+        (Inspector::InspectorDebuggerAgent::didPause):
+        Eliminate the useless didPause. The DOMDebugger was keeping track
+        of its own state that was worse then the state in DebuggerAgent.
+
 2016-11-14  Filip Pizlo  <fpizlo@apple.com>
 
         Unreviewed, fix cloop.
index c8b05e9..44490de 100644 (file)
@@ -688,7 +688,7 @@ InjectedScript.prototype = {
             isArrayLike = injectedScript._subtype(object) === "array" && isFinite(object.length) && object.length > 0;
         } catch(e) {}
 
-        for (var o = object; this._isDefined(o); o = o.__proto__) {
+        for (var o = object; this._isDefined(o); o = Object.getPrototypeOf(o)) {
             var isOwnProperty = o === object;
 
             if (isArrayLike && isOwnProperty)
index 30880ed..c9d7fde 100644 (file)
@@ -476,6 +476,18 @@ void InspectorDebuggerAgent::removeBreakpoint(ErrorString&, const String& breakp
     }
 }
 
+void InspectorDebuggerAgent::continueUntilNextRunLoop(ErrorString& errorString)
+{
+    if (!assertPaused(errorString))
+        return;
+
+    resume(errorString);
+
+    m_enablePauseWhenIdle = true;
+
+    registerIdleHandler();
+}
+
 void InspectorDebuggerAgent::continueToLocation(ErrorString& errorString, const InspectorObject& location)
 {
     if (!assertPaused(errorString))
@@ -572,33 +584,38 @@ void InspectorDebuggerAgent::schedulePauseOnNextStatement(DebuggerFrontendDispat
     if (m_javaScriptPauseScheduled)
         return;
 
+    m_javaScriptPauseScheduled = true;
+
     m_breakReason = breakReason;
     m_breakAuxData = WTFMove(data);
+
     JSC::JSLockHolder locker(m_scriptDebugServer.vm());
     m_scriptDebugServer.setPauseOnNextStatement(true);
 }
 
 void InspectorDebuggerAgent::cancelPauseOnNextStatement()
 {
-    if (m_javaScriptPauseScheduled)
+    if (!m_javaScriptPauseScheduled)
         return;
 
     clearBreakDetails();
     m_scriptDebugServer.setPauseOnNextStatement(false);
+    m_enablePauseWhenIdle = false;
 }
 
 void InspectorDebuggerAgent::pause(ErrorString&)
 {
     schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason::PauseOnNextStatement, nullptr);
-
-    m_javaScriptPauseScheduled = true;
 }
 
 void InspectorDebuggerAgent::resume(ErrorString& errorString)
 {
-    if (!assertPaused(errorString))
+    if (!m_pausedScriptState && !m_javaScriptPauseScheduled) {
+        errorString = ASCIILiteral("Was not paused or waiting to pause");
         return;
+    }
 
+    cancelPauseOnNextStatement();
     m_scriptDebugServer.continueProgram();
     m_conditionToDispatchResumed = ShouldDispatchResumed::WhenContinued;
 }
@@ -630,22 +647,27 @@ void InspectorDebuggerAgent::stepOut(ErrorString& errorString)
     m_scriptDebugServer.stepOutOfFunction();
 }
 
-void InspectorDebuggerAgent::willStepAndMayBecomeIdle()
+void InspectorDebuggerAgent::registerIdleHandler()
 {
-    // When stepping the backend must eventually trigger a "paused" or "resumed" event.
-    // If the step causes us to exit the VM, then we should issue "resumed".
-    m_conditionToDispatchResumed = ShouldDispatchResumed::WhenIdle;
-
     if (!m_registeredIdleCallback) {
         m_registeredIdleCallback = true;
         JSC::VM& vm = m_scriptDebugServer.vm();
         vm.whenIdle([this]() {
-            didBecomeIdleAfterStepping();
+            didBecomeIdle();
         });
     }
 }
 
-void InspectorDebuggerAgent::didBecomeIdleAfterStepping()
+void InspectorDebuggerAgent::willStepAndMayBecomeIdle()
+{
+    // When stepping the backend must eventually trigger a "paused" or "resumed" event.
+    // If the step causes us to exit the VM, then we should issue "resumed".
+    m_conditionToDispatchResumed = ShouldDispatchResumed::WhenIdle;
+
+    registerIdleHandler();
+}
+
+void InspectorDebuggerAgent::didBecomeIdle()
 {
     m_registeredIdleCallback = false;
 
@@ -653,6 +675,11 @@ void InspectorDebuggerAgent::didBecomeIdleAfterStepping()
         m_frontendDispatcher->resumed();
 
     m_conditionToDispatchResumed = ShouldDispatchResumed::No;
+
+    if (m_enablePauseWhenIdle) {
+        ErrorString ignored;
+        pause(ignored);
+    }
 }
 
 void InspectorDebuggerAgent::setPauseOnExceptions(ErrorString& errorString, const String& stringPauseState)
@@ -851,6 +878,7 @@ void InspectorDebuggerAgent::didPause(JSC::ExecState& scriptState, JSC::JSValue
     }
 
     m_conditionToDispatchResumed = ShouldDispatchResumed::No;
+    m_enablePauseWhenIdle = false;
 
     m_frontendDispatcher->paused(currentCallFrames(injectedScript), m_breakReason, m_breakAuxData);
 
@@ -861,9 +889,6 @@ void InspectorDebuggerAgent::didPause(JSC::ExecState& scriptState, JSC::JSValue
         m_continueToLocationBreakpointID = JSC::noBreakpointID;
     }
 
-    if (m_listener)
-        m_listener->didPause();
-
     RefPtr<Stopwatch> stopwatch = m_injectedScriptManager.inspectorEnvironment().executionStopwatch();
     if (stopwatch && stopwatch->isActive()) {
         stopwatch->stop();
index f334813..5f40106 100644 (file)
@@ -67,6 +67,7 @@ public:
     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;
     void removeBreakpoint(ErrorString&, const String& breakpointIdentifier) final;
+    void continueUntilNextRunLoop(ErrorString&) final;
     void continueToLocation(ErrorString&, const InspectorObject& location) final;
     void searchInContent(ErrorString&, const String& scriptID, const String& query, const bool* optionalCaseSensitive, const bool* optionalIsRegex, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::GenericTypes::SearchMatch>>&) final;
     void getScriptSource(ErrorString&, const String& scriptID, String* scriptSource) final;
@@ -90,6 +91,8 @@ public:
 
     void schedulePauseOnNextStatement(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<InspectorObject>&& data);
     void cancelPauseOnNextStatement();
+    bool pauseOnNextStatementEnabled() const { return m_javaScriptPauseScheduled; }
+
     void breakProgram(DebuggerFrontendDispatcher::Reason breakReason, RefPtr<InspectorObject>&& data);
     void scriptExecutionBlockedByCSP(const String& directiveText);
 
@@ -98,7 +101,6 @@ public:
         virtual ~Listener() { }
         virtual void debuggerWasEnabled() = 0;
         virtual void debuggerWasDisabled() = 0;
-        virtual void didPause() = 0;
     };
     void setListener(Listener* listener) { m_listener = listener; }
 
@@ -142,8 +144,9 @@ private:
     void clearExceptionValue();
 
     enum class ShouldDispatchResumed { No, WhenIdle, WhenContinued };
+    void registerIdleHandler();
     void willStepAndMayBecomeIdle();
-    void didBecomeIdleAfterStepping();
+    void didBecomeIdle();
 
     RefPtr<InspectorObject> buildBreakpointPauseReason(JSC::BreakpointID);
     RefPtr<InspectorObject> buildExceptionPauseReason(JSC::JSValue exception, const InjectedScript&);
@@ -170,6 +173,7 @@ private:
     DebuggerFrontendDispatcher::Reason m_breakReason;
     RefPtr<InspectorObject> m_breakAuxData;
     ShouldDispatchResumed m_conditionToDispatchResumed { ShouldDispatchResumed::No };
+    bool m_enablePauseWhenIdle { false };
     bool m_enabled { false };
     bool m_javaScriptPauseScheduled { false };
     bool m_hasExceptionValue { false };
index e58d23f..be5a605 100644 (file)
             "description": "Removes JavaScript breakpoint."
         },
         {
+            "name": "continueUntilNextRunLoop",
+            "description": "Continues execution until the current evaluation completes. This will trigger either a Debugger.paused or Debugger.resumed event."
+        },
+        {
             "name": "continueToLocation",
+            "description": "Continues execution until specific location is reached. This will trigger either a Debugger.paused or Debugger.resumed event.",
             "parameters": [
                 { "name": "location", "$ref": "Location", "description": "Location to continue to." }
-            ],
-            "description": "Continues execution until specific location is reached. This will trigger either a Debugger.paused or Debugger.resumed event."
+            ]
         },
         {
             "name": "stepOver",
index f80c92f..1c0b193 100644 (file)
@@ -1,3 +1,48 @@
+2016-11-14  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Worker debugging should pause all targets and view call frames in all targets
+        https://bugs.webkit.org/show_bug.cgi?id=164305
+        <rdar://problem/29056192>
+
+        Reviewed by Timothy Hatcher.
+
+        Tests: inspector/debugger/continueUntilNextRunLoop
+               inspector/worker/debugger-multiple-targets-pause
+
+        * workers/WorkerMessagingProxy.cpp:
+        (WebCore::WorkerMessagingProxy::postMessageToPageInspector):
+        Switch from postTask (callOnMainThread) to RunLoop::main().dispatch so
+        that a paused Worker can send Inspector protocol messages responses
+        back through the Main Page's InspectorWorkerAgent even if the Page
+        itself is paused and MainThread callbacks are paused.
+
+        * workers/WorkerRunLoop.h:
+        (WebCore::WorkerRunLoop::isNested):
+        * workers/WorkerRunLoop.cpp:
+        (WebCore::WorkerRunLoop::runInMode):
+        When running a nested WorkerRunLoop, running inspector debugger
+        commands, we should not fire timers on the Worker. Timers would
+        then be happening out of order and would not be debuggable.
+
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::fireEventListeners):
+        * inspector/InspectorDOMDebuggerAgent.cpp:
+        (WebCore::InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded):
+        (WebCore::InspectorDOMDebuggerAgent::clear):
+        (WebCore::InspectorDOMDebuggerAgent::didPause): Deleted.
+        * inspector/InspectorDOMDebuggerAgent.h:
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::willHandleEventImpl):
+        (WebCore::InspectorInstrumentation::didFireTimerImpl):
+        (WebCore::InspectorInstrumentation::didHandleEventImpl): Deleted.
+        (WebCore::InspectorInstrumentation::cancelPauseOnNativeEvent): Deleted.
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::willHandleEvent):
+        (WebCore::InspectorInstrumentation::didHandleEvent): Deleted.
+        Remove unnecessary code where WebCore is trying to keep track
+        of pause on next statement but that state is already more
+        accurately provided by InspectorDebuggerAgent.
+
 2016-11-14  Myles C. Maxfield  <mmaxfield@apple.com>
 
         [WebGL2] Teach WebGLRenderingContextBase about new texture internal formats
index 37f8023..61645ef 100644 (file)
@@ -247,9 +247,8 @@ void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners
         if (registeredListener->isPassive())
             event.setInPassiveListener(true);
 
-        auto cookie = InspectorInstrumentation::willHandleEvent(context, event);
+        InspectorInstrumentation::willHandleEvent(context, event);
         registeredListener->callback().handleEvent(context, &event);
-        InspectorInstrumentation::didHandleEvent(cookie);
 
         if (registeredListener->isPassive())
             event.setInPassiveListener(false);
index 22db6ef..00408a1 100644 (file)
@@ -85,11 +85,6 @@ void InspectorDOMDebuggerAgent::debuggerWasDisabled()
     disable();
 }
 
-void InspectorDOMDebuggerAgent::didPause()
-{
-    m_pauseInNextEventListener = false;
-}
-
 void InspectorDOMDebuggerAgent::disable()
 {
     m_instrumentingAgents.setInspectorDOMDebuggerAgent(nullptr);
@@ -348,15 +343,14 @@ void InspectorDOMDebuggerAgent::updateSubtreeBreakpoints(Node* node, uint32_t ro
 void InspectorDOMDebuggerAgent::pauseOnNativeEventIfNeeded(bool isDOMEvent, const String& eventName, bool synchronous)
 {
     String fullEventName = (isDOMEvent ? listenerEventCategoryType : instrumentationEventCategoryType) + eventName;
-    if (m_pauseInNextEventListener)
-        m_pauseInNextEventListener = false;
-    else {
-        if (!m_eventListenerBreakpoints.contains(fullEventName))
-            return;
-    }
+
+    bool shouldPause = m_debuggerAgent->pauseOnNextStatementEnabled() || m_eventListenerBreakpoints.contains(fullEventName);
+    if (!shouldPause)
+        return;
 
     Ref<InspectorObject> eventData = InspectorObject::create();
-    eventData->setString("eventName", fullEventName);
+    eventData->setString(ASCIILiteral("eventName"), fullEventName);
+
     if (synchronous)
         m_debuggerAgent->breakProgram(Inspector::DebuggerFrontendDispatcher::Reason::EventListener, WTFMove(eventData));
     else
@@ -409,7 +403,6 @@ void InspectorDOMDebuggerAgent::willSendXMLHttpRequest(const String& url)
 void InspectorDOMDebuggerAgent::clear()
 {
     m_domBreakpoints.clear();
-    m_pauseInNextEventListener = false;
 }
 
 } // namespace WebCore
index df966e6..fef6d48 100644 (file)
@@ -84,7 +84,6 @@ private:
     // Inspector::InspectorDebuggerAgent::Listener implementation.
     void debuggerWasEnabled() override;
     void debuggerWasDisabled() override;
-    void didPause() override;
     void disable();
 
     void descriptionForDOMEvent(Node& target, int breakpointType, bool insertion, Inspector::InspectorObject& description);
@@ -103,7 +102,6 @@ private:
     HashMap<Node*, uint32_t> m_domBreakpoints;
     HashSet<String> m_eventListenerBreakpoints;
     HashSet<String> m_xhrBreakpoints;
-    bool m_pauseInNextEventListener { false };
     bool m_pauseOnAllXHRsEnabled { false };
 };
 
index d23c2cf..bc3a566 100644 (file)
@@ -365,16 +365,9 @@ InspectorInstrumentationCookie InspectorInstrumentation::willDispatchEventImpl(I
     return InspectorInstrumentationCookie(instrumentingAgents, timelineAgentId);
 }
 
-InspectorInstrumentationCookie InspectorInstrumentation::willHandleEventImpl(InstrumentingAgents& instrumentingAgents, const Event& event)
+void InspectorInstrumentation::willHandleEventImpl(InstrumentingAgents& instrumentingAgents, const Event& event)
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, true, event.type(), false);
-    return InspectorInstrumentationCookie(instrumentingAgents, 0);
-}
-
-void InspectorInstrumentation::didHandleEventImpl(const InspectorInstrumentationCookie& cookie)
-{
-    if (cookie.isValid())
-        cancelPauseOnNativeEvent(*cookie.instrumentingAgents());
 }
 
 void InspectorInstrumentation::didDispatchEventImpl(const InspectorInstrumentationCookie& cookie)
@@ -440,9 +433,6 @@ InspectorInstrumentationCookie InspectorInstrumentation::willFireTimerImpl(Instr
 
 void InspectorInstrumentation::didFireTimerImpl(const InspectorInstrumentationCookie& cookie)
 {
-    if (cookie.isValid())
-        cancelPauseOnNativeEvent(*cookie.instrumentingAgents());
-
     if (InspectorTimelineAgent* timelineAgent = retrieveTimelineAgent(cookie))
         timelineAgent->didFireTimer();
 }
@@ -1146,12 +1136,6 @@ void InspectorInstrumentation::pauseOnNativeEventIfNeeded(InstrumentingAgents& i
         domDebuggerAgent->pauseOnNativeEventIfNeeded(isDOMEvent, eventName, synchronous);
 }
 
-void InspectorInstrumentation::cancelPauseOnNativeEvent(InstrumentingAgents& instrumentingAgents)
-{
-    if (InspectorDebuggerAgent* debuggerAgent = instrumentingAgents.inspectorDebuggerAgent())
-        debuggerAgent->cancelPauseOnNextStatement();
-}
-
 void InspectorInstrumentation::didRequestAnimationFrameImpl(InstrumentingAgents& instrumentingAgents, int callbackId, Frame* frame)
 {
     pauseOnNativeEventIfNeeded(instrumentingAgents, false, requestAnimationFrameEventName, true);
index dc486e7..f3d3ffa 100644 (file)
@@ -130,8 +130,7 @@ public:
     static void didCallFunction(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
     static InspectorInstrumentationCookie willDispatchEvent(Document&, const Event&, bool hasEventListeners);
     static void didDispatchEvent(const InspectorInstrumentationCookie&);
-    static InspectorInstrumentationCookie willHandleEvent(ScriptExecutionContext*, const Event&);
-    static void didHandleEvent(const InspectorInstrumentationCookie&);
+    static void willHandleEvent(ScriptExecutionContext*, const Event&);
     static InspectorInstrumentationCookie willDispatchEventOnWindow(Frame*, const Event&, DOMWindow&);
     static void didDispatchEventOnWindow(const InspectorInstrumentationCookie&);
     static InspectorInstrumentationCookie willEvaluateScript(Frame&, const String& url, int lineNumber);
@@ -303,8 +302,7 @@ private:
     static InspectorInstrumentationCookie willCallFunctionImpl(InstrumentingAgents&, const String& scriptName, int scriptLine, ScriptExecutionContext*);
     static void didCallFunctionImpl(const InspectorInstrumentationCookie&, ScriptExecutionContext*);
     static InspectorInstrumentationCookie willDispatchEventImpl(InstrumentingAgents&, Document&, const Event&, bool hasEventListeners);
-    static InspectorInstrumentationCookie willHandleEventImpl(InstrumentingAgents&, const Event&);
-    static void didHandleEventImpl(const InspectorInstrumentationCookie&);
+    static void willHandleEventImpl(InstrumentingAgents&, const Event&);
     static void didDispatchEventImpl(const InspectorInstrumentationCookie&);
     static InspectorInstrumentationCookie willDispatchEventOnWindowImpl(InstrumentingAgents&, const Event&, DOMWindow&);
     static void didDispatchEventOnWindowImpl(const InspectorInstrumentationCookie&);
@@ -442,7 +440,6 @@ private:
     static InspectorTimelineAgent* retrieveTimelineAgent(const InspectorInstrumentationCookie&);
 
     static void pauseOnNativeEventIfNeeded(InstrumentingAgents&, bool isDOMEvent, const String& eventName, bool synchronous);
-    static void cancelPauseOnNativeEvent(InstrumentingAgents&);
 
     WEBCORE_EXPORT static int s_frontendCounter;
 };
@@ -702,19 +699,11 @@ inline void InspectorInstrumentation::didDispatchEvent(const InspectorInstrument
         didDispatchEventImpl(cookie);
 }
 
-inline InspectorInstrumentationCookie InspectorInstrumentation::willHandleEvent(ScriptExecutionContext* context, const Event& event)
+inline void InspectorInstrumentation::willHandleEvent(ScriptExecutionContext* context, const Event& event)
 {
-    FAST_RETURN_IF_NO_FRONTENDS(InspectorInstrumentationCookie());
+    FAST_RETURN_IF_NO_FRONTENDS(void());
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForContext(context))
         return willHandleEventImpl(*instrumentingAgents, event);
-    return InspectorInstrumentationCookie();
-}
-
-inline void InspectorInstrumentation::didHandleEvent(const InspectorInstrumentationCookie& cookie)
-{
-    FAST_RETURN_IF_NO_FRONTENDS(void());
-    if (cookie.isValid())
-        didHandleEventImpl(cookie);
 }
 
 inline InspectorInstrumentationCookie InspectorInstrumentation::willDispatchEventOnWindow(Frame* frame, const Event& event, DOMWindow& window)
index b4f8c23..ea441aa 100644 (file)
@@ -44,6 +44,7 @@
 #include <inspector/ScriptCallStack.h>
 #include <runtime/ConsoleTypes.h>
 #include <wtf/MainThread.h>
+#include <wtf/RunLoop.h>
 
 namespace WebCore {
 
@@ -168,7 +169,7 @@ void WorkerMessagingProxy::postExceptionToWorkerObject(const String& errorMessag
 
 void WorkerMessagingProxy::postMessageToPageInspector(const String& message)
 {
-    m_scriptExecutionContext->postTask([this, message = message.isolatedCopy()] (ScriptExecutionContext&) {
+    RunLoop::main().dispatch([this, message = message.isolatedCopy()] {
         m_inspectorProxy->sendMessageFromWorkerToFrontend(message);
     });
 }
index fff6eed..65685d7 100644 (file)
@@ -192,7 +192,7 @@ MessageQueueWaitResult WorkerRunLoop::runInMode(WorkerGlobalScope* context, cons
         break;
 
     case MessageQueueTimeout:
-        if (!context->isClosing())
+        if (!context->isClosing() && !isNested())
             m_sharedTimer->fire();
 #if USE(CF)
         if (nextCFRunLoopTimerFireDate <= CFAbsoluteTimeGetCurrent())
index e53e599..317dfbd 100644 (file)
@@ -85,6 +85,8 @@ namespace WebCore {
         // This should only be called when the context is closed or loop has been terminated.
         void runCleanupTasks(WorkerGlobalScope*);
 
+        bool isNested() const { return m_nestedCount > 1; }
+
         MessageQueue<Task> m_messageQueue;
         std::unique_ptr<WorkerSharedTimer> m_sharedTimer;
         int m_nestedCount;
index feaaf78..5be5491 100644 (file)
@@ -1,3 +1,161 @@
+2016-11-14  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Worker debugging should pause all targets and view call frames in all targets
+        https://bugs.webkit.org/show_bug.cgi?id=164305
+        <rdar://problem/29056192>
+
+        Reviewed by Timothy Hatcher.
+
+        This implements a policy where, when one Target ("Thread") pauses
+        the frontend triggers a pause in all other Targets. The intended
+        user experience is "all threads pause" whenever the frontend shows
+        the debugger paused UI.
+
+        DebuggerManager has a few straight forward changes:
+
+            - The paused state reflects if any target is paused.
+            - The Paused Event is fired when going from !paused -> paused.
+              This means when the first target pauses.
+            - The Resumed Event is fired when going from paused -> !paused.
+              This means only after all targets have resumed.
+            - The CallFrameDidChange Event now includes the Target that updated.
+
+        When a Target first pauses the frontend then immediately pauses all
+        other Targets. This puts them into a "pausing" state (we display as
+        Idle) and they will pause as soon as they start executing JavaScript.
+
+        When a Target steps the "paused" state isn't changing. So this is
+        just a CallFramesDidChange update.
+
+        When clicking Resume we resume all targets. This is will be the normal,
+        expected way users resume execution. Note that one of the threads may
+        then hit a breakpoint and re-pause all threads.
+
+        Sometimes when multiple threads are paused you may want to run an
+        individual thread to completion but keep other threads paused. There
+        is a context menu on the ThreadTreeElement to resume just that
+        single thread. It will continue and pause for its next run loop.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Images/Thread.svg: Added.
+        * UserInterface/Images/gtk/Thread.svg: Added.
+        * UserInterface/Main.html:
+        New strings and files.
+
+        * UserInterface/Base/Main.js:
+        (WebInspector.loaded):
+        * UserInterface/Test/Test.js:
+        (WebInspector.loaded):
+        Place the TargetManager first since other managers may want to listen
+        for TargetAdded / TargetRemoved events.
+
+        * UserInterface/Controllers/DebuggerManager.js:
+        (WebInspector.DebuggerManager.prototype.get paused):
+        This is now a computed state.
+
+        (WebInspector.DebuggerManager.prototype.pause):
+        (WebInspector.DebuggerManager.prototype.resume):
+        Affect all targets.
+
+        (WebInspector.DebuggerManager.prototype.stepOver):
+        (WebInspector.DebuggerManager.prototype.stepInto):
+        (WebInspector.DebuggerManager.prototype.stepOut):
+        (WebInspector.DebuggerManager.prototype.reset):
+        Update to use the paused computed property.
+
+        (WebInspector.DebuggerManager.prototype.continueUntilNextRunLoop):
+        Issue the new Debugger.continueUntilNextRunLoop command
+        on a given target.
+
+        (WebInspector.DebuggerManager.prototype.initializeTarget):
+        When a new Target is created and we were already paused,
+        then start that Worker in a paused state.
+
+        (WebInspector.DebuggerManager.prototype.debuggerDidPause):
+        Recover from bad cases where the backend informs the frontend about
+        internal JavaScript that it shouldn't know about. Legacy backend do
+        this but also there are corner cases we need to handle.
+        Dispatch events appropriately now that multiple targets may be paused.
+
+        (WebInspector.DebuggerManager.prototype._didResumeInternal):
+        Dispatch events appropriately now that multiple targets may be paused.
+
+        (WebInspector.DebuggerManager.prototype._targetRemoved):
+        Remove debugger data for targets that go away to avoid leaks.
+
+        * UserInterface/Models/DebuggerData.js:
+        (WebInspector.DebuggerData):
+        (WebInspector.DebuggerData.prototype.get paused):
+        (WebInspector.DebuggerData.prototype.get pausing):
+        Move some more per-Target state into DebuggerData.
+
+        (WebInspector.DebuggerData.prototype.pauseIfNeeded):
+        (WebInspector.DebuggerData.prototype.resumeIfNeeded):
+        (WebInspector.DebuggerData.prototype.continueUntilNextRunLoop):
+        These should only be called by DebuggerManager. They correctly
+        update the state of the DebuggerData for this Target, and also
+        issue the underlying command to the target.
+
+        (WebInspector.DebuggerData.prototype.updateForPause):
+        (WebInspector.DebuggerData.prototype.updateForResume):
+        Handle a special case where continueUntilNextRunLoop triggers
+        an invisible "pause" on the backend that we should mirror.
+
+        * UserInterface/Protocol/Target.js:
+        (WebInspector.MainTarget):
+        (WebInspector.MainTarget.prototype.get displayName):
+        (WebInspector.MainTarget.prototype.initialize):
+        Better display names.
+
+        * UserInterface/Views/DebuggerSidebarPanel.js:
+        (WebInspector.DebuggerSidebarPanel):
+        (WebInspector.DebuggerSidebarPanel.prototype._debuggerDidPause):
+        (WebInspector.DebuggerSidebarPanel.prototype._debuggerDidResume):
+        (WebInspector.DebuggerSidebarPanel.prototype._updateSingleThreadCallStacks):
+        (WebInspector.DebuggerSidebarPanel.prototype._selectActiveCallFrameTreeElement):
+        (WebInspector.DebuggerSidebarPanel.prototype._showSingleThreadCallStacks):
+        (WebInspector.DebuggerSidebarPanel.prototype._showMultipleThreadCallStacks):
+        (WebInspector.DebuggerSidebarPanel.prototype._findThreadTreeElementForTarget):
+        (WebInspector.DebuggerSidebarPanel.prototype._targetAdded):
+        (WebInspector.DebuggerSidebarPanel.prototype._targetRemoved):
+        (WebInspector.DebuggerSidebarPanel.prototype._debuggerCallFramesDidChange):
+        (WebInspector.DebuggerSidebarPanel.prototype._debuggerActiveCallFrameDidChange):
+        The DebuggerSidebar still has a single "Call Stacks" section, but maintains
+        two TreeOutlines and only shows one at a time. The Single Thread view shows
+        a flat list of the call frames for the Main Target when it is the only target.
+        The Multiple Threads view shows a list of Threads and their call frames.
+        We always keep both up to date, because we may need to swap between them
+        purely as Targets are added / removed. There is a bit of extra logic to
+        ensure we select elements properly based only on the visible tree outline.
+
+        * UserInterface/Views/LogContentView.js:
+        (WebInspector.LogContentView.prototype.didAppendConsoleMessageView):
+        When evaluating in a particular target, "runAfterPendingDispatches"
+        must wait for all other commands in that particular target to have
+        completed. So use the target specific version.
+
+        * UserInterface/Views/NavigationSidebarPanel.js:
+        (WebInspector.NavigationSidebarPanel.prototype._isTreeElementWithoutRepresentedObject):
+        Gracefully handle a few more TreeElements without a represented object.
+
+        * UserInterface/Views/IdleTreeElement.css: Added.
+        (.details-section.call-stack .idle .icon):
+        * UserInterface/Views/IdleTreeElement.js: Added.
+        (WebInspector.IdleTreeElement):
+        Very basic tree element to encapsulate an Idle call frame with an
+        empty represented object.
+
+        * UserInterface/Views/ThreadTreeElement.css: Added.
+        (.details-section.call-stack .thread .icon):
+        * UserInterface/Views/ThreadTreeElement.js: Added.
+        (WebInspector.ThreadTreeElement):
+        (WebInspector.ThreadTreeElement.prototype.get target):
+        (WebInspector.ThreadTreeElement.prototype.refresh):
+        (WebInspector.ThreadTreeElement.prototype.onattach):
+        (WebInspector.ThreadTreeElement.prototype.oncontextmenu):
+        ThreadTreeElement has no represented object, but makes it easy
+        to refresh a list of CallFrameTreeElements for a given target.
+
 2016-11-14  Timothy Hatcher  <timothy@apple.com>
 
         Web Inspector: Disable Warning Filter in Debugger Tab By Default
index 002b757..2ce3845 100644 (file)
@@ -30,6 +30,7 @@ localizedStrings["%dpx"] = "%dpx";
 localizedStrings["%dpx²"] = "%dpx²";
 localizedStrings["%s (%s)"] = "%s (%s)";
 localizedStrings["%s (%s, %s)"] = "%s (%s, %s)";
+localizedStrings["%s (default)"] = "%s (default)";
 localizedStrings["%s (hidden)"] = "%s (hidden)";
 localizedStrings["%s Event Dispatched"] = "%s Event Dispatched";
 localizedStrings["%s Prototype"] = "%s Prototype";
@@ -183,7 +184,6 @@ localizedStrings["Comparison of total memory size at the end of the selected tim
 localizedStrings["Composite"] = "Composite";
 localizedStrings["Compressed"] = "Compressed";
 localizedStrings["Compression"] = "Compression";
-localizedStrings["computed"] = "computed";
 localizedStrings["Condition"] = "Condition";
 localizedStrings["Conditional expression"] = "Conditional expression";
 localizedStrings["Connection"] = "Connection";
@@ -239,7 +239,6 @@ localizedStrings["Debugger is disabled during a Timeline recording."] = "Debugge
 localizedStrings["Decoded"] = "Decoded";
 localizedStrings["Decoration"] = "Decoration";
 localizedStrings["Default"] = "Default";
-localizedStrings["default"] = "default";
 localizedStrings["Delay"] = "Delay";
 localizedStrings["Delete"] = "Delete";
 localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
@@ -411,6 +410,7 @@ localizedStrings["Icon Only"] = "Icon Only";
 localizedStrings["Icon and Text (Horizontal)"] = "Icon and Text (Horizontal)";
 localizedStrings["Icon and Text (Vertical)"] = "Icon and Text (Vertical)";
 localizedStrings["Identity"] = "Identity";
+localizedStrings["Idle"] = "Idle";
 localizedStrings["Ignore"] = "Ignore";
 localizedStrings["Ignored"] = "Ignored";
 localizedStrings["Image"] = "Image";
@@ -439,6 +439,7 @@ localizedStrings["Iterations"] = "Iterations";
 localizedStrings["JavaScript"] = "JavaScript";
 localizedStrings["JavaScript & Events"] = "JavaScript & Events";
 localizedStrings["JavaScript Allocations"] = "JavaScript Allocations";
+localizedStrings["JavaScript Context"] = "JavaScript Context";
 localizedStrings["Join"] = "Join";
 localizedStrings["Jump to Definition"] = "Jump to Definition";
 localizedStrings["Keep Log on Navigation"] = "Keep Log on Navigation";
@@ -478,7 +479,7 @@ localizedStrings["Log: "] = "Log: ";
 localizedStrings["Logs"] = "Logs";
 localizedStrings["Lowest: %s"] = "Lowest: %s";
 localizedStrings["MIME Type"] = "MIME Type";
-localizedStrings["Main Context"] = "Main Context";
+localizedStrings["Main"] = "Main";
 localizedStrings["Main Frame"] = "Main Frame";
 localizedStrings["Manifest URL"] = "Manifest URL";
 localizedStrings["Margin"] = "Margin";
@@ -511,7 +512,6 @@ localizedStrings["No Accessibility Information"] = "No Accessibility Information
 localizedStrings["No Application Cache information available"] = "No Application Cache information available";
 localizedStrings["No Attributes"] = "No Attributes";
 localizedStrings["No Box Model Information"] = "No Box Model Information";
-localizedStrings["No Call Frames"] = "No Call Frames";
 localizedStrings["No Chart Available"] = "No Chart Available";
 localizedStrings["No Child Layers"] = "No Child Layers";
 localizedStrings["No Entries."] = "No Entries.";
@@ -579,8 +579,6 @@ localizedStrings["Pause Reason"] = "Pause Reason";
 localizedStrings["Pause script execution (%s or %s)"] = "Pause script execution (%s or %s)";
 localizedStrings["Play Sound"] = "Play Sound";
 localizedStrings["Polite"] = "Polite";
-localizedStrings["popup"] = "popup";
-localizedStrings["popup, toggle"] = "popup, toggle";
 localizedStrings["Port"] = "Port";
 localizedStrings["Position"] = "Position";
 localizedStrings["Position X"] = "Position X";
@@ -637,6 +635,7 @@ localizedStrings["Resources"] = "Resources";
 localizedStrings["Response"] = "Response";
 localizedStrings["Response Headers"] = "Response Headers";
 localizedStrings["Restart (%s)"] = "Restart (%s)";
+localizedStrings["Resume Thread"] = "Resume Thread";
 localizedStrings["Retained Size"] = "Retained Size";
 localizedStrings["Return type for anonymous function"] = "Return type for anonymous function";
 localizedStrings["Return type for function: %s"] = "Return type for function: %s";
@@ -782,7 +781,6 @@ localizedStrings["Timer Installed"] = "Timer Installed";
 localizedStrings["Timer Removed"] = "Timer Removed";
 localizedStrings["Timestamp \u2014 %s"] = "Timestamp \u2014 %s";
 localizedStrings["Timing"] = "Timing";
-localizedStrings["toggle"] = "toggle";
 localizedStrings["Toggle Classes"] = "Toggle Classes";
 localizedStrings["Top"] = "Top";
 localizedStrings["Top Functions"] = "Top Functions";
@@ -845,11 +843,16 @@ localizedStrings["Y1"] = "Y1";
 localizedStrings["Y2"] = "Y2";
 localizedStrings["Yes"] = "Yes";
 localizedStrings["Z-Index"] = "Z-Index";
+localizedStrings["computed"] = "computed";
+localizedStrings["default"] = "default";
 localizedStrings["key"] = "key";
 localizedStrings["line "] = "line ";
 localizedStrings["originally %s"] = "originally %s";
+localizedStrings["popup"] = "popup";
+localizedStrings["popup, toggle"] = "popup, toggle";
 localizedStrings["spaces"] = "spaces";
 localizedStrings["time before stopping"] = "time before stopping";
 localizedStrings["times before stopping"] = "times before stopping";
+localizedStrings["toggle"] = "toggle";
 localizedStrings["value"] = "value";
 localizedStrings["“%s” Profile Recorded"] = "“%s” Profile Recorded";
index 6a40254..13ad2a4 100644 (file)
@@ -108,6 +108,7 @@ WebInspector.loaded = function()
 
     // Create the singleton managers next, before the user interface elements, so the user interface can register
     // as event listeners on these managers.
+    this.targetManager = new WebInspector.TargetManager;
     this.branchManager = new WebInspector.BranchManager;
     this.frameResourceManager = new WebInspector.FrameResourceManager;
     this.storageManager = new WebInspector.StorageManager;
@@ -126,7 +127,6 @@ WebInspector.loaded = function()
     this.layerTreeManager = new WebInspector.LayerTreeManager;
     this.dashboardManager = new WebInspector.DashboardManager;
     this.probeManager = new WebInspector.ProbeManager;
-    this.targetManager = new WebInspector.TargetManager;
     this.workerManager = new WebInspector.WorkerManager;
     this.replayManager = new WebInspector.ReplayManager;
 
index ef2b109..8021dd1 100644 (file)
@@ -43,6 +43,8 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingWillStart, this._timelineCapturingWillStart, this);
         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._timelineCapturingStopped, this);
 
+        WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
+
         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
 
         this._breakpointsSetting = new WebInspector.Setting("breakpoints", []);
@@ -71,11 +73,11 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
         this._nextBreakpointActionIdentifier = 1;
 
-        this._paused = false;
         this._activeCallFrame = null;
 
         this._internalWebKitScripts = [];
         this._targetDebuggerDataMap = new Map;
+        this._targetDebuggerDataMap.set(WebInspector.mainTarget, new WebInspector.DebuggerData(WebInspector.mainTarget));
 
         // Restore the correct breakpoints enabled setting if Web Inspector had
         // previously been left in a state where breakpoints were temporarily disabled.
@@ -110,7 +112,12 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     get paused()
     {
-        return this._paused;
+        for (let [target, targetData] of this._targetDebuggerDataMap) {
+            if (targetData.paused)
+                return true;
+        }
+
+        return false;
     }
 
     get activeCallFrame()
@@ -272,7 +279,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     pause()
     {
-        if (this._paused)
+        if (this.paused)
             return Promise.resolve();
 
         this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.WaitingToPause);
@@ -283,22 +290,16 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
             listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.Paused, resolve);
         });
 
-        // FIXME: <https://webkit.org/b/164305> Web Inspector: Worker debugging should pause all targets and view call frames in all targets
-        // We should pause all targets.
+        let promises = [];
+        for (let [target, targetData] of this._targetDebuggerDataMap)
+            promises.push(targetData.pauseIfNeeded());
 
-        let protocolResult = DebuggerAgent.pause()
-            .catch(function(error) {
-                listener.disconnect();
-                console.error("DebuggerManager.pause failed: ", error);
-                throw error;
-            });
-
-        return Promise.all([managerResult, protocolResult]);
+        return Promise.all([managerResult, ...promises]);
     }
 
     resume()
     {
-        if (!this._paused)
+        if (!this.paused)
             return Promise.resolve();
 
         let listener = new WebInspector.EventListener(this, true);
@@ -307,22 +308,16 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
             listener.connect(WebInspector.debuggerManager, WebInspector.DebuggerManager.Event.Resumed, resolve);
         });
 
-        // FIXME: <https://webkit.org/b/164305> Web Inspector: Worker debugging should pause all targets and view call frames in all targets
-        // We should resume all targets.
+        let promises = [];
+        for (let [target, targetData] of this._targetDebuggerDataMap)
+            promises.push(targetData.resumeIfNeeded());
 
-        let protocolResult = this._activeCallFrame.target.DebuggerAgent.resume()
-            .catch(function(error) {
-                listener.disconnect();
-                console.error("DebuggerManager.resume failed: ", error);
-                throw error;
-            });
-
-        return Promise.all([managerResult, protocolResult]);
+        return Promise.all([managerResult, ...promises]);
     }
 
     stepOver()
     {
-        if (!this._paused)
+        if (!this.paused)
             return Promise.reject(new Error("Cannot step over because debugger is not paused."));
 
         let listener = new WebInspector.EventListener(this, true);
@@ -343,7 +338,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     stepInto()
     {
-        if (!this._paused)
+        if (!this.paused)
             return Promise.reject(new Error("Cannot step into because debugger is not paused."));
 
         let listener = new WebInspector.EventListener(this, true);
@@ -364,7 +359,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     stepOut()
     {
-        if (!this._paused)
+        if (!this.paused)
             return Promise.reject(new Error("Cannot step out because debugger is not paused."));
 
         let listener = new WebInspector.EventListener(this, true);
@@ -383,6 +378,11 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         return Promise.all([managerResult, protocolResult]);
     }
 
+    continueUntilNextRunLoop(target)
+    {
+        return this.dataForTarget(target).continueUntilNextRunLoop();
+    }
+
     continueToLocation(script, lineNumber, columnNumber)
     {
         return script.target.DebuggerAgent.continueToLocation({scriptId: script.id, lineNumber, columnNumber});
@@ -477,6 +477,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
     initializeTarget(target)
     {
         let DebuggerAgent = target.DebuggerAgent;
+        let targetData = this.dataForTarget(target);
 
         // Initialize global state.
         DebuggerAgent.enable();
@@ -484,8 +485,8 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         DebuggerAgent.setPauseOnAssertions(this._assertionsBreakpointEnabledSetting.value);
         DebuggerAgent.setPauseOnExceptions(this._breakOnExceptionsState);
 
-        // FIXME: <https://webkit.org/b/164305> Web Inspector: Worker debugging should pause all targets and view call frames in all targets
-        // Pause this Target if we are currently paused.
+        if (this.paused)
+            targetData.pauseIfNeeded();
 
         // Initialize breakpoints.
         this._restoringBreakpoints = true;
@@ -524,12 +525,10 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
     {
         // Called from WebInspector.DebuggerObserver.
 
-        let wasPaused = this._paused;
+        let wasPaused = this.paused;
 
         WebInspector.Script.resetUniqueDisplayNameNumbers();
 
-        this._paused = false;
-
         this._internalWebKitScripts = [];
         this._targetDebuggerDataMap.clear();
 
@@ -560,9 +559,8 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
             this._delayedResumeTimeout = undefined;
         }
 
-        let wasPaused = this._paused;
-
-        this._paused = true;
+        let wasPaused = this.paused;
+        let targetData = this._targetDebuggerDataMap.get(target);
 
         let callFrames = [];
         let pauseReason = this._pauseReasonFromPayload(reason);
@@ -589,22 +587,40 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         let activeCallFrame = callFrames[0];
 
         if (!activeCallFrame) {
-            // This indicates we were pausing in internal scripts only (Injected Scripts, built-ins).
-            // Just resume and skip past this pause.
-            target.DebuggerAgent.resume();
+            // FIXME: This may not be safe for multiple threads/targets.
+            // This indicates we were pausing in internal scripts only (Injected Scripts).
+            // Just resume and skip past this pause. We should be fixing the backend to
+            // not send such pauses.
+            if (wasPaused)
+                target.DebuggerAgent.continueUntilNextRunLoop();
+            else
+                target.DebuggerAgent.resume();
             this._didResumeInternal(target);
             return;
         }
 
-        let targetData = this._targetDebuggerDataMap.get(target);
         targetData.updateForPause(callFrames, pauseReason, pauseData);
 
-        this._activeCallFrame = activeCallFrame;
+        // Pause other targets because at least one target has paused.
+        // FIXME: Should this be done on the backend?
+        for (let [otherTarget, otherTargetData] of this._targetDebuggerDataMap)
+            otherTargetData.pauseIfNeeded();
+
+        let activeCallFrameDidChange = this._activeCallFrame && this._activeCallFrame.target === target;
+        if (activeCallFrameDidChange)
+            this._activeCallFrame = activeCallFrame;
+        else if (!wasPaused) {
+            this._activeCallFrame = activeCallFrame;
+            activeCallFrameDidChange = true;
+        }
 
         if (!wasPaused)
             this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Paused);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+
+        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange, {target});
+
+        if (activeCallFrameDidChange)
+            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
     }
 
     debuggerDidResume(target)
@@ -1005,6 +1021,16 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
         this._stopDisablingBreakpointsTemporarily();
     }
 
+    _targetRemoved(event)
+    {
+        let wasPaused = this.paused;
+
+        this._targetDebuggerDataMap.delete(event.data.target);
+
+        if (!this.paused && wasPaused)
+            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
+    }
+
     _mainResourceDidChange(event)
     {
         if (!event.target.isMainFrame())
@@ -1015,7 +1041,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     _didResumeInternal(target)
     {
-        if (!this._paused)
+        if (!this.paused)
             return;
 
         if (this._delayedResumeTimeout) {
@@ -1023,14 +1049,21 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
             this._delayedResumeTimeout = undefined;
         }
 
-        this._paused = false;
-        this._activeCallFrame = null;
+        let activeCallFrameDidChange = false;
+        if (this._activeCallFrame && this._activeCallFrame.target === target) {
+            this._activeCallFrame = null;
+            activeCallFrameDidChange = true;
+        }
 
         this.dataForTarget(target).updateForResume();
 
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange);
-        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
+        if (!this.paused)
+            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.Resumed);
+
+        this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.CallFramesDidChange, {target});
+
+        if (activeCallFrameDidChange)
+            this.dispatchEventToListeners(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange);
     }
 
     _updateBreakOnExceptionsState()
diff --git a/Source/WebInspectorUI/UserInterface/Images/Thread.svg b/Source/WebInspectorUI/UserInterface/Images/Thread.svg
new file mode 100644 (file)
index 0000000..273f76e
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2016 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <circle fill="rgb(148, 183, 219)" stroke="rgb(106, 136, 170)" cx="8" cy="8" r="7.5"/>
+    <line stroke="rgb(106, 136, 170)" x1="8" y1="3" x2="8" y2="13" stroke-width="3.5" stroke-linecap="round"/>
+    <line stroke="rgb(106, 136, 170)" x1="5" y1="5" x2="5" y2="11" stroke-width="3.5" stroke-linecap="round"/>
+    <line stroke="rgb(106, 136, 170)" x1="11" y1="5" x2="11" y2="11" stroke-width="3.5" stroke-linecap="round"/>
+    <line stroke="white" x1="8" y1="3" x2="8" y2="13" stroke-width="1.5" stroke-linecap="round"/>
+    <line stroke="white" x1="5" y1="5" x2="5" y2="11" stroke-width="1.5" stroke-linecap="round"/>
+    <line stroke="white" x1="11" y1="5" x2="11" y2="11" stroke-width="1.5" stroke-linecap="round"/>
+</svg>
diff --git a/Source/WebInspectorUI/UserInterface/Images/gtk/Thread.svg b/Source/WebInspectorUI/UserInterface/Images/gtk/Thread.svg
new file mode 100644 (file)
index 0000000..f696414
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed under the Creative Commons Attribution-Share Alike 3.0 United States License (http://creativecommons.org/licenses/by-sa/3.0/) -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+ <defs>
+  <radialGradient id="a" cx="24.446" cy="35.878" r="20.531" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.85696 3.2529e-7 -1.253e-7 .33010 -12.949 .77181)">
+   <stop stop-color="#fff" offset="0"/>
+   <stop stop-color="#cbdbed" offset="1"/>
+  </radialGradient>
+  <linearGradient id="c" x1="321.57" x2="311.65" y1="145.52" y2="118.5" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.55569 0 0 .55568 -169.27 -70.906)">
+   <stop stop-color="#204a87" offset="0"/>
+   <stop stop-color="#bdd2e9" offset="1"/>
+  </linearGradient>
+ </defs>
+ <circle fill="url(#a)" fill-rule="evenodd" stroke="url(#c)" stroke-miterlimit="10" cx="8" cy="8" r="7.5"/>
+ <circle fill="transparent" fill-rule="evenodd" stroke="white" stroke-miterlimit="10" cx="8" cy="8" r="6.5"/>
+ <line stroke="url(#c)" x1="8" y1="4" x2="8" y2="12" stroke-width="3.5" stroke-linecap="round"/>
+ <line stroke="url(#c)" x1="5" y1="6" x2="5" y2="10" stroke-width="3.5" stroke-linecap="round"/>
+ <line stroke="url(#c)" x1="11" y1="6" x2="11" y2="10" stroke-width="3.5" stroke-linecap="round"/>
+ <line stroke="white" x1="8" y1="4" x2="8" y2="12" stroke-width="1.5" stroke-linecap="round"/>
+ <line stroke="white" x1="5" y1="6" x2="5" y2="10" stroke-width="1.5" stroke-linecap="round"/>
+ <line stroke="white" x1="11" y1="6" x2="11" y2="10" stroke-width="1.5" stroke-linecap="round"/>
+</svg>
index f69c949..b90c59e 100644 (file)
@@ -97,6 +97,7 @@
     <link rel="stylesheet" href="Views/HeapSnapshotInstancesContentView.css">
     <link rel="stylesheet" href="Views/HierarchicalPathComponent.css">
     <link rel="stylesheet" href="Views/HoverMenu.css">
+    <link rel="stylesheet" href="Views/IdleTreeElement.css">
     <link rel="stylesheet" href="Views/ImageResourceContentView.css">
     <link rel="stylesheet" href="Views/IndeterminateProgressSpinner.css">
     <link rel="stylesheet" href="Views/IndexedDatabaseObjectStoreContentView.css">
     <link rel="stylesheet" href="Views/TextNavigationItem.css">
     <link rel="stylesheet" href="Views/TextResourceContentView.css">
     <link rel="stylesheet" href="Views/TextToggleButtonNavigationItem.css">
+    <link rel="stylesheet" href="Views/ThreadTreeElement.css">
     <link rel="stylesheet" href="Views/TimelineDataGrid.css">
     <link rel="stylesheet" href="Views/TimelineIcons.css">
     <link rel="stylesheet" href="Views/TimelineOverview.css">
     <script src="Views/HeapSnapshotInstanceFetchMoreDataGridNode.js"></script>
     <script src="Views/HierarchicalPathNavigationItem.js"></script>
     <script src="Views/HoverMenu.js"></script>
+    <script src="Views/IdleTreeElement.js"></script>
     <script src="Views/ImageResourceContentView.js"></script>
     <script src="Views/IndeterminateProgressSpinner.js"></script>
     <script src="Views/IndexedDatabaseDetailsSidebarPanel.js"></script>
     <script src="Views/TextNavigationItem.js"></script>
     <script src="Views/TextResourceContentView.js"></script>
     <script src="Views/TextToggleButtonNavigationItem.js"></script>
+    <script src="Views/ThreadTreeElement.js"></script>
     <script src="Views/TimelineRecordBar.js"></script>
     <script src="Views/TimelineRecordFrame.js"></script>
     <script src="Views/TimelineRecordingContentView.js"></script>
index 79ccf52..07d0494 100644 (file)
@@ -33,20 +33,26 @@ WebInspector.DebuggerData = class DebuggerData extends WebInspector.Object
 
         this._target = target;
 
-        this._callFrames = [];
+        this._paused = false;
+        this._pausing = false;
         this._pauseReason = null;
         this._pauseData = null;
+        this._callFrames = [];
 
         this._scriptIdMap = new Map;
         this._scriptContentIdentifierMap = new Map;
+
+        this._makePausingAfterNextResume = false;
     }
 
     // Public
 
     get target() { return this._target; }
-    get callFrames() { return this._callFrames; }
+    get paused() { return this._paused; }
+    get pausing() { return this._pausing; }
     get pauseReason() { return this._pauseReason; }
     get pauseData() { return this._pauseData; }
+    get callFrames() { return this._callFrames; }
 
     get scripts()
     {
@@ -84,17 +90,62 @@ WebInspector.DebuggerData = class DebuggerData extends WebInspector.Object
         }
     }
 
+    pauseIfNeeded()
+    {
+        if (this._paused || this._pausing)
+            return Promise.resolve();
+
+        this._pausing = true;
+
+        return this._target.DebuggerAgent.pause();
+    }
+
+    resumeIfNeeded()
+    {
+        if (!this._paused && !this._pausing)
+            return Promise.resolve();
+
+        this._pausing = false;
+
+        return this._target.DebuggerAgent.resume();
+    }
+
+    continueUntilNextRunLoop()
+    {
+        if (!this._paused || this._pausing)
+            return Promise.resolve();
+
+        // The backend will automatically start pausing
+        // after resuming, so we need to match that here.
+        this._makePausingAfterNextResume = true;
+
+        return this._target.DebuggerAgent.continueUntilNextRunLoop();
+    }
+
     updateForPause(callFrames, pauseReason, pauseData)
     {
-        this._callFrames = callFrames;
+        this._paused = true;
+        this._pausing = false;
         this._pauseReason = pauseReason;
         this._pauseData = pauseData;
+        this._callFrames = callFrames;
+
+        // We paused, no need for auto-pausing.
+        this._makePausingAfterNextResume = false;
     }
 
     updateForResume()
     {
-        this._callFrames = [];
+        this._paused = false;
+        this._pausing = false;
         this._pauseReason = null;
         this._pauseData = null;
+        this._callFrames = [];
+
+        // We resumed, but may be auto-pausing.
+        if (this._makePausingAfterNextResume) {
+            this._makePausingAfterNextResume = false;
+            this._pausing = true;
+        }
     }
 };
index df94664..e853d97 100644 (file)
@@ -99,16 +99,22 @@ WebInspector.MainTarget = class MainTarget extends WebInspector.Target
 {
     constructor(connection)
     {
-        super("", "", WebInspector.Target.Type.Main, InspectorBackend.mainConnection);
+        super("main", "", WebInspector.Target.Type.Main, InspectorBackend.mainConnection);
     }
 
     // Protected (Target)
 
     get displayName()
     {
-        if (WebInspector.debuggableType === WebInspector.DebuggableType.Web)
-            return WebInspector.UIString("Main Frame");
-        return WebInspector.UIString("Main Context");
+        switch (WebInspector.debuggableType) {
+        case WebInspector.DebuggableType.Web:
+            return WebInspector.UIString("Page");
+        case WebInspector.DebuggableType.JavaScript:
+            return WebInspector.UIString("JavaScript Context");
+        default:
+            console.error("Unexpected debuggable type: ", WebInspector.debuggableType);
+            return WebInspector.UIString("Main");
+        }
     }
 
     get mainResource()
@@ -119,7 +125,8 @@ WebInspector.MainTarget = class MainTarget extends WebInspector.Target
 
     initialize()
     {
-        this._executionContext = new WebInspector.ExecutionContext(this, WebInspector.RuntimeManager.TopLevelContextExecutionIdentifier, this.displayName, true, null);
+        let displayName = WebInspector.debuggableType === WebInspector.DebuggableType.Web ? WebInspector.UIString("Main Frame") : this.displayName;
+        this._executionContext = new WebInspector.ExecutionContext(this, WebInspector.RuntimeManager.TopLevelContextExecutionIdentifier, displayName, true, null);
     }
 }
 
index 9ca29a7..f1538fb 100644 (file)
@@ -53,6 +53,7 @@ WebInspector.loaded = function()
     WebInspector.mainTarget = new WebInspector.MainTarget;
 
     // Instantiate controllers used by tests.
+    this.targetManager = new WebInspector.TargetManager;
     this.frameResourceManager = new WebInspector.FrameResourceManager;
     this.storageManager = new WebInspector.StorageManager;
     this.domTreeManager = new WebInspector.DOMTreeManager;
@@ -65,7 +66,6 @@ WebInspector.loaded = function()
     this.timelineManager = new WebInspector.TimelineManager;
     this.debuggerManager = new WebInspector.DebuggerManager;
     this.probeManager = new WebInspector.ProbeManager;
-    this.targetManager = new WebInspector.TargetManager;
     this.workerManager = new WebInspector.WorkerManager;
     this.replayManager = new WebInspector.ReplayManager;
 
index 78af7fa..bcfb61a 100644 (file)
@@ -36,7 +36,6 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         WebInspector.Target.addEventListener(WebInspector.Target.Event.ResourceAdded, this._resourceAdded, this);
 
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
-        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, this._debuggerCallFramesDidChange, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptAdded, this._scriptAdded, this);
@@ -44,12 +43,16 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
+        WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, this._debuggerCallFramesDidChange, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._debuggerActiveCallFrameDidChange, this);
         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.WaitingToPause, this._debuggerWaitingToPause, this);
 
         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingWillStart, this._timelineCapturingWillStart, this);
         WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._timelineCapturingStopped, this);
 
+        WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetAdded, this._targetAdded, this);
+        WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
+
         this._timelineRecordingWarningElement = document.createElement("div");
         this._timelineRecordingWarningElement.classList.add("warning-banner");
         this._timelineRecordingWarningElement.append(WebInspector.UIString("Debugger is disabled during a Timeline recording."), " ");
@@ -146,6 +149,8 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         let breakpointsSection = new WebInspector.DetailsSection("breakpoints", WebInspector.UIString("Breakpoints"), [breakpointsGroup]);
         this.contentView.element.appendChild(breakpointsSection.element);
 
+        this._breakpointSectionElement = breakpointsSection.element;
+
         this._breakpointsContentTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
         this._breakpointsContentTreeOutline.ondelete = this._breakpointTreeOutlineDeleteTreeElement.bind(this);
         this._breakpointsContentTreeOutline.oncontextmenu = this._breakpointTreeOutlineContextMenuTreeElement.bind(this);
@@ -167,15 +172,30 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         this._scriptsSection = new WebInspector.DetailsSection("scripts", WebInspector.UIString("Sources"), [scriptsGroup]);
         this.contentView.element.appendChild(this._scriptsSection.element);
 
-        this._callStackContentTreeOutline = this.createContentTreeOutline(true, true);
-        this._callStackContentTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
-        this._activeCallFrameTreeElement = null;
+        const dontHideByDefault = true;
+        const suppressFiltering = true;
+        this._callStackTreeOutline = this.createContentTreeOutline(dontHideByDefault, suppressFiltering);
+        this._callStackTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
+
+        this._singleThreadCallStackTreeOutline = this.createContentTreeOutline(dontHideByDefault, suppressFiltering);
+        this._singleThreadCallStackTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
 
-        this._callStackRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Call Frames"));
-        this._callStackRow.showEmptyMessage();
+        let mainTargetTreeElement = new WebInspector.ThreadTreeElement(WebInspector.mainTarget);
+        this._callStackTreeOutline.appendChild(mainTargetTreeElement);
 
-        let callStackGroup = new WebInspector.DetailsSectionGroup([this._callStackRow]);
-        this._callStackSection = new WebInspector.DetailsSection("call-stack", WebInspector.UIString("Call Stack"), [callStackGroup]);
+        this._multipleThreadsCallStackRow = new WebInspector.DetailsSectionRow;
+        this._multipleThreadsCallStackRow.element.appendChild(this._callStackTreeOutline.element);
+
+        this._singleThreadCallStackRow = new WebInspector.DetailsSectionRow;
+        this._singleThreadCallStackRow.element.appendChild(this._singleThreadCallStackTreeOutline.element);
+
+        this._callStackGroup = new WebInspector.DetailsSectionGroup([this._singleThreadCallStackRow]);
+        this._callStackSection = new WebInspector.DetailsSection("call-stack", WebInspector.UIString("Call Stack"), [this._callStackGroup]);
+
+        this._showingSingleThreadCallStack = true;
+
+        this._activeCallFrameTreeElement = null;
+        this._singleThreadActiveCallFrameTreeElement = null;
 
         this._pauseReasonTreeOutline = null;
 
@@ -321,6 +341,7 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
     _debuggerDidPause(event)
     {
         this.contentView.element.insertBefore(this._callStackSection.element, this.contentView.element.firstChild);
+
         if (this._updatePauseReason())
             this.contentView.element.insertBefore(this._pauseReasonSection.element, this.contentView.element.firstChild);
 
@@ -336,6 +357,7 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
     _debuggerDidResume(event)
     {
         this._callStackSection.element.remove();
+
         this._pauseReasonSection.element.remove();
 
         this._debuggerPauseResumeButtonItem.enabled = true;
@@ -576,6 +598,99 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         this._removeDebuggerTreeElement(breakpointTreeElement);
     }
 
+    _updateSingleThreadCallStacks()
+    {
+        let targetData = WebInspector.debuggerManager.dataForTarget(WebInspector.mainTarget);
+        let callFrames = targetData.callFrames;
+
+        this._singleThreadCallStackTreeOutline.removeChildren();
+
+        if (!callFrames.length) {
+            this._singleThreadCallStackTreeOutline.appendChild(new WebInspector.IdleTreeElement);
+            return;
+        }
+
+        let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+        let activeCallFrameTreeElement = null;
+
+        for (let callFrame of callFrames) {
+            let callFrameTreeElement = new WebInspector.CallFrameTreeElement(callFrame);
+            if (callFrame === activeCallFrame)
+                activeCallFrameTreeElement = callFrameTreeElement;
+            this._singleThreadCallStackTreeOutline.appendChild(callFrameTreeElement);
+        }
+
+        if (activeCallFrameTreeElement) {
+            activeCallFrameTreeElement.isActiveCallFrame = true;
+            if (this._showingSingleThreadCallStack)
+                activeCallFrameTreeElement.select(true, true);
+        }
+    }
+
+    _selectActiveCallFrameTreeElement(treeOutline)
+    {
+        let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+        if (activeCallFrame) {
+            let activeCallFrameTreeElement = treeOutline.findTreeElement(activeCallFrame);
+            if (activeCallFrameTreeElement)
+                activeCallFrameTreeElement.select(true, true);
+        }
+    }
+
+    _showSingleThreadCallStacks()
+    {
+        console.assert(!this._showingSingleThreadCallStack);
+        console.assert(WebInspector.targets.size === 1);
+
+        this._showingSingleThreadCallStack = true;
+
+        this._callStackGroup.rows = [this._singleThreadCallStackRow];
+
+        this._selectActiveCallFrameTreeElement(this._singleThreadCallStackTreeOutline);
+    }
+
+    _showMultipleThreadCallStacks()
+    {
+        console.assert(this._showingSingleThreadCallStack);
+        console.assert(WebInspector.targets.size > 1);
+
+        this._showingSingleThreadCallStack = false;
+
+        this._callStackGroup.rows = [this._multipleThreadsCallStackRow];
+
+        this._selectActiveCallFrameTreeElement(this._callStackTreeOutline);
+    }
+
+    _findThreadTreeElementForTarget(target)
+    {
+        for (let child of this._callStackTreeOutline.children) {
+            if (child.target === target)
+                return child;
+        }
+
+        return null;
+    }
+
+    _targetAdded(event)
+    {
+        let target = event.data.target;
+        let treeElement = new WebInspector.ThreadTreeElement(target);
+        this._callStackTreeOutline.appendChild(treeElement);
+
+        if (this._showingSingleThreadCallStack)
+            this._showMultipleThreadCallStacks();
+    }
+
+    _targetRemoved(event)
+    {
+        let target = event.data.target;
+        let treeElement = this._findThreadTreeElementForTarget(target);
+        this._callStackTreeOutline.removeChild(treeElement);
+
+        if (WebInspector.targets.size === 1)
+            this._showSingleThreadCallStacks();
+    }
+
     _handleDebuggerObjectDisplayLocationDidChange(event)
     {
         var debuggerObject = event.target;
@@ -617,59 +732,37 @@ WebInspector.DebuggerSidebarPanel = class DebuggerSidebarPanel extends WebInspec
         parentTreeElement.treeOutline.removeChild(parentTreeElement);
     }
 
-    _debuggerCallFramesDidChange()
+    _debuggerCallFramesDidChange(event)
     {
-        this._callStackContentTreeOutline.removeChildren();
-        this._activeCallFrameTreeElement = null;
-
-        if (!WebInspector.debuggerManager.activeCallFrame) {
-            this._callStackRow.showEmptyMessage();
-            return;
-        }
-
-        let target = WebInspector.debuggerManager.activeCallFrame.target;
-        let targetData = WebInspector.debuggerManager.dataForTarget(target);
-        let callFrames = targetData.callFrames;
-        if (!callFrames || !callFrames.length) {
-            this._callStackRow.showEmptyMessage();
-            return;
-        }
-
-        this._callStackRow.hideEmptyMessage();
-        this._callStackRow.element.appendChild(this._callStackContentTreeOutline.element);
+        let target = event.data.target;
+        let treeElement = this._findThreadTreeElementForTarget(target);
+        treeElement.refresh();
 
-        let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
-        for (let callFrame of callFrames) {
-            let callFrameTreeElement = new WebInspector.CallFrameTreeElement(callFrame);
-            if (callFrame === activeCallFrame)
-                this._activeCallFrameTreeElement = callFrameTreeElement;
-            this._callStackContentTreeOutline.appendChild(callFrameTreeElement);
-        }
-
-        if (this._activeCallFrameTreeElement) {
-            this._activeCallFrameTreeElement.select(true, true);
-            this._activeCallFrameTreeElement.isActiveCallFrame = true;
-        }
+        if (target === WebInspector.mainTarget)
+            this._updateSingleThreadCallStacks();
     }
 
     _debuggerActiveCallFrameDidChange()
     {
-        if (!WebInspector.debuggerManager.activeCallFrame)
-            return;
-
-        let target = WebInspector.debuggerManager.activeCallFrame.target;
-        let targetData = WebInspector.debuggerManager.dataForTarget(target);
-        let callFrames = targetData.callFrames;
-        if (!callFrames)
-            return;
-
-        if (this._activeCallFrameTreeElement)
+        if (this._activeCallFrameTreeElement) {
             this._activeCallFrameTreeElement.isActiveCallFrame = false;
+            this._activeCallFrameTreeElement = null;
+        }
+        if (this._singleThreadActiveCallFrameTreeElement) {
+            this._singleThreadActiveCallFrameTreeElement.isActiveCallFrame = false;
+            this._singleThreadActiveCallFrameTreeElement = null;
+        }
 
-        this._activeCallFrameTreeElement = this._callStackContentTreeOutline.findTreeElement(WebInspector.debuggerManager.activeCallFrame);
+        if (!WebInspector.debuggerManager.activeCallFrame)
+            return;
 
+        this._activeCallFrameTreeElement = this._callStackTreeOutline.findTreeElement(WebInspector.debuggerManager.activeCallFrame);
         if (this._activeCallFrameTreeElement)
             this._activeCallFrameTreeElement.isActiveCallFrame = true;
+
+        this._singleThreadActiveCallFrameTreeElement = this._singleThreadCallStackTreeOutline.findTreeElement(WebInspector.debuggerManager.activeCallFrame);
+        if (this._singleThreadActiveCallFrameTreeElement)
+            this._singleThreadActiveCallFrameTreeElement.isActiveCallFrame = true;
     }
 
     _breakpointsBeneathTreeElement(treeElement)
diff --git a/Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.css
new file mode 100644 (file)
index 0000000..5a04bba
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.details-section.call-stack .idle .icon {
+    content: url(../Images/Native.svg);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/IdleTreeElement.js
new file mode 100644 (file)
index 0000000..b5452b6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.IdleTreeElement = class IdleTreeElement extends WebInspector.GeneralTreeElement
+{
+    constructor(target)
+    {
+        super("idle", WebInspector.UIString("Idle"));
+    }
+};
index efe2d5e..f18ef16 100644 (file)
@@ -158,7 +158,8 @@ WebInspector.LogContentView = class LogContentView extends WebInspector.ContentV
 
         // Some results don't populate until further backend dispatches occur (like the DOM tree).
         // We want to remove focusable children after those pending dispatches too.
-        InspectorBackend.runAfterPendingDispatches(this._clearFocusableChildren.bind(this));
+        let target = messageView.message ? messageView.message.target : WebInspector.runtimeManager.activeExecutionContext.target;
+        target.connection.runAfterPendingDispatches(this._clearFocusableChildren.bind(this));
 
         if (type && type !== WebInspector.ConsoleMessage.MessageType.EndGroup) {
             console.assert(messageView.message instanceof WebInspector.ConsoleMessage);
index e8baf09..914e731 100644 (file)
@@ -707,6 +707,8 @@ WebInspector.NavigationSidebarPanel = class NavigationSidebarPanel extends WebIn
             || treeElement instanceof WebInspector.DatabaseHostTreeElement
             || treeElement instanceof WebInspector.IndexedDatabaseHostTreeElement
             || treeElement instanceof WebInspector.ApplicationCacheManifestTreeElement
+            || treeElement instanceof WebInspector.ThreadTreeElement
+            || treeElement instanceof WebInspector.IdleTreeElement
             || typeof treeElement.representedObject === "string"
             || treeElement.representedObject instanceof String;
     }
diff --git a/Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.css
new file mode 100644 (file)
index 0000000..04e0b8d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.details-section.call-stack .thread .icon {
+    content: url(../Images/Thread.svg);
+    width: 15px;
+    height: 15px;
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/ThreadTreeElement.js
new file mode 100644 (file)
index 0000000..7825bf2
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.ThreadTreeElement = class ThreadTreeElement extends WebInspector.GeneralTreeElement
+{
+    constructor(target)
+    {
+        super("thread", target.displayName);
+
+        this._target = target;
+
+        this._idleTreeElement = new WebInspector.IdleTreeElement;
+    }
+
+    // Public
+
+    get target() { return this._target; }
+
+    refresh()
+    {
+        this.removeChildren();
+
+        let targetData = WebInspector.debuggerManager.dataForTarget(this._target);
+        let callFrames = targetData.callFrames;
+
+        if (targetData.pausing || !callFrames.length) {
+            this.appendChild(this._idleTreeElement);
+            this.expand();
+            return;
+        }
+
+        let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+        let activeCallFrameTreeElement = null;
+
+        for (let callFrame of callFrames) {
+            let callFrameTreeElement = new WebInspector.CallFrameTreeElement(callFrame);
+            if (callFrame === activeCallFrame)
+                activeCallFrameTreeElement = callFrameTreeElement;
+            this.appendChild(callFrameTreeElement);
+        }
+
+        if (activeCallFrameTreeElement) {
+            activeCallFrameTreeElement.select(true, true);
+            activeCallFrameTreeElement.isActiveCallFrame = true;
+        }
+
+        this.expand();
+    }
+
+    // Protected (GeneralTreeElement)
+
+    onattach()
+    {
+        super.onattach();
+
+        this.refresh();
+        this.expand();
+    }
+
+    oncontextmenu(event)
+    {
+        let targetData = WebInspector.debuggerManager.dataForTarget(this._target);
+
+        let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
+        if (DebuggerAgent.continueUntilNextRunLoop) {
+            contextMenu.appendItem(WebInspector.UIString("Resume Thread"), () => {
+                WebInspector.debuggerManager.continueUntilNextRunLoop(this._target);
+            }, !targetData.paused);
+        }
+    }
+};