Web Inspector: Provide $exception in the console for the thrown exception value
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Nov 2014 23:49:36 +0000 (23:49 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Nov 2014 23:49:36 +0000 (23:49 +0000)
https://bugs.webkit.org/show_bug.cgi?id=138726

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* debugger/DebuggerScope.cpp:
(JSC::DebuggerScope::caughtValue):
* debugger/DebuggerScope.h:
Access the caught value if this scope is a catch scope.

* runtime/JSNameScope.h:
(JSC::JSNameScope::isFunctionNameScope):
(JSC::JSNameScope::isCatchScope):
(JSC::JSNameScope::value):
Provide an accessor for the single value in the JSNameScope (with / catch block).

* inspector/InjectedScriptSource.js:
Save the exception value and expose it via $exception. Since the command line api
is recreated on each evaluation, $exception is essentially readonly.

* inspector/ScriptDebugServer.h:
* inspector/ScriptDebugServer.cpp:
(Inspector::ScriptDebugServer::dispatchDidPause):
(Inspector::ScriptDebugServer::exceptionOrCaughtValue):
When pausing, get the exception or caught value. The exception will be provided
if we are breaking on an explicit exception. When inside of a catch block, we
can get the caught value by walking up the scope chain.

* inspector/agents/InspectorDebuggerAgent.h:
* inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::InspectorDebuggerAgent):
(Inspector::InspectorDebuggerAgent::resume):
(Inspector::InspectorDebuggerAgent::stepOver):
(Inspector::InspectorDebuggerAgent::stepInto):
(Inspector::InspectorDebuggerAgent::stepOut):
Clearing state can be done in didContinue.

(Inspector::InspectorDebuggerAgent::didPause):
Set the exception value explicitly in the injected script when we have it.

(Inspector::InspectorDebuggerAgent::didContinue):
Clear state saved when we had paused, including clearly an exception value if needed.

(Inspector::InspectorDebuggerAgent::clearDebuggerBreakpointState):
(Inspector::InspectorDebuggerAgent::clearExceptionValue):
Call into the injected script only when needed.

* inspector/InjectedScript.cpp:
(Inspector::InjectedScript::setExceptionValue):
(Inspector::InjectedScript::clearExceptionValue):
* inspector/InjectedScript.h:
* inspector/InjectedScriptManager.cpp:
(Inspector::InjectedScriptManager::clearExceptionValue):
* inspector/InjectedScriptManager.h:
Clear on all injected scripts.

Source/WebCore:

Tests: inspector/debugger/command-line-api-exception-nested-catch.html
       inspector/debugger/command-line-api-exception.html

* inspector/CommandLineAPIModuleSource.js:
Expose $exception in the more complete command line API.

Source/WebInspectorUI:

* UserInterface/Base/Test.js:
(WebInspector.loaded):
In order to use RuntimeManager to execute in the global context or on the
active debugger call frame, we need to expose the quickConsole controller.

* UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
Expose $exception to completion when we paused because of an exception.

LayoutTests:

Provide some tests to ensure $exception is the value we expect at different times,
such as on the exception line, when there is no exception, or when stepping through
catch blocks (it should always be the most recent caught exception).

* inspector/debugger/command-line-api-exception-expected.txt: Added.
* inspector/debugger/command-line-api-exception-nested-catch-expected.txt: Added.
* inspector/debugger/command-line-api-exception-nested-catch.html: Added.
* inspector/debugger/command-line-api-exception.html: Added.
* inspector/debugger/resources/exceptions.js: Added.
(triggerUncaughtTypeException):
(triggerUncaughtReferenceException):
(triggerUncaughtSyntaxException):
(triggerUncaughtDOMException):
(throwString):
(throwNumber):
(throwNull):
(throwObject):
(throwNode):
(catcher):
(nestedCatchBlocks):

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

24 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/debugger/command-line-api-exception-expected.txt [new file with mode: 0644]
LayoutTests/inspector/debugger/command-line-api-exception-nested-catch-expected.txt [new file with mode: 0644]
LayoutTests/inspector/debugger/command-line-api-exception-nested-catch.html [new file with mode: 0644]
LayoutTests/inspector/debugger/command-line-api-exception.html [new file with mode: 0644]
LayoutTests/inspector/debugger/resources/exceptions.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/debugger/DebuggerScope.cpp
Source/JavaScriptCore/debugger/DebuggerScope.h
Source/JavaScriptCore/inspector/InjectedScript.cpp
Source/JavaScriptCore/inspector/InjectedScript.h
Source/JavaScriptCore/inspector/InjectedScriptManager.cpp
Source/JavaScriptCore/inspector/InjectedScriptManager.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/ScriptDebugServer.cpp
Source/JavaScriptCore/inspector/ScriptDebugServer.h
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h
Source/JavaScriptCore/runtime/JSNameScope.h
Source/WebCore/ChangeLog
Source/WebCore/inspector/CommandLineAPIModuleSource.js
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Base/Test.js
Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js

