diff options
Diffstat (limited to 'devtools/server/actors/environment.js')
-rw-r--r-- | devtools/server/actors/environment.js | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/devtools/server/actors/environment.js b/devtools/server/actors/environment.js new file mode 100644 index 0000000000..aa5ad1f2bc --- /dev/null +++ b/devtools/server/actors/environment.js @@ -0,0 +1,201 @@ +/* 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 { ActorClassWithSpec, Actor } = require("devtools/shared/protocol"); +const { createValueGrip } = require("devtools/server/actors/object/utils"); +const { environmentSpec } = require("devtools/shared/specs/environment"); + +/** + * Creates an EnvironmentActor. EnvironmentActors are responsible for listing + * the bindings introduced by a lexical environment and assigning new values to + * those identifier bindings. + * + * @param Debugger.Environment aEnvironment + * The lexical environment that will be used to create the actor. + * @param ThreadActor aThreadActor + * The parent thread actor that contains this environment. + */ +const EnvironmentActor = ActorClassWithSpec(environmentSpec, { + initialize: function(environment, threadActor) { + Actor.prototype.initialize.call(this, threadActor.conn); + + this.obj = environment; + this.threadActor = threadActor; + }, + + /** + * When the Environment Actor is destroyed it removes the + * Debugger.Environment.actor field so that environment does not + * reference a destroyed actor. + */ + destroy: function() { + this.obj.actor = null; + Actor.prototype.destroy.call(this); + }, + + /** + * Return an environment form for use in a protocol message. + */ + form: function() { + const form = { actor: this.actorID }; + + // What is this environment's type? + if (this.obj.type == "declarative") { + form.type = this.obj.calleeScript ? "function" : "block"; + } else { + form.type = this.obj.type; + } + + form.scopeKind = this.obj.scopeKind; + + // Does this environment have a parent? + if (this.obj.parent) { + form.parent = this.threadActor + .createEnvironmentActor(this.obj.parent, this.getParent()) + .form(); + } + + // Does this environment reflect the properties of an object as variables? + if (this.obj.type == "object" || this.obj.type == "with") { + form.object = createValueGrip( + this.obj.object, + this.getParent(), + this.threadActor.objectGrip + ); + } + + // Is this the environment created for a function call? + if (this.obj.calleeScript) { + // Client only uses "displayName" for "function". + // Create a fake object actor containing only "displayName" as replacement + // for the no longer available obj.callee (see bug 1663847). + // See bug 1664218 for cleanup. + form.function = { displayName: this.obj.calleeScript.displayName }; + } + + // Shall we list this environment's bindings? + if (this.obj.type == "declarative") { + form.bindings = this.bindings(); + } + + return form; + }, + + /** + * Handle a protocol request to fully enumerate the bindings introduced by the + * lexical environment. + */ + bindings: function() { + const bindings = { arguments: [], variables: {} }; + + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands (bug 725815). + if (typeof this.obj.getVariable != "function") { + // if (typeof this.obj.getVariableDescriptor != "function") { + return bindings; + } + + let parameterNames; + if (this.obj.calleeScript) { + parameterNames = this.obj.calleeScript.parameterNames; + } else { + parameterNames = []; + } + for (const name of parameterNames) { + const arg = {}; + const value = this.obj.getVariable(name); + + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands (bug 725815). + const desc = { + value: value, + configurable: false, + writable: !value?.optimizedOut, + enumerable: true, + }; + + // let desc = this.obj.getVariableDescriptor(name); + const descForm = { + enumerable: true, + configurable: desc.configurable, + }; + if ("value" in desc) { + descForm.value = createValueGrip( + desc.value, + this.getParent(), + this.threadActor.objectGrip + ); + descForm.writable = desc.writable; + } else { + descForm.get = createValueGrip( + desc.get, + this.getParent(), + this.threadActor.objectGrip + ); + descForm.set = createValueGrip( + desc.set, + this.getParent(), + this.threadActor.objectGrip + ); + } + arg[name] = descForm; + bindings.arguments.push(arg); + } + + for (const name of this.obj.names()) { + if ( + bindings.arguments.some(function exists(element) { + return !!element[name]; + }) + ) { + continue; + } + + const value = this.obj.getVariable(name); + + // TODO: this part should be removed in favor of the commented-out part + // below when getVariableDescriptor lands. + const desc = { + value: value, + configurable: false, + writable: !( + value && + (value.optimizedOut || value.uninitialized || value.missingArguments) + ), + enumerable: true, + }; + + // let desc = this.obj.getVariableDescriptor(name); + const descForm = { + enumerable: true, + configurable: desc.configurable, + }; + if ("value" in desc) { + descForm.value = createValueGrip( + desc.value, + this.getParent(), + this.threadActor.objectGrip + ); + descForm.writable = desc.writable; + } else { + descForm.get = createValueGrip( + desc.get || undefined, + this.getParent(), + this.threadActor.objectGrip + ); + descForm.set = createValueGrip( + desc.set || undefined, + this.getParent(), + this.threadActor.objectGrip + ); + } + bindings.variables[name] = descForm; + } + + return bindings; + }, +}); + +exports.EnvironmentActor = EnvironmentActor; |