/* 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 { Actor } = require("resource://devtools/shared/protocol.js"); const { environmentSpec, } = require("resource://devtools/shared/specs/environment.js"); const { createValueGrip, } = require("resource://devtools/server/actors/object/utils.js"); /** * 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. */ class EnvironmentActor extends Actor { constructor(environment, threadActor) { super(threadActor.conn, environmentSpec); 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() { this.obj.actor = null; super.destroy(); } /** * Return an environment form for use in a protocol message. */ form() { 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() { 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, 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, 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;