browser_RemoteValue.js (27057B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { Realm } = ChromeUtils.importESModule( 7 "chrome://remote/content/shared/Realm.sys.mjs" 8 ); 9 const { deserialize, serialize, setDefaultSerializationOptions, stringify } = 10 ChromeUtils.importESModule( 11 "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs" 12 ); 13 14 const PRIMITIVE_TYPES = [ 15 { value: undefined, serialized: { type: "undefined" } }, 16 { value: null, serialized: { type: "null" } }, 17 { value: "foo", serialized: { type: "string", value: "foo" } }, 18 { value: Number.NaN, serialized: { type: "number", value: "NaN" } }, 19 { value: -0, serialized: { type: "number", value: "-0" } }, 20 { 21 value: Number.POSITIVE_INFINITY, 22 serialized: { type: "number", value: "Infinity" }, 23 }, 24 { 25 value: Number.NEGATIVE_INFINITY, 26 serialized: { type: "number", value: "-Infinity" }, 27 }, 28 { value: 42, serialized: { type: "number", value: 42 } }, 29 { value: false, serialized: { type: "boolean", value: false } }, 30 { value: 42n, serialized: { type: "bigint", value: "42" } }, 31 ]; 32 33 const REMOTE_SIMPLE_VALUES = [ 34 { 35 value: new RegExp(/foo/), 36 serialized: { 37 type: "regexp", 38 value: { 39 pattern: "foo", 40 flags: "", 41 }, 42 }, 43 deserializable: true, 44 }, 45 { 46 value: new RegExp(/foo/g), 47 serialized: { 48 type: "regexp", 49 value: { 50 pattern: "foo", 51 flags: "g", 52 }, 53 }, 54 deserializable: true, 55 }, 56 { 57 value: new Date(1654004849000), 58 serialized: { 59 type: "date", 60 value: "2022-05-31T13:47:29.000Z", 61 }, 62 deserializable: true, 63 }, 64 ]; 65 66 const REMOTE_COMPLEX_VALUES = [ 67 { value: Symbol("foo"), serialized: { type: "symbol" } }, 68 { 69 value: [1], 70 serialized: { 71 type: "array", 72 value: [{ type: "number", value: 1 }], 73 }, 74 }, 75 { 76 value: [1], 77 serializationOptions: { 78 maxObjectDepth: 0, 79 }, 80 serialized: { 81 type: "array", 82 }, 83 }, 84 { 85 value: [1, "2", true, new RegExp(/foo/g)], 86 serializationOptions: { 87 maxObjectDepth: 1, 88 }, 89 serialized: { 90 type: "array", 91 value: [ 92 { type: "number", value: 1 }, 93 { type: "string", value: "2" }, 94 { type: "boolean", value: true }, 95 { 96 type: "regexp", 97 value: { 98 pattern: "foo", 99 flags: "g", 100 }, 101 }, 102 ], 103 }, 104 deserializable: true, 105 }, 106 { 107 value: [1, [3, "4"]], 108 serializationOptions: { 109 maxObjectDepth: 1, 110 }, 111 serialized: { 112 type: "array", 113 value: [{ type: "number", value: 1 }, { type: "array" }], 114 }, 115 }, 116 { 117 value: [1, [3, "4"]], 118 serializationOptions: { 119 maxObjectDepth: 2, 120 }, 121 serialized: { 122 type: "array", 123 value: [ 124 { type: "number", value: 1 }, 125 { 126 type: "array", 127 value: [ 128 { type: "number", value: 3 }, 129 { type: "string", value: "4" }, 130 ], 131 }, 132 ], 133 }, 134 deserializable: true, 135 }, 136 { 137 value: new Map(), 138 serializationOptions: { 139 maxObjectDepth: 1, 140 }, 141 serialized: { 142 type: "map", 143 value: [], 144 }, 145 deserializable: true, 146 }, 147 { 148 value: new Map([]), 149 serializationOptions: { 150 maxObjectDepth: 1, 151 }, 152 serialized: { 153 type: "map", 154 value: [], 155 }, 156 deserializable: true, 157 }, 158 { 159 value: new Map([ 160 [1, 2], 161 ["2", "3"], 162 [true, false], 163 ]), 164 serialized: { 165 type: "map", 166 value: [ 167 [ 168 { type: "number", value: 1 }, 169 { type: "number", value: 2 }, 170 ], 171 ["2", { type: "string", value: "3" }], 172 [ 173 { type: "boolean", value: true }, 174 { type: "boolean", value: false }, 175 ], 176 ], 177 }, 178 }, 179 { 180 value: new Map([ 181 [1, 2], 182 ["2", "3"], 183 [true, false], 184 ]), 185 serializationOptions: { 186 maxObjectDepth: 0, 187 }, 188 serialized: { 189 type: "map", 190 }, 191 }, 192 { 193 value: new Map([ 194 [1, 2], 195 ["2", "3"], 196 [true, false], 197 ]), 198 serializationOptions: { 199 maxObjectDepth: 1, 200 }, 201 serialized: { 202 type: "map", 203 value: [ 204 [ 205 { type: "number", value: 1 }, 206 { type: "number", value: 2 }, 207 ], 208 ["2", { type: "string", value: "3" }], 209 [ 210 { type: "boolean", value: true }, 211 { type: "boolean", value: false }, 212 ], 213 ], 214 }, 215 deserializable: true, 216 }, 217 { 218 value: new Set(), 219 serializationOptions: { 220 maxObjectDepth: 1, 221 }, 222 serialized: { 223 type: "set", 224 value: [], 225 }, 226 deserializable: true, 227 }, 228 { 229 value: new Set([]), 230 serializationOptions: { 231 maxObjectDepth: 1, 232 }, 233 serialized: { 234 type: "set", 235 value: [], 236 }, 237 deserializable: true, 238 }, 239 { 240 value: new Set([1, "2", true]), 241 serialized: { 242 type: "set", 243 value: [ 244 { type: "number", value: 1 }, 245 { type: "string", value: "2" }, 246 { type: "boolean", value: true }, 247 ], 248 }, 249 }, 250 { 251 value: new Set([1, "2", true]), 252 serializationOptions: { 253 maxObjectDepth: 0, 254 }, 255 serialized: { 256 type: "set", 257 }, 258 }, 259 { 260 value: new Set([1, "2", true]), 261 serializationOptions: { 262 maxObjectDepth: 1, 263 }, 264 serialized: { 265 type: "set", 266 value: [ 267 { type: "number", value: 1 }, 268 { type: "string", value: "2" }, 269 { type: "boolean", value: true }, 270 ], 271 }, 272 deserializable: true, 273 }, 274 { value: new WeakMap([[{}, 1]]), serialized: { type: "weakmap" } }, 275 { value: new WeakSet([{}]), serialized: { type: "weakset" } }, 276 { 277 value: (function* () { 278 yield "a"; 279 })(), 280 serialized: { type: "generator" }, 281 }, 282 { 283 value: (async function* () { 284 yield await Promise.resolve(1); 285 })(), 286 serialized: { type: "generator" }, 287 }, 288 { value: new Error("error message"), serialized: { type: "error" } }, 289 { 290 value: new SyntaxError("syntax error message"), 291 serialized: { type: "error" }, 292 }, 293 { 294 value: new TypeError("type error message"), 295 serialized: { type: "error" }, 296 }, 297 { value: new Proxy({}, {}), serialized: { type: "proxy" } }, 298 { value: new Promise(() => true), serialized: { type: "promise" } }, 299 { value: new Int8Array(), serialized: { type: "typedarray" } }, 300 { value: new ArrayBuffer(), serialized: { type: "arraybuffer" } }, 301 { value: new URL("https://example.com"), serialized: { type: "object" } }, 302 { value: () => true, serialized: { type: "function" } }, 303 { value() {}, serialized: { type: "function" } }, 304 { 305 value: {}, 306 serializationOptions: { 307 maxObjectDepth: 1, 308 }, 309 serialized: { 310 type: "object", 311 value: [], 312 }, 313 deserializable: true, 314 }, 315 { 316 value: { 317 1: 1, 318 2: "2", 319 foo: true, 320 }, 321 serialized: { 322 type: "object", 323 value: [ 324 ["1", { type: "number", value: 1 }], 325 ["2", { type: "string", value: "2" }], 326 ["foo", { type: "boolean", value: true }], 327 ], 328 }, 329 }, 330 { 331 value: { 332 1: 1, 333 2: "2", 334 foo: true, 335 }, 336 serializationOptions: { 337 maxObjectDepth: 0, 338 }, 339 serialized: { 340 type: "object", 341 }, 342 }, 343 { 344 value: { 345 1: 1, 346 2: "2", 347 foo: true, 348 }, 349 serializationOptions: { 350 maxObjectDepth: 1, 351 }, 352 serialized: { 353 type: "object", 354 value: [ 355 ["1", { type: "number", value: 1 }], 356 ["2", { type: "string", value: "2" }], 357 ["foo", { type: "boolean", value: true }], 358 ], 359 }, 360 deserializable: true, 361 }, 362 { 363 value: { 364 1: 1, 365 2: "2", 366 3: { 367 bar: "foo", 368 }, 369 foo: true, 370 }, 371 serializationOptions: { 372 maxObjectDepth: 2, 373 }, 374 serialized: { 375 type: "object", 376 value: [ 377 ["1", { type: "number", value: 1 }], 378 ["2", { type: "string", value: "2" }], 379 [ 380 "3", 381 { 382 type: "object", 383 value: [["bar", { type: "string", value: "foo" }]], 384 }, 385 ], 386 ["foo", { type: "boolean", value: true }], 387 ], 388 }, 389 deserializable: true, 390 }, 391 ]; 392 393 add_task(function test_deserializePrimitiveTypes() { 394 const realm = new Realm(); 395 396 for (const type of PRIMITIVE_TYPES) { 397 const { value: expectedValue, serialized } = type; 398 399 info(`Checking '${serialized.type}'`); 400 const value = deserialize(serialized, realm, {}); 401 402 if (serialized.value == "NaN") { 403 ok(Number.isNaN(value), `Got expected value for ${serialized}`); 404 } else { 405 Assert.strictEqual( 406 value, 407 expectedValue, 408 `Got expected value for ${serialized}` 409 ); 410 } 411 } 412 }); 413 414 add_task(function test_deserializeDateLocalValue() { 415 const realm = new Realm(); 416 417 const validaDateStrings = [ 418 "2009", 419 "2009-05", 420 "2009-05-19", 421 "2022-02-29", 422 "2009T15:00", 423 "2009-05T15:00", 424 "2022-06-31T15:00", 425 "2009-05-19T15:00", 426 "2009-05-19T15:00:15", 427 "2009-05-19T15:00-00:00", 428 "2009-05-19T15:00:15.452", 429 "2009-05-19T15:00:15.452Z", 430 "2009-05-19T15:00:15.452+02:00", 431 "2009-05-19T15:00:15.452-02:00", 432 "-271821-04-20T00:00:00Z", 433 "+000000-01-01T00:00:00Z", 434 ]; 435 for (const dateString of validaDateStrings) { 436 info(`Checking '${dateString}'`); 437 const value = deserialize({ type: "date", value: dateString }, realm, {}); 438 439 Assert.equal( 440 value.getTime(), 441 new Date(dateString).getTime(), 442 `Got expected value for ${dateString}` 443 ); 444 } 445 }); 446 447 add_task(function test_deserializeLocalValues() { 448 const realm = new Realm(); 449 450 for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) { 451 const { value: expectedValue, serialized, deserializable } = type; 452 453 // Skip non deserializable cases 454 if (!deserializable) { 455 continue; 456 } 457 458 info(`Checking '${serialized.type}'`); 459 const value = deserialize(serialized, realm, {}); 460 assertLocalValue(serialized.type, value, expectedValue); 461 } 462 }); 463 464 add_task(async function test_deserializeLocalValuesInWindowRealm() { 465 for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) { 466 const { value: expectedValue, serialized, deserializable } = type; 467 468 // Skip non deserializable cases 469 if (!deserializable) { 470 continue; 471 } 472 473 const value = await deserializeInWindowRealm(serialized); 474 assertLocalValue(serialized.type, value, expectedValue); 475 } 476 }); 477 478 add_task(async function test_deserializeChannel() { 479 const realm = new Realm(); 480 const channel = { 481 type: "channel", 482 value: { channel: "channel_name" }, 483 }; 484 const deserializationOptions = { 485 emitScriptMessage: (realm, channelProperties, message) => message, 486 }; 487 488 info(`Checking 'channel'`); 489 const value = deserialize(channel, realm, deserializationOptions, {}); 490 Assert.equal( 491 Object.prototype.toString.call(value), 492 "[object Function]", 493 "Got expected type Function" 494 ); 495 Assert.equal(value("foo"), "foo", "Got expected result"); 496 }); 497 498 add_task(function test_deserializeLocalValuesByHandle() { 499 // Create two realms, realm1 will be used to serialize values, while realm2 500 // will be used as a reference empty realm without any object reference. 501 const realm1 = new Realm(); 502 const realm2 = new Realm(); 503 504 for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) { 505 const { value: expectedValue, serialized } = type; 506 507 // No need to skip non-deserializable cases here. 508 509 info(`Checking '${serialized.type}'`); 510 // Serialize the value once to get a handle. 511 const serializedValue = serialize( 512 expectedValue, 513 { maxObjectDepth: 0 }, 514 "root", 515 new Map(), 516 realm1, 517 {} 518 ); 519 520 // Create a remote reference containing only the handle. 521 // `deserialize` should not need any other property. 522 const remoteReference = { handle: serializedValue.handle }; 523 524 // Check that the remote reference can be deserialized in realm1. 525 const value = deserialize(remoteReference, realm1, {}); 526 assertLocalValue(serialized.type, value, expectedValue); 527 528 Assert.throws( 529 () => deserialize(remoteReference, realm2, {}), 530 /NoSuchHandleError:/, 531 `Got expected error when using the wrong realm for deserialize` 532 ); 533 534 realm1.removeObjectHandle(serializedValue.handle); 535 Assert.throws( 536 () => deserialize(remoteReference, realm1, {}), 537 /NoSuchHandleError:/, 538 `Got expected error when after deleting the object handle` 539 ); 540 } 541 }); 542 543 add_task(function test_deserializeHandleInvalidTypes() { 544 const realm = new Realm(); 545 546 for (const invalidType of [false, 42, {}, []]) { 547 info(`Checking type: '${invalidType}'`); 548 549 Assert.throws( 550 () => deserialize({ type: "object", handle: invalidType }, realm, {}), 551 /InvalidArgumentError:/, 552 `Got expected error for type ${invalidType}` 553 ); 554 } 555 }); 556 557 add_task(function test_deserializePrimitiveTypesInvalidValues() { 558 const realm = new Realm(); 559 560 const invalidValues = [ 561 { type: "bigint", values: [undefined, null, false, "foo", [], {}] }, 562 { type: "boolean", values: [undefined, null, 42, "foo", [], {}] }, 563 { 564 type: "number", 565 values: [undefined, null, false, "43", [], {}], 566 }, 567 { type: "string", values: [undefined, null, false, 42, [], {}] }, 568 ]; 569 570 for (const invalidValue of invalidValues) { 571 const { type, values } = invalidValue; 572 573 for (const value of values) { 574 info(`Checking '${type}' with value ${value}`); 575 576 Assert.throws( 577 () => deserialize({ type, value }, realm, {}), 578 /InvalidArgument/, 579 `Got expected error for type ${type} and value ${value}` 580 ); 581 } 582 } 583 }); 584 585 add_task(function test_deserializeDateLocalValueInvalidValues() { 586 const realm = new Realm(); 587 588 const invalidaDateStrings = [ 589 "10", 590 "20009", 591 "+20009", 592 "2009-", 593 "2009-0", 594 "2009-15", 595 "2009-02-1", 596 "2009-02-50", 597 "15:00", 598 "T15:00", 599 "9-05-19T15:00", 600 "2009-5-19T15:00", 601 "2009-05-1T15:00", 602 "2009-02-10T15", 603 "2009-05-19T15:", 604 "2009-05-19T1:00", 605 "2009-05-19T10:1", 606 "2009-05-19T60:00", 607 "2009-05-19T15:70", 608 "2009-05-19T15:00.25", 609 "2009-05-19+10:00", 610 "2009-05-19Z", 611 "2009-05-19 15:00", 612 "2009-05-19t15:00Z", 613 "2009-05-19T15:00z", 614 "2009-05-19T15:00+01", 615 "2009-05-19T10:10+1:00", 616 "2009-05-19T10:10+01:1", 617 "2009-05-19T15:00+75:00", 618 "2009-05-19T15:00+02:80", 619 "02009-05-19T15:00", 620 ]; 621 for (const dateString of invalidaDateStrings) { 622 info(`Checking '${dateString}'`); 623 624 Assert.throws( 625 () => deserialize({ type: "date", value: dateString }, realm, {}), 626 /InvalidArgumentError:/, 627 `Got expected error for date string: ${dateString}` 628 ); 629 } 630 }); 631 632 add_task(function test_deserializeLocalValuesInvalidType() { 633 const realm = new Realm(); 634 635 const invalidTypes = [undefined, null, false, 42, {}]; 636 637 for (const invalidType of invalidTypes) { 638 info(`Checking type: '${invalidType}'`); 639 640 Assert.throws( 641 () => deserialize({ type: invalidType }, realm, {}), 642 /InvalidArgumentError:/, 643 `Got expected error for type ${invalidType}` 644 ); 645 646 Assert.throws( 647 () => 648 deserialize( 649 { 650 type: "array", 651 value: [{ type: invalidType }], 652 }, 653 realm, 654 {} 655 ), 656 /InvalidArgumentError:/, 657 `Got expected error for nested type ${invalidType}` 658 ); 659 } 660 }); 661 662 add_task(function test_deserializeLocalValuesInvalidValues() { 663 const realm = new Realm(); 664 665 const invalidValues = [ 666 { type: "array", values: [undefined, null, false, 42, "foo", {}] }, 667 { 668 type: "regexp", 669 values: [ 670 undefined, 671 null, 672 false, 673 "foo", 674 42, 675 [], 676 {}, 677 { pattern: null }, 678 { pattern: 1 }, 679 { pattern: true }, 680 { pattern: "foo", flags: null }, 681 { pattern: "foo", flags: 1 }, 682 { pattern: "foo", flags: false }, 683 { pattern: "foo", flags: "foo" }, 684 ], 685 }, 686 { 687 type: "date", 688 values: [ 689 undefined, 690 null, 691 false, 692 "foo", 693 "05 October 2011 14:48 UTC", 694 "Tue Jun 14 2022 10:46:50 GMT+0200!", 695 42, 696 [], 697 {}, 698 ], 699 }, 700 { 701 type: "map", 702 values: [ 703 undefined, 704 null, 705 false, 706 "foo", 707 42, 708 ["1"], 709 [[]], 710 [["1"]], 711 [{ 1: "2" }], 712 {}, 713 ], 714 }, 715 { 716 type: "set", 717 values: [undefined, null, false, "foo", 42, {}], 718 }, 719 { 720 type: "object", 721 values: [ 722 undefined, 723 null, 724 false, 725 "foo", 726 42, 727 {}, 728 ["1"], 729 [[]], 730 [["1"]], 731 [{ 1: "2" }], 732 [ 733 [ 734 { type: "number", value: "1" }, 735 { type: "number", value: "2" }, 736 ], 737 ], 738 [ 739 [ 740 { type: "object", value: [] }, 741 { type: "number", value: "1" }, 742 ], 743 ], 744 [ 745 [ 746 { 747 type: "regexp", 748 value: { 749 pattern: "foo", 750 }, 751 }, 752 { type: "number", value: "1" }, 753 ], 754 ], 755 ], 756 }, 757 ]; 758 759 for (const invalidValue of invalidValues) { 760 const { type, values } = invalidValue; 761 762 for (const value of values) { 763 info(`Checking '${type}' with value ${value}`); 764 765 Assert.throws( 766 () => deserialize({ type, value }, realm, {}), 767 /InvalidArgumentError:/, 768 `Got expected error for type ${type} and value ${value}` 769 ); 770 } 771 } 772 }); 773 774 add_task(function test_serializePrimitiveTypes() { 775 const realm = new Realm(); 776 777 for (const type of PRIMITIVE_TYPES) { 778 const { value, serialized } = type; 779 const defaultSerializationOptions = setDefaultSerializationOptions(); 780 781 const serializationInternalMap = new Map(); 782 const serializedValue = serialize( 783 value, 784 defaultSerializationOptions, 785 "none", 786 serializationInternalMap, 787 realm, 788 {} 789 ); 790 assertInternalIds(serializationInternalMap, 0); 791 Assert.deepEqual(serialized, serializedValue, "Got expected structure"); 792 793 // For primitive values, the serialization with ownershipType=root should 794 // be exactly identical to the one with ownershipType=none. 795 const serializationInternalMapWithRoot = new Map(); 796 const serializedWithRoot = serialize( 797 value, 798 defaultSerializationOptions, 799 "root", 800 serializationInternalMapWithRoot, 801 realm, 802 {} 803 ); 804 assertInternalIds(serializationInternalMapWithRoot, 0); 805 Assert.deepEqual(serialized, serializedWithRoot, "Got expected structure"); 806 } 807 }); 808 809 add_task(function test_serializeRemoteSimpleValues() { 810 const realm = new Realm(); 811 812 for (const type of REMOTE_SIMPLE_VALUES) { 813 const { value, serialized } = type; 814 const defaultSerializationOptions = setDefaultSerializationOptions(); 815 816 info(`Checking '${serialized.type}' with none ownershipType`); 817 const serializationInternalMapWithNone = new Map(); 818 const serializedValue = serialize( 819 value, 820 defaultSerializationOptions, 821 "none", 822 serializationInternalMapWithNone, 823 realm, 824 {} 825 ); 826 827 assertInternalIds(serializationInternalMapWithNone, 0); 828 Assert.deepEqual(serialized, serializedValue, "Got expected structure"); 829 830 info(`Checking '${serialized.type}' with root ownershipType`); 831 const serializationInternalMapWithRoot = new Map(); 832 const serializedWithRoot = serialize( 833 value, 834 defaultSerializationOptions, 835 "root", 836 serializationInternalMapWithRoot, 837 realm, 838 {} 839 ); 840 841 assertInternalIds(serializationInternalMapWithRoot, 0); 842 Assert.equal( 843 typeof serializedWithRoot.handle, 844 "string", 845 "Got a handle property" 846 ); 847 Assert.deepEqual( 848 Object.assign({}, serialized, { handle: serializedWithRoot.handle }), 849 serializedWithRoot, 850 "Got expected structure, plus a generated handle id" 851 ); 852 } 853 }); 854 855 add_task(function test_serializeRemoteComplexValues() { 856 for (const type of REMOTE_COMPLEX_VALUES) { 857 const { value, serialized, serializationOptions } = type; 858 const serializationOptionsWithDefaults = 859 setDefaultSerializationOptions(serializationOptions); 860 861 info(`Checking '${serialized.type}' with none ownershipType`); 862 const realm = new Realm(); 863 const serializationInternalMapWithNone = new Map(); 864 865 const serializedValue = serialize( 866 value, 867 serializationOptionsWithDefaults, 868 "none", 869 serializationInternalMapWithNone, 870 realm, 871 {} 872 ); 873 874 assertInternalIds(serializationInternalMapWithNone, 0); 875 Assert.deepEqual(serialized, serializedValue, "Got expected structure"); 876 877 info(`Checking '${serialized.type}' with root ownershipType`); 878 const serializationInternalMapWithRoot = new Map(); 879 const serializedWithRoot = serialize( 880 value, 881 serializationOptionsWithDefaults, 882 "root", 883 serializationInternalMapWithRoot, 884 realm, 885 {} 886 ); 887 888 assertInternalIds(serializationInternalMapWithRoot, 0); 889 Assert.equal( 890 typeof serializedWithRoot.handle, 891 "string", 892 "Got a handle property" 893 ); 894 Assert.deepEqual( 895 Object.assign({}, serialized, { handle: serializedWithRoot.handle }), 896 serializedWithRoot, 897 "Got expected structure, plus a generated handle id" 898 ); 899 } 900 }); 901 902 add_task(function test_serializeWithSerializationInternalMap() { 903 const dataSet = [ 904 { 905 data: [1], 906 serializedData: [{ type: "number", value: 1 }], 907 type: "array", 908 }, 909 { 910 data: new Map([[true, false]]), 911 serializedData: [ 912 [ 913 { type: "boolean", value: true }, 914 { type: "boolean", value: false }, 915 ], 916 ], 917 type: "map", 918 }, 919 { 920 data: new Set(["foo"]), 921 serializedData: [{ type: "string", value: "foo" }], 922 type: "set", 923 }, 924 { 925 data: { foo: "bar" }, 926 serializedData: [["foo", { type: "string", value: "bar" }]], 927 type: "object", 928 }, 929 ]; 930 const realm = new Realm(); 931 932 for (const { type, data, serializedData } of dataSet) { 933 info(`Checking '${type}' with serializationInternalMap`); 934 935 const serializationInternalMap = new Map(); 936 const value = [ 937 data, 938 data, 939 [data], 940 new Set([data]), 941 new Map([["bar", data]]), 942 { bar: data }, 943 ]; 944 945 const serializedValue = serialize( 946 value, 947 { maxObjectDepth: 2 }, 948 "none", 949 serializationInternalMap, 950 realm, 951 {} 952 ); 953 954 assertInternalIds(serializationInternalMap, 1); 955 956 const internalId = serializationInternalMap.get(data).internalId; 957 958 const serialized = { 959 type: "array", 960 value: [ 961 { 962 type, 963 value: serializedData, 964 internalId, 965 }, 966 { 967 type, 968 internalId, 969 }, 970 { 971 type: "array", 972 value: [{ type, internalId }], 973 }, 974 { 975 type: "set", 976 value: [{ type, internalId }], 977 }, 978 { 979 type: "map", 980 value: [["bar", { type, internalId }]], 981 }, 982 { 983 type: "object", 984 value: [["bar", { type, internalId }]], 985 }, 986 ], 987 }; 988 989 Assert.deepEqual(serialized, serializedValue, "Got expected structure"); 990 } 991 }); 992 993 add_task(function test_serializeMultipleValuesWithSerializationInternalMap() { 994 const realm = new Realm(); 995 const serializationInternalMap = new Map(); 996 const obj1 = { foo: "bar" }; 997 const obj2 = [1, 2]; 998 const value = [obj1, obj2, obj1, obj2]; 999 1000 serialize( 1001 value, 1002 { maxObjectDepth: 2 }, 1003 "none", 1004 serializationInternalMap, 1005 realm, 1006 {} 1007 ); 1008 1009 assertInternalIds(serializationInternalMap, 2); 1010 1011 const internalId1 = serializationInternalMap.get(obj1).internalId; 1012 const internalId2 = serializationInternalMap.get(obj2).internalId; 1013 1014 Assert.notEqual( 1015 internalId1, 1016 internalId2, 1017 "Internal ids for different object are also different" 1018 ); 1019 }); 1020 1021 add_task(function test_stringify() { 1022 const STRINGIFY_TEST_CASES = [ 1023 [undefined, "undefined"], 1024 [null, "null"], 1025 ["foobar", "foobar"], 1026 ["2", "2"], 1027 [-0, "0"], 1028 [Infinity, "Infinity"], 1029 [-Infinity, "-Infinity"], 1030 [3, "3"], 1031 [1.4, "1.4"], 1032 [true, "true"], 1033 [42n, "42"], 1034 [{ toString: () => "bar" }, "bar", "toString: () => 'bar'"], 1035 [{ toString: () => 4 }, "[object Object]", "toString: () => 4"], 1036 [{ toString: undefined }, "[object Object]", "toString: undefined"], 1037 [{ toString: null }, "[object Object]", "toString: null"], 1038 [ 1039 { 1040 toString: () => { 1041 throw new Error("toString error"); 1042 }, 1043 }, 1044 "[object Object]", 1045 "toString: () => { throw new Error('toString error'); }", 1046 ], 1047 ]; 1048 1049 for (const [value, expectedString, description] of STRINGIFY_TEST_CASES) { 1050 info(`Checking '${description || value}'`); 1051 const stringifiedValue = stringify(value); 1052 1053 Assert.strictEqual(expectedString, stringifiedValue, "Got expected string"); 1054 } 1055 }); 1056 1057 function assertLocalValue(type, value, expectedValue) { 1058 let formattedValue = value; 1059 let formattedExpectedValue = expectedValue; 1060 1061 // Format certain types for easier assertion 1062 if (type == "map") { 1063 Assert.equal( 1064 Object.prototype.toString.call(expectedValue), 1065 "[object Map]", 1066 "Got expected type Map" 1067 ); 1068 1069 formattedValue = Array.from(value.values()); 1070 formattedExpectedValue = Array.from(expectedValue.values()); 1071 } else if (type == "set") { 1072 Assert.equal( 1073 Object.prototype.toString.call(expectedValue), 1074 "[object Set]", 1075 "Got expected type Set" 1076 ); 1077 1078 formattedValue = Array.from(value); 1079 formattedExpectedValue = Array.from(expectedValue); 1080 } 1081 1082 Assert.deepEqual( 1083 formattedValue, 1084 formattedExpectedValue, 1085 "Got expected structure" 1086 ); 1087 } 1088 1089 function assertInternalIds(serializationInternalMap, amount) { 1090 const remoteValuesWithInternalIds = Array.from( 1091 serializationInternalMap.values() 1092 ).filter(remoteValue => !!remoteValue.internalId); 1093 1094 Assert.equal( 1095 remoteValuesWithInternalIds.length, 1096 amount, 1097 "Got expected amount of internalIds in serializationInternalMap" 1098 ); 1099 } 1100 1101 function deserializeInWindowRealm(serialized) { 1102 return SpecialPowers.spawn( 1103 gBrowser.selectedBrowser, 1104 [serialized], 1105 async _serialized => { 1106 const { WindowRealm } = ChromeUtils.importESModule( 1107 "chrome://remote/content/shared/Realm.sys.mjs" 1108 ); 1109 const { deserialize } = ChromeUtils.importESModule( 1110 "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs" 1111 ); 1112 const realm = new WindowRealm(content); 1113 info(`Checking '${_serialized.type}'`); 1114 return deserialize(_serialized, realm, {}); 1115 } 1116 ); 1117 }