index 96761bc..90f8f14 100644 (file)
@@ -1,3 +1,31 @@
+2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Provide $exception in the console for the thrown exception value
+        https://bugs.webkit.org/show_bug.cgi?id=138726
+
+        Reviewed by Timothy Hatcher.
+
+        Provide some tests to ensure $exception is the value we expect at different times,
+        such as on the exception line, when there is no exception, or when stepping through
+        catch blocks (it should always be the most recent caught exception).
+
+        * inspector/debugger/command-line-api-exception-expected.txt: Added.
+        * inspector/debugger/command-line-api-exception-nested-catch-expected.txt: Added.
+        * inspector/debugger/command-line-api-exception-nested-catch.html: Added.
+        * inspector/debugger/command-line-api-exception.html: Added.
+        * inspector/debugger/resources/exceptions.js: Added.
+        (triggerUncaughtTypeException):
+        (triggerUncaughtReferenceException):
+        (triggerUncaughtSyntaxException):
+        (triggerUncaughtDOMException):
+        (throwString):
+        (throwNumber):
+        (throwNull):
+        (throwObject):
+        (throwNode):
+        (catcher):
+        (nestedCatchBlocks):
+
 2014-11-19  David Hyatt  <hyatt@apple.com>
 
         Images/replaced elements that are as tall as a page should be on their own page
