Web Inspector: support JavaScript variable mutation in protocol and V8 bindings
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2013 15:00:13 +0000 (15:00 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Feb 2013 15:00:13 +0000 (15:00 +0000)
https://bugs.webkit.org/show_bug.cgi?id=107829

Source/WebCore:

A new command is added to protocol description and the call is passed through
debugger agent through injected script and debugger script down to V8 mirror
API. JSC bindings got a thorw exception stub.

Only declarative JavaScript scopes are supported (local, closure, catch). Other
scopes (global, with) are not supported by V8 and not supported by protocol, because
manual approach (direct property assigment) is available for them in form of evaluate
commands and is more desirable because of a complex nature of operation (it can throw
exception in several cases such as exception in setter function).

Patch by Peter Rybin <prybin@chromium.org> on 2013-02-07
Reviewed by Pavel Feldman.

Test: inspector-protocol/debugger-setVariableValue.html

* bindings/js/JSInjectedScriptHostCustom.cpp:
(WebCore::JSInjectedScriptHost::setFunctionVariableValue):
(WebCore):
* bindings/js/JSJavaScriptCallFrameCustom.cpp:
(WebCore::JSJavaScriptCallFrame::setVariableValue):
(WebCore):
* bindings/v8/DebuggerScript.js:
(.):
* bindings/v8/JavaScriptCallFrame.cpp:
(WebCore::JavaScriptCallFrame::setVariableValue):
(WebCore):
* bindings/v8/JavaScriptCallFrame.h:
(JavaScriptCallFrame):
* bindings/v8/ScriptDebugServer.cpp:
(WebCore::ScriptDebugServer::setFunctionVariableValue):
(WebCore):
* bindings/v8/ScriptDebugServer.h:
(ScriptDebugServer):
* bindings/v8/custom/V8InjectedScriptHostCustom.cpp:
(WebCore::V8InjectedScriptHost::setFunctionVariableValueCallback):
(WebCore):
* bindings/v8/custom/V8JavaScriptCallFrameCustom.cpp:
(WebCore::V8JavaScriptCallFrame::setVariableValueCallback):
(WebCore):
* inspector/InjectedScript.cpp:
(WebCore::InjectedScript::setVariableValue):
(WebCore):
* inspector/InjectedScript.h:
(InjectedScript):
* inspector/InjectedScriptHost.idl:
* inspector/InjectedScriptSource.js:
(.):
* inspector/Inspector.json:
* inspector/InspectorDebuggerAgent.cpp:
(WebCore::InspectorDebuggerAgent::getFunctionDetails):
(WebCore::InspectorDebuggerAgent::setVariableValue):
(WebCore):
* inspector/InspectorDebuggerAgent.h:
(InspectorDebuggerAgent):
* inspector/JavaScriptCallFrame.idl:

LayoutTests:

Patch by Peter Rybin <prybin@chromium.org> on 2013-02-07
Reviewed by Pavel Feldman.

* inspector-protocol/debugger-setVariableValue-expected.txt: Added.
* inspector-protocol/debugger-setVariableValue.html: Added.
* inspector/console/command-line-api-expected.txt:
* platform/chromium/inspector-protocol/debugger-setVariableValue-expected.txt: Added.

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

23 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector-protocol/debugger-setVariableValue-expected.txt [new file with mode: 0644]
LayoutTests/inspector-protocol/debugger-setVariableValue.html [new file with mode: 0644]
LayoutTests/inspector/console/command-line-api-expected.txt
LayoutTests/platform/chromium/inspector-protocol/debugger-setVariableValue-expected.txt [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSInjectedScriptHostCustom.cpp
Source/WebCore/bindings/js/JSJavaScriptCallFrameCustom.cpp
Source/WebCore/bindings/v8/DebuggerScript.js
Source/WebCore/bindings/v8/JavaScriptCallFrame.cpp
Source/WebCore/bindings/v8/JavaScriptCallFrame.h
Source/WebCore/bindings/v8/ScriptDebugServer.cpp
Source/WebCore/bindings/v8/ScriptDebugServer.h
Source/WebCore/bindings/v8/custom/V8InjectedScriptHostCustom.cpp
Source/WebCore/bindings/v8/custom/V8JavaScriptCallFrameCustom.cpp
Source/WebCore/inspector/InjectedScript.cpp
Source/WebCore/inspector/InjectedScript.h
Source/WebCore/inspector/InjectedScriptHost.idl
Source/WebCore/inspector/InjectedScriptSource.js
Source/WebCore/inspector/Inspector.json
Source/WebCore/inspector/InspectorDebuggerAgent.cpp
Source/WebCore/inspector/InspectorDebuggerAgent.h
Source/WebCore/inspector/JavaScriptCallFrame.idl

index aca9ac5e94d48f39b4222238d62ce9b7218ac6bf..4d02797e2a3a8ede14fb4a8e3b6e92fedcdf673a 100644 (file)
@@ -1,3 +1,15 @@
+2013-02-07  Peter Rybin  <prybin@chromium.org>
+
+        Web Inspector: support JavaScript variable mutation in protocol and V8 bindings
+        https://bugs.webkit.org/show_bug.cgi?id=107829
+
+        Reviewed by Pavel Feldman.
+
+        * inspector-protocol/debugger-setVariableValue-expected.txt: Added.
+        * inspector-protocol/debugger-setVariableValue.html: Added.
+        * inspector/console/command-line-api-expected.txt:
+        * platform/chromium/inspector-protocol/debugger-setVariableValue-expected.txt: Added.
+
 2013-02-07  Yury Semikhatsky  <yurys@chromium.org>
 
         Web Inspector: reduce number of native memory instrumentation categories
diff --git a/LayoutTests/inspector-protocol/debugger-setVariableValue-expected.txt b/LayoutTests/inspector-protocol/debugger-setVariableValue-expected.txt
new file mode 100644 (file)
index 0000000..d69c096
--- /dev/null
@@ -0,0 +1,4 @@
+Closure returns: {"type":"string","value":"ttttrue52013"}
+ (expected: 'ttttrue52013')
+Protocol command 'Debugger.setVariableValue' failed
+
diff --git a/LayoutTests/inspector-protocol/debugger-setVariableValue.html b/LayoutTests/inspector-protocol/debugger-setVariableValue.html
new file mode 100644 (file)
index 0000000..3caa158
--- /dev/null
@@ -0,0 +1,181 @@
+<html>
+<head>
+<script type="text/javascript" src="../http/tests/inspector-protocol/resources/protocol-test.js"></script>
+<script>
+
+function test()
+{
+    // A general-purpose engine for sending a sequence of protocol commands.
+    // The clients provide requests and response handlers, while the engine catches
+    // errors and makes sure that once there's nothing to do completeTest() is called.
+    // @param step is an object with command, params and callback fields 
+    function runRequestSeries(step) {
+        processStep(step);
+
+        function processStep(s) {
+            try {
+                processStepOrFail(s);
+            } catch (e) {
+                InspectorTest.log(e.stack);
+                InspectorTest.completeTest();
+            }
+        }
+        
+        function processStepOrFail(s) {
+            if (!s) {
+                InspectorTest.completeTest();
+                return;
+            }
+            if (!s.command) {
+                // A simple loopback step.
+                var next = s.callback();
+                processStep(next);
+                return;
+            }
+
+            var innerCallback = function(response) {
+                var next;
+                if ("error" in response) {
+                    if (!("errorHandler" in s)) {
+                        // Error message is not logged intentionally, it may be platform-specific.
+                        InspectorTest.log("Protocol command '" + s.command + "' failed");
+                        InspectorTest.completeTest();
+                        return;
+                    }
+                    try {
+                        next = s.errorHandler(response.error);
+                    } catch (e) {
+                        InspectorTest.log(e.stack);
+                        InspectorTest.completeTest();
+                        return;
+                    }
+                } else {
+                    try {
+                        next = s.callback(response.result);
+                    } catch (e) {
+                        InspectorTest.log(e.stack);
+                        InspectorTest.completeTest();
+                        return;
+                    }
+                }
+                processStep(next);
+            }
+            InspectorTest.sendCommand(s.command, s.params, innerCallback);
+        }
+    }
+    
+    var firstStep = { callback: enableDebugger };
+
+    runRequestSeries(firstStep);
+    
+    function enableDebugger() {
+        return { command: "Debugger.enable", params: {}, callback: evalFunction };
+    }
+    
+    // Testing function/closure scopes.
+    
+    function evalFunction(response) {
+        var expression = "(function(p){var r=5;with({year:2013}){return function Closure(q){return p+q+r+year};}})('ttt')";
+        return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEvalFunction };
+    }
+    
+    function callbackEvalFunction(result) {
+        var id = result.result.objectId;
+        if (id === undefined)
+            throw new Error("objectId is expected");
+        return createCheckFunctionStepChain(id);
+    }
+
+    function createCheckFunctionStepChain(functionObjectId) {
+        var params = {
+            objectId: functionObjectId,
+            functionDeclaration: "function(){return this(true);}"
+        };
+        return {
+            command: "Runtime.callFunctionOn", params: params, callback: callbackLogClosureEval
+        };
+
+           function callbackLogClosureEval(result) {
+               InspectorTest.log("Closure returns: " + JSON.stringify(result.result));
+               InspectorTest.log(" (expected: 'ttttrue52013')");
+               
+               var params = {
+                   functionObjectId: functionObjectId,
+                   scopeNumber: 1,
+                   variableName: "r",
+                   newValue: { value: 4 }
+               };
+               return {
+                   command: "Debugger.setVariableValue", params: params, callback: setVariableCallback
+               };
+           }
+           
+           function setVariableCallback() {
+               InspectorTest.log("Debugger.setVariableValue OK");
+               
+               var params = {
+                   objectId: functionObjectId,
+                   functionDeclaration: "function(){return this(true);}"
+               };
+               return {
+                   command: "Runtime.callFunctionOn", params: params, callback: callbackLogClosureEval2
+               };
+               
+           }
+
+           function callbackLogClosureEval2(result) {
+               InspectorTest.log("Closure returns: " + JSON.stringify(result.result));
+               InspectorTest.log(" (expected: 'ttttrue42013')");
+               
+               var params = {
+                   // No target is specified
+                   scopeNumber: 1,
+                   variableName: "r",
+                   newValue: { value: 4 }
+               };
+               return {
+                   command: "Debugger.setVariableValue", params: params, errorHandler: setVariableErrorCallback3
+               };
+           }
+           
+           function setVariableErrorCallback3(error) {
+               InspectorTest.log("Expected error: " + JSON.stringify(error));
+
+               var params = {
+                   functionObjectId: functionObjectId,
+                   scopeNumber: 100, // Wrong scope number
+                   variableName: "r",
+                   newValue: { value: 4 }
+               };
+               return {
+                   command: "Debugger.setVariableValue", params: params, errorHandler: setVariableErrorCallback4
+               };
+           }
+           
+           function setVariableErrorCallback4(error) {
+               InspectorTest.log("Expected error");
+
+               var params = {
+                   functionObjectId: functionObjectId,
+                   scopeNumber: 1,
+                   variableName: "bad", // Wrong variable name
+                   newValue: { value: 4 }
+               };
+               return {
+                   command: "Debugger.setVariableValue", params: params, errorHandler: setVariableErrorCallback5
+               };
+           }
+           
+           function setVariableErrorCallback5(error) {
+               InspectorTest.log("Expected error");
+               
+               // End of test.
+               return;
+           }
+    }
+}
+</script>
+</head>
+<body onLoad="runTest();">
+</body>
+</html>
index 4ca7b7ad9baa5f9ad55a0a206dbbc7c49a4d4977..ac36999175c72efff69856ca7be2c2a664aae136 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 1060: The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $("#%s")
+CONSOLE MESSAGE: line 1121: The console function $() has changed from $=getElementById(id) to $=querySelector(selector). You might try $("#%s")
 Tests that command line api works.
 
 
diff --git a/LayoutTests/platform/chromium/inspector-protocol/debugger-setVariableValue-expected.txt b/LayoutTests/platform/chromium/inspector-protocol/debugger-setVariableValue-expected.txt
new file mode 100644 (file)
index 0000000..633edf3
--- /dev/null
@@ -0,0 +1,9 @@
+Closure returns: {"type":"string","value":"ttttrue52013"}
+ (expected: 'ttttrue52013')
+Debugger.setVariableValue OK
+Closure returns: {"type":"string","value":"ttttrue42013"}
+ (expected: 'ttttrue42013')
+Expected error: {"code":-32000,"message":"Either call frame or function object must be specified"}
+Expected error
+Expected error
+
index 6f177f5f10553621adf69cea4b586c1e8ffc1b14..34c50bf0465db50048959c81a6aa285f3672aab0 100644 (file)
@@ -1,3 +1,63 @@
+2013-02-07  Peter Rybin  <prybin@chromium.org>
+
+        Web Inspector: support JavaScript variable mutation in protocol and V8 bindings
+        https://bugs.webkit.org/show_bug.cgi?id=107829
+
+        A new command is added to protocol description and the call is passed through
+        debugger agent through injected script and debugger script down to V8 mirror
+        API. JSC bindings got a thorw exception stub.
+
+        Only declarative JavaScript scopes are supported (local, closure, catch). Other
+        scopes (global, with) are not supported by V8 and not supported by protocol, because
+        manual approach (direct property assigment) is available for them in form of evaluate
+        commands and is more desirable because of a complex nature of operation (it can throw
+        exception in several cases such as exception in setter function).
+
+        Reviewed by Pavel Feldman.
+
+        Test: inspector-protocol/debugger-setVariableValue.html
+
+        * bindings/js/JSInjectedScriptHostCustom.cpp:
+        (WebCore::JSInjectedScriptHost::setFunctionVariableValue):
+        (WebCore):
+        * bindings/js/JSJavaScriptCallFrameCustom.cpp:
+        (WebCore::JSJavaScriptCallFrame::setVariableValue):
+        (WebCore):
+        * bindings/v8/DebuggerScript.js:
+        (.):
+        * bindings/v8/JavaScriptCallFrame.cpp:
+        (WebCore::JavaScriptCallFrame::setVariableValue):
+        (WebCore):
+        * bindings/v8/JavaScriptCallFrame.h:
+        (JavaScriptCallFrame):
+        * bindings/v8/ScriptDebugServer.cpp:
+        (WebCore::ScriptDebugServer::setFunctionVariableValue):
+        (WebCore):
+        * bindings/v8/ScriptDebugServer.h:
+        (ScriptDebugServer):
+        * bindings/v8/custom/V8InjectedScriptHostCustom.cpp:
+        (WebCore::V8InjectedScriptHost::setFunctionVariableValueCallback):
+        (WebCore):
+        * bindings/v8/custom/V8JavaScriptCallFrameCustom.cpp:
+        (WebCore::V8JavaScriptCallFrame::setVariableValueCallback):
+        (WebCore):
+        * inspector/InjectedScript.cpp:
+        (WebCore::InjectedScript::setVariableValue):
+        (WebCore):
+        * inspector/InjectedScript.h:
+        (InjectedScript):
+        * inspector/InjectedScriptHost.idl:
+        * inspector/InjectedScriptSource.js:
+        (.):
+        * inspector/Inspector.json:
+        * inspector/InspectorDebuggerAgent.cpp:
+        (WebCore::InspectorDebuggerAgent::getFunctionDetails):
+        (WebCore::InspectorDebuggerAgent::setVariableValue):
+        (WebCore):
+        * inspector/InspectorDebuggerAgent.h:
+        (InspectorDebuggerAgent):
+        * inspector/JavaScriptCallFrame.idl:
+
 2013-02-07  Caio Marcelo de Oliveira Filho  <caio.oliveira@openbossa.org>
 
         [CoordinatedGraphics] Use ScrollingCoordinator to track fixed layers
index 089a82fe8dc01d179f57564df6e79edaac799f1b..0f15e4be0e0ce9c150ed8afd0fe2f53bb6f39cac 100644 (file)
@@ -307,6 +307,13 @@ JSValue JSInjectedScriptHost::evaluate(ExecState* exec)
     return result;
 }
 
