environment.js (5636B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { Actor } = require("resource://devtools/shared/protocol.js"); 7 const { 8 environmentSpec, 9 } = require("resource://devtools/shared/specs/environment.js"); 10 11 /** 12 * Creates an EnvironmentActor. EnvironmentActors are responsible for listing 13 * the bindings introduced by a lexical environment and assigning new values to 14 * those identifier bindings. 15 * 16 * @param Debugger.Environment aEnvironment 17 * The lexical environment that will be used to create the actor. 18 * @param ThreadActor aThreadActor 19 * The parent thread actor that contains this environment. 20 */ 21 class EnvironmentActor extends Actor { 22 constructor(environment, threadActor) { 23 super(threadActor.conn, environmentSpec); 24 25 this.obj = environment; 26 this.threadActor = threadActor; 27 } 28 29 /** 30 * When the Environment Actor is destroyed it removes the 31 * Debugger.Environment.actor field so that environment does not 32 * reference a destroyed actor. 33 */ 34 destroy() { 35 this.obj.actor = null; 36 super.destroy(); 37 } 38 39 /** 40 * Return an environment form for use in a protocol message. 41 */ 42 form() { 43 const form = { actor: this.actorID }; 44 45 // What is this environment's type? 46 if (this.obj.type == "declarative") { 47 form.type = this.obj.calleeScript ? "function" : "block"; 48 } else { 49 form.type = this.obj.type; 50 } 51 52 form.scopeKind = this.obj.scopeKind; 53 54 // Does this environment have a parent? 55 if (this.obj.parent) { 56 form.parent = this.threadActor 57 .createEnvironmentActor(this.obj.parent, this.threadActor) 58 .form(); 59 } 60 61 // Does this environment reflect the properties of an object as variables? 62 if (this.obj.type == "object" || this.obj.type == "with") { 63 form.object = this.threadActor.createValueGrip(this.obj.object); 64 } 65 66 // Is this the environment created for a function call? 67 if (this.obj.calleeScript) { 68 // Client only uses "displayName" for "function". 69 // Create a fake object actor containing only "displayName" as replacement 70 // for the no longer available obj.callee (see bug 1663847). 71 // See bug 1664218 for cleanup. 72 form.function = { displayName: this.obj.calleeScript.displayName }; 73 } 74 75 // Shall we list this environment's bindings? 76 if (this.obj.type == "declarative") { 77 form.bindings = this.bindings(); 78 } 79 80 return form; 81 } 82 83 /** 84 * Handle a protocol request to fully enumerate the bindings introduced by the 85 * lexical environment. 86 */ 87 bindings() { 88 const bindings = { arguments: [], variables: Object.create(null) }; 89 90 // TODO: this part should be removed in favor of the commented-out part 91 // below when getVariableDescriptor lands (bug 725815). 92 if (typeof this.obj.getVariable != "function") { 93 // if (typeof this.obj.getVariableDescriptor != "function") { 94 return bindings; 95 } 96 97 let parameterNames; 98 if (this.obj.calleeScript) { 99 parameterNames = this.obj.calleeScript.parameterNames; 100 } else { 101 parameterNames = []; 102 } 103 for (const name of parameterNames) { 104 // The names of destructuring parameters will be `undefined`. 105 // See https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#parameternames 106 if (name == undefined) { 107 continue; 108 } 109 const arg = Object.create(null); 110 const value = this.obj.getVariable(name); 111 112 // TODO: this part should be removed in favor of the commented-out part 113 // below when getVariableDescriptor lands (bug 725815). 114 const desc = { 115 value, 116 configurable: false, 117 writable: !value?.optimizedOut, 118 enumerable: true, 119 }; 120 121 // let desc = this.obj.getVariableDescriptor(name); 122 const descForm = { 123 enumerable: true, 124 configurable: desc.configurable, 125 }; 126 if ("value" in desc) { 127 descForm.value = this.threadActor.createValueGrip(desc.value); 128 descForm.writable = desc.writable; 129 } else { 130 descForm.get = this.threadActor.createValueGrip(desc.get); 131 descForm.set = this.threadActor.createValueGrip(desc.set); 132 } 133 arg[name] = descForm; 134 bindings.arguments.push(arg); 135 } 136 137 for (const name of this.obj.names()) { 138 if ( 139 bindings.arguments.some(function exists(element) { 140 return !!element[name]; 141 }) 142 ) { 143 continue; 144 } 145 146 const value = this.obj.getVariable(name); 147 148 // TODO: this part should be removed in favor of the commented-out part 149 // below when getVariableDescriptor lands. 150 const desc = { 151 value, 152 configurable: false, 153 writable: !( 154 value && 155 (value.optimizedOut || value.uninitialized || value.missingArguments) 156 ), 157 enumerable: true, 158 }; 159 160 // let desc = this.obj.getVariableDescriptor(name); 161 const descForm = { 162 enumerable: true, 163 configurable: desc.configurable, 164 }; 165 if ("value" in desc) { 166 descForm.value = this.threadActor.createValueGrip(desc.value); 167 descForm.writable = desc.writable; 168 } else { 169 descForm.get = this.threadActor.createValueGrip(desc.get || undefined); 170 descForm.set = this.threadActor.createValueGrip(desc.set || undefined); 171 } 172 bindings.variables[name] = descForm; 173 } 174 175 return bindings; 176 } 177 } 178 179 exports.EnvironmentActor = EnvironmentActor;