diff --git a/LayoutTests/inspector/debugger/command-line-api-exception-expected.txt b/LayoutTests/inspector/debugger/command-line-api-exception-expected.txt
new file mode 100644 (file)
index 0000000..6f7729b
--- /dev/null
@@ -0,0 +1,32 @@
+CONSOLE MESSAGE: line 4: TypeError: undefined is not an object (evaluating '[].x.x')
+CONSOLE MESSAGE: line 10: ReferenceError: Can't find variable: variableThatDoesNotExist
+CONSOLE MESSAGE: line 16: SyntaxError: Unexpected token ')'
+CONSOLE MESSAGE: line 22: IndexSizeError: DOM Exception 1: Index or size was negative, or greater than the allowed value.
+CONSOLE MESSAGE: line 27: thrown string
+CONSOLE MESSAGE: line 32: 123.456
+CONSOLE MESSAGE: line 37: null
+CONSOLE MESSAGE: line 42: [object Object]
+CONSOLE MESSAGE: line 47: [object HTMLBodyElement]
+CONSOLE MESSAGE: line 55: CATCHER: TypeError: undefined is not an object (evaluating '[].x.x')
+CONSOLE MESSAGE: line 55: CATCHER: thrown string
+CONSOLE MESSAGE: line 55: CATCHER: [object Object]
+Checks that $exception is available and accurate in evaluations when paused on an exception.
+
+BEFORE: $exception => undefined
+PAUSE #1: $exception => TypeError: undefined is not an object (evaluating '[].x.x')
+PAUSE #2: $exception => ReferenceError: Can't find variable: variableThatDoesNotExist
+PAUSE #3: $exception => SyntaxError: Unexpected token ')'
+PAUSE #4: $exception => DOMException
+PAUSE #5: $exception => thrown string
+PAUSE #6: $exception => 123.456
+PAUSE #7: $exception => null
+PAUSE #8: $exception => Object
+PAUSE #9: $exception => body
+PAUSE #10: $exception => TypeError: undefined is not an object (evaluating '[].x.x')
+STEPPED OUT TO CATCH BLOCK: $exception === e ? true
+PAUSE #11: $exception => thrown string
+STEPPED OUT TO CATCH BLOCK: $exception === e ? true
+PAUSE #12: $exception => Object
+STEPPED OUT TO CATCH BLOCK: $exception === e ? true
+AFTER: $exception => undefined
+
diff --git a/LayoutTests/inspector/debugger/command-line-api-exception-nested-catch-expected.txt b/LayoutTests/inspector/debugger/command-line-api-exception-nested-catch-expected.txt
new file mode 100644 (file)
index 0000000..bf7f7c7
--- /dev/null
@@ -0,0 +1,13 @@
+CONSOLE MESSAGE: line 67: inner exception
+CONSOLE MESSAGE: line 69: outer exception
+Checks that $exception is the value of the current exception, even in nested catch blocks.
+
+BEFORE : $exception => undefined
+OUTER 1: $exception => outer exception
+INNER 1: $exception => inner exception
+INNER 2: $exception => inner exception
+  CATCH: $exception === e2 ? true
+OUTER 2: $exception => outer exception
+  CATCH: $exception === e1 ? true
+AFTER  : $exception => undefined
+
diff --git a/LayoutTests/inspector/debugger/command-line-api-exception-nested-catch.html b/LayoutTests/inspector/debugger/command-line-api-exception-nested-catch.html
new file mode 100644 (file)
index 0000000..cc62998
--- /dev/null
@@ -0,0 +1,84 @@
+<!doctype html>
+<html>
+<head>
+<script type="text/javascript" src="../../http/tests/inspector/inspector-test.js"></script>
+<script type="text/javascript" src="./resources/exceptions.js"></script>
+<script>
+function triggerException()
+{
+    // We expect uncaught exceptions, so avoid logs for them.
+    window.onerror = function(){};
+    setTimeout(nestedCatchBlocks, 0);
+}
+
+function test()
+{
+    WebInspector.debuggerManager.allExceptionsBreakpoint.disabled = false;
+
+    function dumpCommandLineAPIValue(prefix) {
+        WebInspector.runtimeManager.evaluateInInspectedWindow("$exception", "test", true, true, false, function(result, wasThrown) {
+            InspectorTest.log(prefix + ": $exception => " + result.description);
+        });
+    }
+
+    function checkIfExceptionValueMatchesVariable(varName) {
+        WebInspector.runtimeManager.evaluateInInspectedWindow("$exception === " + varName, "test", true, true, false, function(result, wasThrown) {
+            InspectorTest.log("  CATCH: $exception === " + varName + " ? " + result.description);
+        });
+    }
+
+    var done = false;
+    var phase = 0;
+
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, function(event) {
+        if (done)
+            return;
+
+        phase++;
+
+        // Skip past the first pause to the second pause.
+        if (phase === 1) {
+            dumpCommandLineAPIValue("OUTER 1");
+            WebInspector.debuggerManager.resume();
+            return;
+        }
+
+        // Paused on the exception in the inner try, step over to get into the inner catch.
+        if (phase === 2) {
+            dumpCommandLineAPIValue("INNER 1");
+            WebInspector.debuggerManager.stepOver();
+            return;
+        }
+
+        // Paused in the inner catch, verify $exception is "inner exception".
+        if (phase === 3) {
+            dumpCommandLineAPIValue("INNER 2");
+            checkIfExceptionValueMatchesVariable("e2");
+            WebInspector.debuggerManager.stepOver();
+            return;
+        }
+        
+        // Stepped into the outer catch, verify $exception is "outer exception".
+        if (phase === 4) {
+            dumpCommandLineAPIValue("OUTER 2");
+            checkIfExceptionValueMatchesVariable("e1");
+            WebInspector.debuggerManager.resume();
+            return;
+        }
+    });
+
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, function(event) {
+        done = true;
+        dumpCommandLineAPIValue("AFTER  ");
+        InspectorTest.completeTest();
+    });
+
+    dumpCommandLineAPIValue("BEFORE ");
+    InspectorTest.evaluateInPage("triggerException()");
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Checks that <code>$exception</code> is the value of the current exception, even in nested catch blocks.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/debugger/command-line-api-exception.html b/LayoutTests/inspector/debugger/command-line-api-exception.html
new file mode 100644 (file)
index 0000000..319b173
--- /dev/null
@@ -0,0 +1,98 @@
+<!doctype html>
+<html>
+<head>
+<script type="text/javascript" src="../../http/tests/inspector/inspector-test.js"></script>
+<script type="text/javascript" src="./resources/exceptions.js"></script>
+<script>
+var exceptionIndex = 0;
+var exceptionCausers = [
+    triggerUncaughtTypeException,
+    triggerUncaughtReferenceException,
+    triggerUncaughtSyntaxException,
+    triggerUncaughtDOMException,
+    throwString,
+    throwNumber,
+    throwNull,
+    throwObject,
+    throwNode,
+    function() { catcher(triggerUncaughtTypeException); },
+    function() { catcher(throwString); },
+    function() { catcher(throwObject); },
+];
+
+
+function triggerNextException()
+{
+    // We expect uncaught exceptions, so avoid logs for them.
+    window.onerror = function(){};
+
+    setTimeout(function() {
+        exceptionCausers[exceptionIndex++]();
+    }, 0);
+}
+
+function test()
+{
+    WebInspector.debuggerManager.allExceptionsBreakpoint.disabled = false;
+
+    function triggerNextException() {
+        InspectorTest.evaluateInPage("triggerNextException()");
+    }
+
+    function dumpCommandLineAPIValue(prefix) {
+        WebInspector.runtimeManager.evaluateInInspectedWindow("$exception", "test", true, true, false, function(result, wasThrown) {
+            InspectorTest.log(prefix + ": $exception => " + result.description);
+        });
+    }
+
+    function checkIfExceptionValueMatchesCatchVariable() {
+        WebInspector.runtimeManager.evaluateInInspectedWindow("$exception === e", "test", true, true, false, function(result, wasThrown) {
+            InspectorTest.log("STEPPED OUT TO CATCH BLOCK: $exception === e ? " + result.description);
+        });
+    }
+
+    var pauses = 0;
+    var stepping = false;
+    var done = false;
+    const pointWhereExpectionsAreBeingCaught = 9;
+    const expectedPauses = 12;
+
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, function(event) {
+        if (done)
+            return;
+
+        if (!stepping) {
+            dumpCommandLineAPIValue("PAUSE #" + (++pauses));
+            if (pauses > pointWhereExpectionsAreBeingCaught) {
+                WebInspector.debuggerManager.stepOut();
+                stepping = true;
+                return;
+            }
+        } else {
+            checkIfExceptionValueMatchesCatchVariable();
+            stepping = false;
+        }
+
+        WebInspector.debuggerManager.resume();
+
+        if (pauses !== expectedPauses) {
+            triggerNextException();
+            return;
+        }
+    });
+
+    WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, function(event) {
+        done = true;
+        dumpCommandLineAPIValue("AFTER");
+        InspectorTest.completeTest();
+    });
+
+    dumpCommandLineAPIValue("BEFORE");
+    triggerNextException();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Checks that <code>$exception</code> is available and accurate in evaluations when paused on an exception.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/debugger/resources/exceptions.js b/LayoutTests/inspector/debugger/resources/exceptions.js
new file mode 100644 (file)
index 0000000..90506e9
--- /dev/null
@@ -0,0 +1,71 @@
+function triggerUncaughtTypeException()
+{
+    // Exception: TypeError: undefined is not an object (evaluating '[].x.x')
+    [].x.x;
+}
+
+function triggerUncaughtReferenceException()
+{
+    // Exception: ReferenceError: Can't find variable: variableThatDoesNotExist
+    variableThatDoesNotExist += 1;
+}
+
+function triggerUncaughtSyntaxException()
+{
+    // Exception: SyntaxError: Unexpected token ')'
+    eval("if()");
+}
+
+function triggerUncaughtDOMException()
+{
+    // Error: IndexSizeError: DOM Exception 1
+    document.createTextNode("").splitText(100);
+}
+
+function throwString()
+{
+    throw "thrown string";
+}
+
+function throwNumber()
+{
+    throw 123.456;
+}
+
+function throwNull()
+{
+    throw null;
+}
+
+function throwObject()
+{
+    throw {x:1,y:2};
+}
+
+function throwNode()
+{
+    throw document.body;
+}
+
+function catcher(func)
+{
+    try {
+        func();
+    } catch (e) {
+        console.log("CATCHER: " + e);
+    }
+}
+
+function nestedCatchBlocks()
+{
+    try {
+        throw "outer exception";
+    } catch (e1) {
+        try {
+            throw "inner exception";
+        } catch (e2) {
+            console.log(e2);
+        }
+        console.log(e1);
+    }
+}
index 4e1ff19..bcfe379 100644 (file)
@@ -1,5 +1,63 @@
 2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
 
