Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / devtools / client / fronts / object.js
blob13cd4b8151976b430d86ac2f8bbb3e586ea24abb
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/. */
5 "use strict";
7 const { objectSpec } = require("resource://devtools/shared/specs/object.js");
8 const {
9 FrontClassWithSpec,
10 registerFront,
11 } = require("resource://devtools/shared/protocol.js");
12 const {
13 LongStringFront,
14 } = require("resource://devtools/client/fronts/string.js");
16 const SUPPORT_ENUM_ENTRIES_SET = new Set([
17 "CustomStateSet",
18 "FormData",
19 "Headers",
20 "HighlightRegistry",
21 "Map",
22 "MIDIInputMap",
23 "MIDIOutputMap",
24 "Set",
25 "Storage",
26 "URLSearchParams",
27 "WeakMap",
28 "WeakSet",
29 ]);
31 /**
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) {
37 if (!parentFront) {
38 throw new Error("ObjectFront require a parent front");
41 super(conn, targetFront, parentFront);
43 this._grip = data;
44 this.actorID = this._grip.actor;
45 this.valid = true;
47 parentFront.manage(this);
50 form(data) {
51 this.actorID = data.actor;
52 this._grip = data;
55 skipDestroy() {
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;
62 getGrip() {
63 return this._grip;
66 get isFrozen() {
67 return this._grip.frozen;
70 get isSealed() {
71 return this._grip.sealed;
74 get isExtensible() {
75 return this._grip.extensible;
78 /**
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]) => {
94 if (descriptor) {
95 for (const gripKey of gripKeys) {
96 if (descriptor.hasOwnProperty(gripKey)) {
97 result.ownProperties[key][gripKey] = getAdHocFrontOrPrimitiveGrip(
98 descriptor[gripKey],
99 this
107 if (result.safeGetterValues) {
108 Object.entries(result.safeGetterValues).forEach(([key, descriptor]) => {
109 if (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) => {
122 if (descriptor) {
123 for (const gripKey of gripKeys) {
124 if (descriptor.hasOwnProperty(gripKey)) {
125 arr[i][gripKey] = getAdHocFrontOrPrimitiveGrip(
126 descriptor[gripKey],
127 this
135 return result;
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`.
151 * - sort Boolean
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.
163 enumEntries() {
164 if (!SUPPORT_ENUM_ENTRIES_SET.has(this._grip.class)) {
165 console.error(
166 `enumEntries can't be called for "${
167 this._grip.class
168 }" grips. Supported grips are: ${[...SUPPORT_ENUM_ENTRIES_SET].join(
169 ", "
170 )}.`
172 return null;
174 return super.enumEntries();
178 * Request a SymbolIteratorFront instance to enumerate symbols in an object.
180 enumSymbols() {
181 if (this._grip.type !== "object") {
182 console.error("enumSymbols is only valid for objects grips.");
183 return null;
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.
193 getProperty(name) {
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;
208 if (value.return) {
209 response.value.return = getAdHocFrontOrPrimitiveGrip(
210 value.return,
211 this
215 if (value.throw) {
216 response.value.throw = getAdHocFrontOrPrimitiveGrip(value.throw, this);
219 return response;
223 * Get the body of a custom formatted object.
225 async customFormatterBody() {
226 const result = await super.customFormatterBody();
228 if (!result?.customFormatterBody) {
229 return result;
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
243 return result;
247 * Request the prototype of the object.
249 async getPrototype() {
250 const result = await super.prototype();
252 if (!result.prototype) {
253 return result;
256 result.prototype = getAdHocFrontOrPrimitiveGrip(result.prototype, this);
258 return result;
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.");
267 return null;
270 let response, promiseState;
271 try {
272 response = await super.promiseState();
273 promiseState = response.promiseState;
274 } catch (error) {
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 };
280 } else {
281 throw error;
285 const { value, reason } = promiseState;
287 if (value) {
288 promiseState.value = getAdHocFrontOrPrimitiveGrip(value, this);
291 if (reason) {
292 promiseState.reason = getAdHocFrontOrPrimitiveGrip(reason, this);
295 return response;
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.");
304 return null;
307 const response = await super.proxySlots();
308 const { proxyHandler, proxyTarget } = response;
310 if (proxyHandler) {
311 response.proxyHandler = getAdHocFrontOrPrimitiveGrip(proxyHandler, this);
314 if (proxyTarget) {
315 response.proxyTarget = getAdHocFrontOrPrimitiveGrip(proxyTarget, this);
318 return response;
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
328 * type of objects:
329 * - a primitive (string, number, null, false, boolean)
330 * - a long string
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
349 // actorID, unless:
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;
356 if (
357 !isPacketAnObject ||
358 packet.type == "symbol" ||
359 (packet.type !== "mapEntry" &&
360 packet.type !== "highlightRegistryEntry" &&
361 !packet.actor) ||
362 isFront
364 return packet;
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
370 ? parentFront
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);
376 if (existingFront) {
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;
399 if (
400 (type === "mapEntry" || type === "highlightRegistryEntry") &&
401 packet.preview
403 const { key, value } = packet.preview;
404 packet.preview.key = getAdHocFrontOrPrimitiveGrip(
405 key,
406 parentFront,
407 targetFront
409 packet.preview.value = getAdHocFrontOrPrimitiveGrip(
410 value,
411 parentFront,
412 targetFront
414 return packet;
417 const objectFront = new ObjectFront(conn, targetFront, parentFront, packet);
418 createChildFronts(objectFront, packet);
419 return objectFront;
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
425 * values, …).
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(
437 message,
438 objectFront
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) {
458 if (
459 descriptor &&
460 typeof descriptor === "object" &&
461 descriptor.hasOwnProperty(key)
463 packet.ownProperties[name][key] = getAdHocFrontOrPrimitiveGrip(
464 descriptor[key],
465 objectFront
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;