+JSValue JSInjectedScriptHost::setFunctionVariableValue(JSC::ExecState* exec)
+{
+    // FIXME: implement this. https://bugs.webkit.org/show_bug.cgi?id=107830
+    throwError(exec, createTypeError(exec, "Variable value mutation is not supported"));
+    return jsUndefined();
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(INSPECTOR)
index a068084afdfe8031cbd5fc30f3acbf87f8ed75e7..e1dd7c741acd018296b735af1183b719082d6288 100644 (file)
@@ -130,6 +130,13 @@ JSValue JSJavaScriptCallFrame::scopeType(ExecState* exec)
     return jsUndefined();
 }
 
+JSValue JSJavaScriptCallFrame::setVariableValue(JSC::ExecState* exec)
+{
+    // FIXME: implement this. https://bugs.webkit.org/show_bug.cgi?id=107830
+    throwError(exec, createTypeError(exec, "Variable value mutation is not supported"));
+    return jsUndefined();
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
index 1691f55e642bf20e88bf0ba414b8f76f4fadd272..2eb43c61a9b47a2d933abea30a45f0513a790349 100644 (file)
@@ -93,6 +93,23 @@ DebuggerScript.getInternalProperties = function(value)
     return result;
 }
 
+DebuggerScript.setFunctionVariableValue = function(functionValue, scopeIndex, variableName, newValue)
+{
+    var mirror = MakeMirror(functionValue);
+    if (!mirror.isFunction())
+        throw new Error("Function value has incorrect type");
+    return DebuggerScript._setScopeVariableValue(mirror, scopeIndex, variableName, newValue);
+}
+
+DebuggerScript._setScopeVariableValue = function(scopeHolder, scopeIndex, variableName, newValue)
+{
+    var scopeMirror = scopeHolder.scope(scopeIndex);
+    if (!scopeMirror)
+        throw new Error("Incorrect scope index");
+    scopeMirror.setVariableValue(variableName, newValue);
+    return undefined;
+}
+
 DebuggerScript.getScripts = function(contextData)
 {
     var result = [];
@@ -295,6 +312,11 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame)
         return Debug.LiveEdit.RestartFrame(frameMirror);
     }
 