+        Web Inspector: Provide $exception in the console for the thrown exception value
+        https://bugs.webkit.org/show_bug.cgi?id=138726
+
+        Reviewed by Timothy Hatcher.
+
+        * debugger/DebuggerScope.cpp:
+        (JSC::DebuggerScope::caughtValue):
+        * debugger/DebuggerScope.h:
+        Access the caught value if this scope is a catch scope.
+
+        * runtime/JSNameScope.h:
+        (JSC::JSNameScope::isFunctionNameScope):
+        (JSC::JSNameScope::isCatchScope):
+        (JSC::JSNameScope::value):
+        Provide an accessor for the single value in the JSNameScope (with / catch block).
+
+        * inspector/InjectedScriptSource.js:
+        Save the exception value and expose it via $exception. Since the command line api
+        is recreated on each evaluation, $exception is essentially readonly.
+
+        * inspector/ScriptDebugServer.h:
+        * inspector/ScriptDebugServer.cpp:
+        (Inspector::ScriptDebugServer::dispatchDidPause):
+        (Inspector::ScriptDebugServer::exceptionOrCaughtValue):
+        When pausing, get the exception or caught value. The exception will be provided
+        if we are breaking on an explicit exception. When inside of a catch block, we
+        can get the caught value by walking up the scope chain.
+
+        * inspector/agents/InspectorDebuggerAgent.h:
+        * inspector/agents/InspectorDebuggerAgent.cpp:
+        (Inspector::InspectorDebuggerAgent::InspectorDebuggerAgent):
+        (Inspector::InspectorDebuggerAgent::resume):
+        (Inspector::InspectorDebuggerAgent::stepOver):
+        (Inspector::InspectorDebuggerAgent::stepInto):
+        (Inspector::InspectorDebuggerAgent::stepOut):
+        Clearing state can be done in didContinue.
+
+        (Inspector::InspectorDebuggerAgent::didPause):
+        Set the exception value explicitly in the injected script when we have it.
+
+        (Inspector::InspectorDebuggerAgent::didContinue):
+        Clear state saved when we had paused, including clearly an exception value if needed.
+
+        (Inspector::InspectorDebuggerAgent::clearDebuggerBreakpointState):
+        (Inspector::InspectorDebuggerAgent::clearExceptionValue):
+        Call into the injected script only when needed.
+
+        * inspector/InjectedScript.cpp:
+        (Inspector::InjectedScript::setExceptionValue):
+        (Inspector::InjectedScript::clearExceptionValue):
+        * inspector/InjectedScript.h:
+        * inspector/InjectedScriptManager.cpp:
+        (Inspector::InjectedScriptManager::clearExceptionValue):
+        * inspector/InjectedScriptManager.h:
+        Clear on all injected scripts.
+
+2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
+
         Unreviewed build fixes after r176329.
 
           - export all of the codegen python files as they are included by the main generator
