mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
Bug 828680 - Variables view needs a controller of its own, r=vporof, msucan
This commit is contained in:
parent
d886ea39e6
commit
97d0060a1b
@ -24,6 +24,7 @@ Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
|
||||
@ -73,6 +74,24 @@ let DebuggerController = {
|
||||
DebuggerView.initialize(() => {
|
||||
DebuggerView._isInitialized = true;
|
||||
|
||||
VariablesViewController.attach(DebuggerView.Variables, {
|
||||
getGripClient: aObject => {
|
||||
return this.activeThread.pauseGrip(aObject);
|
||||
}
|
||||
});
|
||||
|
||||
// Relay events from the VariablesView.
|
||||
DebuggerView.Variables.on("fetched", (aEvent, aType) => {
|
||||
switch (aType) {
|
||||
case "variables":
|
||||
window.dispatchEvent(document, "Debugger:FetchedVariables");
|
||||
break;
|
||||
case "properties":
|
||||
window.dispatchEvent(document, "Debugger:FetchedProperties");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Chrome debugging needs to initiate the connection by itself.
|
||||
if (window._isChromeDebugger) {
|
||||
this.connect().then(deferred.resolve);
|
||||
@ -403,6 +422,7 @@ ThreadState.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Keeps the stack frame list up-to-date, using the thread client's
|
||||
* stack frame cache.
|
||||
@ -413,9 +433,6 @@ function StackFrames() {
|
||||
this._onFrames = this._onFrames.bind(this);
|
||||
this._onFramesCleared = this._onFramesCleared.bind(this);
|
||||
this._afterFramesCleared = this._afterFramesCleared.bind(this);
|
||||
this._fetchScopeVariables = this._fetchScopeVariables.bind(this);
|
||||
this._fetchVarProperties = this._fetchVarProperties.bind(this);
|
||||
this._addVarExpander = this._addVarExpander.bind(this);
|
||||
this.evaluate = this.evaluate.bind(this);
|
||||
}
|
||||
|
||||
@ -588,7 +605,12 @@ StackFrames.prototype = {
|
||||
DebuggerView.StackFrames.empty();
|
||||
|
||||
for (let frame of this.activeThread.cachedFrames) {
|
||||
this._addFrame(frame);
|
||||
let depth = frame.depth;
|
||||
let { url, line } = frame.where;
|
||||
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
|
||||
let frameTitle = StackFrameUtils.getFrameTitle(frame);
|
||||
|
||||
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
|
||||
}
|
||||
if (this.currentFrame == null) {
|
||||
DebuggerView.StackFrames.selectedDepth = 0;
|
||||
@ -661,6 +683,7 @@ StackFrames.prototype = {
|
||||
// Clear existing scopes and create each one dynamically.
|
||||
DebuggerView.Variables.empty();
|
||||
|
||||
|
||||
// If watch expressions evaluation results are available, create a scope
|
||||
// to contain all the values.
|
||||
if (this.syncedWatchExpressions && watchExpressionsEvaluation) {
|
||||
@ -684,18 +707,20 @@ StackFrames.prototype = {
|
||||
// Create a scope to contain all the inspected variables.
|
||||
let label = StackFrameUtils.getScopeLabel(environment);
|
||||
let scope = DebuggerView.Variables.addScope(label);
|
||||
let innermost = environment == frame.environment;
|
||||
|
||||
// Handle additions to the innermost scope.
|
||||
if (environment == frame.environment) {
|
||||
// Handle special additions to the innermost scope.
|
||||
if (innermost) {
|
||||
this._insertScopeFrameReferences(scope, frame);
|
||||
this._addScopeExpander(scope, environment);
|
||||
// Always expand the innermost scope by default.
|
||||
scope.expand();
|
||||
}
|
||||
// Lazily add nodes for every other environment scope.
|
||||
else {
|
||||
this._addScopeExpander(scope, environment);
|
||||
this.autoScopeExpand && scope.expand();
|
||||
|
||||
DebuggerView.Variables.controller.addExpander(scope, environment);
|
||||
|
||||
// The innermost scope is always automatically expanded, because it
|
||||
// contains the variables in the current stack frame which are likely to
|
||||
// be inspected.
|
||||
if (innermost || this.autoScopeExpand) {
|
||||
scope.expand();
|
||||
}
|
||||
} while ((environment = environment.parent));
|
||||
|
||||
@ -704,49 +729,6 @@ StackFrames.prototype = {
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a scope, lazily handling
|
||||
* the addition of new variables.
|
||||
*
|
||||
* @param Scope aScope
|
||||
* The scope where the variables will be placed into.
|
||||
* @param object aEnv
|
||||
* The scope's environment.
|
||||
*/
|
||||
_addScopeExpander: function(aScope, aEnv) {
|
||||
aScope._sourceEnvironment = aEnv;
|
||||
|
||||
// It's a good idea to be prepared in case of an expansion.
|
||||
aScope.addEventListener("mouseover", this._fetchScopeVariables, false);
|
||||
// Make sure that variables are always available on expansion.
|
||||
aScope.onexpand = this._fetchScopeVariables;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a variable, lazily handling
|
||||
* the addition of new properties.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
* @param any aGrip
|
||||
* The grip of the variable.
|
||||
*/
|
||||
_addVarExpander: function(aVar, aGrip) {
|
||||
// No need for expansion for primitive values.
|
||||
if (VariablesView.isPrimitive({ value: aGrip })) {
|
||||
return;
|
||||
}
|
||||
aVar._sourceGrip = aGrip;
|
||||
|
||||
// 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 (aVar.name == "window" || aVar.name == "this") {
|
||||
aVar.addEventListener("mouseover", this._fetchVarProperties, false);
|
||||
}
|
||||
// Make sure that properties are always available on expansion.
|
||||
aVar.onexpand = this._fetchVarProperties;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the watch expressions evaluation results to a scope in the view.
|
||||
*
|
||||
@ -770,8 +752,8 @@ StackFrames.prototype = {
|
||||
for (let i = 0; i < totalExpressions; i++) {
|
||||
let name = DebuggerView.WatchExpressions.getExpression(i);
|
||||
let expVal = ownProperties[i].value;
|
||||
let expRef = aScope.addVar(name, ownProperties[i]);
|
||||
this._addVarExpander(expRef, expVal);
|
||||
let expRef = aScope.addItem(name, ownProperties[i]);
|
||||
DebuggerView.Variables.controller.addExpander(expRef, expVal);
|
||||
|
||||
// Revert some of the custom watch expressions scope presentation flags.
|
||||
expRef.switch = null;
|
||||
@ -786,51 +768,6 @@ StackFrames.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds variables to a scope in the view. Triggered when a scope is
|
||||
* expanded or is hovered. It does not expand the scope.
|
||||
*
|
||||
* @param Scope aScope
|
||||
* The scope where the variables will be placed into.
|
||||
*/
|
||||
_fetchScopeVariables: function(aScope) {
|
||||
// Fetch the variables only once.
|
||||
if (aScope._fetched) {
|
||||
return;
|
||||
}
|
||||
aScope._fetched = true;
|
||||
let env = aScope._sourceEnvironment;
|
||||
|
||||
switch (env.type) {
|
||||
case "with":
|
||||
case "object":
|
||||
// Add nodes for every variable in scope.
|
||||
this.activeThread.pauseGrip(env.object).getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, safeGetterValues } = aResponse;
|
||||
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
|
||||
this._insertScopeVariables(ownProperties, aScope);
|
||||
|
||||
// Signal that variables have been fetched.
|
||||
window.dispatchEvent(document, "Debugger:FetchedVariables");
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
});
|
||||
break;
|
||||
case "block":
|
||||
case "function":
|
||||
// Add nodes for every argument and every other variable in scope.
|
||||
this._insertScopeArguments(env.bindings.arguments, aScope);
|
||||
this._insertScopeVariables(env.bindings.variables, aScope);
|
||||
|
||||
// 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.
|
||||
break;
|
||||
default:
|
||||
Cu.reportError("Unknown Debugger.Environment type: " + env.type);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for special frame references in the innermost scope.
|
||||
*
|
||||
@ -842,154 +779,21 @@ StackFrames.prototype = {
|
||||
_insertScopeFrameReferences: function(aScope, aFrame) {
|
||||
// Add any thrown exception.
|
||||
if (this.currentException) {
|
||||
let excRef = aScope.addVar("<exception>", { value: this.currentException });
|
||||
this._addVarExpander(excRef, this.currentException);
|
||||
let excRef = aScope.addItem("<exception>", { value: this.currentException });
|
||||
DebuggerView.Variables.controller.addExpander(excRef, this.currentException);
|
||||
}
|
||||
// Add any returned value.
|
||||
if (this.currentReturnedValue) {
|
||||
let retRef = aScope.addVar("<return>", { value: this.currentReturnedValue });
|
||||
this._addVarExpander(retRef, this.currentReturnedValue);
|
||||
let retRef = aScope.addItem("<return>", { value: this.currentReturnedValue });
|
||||
DebuggerView.Variables.controller.addExpander(retRef, this.currentReturnedValue);
|
||||
}
|
||||
// Add "this".
|
||||
if (aFrame.this) {
|
||||
let thisRef = aScope.addVar("this", { value: aFrame.this });
|
||||
this._addVarExpander(thisRef, aFrame.this);
|
||||
let thisRef = aScope.addItem("this", { value: aFrame.this });
|
||||
DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for every argument in scope.
|
||||
*
|
||||
* @param object aArguments
|
||||
* The map of names to arguments, as specified in the protocol.
|
||||
* @param Scope aScope
|
||||
* The scope where the nodes will be placed into.
|
||||
*/
|
||||
_insertScopeArguments: function(aArguments, aScope) {
|
||||
if (!aArguments) {
|
||||
return;
|
||||
}
|
||||
for (let argument of aArguments) {
|
||||
let name = Object.getOwnPropertyNames(argument)[0];
|
||||
let argRef = aScope.addVar(name, argument[name]);
|
||||
let argVal = argument[name].value;
|
||||
this._addVarExpander(argRef, argVal);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Add nodes for every variable in scope.
|
||||
*
|
||||
* @param object aVariables
|
||||
* The map of names to variables, as specified in the protocol.
|
||||
* @param Scope aScope
|
||||
* The scope where the nodes will be placed into.
|
||||
*/
|
||||
_insertScopeVariables: function(aVariables, aScope) {
|
||||
if (!aVariables) {
|
||||
return;
|
||||
}
|
||||
let variableNames = Object.keys(aVariables);
|
||||
|
||||
// Sort all of the variables before adding them, if preferred.
|
||||
if (Prefs.variablesSortingEnabled) {
|
||||
variableNames.sort();
|
||||
}
|
||||
// Add the variables to the specified scope.
|
||||
for (let name of variableNames) {
|
||||
let varRef = aScope.addVar(name, aVariables[name]);
|
||||
let varVal = aVariables[name].value;
|
||||
this._addVarExpander(varRef, varVal);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a variable in the view. Triggered when a variable is
|
||||
* expanded or certain variables are hovered. It does not expand the variable.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
*/
|
||||
_fetchVarProperties: function(aVar) {
|
||||
// Fetch the properties only once.
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
let grip = aVar._sourceGrip;
|
||||
|
||||
this.activeThread.pauseGrip(grip).getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
|
||||
|
||||
this._mergeSafeGetterValues(ownProperties, safeGetterValues);
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aVar.addProperties(ownProperties, {
|
||||
// Not all variables need to force sorted properties.
|
||||
sorted: sortable,
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
callback: this._addVarExpander
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
aVar.addProperty("__proto__", { value: prototype });
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
this._addVarExpander(aVar.get("__proto__"), prototype);
|
||||
}
|
||||
|
||||
// Mark the variable as having retrieved all its properties.
|
||||
aVar._retrieved = true;
|
||||
|
||||
// Signal that properties have been fetched.
|
||||
window.dispatchEvent(document, "Debugger:FetchedProperties");
|
||||
DebuggerView.Variables.commitHierarchy();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Merge the safe getter values descriptors into the "own properties" object
|
||||
* that comes from a "prototypeAndProperties" response packet. This is needed
|
||||
* for Variables View.
|
||||
*
|
||||
* @private
|
||||
* @param object aOwnProperties
|
||||
* The |ownProperties| object that will get the new safe getter values.
|
||||
* @param object aSafeGetterValues
|
||||
* The |safeGetterValues| object.
|
||||
*/
|
||||
_mergeSafeGetterValues: function(aOwnProperties, aSafeGetterValues) {
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(aSafeGetterValues)) {
|
||||
if (name in aOwnProperties) {
|
||||
aOwnProperties[name].getterValue = aSafeGetterValues[name].getterValue;
|
||||
aOwnProperties[name].getterPrototypeLevel =
|
||||
aSafeGetterValues[name].getterPrototypeLevel;
|
||||
} else {
|
||||
aOwnProperties[name] = aSafeGetterValues[name];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the specified stack frame to the list.
|
||||
*
|
||||
* @param object aFrame
|
||||
* The new frame to add.
|
||||
*/
|
||||
_addFrame: function(aFrame) {
|
||||
let depth = aFrame.depth;
|
||||
let { url, line } = aFrame.where;
|
||||
let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
|
||||
let frameTitle = StackFrameUtils.getFrameTitle(aFrame);
|
||||
|
||||
DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads more stack frames from the debugger server cache.
|
||||
*/
|
||||
|
@ -24,9 +24,9 @@ function testNonEnumProperties() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope");
|
||||
let testVar = testScope.addVar("foo");
|
||||
let testVar = testScope.addItem("foo");
|
||||
|
||||
testVar.addProperties({
|
||||
testVar.addItems({
|
||||
foo: {
|
||||
value: "bar",
|
||||
enumerable: true
|
||||
|
@ -24,8 +24,8 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope");
|
||||
let testVar = testScope.addVar("something");
|
||||
let duplVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
let duplVar = testScope.addItem("something");
|
||||
|
||||
info("Scope id: " + testScope.target.id);
|
||||
info("Scope name: " + testScope.target.name);
|
||||
@ -61,8 +61,8 @@ function testSimpleCall() {
|
||||
"Any new variable should have a details container with no child nodes.");
|
||||
|
||||
|
||||
let properties = testVar.addProperties({ "child": { "value": { "type": "object",
|
||||
"class": "Object" } } });
|
||||
let properties = testVar.addItems({ "child": { "value": { "type": "object",
|
||||
"class": "Object" } } });
|
||||
|
||||
|
||||
ok(!testVar.expanded,
|
||||
|
@ -24,9 +24,9 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test");
|
||||
let testVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
||||
let properties = testVar.addProperties({
|
||||
let properties = testVar.addItems({
|
||||
"child": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
@ -43,7 +43,7 @@ function testSimpleCall() {
|
||||
"The added detail property should be accessible from the variable.");
|
||||
|
||||
|
||||
let properties2 = testVar.get("child").addProperties({
|
||||
let properties2 = testVar.get("child").addItems({
|
||||
"grandchild": {
|
||||
"value": {
|
||||
"type": "object",
|
||||
|
@ -24,7 +24,7 @@ function testSimpleCall() {
|
||||
Services.tm.currentThread.dispatch({ run: function() {
|
||||
|
||||
let testScope = gDebugger.DebuggerView.Variables.addScope("test");
|
||||
let testVar = testScope.addVar("something");
|
||||
let testVar = testScope.addItem("something");
|
||||
|
||||
testVar.setGrip(1.618);
|
||||
|
||||
@ -44,32 +44,32 @@ function testSimpleCall() {
|
||||
"The information for the variable wasn't set correctly.");
|
||||
|
||||
|
||||
testVar.addProperties({ "helloWorld": { "value": "hello world", "enumerable": true } });
|
||||
testVar.addItems({ "helloWorld": { "value": "hello world", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
|
||||
"A new detail node should have been added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "helloWorld": { "value": "hello jupiter", "enumerable": true } });
|
||||
testVar.addItems({ "helloWorld": { "value": "hello jupiter", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
|
||||
"Shouldn't be able to duplicate nodes added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "someProp0": { "value": "random string", "enumerable": true },
|
||||
"someProp1": { "value": "another string", "enumerable": true } });
|
||||
testVar.addItems({ "someProp0": { "value": "random string", "enumerable": true },
|
||||
"someProp1": { "value": "another string", "enumerable": true } });
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 3,
|
||||
"Two new detail nodes should have been added in the variable tree.");
|
||||
|
||||
|
||||
testVar.addProperties({ "someProp2": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
testVar.addItems({ "someProp2": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
|
||||
is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 6,
|
||||
"Three new detail nodes should have been added in the variable tree.");
|
||||
|
@ -26,14 +26,14 @@ function testSimpleCall() {
|
||||
let globalScope = gDebugger.DebuggerView.Variables.addScope("Test-Global");
|
||||
let localScope = gDebugger.DebuggerView.Variables.addScope("Test-Local");
|
||||
|
||||
let windowVar = globalScope.addVar("window");
|
||||
let documentVar = globalScope.addVar("document");
|
||||
let localVar0 = localScope.addVar("localVariable");
|
||||
let localVar1 = localScope.addVar("localVar1");
|
||||
let localVar2 = localScope.addVar("localVar2");
|
||||
let localVar3 = localScope.addVar("localVar3");
|
||||
let localVar4 = localScope.addVar("localVar4");
|
||||
let localVar5 = localScope.addVar("localVar5");
|
||||
let windowVar = globalScope.addItem("window");
|
||||
let documentVar = globalScope.addItem("document");
|
||||
let localVar0 = localScope.addItem("localVariable");
|
||||
let localVar1 = localScope.addItem("localVar1");
|
||||
let localVar2 = localScope.addItem("localVar2");
|
||||
let localVar3 = localScope.addItem("localVar3");
|
||||
let localVar4 = localScope.addItem("localVar4");
|
||||
let localVar5 = localScope.addItem("localVar5");
|
||||
|
||||
localVar0.setGrip(42);
|
||||
localVar1.setGrip(true);
|
||||
@ -43,36 +43,36 @@ function testSimpleCall() {
|
||||
localVar4.setGrip({ "type": "null" });
|
||||
localVar5.setGrip({ "type": "object", "class": "Object" });
|
||||
|
||||
localVar5.addProperties({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true , "enumerable": true},
|
||||
"someProp2": { "value": "nasu", "enumerable": true},
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true},
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp5": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
localVar5.addItems({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true , "enumerable": true},
|
||||
"someProp2": { "value": "nasu", "enumerable": true},
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true},
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someProp5": {
|
||||
"value": { "type": "object", "class": "Object" },
|
||||
"enumerable": true
|
||||
}
|
||||
});
|
||||
|
||||
localVar5.get("someProp5").addProperties({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true, "enumerable": true },
|
||||
"someProp2": { "value": "nasu", "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someAccessor": { "get": { "type": "object", "class": "Function" },
|
||||
"set": { "type": "undefined" },
|
||||
"enumerable": true } });
|
||||
localVar5.get("someProp5").addItems({ "someProp0": { "value": 42, "enumerable": true },
|
||||
"someProp1": { "value": true, "enumerable": true },
|
||||
"someProp2": { "value": "nasu", "enumerable": true },
|
||||
"someProp3": { "value": { "type": "undefined" }, "enumerable": true },
|
||||
"someProp4": { "value": { "type": "null" }, "enumerable": true },
|
||||
"someAccessor": { "get": { "type": "object", "class": "Function" },
|
||||
"set": { "type": "undefined" }, "enumerable": true }
|
||||
});
|
||||
|
||||
windowVar.setGrip({ "type": "object", "class": "Window" });
|
||||
windowVar.addProperties({ "helloWorld": { "value": "hello world" } });
|
||||
windowVar.addItems({ "helloWorld": { "value": "hello world" } });
|
||||
|
||||
documentVar.setGrip({ "type": "object", "class": "HTMLDocument" });
|
||||
documentVar.addProperties({ "onload": { "value": { "type": "null" } },
|
||||
"onunload": { "value": { "type": "null" } },
|
||||
"onfocus": { "value": { "type": "null" } },
|
||||
"onblur": { "value": { "type": "null" } },
|
||||
"onclick": { "value": { "type": "null" } },
|
||||
"onkeypress": { "value": { "type": "null" } } });
|
||||
documentVar.addItems({ "onload": { "value": { "type": "null" } },
|
||||
"onunload": { "value": { "type": "null" } },
|
||||
"onfocus": { "value": { "type": "null" } },
|
||||
"onblur": { "value": { "type": "null" } },
|
||||
"onclick": { "value": { "type": "null" } },
|
||||
"onkeypress": { "value": { "type": "null" } } });
|
||||
|
||||
|
||||
ok(windowVar, "The windowVar hasn't been created correctly.");
|
||||
|
@ -75,11 +75,11 @@ function testVariablesView()
|
||||
testIntegrity(arr, obj);
|
||||
|
||||
let fooScope = gVariablesView.addScope("foo");
|
||||
let anonymousVar = fooScope.addVar();
|
||||
let anonymousVar = fooScope.addItem();
|
||||
|
||||
let anonymousScope = gVariablesView.addScope();
|
||||
let barVar = anonymousScope.addVar("bar");
|
||||
let bazProperty = barVar.addProperty("baz");
|
||||
let barVar = anonymousScope.addItem("bar");
|
||||
let bazProperty = barVar.addItem("baz");
|
||||
|
||||
testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
|
||||
testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
|
||||
|
@ -110,8 +110,9 @@ function testVariablesFiltering()
|
||||
is(gSearchBox.value, "*",
|
||||
"Searchbox value is incorrect after 3 backspaces");
|
||||
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
|
||||
"There should be 3 variables displayed in the inner scope");
|
||||
// variable count includes `__proto__` for object scopes
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 4,
|
||||
"There should be 4 variables displayed in the inner scope");
|
||||
isnot(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
"There should be some variables displayed in the math scope");
|
||||
isnot(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
@ -140,8 +141,9 @@ function testVariablesFiltering()
|
||||
is(gSearchBox.value, "",
|
||||
"Searchbox value is incorrect after 1 backspace");
|
||||
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 3,
|
||||
"There should be 3 variables displayed in the inner scope");
|
||||
// variable count includes `__proto__` for object scopes
|
||||
is(innerScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 4,
|
||||
"There should be 4 variables displayed in the inner scope");
|
||||
isnot(mathScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
"There should be some variables displayed in the math scope");
|
||||
isnot(testScope.querySelectorAll(".variables-view-variable:not([non-match])").length, 0,
|
||||
|
@ -1446,7 +1446,7 @@ NetworkDetailsView.prototype = {
|
||||
headersScope.expanded = true;
|
||||
|
||||
for (let header of aResponse.headers) {
|
||||
let headerVar = headersScope.addVar(header.name, { null: true }, true);
|
||||
let headerVar = headersScope.addItem(header.name, { null: true }, true);
|
||||
gNetwork.getString(header.value).then((aString) => headerVar.setGrip(aString));
|
||||
}
|
||||
},
|
||||
@ -1489,7 +1489,7 @@ NetworkDetailsView.prototype = {
|
||||
cookiesScope.expanded = true;
|
||||
|
||||
for (let cookie of aResponse.cookies) {
|
||||
let cookieVar = cookiesScope.addVar(cookie.name, { null: true }, true);
|
||||
let cookieVar = cookiesScope.addItem(cookie.name, { null: true }, true);
|
||||
gNetwork.getString(cookie.value).then((aString) => cookieVar.setGrip(aString));
|
||||
|
||||
// By default the cookie name and value are shown. If this is the only
|
||||
@ -1591,7 +1591,7 @@ NetworkDetailsView.prototype = {
|
||||
paramsScope.expanded = true;
|
||||
|
||||
for (let param of paramsArray) {
|
||||
let headerVar = paramsScope.addVar(param.name, { null: true }, true);
|
||||
let headerVar = paramsScope.addItem(param.name, { null: true }, true);
|
||||
headerVar.setGrip(param.value);
|
||||
}
|
||||
},
|
||||
@ -1634,7 +1634,7 @@ NetworkDetailsView.prototype = {
|
||||
: L10N.getStr("jsonScopeName");
|
||||
|
||||
let jsonScope = this._json.addScope(jsonScopeName);
|
||||
jsonScope.addVar().populate(jsonObject, { expanded: true });
|
||||
jsonScope.addItem().populate(jsonObject, { expanded: true });
|
||||
jsonScope.expanded = true;
|
||||
}
|
||||
// Malformed JSON.
|
||||
|
@ -20,6 +20,8 @@ const SEARCH_ACTION_MAX_DELAY = 300; // ms
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
|
||||
"resource://gre/modules/devtools/NetworkHelper.jsm");
|
||||
@ -74,6 +76,8 @@ this.VariablesView = function VariablesView(aParentNode, aFlags = {}) {
|
||||
for (let name in aFlags) {
|
||||
this[name] = aFlags[name];
|
||||
}
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
|
||||
VariablesView.prototype = {
|
||||
@ -86,7 +90,7 @@ VariablesView.prototype = {
|
||||
*/
|
||||
set rawObject(aObject) {
|
||||
this.empty();
|
||||
this.addScope().addVar().populate(aObject);
|
||||
this.addScope().addItem().populate(aObject);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -180,6 +184,11 @@ VariablesView.prototype = {
|
||||
}, aTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
* The controller for this VariablesView, if it has one.
|
||||
*/
|
||||
controller: null,
|
||||
|
||||
/**
|
||||
* The amount of time (in milliseconds) it takes to empty this view lazily.
|
||||
*/
|
||||
@ -587,7 +596,8 @@ VariablesView.prototype = {
|
||||
*/
|
||||
getScopeForNode: function(aNode) {
|
||||
let item = this._itemsByElement.get(aNode);
|
||||
if (item && !(item instanceof Variable) && !(item instanceof Property)) {
|
||||
// Match only Scopes, not Variables or Properties.
|
||||
if (item && !(item instanceof Variable)) {
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
@ -790,9 +800,8 @@ VariablesView.prototype = {
|
||||
|
||||
case e.DOM_VK_RETURN:
|
||||
case e.DOM_VK_ENTER:
|
||||
// Start editing the value or name of the variable or property.
|
||||
if (item instanceof Variable ||
|
||||
item instanceof Property) {
|
||||
// Start editing the value or name of the Variable or Property.
|
||||
if (item instanceof Variable) {
|
||||
if (e.metaKey || e.altKey || e.shiftKey) {
|
||||
item._activateNameInput();
|
||||
} else {
|
||||
@ -803,9 +812,8 @@ VariablesView.prototype = {
|
||||
|
||||
case e.DOM_VK_DELETE:
|
||||
case e.DOM_VK_BACK_SPACE:
|
||||
// Delete the variable or property if allowed.
|
||||
if (item instanceof Variable ||
|
||||
item instanceof Property) {
|
||||
// Delete the Variable or Property if allowed.
|
||||
if (item instanceof Variable) {
|
||||
item._onDelete(e);
|
||||
}
|
||||
return;
|
||||
@ -902,6 +910,7 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
"Array",
|
||||
"Int8Array",
|
||||
"Uint8Array",
|
||||
"Uint8ClampedArray",
|
||||
"Int16Array",
|
||||
"Uint16Array",
|
||||
"Int32Array",
|
||||
@ -910,6 +919,16 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
"Float64Array"
|
||||
];
|
||||
|
||||
/**
|
||||
* Determine whether an object's properties should be sorted based on its class.
|
||||
*
|
||||
* @param string aClassName
|
||||
* The class of the object.
|
||||
*/
|
||||
VariablesView.isSortable = function(aClassName) {
|
||||
return VariablesView.NON_SORTABLE_CLASSES.indexOf(aClassName) == -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing simple value changes.
|
||||
*
|
||||
@ -917,11 +936,13 @@ VariablesView.NON_SORTABLE_CLASSES = [
|
||||
* The current variable or property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString) {
|
||||
return aItem._symbolicName + "=" + aCurrentString;
|
||||
VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
return aPrefix + aItem._symbolicName + "=" + aCurrentString;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -932,12 +953,14 @@ VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString) {
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString) {
|
||||
VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
let property = "\"" + aItem._nameString + "\"";
|
||||
let parent = aItem.ownerView._symbolicName || "this";
|
||||
let parent = aPrefix + aItem.ownerView._symbolicName || "this";
|
||||
|
||||
return "Object.defineProperty(" + parent + "," + property + "," +
|
||||
"{ value: " + aCurrentString +
|
||||
@ -954,15 +977,17 @@ VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString) {
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @param string aPrefix [optional]
|
||||
* Prefix for the symbolic name.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
|
||||
let type = aItem._nameString;
|
||||
let propertyObject = aItem.ownerView;
|
||||
let parentObject = propertyObject.ownerView;
|
||||
let property = "\"" + propertyObject._nameString + "\"";
|
||||
let parent = parentObject._symbolicName || "this";
|
||||
let parent = aPrefix + parentObject._symbolicName || "this";
|
||||
|
||||
switch (aCurrentString) {
|
||||
case "":
|
||||
@ -976,7 +1001,7 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
if ((type == "set" && propertyObject.getter.type == "undefined") ||
|
||||
(type == "get" && propertyObject.setter.type == "undefined")) {
|
||||
// Make sure the right getter/setter to value override macro is applied to the target object.
|
||||
return propertyObject.evaluationMacro(propertyObject, "undefined");
|
||||
return propertyObject.evaluationMacro(propertyObject, "undefined", aPrefix);
|
||||
}
|
||||
|
||||
// Construct and return the getter/setter removal evaluation string.
|
||||
@ -995,16 +1020,16 @@ VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
|
||||
|
||||
default:
|
||||
// Wrap statements inside a function declaration if not already wrapped.
|
||||
if (aCurrentString.indexOf("function") != 0) {
|
||||
if (!aCurrentString.startsWith("function")) {
|
||||
let header = "function(" + (type == "set" ? "value" : "") + ")";
|
||||
let body = "";
|
||||
// If there's a return statement explicitly written, always use the
|
||||
// standard function definition syntax
|
||||
if (aCurrentString.indexOf("return ") != -1) {
|
||||
if (aCurrentString.contains("return ")) {
|
||||
body = "{" + aCurrentString + "}";
|
||||
}
|
||||
// If block syntax is used, use the whole string as the function body.
|
||||
else if (aCurrentString.indexOf("{") == 0) {
|
||||
else if (aCurrentString.startsWith("{")) {
|
||||
body = aCurrentString;
|
||||
}
|
||||
// Prefer an expression closure.
|
||||
@ -1042,6 +1067,7 @@ VariablesView.getterOrSetterDeleteCallback = function(aItem) {
|
||||
return true; // Don't hide the element.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A Scope is an object holding Variable instances.
|
||||
* Iterable via "for (let [name, variable] in instance) { }".
|
||||
@ -1083,12 +1109,31 @@ function Scope(aView, aName, aFlags = {}) {
|
||||
|
||||
Scope.prototype = {
|
||||
/**
|
||||
* Adds a variable to contain any inspected properties.
|
||||
* Whether this Scope should be prefetched when it is remoted.
|
||||
*/
|
||||
shouldPrefetch: true,
|
||||
|
||||
/**
|
||||
* Create a new Variable that is a child of this Scope.
|
||||
*
|
||||
* @param string aName
|
||||
* The variable's name.
|
||||
* The name of the new Property.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the variable,
|
||||
* The variable's descriptor.
|
||||
* @return Variable
|
||||
* The newly created child Variable.
|
||||
*/
|
||||
_createChild: function(aName, aDescriptor) {
|
||||
return new Variable(this, aName, aDescriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a child to contain any inspected properties.
|
||||
*
|
||||
* @param string aName
|
||||
* The child's name.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the child,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { value: 42 }
|
||||
@ -1104,17 +1149,56 @@ Scope.prototype = {
|
||||
* @return Variable
|
||||
* The newly created Variable instance, null if it already exists.
|
||||
*/
|
||||
addVar: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
addItem: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
if (this._store.has(aName) && !aRelaxed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let variable = new Variable(this, aName, aDescriptor);
|
||||
this._store.set(aName, variable);
|
||||
this._variablesView._itemsByElement.set(variable._target, variable);
|
||||
this._variablesView._currHierarchy.set(variable._absoluteName, variable);
|
||||
variable.header = !!aName;
|
||||
return variable;
|
||||
let child = this._createChild(aName, aDescriptor);
|
||||
this._store.set(aName, child);
|
||||
this._variablesView._itemsByElement.set(child._target, child);
|
||||
this._variablesView._currHierarchy.set(child._absoluteName, child);
|
||||
child.header = !!aName;
|
||||
return child;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds items for this variable.
|
||||
*
|
||||
* @param object aItems
|
||||
* An object containing some { name: descriptor } data properties,
|
||||
* specifying the value and/or type & class of the variable,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { someProp0: { value: 42 },
|
||||
* someProp1: { value: true },
|
||||
* someProp2: { value: "nasu" },
|
||||
* someProp3: { value: { type: "undefined" } },
|
||||
* someProp4: { value: { type: "null" } },
|
||||
* someProp5: { value: { type: "object", class: "Object" } },
|
||||
* someProp6: { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } } }
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for adding the properties. Supported options:
|
||||
* - sorted: true to sort all the properties before adding them
|
||||
* - callback: function invoked after each item is added
|
||||
*/
|
||||
addItems: function(aItems, aOptions = {}) {
|
||||
let names = Object.keys(aItems);
|
||||
|
||||
// Sort all of the properties before adding them, if preferred.
|
||||
if (aOptions.sorted) {
|
||||
names.sort();
|
||||
}
|
||||
// Add the properties to the current scope.
|
||||
for (let name of names) {
|
||||
let descriptor = aItems[name];
|
||||
let item = this.addItem(name, descriptor);
|
||||
|
||||
if (aOptions.callback) {
|
||||
aOptions.callback(item, descriptor.value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1179,11 +1263,13 @@ Scope.prototype = {
|
||||
if (this.isChildOf(aParent)) {
|
||||
return true;
|
||||
}
|
||||
if (this.ownerView instanceof Scope ||
|
||||
this.ownerView instanceof Variable ||
|
||||
this.ownerView instanceof Property) {
|
||||
|
||||
// Recurse to parent if it is a Scope, Variable, or Property.
|
||||
if (this.ownerView instanceof Scope) {
|
||||
return this.ownerView.isDescendantOf(aParent);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1405,10 +1491,9 @@ Scope.prototype = {
|
||||
}
|
||||
// Check if all parent objects are expanded.
|
||||
let item = this;
|
||||
while ((item = item.ownerView) && /* Parent object exists. */
|
||||
(item instanceof Scope ||
|
||||
item instanceof Variable ||
|
||||
item instanceof Property)) {
|
||||
|
||||
// Recurse while parent is a Scope, Variable, or Property
|
||||
while ((item = item.ownerView) && item instanceof Scope) {
|
||||
if (!item._isExpanded) {
|
||||
return false;
|
||||
}
|
||||
@ -1722,14 +1807,11 @@ Scope.prototype = {
|
||||
variable._wasToggled = true;
|
||||
}
|
||||
|
||||
// If the variable is contained in another scope (variable or property),
|
||||
// If the variable is contained in another Scope, Variable, or Property,
|
||||
// the parent may not be a match, thus hidden. It should be visible
|
||||
// ("expand upwards").
|
||||
|
||||
while ((variable = variable.ownerView) && /* Parent object exists. */
|
||||
(variable instanceof Scope ||
|
||||
variable instanceof Variable ||
|
||||
variable instanceof Property)) {
|
||||
variable instanceof Scope) {
|
||||
|
||||
// Show and expand the parent, as it is certainly accessible.
|
||||
variable._matched = true;
|
||||
@ -1971,79 +2053,24 @@ function Variable(aScope, aName, aDescriptor) {
|
||||
|
||||
ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
/**
|
||||
* Adds a property for this variable.
|
||||
*
|
||||
* @param string aName
|
||||
* The property's name.
|
||||
* @param object aDescriptor
|
||||
* Specifies the value and/or type & class of the property,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { value: 42 }
|
||||
* - { value: true }
|
||||
* - { value: "nasu" }
|
||||
* - { value: { type: "undefined" } }
|
||||
* - { value: { type: "null" } }
|
||||
* - { value: { type: "object", class: "Object" } }
|
||||
* - { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } }
|
||||
* - { get: { type "object", class: "Function" },
|
||||
* getterValue: "foo", getterPrototypeLevel: 2 }
|
||||
* @param boolean aRelaxed
|
||||
* True if name duplicates should be allowed.
|
||||
* @return Property
|
||||
* The newly created Property instance, null if it already exists.
|
||||
* Whether this Scope should be prefetched when it is remoted.
|
||||
*/
|
||||
addProperty: function(aName = "", aDescriptor = {}, aRelaxed = false) {
|
||||
if (this._store.has(aName) && !aRelaxed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let property = new Property(this, aName, aDescriptor);
|
||||
this._store.set(aName, property);
|
||||
this._variablesView._itemsByElement.set(property._target, property);
|
||||
this._variablesView._currHierarchy.set(property._absoluteName, property);
|
||||
property.header = !!aName;
|
||||
return property;
|
||||
get shouldPrefetch(){
|
||||
return this.name == "window" || this.name == "this";
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties for this variable.
|
||||
* Create a new Property that is a child of Variable.
|
||||
*
|
||||
* @param object aProperties
|
||||
* An object containing some { name: descriptor } data properties,
|
||||
* specifying the value and/or type & class of the variable,
|
||||
* or 'get' & 'set' accessor properties. If the type is implicit,
|
||||
* it will be inferred from the value.
|
||||
* e.g. - { someProp0: { value: 42 },
|
||||
* someProp1: { value: true },
|
||||
* someProp2: { value: "nasu" },
|
||||
* someProp3: { value: { type: "undefined" } },
|
||||
* someProp4: { value: { type: "null" } },
|
||||
* someProp5: { value: { type: "object", class: "Object" } },
|
||||
* someProp6: { get: { type: "object", class: "Function" },
|
||||
* set: { type: "undefined" } } }
|
||||
* @param object aOptions [optional]
|
||||
* Additional options for adding the properties. Supported options:
|
||||
* - sorted: true to sort all the properties before adding them
|
||||
* - callback: function invoked after each property is added
|
||||
* @param string aName
|
||||
* The name of the new Property.
|
||||
* @param object aDescriptor
|
||||
* The property's descriptor.
|
||||
* @return Property
|
||||
* The newly created child Property.
|
||||
*/
|
||||
addProperties: function(aProperties, aOptions = {}) {
|
||||
let propertyNames = Object.keys(aProperties);
|
||||
|
||||
// Sort all of the properties before adding them, if preferred.
|
||||
if (aOptions.sorted) {
|
||||
propertyNames.sort();
|
||||
}
|
||||
// Add the properties to the current scope.
|
||||
for (let name of propertyNames) {
|
||||
let descriptor = aProperties[name];
|
||||
let property = this.addProperty(name, descriptor);
|
||||
|
||||
if (aOptions.callback) {
|
||||
aOptions.callback(property, descriptor.value);
|
||||
}
|
||||
}
|
||||
_createChild: function(aName, aDescriptor) {
|
||||
return new Property(this, aName, aDescriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2122,7 +2149,7 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
let descriptor = Object.create(aDescriptor);
|
||||
descriptor.value = VariablesView.getGrip(aValue);
|
||||
|
||||
let propertyItem = this.addProperty(aName, descriptor);
|
||||
let propertyItem = this.addItem(aName, descriptor);
|
||||
propertyItem._sourceValue = aValue;
|
||||
|
||||
// Add an 'onexpand' callback for the property, lazily handling
|
||||
@ -2149,7 +2176,7 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
descriptor.get = VariablesView.getGrip(aDescriptor.get);
|
||||
descriptor.set = VariablesView.getGrip(aDescriptor.set);
|
||||
|
||||
return this.addProperty(aName, descriptor);
|
||||
return this.addItem(aName, descriptor);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -2311,8 +2338,8 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, {
|
||||
this.evaluationMacro = null;
|
||||
}
|
||||
|
||||
let getter = this.addProperty("get", { value: descriptor.get });
|
||||
let setter = this.addProperty("set", { value: descriptor.set });
|
||||
let getter = this.addItem("get", { value: descriptor.get });
|
||||
let setter = this.addItem("set", { value: descriptor.set });
|
||||
getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
|
||||
setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
|
||||
|
||||
@ -2852,9 +2879,8 @@ VariablesView.prototype.commitHierarchy = function() {
|
||||
if (prevVariable) {
|
||||
expanded = prevVariable._isExpanded;
|
||||
|
||||
// Only analyze variables and properties for displayed value changes.
|
||||
if (currVariable instanceof Variable ||
|
||||
currVariable instanceof Property) {
|
||||
// Only analyze Variables and Properties for displayed value changes.
|
||||
if (currVariable instanceof Variable) {
|
||||
changed = prevVariable._valueString != currVariable._valueString;
|
||||
}
|
||||
}
|
||||
@ -2974,6 +3000,16 @@ VariablesView.isFalsy = function(aDescriptor) {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the value is an instance of Variable or Property.
|
||||
*
|
||||
* @param any aValue
|
||||
* The value to test.
|
||||
*/
|
||||
VariablesView.isVariable = function(aValue) {
|
||||
return aValue instanceof Variable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a standard grip for a value.
|
||||
*
|
||||
|
350
browser/devtools/shared/widgets/VariablesViewController.jsm
Normal file
350
browser/devtools/shared/widgets/VariablesViewController.jsm
Normal file
@ -0,0 +1,350 @@
|
||||
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource:///modules/devtools/VariablesView.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
|
||||
Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
|
||||
);
|
||||
|
||||
const MAX_LONG_STRING_LENGTH = 200000;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["VariablesViewController"];
|
||||
|
||||
|
||||
/**
|
||||
* Controller for a VariablesView that handles interfacing with the debugger
|
||||
* protocol. Is able to populate scopes and variables via the protocol as well
|
||||
* as manage actor lifespans.
|
||||
*
|
||||
* @param VariablesView aView
|
||||
* The view to attach to.
|
||||
* @param object aOptions
|
||||
* Options for configuring the controller. Supported options:
|
||||
* - getGripClient: callback for creating an object grip client
|
||||
* - getLongStringClient: callback for creating a long string grip client
|
||||
* - releaseActor: callback for releasing an actor when it's no longer needed
|
||||
* - overrideValueEvalMacro: callback for creating an overriding eval macro
|
||||
* - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
|
||||
* - simpleValueEvalMacro: callback for creating a simple value eval macro
|
||||
*/
|
||||
function VariablesViewController(aView, aOptions) {
|
||||
this.addExpander = this.addExpander.bind(this);
|
||||
|
||||
this._getGripClient = aOptions.getGripClient;
|
||||
this._getLongStringClient = aOptions.getLongStringClient;
|
||||
this._releaseActor = aOptions.releaseActor;
|
||||
|
||||
if (aOptions.overrideValueEvalMacro) {
|
||||
this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
|
||||
}
|
||||
if (aOptions.getterOrSetterEvalMacro) {
|
||||
this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
|
||||
}
|
||||
if (aOptions.simpleValueEvalMacro) {
|
||||
this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
|
||||
}
|
||||
|
||||
this._actors = new Set();
|
||||
this.view = aView;
|
||||
this.view.controller = this;
|
||||
}
|
||||
|
||||
VariablesViewController.prototype = {
|
||||
/**
|
||||
* The default getter/setter evaluation macro.
|
||||
*/
|
||||
_getterOrSetterEvalMacro: VariablesView.getterOrSetterEvalMacro,
|
||||
|
||||
/**
|
||||
* The default override value evaluation macro.
|
||||
*/
|
||||
_overrideValueEvalMacro: VariablesView.overrideValueEvalMacro,
|
||||
|
||||
/**
|
||||
* The default simple value evaluation macro.
|
||||
*/
|
||||
_simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
|
||||
|
||||
/**
|
||||
* Populate a long string into a target using a grip.
|
||||
*
|
||||
* @param Variable aTarget
|
||||
* The target Variable/Property to put the retrieved string into.
|
||||
* @param LongStringActor aGrip
|
||||
* The long string grip that use to retrieve the full string.
|
||||
* @return Promise
|
||||
* The promise that will be resolved when the string is retrieved.
|
||||
*/
|
||||
_populateFromLongString: function(aTarget, aGrip){
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let from = aGrip.initial.length;
|
||||
let to = Math.min(aGrip.length, MAX_LONG_STRING_LENGTH);
|
||||
|
||||
this._getLongStringClient(aGrip).substring(from, to, aResponse => {
|
||||
// Stop tracking the actor because it's no longer needed.
|
||||
this.releaseActor(aGrip);
|
||||
|
||||
// Replace the preview with the full string and make it non-expandable.
|
||||
aTarget.onexpand = null;
|
||||
aTarget.setGrip(aGrip.initial + aResponse.substring);
|
||||
aTarget.hideArrow();
|
||||
|
||||
// Mark the string as having retrieved.
|
||||
aTarget._retrieved = true;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a Scope, Variable, or Property in the view. Triggered
|
||||
* when a scope is expanded or certain variables are hovered.
|
||||
*
|
||||
* @param Scope aTarget
|
||||
* The Scope where the properties will be placed into.
|
||||
* @param object aGrip
|
||||
* The grip to use to populate the target.
|
||||
*/
|
||||
_populateFromObject: function(aTarget, aGrip) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
this._getGripClient(aGrip).getPrototypeAndProperties(aResponse => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.isSortable(aGrip.class);
|
||||
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(safeGetterValues)) {
|
||||
if (name in ownProperties) {
|
||||
ownProperties[name].getterValue = safeGetterValues[name].getterValue;
|
||||
ownProperties[name].getterPrototypeLevel = safeGetterValues[name]
|
||||
.getterPrototypeLevel;
|
||||
} else {
|
||||
ownProperties[name] = safeGetterValues[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aTarget.addItems(ownProperties, {
|
||||
// Not all variables need to force sorted properties.
|
||||
sorted: sortable,
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
callback: this.addExpander
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
let proto = aTarget.addItem("__proto__", { value: prototype });
|
||||
// Expansion handlers must be set after the properties are added.
|
||||
this.addExpander(proto, prototype);
|
||||
}
|
||||
|
||||
// Mark the variable as having retrieved all its properties.
|
||||
aTarget._retrieved = true;
|
||||
this.view.commitHierarchy();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an 'onexpand' callback for a variable, lazily handling
|
||||
* the addition of new properties.
|
||||
*
|
||||
* @param Variable aVar
|
||||
* The variable where the properties will be placed into.
|
||||
* @param any aSource
|
||||
* The source to use to populate the target.
|
||||
*/
|
||||
addExpander: function(aTarget, aSource) {
|
||||
// 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;
|
||||
}
|
||||
} else {
|
||||
aTarget.evaluationMacro = this._simpleValueEvalMacro;
|
||||
}
|
||||
|
||||
// If the source is primitive then an expander is not needed.
|
||||
if (VariablesView.isPrimitive({ value: aSource })) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the source is a long string then show the arrow.
|
||||
if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
|
||||
aTarget.showArrow();
|
||||
}
|
||||
|
||||
// Make sure that properties are always available on expansion.
|
||||
aTarget.onexpand = () => this.expand(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);
|
||||
}
|
||||
|
||||
// Register all the actors that this controller now depends on.
|
||||
for (let grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
|
||||
if (WebConsoleUtils.isActorGrip(grip)) {
|
||||
this._actors.add(grip.actor);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a Scope, Variable, or Property in the view. Triggered
|
||||
* when a scope is expanded or certain variables are hovered.
|
||||
*
|
||||
* @param Scope aTarget
|
||||
* The Scope to be expanded.
|
||||
* @param object aSource
|
||||
* The source to use to populate the target.
|
||||
* @return Promise
|
||||
* The promise that is resolved once the target has been expanded.
|
||||
*/
|
||||
expand: function(aTarget, aSource) {
|
||||
// Fetch the variables only once.
|
||||
if (aTarget._fetched) {
|
||||
return aTarget._fetched;
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
aTarget._fetched = deferred.promise;
|
||||
|
||||
if (!aSource) {
|
||||
throw new Error("No actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
// If the target 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);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
||||
deferred.resolve();
|
||||
break;
|
||||
default:
|
||||
let error = "Unknown Debugger.Environment type: " + aSource.type;
|
||||
Cu.reportError(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an actor from the controller.
|
||||
*
|
||||
* @param object aActor
|
||||
* The actor to release.
|
||||
*/
|
||||
releaseActor: function(aActor){
|
||||
if (this._releaseActor) {
|
||||
this._releaseActor(aActor);
|
||||
}
|
||||
this._actors.delete(aActor);
|
||||
},
|
||||
|
||||
/**
|
||||
* Release all the actors referenced by the controller, optionally filtered.
|
||||
*
|
||||
* @param function aFilter [optional]
|
||||
* Callback to filter which actors are released.
|
||||
*/
|
||||
releaseActors: function(aFilter) {
|
||||
for (let actor of this._actors) {
|
||||
if (!aFilter || aFilter(actor)) {
|
||||
this.releaseActor(actor);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attaches a VariablesViewController to a VariablesView if it doesn't already
|
||||
* have one.
|
||||
*
|
||||
* @param VariablesView aView
|
||||
* The view to attach to.
|
||||
* @param object aOptions
|
||||
* The options to use in creating the controller.
|
||||
* @return VariablesViewController
|
||||
*/
|
||||
VariablesViewController.attach = function(aView, aOptions) {
|
||||
if (aView.controller) {
|
||||
return aView.controller;
|
||||
}
|
||||
return new VariablesViewController(aView, aOptions);
|
||||
};
|
@ -37,6 +37,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
"resource:///modules/devtools/VariablesView.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
|
||||
"resource:///modules/devtools/VariablesViewController.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
|
||||
"resource:///modules/devtools/shared/event-emitter.js");
|
||||
|
||||
@ -2104,13 +2107,9 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
else if (aNode.classList.contains("webconsole-msg-inspector")) {
|
||||
let view = aNode._variablesView;
|
||||
let actors = view ?
|
||||
this.jsterm._objectActorsInVariablesViews.get(view) :
|
||||
new Set();
|
||||
for (let actor of actors) {
|
||||
this._releaseObject(actor);
|
||||
if (view) {
|
||||
view.controller.releaseActors();
|
||||
}
|
||||
actors.clear();
|
||||
aNode._variablesView = null;
|
||||
}
|
||||
|
||||
@ -2743,6 +2742,35 @@ WebConsoleFrame.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.simpleValueEvalMacro
|
||||
*/
|
||||
function simpleValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.simpleValueEvalMacro(aItem, aCurrentString, "_self");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.overrideValueEvalMacro
|
||||
*/
|
||||
function overrideValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.overrideValueEvalMacro(aItem, aCurrentString, "_self");
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @see VariablesView.getterOrSetterEvalMacro
|
||||
*/
|
||||
function getterOrSetterEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return VariablesView.getterOrSetterEvalMacro(aItem, aCurrentString, "_self");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a JSTerminal (a JavaScript command line). This is attached to an
|
||||
* existing HeadsUpDisplay (a Web Console instance). This code is responsible
|
||||
@ -2771,8 +2799,6 @@ function JSTerm(aWebConsoleFrame)
|
||||
|
||||
this._keyPress = this.keyPress.bind(this);
|
||||
this._inputEventHandler = this.inputEventHandler.bind(this);
|
||||
this._fetchVarProperties = this._fetchVarProperties.bind(this);
|
||||
this._fetchVarLongString = this._fetchVarLongString.bind(this);
|
||||
this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
@ -3282,7 +3308,27 @@ JSTerm.prototype = {
|
||||
view.searchEnabled = !aOptions.hideFilterInput;
|
||||
view.lazyEmpty = this._lazyVariablesView;
|
||||
view.lazyAppend = this._lazyVariablesView;
|
||||
this._objectActorsInVariablesViews.set(view, new Set());
|
||||
|
||||
VariablesViewController.attach(view, {
|
||||
getGripClient: aGrip => {
|
||||
return new GripClient(this.hud.proxy.client, aGrip);
|
||||
},
|
||||
getLongStringClient: aGrip => {
|
||||
return this.webConsoleClient.longString(aGrip);
|
||||
},
|
||||
releaseActor: aActor => {
|
||||
this.hud._releaseObject(aActor);
|
||||
},
|
||||
simpleValueEvalMacro: simpleValueEvalMacro,
|
||||
overrideValueEvalMacro: overrideValueEvalMacro,
|
||||
getterOrSetterEvalMacro: getterOrSetterEvalMacro,
|
||||
});
|
||||
|
||||
// Relay events from the VariablesView.
|
||||
view.on("fetched", (aEvent, aType, aVar) => {
|
||||
this.emit("variablesview-fetched", aVar);
|
||||
});
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
@ -3304,16 +3350,11 @@ JSTerm.prototype = {
|
||||
view.createHierarchy();
|
||||
view.empty();
|
||||
|
||||
let actors = this._objectActorsInVariablesViews.get(view);
|
||||
for (let actor of actors) {
|
||||
// We need to avoid pruning the object inspection starting point.
|
||||
// That one is pruned when the console message is removed.
|
||||
if (view._consoleLastObjectActor != actor) {
|
||||
this.hud._releaseObject(actor);
|
||||
}
|
||||
}
|
||||
|
||||
actors.clear();
|
||||
// We need to avoid pruning the object inspection starting point.
|
||||
// That one is pruned when the console message is removed.
|
||||
view.controller.releaseActors(aActor => {
|
||||
return view._consoleLastObjectActor != aActor;
|
||||
});
|
||||
|
||||
if (aOptions.objectActor) {
|
||||
// Make sure eval works in the correct context.
|
||||
@ -3331,11 +3372,11 @@ JSTerm.prototype = {
|
||||
scope.expanded = true;
|
||||
scope.locked = true;
|
||||
|
||||
let container = scope.addVar();
|
||||
container.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
|
||||
let container = scope.addItem();
|
||||
container.evaluationMacro = simpleValueEvalMacro;
|
||||
|
||||
if (aOptions.objectActor) {
|
||||
this._fetchVarProperties(container, aOptions.objectActor);
|
||||
view.controller.expand(container, aOptions.objectActor);
|
||||
view._consoleLastObjectActor = aOptions.objectActor.actor;
|
||||
}
|
||||
else if (aOptions.rawObject) {
|
||||
@ -3374,80 +3415,6 @@ JSTerm.prototype = {
|
||||
this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing simple value changes in the
|
||||
* variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Variable | Property aItem
|
||||
* The current variable or property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewSimpleValueEvalMacro:
|
||||
function JST__variablesViewSimpleValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
return "_self" + aItem.symbolicName + "=" + aCurrentString;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when overriding getters and setters with
|
||||
* plain values in the variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Property aItem
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewOverrideValueEvalMacro:
|
||||
function JST__variablesViewOverrideValueEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
let parent = aItem.ownerView;
|
||||
let symbolicName = parent.symbolicName;
|
||||
if (symbolicName.indexOf("_self") != 0) {
|
||||
parent._symbolicName = "_self" + symbolicName;
|
||||
}
|
||||
|
||||
let result = VariablesView.overrideValueEvalMacro.apply(this, arguments);
|
||||
|
||||
parent._symbolicName = symbolicName;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates the string evaluated when performing getters and setters changes
|
||||
* in the variables view.
|
||||
*
|
||||
* @private
|
||||
* @param Property aItem
|
||||
* The current getter or setter property.
|
||||
* @param string aCurrentString
|
||||
* The trimmed user inputted string.
|
||||
* @return string
|
||||
* The string to be evaluated.
|
||||
*/
|
||||
_variablesViewGetterOrSetterEvalMacro:
|
||||
function JST__variablesViewGetterOrSetterEvalMacro(aItem, aCurrentString)
|
||||
{
|
||||
let propertyObject = aItem.ownerView;
|
||||
let parentObject = propertyObject.ownerView;
|
||||
let parent = parentObject.symbolicName;
|
||||
parentObject._symbolicName = "_self" + parent;
|
||||
|
||||
let result = VariablesView.getterOrSetterEvalMacro.apply(this, arguments);
|
||||
|
||||
parentObject._symbolicName = parent;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* The property deletion function used by the variables view when a property
|
||||
* is deleted.
|
||||
@ -3556,144 +3523,7 @@ JSTerm.prototype = {
|
||||
aCallback && aCallback(aResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds properties to a variable in the view. Triggered when a variable is
|
||||
* expanded. It does not expand the variable.
|
||||
*
|
||||
* @param object aVar
|
||||
* The VariablseView Variable instance where the properties get added.
|
||||
* @param object [aGrip]
|
||||
* Optional, the object actor grip of the variable. If the grip is not
|
||||
* provided, then the aVar.value is used as the object actor grip.
|
||||
*/
|
||||
_fetchVarProperties: function JST__fetchVarProperties(aVar, aGrip)
|
||||
{
|
||||
// Retrieve the properties only once.
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
|
||||
let grip = aGrip || aVar.value;
|
||||
if (!grip) {
|
||||
throw new Error("No object actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
let view = aVar._variablesView;
|
||||
let actors = this._objectActorsInVariablesViews.get(view);
|
||||
|
||||
function addActorForDescriptor(aGrip) {
|
||||
if (WebConsoleUtils.isActorGrip(aGrip)) {
|
||||
actors.add(aGrip.actor);
|
||||
}
|
||||
}
|
||||
|
||||
let onNewProperty = (aProperty) => {
|
||||
if (aProperty.getter || aProperty.setter) {
|
||||
aProperty.evaluationMacro = this._variablesViewOverrideValueEvalMacro;
|
||||
let getter = aProperty.get("get");
|
||||
let setter = aProperty.get("set");
|
||||
if (getter) {
|
||||
getter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
|
||||
}
|
||||
if (setter) {
|
||||
setter.evaluationMacro = this._variablesViewGetterOrSetterEvalMacro;
|
||||
}
|
||||
}
|
||||
else {
|
||||
aProperty.evaluationMacro = this._variablesViewSimpleValueEvalMacro;
|
||||
}
|
||||
|
||||
let grips = [aProperty.value, aProperty.getter, aProperty.setter];
|
||||
grips.forEach(addActorForDescriptor);
|
||||
|
||||
let inspectable = !VariablesView.isPrimitive({ value: aProperty.value });
|
||||
let longString = WebConsoleUtils.isActorGrip(aProperty.value) &&
|
||||
aProperty.value.type == "longString";
|
||||
if (inspectable) {
|
||||
aProperty.onexpand = this._fetchVarProperties;
|
||||
}
|
||||
else if (longString) {
|
||||
aProperty.onexpand = this._fetchVarLongString;
|
||||
aProperty.showArrow();
|
||||
}
|
||||
};
|
||||
|
||||
let client = new GripClient(this.hud.proxy.client, grip);
|
||||
client.getPrototypeAndProperties((aResponse) => {
|
||||
let { ownProperties, prototype, safeGetterValues } = aResponse;
|
||||
let sortable = VariablesView.NON_SORTABLE_CLASSES.indexOf(grip.class) == -1;
|
||||
|
||||
// Merge the safe getter values into one object such that we can use it
|
||||
// in VariablesView.
|
||||
for (let name of Object.keys(safeGetterValues)) {
|
||||
if (name in ownProperties) {
|
||||
ownProperties[name].getterValue = safeGetterValues[name].getterValue;
|
||||
ownProperties[name].getterPrototypeLevel = safeGetterValues[name]
|
||||
.getterPrototypeLevel;
|
||||
}
|
||||
else {
|
||||
ownProperties[name] = safeGetterValues[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Add all the variable properties.
|
||||
if (ownProperties) {
|
||||
aVar.addProperties(ownProperties, {
|
||||
sorted: sortable,
|
||||
callback: onNewProperty,
|
||||
});
|
||||
}
|
||||
|
||||
// Add the variable's __proto__.
|
||||
if (prototype && prototype.type != "null") {
|
||||
let proto = aVar.addProperty("__proto__", { value: prototype });
|
||||
onNewProperty(proto);
|
||||
}
|
||||
|
||||
aVar._retrieved = true;
|
||||
view.commitHierarchy();
|
||||
this.emit("variablesview-fetched", aVar);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the full string for a given variable that displays a long string.
|
||||
*
|
||||
* @param object aVar
|
||||
* The VariablesView Variable instance where the properties get added.
|
||||
*/
|
||||
_fetchVarLongString: function JST__fetchVarLongString(aVar)
|
||||
{
|
||||
if (aVar._fetched) {
|
||||
return;
|
||||
}
|
||||
aVar._fetched = true;
|
||||
|
||||
let grip = aVar.value;
|
||||
if (!grip) {
|
||||
throw new Error("No long string actor grip was given for the variable.");
|
||||
}
|
||||
|
||||
let client = this.webConsoleClient.longString(grip);
|
||||
let toIndex = Math.min(grip.length, MAX_LONG_STRING_LENGTH);
|
||||
client.substring(grip.initial.length, toIndex, (aResponse) => {
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("JST__fetchVarLongString substring failure: " +
|
||||
aResponse.error + ": " + aResponse.message);
|
||||
return;
|
||||
}
|
||||
|
||||
aVar.onexpand = null;
|
||||
aVar.setGrip(grip.initial + aResponse.substring);
|
||||
aVar.hideArrow();
|
||||
aVar._retrieved = true;
|
||||
|
||||
if (toIndex != grip.length) {
|
||||
this.hud.logWarningAboutStringTooLong();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Writes a JS object to the JSTerm outputNode.
|
||||
@ -4358,11 +4188,7 @@ JSTerm.prototype = {
|
||||
_sidebarDestroy: function JST__sidebarDestroy()
|
||||
{
|
||||
if (this._variablesView) {
|
||||
let actors = this._objectActorsInVariablesViews.get(this._variablesView);
|
||||
for (let actor of actors) {
|
||||
this.hud._releaseObject(actor);
|
||||
}
|
||||
actors.clear();
|
||||
this._variablesView.controller.releaseActors();
|
||||
this._variablesView = null;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user