123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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 { utils: Cu } = Components;
- var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
- var {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
- var {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
- var Services = require("Services");
- var promise = require("promise");
- var defer = require("devtools/shared/defer");
- var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
- Object.defineProperty(this, "WebConsoleUtils", {
- get: function () {
- return require("devtools/client/webconsole/utils").Utils;
- },
- configurable: true,
- enumerable: true
- });
- XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
- Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
- );
- XPCOMUtils.defineLazyModuleGetter(this, "console",
- "resource://gre/modules/Console.jsm");
- const MAX_LONG_STRING_LENGTH = 200000;
- const MAX_PROPERTY_ITEMS = 2000;
- const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
- this.EXPORTED_SYMBOLS = ["VariablesViewController", "StackFrameUtils"];
- /**
- * Localization convenience methods.
- */
- var L10N = new LocalizationHelper(DBG_STRINGS_URI);
- /**
- * 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 [optional]
- * Options for configuring the controller. Supported options:
- * - getObjectClient: @see this._setClientGetters
- * - getLongStringClient: @see this._setClientGetters
- * - getEnvironmentClient: @see this._setClientGetters
- * - releaseActor: @see this._setClientGetters
- * - overrideValueEvalMacro: @see _setEvaluationMacros
- * - getterOrSetterEvalMacro: @see _setEvaluationMacros
- * - simpleValueEvalMacro: @see _setEvaluationMacros
- */
- function VariablesViewController(aView, aOptions = {}) {
- this.addExpander = this.addExpander.bind(this);
- this._setClientGetters(aOptions);
- this._setEvaluationMacros(aOptions);
- this._actors = new Set();
- this.view = aView;
- this.view.controller = this;
- }
- this.VariablesViewController = VariablesViewController;
- 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,
- /**
- * Set the functions used to retrieve debugger client grips.
- *
- * @param object aOptions
- * Options for getting the client grips. Supported options:
- * - getObjectClient: callback for creating an object grip client
- * - getLongStringClient: callback for creating a long string grip client
- * - getEnvironmentClient: callback for creating an environment client
- * - releaseActor: callback for releasing an actor when it's no longer needed
- */
- _setClientGetters: function (aOptions) {
- if (aOptions.getObjectClient) {
- this._getObjectClient = aOptions.getObjectClient;
- }
- if (aOptions.getLongStringClient) {
- this._getLongStringClient = aOptions.getLongStringClient;
- }
- if (aOptions.getEnvironmentClient) {
- this._getEnvironmentClient = aOptions.getEnvironmentClient;
- }
- if (aOptions.releaseActor) {
- this._releaseActor = aOptions.releaseActor;
- }
- },
- /**
- * Sets the functions used when evaluating strings in the variables view.
- *
- * @param object aOptions
- * Options for configuring the macros. Supported options:
- * - 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
- */
- _setEvaluationMacros: function (aOptions) {
- if (aOptions.overrideValueEvalMacro) {
- this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
- }
- if (aOptions.getterOrSetterEvalMacro) {
- this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
- }
- if (aOptions.simpleValueEvalMacro) {
- this._simpleValueEvalMacro = aOptions.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 = 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();
- deferred.resolve();
- });
- return deferred.promise;
- },
- /**
- * Adds pseudo items in case there is too many properties to display.
- * Each item can expand into property slices.
- *
- * @param Scope aTarget
- * The Scope where the properties will be placed into.
- * @param object aGrip
- * The property iterator grip.
- */
- _populatePropertySlices: function (aTarget, aGrip) {
- if (aGrip.count < MAX_PROPERTY_ITEMS) {
- return this._populateFromPropertyIterator(aTarget, aGrip);
- }
- // Divide the keys into quarters.
- let items = Math.ceil(aGrip.count / 4);
- let iterator = aGrip.propertyIterator;
- let promises = [];
- for (let i = 0; i < 4; i++) {
- let start = aGrip.start + i * items;
- let count = i != 3 ? items : aGrip.count - i * items;
- // Create a new kind of grip, with additional fields to define the slice
- let sliceGrip = {
- type: "property-iterator",
- propertyIterator: iterator,
- start: start,
- count: count
- };
- // Query the name of the first and last items for this slice
- let deferred = defer();
- iterator.names([start, start + count - 1], ({ names }) => {
- let label = "[" + names[0] + ELLIPSIS + names[1] + "]";
- let item = aTarget.addItem(label, {}, { internalItem: true });
- item.showArrow();
- this.addExpander(item, sliceGrip);
- deferred.resolve();
- });
- promises.push(deferred.promise);
- }
- return promise.all(promises);
- },
- /**
- * Adds a property slice for a Variable in the view using the already
- * property iterator
- *
- * @param Scope aTarget
- * The Scope where the properties will be placed into.
- * @param object aGrip
- * The property iterator grip.
- */
- _populateFromPropertyIterator: function (aTarget, aGrip) {
- if (aGrip.count >= MAX_PROPERTY_ITEMS) {
- // We already started to split, but there is still too many properties, split again.
- return this._populatePropertySlices(aTarget, aGrip);
- }
- // We started slicing properties, and the slice is now small enough to be displayed
- let deferred = defer();
- aGrip.propertyIterator.slice(aGrip.start, aGrip.count,
- ({ ownProperties }) => {
- // Add all the variable properties.
- if (Object.keys(ownProperties).length > 0) {
- aTarget.addItems(ownProperties, {
- sorted: true,
- // Expansion handlers must be set after the properties are added.
- callback: this.addExpander
- });
- }
- deferred.resolve();
- });
- return deferred.promise;
- },
- /**
- * Adds the properties for a Variable in the view using a new feature in FF40+
- * that allows iteration over properties in slices.
- *
- * @param Scope aTarget
- * The Scope where the properties will be placed into.
- * @param object aGrip
- * The grip to use to populate the target.
- * @param string aQuery [optional]
- * The query string used to fetch only a subset of properties
- */
- _populateFromObjectWithIterator: function (aTarget, aGrip, aQuery) {
- // FF40+ starts exposing `ownPropertyLength` on ObjectActor's grip,
- // as well as `enumProperties` request.
- let deferred = defer();
- let objectClient = this._getObjectClient(aGrip);
- let isArray = aGrip.preview && aGrip.preview.kind === "ArrayLike";
- if (isArray) {
- // First enumerate array items, e.g. properties from `0` to `array.length`.
- let options = {
- ignoreNonIndexedProperties: true,
- query: aQuery
- };
- objectClient.enumProperties(options, ({ iterator }) => {
- let sliceGrip = {
- type: "property-iterator",
- propertyIterator: iterator,
- start: 0,
- count: iterator.count
- };
- this._populatePropertySlices(aTarget, sliceGrip)
- .then(() => {
- // Then enumerate the rest of the properties, like length, buffer, etc.
- let options = {
- ignoreIndexedProperties: true,
- sort: true,
- query: aQuery
- };
- objectClient.enumProperties(options, ({ iterator }) => {
- let sliceGrip = {
- type: "property-iterator",
- propertyIterator: iterator,
- start: 0,
- count: iterator.count
- };
- deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
- });
- });
- });
- } else {
- // For objects, we just enumerate all the properties sorted by name.
- objectClient.enumProperties({ sort: true, query: aQuery }, ({ iterator }) => {
- let sliceGrip = {
- type: "property-iterator",
- propertyIterator: iterator,
- start: 0,
- count: iterator.count
- };
- deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
- });
- }
- return deferred.promise;
- },
- /**
- * Adds the given prototype in the view.
- *
- * @param Scope aTarget
- * The Scope where the properties will be placed into.
- * @param object aProtype
- * The prototype grip.
- */
- _populateObjectPrototype: function (aTarget, aPrototype) {
- // Add the variable's __proto__.
- if (aPrototype && aPrototype.type != "null") {
- let proto = aTarget.addItem("__proto__", { value: aPrototype });
- this.addExpander(proto, aPrototype);
- }
- },
- /**
- * 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) {
- if (aGrip.class === "Proxy") {
- this.addExpander(
- aTarget.addItem("<target>", { value: aGrip.proxyTarget }, { internalItem: true }),
- aGrip.proxyTarget);
- this.addExpander(
- aTarget.addItem("<handler>", { value: aGrip.proxyHandler }, { internalItem: true }),
- aGrip.proxyHandler);
- // Refuse to play the proxy's stupid game and return immediately
- let deferred = defer();
- deferred.resolve();
- return deferred.promise;
- }
- if (aGrip.class === "Promise" && aGrip.promiseState) {
- const { state, value, reason } = aGrip.promiseState;
- aTarget.addItem("<state>", { value: state }, { internalItem: true });
- if (state === "fulfilled") {
- this.addExpander(
- aTarget.addItem("<value>", { value }, { internalItem: true }),
- value);
- } else if (state === "rejected") {
- this.addExpander(
- aTarget.addItem("<reason>", { value: reason }, { internalItem: true }),
- reason);
- }
- } else if (["Map", "WeakMap", "Set", "WeakSet"].includes(aGrip.class)) {
- let entriesList = aTarget.addItem("<entries>", {}, { internalItem: true });
- entriesList.showArrow();
- this.addExpander(entriesList, {
- type: "entries-list",
- obj: aGrip
- });
- }
- // Fetch properties by slices if there is too many in order to prevent UI freeze.
- if ("ownPropertyLength" in aGrip && aGrip.ownPropertyLength >= MAX_PROPERTY_ITEMS) {
- return this._populateFromObjectWithIterator(aTarget, aGrip)
- .then(() => {
- let deferred = defer();
- let objectClient = this._getObjectClient(aGrip);
- objectClient.getPrototype(({ prototype }) => {
- this._populateObjectPrototype(aTarget, prototype);
- deferred.resolve();
- });
- return deferred.promise;
- });
- }
- return this._populateProperties(aTarget, aGrip);
- },
- _populateProperties: function (aTarget, aGrip, aOptions) {
- let deferred = defer();
- let objectClient = this._getObjectClient(aGrip);
- objectClient.getPrototypeAndProperties(aResponse => {
- let ownProperties = aResponse.ownProperties || {};
- let prototype = aResponse.prototype || null;
- // 'safeGetterValues' is new and isn't necessary defined on old actors.
- let safeGetterValues = aResponse.safeGetterValues || {};
- 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) {
- let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
- ownProperties[name].getterValue = getterValue;
- ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
- } else {
- ownProperties[name] = safeGetterValues[name];
- }
- }
- // Add all the variable properties.
- 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__.
- this._populateObjectPrototype(aTarget, prototype);
- // If the object is a function we need to fetch its scope chain
- // to show them as closures for the respective function.
- if (aGrip.class == "Function") {
- objectClient.getScope(aResponse => {
- if (aResponse.error) {
- // This function is bound to a built-in object or it's not present
- // 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 deferred.resolve();
- }
- this._populateWithClosure(aTarget, aResponse.scope).then(deferred.resolve);
- });
- } else {
- deferred.resolve();
- }
- });
- return deferred.promise;
- },
- /**
- * 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.
- */
- _populateWithClosure: function (aTarget, aScope) {
- let objectScopes = [];
- let environment = aScope;
- let funcScope = aTarget.addItem("<Closure>");
- funcScope.target.setAttribute("scope", "");
- funcScope.showArrow();
- do {
- // Create a scope to contain all the inspected variables.
- let label = StackFrameUtils.getScopeLabel(environment);
- // Block scopes may have the same label, so make addItem allow duplicates.
- let closure = funcScope.addItem(label, undefined, {relaxed: true});
- closure.target.setAttribute("scope", "");
- closure.showArrow();
- // Add nodes for every argument and every other variable in scope.
- if (environment.bindings) {
- this._populateWithEnvironmentBindings(closure, environment.bindings);
- } else {
- let deferred = defer();
- objectScopes.push(deferred.promise);
- this._getEnvironmentClient(environment).getBindings(response => {
- this._populateWithEnvironmentBindings(closure, response.bindings);
- deferred.resolve();
- });
- }
- } while ((environment = environment.parent));
- return promise.all(objectScopes).then(() => {
- // Signal that scopes have been fetched.
- this.view.emit("fetched", "scopes", funcScope);
- });
- },
- /**
- * Adds nodes for every specified binding to the closure node.
- *
- * @param Variable aTarget
- * The variable where the bindings will be placed into.
- * @param object aBindings
- * The bindings form as specified in the protocol.
- */
- _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
- });
- // 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
- });
- },
- _populateFromEntries: function (target, grip) {
- let objGrip = grip.obj;
- let objectClient = this._getObjectClient(objGrip);
- return new promise((resolve, reject) => {
- objectClient.enumEntries((response) => {
- if (response.error) {
- // Older server might not support the enumEntries method
- console.warn(response.error + ": " + response.message);
- resolve();
- } else {
- let sliceGrip = {
- type: "property-iterator",
- propertyIterator: response.iterator,
- start: 0,
- count: response.iterator.count
- };
- resolve(this._populatePropertySlices(target, sliceGrip));
- }
- });
- });
- },
- /**
- * Adds an 'onexpand' callback for a variable, lazily handling
- * the addition of new properties.
- *
- * @param Variable aTarget
- * 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.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);
- }
- // 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.
- *
- * This does not expand the target, it only populates it.
- *
- * @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.
- */
- populate: function (aTarget, aSource) {
- // Fetch the variables only once.
- if (aTarget._fetched) {
- return aTarget._fetched;
- }
- // Make sure the source grip is available.
- if (!aSource) {
- return promise.reject(new Error("No actor grip was given for the variable."));
- }
- let deferred = defer();
- aTarget._fetched = deferred.promise;
- if (aSource.type === "property-iterator") {
- return this._populateFromPropertyIterator(aTarget, aSource);
- }
- if (aSource.type === "entries-list") {
- return this._populateFromEntries(aTarget, aSource);
- }
- if (aSource.type === "mapEntry") {
- aTarget.addItems({
- key: { value: aSource.preview.key },
- value: { value: aSource.preview.value }
- }, {
- callback: this.addExpander
- });
- return promise.resolve();
- }
- // If the target is a Variable or Property then we're fetching properties.
- if (VariablesView.isVariable(aTarget)) {
- this._populateFromObject(aTarget, aSource).then(() => {
- // 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;
- }
- switch (aSource.type) {
- case "longString":
- this._populateFromLongString(aTarget, aSource).then(() => {
- // 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(() => {
- // 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":
- 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:
- let error = "Unknown Debugger.Environment type: " + aSource.type;
- console.error(error);
- deferred.reject(error);
- }
- return deferred.promise;
- },
- /**
- * Indicates to the view if the targeted actor supports properties search
- *
- * @return boolean True, if the actor supports enumProperty request
- */
- supportsSearch: function () {
- // FF40+ starts exposing ownPropertyLength on object actor's grip
- // as well as enumProperty which allows to query a subset of properties.
- return this.objectActor && ("ownPropertyLength" in this.objectActor);
- },
- /**
- * Try to use the actor to perform an attribute search.
- *
- * @param Scope aScope
- * The Scope instance to populate with properties
- * @param string aToken
- * The query string
- */
- performSearch: function (aScope, aToken) {
- this._populateFromObjectWithIterator(aScope, this.objectActor, aToken)
- .then(() => {
- this.view.emit("fetched", "search", aScope);
- });
- },
- /**
- * 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);
- }
- }
- },
- /**
- * Helper function for setting up a single Scope with a single Variable
- * contained within it.
- *
- * This function will empty the variables view.
- *
- * @param object options
- * Options for the contents of the view:
- * - objectActor: the grip of the new ObjectActor to show.
- * - rawObject: the raw object to show.
- * - label: the label for the inspected object.
- * @param object configuration
- * Additional options for the controller:
- * - overrideValueEvalMacro: @see _setEvaluationMacros
- * - getterOrSetterEvalMacro: @see _setEvaluationMacros
- * - simpleValueEvalMacro: @see _setEvaluationMacros
- * @return Object
- * - variable: the created Variable.
- * - expanded: the Promise that resolves when the variable expands.
- */
- setSingleVariable: function (options, configuration = {}) {
- this._setEvaluationMacros(configuration);
- this.view.empty();
- let scope = this.view.addScope(options.label);
- scope.expanded = true; // Expand the scope by default.
- scope.locked = true; // Prevent collapsing the scope.
- let variable = scope.addItem(undefined, { enumerable: true });
- let populated;
- if (options.objectActor) {
- // Save objectActor for properties filtering
- this.objectActor = options.objectActor;
- if (VariablesView.isPrimitive({ value: this.objectActor })) {
- populated = promise.resolve();
- } else {
- populated = this.populate(variable, options.objectActor);
- variable.expand();
- }
- } else if (options.rawObject) {
- variable.populate(options.rawObject, { expanded: true });
- populated = promise.resolve();
- }
- return { variable: variable, expanded: populated };
- },
- };
- /**
- * 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);
- };
- /**
- * Utility functions for handling stackframes.
- */
- var StackFrameUtils = this.StackFrameUtils = {
- /**
- * Create a textual representation for the specified stack frame
- * to display in the stackframes container.
- *
- * @param object aFrame
- * The stack frame to label.
- */
- getFrameTitle: function (aFrame) {
- if (aFrame.type == "call") {
- let c = aFrame.callee;
- return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
- }
- return "(" + aFrame.type + ")";
- },
- /**
- * Constructs a scope label based on its environment.
- *
- * @param object aEnv
- * The scope's environment.
- * @return string
- * The scope's label.
- */
- getScopeLabel: function (aEnv) {
- let name = "";
- // Name the outermost scope Global.
- if (!aEnv.parent) {
- name = L10N.getStr("globalScopeLabel");
- }
- // Otherwise construct the scope name.
- else {
- name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
- }
- let label = L10N.getFormatStr("scopeLabel", name);
- switch (aEnv.type) {
- case "with":
- case "object":
- label += " [" + aEnv.object.class + "]";
- break;
- case "function":
- let f = aEnv.function;
- label += " [" +
- (f.name || f.userDisplayName || f.displayName || "(anonymous)") +
- "]";
- break;
- }
- return label;
- }
- };
|