diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 2632fb34398..d8ac29e699d 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -470,6 +470,38 @@ StackFrames.prototype = { this._addExpander(thisVar, frame.this); } + if (frame.environment) { + // Add nodes for every argument. + let variables = frame.environment.bindings.arguments; + for each (let variable in variables) { + let name = Object.getOwnPropertyNames(variable)[0]; + let paramVar = localScope.addVar(name); + let paramVal = variable[name].value; + paramVar.setGrip(paramVal); + this._addExpander(paramVar, paramVal); + } + + // Add nodes for every other variable in scope. + variables = frame.environment.bindings.variables; + for (let variable in variables) { + let paramVar = localScope.addVar(variable); + let paramVal = variables[variable].value; + paramVar.setGrip(paramVal); + this._addExpander(paramVar, paramVal); + } + + // If we already found 'arguments', we are done here. + if ("arguments" in frame.environment.bindings.variables) { + // Signal that variables have been fetched. + DebuggerController.dispatchEvent("Debugger:FetchedVariables"); + return; + } + } + + // Sometimes in call frames with arguments we don't get 'arguments' in the + // environment (bug 746601) and we have to construct it manually. Note, that + // in this case arguments.callee will be absent, even in the cases where it + // shouldn't be. if (frame.arguments && frame.arguments.length > 0) { // Add "arguments". let argsVar = localScope.addVar("arguments"); @@ -479,33 +511,20 @@ StackFrames.prototype = { }); this._addExpander(argsVar, frame.arguments); - // Add variables for every argument. - let objClient = this.activeThread.pauseGrip(frame.callee); - objClient.getSignature(function SF_getSignature(aResponse) { - for (let i = 0, l = aResponse.parameters.length; i < l; i++) { - let param = aResponse.parameters[i]; - let paramVar = localScope.addVar(param); - let paramVal = frame.arguments[i]; - - paramVar.setGrip(paramVal); - this._addExpander(paramVar, paramVal); - } - - // Signal that call parameters have been fetched. - DebuggerController.dispatchEvent("Debugger:FetchedParameters"); - - }.bind(this)); + // Signal that variables have been fetched. + DebuggerController.dispatchEvent("Debugger:FetchedVariables"); } + }, /** - * Adds a onexpand callback for a variable, lazily handling the addition of + * Adds an 'onexpand' callback for a variable, lazily handling the addition of * new properties. */ _addExpander: function SF__addExpander(aVar, aObject) { // No need for expansion for null and undefined values, but we do need them // for frame.arguments which is a regular array. - if (!aObject || typeof aObject !== "object" || + if (!aVar || !aObject || typeof aObject !== "object" || (aObject.type !== "object" && !Array.isArray(aObject))) { return; } diff --git a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js index e0efb63fc4c..6c12ba93f76 100644 --- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js @@ -27,10 +27,10 @@ function testFrameParameters() { dump("Started testFrameParameters!\n"); - gDebugger.addEventListener("Debugger:FetchedParameters", function test() { - dump("Entered Debugger:FetchedParameters!\n"); + gDebugger.addEventListener("Debugger:FetchedVariables", function test() { + dump("Entered Debugger:FetchedVariables!\n"); - gDebugger.removeEventListener("Debugger:FetchedParameters", test, false); + gDebugger.removeEventListener("Debugger:FetchedVariables", test, false); Services.tm.currentThread.dispatch({ run: function() { dump("After currentThread.dispatch!\n"); @@ -52,33 +52,42 @@ function testFrameParameters() is(frames.querySelectorAll(".dbg-stackframe").length, 3, "Should have three frames."); - is(localNodes.length, 8, + is(localNodes.length, 11, "The localScope should contain all the created variable elements."); is(localNodes[0].querySelector(".info").textContent, "[object Proxy]", "Should have the right property value for 'this'."); - is(localNodes[1].querySelector(".info").textContent, "[object Arguments]", - "Should have the right property value for 'arguments'."); - - is(localNodes[2].querySelector(".info").textContent, "[object Object]", + is(localNodes[1].querySelector(".info").textContent, "[object Object]", "Should have the right property value for 'aArg'."); - is(localNodes[3].querySelector(".info").textContent, '"beta"', + is(localNodes[2].querySelector(".info").textContent, '"beta"', "Should have the right property value for 'bArg'."); - is(localNodes[4].querySelector(".info").textContent, "3", + is(localNodes[3].querySelector(".info").textContent, "3", "Should have the right property value for 'cArg'."); - is(localNodes[5].querySelector(".info").textContent, "false", + is(localNodes[4].querySelector(".info").textContent, "false", "Should have the right property value for 'dArg'."); - is(localNodes[6].querySelector(".info").textContent, "null", + is(localNodes[5].querySelector(".info").textContent, "null", "Should have the right property value for 'eArg'."); - is(localNodes[7].querySelector(".info").textContent, "undefined", + is(localNodes[6].querySelector(".info").textContent, "undefined", "Should have the right property value for 'fArg'."); + is(localNodes[7].querySelector(".info").textContent, "1", + "Should have the right property value for 'a'."); + + is(localNodes[8].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'b'."); + + is(localNodes[9].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'c'."); + + is(localNodes[10].querySelector(".info").textContent, "[object Arguments]", + "Should have the right property value for 'arguments'."); + resumeAndFinish(); }}, 0); }, false); diff --git a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js index fbbccf3fea1..da5f18d1f2c 100644 --- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js @@ -27,10 +27,10 @@ function testFrameParameters() { dump("Started testFrameParameters!\n"); - gDebugger.addEventListener("Debugger:FetchedParameters", function test() { - dump("Entered Debugger:FetchedParameters!\n"); + gDebugger.addEventListener("Debugger:FetchedVariables", function test() { + dump("Entered Debugger:FetchedVariables!\n"); - gDebugger.removeEventListener("Debugger:FetchedParameters", test, false); + gDebugger.removeEventListener("Debugger:FetchedVariables", test, false); Services.tm.currentThread.dispatch({ run: function() { dump("After currentThread.dispatch!\n"); @@ -50,16 +50,17 @@ function testFrameParameters() is(frames.querySelectorAll(".dbg-stackframe").length, 3, "Should have three frames."); - is(localNodes.length, 8, + is(localNodes.length, 11, "The localScope should contain all the created variable elements."); is(localNodes[0].querySelector(".info").textContent, "[object Proxy]", "Should have the right property value for 'this'."); - // Expand the __proto__ and arguments tree nodes. This causes their - // properties to be retrieved and displayed. + // Expand the '__proto__', 'arguments' and 'a' tree nodes. This causes + // their properties to be retrieved and displayed. localNodes[0].expand(); - localNodes[1].expand(); + localNodes[9].expand(); + localNodes[10].expand(); // Poll every few milliseconds until the properties are retrieved. // It's important to set the timer in the chrome window, because the @@ -70,7 +71,9 @@ function testFrameParameters() ok(false, "Timed out while polling for the properties."); resumeAndFinish(); } - if (!localNodes[0].fetched || !localNodes[1].fetched) { + if (!localNodes[0].fetched || + !localNodes[9].fetched || + !localNodes[10].fetched) { return; } window.clearInterval(intervalID); @@ -82,14 +85,26 @@ function testFrameParameters() .textContent.search(/object/) != -1, "__proto__ should be an object."); - is(localNodes[1].querySelector(".info").textContent, "[object Arguments]", + is(localNodes[9].querySelector(".info").textContent, "[object Object]", + "Should have the right property value for 'c'."); + + is(localNodes[9].querySelectorAll(".property > .title > .key")[1] + .textContent, "a", + "Should have the right property name for 'a'."); + + is(localNodes[9].querySelectorAll(".property > .title > .value")[1] + .textContent, 1, + "Should have the right value for 'c.a'."); + + is(localNodes[10].querySelector(".info").textContent, + "[object Arguments]", "Should have the right property value for 'arguments'."); - is(localNodes[1].querySelector(".property > .title > .key") + is(localNodes[10].querySelector(".property > .title > .key") .textContent, "length", - "Should have the right property name for length."); + "Should have the right property name for 'length'."); - is(localNodes[1].querySelector(".property > .title > .value") + is(localNodes[10].querySelector(".property > .title > .value") .textContent, 5, "Should have the right argument length."); @@ -104,7 +119,8 @@ function testFrameParameters() } function resumeAndFinish() { - gDebugger.DebuggerController.activeThread.addOneTimeListener("framescleared", function() { + let thread = gDebugger.DebuggerController.activeThread; + thread.addOneTimeListener("framescleared", function() { Services.tm.currentThread.dispatch({ run: function() { var frames = gDebugger.DebuggerView.StackFrames._frames; @@ -115,7 +131,7 @@ function resumeAndFinish() { }}, 0); }); - gDebugger.DebuggerController.activeThread.resume(); + thread.resume(); } registerCleanupFunction(function() { diff --git a/toolkit/devtools/debugger/server/dbg-script-actors.js b/toolkit/devtools/debugger/server/dbg-script-actors.js index a479825756c..d5aef886b41 100644 --- a/toolkit/devtools/debugger/server/dbg-script-actors.js +++ b/toolkit/devtools/debugger/server/dbg-script-actors.js @@ -714,29 +714,29 @@ ThreadActor.prototype = { }, /** - * Create and return an environment actor that corresponds to the - * Debugger.Environment for the provided object. - * @param Debugger.Object aObject - * The object whose lexical environment we want to extract. + * Create and return an environment actor that corresponds to the provided + * Debugger.Environment. + * @param Debugger.Environment aEnvironment + * The lexical environment we want to extract. * @param object aPool * The pool where the newly-created actor will be placed. - * @return The EnvironmentActor for aObject or undefined for host functions or - * functions scoped to a non-debuggee global. + * @return The EnvironmentActor for aEnvironment or undefined for host + * functions or functions scoped to a non-debuggee global. */ - createEnvironmentActor: function TA_createEnvironmentActor(aObject, aPool) { - let environment = aObject.environment; - if (!environment) { + createEnvironmentActor: + function TA_createEnvironmentActor(aEnvironment, aPool) { + if (!aEnvironment) { return undefined; } - if (environment.actor) { - return environment.actor; + if (aEnvironment.actor) { + return aEnvironment.actor; } - let actor = new EnvironmentActor(aObject, this); + let actor = new EnvironmentActor(aEnvironment, this); this._environmentActors.push(actor); aPool.addActor(actor); - environment.actor = actor; + aEnvironment.actor = actor; return actor; }, @@ -1054,7 +1054,7 @@ ObjectActor.prototype = { let descriptor = {}; descriptor.configurable = aObject.configurable; descriptor.enumerable = aObject.enumerable; - if (aObject.value) { + if (aObject.value !== undefined) { descriptor.writable = aObject.writable; descriptor.value = this.threadActor.createValueGrip(aObject.value); } else { @@ -1102,7 +1102,7 @@ ObjectActor.prototype = { " 'Function' class." }; } - let envActor = this.threadActor.createEnvironmentActor(this.obj, + let envActor = this.threadActor.createEnvironmentActor(this.obj.environment, this.registeredPool); if (!envActor) { return { error: "notDebuggee", @@ -1110,7 +1110,7 @@ ObjectActor.prototype = { } return { name: this.obj.name || null, - scope: envActor.form() }; + scope: envActor.form(this.obj) }; }, /** @@ -1239,9 +1239,9 @@ FrameActor.prototype = { } let envActor = this.threadActor - .createEnvironmentActor(this.frame, + .createEnvironmentActor(this.frame.environment, this.frameLifetimePool); - form.environment = envActor ? envActor.form() : envActor; + form.environment = envActor ? envActor.form(this.frame) : envActor; form.this = this.threadActor.createValueGrip(this.frame.this); form.arguments = this._args(); if (this.frame.script) { @@ -1350,14 +1350,14 @@ BreakpointActor.prototype.requestTypes = { * the bindings introduced by a lexical environment and assigning new values to * those identifier bindings. * - * @param Debugger.Object aObject - * The object whose lexical environment will be used to create the actor. + * @param Debugger.Environment aEnvironment + * The lexical environment that will be used to create the actor. * @param ThreadActor aThreadActor * The parent thread actor that contains this environment. */ -function EnvironmentActor(aObject, aThreadActor) +function EnvironmentActor(aEnvironment, aThreadActor) { - this.obj = aObject; + this.obj = aEnvironment; this.threadActor = aThreadActor; } @@ -1365,41 +1365,47 @@ EnvironmentActor.prototype = { actorPrefix: "environment", /** - * Returns an environment form for use in a protocol message. + * Returns an environment form for use in a protocol message. Note that the + * requirement of passing the frame or function as a parameter is only + * temporary, since when bug 747514 lands, the environment will have a callee + * property that will contain it. + * + * @param object aObject + * The stack frame or function object whose environment bindings are + * being generated. */ - form: function EA_form() { + form: function EA_form(aObject) { // Debugger.Frame might be dead by the time we get here, which will cause // accessing its properties to throw. - if (!this.obj.live) { + if (!aObject.live) { return undefined; } let parent; - if (this.obj.environment.parent) { - parent = this.threadActor - .createEnvironmentActor(this.obj.environment.parent, - this.registeredPool); + if (this.obj.parent) { + let thread = this.threadActor; + parent = thread.createEnvironmentActor(this.obj.parent.environment, + this.registeredPool); } let form = { actor: this.actorID, - parent: parent ? parent.form() : parent }; + parent: parent ? parent.form(this.obj.parent) : parent }; - if (this.obj.environment.type == "object") { - if (this.obj.environment.parent) { + if (aObject.type == "object") { + if (this.obj.parent) { form.type = "with"; } else { form.type = "object"; } - form.object = this.threadActor.createValueGrip(this.obj.environment.object); + form.object = this.threadActor.createValueGrip(aObject.object); } else { - if (this.obj.class == "Function") { + if (aObject.class == "Function") { form.type = "function"; - form.function = this.threadActor.createValueGrip(this.obj); - form.functionName = this.obj.name; + form.function = this.threadActor.createValueGrip(aObject); + form.functionName = aObject.name; } else { form.type = "block"; } - - form.bindings = this._bindings(); + form.bindings = this._bindings(aObject); } return form; @@ -1407,19 +1413,41 @@ EnvironmentActor.prototype = { /** * Return the identifier bindings object as required by the remote protocol - * specification. + * specification. Note that the requirement of passing the frame or function + * as a parameter is only temporary, since when bug 747514 lands, the + * environment will have a callee property that will contain it. + * + * @param object aObject [optional] + * The stack frame or function object whose environment bindings are + * being generated. When left unspecified, the bindings do not contain + * an 'arguments' property. */ - _bindings: function EA_bindings() { + _bindings: function EA_bindings(aObject) { let bindings = { arguments: [], variables: {} }; - // TODO: this will be redundant after bug 692984 is fixed. - if (typeof this.obj.environment.getVariableDescriptor != "function") { + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands (bug 725815). + if (typeof this.obj.getVariable != "function") { + //if (typeof this.obj.getVariableDescriptor != "function") { return bindings; } - for (let name in this.obj.parameterNames) { + let parameterNames; + if (aObject && aObject.callee) { + parameterNames = aObject.callee.parameterNames; + } + for each (let name in parameterNames) { let arg = {}; - let desc = this.obj.environment.getVariableDescriptor(name); + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands (bug 725815). + let desc = { + value: this.obj.getVariable(name), + configurable: false, + writable: true, + enumerable: true + }; + + // let desc = this.obj.getVariableDescriptor(name); let descForm = { enumerable: true, configurable: desc.configurable @@ -1435,14 +1463,22 @@ EnvironmentActor.prototype = { bindings.arguments.push(arg); } - for (let name in this.obj.environment.names()) { + for each (let name in this.obj.names()) { if (bindings.arguments.some(function exists(element) { return !!element[name]; })) { continue; } - let desc = this.obj.environment.getVariableDescriptor(name); + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands. + let desc = { + value: this.obj.getVariable(name), + configurable: false, + writable: true, + enumerable: true + }; + //let desc = this.obj.getVariableDescriptor(name); let descForm = { enumerable: true, configurable: desc.configurable @@ -1468,7 +1504,7 @@ EnvironmentActor.prototype = { * The protocol request object. */ onAssign: function EA_onAssign(aRequest) { - let desc = this.obj.environment.getVariableDescriptor(aRequest.name); + let desc = this.obj.getVariableDescriptor(aRequest.name); if (!desc.writable) { return { error: "immutableBinding", @@ -1477,7 +1513,7 @@ EnvironmentActor.prototype = { } try { - this.obj.environment.setVariable(aRequest.name, aRequest.value); + this.obj.setVariable(aRequest.name, aRequest.value); } catch (e) { if (e instanceof Debugger.DebuggeeWouldRun) { return { error: "threadWouldRun", diff --git a/toolkit/devtools/debugger/tests/unit/test_framebindings-01.js b/toolkit/devtools/debugger/tests/unit/test_framebindings-01.js new file mode 100644 index 00000000000..15ab5febdd6 --- /dev/null +++ b/toolkit/devtools/debugger/tests/unit/test_framebindings-01.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check a frame actor's bindings property. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-stack"); + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestGlobalClientAndResume(gClient, "test-stack", function(aResponse, aThreadClient) { + gThreadClient = aThreadClient; + test_pause_frame(); + }); + }); + do_test_pending(); +} + +function test_pause_frame() +{ + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let bindings = aPacket.frame.environment.bindings; + let args = bindings.arguments; + let vars = bindings.variables; + + do_check_eq(args.length, 6); + do_check_eq(args[0].aNumber.value, 42); + do_check_eq(args[1].aBool.value, true); + do_check_eq(args[2].aString.value, "nasu"); + do_check_eq(args[3].aNull.value.type, "null"); + do_check_eq(args[4].aUndefined.value.type, "undefined"); + do_check_eq(args[5].aObject.value.type, "object"); + do_check_eq(args[5].aObject.value.class, "Object"); + do_check_true(!!args[5].aObject.value.actor); + + do_check_eq(vars.a.value, 1); + do_check_eq(vars.b.value, true); + do_check_eq(vars.c.value.type, "object"); + do_check_eq(vars.c.value.class, "Object"); + do_check_true(!!vars.c.value.actor); + + gThreadClient.resume(function() { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function() { + function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) { + var a = 1; + var b = true; + var c = { a: "a" }; + debugger; + }; + stopMe(42, true, "nasu", null, undefined, { foo: "bar" }); + } + ")()"); +} diff --git a/toolkit/devtools/debugger/tests/unit/xpcshell.ini b/toolkit/devtools/debugger/tests/unit/xpcshell.ini index 43f6ce50b29..d5eda702346 100644 --- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini +++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini @@ -53,3 +53,4 @@ tail = [test_stepping-02.js] [test_stepping-03.js] [test_stepping-04.js] +[test_framebindings-01.js]