index e9b5016..2f7491f 100644 (file)
@@ -183,4 +183,10 @@ bool DebuggerScope::isFunctionOrEvalScope() const
     return m_scope->isActivationObject();
 }
 
+JSValue DebuggerScope::caughtValue() const
+{
+    ASSERT(isCatchScope());
+    return reinterpret_cast<JSNameScope*>(m_scope.get())->value();
+}
+
 } // namespace JSC
index 32a72f7..227cb19 100644 (file)
@@ -90,6 +90,8 @@ public:
     bool isGlobalScope() const;
     bool isFunctionOrEvalScope() const;
 
+    JSValue caughtValue() const;
+
 private:
     JS_EXPORT_PRIVATE DebuggerScope(VM&, JSScope*);
     JS_EXPORT_PRIVATE void finishCreation(VM&);
index 61ddc0c..95fa75d 100644 (file)
@@ -202,6 +202,23 @@ PassRefPtr<Inspector::Protocol::Runtime::RemoteObject> InjectedScript::wrapTable
     return BindingTraits<Inspector::Protocol::Runtime::RemoteObject>::runtimeCast(resultObject);
 }
 
+void InjectedScript::setExceptionValue(const Deprecated::ScriptValue& value)
+{
+    ASSERT(!hasNoValue());
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("setExceptionValue"), inspectorEnvironment()->functionCallHandler());
+    function.appendArgument(value);
+    RefPtr<InspectorValue> result;
+    makeCall(function, &result);
+}
+
+void InjectedScript::clearExceptionValue()
+{
+    ASSERT(!hasNoValue());
+    Deprecated::ScriptFunctionCall function(injectedScriptObject(), ASCIILiteral("clearExceptionValue"), inspectorEnvironment()->functionCallHandler());
+    RefPtr<InspectorValue> result;
+    makeCall(function, &result);
+}
+
 Deprecated::ScriptValue InjectedScript::findObjectById(const String& objectId) const
 {
     ASSERT(!hasNoValue());
index 71bd68b..42c790d 100644 (file)
@@ -66,6 +66,9 @@ public:
     PassRefPtr<Protocol::Runtime::RemoteObject> wrapObject(const Deprecated::ScriptValue&, const String& groupName, bool generatePreview = false) const;
     PassRefPtr<Protocol::Runtime::RemoteObject> wrapTable(const Deprecated::ScriptValue& table, const Deprecated::ScriptValue& columns) const;
 
+    void setExceptionValue(const Deprecated::ScriptValue&);
+    void clearExceptionValue();
+
     Deprecated::ScriptValue findObjectById(const String& objectId) const;
     void inspectObject(Deprecated::ScriptValue);
     void releaseObject(const String& objectId);
index 9a9018b..d148568 100644 (file)
@@ -118,8 +118,14 @@ void InjectedScriptManager::discardInjectedScripts()
 
 void InjectedScriptManager::releaseObjectGroup(const String& objectGroup)
 {
-    for (auto it = m_idToInjectedScript.begin(); it != m_idToInjectedScript.end(); ++it)
-        it->value.releaseObjectGroup(objectGroup);
+    for (auto& injectedScript : m_idToInjectedScript.values())
+        injectedScript.releaseObjectGroup(objectGroup);
+}
+
+void InjectedScriptManager::clearExceptionValue()
+{
+    for (auto& injectedScript : m_idToInjectedScript.values())
+        injectedScript.clearExceptionValue();
 }
 
 String InjectedScriptManager::injectedScriptSource()
index 33a6084..f6d0530 100644 (file)
@@ -66,6 +66,7 @@ public:
     InjectedScript injectedScriptForObjectId(const String& objectId);
     void discardInjectedScripts();
     void releaseObjectGroup(const String& objectGroup);
+    void clearExceptionValue();
 
 protected:
     virtual void didCreateInjectedScript(InjectedScript);
index fc9ef7a..bf0ddc6 100644 (file)
@@ -86,6 +86,16 @@ InjectedScript.prototype = {
         return this._fallbackWrapper(object);
     },
 
+    setExceptionValue: function(value)
+    {
+        this._exceptionValue = value;
+    },
+
+    clearExceptionValue: function()
+    {
+        delete this._exceptionValue;
+    },
+
     /**
      * @param {*} object
      * @return {!RuntimeAgent.RemoteObject}
@@ -1041,6 +1051,7 @@ InjectedScript.CallFrameProxy._createScopeJson = function(scopeTypeCode, scopeOb
 function BasicCommandLineAPI()
 {
     this.$_ = injectedScript._lastResult;
+    this.$exception = injectedScript._exceptionValue;
 }
 
 return injectedScript;
index 43d0227..c3a0abc 100644 (file)
@@ -138,8 +138,8 @@ void ScriptDebugServer::dispatchDidPause(ScriptDebugListener* listener)
     JSC::ExecState* state = globalObject->globalExec();
     RefPtr<JavaScriptCallFrame> javaScriptCallFrame = JavaScriptCallFrame::create(debuggerCallFrame);
     JSValue jsCallFrame = toJS(state, globalObject, javaScriptCallFrame.get());
-    Deprecated::ScriptValue exception = reasonForPause() == PausedForException ? Deprecated::ScriptValue(state->vm(), currentException()) : Deprecated::ScriptValue();
-    listener->didPause(state, Deprecated::ScriptValue(state->vm(), jsCallFrame), exception);
+
+    listener->didPause(state, Deprecated::ScriptValue(state->vm(), jsCallFrame), exceptionOrCaughtValue(state));
 }
 
 void ScriptDebugServer::dispatchBreakpointActionLog(ExecState* exec, const String& message)
@@ -327,6 +327,22 @@ const BreakpointActions& ScriptDebugServer::getActionsForBreakpoint(JSC::Breakpo
     return emptyActionVector;
 }
 
+Deprecated::ScriptValue ScriptDebugServer::exceptionOrCaughtValue(JSC::ExecState* state)
+{
+    if (reasonForPause() == PausedForException)
+        return Deprecated::ScriptValue(state->vm(), currentException());
+
+    RefPtr<DebuggerCallFrame> debuggerCallFrame = currentDebuggerCallFrame();
+    while (debuggerCallFrame) {
+        DebuggerScope* scope = debuggerCallFrame->scope();
+        if (scope->isCatchScope())
+            return Deprecated::ScriptValue(state->vm(), scope->caughtValue());
+        debuggerCallFrame = debuggerCallFrame->callerFrame();
+    }
+
+    return Deprecated::ScriptValue();
+}
+
 } // namespace Inspector
 
 #endif // ENABLE(INSPECTOR)
index 4577b81..4b21ade 100644 (file)
@@ -105,6 +105,8 @@ private:
     virtual void handlePause(JSC::Debugger::ReasonForPause, JSC::JSGlobalObject*) override final;
     virtual void notifyDoneProcessingDebuggerEvents() override final;
 
+    Deprecated::ScriptValue exceptionOrCaughtValue(JSC::ExecState*);
+
     unsigned m_hitCount;
     bool m_callingListeners;
     BreakpointIDToActionsMap m_breakpointIDToActions;
index a06a6d3..28da2c2 100644 (file)
@@ -65,6 +65,7 @@ InspectorDebuggerAgent::InspectorDebuggerAgent(InjectedScriptManager* injectedSc
     , m_continueToLocationBreakpointID(JSC::noBreakpointID)
     , m_enabled(false)
     , m_javaScriptPauseScheduled(false)
+    , m_hasExceptionValue(false)
     , m_nextProbeSampleId(1)
 {
     // FIXME: make breakReason optional so that there was no need to init it with "other".
@@ -468,7 +469,6 @@ void InspectorDebuggerAgent::resume(ErrorString& errorString)
     if (!assertPaused(errorString))
         return;
 
-    m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
     scriptDebugServer().continueProgram();
 }
 
@@ -477,7 +477,6 @@ void InspectorDebuggerAgent::stepOver(ErrorString& errorString)
     if (!assertPaused(errorString))
         return;
 
-    m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
     scriptDebugServer().stepOverStatement();
 }
 
@@ -486,7 +485,6 @@ void InspectorDebuggerAgent::stepInto(ErrorString& errorString)
     if (!assertPaused(errorString))
         return;
 
-    m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
     scriptDebugServer().stepIntoStatement();
 
     if (m_listener)
@@ -498,7 +496,6 @@ void InspectorDebuggerAgent::stepOut(ErrorString& errorString)
     if (!assertPaused(errorString))
         return;
 
-    m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
     scriptDebugServer().stepOutOfFunction();
 }
 
@@ -643,6 +640,8 @@ void InspectorDebuggerAgent::didPause(JSC::ExecState* scriptState, const Depreca
             m_breakReason = InspectorDebuggerFrontendDispatcher::Reason::Exception;
             m_breakAuxData = injectedScript.wrapObject(exception, InspectorDebuggerAgent::backtraceObjectGroup)->openAccessors();
             // m_breakAuxData might be null after this.
+            injectedScript.setExceptionValue(exception);
+            m_hasExceptionValue = true;
         }
     }
 
@@ -687,8 +686,10 @@ void InspectorDebuggerAgent::didContinue()
 {
     m_pausedScriptState = nullptr;
     m_currentCallStack = Deprecated::ScriptValue();
+    m_injectedScriptManager->releaseObjectGroup(InspectorDebuggerAgent::backtraceObjectGroup);
     m_injectedScriptManager->inspectorEnvironment().executionStopwatch()->start();
     clearBreakDetails();
+    clearExceptionValue();
 
     m_frontendDispatcher->resumed();
 }
@@ -724,6 +725,7 @@ void InspectorDebuggerAgent::clearDebuggerBreakpointState()
     m_continueToLocationBreakpointID = JSC::noBreakpointID;
     clearBreakDetails();
     m_javaScriptPauseScheduled = false;
+    m_hasExceptionValue = false;
 
     scriptDebugServer().continueProgram();
 }
@@ -754,6 +756,14 @@ void InspectorDebuggerAgent::clearBreakDetails()
     m_breakAuxData = nullptr;
 }
 
+void InspectorDebuggerAgent::clearExceptionValue()
+{
+    if (m_hasExceptionValue) {
+        m_injectedScriptManager->clearExceptionValue();
+        m_hasExceptionValue = false;
+    }
+}
+
 } // namespace Inspector
 
 #endif // ENABLE(INSPECTOR)
index 743c729..668372c 100644 (file)
@@ -146,6 +146,7 @@ private:
     void clearDebuggerBreakpointState();
     void clearInspectorBreakpointState();
     void clearBreakDetails();
+    void clearExceptionValue();
 
     bool breakpointActionsFromProtocol(ErrorString&, RefPtr<InspectorArray>& actions, BreakpointActions* result);
 
@@ -167,6 +168,7 @@ private:
     RefPtr<InspectorObject> m_breakAuxData;
     bool m_enabled;
     bool m_javaScriptPauseScheduled;
+    bool m_hasExceptionValue;
     RefPtr<WTF::Stopwatch> m_stopwatch;
     int m_nextProbeSampleId;
 };
index 2775346..09d1d9b 100644 (file)
@@ -65,8 +65,10 @@ public:
 
     DECLARE_INFO;
 
-    bool isFunctionNameScope() { return m_type == FunctionNameScope; }
-    bool isCatchScope() { return m_type == CatchScope; }
+    bool isFunctionNameScope() const { return m_type == FunctionNameScope; }
+    bool isCatchScope() const { return m_type == CatchScope; }
+
+    JSValue value() const { return m_registerStore.get(); }
 
 protected:
     void finishCreation(VM& vm, const Identifier& identifier, JSValue value, unsigned attributes)
index 5d7a3ea..5fdf27a 100644 (file)
@@ -1,3 +1,16 @@
+2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Provide $exception in the console for the thrown exception value
+        https://bugs.webkit.org/show_bug.cgi?id=138726
+
+        Reviewed by Timothy Hatcher.
+
+        Tests: inspector/debugger/command-line-api-exception-nested-catch.html
+               inspector/debugger/command-line-api-exception.html
+
+        * inspector/CommandLineAPIModuleSource.js:
+        Expose $exception in the more complete command line API.
+
 2014-11-19  David Hyatt  <hyatt@apple.com>
 
         Images/replaced elements that are as tall as a page should be on their own page
index 61e583c..fe3cf3d 100644 (file)
@@ -124,6 +124,7 @@ function CommandLineAPI(commandLineAPIImpl, callFrame)
     }
 
     this.$_ = injectedScript._lastResult;
+    this.$exception = injectedScript._exceptionValue;
 }
 
 /**
index 5375909..5a24b74 100644 (file)
@@ -1,5 +1,20 @@
 2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
 
+        Web Inspector: Provide $exception in the console for the thrown exception value
+        https://bugs.webkit.org/show_bug.cgi?id=138726
+
+        Reviewed by Timothy Hatcher.
+
+        * UserInterface/Base/Test.js:
+        (WebInspector.loaded):
+        In order to use RuntimeManager to execute in the global context or on the
+        active debugger call frame, we need to expose the quickConsole controller.
+
+        * UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
+        Expose $exception to completion when we paused because of an exception.
+
+2014-11-19  Joseph Pecoraro  <pecoraro@apple.com>
+
         Web Inspector: Debugger should not mutate variable when hovering mouse over ++n expression
         https://bugs.webkit.org/show_bug.cgi?id=138839
 
index f442e0e..afb2349 100644 (file)
@@ -56,6 +56,9 @@ WebInspector.loaded = function()
     this.probeManager = new WebInspector.ProbeManager;
     this.replayManager = new WebInspector.ReplayManager;
 
+    // Global controllers.
+    this.quickConsole = {executionContextIdentifier: undefined};
+
     document.addEventListener("DOMContentLoaded", this.contentLoaded.bind(this));
 
     // Enable agents.
index 0c2d98f..ce5ffba 100644 (file)
@@ -174,7 +174,9 @@ WebInspector.JavaScriptRuntimeCompletionProvider.prototype = {
             RuntimeAgent.releaseObjectGroup("completion");
 
             if (!base) {
-                const commandLineAPI = ["$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", "$0", "$1", "$2", "$3", "$4", "$_"];
+                var commandLineAPI = ["$", "$$", "$x", "dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear", "getEventListeners", "$0", "$1", "$2", "$3", "$4", "$_"];
+                if (WebInspector.debuggerManager.paused && WebInspector.debuggerManager.pauseReason === WebInspector.DebuggerManager.PauseReason.Exception)
+                    commandLineAPI.push("$exception");
                 for (var i = 0; i < commandLineAPI.length; ++i)
                     propertyNames[commandLineAPI[i]] = true;
             }