tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_json.js (12100B)


      1 const { json, getKnownElement, getKnownShadowRoot } =
      2  ChromeUtils.importESModule("chrome://remote/content/marionette/json.sys.mjs");
      3 const { NodeCache } = ChromeUtils.importESModule(
      4  "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
      5 );
      6 const { ShadowRoot, WebElement, WebReference } = ChromeUtils.importESModule(
      7  "chrome://remote/content/marionette/web-reference.sys.mjs"
      8 );
      9 
     10 const MemoryReporter = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
     11  Ci.nsIMemoryReporterManager
     12 );
     13 
     14 function setupTest() {
     15  const browser = Services.appShell.createWindowlessBrowser(false);
     16  const nodeCache = new NodeCache();
     17 
     18  const videoEl = browser.document.createElement("video");
     19  browser.document.body.appendChild(videoEl);
     20 
     21  const svgEl = browser.document.createElementNS(
     22    "http://www.w3.org/2000/svg",
     23    "rect"
     24  );
     25  browser.document.body.appendChild(svgEl);
     26 
     27  const shadowRoot = videoEl.openOrClosedShadowRoot;
     28 
     29  const iframeEl = browser.document.createElement("iframe");
     30  browser.document.body.appendChild(iframeEl);
     31  const childEl = iframeEl.contentDocument.createElement("div");
     32 
     33  return {
     34    browser,
     35    browsingContext: browser.browsingContext,
     36    nodeCache,
     37    childEl,
     38    iframeEl,
     39    seenNodeIds: new Map(),
     40    shadowRoot,
     41    svgEl,
     42    videoEl,
     43  };
     44 }
     45 
     46 function assert_cloned_value(value, clonedValue, nodeCache, seenNodes = []) {
     47  const { seenNodeIds, serializedValue } = json.clone(value, nodeCache);
     48 
     49  deepEqual(serializedValue, clonedValue);
     50  deepEqual([...seenNodeIds.values()], seenNodes);
     51 }
     52 
     53 add_task(function test_clone_generalTypes() {
     54  const { nodeCache } = setupTest();
     55 
     56  // null
     57  assert_cloned_value(undefined, null, nodeCache);
     58  assert_cloned_value(null, null, nodeCache);
     59 
     60  // primitives
     61  assert_cloned_value(true, true, nodeCache);
     62  assert_cloned_value(42, 42, nodeCache);
     63  assert_cloned_value("foo", "foo", nodeCache);
     64 
     65  // toJSON
     66  assert_cloned_value(
     67    {
     68      toJSON() {
     69        return "foo";
     70      },
     71    },
     72    "foo",
     73    nodeCache
     74  );
     75 });
     76 
     77 add_task(function test_clone_ShadowRoot() {
     78  const { nodeCache, seenNodeIds, shadowRoot } = setupTest();
     79 
     80  const shadowRootRef = nodeCache.getOrCreateNodeReference(
     81    shadowRoot,
     82    seenNodeIds
     83  );
     84  assert_cloned_value(
     85    shadowRoot,
     86    WebReference.from(shadowRoot, shadowRootRef).toJSON(),
     87    nodeCache,
     88    seenNodeIds
     89  );
     90 });
     91 
     92 add_task(function test_clone_WebElement() {
     93  const { videoEl, nodeCache, seenNodeIds, svgEl } = setupTest();
     94 
     95  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
     96  assert_cloned_value(
     97    videoEl,
     98    WebReference.from(videoEl, videoElRef).toJSON(),
     99    nodeCache,
    100    seenNodeIds
    101  );
    102 
    103  // Check an element with a different namespace
    104  const svgElRef = nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds);
    105  assert_cloned_value(
    106    svgEl,
    107    WebReference.from(svgEl, svgElRef).toJSON(),
    108    nodeCache,
    109    seenNodeIds
    110  );
    111 });
    112 
    113 add_task(function test_clone_Sequences() {
    114  const { videoEl, nodeCache, seenNodeIds } = setupTest();
    115 
    116  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    117 
    118  const input = [
    119    null,
    120    true,
    121    [42],
    122    videoEl,
    123    {
    124      toJSON() {
    125        return "foo";
    126      },
    127    },
    128    { bar: "baz" },
    129  ];
    130 
    131  assert_cloned_value(
    132    input,
    133    [
    134      null,
    135      true,
    136      [42],
    137      { [WebElement.Identifier]: videoElRef },
    138      "foo",
    139      { bar: "baz" },
    140    ],
    141    nodeCache,
    142    seenNodeIds
    143  );
    144 });
    145 
    146 add_task(function test_clone_objects() {
    147  const { videoEl, nodeCache, seenNodeIds } = setupTest();
    148 
    149  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    150 
    151  const input = {
    152    null: null,
    153    boolean: true,
    154    array: [42],
    155    element: videoEl,
    156    toJSON: {
    157      toJSON() {
    158        return "foo";
    159      },
    160    },
    161    object: { bar: "baz" },
    162  };
    163 
    164  assert_cloned_value(
    165    input,
    166    {
    167      null: null,
    168      boolean: true,
    169      array: [42],
    170      element: { [WebElement.Identifier]: videoElRef },
    171      toJSON: "foo",
    172      object: { bar: "baz" },
    173    },
    174    nodeCache,
    175    seenNodeIds
    176  );
    177 });
    178 
    179 add_task(function test_clone_сyclicReference() {
    180  const { nodeCache } = setupTest();
    181 
    182  // object
    183  Assert.throws(() => {
    184    const obj = {};
    185    obj.reference = obj;
    186    json.clone(obj, nodeCache);
    187  }, /JavaScriptError/);
    188 
    189  // array
    190  Assert.throws(() => {
    191    const array = [];
    192    array.push(array);
    193    json.clone(array, nodeCache);
    194  }, /JavaScriptError/);
    195 
    196  // array in object
    197  Assert.throws(() => {
    198    const array = [];
    199    array.push(array);
    200    json.clone({ array }, nodeCache);
    201  }, /JavaScriptError/);
    202 
    203  // object in array
    204  Assert.throws(() => {
    205    const obj = {};
    206    obj.reference = obj;
    207    json.clone([obj], nodeCache);
    208  }, /JavaScriptError/);
    209 });
    210 
    211 add_task(function test_deserialize_generalTypes() {
    212  const { browsingContext, nodeCache } = setupTest();
    213 
    214  // null
    215  equal(json.deserialize(undefined, nodeCache, browsingContext), undefined);
    216  equal(json.deserialize(null, nodeCache, browsingContext), null);
    217 
    218  // primitives
    219  equal(json.deserialize(true, nodeCache, browsingContext), true);
    220  equal(json.deserialize(42, nodeCache, browsingContext), 42);
    221  equal(json.deserialize("foo", nodeCache, browsingContext), "foo");
    222 });
    223 
    224 add_task(function test_deserialize_ShadowRoot() {
    225  const { browsingContext, nodeCache, seenNodeIds, shadowRoot } = setupTest();
    226  const seenNodes = new Set();
    227 
    228  // Fails to resolve for unknown elements
    229  const unknownShadowRootId = { [ShadowRoot.Identifier]: "foo" };
    230  Assert.throws(() => {
    231    json.deserialize(
    232      unknownShadowRootId,
    233      nodeCache,
    234      browsingContext,
    235      seenNodes
    236    );
    237  }, /NoSuchShadowRootError/);
    238 
    239  const shadowRootRef = nodeCache.getOrCreateNodeReference(
    240    shadowRoot,
    241    seenNodeIds
    242  );
    243  const shadowRootEl = { [ShadowRoot.Identifier]: shadowRootRef };
    244 
    245  // Fails to resolve for missing window reference
    246  Assert.throws(() => json.deserialize(shadowRootEl, nodeCache), /TypeError/);
    247 
    248  // Previously seen element is associated with original web element reference
    249  seenNodes.add(shadowRootRef);
    250  const root = json.deserialize(
    251    shadowRootEl,
    252    nodeCache,
    253    browsingContext,
    254    seenNodes
    255  );
    256  deepEqual(root, shadowRoot);
    257  deepEqual(root, nodeCache.getNode(browsingContext, shadowRootRef));
    258 });
    259 
    260 add_task(function test_deserialize_WebElement() {
    261  const { browser, browsingContext, videoEl, nodeCache, seenNodeIds } =
    262    setupTest();
    263  const seenNodes = new Set();
    264 
    265  // Fails to resolve for unknown elements
    266  const unknownWebElId = { [WebElement.Identifier]: "foo" };
    267  Assert.throws(() => {
    268    json.deserialize(unknownWebElId, nodeCache, browsingContext, seenNodes);
    269  }, /NoSuchElementError/);
    270 
    271  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    272  const htmlWebEl = { [WebElement.Identifier]: videoElRef };
    273 
    274  // Fails to resolve for missing window reference
    275  Assert.throws(() => json.deserialize(htmlWebEl, nodeCache), /TypeError/);
    276 
    277  // Previously seen element is associated with original web element reference
    278  seenNodes.add(videoElRef);
    279  const el = json.deserialize(htmlWebEl, nodeCache, browsingContext, seenNodes);
    280  deepEqual(el, videoEl);
    281  deepEqual(el, nodeCache.getNode(browser.browsingContext, videoElRef));
    282 });
    283 
    284 add_task(function test_deserialize_Sequences() {
    285  const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest();
    286  const seenNodes = new Set();
    287 
    288  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    289  seenNodes.add(videoElRef);
    290 
    291  const input = [
    292    null,
    293    true,
    294    [42],
    295    { [WebElement.Identifier]: videoElRef },
    296    { bar: "baz" },
    297  ];
    298 
    299  const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
    300 
    301  equal(actual[0], null);
    302  equal(actual[1], true);
    303  deepEqual(actual[2], [42]);
    304  deepEqual(actual[3], videoEl);
    305  deepEqual(actual[4], { bar: "baz" });
    306 });
    307 
    308 add_task(function test_deserialize_objects() {
    309  const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest();
    310  const seenNodes = new Set();
    311 
    312  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    313  seenNodes.add(videoElRef);
    314 
    315  const input = {
    316    null: null,
    317    boolean: true,
    318    array: [42],
    319    element: { [WebElement.Identifier]: videoElRef },
    320    object: { bar: "baz" },
    321  };
    322 
    323  const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
    324 
    325  equal(actual.null, null);
    326  equal(actual.boolean, true);
    327  deepEqual(actual.array, [42]);
    328  deepEqual(actual.element, videoEl);
    329  deepEqual(actual.object, { bar: "baz" });
    330 
    331  nodeCache.clear({ all: true });
    332 });
    333 
    334 add_task(async function test_getKnownElement() {
    335  const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest();
    336  const seenNodes = new Set();
    337 
    338  // Unknown element reference
    339  Assert.throws(() => {
    340    getKnownElement(browser.browsingContext, "foo", nodeCache, seenNodes);
    341  }, /NoSuchElementError/);
    342 
    343  // With a ShadowRoot reference
    344  const shadowRootRef = nodeCache.getOrCreateNodeReference(
    345    shadowRoot,
    346    seenNodeIds
    347  );
    348  seenNodes.add(shadowRootRef);
    349 
    350  Assert.throws(() => {
    351    getKnownElement(
    352      browser.browsingContext,
    353      shadowRootRef,
    354      nodeCache,
    355      seenNodes
    356    );
    357  }, /NoSuchElementError/);
    358 
    359  let detachedEl = browser.document.createElement("div");
    360  const detachedElRef = nodeCache.getOrCreateNodeReference(
    361    detachedEl,
    362    seenNodeIds
    363  );
    364  seenNodes.add(detachedElRef);
    365 
    366  // Element not connected to the DOM
    367  Assert.throws(() => {
    368    getKnownElement(
    369      browser.browsingContext,
    370      detachedElRef,
    371      nodeCache,
    372      seenNodes
    373    );
    374  }, /StaleElementReferenceError/);
    375 
    376  // Element garbage collected
    377  detachedEl = null;
    378 
    379  await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
    380  Assert.throws(() => {
    381    getKnownElement(
    382      browser.browsingContext,
    383      detachedElRef,
    384      nodeCache,
    385      seenNodes
    386    );
    387  }, /StaleElementReferenceError/);
    388 
    389  // Known element reference
    390  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    391  seenNodes.add(videoElRef);
    392 
    393  equal(
    394    getKnownElement(browser.browsingContext, videoElRef, nodeCache, seenNodes),
    395    videoEl
    396  );
    397 });
    398 
    399 add_task(async function test_getKnownShadowRoot() {
    400  const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest();
    401  const seenNodes = new Set();
    402 
    403  const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
    404  seenNodes.add(videoElRef);
    405 
    406  // Unknown ShadowRoot reference
    407  Assert.throws(() => {
    408    getKnownShadowRoot(browser.browsingContext, "foo", nodeCache, seenNodes);
    409  }, /NoSuchShadowRootError/);
    410 
    411  // With a videoElement reference
    412  Assert.throws(() => {
    413    getKnownShadowRoot(
    414      browser.browsingContext,
    415      videoElRef,
    416      nodeCache,
    417      seenNodes
    418    );
    419  }, /NoSuchShadowRootError/);
    420 
    421  // Known ShadowRoot reference
    422  const shadowRootRef = nodeCache.getOrCreateNodeReference(
    423    shadowRoot,
    424    seenNodeIds
    425  );
    426  seenNodes.add(shadowRootRef);
    427 
    428  equal(
    429    getKnownShadowRoot(
    430      browser.browsingContext,
    431      shadowRootRef,
    432      nodeCache,
    433      seenNodes
    434    ),
    435    shadowRoot
    436  );
    437 
    438  // Detached ShadowRoot host
    439  let el = browser.document.createElement("div");
    440  let detachedShadowRoot = el.attachShadow({ mode: "open" });
    441  detachedShadowRoot.innerHTML = "<input></input>";
    442 
    443  const detachedShadowRootRef = nodeCache.getOrCreateNodeReference(
    444    detachedShadowRoot,
    445    seenNodeIds
    446  );
    447  seenNodes.add(detachedShadowRootRef);
    448 
    449  // ... not connected to the DOM
    450  Assert.throws(() => {
    451    getKnownShadowRoot(
    452      browser.browsingContext,
    453      detachedShadowRootRef,
    454      nodeCache,
    455      seenNodes
    456    );
    457  }, /DetachedShadowRootError/);
    458 
    459  // ... host and shadow root garbage collected
    460  el = null;
    461  detachedShadowRoot = null;
    462 
    463  await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
    464  Assert.throws(() => {
    465    getKnownShadowRoot(
    466      browser.browsingContext,
    467      detachedShadowRootRef,
    468      nodeCache,
    469      seenNodes
    470    );
    471  }, /DetachedShadowRootError/);
    472 });