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/. */
7 const { objectSpec
} = require("resource://devtools/shared/specs/object.js");
11 } = require("resource://devtools/shared/protocol.js");
14 } = require("resource://devtools/client/fronts/string.js");
16 const SUPPORT_ENUM_ENTRIES_SET
= new Set([
32 * A ObjectFront is used as a front end for the ObjectActor that is
33 * created on the server, hiding implementation details.
35 class ObjectFront
extends FrontClassWithSpec(objectSpec
) {
36 constructor(conn
= null, targetFront
= null, parentFront
= null, data
) {
38 throw new Error("ObjectFront require a parent front");
41 super(conn
, targetFront
, parentFront
);
44 this.actorID
= this._grip
.actor
;
47 parentFront
.manage(this);
51 this.actorID
= data
.actor
;
56 // Object fronts are simple fronts, they don't need to be cleaned up on
57 // toolbox destroy. `conn` is a DebuggerClient instance, check the
58 // `isToolboxDestroy` flag to skip the destroy.
59 return this.conn
&& this.conn
.isToolboxDestroy
;
67 return this._grip
.frozen
;
71 return this._grip
.sealed
;
75 return this._grip
.extensible
;
79 * Request the prototype and own properties of the object.
81 async
getPrototypeAndProperties() {
82 const result
= await
super.prototypeAndProperties();
84 if (result
.prototype) {
85 result
.prototype = getAdHocFrontOrPrimitiveGrip(result
.prototype, this);
88 // The result packet can have multiple properties that hold grips which we may need
89 // to turn into fronts.
90 const gripKeys
= ["value", "getterValue", "get", "set"];
92 if (result
.ownProperties
) {
93 Object
.entries(result
.ownProperties
).forEach(([key
, descriptor
]) => {
95 for (const gripKey
of gripKeys
) {
96 if (descriptor
.hasOwnProperty(gripKey
)) {
97 result
.ownProperties
[key
][gripKey
] = getAdHocFrontOrPrimitiveGrip(
107 if (result
.safeGetterValues
) {
108 Object
.entries(result
.safeGetterValues
).forEach(([key
, descriptor
]) => {
110 for (const gripKey
of gripKeys
) {
111 if (descriptor
.hasOwnProperty(gripKey
)) {
112 result
.safeGetterValues
[key
][gripKey
] =
113 getAdHocFrontOrPrimitiveGrip(descriptor
[gripKey
], this);
120 if (result
.ownSymbols
) {
121 result
.ownSymbols
.forEach((descriptor
, i
, arr
) => {
123 for (const gripKey
of gripKeys
) {
124 if (descriptor
.hasOwnProperty(gripKey
)) {
125 arr
[i
][gripKey
] = getAdHocFrontOrPrimitiveGrip(
139 * Request a PropertyIteratorFront instance to ease listing
140 * properties for this object.
142 * @param options Object
143 * A dictionary object with various boolean attributes:
144 * - ignoreIndexedProperties Boolean
145 * If true, filters out Array items.
146 * e.g. properties names between `0` and `object.length`.
147 * - ignoreNonIndexedProperties Boolean
148 * If true, filters out items that aren't array items
149 * e.g. properties names that are not a number between `0`
150 * and `object.length`.
152 * If true, the iterator will sort the properties by name
153 * before dispatching them.
155 enumProperties(options
) {
156 return super.enumProperties(options
);
160 * Request a PropertyIteratorFront instance to enumerate entries in a
161 * Map/Set-like object.
164 if (!SUPPORT_ENUM_ENTRIES_SET
.has(this._grip
.class)) {
166 `enumEntries can't be called for "${
168 }" grips. Supported grips are: ${[...SUPPORT_ENUM_ENTRIES_SET].join(
174 return super.enumEntries();
178 * Request a SymbolIteratorFront instance to enumerate symbols in an object.
181 if (this._grip
.type
!== "object") {
182 console
.error("enumSymbols is only valid for objects grips.");
185 return super.enumSymbols();
189 * Request the property descriptor of the object's specified property.
191 * @param name string The name of the requested property.
194 return super.property(name
);
198 * Request the value of the object's specified property.
200 * @param name string The name of the requested property.
201 * @param receiverId string|null The actorId of the receiver to be used for getters.
203 async
getPropertyValue(name
, receiverId
) {
204 const response
= await
super.propertyValue(name
, receiverId
);
206 if (response
.value
) {
207 const { value
} = response
;
209 response
.value
.return = getAdHocFrontOrPrimitiveGrip(
216 response
.value
.throw = getAdHocFrontOrPrimitiveGrip(value
.throw, this);
223 * Get the body of a custom formatted object.
225 async
customFormatterBody() {
226 const result
= await
super.customFormatterBody();
228 if (!result
?.customFormatterBody
) {
232 const createFrontsInJsonMl
= item
=> {
233 if (Array
.isArray(item
)) {
234 return item
.map(i
=> createFrontsInJsonMl(i
));
236 return getAdHocFrontOrPrimitiveGrip(item
, this);
239 result
.customFormatterBody
= createFrontsInJsonMl(
240 result
.customFormatterBody
247 * Request the prototype of the object.
249 async
getPrototype() {
250 const result
= await
super.prototype();
252 if (!result
.prototype) {
256 result
.prototype = getAdHocFrontOrPrimitiveGrip(result
.prototype, this);
262 * Request the state of a promise.
264 async
getPromiseState() {
265 if (this._grip
.class !== "Promise") {
266 console
.error("getPromiseState is only valid for promise grips.");
270 let response
, promiseState
;
272 response
= await
super.promiseState();
273 promiseState
= response
.promiseState
;
275 // @backward-compat { version 85 } On older server, the promiseState request didn't
276 // didn't exist (bug 1552648). The promise state was directly included in the grip.
277 if (error
.message
.includes("unrecognizedPacketType")) {
278 promiseState
= this._grip
.promiseState
;
279 response
= { promiseState
};
285 const { value
, reason
} = promiseState
;
288 promiseState
.value
= getAdHocFrontOrPrimitiveGrip(value
, this);
292 promiseState
.reason
= getAdHocFrontOrPrimitiveGrip(reason
, this);
299 * Request the target and handler internal slots of a proxy.
301 async
getProxySlots() {
302 if (this._grip
.class !== "Proxy") {
303 console
.error("getProxySlots is only valid for proxy grips.");
307 const response
= await
super.proxySlots();
308 const { proxyHandler
, proxyTarget
} = response
;
311 response
.proxyHandler
= getAdHocFrontOrPrimitiveGrip(proxyHandler
, this);
315 response
.proxyTarget
= getAdHocFrontOrPrimitiveGrip(proxyTarget
, this);
321 get isSyntaxError() {
322 return this._grip
.preview
&& this._grip
.preview
.name
== "SyntaxError";
327 * When we are asking the server for the value of a given variable, we might get different
329 * - a primitive (string, number, null, false, boolean)
331 * - an "object" (i.e. not primitive nor long string)
333 * Each of those type need a different front, or none:
334 * - a primitive does not allow further interaction with the server, so we don't need
335 * to have a dedicated front.
336 * - a long string needs a longStringFront to be able to retrieve the full string.
337 * - an object need an objectFront to retrieve properties, symbols and prototype.
339 * In the case an ObjectFront is created, we also check if the object has properties
340 * that should be turned into fronts as well.
342 * @param {String|Number|Object} options: The packet returned by the server.
343 * @param {Front} parentFront
345 * @returns {Number|String|Object|LongStringFront|ObjectFront}
347 function getAdHocFrontOrPrimitiveGrip(packet
, parentFront
) {
348 // We only want to try to create a front when it makes sense, i.e when it has an
350 // - it's a Symbol (See Bug 1600299)
351 // - it's a mapEntry (the preview.key and preview.value properties can hold actors)
352 // - it's a highlightRegistryEntry (the preview.value properties can hold actors)
353 // - or it is already a front (happens when we are using the legacy listeners in the ResourceCommand)
354 const isPacketAnObject
= packet
&& typeof packet
=== "object";
355 const isFront
= !!packet
?.typeName
;
358 packet
.type
== "symbol" ||
359 (packet
.type
!== "mapEntry" &&
360 packet
.type
!== "highlightRegistryEntry" &&
367 const { conn
} = parentFront
;
368 // If the parent front is a target, consider it as the target to use for all objects
369 const targetFront
= parentFront
.isTargetFront
371 : parentFront
.targetFront
;
373 // We may have already created a front for this object actor since some actor (e.g. the
374 // thread actor) cache the object actors they create.
375 const existingFront
= conn
.getFrontByID(packet
.actor
);
377 // This methods replicates Protocol.js logic when we receive an actor "form" (here `packet`):
378 // https://searchfox.org/mozilla-central/rev/aecbd5cdd28a09e11872bc829d9e6e4b943e6e49/devtools/shared/protocol/types.js#346
379 // We notify the Object Front about the new "form" so that it can update itself
380 // with latest data provided by the server.
381 // This will help ensure that the object previews get updated.
382 existingFront
.form(packet
);
384 // The `packet` may contain nested actor forms which should be converted into Fronts.
385 createChildFronts(existingFront
, packet
);
387 return existingFront
;
390 const { type
} = packet
;
392 if (type
=== "longString") {
393 const longStringFront
= new LongStringFront(conn
, targetFront
, parentFront
);
394 longStringFront
.form(packet
);
395 parentFront
.manage(longStringFront
);
396 return longStringFront
;
400 (type
=== "mapEntry" || type
=== "highlightRegistryEntry") &&
403 const { key
, value
} = packet
.preview
;
404 packet
.preview
.key
= getAdHocFrontOrPrimitiveGrip(
409 packet
.preview
.value
= getAdHocFrontOrPrimitiveGrip(
417 const objectFront
= new ObjectFront(conn
, targetFront
, parentFront
, packet
);
418 createChildFronts(objectFront
, packet
);
423 * Create child fronts of the passed object front given a packet. Those child fronts are
424 * usually mapping actors of the packet sub-properties (preview items, promise fullfilled
427 * @param {ObjectFront} objectFront
428 * @param {String|Number|Object} packet: The packet returned by the server
430 function createChildFronts(objectFront
, packet
) {
431 if (packet
.preview
) {
432 const { message
, entries
} = packet
.preview
;
434 // The message could be a longString.
435 if (packet
.preview
.message
) {
436 packet
.preview
.message
= getAdHocFrontOrPrimitiveGrip(
442 // Handle Map/WeakMap preview entries (the preview might be directly used if has all the
443 // items needed, i.e. if the Map has less than 10 items).
444 if (entries
&& Array
.isArray(entries
)) {
445 packet
.preview
.entries
= entries
.map(([key
, value
]) => [
446 getAdHocFrontOrPrimitiveGrip(key
, objectFront
),
447 getAdHocFrontOrPrimitiveGrip(value
, objectFront
),
452 if (packet
&& typeof packet
.ownProperties
=== "object") {
453 for (const [name
, descriptor
] of Object
.entries(packet
.ownProperties
)) {
454 // The descriptor can have multiple properties that hold grips which we may need
455 // to turn into fronts.
456 const gripKeys
= ["value", "getterValue", "get", "set"];
457 for (const key
of gripKeys
) {
460 typeof descriptor
=== "object" &&
461 descriptor
.hasOwnProperty(key
)
463 packet
.ownProperties
[name
][key
] = getAdHocFrontOrPrimitiveGrip(
472 // Handle custom formatters
473 if (packet
&& packet
.useCustomFormatter
&& Array
.isArray(packet
.header
)) {
474 const createFrontsInJsonMl
= item
=> {
475 if (Array
.isArray(item
)) {
476 return item
.map(i
=> createFrontsInJsonMl(i
));
478 return getAdHocFrontOrPrimitiveGrip(item
, objectFront
);
481 packet
.header
= createFrontsInJsonMl(packet
.header
);
485 registerFront(ObjectFront
);
487 exports
.ObjectFront
= ObjectFront
;
488 exports
.getAdHocFrontOrPrimitiveGrip
= getAdHocFrontOrPrimitiveGrip
;