+    function setVariableValue(scopeNumber, variableName, newValue)
+    {
+        return DebuggerScript._setScopeVariableValue(frameMirror, scopeNumber, variableName, newValue);
+    }
+
     return {
         "sourceID": sourceID,
         "line": location ? location.line : 0,
@@ -305,7 +327,8 @@ DebuggerScript._frameMirrorToJSCallFrame = function(frameMirror, callerFrame)
         "scopeType": scopeType,
         "evaluate": evaluate,
         "caller": callerFrame,
-        "restart": restart
+        "restart": restart,
+        "setVariableValue": setVariableValue
     };
 }
 
index 7711e096907f6608f0b633e24ae0da251a759364..ba05d603eeedd108b74fcb174058f46f41d1fdc0 100644 (file)
@@ -134,6 +134,17 @@ v8::Handle<v8::Value> JavaScriptCallFrame::restart()
     return result;
 }
 
+v8::Handle<v8::Value> JavaScriptCallFrame::setVariableValue(int scopeNumber, const String& variableName, v8::Handle<v8::Value> newValue)
+{
+    v8::Handle<v8::Function> setVariableValueFunction = v8::Handle<v8::Function>::Cast(m_callFrame.get()->Get(v8::String::NewSymbol("setVariableValue")));
+    v8::Handle<v8::Value> argv[] = {
+        v8::Handle<v8::Value>(v8::Integer::New(scopeNumber)),
+        v8String(variableName, m_debuggerContext->GetIsolate()),
+        newValue
+    };
+    return setVariableValueFunction->Call(m_callFrame.get(), 3, argv);
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(JAVASCRIPT_DEBUGGER)
index 31317090e37ca20679319d08ba408a070e44b78b..a7e3db1e3d1396fc402f114afb9e9b981ef26ecf 100644 (file)
@@ -61,6 +61,7 @@ public:
     
     v8::Handle<v8::Value> evaluate(const String& expression);
     v8::Handle<v8::Value> restart();
+    v8::Handle<v8::Value> setVariableValue(int scopeNumber, const String& variableName, v8::Handle<v8::Value> newValue);
     
 private:
     JavaScriptCallFrame(v8::Handle<v8::Context> debuggerContext, v8::Handle<v8::Object> callFrame);
index 203e72598bd8f7b581f38acedcef49ee81907308..e40495f24855c9f1cc8b0338b1883724e0b59e16 100644 (file)
@@ -557,6 +557,21 @@ v8::Local<v8::Value> ScriptDebugServer::getInternalProperties(v8::Handle<v8::Obj
     return callDebuggerMethod("getInternalProperties", 1, argv);
 }
 
+v8::Local<v8::Value> ScriptDebugServer::setFunctionVariableValue(v8::Handle<v8::Value> functionValue, int scopeNumber, const String& variableName, v8::Handle<v8::Value> newValue)
+{
+    v8::Local<v8::Context> debuggerContext = v8::Debug::GetDebugContext();
+    if (m_debuggerScript.get().IsEmpty())
+        return *(v8::ThrowException(v8::String::New("Debugging is not enabled.")));
+
+    v8::Handle<v8::Value> argv[] = {
+        functionValue,
+        v8::Handle<v8::Value>(v8::Integer::New(scopeNumber)),
+        v8String(variableName, debuggerContext->GetIsolate()),
+        newValue
+    };
+    return callDebuggerMethod("setFunctionVariableValue", 4, argv);
+}
+
 
 bool ScriptDebugServer::isPaused()
 {
index aefe2ebbc37079c09bc9a06510a8cb0bff4ab6d7..7dc01f29ce6d15af3dd63b3ce45ccc94bcf58275 100644 (file)
@@ -101,6 +101,8 @@ public:
 
     v8::Local<v8::Value> functionScopes(v8::Handle<v8::Function>);
     v8::Local<v8::Value> getInternalProperties(v8::Handle<v8::Object>&);
+    v8::Local<v8::Value> setFunctionVariableValue(v8::Handle<v8::Value> functionValue, int scopeNumber, const String& variableName, v8::Handle<v8::Value> newValue);
+
 
     virtual void compileScript(ScriptState*, const String& expression, const String& sourceURL, String* scriptId, String* exceptionMessage);
     virtual void clearCompiledScripts();
index 4104bdfaf87385025396106bac8f71533dccc6a6..0783d2c60fd08f6d6dd86590c65c7331d22e2b35 100644 (file)
@@ -321,6 +321,19 @@ v8::Handle<v8::Value> V8InjectedScriptHost::evaluateCallback(const v8::Arguments
     return script->Run();
 }
 
+v8::Handle<v8::Value> V8InjectedScriptHost::setFunctionVariableValueCallback(const v8::Arguments& args)
+{
+    v8::Handle<v8::Value> functionValue = args[0];
+    int scopeIndex = args[1]->Int32Value();
+    String variableName = toWebCoreStringWithUndefinedOrNullCheck(args[2]);
+    v8::Handle<v8::Value> newValue = args[3];
+
+    InjectedScriptHost* host = V8InjectedScriptHost::toNative(args.Holder());
+    ScriptDebugServer& debugServer = host->scriptDebugServer();
+    return debugServer.setFunctionVariableValue(functionValue, scopeIndex, variableName, newValue);
+}
+
+
 } // namespace WebCore
 
 #endif // ENABLE(INSPECTOR)
index e0874b2d9c957b391cd22ef6a2c728c5655f486a..7cb289e81e8a8676f04a2a78a075973f26f3a150 100644 (file)
@@ -50,6 +50,15 @@ v8::Handle<v8::Value> V8JavaScriptCallFrame::restartCallback(const v8::Arguments
     return impl->restart();
 }
 
+v8::Handle<v8::Value> V8JavaScriptCallFrame::setVariableValueCallback(const v8::Arguments& args)
+{
+    JavaScriptCallFrame* impl = V8JavaScriptCallFrame::toNative(args.Holder());
+    int scopeIndex = args[0]->Int32Value();
+    String variableName = toWebCoreStringWithUndefinedOrNullCheck(args[1]);
+    v8::Handle<v8::Value> newValue = args[2];
+    return impl->setVariableValue(scopeIndex, variableName, newValue);
+}
+
 v8::Handle<v8::Value> V8JavaScriptCallFrame::scopeChainAccessorGetter(v8::Local<v8::String> name, const v8::AccessorInfo& info)
 {
     JavaScriptCallFrame* impl = V8JavaScriptCallFrame::toNative(info.Holder());
index fae81db9e4fca6bcf947dd17cb42d048753f4e32..60aa0ca892817b1e2d59966e0133affe2e82c228 100644 (file)
@@ -116,6 +116,36 @@ void InjectedScript::restartFrame(ErrorString* errorString, const ScriptValue& c
     *errorString = "Internal error";
 }
 
+void InjectedScript::setVariableValue(ErrorString* errorString, const ScriptValue& callFrames, const String* callFrameIdOpt, const String* functionObjectIdOpt, int scopeNumber, const String& variableName, const String& newValueStr)
+{
+    ScriptFunctionCall function(injectedScriptObject(), "setVariableValue");
+    if (callFrameIdOpt) {
+        function.appendArgument(callFrames);
+        function.appendArgument(*callFrameIdOpt);
+    } else {
+        function.appendArgument(false);
+        function.appendArgument(false);
+    }
+    if (functionObjectIdOpt)
+        function.appendArgument(*functionObjectIdOpt);
+    else
+        function.appendArgument(false);
+    function.appendArgument(scopeNumber);
+    function.appendArgument(variableName);
+    function.appendArgument(newValueStr);
+    RefPtr<InspectorValue> resultValue;
+    makeCall(function, &resultValue);
+    if (!resultValue) {
+        *errorString = "Internal error";
+        return;
+    }
+    if (resultValue->type() == InspectorValue::TypeString) {
+        resultValue->asString(errorString);
+        return;
+    }
+    // Normal return.
+}
+
 void InjectedScript::getFunctionDetails(ErrorString* errorString, const String& functionId, RefPtr<FunctionDetails>* result)
 {
     ScriptFunctionCall function(injectedScriptObject(), "getFunctionDetails");
index 5d5dc4084b080b26502a3c0ab5969c85ac411ea7..2f96c2df7886892db3373a546a0076bc9806b3b2 100644 (file)
@@ -80,6 +80,7 @@ public:
                              RefPtr<TypeBuilder::Runtime::RemoteObject>* result,
                              TypeBuilder::OptOutput<bool>* wasThrown);
     void restartFrame(ErrorString*, const ScriptValue& callFrames, const String& callFrameId, RefPtr<InspectorObject>* result);
+    void setVariableValue(ErrorString*, const ScriptValue& callFrames, const String* callFrameIdOpt, const String* functionObjectIdOpt, int scopeNumber, const String& variableName, const String& newValueStr);
     void getFunctionDetails(ErrorString*, const String& functionId, RefPtr<TypeBuilder::Debugger::FunctionDetails>* result);
     void getProperties(ErrorString*, const String& objectId, bool ownProperties, RefPtr<TypeBuilder::Array<TypeBuilder::Runtime::PropertyDescriptor> >* result);
     void getInternalProperties(ErrorString*, const String& objectId, RefPtr<TypeBuilder::Array<TypeBuilder::Runtime::InternalPropertyDescriptor> >* result);
index d37a1b80b392053071de158da470abf009dfac75..7a5a580d58d10a63e65d8aacc3ecdc79982bf6e2 100644 (file)
@@ -49,4 +49,6 @@
     [Custom] DOMString databaseId(in DOMObject database);
     [Custom] DOMString storageId(in DOMObject storage);
     [Custom] DOMObject evaluate(in DOMString text);
+    // Only declarative scope (local, with and catch) is accepted. Returns undefined. 
+    [Custom] DOMObject setFunctionVariableValue(in DOMObject functionObject, in int scopeIndex, in DOMString variableName, DOMObject newValue);
 };
index 5b1ca8a12d22c2db119370ed39a08efd221b575b..91eb1e27b0d96161f72b0d514c04f7646d99007f 100644 (file)
@@ -427,21 +427,13 @@ InjectedScript.prototype = {
             var resolvedArgs = [];
             args = InjectedScriptHost.evaluate(args);
             for (var i = 0; i < args.length; ++i) {
-                objectId = args[i].objectId;
-                if (objectId) {
-                    var parsedArgId = this._parseObjectId(objectId);
-                    if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
-                        return "Arguments should belong to the same JavaScript world as the target object.";
-
-                    var resolvedArg = this._objectForId(parsedArgId);
-                    if (!this._isDefined(resolvedArg))
-                        return "Could not find object with given id";
-
-                    resolvedArgs.push(resolvedArg);
-                } else if ("value" in args[i])
-                    resolvedArgs.push(args[i].value);
-                else
-                    resolvedArgs.push(undefined);
+                var resolvedCallArgument;
+                try {
+                    resolvedCallArgument = this._resolveCallArgument(args[i]);
+                } catch (e) {
+                    return String(e);
+                }
+                resolvedArgs.push(resolvedCallArgument)
             }
         }
 
@@ -457,6 +449,30 @@ InjectedScript.prototype = {
             return this._createThrownValue(e, objectGroup);
         }
     },
