Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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); |