RemoteValue.sys.mjs (30937B)
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 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", 9 dom: "chrome://remote/content/shared/DOM.sys.mjs", 10 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 11 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", 12 Log: "chrome://remote/content/shared/Log.sys.mjs", 13 pprint: "chrome://remote/content/shared/Format.sys.mjs", 14 }); 15 16 ChromeUtils.defineLazyGetter(lazy, "logger", () => 17 lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI) 18 ); 19 20 /** 21 * @typedef {object} IncludeShadowTreeMode 22 */ 23 24 /** 25 * Enum of include shadow tree modes supported by the serialization. 26 * 27 * @readonly 28 * @enum {IncludeShadowTreeMode} 29 */ 30 export const IncludeShadowTreeMode = { 31 All: "all", 32 None: "none", 33 Open: "open", 34 }; 35 36 /** 37 * @typedef {object} OwnershipModel 38 */ 39 40 /** 41 * Enum of ownership models supported by the serialization. 42 * 43 * @readonly 44 * @enum {OwnershipModel} 45 */ 46 export const OwnershipModel = { 47 None: "none", 48 Root: "root", 49 }; 50 51 /** 52 * Extra options for deserializing remote values. 53 * 54 * @typedef {object} ExtraDeserializationOptions 55 * 56 * @property {NodeCache=} nodeCache 57 * The cache containing DOM node references. 58 * @property {Function=} emitScriptMessage 59 * The function to emit "script.message" event. 60 */ 61 62 /** 63 * Extra options for serializing remote values. 64 * 65 * @typedef {object} ExtraSerializationOptions 66 * 67 * @property {NodeCache=} nodeCache 68 * The cache containing DOM node references. 69 * @property {Map<BrowsingContext, Array<string>>} seenNodeIds 70 * Map of browsing contexts to their seen node ids during the current 71 * serialization. 72 */ 73 74 /** 75 * An object which holds the information of how 76 * ECMAScript objects should be serialized. 77 * 78 * @typedef {object} SerializationOptions 79 * 80 * @property {number} [maxDomDepth=0] 81 * Depth of a serialization of DOM Nodes. Defaults to 0. 82 * @property {number} [maxObjectDepth=null] 83 * Depth of a serialization of objects. Defaults to null. 84 * @property {IncludeShadowTreeMode} [includeShadowTree=IncludeShadowTreeMode.None] 85 * Mode of a serialization of shadow dom. Defaults to "none". 86 */ 87 88 const TYPED_ARRAY_CLASSES = [ 89 "Uint8Array", 90 "Uint8ClampedArray", 91 "Uint16Array", 92 "Uint32Array", 93 "Int8Array", 94 "Int16Array", 95 "Int32Array", 96 "Float32Array", 97 "Float64Array", 98 "BigInt64Array", 99 "BigUint64Array", 100 ]; 101 102 /** 103 * Build the serialized RemoteValue. 104 * 105 * @returns {object} 106 * An object with a mandatory `type` property, and optional `handle`, 107 * depending on the OwnershipModel, used for the serialization and 108 * on the value's type. 109 */ 110 function buildSerialized(type, handle = null) { 111 const serialized = { type }; 112 113 if (handle !== null) { 114 serialized.handle = handle; 115 } 116 117 return serialized; 118 } 119 120 /** 121 * Helper to deserialize value list. 122 * 123 * @see https://w3c.github.io/webdriver-bidi/#deserialize-value-list 124 * 125 * @param {Array} serializedValueList 126 * List of serialized values. 127 * @param {Realm} realm 128 * The Realm in which the value is deserialized. 129 * @param {ExtraDeserializationOptions} extraOptions 130 * Extra Remote Value deserialization options. 131 * 132 * @returns {Array} List of deserialized values. 133 * 134 * @throws {InvalidArgumentError} 135 * If <var>serializedValueList</var> is not an array. 136 */ 137 function deserializeValueList(serializedValueList, realm, extraOptions) { 138 lazy.assert.array( 139 serializedValueList, 140 lazy.pprint`Expected "serializedValueList" to be an array, got ${serializedValueList}` 141 ); 142 143 const deserializedValues = []; 144 145 for (const item of serializedValueList) { 146 deserializedValues.push(deserialize(item, realm, extraOptions)); 147 } 148 149 return deserializedValues; 150 } 151 152 /** 153 * Helper to deserialize key-value list. 154 * 155 * @see https://w3c.github.io/webdriver-bidi/#deserialize-key-value-list 156 * 157 * @param {Array} serializedKeyValueList 158 * List of serialized key-value. 159 * @param {Realm} realm 160 * The Realm in which the value is deserialized. 161 * @param {ExtraDeserializationOptions} extraOptions 162 * Extra Remote Value deserialization options. 163 * 164 * @returns {Array} List of deserialized key-value. 165 * 166 * @throws {InvalidArgumentError} 167 * If <var>serializedKeyValueList</var> is not an array or 168 * not an array of key-value arrays. 169 */ 170 function deserializeKeyValueList(serializedKeyValueList, realm, extraOptions) { 171 lazy.assert.array( 172 serializedKeyValueList, 173 lazy.pprint`Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}` 174 ); 175 176 const deserializedKeyValueList = []; 177 178 for (const serializedKeyValue of serializedKeyValueList) { 179 if (!Array.isArray(serializedKeyValue) || serializedKeyValue.length != 2) { 180 throw new lazy.error.InvalidArgumentError( 181 `Expected key-value pair to be an array with 2 elements, got ${serializedKeyValue}` 182 ); 183 } 184 const [serializedKey, serializedValue] = serializedKeyValue; 185 const deserializedKey = 186 typeof serializedKey == "string" 187 ? serializedKey 188 : deserialize(serializedKey, realm, extraOptions); 189 const deserializedValue = deserialize(serializedValue, realm, extraOptions); 190 191 deserializedKeyValueList.push([deserializedKey, deserializedValue]); 192 } 193 194 return deserializedKeyValueList; 195 } 196 197 /** 198 * Deserialize a Node as referenced by the shared unique reference. 199 * 200 * This unique reference can be shared by WebDriver clients with the WebDriver 201 * classic implementation (Marionette) if the reference is for an Element or 202 * ShadowRoot. 203 * 204 * @param {string} sharedRef 205 * Shared unique reference of the Node. 206 * @param {Realm} realm 207 * The Realm in which the value is deserialized. 208 * @param {ExtraDeserializationOptions} extraOptions 209 * Extra Remote Value deserialization options. 210 * 211 * @returns {Node} The deserialized DOM node. 212 */ 213 function deserializeSharedReference(sharedRef, realm, extraOptions) { 214 const { nodeCache } = extraOptions; 215 216 const browsingContext = realm.browsingContext; 217 if (!browsingContext) { 218 throw new lazy.error.NoSuchNodeError("Realm isn't a Window global"); 219 } 220 221 const node = nodeCache.getNode(browsingContext, sharedRef); 222 223 if (node === null) { 224 throw new lazy.error.NoSuchNodeError( 225 `The node with the reference ${sharedRef} is not known` 226 ); 227 } 228 229 // Bug 1819902: Instead of a browsing context check compare the origin 230 const isSameBrowsingContext = sharedRef => { 231 const nodeDetails = nodeCache.getReferenceDetails(sharedRef); 232 233 if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) { 234 // As long as Navigables are not available any cross-group navigation will 235 // cause a swap of the current top-level browsing context. The only unique 236 // identifier in such a case is the browser id the top-level browsing 237 // context actually lives in. 238 return nodeDetails.browserId === browsingContext.browserId; 239 } 240 241 return nodeDetails.browsingContextId === browsingContext.id; 242 }; 243 244 if (!isSameBrowsingContext(sharedRef)) { 245 return null; 246 } 247 248 return node; 249 } 250 251 /** 252 * Deserialize a local value. 253 * 254 * @see https://w3c.github.io/webdriver-bidi/#deserialize-local-value 255 * 256 * @param {object} serializedValue 257 * Value of any type to be deserialized. 258 * @param {Realm} realm 259 * The Realm in which the value is deserialized. 260 * @param {ExtraDeserializationOptions} extraOptions 261 * Extra Remote Value deserialization options. 262 * 263 * @returns {object} Deserialized representation of the value. 264 */ 265 export function deserialize(serializedValue, realm, extraOptions) { 266 const { handle, sharedId, type, value } = serializedValue; 267 268 // With a shared id present deserialize as node reference. 269 if (sharedId !== undefined) { 270 lazy.assert.string( 271 sharedId, 272 lazy.pprint`Expected "sharedId" to be a string, got ${sharedId}` 273 ); 274 275 return deserializeSharedReference(sharedId, realm, extraOptions); 276 } 277 278 // With a handle present deserialize as remote reference. 279 if (handle !== undefined) { 280 lazy.assert.string( 281 handle, 282 lazy.pprint`Expected "handle" to be a string, got ${handle}` 283 ); 284 285 const object = realm.getObjectForHandle(handle); 286 if (!object) { 287 throw new lazy.error.NoSuchHandleError( 288 `Unable to find an object reference for "handle" ${handle}` 289 ); 290 } 291 292 return object; 293 } 294 295 lazy.assert.string( 296 type, 297 lazy.pprint`Expected "type" to be a string, got ${type}` 298 ); 299 300 // Primitive protocol values 301 switch (type) { 302 case "undefined": 303 return undefined; 304 case "null": 305 return null; 306 case "string": 307 lazy.assert.string( 308 value, 309 lazy.pprint`Expected "value" to be a string, got ${value}` 310 ); 311 return value; 312 case "number": 313 // If value is already a number return its value. 314 if (typeof value === "number") { 315 return value; 316 } 317 318 // Otherwise it has to be one of the special strings 319 lazy.assert.in( 320 value, 321 ["NaN", "-0", "Infinity", "-Infinity"], 322 lazy.pprint`Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}` 323 ); 324 return Number(value); 325 case "boolean": 326 lazy.assert.boolean( 327 value, 328 lazy.pprint`Expected "value" to be a boolean, got ${value}` 329 ); 330 return value; 331 case "bigint": 332 lazy.assert.string( 333 value, 334 lazy.pprint`Expected "value" to be a string, got ${value}` 335 ); 336 try { 337 return BigInt(value); 338 } catch (e) { 339 throw new lazy.error.InvalidArgumentError( 340 `Failed to deserialize value as BigInt: ${value}` 341 ); 342 } 343 344 // Script channel 345 case "channel": { 346 const channel = message => 347 extraOptions.emitScriptMessage(realm, value, message); 348 return realm.cloneIntoRealm(channel); 349 } 350 351 // Non-primitive protocol values 352 case "array": { 353 const array = realm.cloneIntoRealm([]); 354 deserializeValueList(value, realm, extraOptions).forEach(v => 355 array.push(v) 356 ); 357 return array; 358 } 359 case "date": 360 // We want to support only Date Time String format, 361 // check if the value follows it. 362 if (!ChromeUtils.isISOStyleDate(value)) { 363 throw new lazy.error.InvalidArgumentError( 364 `Expected "value" for Date to be a Date Time string, got ${value}` 365 ); 366 } 367 368 return realm.cloneIntoRealm(new Date(value)); 369 case "map": { 370 const map = realm.cloneIntoRealm(new Map()); 371 deserializeKeyValueList(value, realm, extraOptions).forEach(([k, v]) => 372 map.set(k, v) 373 ); 374 375 return map; 376 } 377 case "object": { 378 const object = realm.cloneIntoRealm({}); 379 deserializeKeyValueList(value, realm, extraOptions).forEach( 380 ([k, v]) => (object[k] = v) 381 ); 382 return object; 383 } 384 case "regexp": { 385 lazy.assert.object( 386 value, 387 lazy.pprint`Expected "value" for RegExp to be an object, got ${value}` 388 ); 389 const { pattern, flags } = value; 390 lazy.assert.string( 391 pattern, 392 lazy.pprint`Expected "pattern" for RegExp to be a string, got ${pattern}` 393 ); 394 if (flags !== undefined) { 395 lazy.assert.string( 396 flags, 397 lazy.pprint`Expected "flags" for RegExp to be a string, got ${flags}` 398 ); 399 } 400 try { 401 return realm.cloneIntoRealm(new RegExp(pattern, flags)); 402 } catch (e) { 403 throw new lazy.error.InvalidArgumentError( 404 `Failed to deserialize value as RegExp: ${value}` 405 ); 406 } 407 } 408 case "set": { 409 const set = realm.cloneIntoRealm(new Set()); 410 deserializeValueList(value, realm, extraOptions).forEach(v => set.add(v)); 411 return set; 412 } 413 } 414 415 lazy.logger.warn(`Unsupported type for local value ${type}`); 416 return undefined; 417 } 418 419 /** 420 * Helper to retrieve the handle id for a given object, for the provided realm 421 * and ownership type. 422 * 423 * See https://w3c.github.io/webdriver-bidi/#handle-for-an-object 424 * 425 * @param {Realm} realm 426 * The Realm from which comes the value being serialized. 427 * @param {OwnershipModel} ownershipType 428 * The ownership model to use for this serialization. 429 * @param {object} object 430 * The object being serialized. 431 * 432 * @returns {string} The unique handle id for the object. Will be null if the 433 * Ownership type is "none". 434 */ 435 function getHandleForObject(realm, ownershipType, object) { 436 if (ownershipType === OwnershipModel.None) { 437 return null; 438 } 439 return realm.getHandleForObject(object); 440 } 441 442 /** 443 * Gets or creates a new shared unique reference for the DOM node. 444 * 445 * This unique reference can be shared by WebDriver clients with the WebDriver 446 * classic implementation (Marionette) if the reference is for an Element or 447 * ShadowRoot. 448 * 449 * @param {Node} node 450 * Node to create the unique reference for. 451 * @param {ExtraSerializationOptions} extraOptions 452 * Extra Remote Value serialization options. 453 * 454 * @returns {string} 455 * Shared unique reference for the Node. 456 */ 457 function getSharedIdForNode(node, extraOptions) { 458 const { nodeCache, seenNodeIds } = extraOptions; 459 460 if (!Node.isInstance(node)) { 461 return null; 462 } 463 464 const browsingContext = node.ownerGlobal.browsingContext; 465 if (!browsingContext) { 466 return null; 467 } 468 469 return nodeCache.getOrCreateNodeReference(node, seenNodeIds); 470 } 471 472 /** 473 * Helper to serialize an Array-like object. 474 * 475 * @see https://w3c.github.io/webdriver-bidi/#serialize-an-array-like 476 * 477 * @param {string} production 478 * Type of object 479 * @param {string} handleId 480 * The unique id of the <var>value</var>. 481 * @param {boolean} knownObject 482 * Indicates if the <var>value</var> has already been serialized. 483 * @param {object} value 484 * The Array-like object to serialize. 485 * @param {SerializationOptions} serializationOptions 486 * Options which define how ECMAScript objects should be serialized. 487 * @param {OwnershipModel} ownershipType 488 * The ownership model to use for this serialization. 489 * @param {Map} serializationInternalMap 490 * Map of internal ids. 491 * @param {Realm} realm 492 * The Realm from which comes the value being serialized. 493 * @param {ExtraSerializationOptions} extraOptions 494 * Extra Remote Value serialization options. 495 * 496 * @returns {object} Object for serialized values. 497 */ 498 function serializeArrayLike( 499 production, 500 handleId, 501 knownObject, 502 value, 503 serializationOptions, 504 ownershipType, 505 serializationInternalMap, 506 realm, 507 extraOptions 508 ) { 509 const serialized = buildSerialized(production, handleId); 510 setInternalIdsIfNeeded(serializationInternalMap, serialized, value); 511 512 if (!knownObject && serializationOptions.maxObjectDepth !== 0) { 513 serialized.value = serializeList( 514 value, 515 serializationOptions, 516 ownershipType, 517 serializationInternalMap, 518 realm, 519 extraOptions 520 ); 521 } 522 523 return serialized; 524 } 525 526 /** 527 * Helper to serialize as a list. 528 * 529 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-list 530 * 531 * @param {Iterable} iterable 532 * List of values to be serialized. 533 * @param {SerializationOptions} serializationOptions 534 * Options which define how ECMAScript objects should be serialized. 535 * @param {OwnershipModel} ownershipType 536 * The ownership model to use for this serialization. 537 * @param {Map} serializationInternalMap 538 * Map of internal ids. 539 * @param {Realm} realm 540 * The Realm from which comes the value being serialized. 541 * @param {ExtraSerializationOptions} extraOptions 542 * Extra Remote Value serialization options. 543 * 544 * @returns {Array} List of serialized values. 545 */ 546 function serializeList( 547 iterable, 548 serializationOptions, 549 ownershipType, 550 serializationInternalMap, 551 realm, 552 extraOptions 553 ) { 554 const { maxObjectDepth } = serializationOptions; 555 const serialized = []; 556 const childSerializationOptions = { 557 ...serializationOptions, 558 }; 559 if (maxObjectDepth !== null) { 560 childSerializationOptions.maxObjectDepth = maxObjectDepth - 1; 561 } 562 563 for (const item of iterable) { 564 serialized.push( 565 serialize( 566 item, 567 childSerializationOptions, 568 ownershipType, 569 serializationInternalMap, 570 realm, 571 extraOptions 572 ) 573 ); 574 } 575 576 return serialized; 577 } 578 579 /** 580 * Helper to serialize as a mapping. 581 * 582 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-mapping 583 * 584 * @param {Iterable} iterable 585 * List of values to be serialized. 586 * @param {SerializationOptions} serializationOptions 587 * Options which define how ECMAScript objects should be serialized. 588 * @param {OwnershipModel} ownershipType 589 * The ownership model to use for this serialization. 590 * @param {Map} serializationInternalMap 591 * Map of internal ids. 592 * @param {Realm} realm 593 * The Realm from which comes the value being serialized. 594 * @param {ExtraSerializationOptions} extraOptions 595 * Extra Remote Value serialization options. 596 * 597 * @returns {Array} List of serialized values. 598 */ 599 function serializeMapping( 600 iterable, 601 serializationOptions, 602 ownershipType, 603 serializationInternalMap, 604 realm, 605 extraOptions 606 ) { 607 const { maxObjectDepth } = serializationOptions; 608 const serialized = []; 609 const childSerializationOptions = { 610 ...serializationOptions, 611 }; 612 if (maxObjectDepth !== null) { 613 childSerializationOptions.maxObjectDepth = maxObjectDepth - 1; 614 } 615 616 for (const [key, item] of iterable) { 617 const serializedKey = 618 typeof key == "string" 619 ? key 620 : serialize( 621 key, 622 childSerializationOptions, 623 ownershipType, 624 serializationInternalMap, 625 realm, 626 extraOptions 627 ); 628 const serializedValue = serialize( 629 item, 630 childSerializationOptions, 631 ownershipType, 632 serializationInternalMap, 633 realm, 634 extraOptions 635 ); 636 637 serialized.push([serializedKey, serializedValue]); 638 } 639 640 return serialized; 641 } 642 643 /** 644 * Helper to serialize as a Node. 645 * 646 * @param {Node} node 647 * Node to be serialized. 648 * @param {SerializationOptions} serializationOptions 649 * Options which define how ECMAScript objects should be serialized. 650 * @param {OwnershipModel} ownershipType 651 * The ownership model to use for this serialization. 652 * @param {Map} serializationInternalMap 653 * Map of internal ids. 654 * @param {Realm} realm 655 * The Realm from which comes the value being serialized. 656 * @param {ExtraSerializationOptions} extraOptions 657 * Extra Remote Value serialization options. 658 * 659 * @returns {object} Serialized value. 660 */ 661 function serializeNode( 662 node, 663 serializationOptions, 664 ownershipType, 665 serializationInternalMap, 666 realm, 667 extraOptions 668 ) { 669 const { includeShadowTree, maxDomDepth } = serializationOptions; 670 const isAttribute = Attr.isInstance(node); 671 const isElement = Element.isInstance(node); 672 673 const serialized = { 674 nodeType: node.nodeType, 675 }; 676 677 if (node.nodeValue !== null) { 678 serialized.nodeValue = node.nodeValue; 679 } 680 681 if (isElement || isAttribute) { 682 serialized.localName = node.localName; 683 serialized.namespaceURI = node.namespaceURI; 684 } 685 686 serialized.childNodeCount = node.childNodes.length; 687 if ( 688 maxDomDepth !== 0 && 689 (!lazy.dom.isShadowRoot(node) || 690 (includeShadowTree === IncludeShadowTreeMode.Open && 691 node.mode === "open") || 692 includeShadowTree === IncludeShadowTreeMode.All) 693 ) { 694 const children = []; 695 const childSerializationOptions = { 696 ...serializationOptions, 697 }; 698 if (maxDomDepth !== null) { 699 childSerializationOptions.maxDomDepth = maxDomDepth - 1; 700 } 701 for (const child of node.childNodes) { 702 children.push( 703 serialize( 704 child, 705 childSerializationOptions, 706 ownershipType, 707 serializationInternalMap, 708 realm, 709 extraOptions 710 ) 711 ); 712 } 713 714 serialized.children = children; 715 } 716 717 if (isElement) { 718 serialized.attributes = [...node.attributes].reduce((map, attr) => { 719 map[attr.name] = attr.value; 720 return map; 721 }, {}); 722 723 const shadowRoot = node.openOrClosedShadowRoot; 724 serialized.shadowRoot = null; 725 if (shadowRoot !== null) { 726 serialized.shadowRoot = serialize( 727 shadowRoot, 728 serializationOptions, 729 ownershipType, 730 serializationInternalMap, 731 realm, 732 extraOptions 733 ); 734 } 735 } 736 737 if (lazy.dom.isShadowRoot(node)) { 738 serialized.mode = node.mode; 739 } 740 741 return serialized; 742 } 743 744 /** 745 * Serialize a value as a remote value. 746 * 747 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-remote-value 748 * 749 * @param {object} value 750 * Value of any type to be serialized. 751 * @param {SerializationOptions} serializationOptions 752 * Options which define how ECMAScript objects should be serialized. 753 * @param {OwnershipModel} ownershipType 754 * The ownership model to use for this serialization. 755 * @param {Map} serializationInternalMap 756 * Map of internal ids. 757 * @param {Realm} realm 758 * The Realm from which comes the value being serialized. 759 * @param {ExtraSerializationOptions} extraOptions 760 * Extra Remote Value serialization options. 761 * 762 * @returns {object} Serialized representation of the value. 763 */ 764 export function serialize( 765 value, 766 serializationOptions, 767 ownershipType, 768 serializationInternalMap, 769 realm, 770 extraOptions 771 ) { 772 const { maxObjectDepth } = serializationOptions; 773 const type = typeof value; 774 775 // Primitive protocol values 776 if (type == "undefined") { 777 return { type }; 778 } else if (Object.is(value, null)) { 779 return { type: "null" }; 780 } else if (Object.is(value, NaN)) { 781 return { type: "number", value: "NaN" }; 782 } else if (Object.is(value, -0)) { 783 return { type: "number", value: "-0" }; 784 } else if (Object.is(value, Infinity)) { 785 return { type: "number", value: "Infinity" }; 786 } else if (Object.is(value, -Infinity)) { 787 return { type: "number", value: "-Infinity" }; 788 } else if (type == "bigint") { 789 return { type, value: value.toString() }; 790 } else if (["boolean", "number", "string"].includes(type)) { 791 return { type, value }; 792 } 793 794 const handleId = getHandleForObject(realm, ownershipType, value); 795 const knownObject = serializationInternalMap.has(value); 796 797 // Set the OwnershipModel to use for all complex object serializations. 798 ownershipType = OwnershipModel.None; 799 800 // Remote values 801 802 // symbols are primitive JS values which can only be serialized 803 // as remote values. 804 if (type == "symbol") { 805 return buildSerialized("symbol", handleId); 806 } 807 808 // All other remote values are non-primitives and their 809 // className can be extracted with ChromeUtils.getClassName 810 const className = ChromeUtils.getClassName(value); 811 if (["Array", "HTMLCollection", "NodeList"].includes(className)) { 812 return serializeArrayLike( 813 className.toLowerCase(), 814 handleId, 815 knownObject, 816 value, 817 serializationOptions, 818 ownershipType, 819 serializationInternalMap, 820 realm, 821 extraOptions 822 ); 823 } else if (className == "RegExp") { 824 const serialized = buildSerialized("regexp", handleId); 825 serialized.value = { pattern: value.source, flags: value.flags }; 826 return serialized; 827 } else if (className == "Date") { 828 const serialized = buildSerialized("date", handleId); 829 serialized.value = value.toISOString(); 830 return serialized; 831 } else if (className == "Map") { 832 const serialized = buildSerialized("map", handleId); 833 setInternalIdsIfNeeded(serializationInternalMap, serialized, value); 834 835 if (!knownObject && maxObjectDepth !== 0) { 836 serialized.value = serializeMapping( 837 value.entries(), 838 serializationOptions, 839 ownershipType, 840 serializationInternalMap, 841 realm, 842 extraOptions 843 ); 844 } 845 return serialized; 846 } else if (className == "Set") { 847 const serialized = buildSerialized("set", handleId); 848 setInternalIdsIfNeeded(serializationInternalMap, serialized, value); 849 850 if (!knownObject && maxObjectDepth !== 0) { 851 serialized.value = serializeList( 852 value.values(), 853 serializationOptions, 854 ownershipType, 855 serializationInternalMap, 856 realm, 857 extraOptions 858 ); 859 } 860 return serialized; 861 } else if ( 862 ["ArrayBuffer", "Function", "Promise", "WeakMap", "WeakSet"].includes( 863 className 864 ) 865 ) { 866 return buildSerialized(className.toLowerCase(), handleId); 867 } else if (className.includes("Generator")) { 868 return buildSerialized("generator", handleId); 869 } else if (lazy.error.isError(value)) { 870 return buildSerialized("error", handleId); 871 } else if (Cu.isProxy(value)) { 872 return buildSerialized("proxy", handleId); 873 } else if (TYPED_ARRAY_CLASSES.includes(className)) { 874 return buildSerialized("typedarray", handleId); 875 } else if (Node.isInstance(value)) { 876 const serialized = buildSerialized("node", handleId); 877 878 value = Cu.unwaiveXrays(value); 879 880 // Get or create the shared id for WebDriver classic compat from the node. 881 const sharedId = getSharedIdForNode(value, extraOptions); 882 if (sharedId !== null) { 883 serialized.sharedId = sharedId; 884 } 885 886 setInternalIdsIfNeeded(serializationInternalMap, serialized, value); 887 888 if (!knownObject) { 889 serialized.value = serializeNode( 890 value, 891 serializationOptions, 892 ownershipType, 893 serializationInternalMap, 894 realm, 895 extraOptions 896 ); 897 } 898 899 return serialized; 900 } else if (Window.isInstance(value)) { 901 const serialized = buildSerialized("window", handleId); 902 const window = Cu.unwaiveXrays(value); 903 904 if (window.browsingContext.parent == null) { 905 serialized.value = { 906 context: window.browsingContext.browserId.toString(), 907 isTopBrowsingContext: true, 908 }; 909 } else { 910 serialized.value = { 911 context: window.browsingContext.id.toString(), 912 }; 913 } 914 915 return serialized; 916 } else if (ChromeUtils.isDOMObject(value)) { 917 const serialized = buildSerialized("object", handleId); 918 return serialized; 919 } 920 921 // Otherwise serialize the JavaScript object as generic object. 922 const serialized = buildSerialized("object", handleId); 923 setInternalIdsIfNeeded(serializationInternalMap, serialized, value); 924 925 if (!knownObject && maxObjectDepth !== 0) { 926 serialized.value = serializeMapping( 927 Object.entries(value), 928 serializationOptions, 929 ownershipType, 930 serializationInternalMap, 931 realm, 932 extraOptions 933 ); 934 } 935 return serialized; 936 } 937 938 /** 939 * Set default serialization options. 940 * 941 * @param {SerializationOptions} options 942 * Options which define how ECMAScript objects should be serialized. 943 * @returns {SerializationOptions} 944 * Serialiation options with default value added. 945 */ 946 export function setDefaultSerializationOptions(options = {}) { 947 const serializationOptions = { ...options }; 948 if (!("maxDomDepth" in serializationOptions)) { 949 serializationOptions.maxDomDepth = 0; 950 } 951 if (!("maxObjectDepth" in serializationOptions)) { 952 serializationOptions.maxObjectDepth = null; 953 } 954 if (!("includeShadowTree" in serializationOptions)) { 955 serializationOptions.includeShadowTree = IncludeShadowTreeMode.None; 956 } 957 958 return serializationOptions; 959 } 960 961 /** 962 * Set default values and assert if serialization options have 963 * expected types. 964 * 965 * @param {SerializationOptions} options 966 * Options which define how ECMAScript objects should be serialized. 967 * @returns {SerializationOptions} 968 * Serialiation options with default value added. 969 */ 970 export function setDefaultAndAssertSerializationOptions(options = {}) { 971 lazy.assert.object( 972 options, 973 lazy.pprint`Expected "options" to be an object, got ${options}` 974 ); 975 976 const serializationOptions = setDefaultSerializationOptions(options); 977 978 const { includeShadowTree, maxDomDepth, maxObjectDepth } = 979 serializationOptions; 980 981 if (maxDomDepth !== null) { 982 lazy.assert.positiveInteger( 983 maxDomDepth, 984 lazy.pprint`Expected "maxDomDepth" to be a positive integer or null, got ${maxDomDepth}` 985 ); 986 } 987 if (maxObjectDepth !== null) { 988 lazy.assert.positiveInteger( 989 maxObjectDepth, 990 lazy.pprint`Expected "maxObjectDepth" to be a positive integer or null, got ${maxObjectDepth}` 991 ); 992 } 993 const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode); 994 lazy.assert.that( 995 includeShadowTree => 996 includeShadowTreeModesValues.includes(includeShadowTree), 997 `Expected "includeShadowTree" to be one of ${includeShadowTreeModesValues}, ` + 998 lazy.pprint`got ${includeShadowTree}` 999 )(includeShadowTree); 1000 1001 return serializationOptions; 1002 } 1003 1004 /** 1005 * Set the internalId property of a provided serialized RemoteValue, 1006 * and potentially of a previously created serialized RemoteValue, 1007 * corresponding to the same provided object. 1008 * 1009 * @see https://w3c.github.io/webdriver-bidi/#set-internal-ids-if-needed 1010 * 1011 * @param {Map} serializationInternalMap 1012 * Map of objects to remote values. 1013 * @param {object} remoteValue 1014 * A serialized RemoteValue for the provided object. 1015 * @param {object} object 1016 * Object of any type to be serialized. 1017 */ 1018 function setInternalIdsIfNeeded(serializationInternalMap, remoteValue, object) { 1019 if (!serializationInternalMap.has(object)) { 1020 // If the object was not tracked yet in the current serialization, add 1021 // a new entry in the serialization internal map. An internal id will only 1022 // be generated if the same object is encountered again. 1023 serializationInternalMap.set(object, remoteValue); 1024 } else { 1025 // This is at least the second time this object is encountered, retrieve the 1026 // original remote value stored for this object. 1027 const previousRemoteValue = serializationInternalMap.get(object); 1028 1029 if (!previousRemoteValue.internalId) { 1030 // If the original remote value has no internal id yet, generate a uuid 1031 // and update the internalId of the original remote value with it. 1032 previousRemoteValue.internalId = lazy.generateUUID(); 1033 } 1034 1035 // Copy the internalId of the original remote value to the new remote value. 1036 remoteValue.internalId = previousRemoteValue.internalId; 1037 } 1038 } 1039 1040 /** 1041 * Safely stringify a value. 1042 * 1043 * @param {object} obj 1044 * Value of any type to be stringified. 1045 * 1046 * @returns {string} String representation of the value. 1047 */ 1048 export function stringify(obj) { 1049 let text; 1050 try { 1051 text = 1052 obj !== null && typeof obj === "object" ? obj.toString() : String(obj); 1053 } catch (e) { 1054 // The error-case will also be handled in `finally {}`. 1055 } finally { 1056 if (typeof text != "string") { 1057 text = Object.prototype.toString.apply(obj); 1058 } 1059 } 1060 1061 return text; 1062 }