+    
+    /**
+     * Resolves a value from CallArgument description.
+     * @param {RuntimeAgent.CallArgument} callArgumentJson
+     * @return {*} resolved value
+     * @throw {string} error message
+     */
+    _resolveCallArgument: function(callArgumentJson) {
+        var objectId = callArgumentJson.objectId;
+        if (objectId) {
+            var parsedArgId = this._parseObjectId(objectId);
+            if (!parsedArgId || parsedArgId["injectedScriptId"] !== injectedScriptId)
+                throw "Arguments should belong to the same JavaScript world as the target object.";
+
+            var resolvedArg = this._objectForId(parsedArgId);
+            if (!this._isDefined(resolvedArg))
+                throw "Could not find object with given id";
+
+            return resolvedArg;
+        } else if ("value" in callArgumentJson)
+            return callArgumentJson.value;
+        else
+            return undefined;
+    },
 
     /**
      * @param {Function} evalFunction
@@ -575,6 +591,51 @@ InjectedScript.prototype = {
         return result;
     },
 
+    /**
+     * Either callFrameId or functionObjectId must be specified.
+     * @param {Object} topCallFrame
+     * @param {string|boolean} callFrameId or false
+     * @param {string|boolean} functionObjectId or false
+     * @param {integer} scopeNumber
+     * @param {string} variableName
+     * @param {string} newValueJsonString RuntimeAgent.CallArgument structure serialized as string 
+     * @return {string|undefined} undefined if success or an error message 
+     */
+    setVariableValue: function(topCallFrame, callFrameId, functionObjectId, scopeNumber, variableName, newValueJsonString)
+    {   
+        var setter;
+        if (callFrameId) {
+            var callFrame = this._callFrameForId(topCallFrame, callFrameId);
+            if (!callFrame)
+                return "Could not find call frame with given id";
+            setter = callFrame.setVariableValue.bind(callFrame);    
+        } else {
+            var parsedFunctionId = this._parseObjectId(functionObjectId);
+            var func = this._objectForId(parsedFunctionId);
+            if (typeof func !== "function")
+                return "Cannot resolve function by id.";
+            setter = InjectedScriptHost.setFunctionVariableValue.bind(InjectedScriptHost, func); 
+        }
+        var newValueJson;
+        try {
+            newValueJson = InjectedScriptHost.evaluate("(" + newValueJsonString + ")");
+        } catch (e) {
+            return "Failed to parse new value JSON " + newValueJsonString + " : " + e;
+        }
+        var resolvedValue;
+        try {
+            resolvedValue = this._resolveCallArgument(newValueJson);
+        } catch (e) {
+            return String(e);
+        }
+        try {
+            setter(scopeNumber, variableName, resolvedValue);
+        } catch (e) {
+            return "Failed to change variable value: " + e;
+        }
+        return undefined;
+    },
+
     /**
      * @param {Object} topCallFrame
      * @param {string} callFrameId
index 22f2751b11a49437529eff77bc4659a7aef3ddc2..4e5bb3253b14297d441c528ab272ef5bd458b747 100644 (file)
                 ],
                 "hidden": true,
                 "description": "Sets overlay message."
+            },
+            {
+                "name": "setVariableValue",
+                "parameters": [
+                    { "name": "callFrameId", "$ref": "CallFrameId", "optional": true, "description": "Id of callframe that holds variable." },
+                    { "name": "functionObjectId", "$ref": "Runtime.RemoteObjectId", "optional": true, "description": "Object id of closure (function) that holds variable." },
+                    { "name": "scopeNumber", "type": "integer", "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually." },
+                    { "name": "variableName", "type": "string", "description": "Variable name." },
+                    { "name": "newValue", "$ref": "Runtime.CallArgument", "description": "New variable value." }
+                ],
+                "hidden": true,
+                "description": "Changes value of variable in a callframe or a closure. Either callframe or function must be specified. Object-based scopes are not supported and must be mutated manually."
             }
         ],
         "events": [
index 8347f67edd358cb76531cc9ec6dca1cda67a1f09..37fce5b383b7f468fa4f45d8062a442bcf5d339f 100644 (file)
@@ -411,7 +411,7 @@ void InspectorDebuggerAgent::getFunctionDetails(ErrorString* errorString, const
 {
     InjectedScript injectedScript = m_injectedScriptManager->injectedScriptForObjectId(functionId);
     if (injectedScript.hasNoValue()) {
-        *errorString = "Inspected frame has gone";
+        *errorString = "Function object id is obsolete";
         return;
     }
     injectedScript.getFunctionDetails(errorString, functionId, &details);
@@ -580,6 +580,30 @@ void InspectorDebuggerAgent::setOverlayMessage(ErrorString*, const String*)
 {
 }
 
+void InspectorDebuggerAgent::setVariableValue(ErrorString* errorString, const String* callFrameId, const String* functionObjectId, int scopeNumber, const String& variableName, const RefPtr<InspectorObject>& newValue)
+{
+    InjectedScript injectedScript;
+    if (callFrameId) {
+        injectedScript = m_injectedScriptManager->injectedScriptForObjectId(*callFrameId);
+        if (injectedScript.hasNoValue()) {
+            *errorString = "Inspected frame has gone";
+            return;
+        }
+    } else if (functionObjectId) {
+        injectedScript = m_injectedScriptManager->injectedScriptForObjectId(*functionObjectId);
+        if (injectedScript.hasNoValue()) {
+            *errorString = "Function object id cannot be resolved";
+            return;
+        }
+    } else {
+        *errorString = "Either call frame or function object must be specified";
+        return;
+    }
+    String newValueString = newValue->toJSONString();
+
+    injectedScript.setVariableValue(errorString, m_currentCallStack, callFrameId, functionObjectId, scopeNumber, variableName, newValueString);
+}
+
 void InspectorDebuggerAgent::scriptExecutionBlockedByCSP(const String& directiveText)
 {
     if (scriptDebugServer().pauseOnExceptionsState() != ScriptDebugServer::DontPauseOnExceptions) {
index 8488f53d1edaeee7f879688b78c08ffd028980d8..7486d19ca1e2cc9573bfde7d7b8d1398b914ee56 100644 (file)
@@ -113,6 +113,7 @@ public:
     void compileScript(ErrorString*, const String& expression, const String& sourceURL, TypeBuilder::OptOutput<TypeBuilder::Debugger::ScriptId>*, TypeBuilder::OptOutput<String>* syntaxErrorMessage);
     void runScript(ErrorString*, const TypeBuilder::Debugger::ScriptId&, const int* executionContextId, const String* objectGroup, const bool* doNotPauseOnExceptionsAndMuteConsole, RefPtr<TypeBuilder::Runtime::RemoteObject>& result, TypeBuilder::OptOutput<bool>* wasThrown);
     virtual void setOverlayMessage(ErrorString*, const String*);
+    virtual void setVariableValue(ErrorString*, const String* in_callFrame, const String* in_functionObjectId, int in_scopeNumber, const String& in_variableName, const RefPtr<InspectorObject>& in_newValue);
 
     void schedulePauseOnNextStatement(InspectorFrontend::Debugger::Reason::Enum breakReason, PassRefPtr<InspectorObject> data);
     void cancelPauseOnNextStatement();
index 3c1d31da5fc4e939c025cf5cc0053948319b5167..e9e53d8aedb72d84ab3f57bb16fc93d4c830c251 100644 (file)
@@ -39,6 +39,9 @@
 
     [Custom] void evaluate(in DOMString script);
     [Custom] DOMObject restart();
+    
+    // Only declarative scope (local, with and catch) is accepted. Returns undefined. 
+    [Custom] DOMObject setVariableValue(in int scopeIndex, in DOMString variableName, DOMObject newValue);
 
     readonly attribute JavaScriptCallFrame caller;
     readonly attribute long sourceID;