summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/environment.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/environment.js')
-rw-r--r--devtools/server/actors/environment.js201
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;