browser_RemoteValueDOM.js (23917B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* eslint no-undef: 0 no-unused-vars: 0 */ 7 8 add_setup(function () { 9 return SpecialPowers.pushPrefEnv({ 10 set: [["security.allow_eval_with_system_principal", true]], 11 }); 12 }); 13 14 add_task(async function test_deserializeSharedIdInvalidTypes() { 15 await runTestInContent(() => { 16 for (const invalidType of [false, 42, {}, []]) { 17 info(`Checking type: '${invalidType}'`); 18 19 const serializedValue = { 20 sharedId: invalidType, 21 }; 22 23 Assert.throws( 24 () => deserialize(serializedValue, realm, { nodeCache }), 25 /InvalidArgumentError:/, 26 `Got expected error for type ${invalidType}` 27 ); 28 } 29 }); 30 }); 31 32 add_task(async function test_deserializeSharedIdInvalidValue() { 33 await runTestInContent(() => { 34 const serializedValue = { 35 sharedId: "foo", 36 }; 37 38 Assert.throws( 39 () => deserialize(serializedValue, realm, { nodeCache }), 40 /NoSuchNodeError:/, 41 "Got expected error for unknown 'sharedId'" 42 ); 43 }); 44 }); 45 46 add_task(async function test_deserializeSharedId() { 47 await loadURL(inline("<div>")); 48 49 await runTestInContent(() => { 50 const domEl = content.document.querySelector("div"); 51 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 52 53 const serializedValue = { 54 sharedId: domElRef, 55 }; 56 57 const node = deserialize(serializedValue, realm, { nodeCache }); 58 59 Assert.equal(node, domEl); 60 }); 61 }); 62 63 add_task(async function test_deserializeSharedIdPrecedenceOverHandle() { 64 await loadURL(inline("<div>")); 65 66 await runTestInContent(() => { 67 const domEl = content.document.querySelector("div"); 68 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 69 70 const serializedValue = { 71 handle: "foo", 72 sharedId: domElRef, 73 }; 74 75 const node = deserialize(serializedValue, realm, { nodeCache }); 76 77 Assert.equal(node, domEl); 78 }); 79 }); 80 81 add_task(async function test_deserializeSharedIdNoWindowRealm() { 82 await loadURL(inline("<div>")); 83 84 await runTestInContent(() => { 85 const domEl = content.document.querySelector("div"); 86 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 87 88 const serializedValue = { 89 sharedId: domElRef, 90 }; 91 92 Assert.throws( 93 () => deserialize(serializedValue, new Realm(), { nodeCache }), 94 /NoSuchNodeError/, 95 `Got expected error for a non-window realm` 96 ); 97 }); 98 }); 99 100 // Bug 1819902: Instead of a browsing context check compare the origin 101 add_task(async function test_deserializeSharedIdOtherBrowsingContext() { 102 await loadURL(inline("<iframe>")); 103 104 await runTestInContent(() => { 105 const iframeEl = content.document.querySelector("iframe"); 106 const domEl = iframeEl.contentWindow.document.createElement("div"); 107 iframeEl.contentWindow.document.body.appendChild(domEl); 108 109 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 110 111 const serializedValue = { 112 sharedId: domElRef, 113 }; 114 115 const node = deserialize(serializedValue, realm, { nodeCache }); 116 117 Assert.equal(node, null); 118 }); 119 }); 120 121 add_task(async function test_serializeRemoteComplexValues() { 122 await loadURL(inline("<div>")); 123 124 await runTestInContent(() => { 125 const domEl = content.document.querySelector("div"); 126 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 127 128 const REMOTE_COMPLEX_VALUES = [ 129 { 130 value: content.document.querySelector("div"), 131 serialized: { 132 type: "node", 133 sharedId: domElRef, 134 value: { 135 attributes: {}, 136 childNodeCount: 0, 137 localName: "div", 138 namespaceURI: "http://www.w3.org/1999/xhtml", 139 nodeType: 1, 140 shadowRoot: null, 141 }, 142 }, 143 }, 144 { 145 value: content.document.querySelectorAll("div"), 146 serialized: { 147 type: "nodelist", 148 value: [ 149 { 150 type: "node", 151 sharedId: domElRef, 152 value: { 153 nodeType: 1, 154 localName: "div", 155 namespaceURI: "http://www.w3.org/1999/xhtml", 156 childNodeCount: 0, 157 attributes: {}, 158 shadowRoot: null, 159 }, 160 }, 161 ], 162 }, 163 }, 164 { 165 value: content.document.getElementsByTagName("div"), 166 serialized: { 167 type: "htmlcollection", 168 value: [ 169 { 170 type: "node", 171 sharedId: domElRef, 172 value: { 173 nodeType: 1, 174 localName: "div", 175 namespaceURI: "http://www.w3.org/1999/xhtml", 176 childNodeCount: 0, 177 attributes: {}, 178 shadowRoot: null, 179 }, 180 }, 181 ], 182 }, 183 }, 184 ]; 185 186 for (const type of REMOTE_COMPLEX_VALUES) { 187 serializeAndAssertRemoteValue(type); 188 } 189 }); 190 }); 191 192 add_task(async function test_serializeWindow() { 193 await loadURL(inline("<iframe>")); 194 195 await runTestInContent(() => { 196 const REMOTE_COMPLEX_VALUES = [ 197 { 198 value: content, 199 serialized: { 200 type: "window", 201 value: { 202 context: content.browsingContext.browserId.toString(), 203 isTopBrowsingContext: true, 204 }, 205 }, 206 }, 207 { 208 value: content.frames[0], 209 serialized: { 210 type: "window", 211 value: { 212 context: content.frames[0].browsingContext.id.toString(), 213 }, 214 }, 215 }, 216 { 217 value: content.document.querySelector("iframe").contentWindow, 218 serialized: { 219 type: "window", 220 value: { 221 context: content.document 222 .querySelector("iframe") 223 .contentWindow.browsingContext.id.toString(), 224 }, 225 }, 226 }, 227 ]; 228 229 for (const type of REMOTE_COMPLEX_VALUES) { 230 serializeAndAssertRemoteValue(type); 231 } 232 }); 233 }); 234 235 add_task(async function test_serializeNodeChildren() { 236 await loadURL(inline("<div></div><iframe/>")); 237 238 await runTestInContent(() => { 239 // Add the used elements to the cache so that we know the unique reference. 240 const bodyEl = content.document.body; 241 const domEl = bodyEl.querySelector("div"); 242 const iframeEl = bodyEl.querySelector("iframe"); 243 244 const bodyElRef = nodeCache.getOrCreateNodeReference(bodyEl, seenNodeIds); 245 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 246 const iframeElRef = nodeCache.getOrCreateNodeReference( 247 iframeEl, 248 seenNodeIds 249 ); 250 251 const dataSet = [ 252 { 253 node: bodyEl, 254 serializationOptions: { 255 maxDomDepth: null, 256 }, 257 serialized: { 258 type: "node", 259 sharedId: bodyElRef, 260 value: { 261 nodeType: 1, 262 localName: "body", 263 namespaceURI: "http://www.w3.org/1999/xhtml", 264 childNodeCount: 2, 265 children: [ 266 { 267 type: "node", 268 sharedId: domElRef, 269 value: { 270 nodeType: 1, 271 localName: "div", 272 namespaceURI: "http://www.w3.org/1999/xhtml", 273 childNodeCount: 0, 274 children: [], 275 attributes: {}, 276 shadowRoot: null, 277 }, 278 }, 279 { 280 type: "node", 281 sharedId: iframeElRef, 282 value: { 283 nodeType: 1, 284 localName: "iframe", 285 namespaceURI: "http://www.w3.org/1999/xhtml", 286 childNodeCount: 0, 287 children: [], 288 attributes: {}, 289 shadowRoot: null, 290 }, 291 }, 292 ], 293 attributes: {}, 294 shadowRoot: null, 295 }, 296 }, 297 }, 298 { 299 node: bodyEl, 300 serializationOptions: { 301 maxDomDepth: 0, 302 }, 303 serialized: { 304 type: "node", 305 sharedId: bodyElRef, 306 value: { 307 attributes: {}, 308 childNodeCount: 2, 309 localName: "body", 310 namespaceURI: "http://www.w3.org/1999/xhtml", 311 nodeType: 1, 312 shadowRoot: null, 313 }, 314 }, 315 }, 316 { 317 node: bodyEl, 318 serializationOptions: { 319 maxDomDepth: 1, 320 }, 321 serialized: { 322 type: "node", 323 sharedId: bodyElRef, 324 value: { 325 attributes: {}, 326 childNodeCount: 2, 327 children: [ 328 { 329 type: "node", 330 sharedId: domElRef, 331 value: { 332 attributes: {}, 333 childNodeCount: 0, 334 localName: "div", 335 namespaceURI: "http://www.w3.org/1999/xhtml", 336 nodeType: 1, 337 shadowRoot: null, 338 }, 339 }, 340 { 341 type: "node", 342 sharedId: iframeElRef, 343 value: { 344 attributes: {}, 345 childNodeCount: 0, 346 localName: "iframe", 347 namespaceURI: "http://www.w3.org/1999/xhtml", 348 nodeType: 1, 349 shadowRoot: null, 350 }, 351 }, 352 ], 353 localName: "body", 354 namespaceURI: "http://www.w3.org/1999/xhtml", 355 nodeType: 1, 356 shadowRoot: null, 357 }, 358 }, 359 }, 360 { 361 node: domEl, 362 serializationOptions: { 363 maxDomDepth: 0, 364 }, 365 serialized: { 366 type: "node", 367 sharedId: domElRef, 368 value: { 369 attributes: {}, 370 childNodeCount: 0, 371 localName: "div", 372 namespaceURI: "http://www.w3.org/1999/xhtml", 373 nodeType: 1, 374 shadowRoot: null, 375 }, 376 }, 377 }, 378 { 379 node: domEl, 380 serializationOptions: { 381 maxDomDepth: 1, 382 }, 383 serialized: { 384 type: "node", 385 sharedId: domElRef, 386 value: { 387 attributes: {}, 388 childNodeCount: 0, 389 children: [], 390 localName: "div", 391 namespaceURI: "http://www.w3.org/1999/xhtml", 392 nodeType: 1, 393 shadowRoot: null, 394 }, 395 }, 396 }, 397 ]; 398 399 for (const { node, serializationOptions, serialized } of dataSet) { 400 const { maxDomDepth } = serializationOptions; 401 info(`Checking '${node.localName}' with maxDomDepth ${maxDomDepth}`); 402 403 const serializationInternalMap = new Map(); 404 405 const serializedValue = serialize( 406 node, 407 serializationOptions, 408 "none", 409 serializationInternalMap, 410 realm, 411 { nodeCache, seenNodeIds } 412 ); 413 414 Assert.deepEqual(serializedValue, serialized, "Got expected structure"); 415 } 416 }); 417 }); 418 419 add_task(async function test_serializeNodeEmbeddedWithin() { 420 await loadURL(inline("<div>")); 421 422 await runTestInContent(() => { 423 // Add the used elements to the cache so that we know the unique reference. 424 const bodyEl = content.document.body; 425 const domEl = bodyEl.querySelector("div"); 426 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 427 428 const dataSet = [ 429 { 430 embedder: "array", 431 wrapper: node => [node], 432 serialized: { 433 type: "array", 434 value: [ 435 { 436 type: "node", 437 sharedId: domElRef, 438 value: { 439 attributes: {}, 440 childNodeCount: 0, 441 localName: "div", 442 namespaceURI: "http://www.w3.org/1999/xhtml", 443 nodeType: 1, 444 shadowRoot: null, 445 }, 446 }, 447 ], 448 }, 449 }, 450 { 451 embedder: "map", 452 wrapper: node => { 453 const map = new Map(); 454 map.set(node, "elem"); 455 return map; 456 }, 457 serialized: { 458 type: "map", 459 value: [ 460 [ 461 { 462 type: "node", 463 sharedId: domElRef, 464 value: { 465 attributes: {}, 466 childNodeCount: 0, 467 localName: "div", 468 namespaceURI: "http://www.w3.org/1999/xhtml", 469 nodeType: 1, 470 shadowRoot: null, 471 }, 472 }, 473 { 474 type: "string", 475 value: "elem", 476 }, 477 ], 478 ], 479 }, 480 }, 481 { 482 embedder: "map", 483 wrapper: node => { 484 const map = new Map(); 485 map.set("elem", node); 486 return map; 487 }, 488 serialized: { 489 type: "map", 490 value: [ 491 [ 492 "elem", 493 { 494 type: "node", 495 sharedId: domElRef, 496 value: { 497 attributes: {}, 498 childNodeCount: 0, 499 localName: "div", 500 namespaceURI: "http://www.w3.org/1999/xhtml", 501 nodeType: 1, 502 shadowRoot: null, 503 }, 504 }, 505 ], 506 ], 507 }, 508 }, 509 { 510 embedder: "object", 511 wrapper: node => ({ elem: node }), 512 serialized: { 513 type: "object", 514 value: [ 515 [ 516 "elem", 517 { 518 type: "node", 519 sharedId: domElRef, 520 value: { 521 attributes: {}, 522 childNodeCount: 0, 523 localName: "div", 524 namespaceURI: "http://www.w3.org/1999/xhtml", 525 nodeType: 1, 526 shadowRoot: null, 527 }, 528 }, 529 ], 530 ], 531 }, 532 }, 533 { 534 embedder: "set", 535 wrapper: node => { 536 const set = new Set(); 537 set.add(node); 538 return set; 539 }, 540 serialized: { 541 type: "set", 542 value: [ 543 { 544 type: "node", 545 sharedId: domElRef, 546 value: { 547 attributes: {}, 548 childNodeCount: 0, 549 localName: "div", 550 namespaceURI: "http://www.w3.org/1999/xhtml", 551 nodeType: 1, 552 shadowRoot: null, 553 }, 554 }, 555 ], 556 }, 557 }, 558 ]; 559 560 for (const { embedder, wrapper, serialized } of dataSet) { 561 info(`Checking embedding node within ${embedder}`); 562 563 const serializationInternalMap = new Map(); 564 565 const serializedValue = serialize( 566 wrapper(domEl), 567 { maxDomDepth: 0 }, 568 "none", 569 serializationInternalMap, 570 realm, 571 { nodeCache } 572 ); 573 574 Assert.deepEqual(serializedValue, serialized, "Got expected structure"); 575 } 576 }); 577 }); 578 579 add_task(async function test_serializeShadowRoot() { 580 await runTestInContent(() => { 581 for (const mode of ["open", "closed"]) { 582 info(`Checking shadow root with mode '${mode}'`); 583 const customElement = content.document.createElement( 584 `${mode}-custom-element` 585 ); 586 const insideShadowRootElement = content.document.createElement("input"); 587 content.document.body.appendChild(customElement); 588 const shadowRoot = customElement.attachShadow({ mode }); 589 shadowRoot.appendChild(insideShadowRootElement); 590 591 // Add the used elements to the cache so that we know the unique reference. 592 const customElementRef = nodeCache.getOrCreateNodeReference( 593 customElement, 594 seenNodeIds 595 ); 596 const shadowRootRef = nodeCache.getOrCreateNodeReference( 597 shadowRoot, 598 seenNodeIds 599 ); 600 const insideShadowRootElementRef = nodeCache.getOrCreateNodeReference( 601 insideShadowRootElement, 602 seenNodeIds 603 ); 604 605 const dataSet = [ 606 { 607 node: customElement, 608 serializationOptions: { 609 maxDomDepth: 1, 610 }, 611 serialized: { 612 type: "node", 613 sharedId: customElementRef, 614 value: { 615 attributes: {}, 616 childNodeCount: 0, 617 children: [], 618 localName: `${mode}-custom-element`, 619 namespaceURI: "http://www.w3.org/1999/xhtml", 620 nodeType: 1, 621 shadowRoot: { 622 sharedId: shadowRootRef, 623 type: "node", 624 value: { 625 childNodeCount: 1, 626 mode, 627 nodeType: 11, 628 }, 629 }, 630 }, 631 }, 632 }, 633 { 634 node: customElement, 635 serializationOptions: { 636 includeShadowTree: "open", 637 maxDomDepth: 1, 638 }, 639 serialized: { 640 type: "node", 641 sharedId: customElementRef, 642 value: { 643 attributes: {}, 644 childNodeCount: 0, 645 children: [], 646 localName: `${mode}-custom-element`, 647 namespaceURI: "http://www.w3.org/1999/xhtml", 648 nodeType: 1, 649 shadowRoot: { 650 sharedId: shadowRootRef, 651 type: "node", 652 value: { 653 childNodeCount: 1, 654 mode, 655 nodeType: 11, 656 ...(mode === "open" 657 ? { 658 children: [ 659 { 660 type: "node", 661 sharedId: insideShadowRootElementRef, 662 value: { 663 nodeType: 1, 664 localName: "input", 665 namespaceURI: "http://www.w3.org/1999/xhtml", 666 childNodeCount: 0, 667 attributes: {}, 668 shadowRoot: null, 669 }, 670 }, 671 ], 672 } 673 : {}), 674 }, 675 }, 676 }, 677 }, 678 }, 679 { 680 node: customElement, 681 serializationOptions: { 682 includeShadowTree: "all", 683 maxDomDepth: 1, 684 }, 685 serialized: { 686 type: "node", 687 sharedId: customElementRef, 688 value: { 689 attributes: {}, 690 childNodeCount: 0, 691 children: [], 692 localName: `${mode}-custom-element`, 693 namespaceURI: "http://www.w3.org/1999/xhtml", 694 nodeType: 1, 695 shadowRoot: { 696 sharedId: shadowRootRef, 697 type: "node", 698 value: { 699 childNodeCount: 1, 700 mode, 701 nodeType: 11, 702 children: [ 703 { 704 type: "node", 705 sharedId: insideShadowRootElementRef, 706 value: { 707 nodeType: 1, 708 localName: "input", 709 namespaceURI: "http://www.w3.org/1999/xhtml", 710 childNodeCount: 0, 711 attributes: {}, 712 shadowRoot: null, 713 }, 714 }, 715 ], 716 }, 717 }, 718 }, 719 }, 720 }, 721 ]; 722 723 for (const { node, serializationOptions, serialized } of dataSet) { 724 const { maxDomDepth, includeShadowTree } = serializationOptions; 725 info( 726 `Checking shadow root with maxDomDepth ${maxDomDepth} and includeShadowTree ${includeShadowTree}` 727 ); 728 729 const serializationInternalMap = new Map(); 730 731 const serializedValue = serialize( 732 node, 733 serializationOptions, 734 "none", 735 serializationInternalMap, 736 realm, 737 { nodeCache } 738 ); 739 740 Assert.deepEqual(serializedValue, serialized, "Got expected structure"); 741 } 742 } 743 }); 744 }); 745 746 add_task(async function test_serializeNodeSharedId() { 747 await loadURL(inline("<div>")); 748 749 await runTestInContent(() => { 750 const domEl = content.document.querySelector("div"); 751 752 // Already add the domEl to the cache so that we know the unique reference. 753 const domElRef = nodeCache.getOrCreateNodeReference(domEl, seenNodeIds); 754 755 const serializedValue = serialize( 756 domEl, 757 { maxDomDepth: 0 }, 758 "root", 759 serializationInternalMap, 760 realm, 761 { nodeCache, seenNodeIds } 762 ); 763 764 Assert.equal(nodeCache.size, 1, "No additional reference added"); 765 Assert.equal(serializedValue.sharedId, domElRef); 766 Assert.notEqual(serializedValue.handle, domElRef); 767 }); 768 }); 769 770 function runTestInContent(callback) { 771 return SpecialPowers.spawn( 772 gBrowser.selectedBrowser, 773 [callback.toString()], 774 async callback => { 775 const { NodeCache } = ChromeUtils.importESModule( 776 "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs" 777 ); 778 const { Realm, WindowRealm } = ChromeUtils.importESModule( 779 "chrome://remote/content/shared/Realm.sys.mjs" 780 ); 781 const { deserialize, serialize, setDefaultSerializationOptions } = 782 ChromeUtils.importESModule( 783 "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs" 784 ); 785 786 function assertInternalIds(serializationInternalMap, amount) { 787 const remoteValuesWithInternalIds = Array.from( 788 serializationInternalMap.values() 789 ).filter(remoteValue => !!remoteValue.internalId); 790 791 Assert.equal( 792 remoteValuesWithInternalIds.length, 793 amount, 794 "Got expected amount of internalIds in serializationInternalMap" 795 ); 796 } 797 798 const nodeCache = new NodeCache(); 799 const seenNodeIds = new Map(); 800 const realm = new WindowRealm(content); 801 const serializationInternalMap = new Map(); 802 803 function serializeAndAssertRemoteValue(remoteValue) { 804 const { value, serialized } = remoteValue; 805 const serializationOptionsWithDefaults = 806 setDefaultSerializationOptions(); 807 const serializationInternalMapWithNone = new Map(); 808 809 info(`Checking '${serialized.type}' with none ownershipType`); 810 811 const serializedValue = serialize( 812 value, 813 serializationOptionsWithDefaults, 814 "none", 815 serializationInternalMapWithNone, 816 realm, 817 { nodeCache, seenNodeIds } 818 ); 819 820 assertInternalIds(serializationInternalMapWithNone, 0); 821 Assert.deepEqual(serialized, serializedValue, "Got expected structure"); 822 823 info(`Checking '${serialized.type}' with root ownershipType`); 824 const serializationInternalMapWithRoot = new Map(); 825 const serializedWithRoot = serialize( 826 value, 827 serializationOptionsWithDefaults, 828 "root", 829 serializationInternalMapWithRoot, 830 realm, 831 { nodeCache, seenNodeIds } 832 ); 833 834 assertInternalIds(serializationInternalMapWithRoot, 0); 835 Assert.equal( 836 typeof serializedWithRoot.handle, 837 "string", 838 "Got a handle property" 839 ); 840 Assert.deepEqual( 841 Object.assign({}, serialized, { handle: serializedWithRoot.handle }), 842 serializedWithRoot, 843 "Got expected structure, plus a generated handle id" 844 ); 845 } 846 847 // eslint-disable-next-line no-eval 848 eval(`(${callback})()`); 849 } 850 ); 851 }