browser/devtools/shared/widgets/VariablesViewController.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6 "use strict";
michael@0 7
michael@0 8 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 9
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
michael@0 13 Cu.import("resource:///modules/devtools/VariablesView.jsm");
michael@0 14 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
michael@0 15
michael@0 16 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
michael@0 17 "resource://gre/modules/devtools/Loader.jsm");
michael@0 18
michael@0 19 Object.defineProperty(this, "WebConsoleUtils", {
michael@0 20 get: function() {
michael@0 21 return devtools.require("devtools/toolkit/webconsole/utils").Utils;
michael@0 22 },
michael@0 23 configurable: true,
michael@0 24 enumerable: true
michael@0 25 });
michael@0 26
michael@0 27 XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
michael@0 28 Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
michael@0 29 );
michael@0 30
michael@0 31 XPCOMUtils.defineLazyModuleGetter(this, "console",
michael@0 32 "resource://gre/modules/devtools/Console.jsm");
michael@0 33
michael@0 34 const MAX_LONG_STRING_LENGTH = 200000;
michael@0 35 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
michael@0 36
michael@0 37 this.EXPORTED_SYMBOLS = ["VariablesViewController", "StackFrameUtils"];
michael@0 38
michael@0 39
michael@0 40 /**
michael@0 41 * Controller for a VariablesView that handles interfacing with the debugger
michael@0 42 * protocol. Is able to populate scopes and variables via the protocol as well
michael@0 43 * as manage actor lifespans.
michael@0 44 *
michael@0 45 * @param VariablesView aView
michael@0 46 * The view to attach to.
michael@0 47 * @param object aOptions [optional]
michael@0 48 * Options for configuring the controller. Supported options:
michael@0 49 * - getObjectClient: @see this._setClientGetters
michael@0 50 * - getLongStringClient: @see this._setClientGetters
michael@0 51 * - getEnvironmentClient: @see this._setClientGetters
michael@0 52 * - releaseActor: @see this._setClientGetters
michael@0 53 * - overrideValueEvalMacro: @see _setEvaluationMacros
michael@0 54 * - getterOrSetterEvalMacro: @see _setEvaluationMacros
michael@0 55 * - simpleValueEvalMacro: @see _setEvaluationMacros
michael@0 56 */
michael@0 57 function VariablesViewController(aView, aOptions = {}) {
michael@0 58 this.addExpander = this.addExpander.bind(this);
michael@0 59
michael@0 60 this._setClientGetters(aOptions);
michael@0 61 this._setEvaluationMacros(aOptions);
michael@0 62
michael@0 63 this._actors = new Set();
michael@0 64 this.view = aView;
michael@0 65 this.view.controller = this;
michael@0 66 }
michael@0 67
michael@0 68 VariablesViewController.prototype = {
michael@0 69 /**
michael@0 70 * The default getter/setter evaluation macro.
michael@0 71 */
michael@0 72 _getterOrSetterEvalMacro: VariablesView.getterOrSetterEvalMacro,
michael@0 73
michael@0 74 /**
michael@0 75 * The default override value evaluation macro.
michael@0 76 */
michael@0 77 _overrideValueEvalMacro: VariablesView.overrideValueEvalMacro,
michael@0 78
michael@0 79 /**
michael@0 80 * The default simple value evaluation macro.
michael@0 81 */
michael@0 82 _simpleValueEvalMacro: VariablesView.simpleValueEvalMacro,
michael@0 83
michael@0 84 /**
michael@0 85 * Set the functions used to retrieve debugger client grips.
michael@0 86 *
michael@0 87 * @param object aOptions
michael@0 88 * Options for getting the client grips. Supported options:
michael@0 89 * - getObjectClient: callback for creating an object grip client
michael@0 90 * - getLongStringClient: callback for creating a long string grip client
michael@0 91 * - getEnvironmentClient: callback for creating an environment client
michael@0 92 * - releaseActor: callback for releasing an actor when it's no longer needed
michael@0 93 */
michael@0 94 _setClientGetters: function(aOptions) {
michael@0 95 if (aOptions.getObjectClient) {
michael@0 96 this._getObjectClient = aOptions.getObjectClient;
michael@0 97 }
michael@0 98 if (aOptions.getLongStringClient) {
michael@0 99 this._getLongStringClient = aOptions.getLongStringClient;
michael@0 100 }
michael@0 101 if (aOptions.getEnvironmentClient) {
michael@0 102 this._getEnvironmentClient = aOptions.getEnvironmentClient;
michael@0 103 }
michael@0 104 if (aOptions.releaseActor) {
michael@0 105 this._releaseActor = aOptions.releaseActor;
michael@0 106 }
michael@0 107 },
michael@0 108
michael@0 109 /**
michael@0 110 * Sets the functions used when evaluating strings in the variables view.
michael@0 111 *
michael@0 112 * @param object aOptions
michael@0 113 * Options for configuring the macros. Supported options:
michael@0 114 * - overrideValueEvalMacro: callback for creating an overriding eval macro
michael@0 115 * - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
michael@0 116 * - simpleValueEvalMacro: callback for creating a simple value eval macro
michael@0 117 */
michael@0 118 _setEvaluationMacros: function(aOptions) {
michael@0 119 if (aOptions.overrideValueEvalMacro) {
michael@0 120 this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
michael@0 121 }
michael@0 122 if (aOptions.getterOrSetterEvalMacro) {
michael@0 123 this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
michael@0 124 }
michael@0 125 if (aOptions.simpleValueEvalMacro) {
michael@0 126 this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
michael@0 127 }
michael@0 128 },
michael@0 129
michael@0 130 /**
michael@0 131 * Populate a long string into a target using a grip.
michael@0 132 *
michael@0 133 * @param Variable aTarget
michael@0 134 * The target Variable/Property to put the retrieved string into.
michael@0 135 * @param LongStringActor aGrip
michael@0 136 * The long string grip that use to retrieve the full string.
michael@0 137 * @return Promise
michael@0 138 * The promise that will be resolved when the string is retrieved.
michael@0 139 */
michael@0 140 _populateFromLongString: function(aTarget, aGrip){
michael@0 141 let deferred = promise.defer();
michael@0 142
michael@0 143 let from = aGrip.initial.length;
michael@0 144 let to = Math.min(aGrip.length, MAX_LONG_STRING_LENGTH);
michael@0 145
michael@0 146 this._getLongStringClient(aGrip).substring(from, to, aResponse => {
michael@0 147 // Stop tracking the actor because it's no longer needed.
michael@0 148 this.releaseActor(aGrip);
michael@0 149
michael@0 150 // Replace the preview with the full string and make it non-expandable.
michael@0 151 aTarget.onexpand = null;
michael@0 152 aTarget.setGrip(aGrip.initial + aResponse.substring);
michael@0 153 aTarget.hideArrow();
michael@0 154
michael@0 155 deferred.resolve();
michael@0 156 });
michael@0 157
michael@0 158 return deferred.promise;
michael@0 159 },
michael@0 160
michael@0 161 /**
michael@0 162 * Adds properties to a Scope, Variable, or Property in the view. Triggered
michael@0 163 * when a scope is expanded or certain variables are hovered.
michael@0 164 *
michael@0 165 * @param Scope aTarget
michael@0 166 * The Scope where the properties will be placed into.
michael@0 167 * @param object aGrip
michael@0 168 * The grip to use to populate the target.
michael@0 169 */
michael@0 170 _populateFromObject: function(aTarget, aGrip) {
michael@0 171 let deferred = promise.defer();
michael@0 172
michael@0 173 let objectClient = this._getObjectClient(aGrip);
michael@0 174 objectClient.getPrototypeAndProperties(aResponse => {
michael@0 175 let { ownProperties, prototype } = aResponse;
michael@0 176 // 'safeGetterValues' is new and isn't necessary defined on old actors.
michael@0 177 let safeGetterValues = aResponse.safeGetterValues || {};
michael@0 178 let sortable = VariablesView.isSortable(aGrip.class);
michael@0 179
michael@0 180 // Merge the safe getter values into one object such that we can use it
michael@0 181 // in VariablesView.
michael@0 182 for (let name of Object.keys(safeGetterValues)) {
michael@0 183 if (name in ownProperties) {
michael@0 184 let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
michael@0 185 ownProperties[name].getterValue = getterValue;
michael@0 186 ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
michael@0 187 } else {
michael@0 188 ownProperties[name] = safeGetterValues[name];
michael@0 189 }
michael@0 190 }
michael@0 191
michael@0 192 // Add all the variable properties.
michael@0 193 if (ownProperties) {
michael@0 194 aTarget.addItems(ownProperties, {
michael@0 195 // Not all variables need to force sorted properties.
michael@0 196 sorted: sortable,
michael@0 197 // Expansion handlers must be set after the properties are added.
michael@0 198 callback: this.addExpander
michael@0 199 });
michael@0 200 }
michael@0 201
michael@0 202 // Add the variable's __proto__.
michael@0 203 if (prototype && prototype.type != "null") {
michael@0 204 let proto = aTarget.addItem("__proto__", { value: prototype });
michael@0 205 // Expansion handlers must be set after the properties are added.
michael@0 206 this.addExpander(proto, prototype);
michael@0 207 }
michael@0 208
michael@0 209 // If the object is a function we need to fetch its scope chain
michael@0 210 // to show them as closures for the respective function.
michael@0 211 if (aGrip.class == "Function") {
michael@0 212 objectClient.getScope(aResponse => {
michael@0 213 if (aResponse.error) {
michael@0 214 // This function is bound to a built-in object or it's not present
michael@0 215 // in the current scope chain. Not necessarily an actual error,
michael@0 216 // it just means that there's no closure for the function.
michael@0 217 console.warn(aResponse.error + ": " + aResponse.message);
michael@0 218 return void deferred.resolve();
michael@0 219 }
michael@0 220 this._populateWithClosure(aTarget, aResponse.scope).then(deferred.resolve);
michael@0 221 });
michael@0 222 } else {
michael@0 223 deferred.resolve();
michael@0 224 }
michael@0 225 });
michael@0 226
michael@0 227 return deferred.promise;
michael@0 228 },
michael@0 229
michael@0 230 /**
michael@0 231 * Adds the scope chain elements (closures) of a function variable.
michael@0 232 *
michael@0 233 * @param Variable aTarget
michael@0 234 * The variable where the properties will be placed into.
michael@0 235 * @param Scope aScope
michael@0 236 * The lexical environment form as specified in the protocol.
michael@0 237 */
michael@0 238 _populateWithClosure: function(aTarget, aScope) {
michael@0 239 let objectScopes = [];
michael@0 240 let environment = aScope;
michael@0 241 let funcScope = aTarget.addItem("<Closure>");
michael@0 242 funcScope.target.setAttribute("scope", "");
michael@0 243 funcScope.showArrow();
michael@0 244
michael@0 245 do {
michael@0 246 // Create a scope to contain all the inspected variables.
michael@0 247 let label = StackFrameUtils.getScopeLabel(environment);
michael@0 248
michael@0 249 // Block scopes may have the same label, so make addItem allow duplicates.
michael@0 250 let closure = funcScope.addItem(label, undefined, true);
michael@0 251 closure.target.setAttribute("scope", "");
michael@0 252 closure.showArrow();
michael@0 253
michael@0 254 // Add nodes for every argument and every other variable in scope.
michael@0 255 if (environment.bindings) {
michael@0 256 this._populateWithEnvironmentBindings(closure, environment.bindings);
michael@0 257 } else {
michael@0 258 let deferred = promise.defer();
michael@0 259 objectScopes.push(deferred.promise);
michael@0 260 this._getEnvironmentClient(environment).getBindings(response => {
michael@0 261 this._populateWithEnvironmentBindings(closure, response.bindings);
michael@0 262 deferred.resolve();
michael@0 263 });
michael@0 264 }
michael@0 265 } while ((environment = environment.parent));
michael@0 266
michael@0 267 return promise.all(objectScopes).then(() => {
michael@0 268 // Signal that scopes have been fetched.
michael@0 269 this.view.emit("fetched", "scopes", funcScope);
michael@0 270 });
michael@0 271 },
michael@0 272
michael@0 273 /**
michael@0 274 * Adds nodes for every specified binding to the closure node.
michael@0 275 *
michael@0 276 * @param Variable aTarget
michael@0 277 * The variable where the bindings will be placed into.
michael@0 278 * @param object aBindings
michael@0 279 * The bindings form as specified in the protocol.
michael@0 280 */
michael@0 281 _populateWithEnvironmentBindings: function(aTarget, aBindings) {
michael@0 282 // Add nodes for every argument in the scope.
michael@0 283 aTarget.addItems(aBindings.arguments.reduce((accumulator, arg) => {
michael@0 284 let name = Object.getOwnPropertyNames(arg)[0];
michael@0 285 let descriptor = arg[name];
michael@0 286 accumulator[name] = descriptor;
michael@0 287 return accumulator;
michael@0 288 }, {}), {
michael@0 289 // Arguments aren't sorted.
michael@0 290 sorted: false,
michael@0 291 // Expansion handlers must be set after the properties are added.
michael@0 292 callback: this.addExpander
michael@0 293 });
michael@0 294
michael@0 295 // Add nodes for every other variable in the scope.
michael@0 296 aTarget.addItems(aBindings.variables, {
michael@0 297 // Not all variables need to force sorted properties.
michael@0 298 sorted: VARIABLES_SORTING_ENABLED,
michael@0 299 // Expansion handlers must be set after the properties are added.
michael@0 300 callback: this.addExpander
michael@0 301 });
michael@0 302 },
michael@0 303
michael@0 304 /**
michael@0 305 * Adds an 'onexpand' callback for a variable, lazily handling
michael@0 306 * the addition of new properties.
michael@0 307 *
michael@0 308 * @param Variable aTarget
michael@0 309 * The variable where the properties will be placed into.
michael@0 310 * @param any aSource
michael@0 311 * The source to use to populate the target.
michael@0 312 */
michael@0 313 addExpander: function(aTarget, aSource) {
michael@0 314 // Attach evaluation macros as necessary.
michael@0 315 if (aTarget.getter || aTarget.setter) {
michael@0 316 aTarget.evaluationMacro = this._overrideValueEvalMacro;
michael@0 317 let getter = aTarget.get("get");
michael@0 318 if (getter) {
michael@0 319 getter.evaluationMacro = this._getterOrSetterEvalMacro;
michael@0 320 }
michael@0 321 let setter = aTarget.get("set");
michael@0 322 if (setter) {
michael@0 323 setter.evaluationMacro = this._getterOrSetterEvalMacro;
michael@0 324 }
michael@0 325 } else {
michael@0 326 aTarget.evaluationMacro = this._simpleValueEvalMacro;
michael@0 327 }
michael@0 328
michael@0 329 // If the source is primitive then an expander is not needed.
michael@0 330 if (VariablesView.isPrimitive({ value: aSource })) {
michael@0 331 return;
michael@0 332 }
michael@0 333
michael@0 334 // If the source is a long string then show the arrow.
michael@0 335 if (WebConsoleUtils.isActorGrip(aSource) && aSource.type == "longString") {
michael@0 336 aTarget.showArrow();
michael@0 337 }
michael@0 338
michael@0 339 // Make sure that properties are always available on expansion.
michael@0 340 aTarget.onexpand = () => this.populate(aTarget, aSource);
michael@0 341
michael@0 342 // Some variables are likely to contain a very large number of properties.
michael@0 343 // It's a good idea to be prepared in case of an expansion.
michael@0 344 if (aTarget.shouldPrefetch) {
michael@0 345 aTarget.addEventListener("mouseover", aTarget.onexpand, false);
michael@0 346 }
michael@0 347
michael@0 348 // Register all the actors that this controller now depends on.
michael@0 349 for (let grip of [aTarget.value, aTarget.getter, aTarget.setter]) {
michael@0 350 if (WebConsoleUtils.isActorGrip(grip)) {
michael@0 351 this._actors.add(grip.actor);
michael@0 352 }
michael@0 353 }
michael@0 354 },
michael@0 355
michael@0 356 /**
michael@0 357 * Adds properties to a Scope, Variable, or Property in the view. Triggered
michael@0 358 * when a scope is expanded or certain variables are hovered.
michael@0 359 *
michael@0 360 * This does not expand the target, it only populates it.
michael@0 361 *
michael@0 362 * @param Scope aTarget
michael@0 363 * The Scope to be expanded.
michael@0 364 * @param object aSource
michael@0 365 * The source to use to populate the target.
michael@0 366 * @return Promise
michael@0 367 * The promise that is resolved once the target has been expanded.
michael@0 368 */
michael@0 369 populate: function(aTarget, aSource) {
michael@0 370 // Fetch the variables only once.
michael@0 371 if (aTarget._fetched) {
michael@0 372 return aTarget._fetched;
michael@0 373 }
michael@0 374 // Make sure the source grip is available.
michael@0 375 if (!aSource) {
michael@0 376 return promise.reject(new Error("No actor grip was given for the variable."));
michael@0 377 }
michael@0 378
michael@0 379 let deferred = promise.defer();
michael@0 380 aTarget._fetched = deferred.promise;
michael@0 381
michael@0 382 // If the target is a Variable or Property then we're fetching properties.
michael@0 383 if (VariablesView.isVariable(aTarget)) {
michael@0 384 this._populateFromObject(aTarget, aSource).then(() => {
michael@0 385 // Signal that properties have been fetched.
michael@0 386 this.view.emit("fetched", "properties", aTarget);
michael@0 387 // Commit the hierarchy because new items were added.
michael@0 388 this.view.commitHierarchy();
michael@0 389 deferred.resolve();
michael@0 390 });
michael@0 391 return deferred.promise;
michael@0 392 }
michael@0 393
michael@0 394 switch (aSource.type) {
michael@0 395 case "longString":
michael@0 396 this._populateFromLongString(aTarget, aSource).then(() => {
michael@0 397 // Signal that a long string has been fetched.
michael@0 398 this.view.emit("fetched", "longString", aTarget);
michael@0 399 deferred.resolve();
michael@0 400 });
michael@0 401 break;
michael@0 402 case "with":
michael@0 403 case "object":
michael@0 404 this._populateFromObject(aTarget, aSource.object).then(() => {
michael@0 405 // Signal that variables have been fetched.
michael@0 406 this.view.emit("fetched", "variables", aTarget);
michael@0 407 // Commit the hierarchy because new items were added.
michael@0 408 this.view.commitHierarchy();
michael@0 409 deferred.resolve();
michael@0 410 });
michael@0 411 break;
michael@0 412 case "block":
michael@0 413 case "function":
michael@0 414 this._populateWithEnvironmentBindings(aTarget, aSource.bindings);
michael@0 415 // No need to signal that variables have been fetched, since
michael@0 416 // the scope arguments and variables are already attached to the
michael@0 417 // environment bindings, so pausing the active thread is unnecessary.
michael@0 418 // Commit the hierarchy because new items were added.
michael@0 419 this.view.commitHierarchy();
michael@0 420 deferred.resolve();
michael@0 421 break;
michael@0 422 default:
michael@0 423 let error = "Unknown Debugger.Environment type: " + aSource.type;
michael@0 424 Cu.reportError(error);
michael@0 425 deferred.reject(error);
michael@0 426 }
michael@0 427
michael@0 428 return deferred.promise;
michael@0 429 },
michael@0 430
michael@0 431 /**
michael@0 432 * Release an actor from the controller.
michael@0 433 *
michael@0 434 * @param object aActor
michael@0 435 * The actor to release.
michael@0 436 */
michael@0 437 releaseActor: function(aActor){
michael@0 438 if (this._releaseActor) {
michael@0 439 this._releaseActor(aActor);
michael@0 440 }
michael@0 441 this._actors.delete(aActor);
michael@0 442 },
michael@0 443
michael@0 444 /**
michael@0 445 * Release all the actors referenced by the controller, optionally filtered.
michael@0 446 *
michael@0 447 * @param function aFilter [optional]
michael@0 448 * Callback to filter which actors are released.
michael@0 449 */
michael@0 450 releaseActors: function(aFilter) {
michael@0 451 for (let actor of this._actors) {
michael@0 452 if (!aFilter || aFilter(actor)) {
michael@0 453 this.releaseActor(actor);
michael@0 454 }
michael@0 455 }
michael@0 456 },
michael@0 457
michael@0 458 /**
michael@0 459 * Helper function for setting up a single Scope with a single Variable
michael@0 460 * contained within it.
michael@0 461 *
michael@0 462 * This function will empty the variables view.
michael@0 463 *
michael@0 464 * @param object aOptions
michael@0 465 * Options for the contents of the view:
michael@0 466 * - objectActor: the grip of the new ObjectActor to show.
michael@0 467 * - rawObject: the raw object to show.
michael@0 468 * - label: the label for the inspected object.
michael@0 469 * @param object aConfiguration
michael@0 470 * Additional options for the controller:
michael@0 471 * - overrideValueEvalMacro: @see _setEvaluationMacros
michael@0 472 * - getterOrSetterEvalMacro: @see _setEvaluationMacros
michael@0 473 * - simpleValueEvalMacro: @see _setEvaluationMacros
michael@0 474 * @return Object
michael@0 475 * - variable: the created Variable.
michael@0 476 * - expanded: the Promise that resolves when the variable expands.
michael@0 477 */
michael@0 478 setSingleVariable: function(aOptions, aConfiguration = {}) {
michael@0 479 this._setEvaluationMacros(aConfiguration);
michael@0 480 this.view.empty();
michael@0 481
michael@0 482 let scope = this.view.addScope(aOptions.label);
michael@0 483 scope.expanded = true; // Expand the scope by default.
michael@0 484 scope.locked = true; // Prevent collpasing the scope.
michael@0 485
michael@0 486 let variable = scope.addItem("", { enumerable: true });
michael@0 487 let populated;
michael@0 488
michael@0 489 if (aOptions.objectActor) {
michael@0 490 populated = this.populate(variable, aOptions.objectActor);
michael@0 491 variable.expand();
michael@0 492 } else if (aOptions.rawObject) {
michael@0 493 variable.populate(aOptions.rawObject, { expanded: true });
michael@0 494 populated = promise.resolve();
michael@0 495 }
michael@0 496
michael@0 497 return { variable: variable, expanded: populated };
michael@0 498 },
michael@0 499 };
michael@0 500
michael@0 501
michael@0 502 /**
michael@0 503 * Attaches a VariablesViewController to a VariablesView if it doesn't already
michael@0 504 * have one.
michael@0 505 *
michael@0 506 * @param VariablesView aView
michael@0 507 * The view to attach to.
michael@0 508 * @param object aOptions
michael@0 509 * The options to use in creating the controller.
michael@0 510 * @return VariablesViewController
michael@0 511 */
michael@0 512 VariablesViewController.attach = function(aView, aOptions) {
michael@0 513 if (aView.controller) {
michael@0 514 return aView.controller;
michael@0 515 }
michael@0 516 return new VariablesViewController(aView, aOptions);
michael@0 517 };
michael@0 518
michael@0 519 /**
michael@0 520 * Utility functions for handling stackframes.
michael@0 521 */
michael@0 522 let StackFrameUtils = {
michael@0 523 /**
michael@0 524 * Create a textual representation for the specified stack frame
michael@0 525 * to display in the stackframes container.
michael@0 526 *
michael@0 527 * @param object aFrame
michael@0 528 * The stack frame to label.
michael@0 529 */
michael@0 530 getFrameTitle: function(aFrame) {
michael@0 531 if (aFrame.type == "call") {
michael@0 532 let c = aFrame.callee;
michael@0 533 return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
michael@0 534 }
michael@0 535 return "(" + aFrame.type + ")";
michael@0 536 },
michael@0 537
michael@0 538 /**
michael@0 539 * Constructs a scope label based on its environment.
michael@0 540 *
michael@0 541 * @param object aEnv
michael@0 542 * The scope's environment.
michael@0 543 * @return string
michael@0 544 * The scope's label.
michael@0 545 */
michael@0 546 getScopeLabel: function(aEnv) {
michael@0 547 let name = "";
michael@0 548
michael@0 549 // Name the outermost scope Global.
michael@0 550 if (!aEnv.parent) {
michael@0 551 name = L10N.getStr("globalScopeLabel");
michael@0 552 }
michael@0 553 // Otherwise construct the scope name.
michael@0 554 else {
michael@0 555 name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
michael@0 556 }
michael@0 557
michael@0 558 let label = L10N.getFormatStr("scopeLabel", name);
michael@0 559 switch (aEnv.type) {
michael@0 560 case "with":
michael@0 561 case "object":
michael@0 562 label += " [" + aEnv.object.class + "]";
michael@0 563 break;
michael@0 564 case "function":
michael@0 565 let f = aEnv.function;
michael@0 566 label += " [" +
michael@0 567 (f.name || f.userDisplayName || f.displayName || "(anonymous)") +
michael@0 568 "]";
michael@0 569 break;
michael@0 570 }
michael@0 571 return label;
michael@0 572 }
michael@0 573 };
michael@0 574
michael@0 575 /**
michael@0 576 * Localization convenience methods.
michael@0 577 */
michael@0 578 let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);

mercurial