Bug 952767 - Variables view scopes should always be lazily populated, r=past

This commit is contained in:
Victor Porof 2014-01-03 23:41:28 +02:00
Родитель 92ed141540
Коммит 396353cbb4
7 изменённых файлов: 133 добавлений и 209 удалений

Просмотреть файл

@ -887,11 +887,8 @@ StackFrames.prototype = {
}
} while ((environment = environment.parent));
// Signal that scope environments have been shown and commit the current
// variables view hierarchy to briefly flash items that changed between the
// previous and current scope/variables/properties.
// Signal that scope environments have been shown.
window.emit(EVENTS.FETCHED_SCOPES);
DebuggerView.Variables.commitHierarchy();
},
/**
@ -1005,11 +1002,8 @@ StackFrames.prototype = {
expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
}
// Signal that watch expressions have been fetched and commit the
// current variables view hierarchy to briefly flash items that changed
// between the previous and current scope/variables/properties.
// Signal that watch expressions have been fetched.
window.emit(EVENTS.FETCHED_WATCH_EXPRESSIONS);
DebuggerView.Variables.commitHierarchy();
});
},

Просмотреть файл

@ -181,6 +181,9 @@ let DebuggerView = {
// Relay events from the VariablesView.
this.Variables.on("fetched", (aEvent, aType) => {
switch (aType) {
case "scopes":
window.emit(EVENTS.FETCHED_SCOPES);
break;
case "variables":
window.emit(EVENTS.FETCHED_VARIABLES);
break;

Просмотреть файл

@ -1,7 +1,5 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const TAB_URL = EXAMPLE_URL + "doc_closures.html";
@ -15,6 +13,7 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gDebuggee.gRecurseLimit = 2;
waitForSourceShown(gPanel, ".html")
.then(testClosure)
@ -33,45 +32,25 @@ function test() {
gDebuggee);
});
gDebuggee.gRecurseLimit = 2;
return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
let deferred = promise.defer();
let gVars = gDebugger.DebuggerView.Variables,
localScope = gVars.getScopeAtIndex(0),
globalScope = gVars.getScopeAtIndex(1),
localNodes = localScope.target.querySelector(".variables-view-element-details").childNodes,
globalNodes = globalScope.target.querySelector(".variables-view-element-details").childNodes;
let gVars = gDebugger.DebuggerView.Variables;
let localScope = gVars.getScopeAtIndex(0);
let localNodes = localScope.target.querySelector(".variables-view-element-details").childNodes;
is(localNodes[4].querySelector(".name").getAttribute("value"), "person",
"Should have the right property name for |person|.");
is(localNodes[4].querySelector(".value").getAttribute("value"), "Object",
"Should have the right property value for |person|.");
// Expand the 'person' tree node. This causes its properties to be
// retrieved and displayed.
let personNode = gVars.getItemForNode(localNodes[4]);
let personFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES);
personNode.expand();
is(personNode.expanded, true, "person should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count1 = 0;
let intervalID = window.setInterval(function(){
info("count1: " + count1);
if (++count1 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID);
deferred.reject("Timed out.");
return;
}
if (!personNode._retrieved) {
return;
}
window.clearInterval(intervalID);
return personFetched.then(() => {
is(personNode.expanded, true,
"|person| should be expanded at this point.");
is(personNode.get("getName").target.querySelector(".name")
.getAttribute("value"), "getName",
@ -90,27 +69,16 @@ function test() {
// retrieved and displayed.
let getFooNode = personNode.get("getFoo");
let getNameNode = personNode.get("getName");
let funcsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
let funcClosuresFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 2);
getFooNode.expand();
getNameNode.expand();
is(getFooNode.expanded, true, "person.getFoo should be expanded at this point.");
is(getNameNode.expanded, true, "person.getName should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count2 = 0;
let intervalID1 = window.setInterval(function(){
info("count2: " + count2);
if (++count2 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID1);
deferred.reject("Timed out.");
return;
}
if (!getFooNode._retrieved || !getNameNode._retrieved) {
return;
}
window.clearInterval(intervalID1);
return funcsFetched.then(() => {
is(getFooNode.expanded, true,
"|person.getFoo| should be expanded at this point.");
is(getNameNode.expanded, true,
"|person.getName| should be expanded at this point.");
is(getFooNode.get("<Closure>").target.querySelector(".name")
.getAttribute("value"), "<Closure>",
@ -125,30 +93,18 @@ function test() {
.getAttribute("value"), "",
"The closure node has no value for getName.");
// Expand the Closure nodes.
// Expand the closure nodes. This causes their environments to be
// retrieved and displayed.
let getFooClosure = getFooNode.get("<Closure>");
let getNameClosure = getNameNode.get("<Closure>");
getFooClosure.expand();
getNameClosure.expand();
is(getFooClosure.expanded, true, "person.getFoo closure should be expanded at this point.");
is(getNameClosure.expanded, true, "person.getName closure should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count3 = 0;
let intervalID2 = window.setInterval(function(){
info("count3: " + count3);
if (++count3 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID2);
deferred.reject("Timed out.");
return;
}
if (!getFooClosure._retrieved || !getNameClosure._retrieved) {
return;
}
window.clearInterval(intervalID2);
return funcClosuresFetched.then(() => {
is(getFooClosure.expanded, true,
"|person.getFoo| closure should be expanded at this point.");
is(getNameClosure.expanded, true,
"|person.getName| closure should be expanded at this point.");
is(getFooClosure.get("Function scope [_pfactory]").target.querySelector(".name")
.getAttribute("value"), "Function scope [_pfactory]",
@ -166,27 +122,15 @@ function test() {
// Expand the scope nodes.
let getFooInnerScope = getFooClosure.get("Function scope [_pfactory]");
let getNameInnerScope = getNameClosure.get("Function scope [_pfactory]");
let innerFuncsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
getFooInnerScope.expand();
getNameInnerScope.expand();
is(getFooInnerScope.expanded, true, "person.getFoo inner scope should be expanded at this point.");
is(getNameInnerScope.expanded, true, "person.getName inner scope should be expanded at this point.");
// Poll every few milliseconds until the properties are retrieved.
// It's important to set the timer in the chrome window, because the
// content window timers are disabled while the debuggee is paused.
let count4 = 0;
let intervalID3 = window.setInterval(function(){
info("count4: " + count4);
if (++count4 > 50) {
ok(false, "Timed out while polling for the properties.");
window.clearInterval(intervalID3);
deferred.reject("Timed out.");
return;
}
if (!getFooInnerScope._retrieved || !getNameInnerScope._retrieved) {
return;
}
window.clearInterval(intervalID3);
return funcsFetched.then(() => {
is(getFooInnerScope.expanded, true,
"|person.getFoo| inner scope should be expanded at this point.");
is(getNameInnerScope.expanded, true,
"|person.getName| inner scope should be expanded at this point.");
// Only test that each function closes over the necessary variable.
// We wouldn't want future SpiderMonkey closure space
@ -203,14 +147,10 @@ function test() {
is(getNameInnerScope.get("name").target.querySelector(".value")
.getAttribute("value"), '"Bob"',
"The name node has the expected value.");
deferred.resolve();
}, 100);
}, 100);
}, 100);
}, 100);
return deferred.promise;
});
});
});
});
});
}
}

Просмотреть файл

@ -49,12 +49,12 @@ function test() {
is(firstScope._store.size, 3,
"The first scope should have all the variables available.");
is(secondScope._store.size, 3,
"The second scope shoild have all the variables available.");
is(thirdScope._store.size, 3,
"The third scope shoild have all the variables available.");
is(secondScope._store.size, 0,
"The second scope shoild have no variables available yet.");
is(thirdScope._store.size, 0,
"The third scope shoild have no variables available yet.");
is(globalScope._store.size, 0,
"The global scope shoild have no variables available.");
"The global scope shoild have no variables available yet.");
// Test getOwnerScopeForVariableOrProperty with simple variables.
@ -95,6 +95,14 @@ function test() {
// Test getOwnerScopeForVariableOrProperty with a simple variable
// from non-topmost scopes.
// Only need to wait for a single FETCHED_VARIABLES event, just for the
// global scope, because the other local scopes already have the
// arguments and variables available as evironment bindings.
let fetched = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES);
secondScope.expand();
thirdScope.expand();
globalScope.expand();
let someVar2 = secondScope.get("a");
let someOwner2 = variables.getOwnerScopeForVariableOrProperty(someVar2);
is(someOwner2, secondScope,

Просмотреть файл

@ -15,37 +15,51 @@ function test() {
let variables = win.DebuggerView.Variables;
// Wait for the hierarchy to be committed by the VariablesViewController.
let committed = promise.defer();
variables.oncommit = committed.resolve;
let committedLocalScopeHierarchy = promise.defer();
variables.oncommit = committedLocalScopeHierarchy.resolve;
// Allow this generator function to yield first.
executeSoon(() => debuggee.test());
yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
yield committed.promise;
yield committedLocalScopeHierarchy.promise;
let firstScope = variables.getScopeAtIndex(0);
let secondScope = variables.getScopeAtIndex(1);
let thirdScope = variables.getScopeAtIndex(2);
let someVar1 = firstScope.get("a");
let someVar2 = secondScope.get("a");
let someVar3 = thirdScope.get("a");
let argsVar1 = firstScope.get("arguments");
let argsVar2 = secondScope.get("arguments");
let argsVar3 = thirdScope.get("arguments");
is(someVar1.target.hasAttribute("overridden"), false,
"The first 'a' variable should not be marked as being overridden.");
is(someVar2.target.hasAttribute("overridden"), true,
"The second 'a' variable should be marked as being overridden.");
is(someVar3.target.hasAttribute("overridden"), true,
"The third 'a' variable should be marked as being overridden.");
is(argsVar1.target.hasAttribute("overridden"), false,
"The first 'arguments' variable should not be marked as being overridden.");
// Wait for the hierarchy to be committed by the VariablesViewController.
let committedSecondScopeHierarchy = promise.defer();
variables.oncommit = committedSecondScopeHierarchy.resolve;
secondScope.expand();
yield committedSecondScopeHierarchy.promise;
let someVar2 = secondScope.get("a");
let argsVar2 = secondScope.get("arguments");
is(someVar2.target.hasAttribute("overridden"), true,
"The second 'a' variable should be marked as being overridden.");
is(argsVar2.target.hasAttribute("overridden"), true,
"The second 'arguments' variable should be marked as being overridden.");
// Wait for the hierarchy to be committed by the VariablesViewController.
let committedThirdScopeHierarchy = promise.defer();
variables.oncommit = committedThirdScopeHierarchy.resolve;
thirdScope.expand();
yield committedThirdScopeHierarchy.promise;
let someVar3 = thirdScope.get("a");
let argsVar3 = thirdScope.get("arguments");
is(someVar3.target.hasAttribute("overridden"), true,
"The third 'a' variable should be marked as being overridden.");
is(argsVar3.target.hasAttribute("overridden"), true,
"The third 'arguments' variable should be marked as being overridden.");

Просмотреть файл

@ -2078,7 +2078,6 @@ Scope.prototype = {
_enumItems: null,
_nonEnumItems: null,
_fetched: false,
_retrieved: false,
_committed: false,
_isLocked: false,
_isExpanded: false,

Просмотреть файл

@ -152,8 +152,6 @@ VariablesViewController.prototype = {
aTarget.setGrip(aGrip.initial + aResponse.substring);
aTarget.hideArrow();
// Mark the string as having retrieved.
aTarget._retrieved = true;
deferred.resolve();
});
@ -172,13 +170,6 @@ VariablesViewController.prototype = {
_populateFromObject: function(aTarget, aGrip) {
let deferred = promise.defer();
// Mark the specified variable as having retrieved all its properties.
let finish = variable => {
variable._retrieved = true;
this.view.commitHierarchy();
deferred.resolve();
};
let objectClient = this._getObjectClient(aGrip);
objectClient.getPrototypeAndProperties(aResponse => {
let { ownProperties, prototype } = aResponse;
@ -224,12 +215,12 @@ VariablesViewController.prototype = {
// in the current scope chain. Not necessarily an actual error,
// it just means that there's no closure for the function.
console.warn(aResponse.error + ": " + aResponse.message);
return void finish(aTarget);
return void deferred.resolve();
}
this._addVarScope(aTarget, aResponse.scope).then(() => finish(aTarget));
this._populateWithClosure(aTarget, aResponse.scope).then(deferred.resolve);
});
} else {
finish(aTarget);
deferred.resolve();
}
});
@ -237,48 +228,43 @@ VariablesViewController.prototype = {
},
/**
* Adds the scope chain elements (closures) of a function variable to the
* view.
* Adds the scope chain elements (closures) of a function variable.
*
* @param Variable aTarget
* The variable where the properties will be placed into.
* @param Scope aScope
* The lexical environment form as specified in the protocol.
*/
_addVarScope: function(aTarget, aScope) {
_populateWithClosure: function(aTarget, aScope) {
let objectScopes = [];
let environment = aScope;
let funcScope = aTarget.addItem("<Closure>");
funcScope._target.setAttribute("scope", "");
funcScope._fetched = true;
funcScope.target.setAttribute("scope", "");
funcScope.showArrow();
do {
// Create a scope to contain all the inspected variables.
let label = StackFrameUtils.getScopeLabel(environment);
// Block scopes have the same label, so make addItem allow duplicates.
// Block scopes may have the same label, so make addItem allow duplicates.
let closure = funcScope.addItem(label, undefined, true);
closure._target.setAttribute("scope", "");
closure._fetched = environment.class == "Function";
closure.target.setAttribute("scope", "");
closure.showArrow();
// Add nodes for every argument and every other variable in scope.
if (environment.bindings) {
this._addBindings(closure, environment.bindings);
funcScope._retrieved = true;
closure._retrieved = true;
this._populateWithEnvironmentBindings(closure, environment.bindings);
} else {
let deferred = Promise.defer();
let deferred = promise.defer();
objectScopes.push(deferred.promise);
this._getEnvironmentClient(environment).getBindings(response => {
this._addBindings(closure, response.bindings);
funcScope._retrieved = true;
closure._retrieved = true;
this._populateWithEnvironmentBindings(closure, response.bindings);
deferred.resolve();
});
}
} while ((environment = environment.parent));
aTarget.expand();
return Promise.all(objectScopes).then(() => {
return promise.all(objectScopes).then(() => {
// Signal that scopes have been fetched.
this.view.emit("fetched", "scopes", funcScope);
});
@ -287,32 +273,32 @@ VariablesViewController.prototype = {
/**
* Adds nodes for every specified binding to the closure node.
*
* @param Variable closure
* The node where the bindings will be placed into.
* @param object bindings
* @param Variable aTarget
* The variable where the bindings will be placed into.
* @param object aBindings
* The bindings form as specified in the protocol.
*/
_addBindings: function(closure, bindings) {
for (let argument of bindings.arguments) {
let name = Object.getOwnPropertyNames(argument)[0];
let argRef = closure.addItem(name, argument[name]);
let argVal = argument[name].value;
this.addExpander(argRef, argVal);
}
_populateWithEnvironmentBindings: function(aTarget, aBindings) {
// Add nodes for every argument in the scope.
aTarget.addItems(aBindings.arguments.reduce((accumulator, arg) => {
let name = Object.getOwnPropertyNames(arg)[0];
let descriptor = arg[name];
accumulator[name] = descriptor;
return accumulator;
}, {}), {
// Arguments aren't sorted.
sorted: false,
// Expansion handlers must be set after the properties are added.
callback: this.addExpander
});
let aVariables = bindings.variables;
let variableNames = Object.keys(aVariables);
// Sort all of the variables before adding them, if preferred.
if (VARIABLES_SORTING_ENABLED) {
variableNames.sort();
}
// Add the variables to the specified scope.
for (let name of variableNames) {
let varRef = closure.addItem(name, aVariables[name]);
let varVal = aVariables[name].value;
this.addExpander(varRef, varVal);
}
// Add nodes for every other variable in the scope.
aTarget.addItems(aBindings.variables, {
// Not all variables need to force sorted properties.
sorted: VARIABLES_SORTING_ENABLED,
// Expansion handlers must be set after the properties are added.
callback: this.addExpander
});
},
/**
@ -328,12 +314,10 @@ VariablesViewController.prototype = {
// Attach evaluation macros as necessary.
if (aTarget.getter || aTarget.setter) {
aTarget.evaluationMacro = this._overrideValueEvalMacro;
let getter = aTarget.get("get");
if (getter) {
getter.evaluationMacro = this._getterOrSetterEvalMacro;
}
let setter = aTarget.get("set");
if (setter) {
setter.evaluationMacro = this._getterOrSetterEvalMacro;
@ -352,19 +336,13 @@ VariablesViewController.prototype = {
aTarget.showArrow();
}
if (aSource.type == "block" || aSource.type == "function") {
// Block and function environments already contain scope arguments and
// corresponding variables as bindings.
this.populate(aTarget, aSource);
} else {
// Make sure that properties are always available on expansion.
aTarget.onexpand = () => this.populate(aTarget, aSource);
// Make sure that properties are always available on expansion.
aTarget.onexpand = () => this.populate(aTarget, aSource);
// Some variables are likely to contain a very large number of properties.
// It's a good idea to be prepared in case of an expansion.
if (aTarget.shouldPrefetch) {
aTarget.addEventListener("mouseover", aTarget.onexpand, false);
}
// Some variables are likely to contain a very large number of properties.
// It's a good idea to be prepared in case of an expansion.
if (aTarget.shouldPrefetch) {
aTarget.addEventListener("mouseover", aTarget.onexpand, false);
}
// Register all the actors that this controller now depends on.
@ -404,9 +382,11 @@ VariablesViewController.prototype = {
// If the target is a Variable or Property then we're fetching properties.
if (VariablesView.isVariable(aTarget)) {
this._populateFromObject(aTarget, aSource).then(() => {
deferred.resolve();
// Signal that properties have been fetched.
this.view.emit("fetched", "properties", aTarget);
// Commit the hierarchy because new items were added.
this.view.commitHierarchy();
deferred.resolve();
});
return deferred.promise;
}
@ -414,43 +394,29 @@ VariablesViewController.prototype = {
switch (aSource.type) {
case "longString":
this._populateFromLongString(aTarget, aSource).then(() => {
deferred.resolve();
// Signal that a long string has been fetched.
this.view.emit("fetched", "longString", aTarget);
deferred.resolve();
});
break;
case "with":
case "object":
this._populateFromObject(aTarget, aSource.object).then(() => {
deferred.resolve();
// Signal that variables have been fetched.
this.view.emit("fetched", "variables", aTarget);
// Commit the hierarchy because new items were added.
this.view.commitHierarchy();
deferred.resolve();
});
break;
case "block":
case "function":
// Add nodes for every argument and every other variable in scope.
let args = aSource.bindings.arguments;
if (args) {
for (let arg of args) {
let name = Object.getOwnPropertyNames(arg)[0];
let ref = aTarget.addItem(name, arg[name]);
let val = arg[name].value;
this.addExpander(ref, val);
}
}
aTarget.addItems(aSource.bindings.variables, {
// Not all variables need to force sorted properties.
sorted: VARIABLES_SORTING_ENABLED,
// Expansion handlers must be set after the properties are added.
callback: this.addExpander
});
this._populateWithEnvironmentBindings(aTarget, aSource.bindings);
// No need to signal that variables have been fetched, since
// the scope arguments and variables are already attached to the
// environment bindings, so pausing the active thread is unnecessary.
// Commit the hierarchy because new items were added.
this.view.commitHierarchy();
deferred.resolve();
break;
default: