Commit 14f63908 authored by Henrik Skupin's avatar Henrik Skupin
Browse files

Bug 1770733 - [bidi] Serialize and deserialize objects of type Node with...

Bug 1770733 - [bidi] Serialize and deserialize objects of type Node with sharedId field. r=webdriver-reviewers,Sasha,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D169490
parent d79afba1
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -59,6 +59,10 @@ export class WindowGlobalMessageHandler extends MessageHandler {
    return this.#innerWindowId;
  }

  get processActor() {
    return ChromeUtils.domProcessChild.getActor("WebDriverProcessData");
  }

  get window() {
    return this.context.window;
  }
+13 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ const ERRORS = new Set([
  "NoSuchAlertError",
  "NoSuchElementError",
  "NoSuchFrameError",
  "NoSuchNodeError",
  "NoSuchScriptError",
  "NoSuchShadowRootError",
  "NoSuchWindowError",
@@ -436,6 +437,17 @@ class NoSuchFrameError extends WebDriverError {
  }
}

/**
 * A node as given by its unique shared id could not be found within the cache
 * of known nodes.
 */
class NoSuchNodeError extends WebDriverError {
  constructor(message) {
    super(message);
    this.status = "no such node";
  }
}

/**
 * A command to switch to a window could not be satisfied because
 * the window could not be found.
@@ -547,6 +559,7 @@ const STATUSES = new Map([
  ["no such alert", NoSuchAlertError],
  ["no such element", NoSuchElementError],
  ["no such frame", NoSuchFrameError],
  ["no such node", NoSuchNodeError],
  ["no such script", NoSuchScriptError],
  ["no such shadow root", NoSuchShadowRootError],
  ["no such window", NoSuchWindowError],
+9 −14
Original line number Diff line number Diff line
@@ -2,9 +2,6 @@
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const DOCUMENT_FRAGMENT_NODE = 11;
const ELEMENT_NODE = 1;

/**
 * @typedef {Object} NodeReferenceDetails
 * @property {number} browserId
@@ -52,12 +49,7 @@ export class NodeCache {
   *     The unique node reference for the DOM node.
   */
  getOrCreateNodeReference(node) {
    if (
      !node ||
      ![DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE].includes(node.nodeType) ||
      (node.nodeType === DOCUMENT_FRAGMENT_NODE &&
        node.containingShadowRoot !== node)
    ) {
    if (!Node.isInstance(node)) {
      throw new TypeError(`Failed to create node reference for ${node}`);
    }

@@ -66,7 +58,10 @@ export class NodeCache {
      // For already known nodes return the cached node id.
      nodeId = this.#nodeIdMap.get(node);
    } else {
      const browsingContext = node.ownerGlobal.browsingContext;
      // Bug 1820734: For some Node types like `CDATA` no `ownerGlobal`
      // property is available, and as such they cannot be deserialized
      // right now.
      const browsingContext = node.ownerGlobal?.browsingContext;

      // For not yet cached nodes generate a unique id without curly braces.
      nodeId = Services.uuid
@@ -75,10 +70,10 @@ export class NodeCache {
        .slice(1, -1);

      const details = {
        browserId: browsingContext.browserId,
        browsingContextGroupId: browsingContext.group.id,
        browsingContextId: browsingContext.id,
        isTopBrowsingContext: browsingContext.parent === null,
        browserId: browsingContext?.browserId,
        browsingContextGroupId: browsingContext?.group.id,
        browsingContextId: browsingContext?.id,
        isTopBrowsingContext: browsingContext?.parent === null,
        nodeWeakRef: Cu.getWeakReference(node),
      };

+8 −0
Original line number Diff line number Diff line
@@ -342,6 +342,14 @@ add_task(function test_NoSuchFrameError() {
  ok(err instanceof error.WebDriverError);
});

add_task(function test_NoSuchNodeError() {
  let err = new error.NoSuchNodeError("foo");
  equal("NoSuchNodeError", err.name);
  equal("foo", err.message);
  equal("no such node", err.status);
  ok(err instanceof error.WebDriverError);
});

add_task(function test_NoSuchScriptError() {
  let err = new error.NoSuchScriptError("foo");
  equal("NoSuchScriptError", err.name);
+115 −64
Original line number Diff line number Diff line
@@ -4,41 +4,45 @@ const { NodeCache } = ChromeUtils.importESModule(

function setupTest() {
  const browser = Services.appShell.createWindowlessBrowser(false);
  const nodeCache = new NodeCache();

  const htmlEl = browser.document.createElement("video");
  htmlEl.setAttribute("id", "foo");
  browser.document.body.appendChild(htmlEl);

  const svgEl = browser.document.createElementNS(
    "http://www.w3.org/2000/svg",
    "rect"
  );
  browser.document.body.appendChild(svgEl);

  const shadowRoot = htmlEl.openOrClosedShadowRoot;

  const iframeEl = browser.document.createElement("iframe");
  browser.document.body.appendChild(iframeEl);
  browser.document.body.innerHTML = `
    <div id="foo" style="margin: 50px">
      <iframe></iframe>
      <video></video>
      <svg xmlns="http://www.w3.org/2000/svg"></svg>
      <textarea></textarea>
    </div>
    <div id="with-comment"><!-- Comment --></div>
  `;

  const divEl = browser.document.querySelector("div");
  const svgEl = browser.document.querySelector("svg");
  const textareaEl = browser.document.querySelector("textarea");
  const videoEl = browser.document.querySelector("video");

  const iframeEl = browser.document.querySelector("iframe");
  const childEl = iframeEl.contentDocument.createElement("div");

  return { browser, nodeCache, childEl, iframeEl, htmlEl, shadowRoot, svgEl };
  iframeEl.contentDocument.body.appendChild(childEl);

  const shadowRoot = videoEl.openOrClosedShadowRoot;

  return {
    browser,
    nodeCache: new NodeCache(),
    childEl,
    divEl,
    iframeEl,
    shadowRoot,
    svgEl,
    textareaEl,
    videoEl,
  };
}

add_task(function getOrCreateNodeReference_invalid() {
  const { browser, htmlEl, nodeCache } = setupTest();
  const { nodeCache } = setupTest();

  const invalidValues = [
    null,
    undefined,
    "foo",
    42,
    true,
    [],
    {},
    htmlEl.attributes[0],
    browser.document.createDocumentFragment(),
  ];
  const invalidValues = [null, undefined, "foo", 42, true, [], {}];

  for (const value of invalidValues) {
    info(`Testing value: ${value}`);
@@ -47,46 +51,93 @@ add_task(function getOrCreateNodeReference_invalid() {
});

add_task(function getOrCreateNodeReference_supportedNodeTypes() {
  const { htmlEl, nodeCache, shadowRoot } = setupTest();
  const { browser, divEl, nodeCache } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  equal(nodeCache.size, 1);
  const xmlDocument = new DOMParser().parseFromString(
    "<xml></xml>",
    "application/xml"
  );

  const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
  equal(nodeCache.size, 2);
  const values = [
    { node: divEl, type: Node.ELEMENT_NODE },
    { node: divEl.attributes[0], type: Node.ATTRIBUTE_NODE },
    { node: browser.document.createTextNode("foo"), type: Node.TEXT_NODE },
    {
      node: xmlDocument.createCDATASection("foo"),
      type: Node.CDATA_SECTION_NODE,
    },
    {
      node: browser.document.createProcessingInstruction(
        "xml-stylesheet",
        "href='foo.css'"
      ),
      type: Node.PROCESSING_INSTRUCTION_NODE_NODE,
    },
    { node: browser.document.createComment("foo"), type: Node.COMMENT_NODE },
    { node: browser.document, type: Node.Document_NODE },
    {
      node: browser.document.implementation.createDocumentType(
        "foo",
        "bar",
        "dtd"
      ),
      type: Node.DOCUMENT_TYPE_NODE_NODE,
    },
    {
      node: browser.document.createDocumentFragment(),
      type: Node.DOCUMENT_FRAGMENT_NODE,
    },
  ];

  notEqual(htmlElRef, shadowRootRef);
  values.forEach((value, index) => {
    info(`Testing value: ${value.type}`);
    const nodeRef = nodeCache.getOrCreateNodeReference(value.node);
    equal(nodeCache.size, index + 1);
    equal(typeof nodeRef, "string");
  });
});

add_task(function getOrCreateNodeReference_referenceAlreadyCreated() {
  const { htmlEl, nodeCache } = setupTest();
  const { divEl, nodeCache } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  const htmlElRefOther = nodeCache.getOrCreateNodeReference(htmlEl);
  const divElRef = nodeCache.getOrCreateNodeReference(divEl);
  const divElRefOther = nodeCache.getOrCreateNodeReference(divEl);
  equal(nodeCache.size, 1);
  equal(htmlElRefOther, htmlElRef);
  equal(divElRefOther, divElRef);
});

add_task(function getOrCreateNodeReference_differentReference() {
  const { divEl, nodeCache, shadowRoot } = setupTest();

  const divElRef = nodeCache.getOrCreateNodeReference(divEl);
  equal(nodeCache.size, 1);

  const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
  equal(nodeCache.size, 2);

  notEqual(divElRef, shadowRootRef);
});

add_task(function getOrCreateNodeReference_differentReferencePerNodeCache() {
  const { browser, htmlEl, nodeCache } = setupTest();
  const { browser, divEl, nodeCache } = setupTest();
  const nodeCache2 = new NodeCache();

  const htmlElRef1 = nodeCache.getOrCreateNodeReference(htmlEl);
  const htmlElRef2 = nodeCache2.getOrCreateNodeReference(htmlEl);
  const divElRef1 = nodeCache.getOrCreateNodeReference(divEl);
  const divElRef2 = nodeCache2.getOrCreateNodeReference(divEl);

  notEqual(htmlElRef1, htmlElRef2);
  notEqual(divElRef1, divElRef2);
  equal(
    nodeCache.getNode(browser.browsingContext, htmlElRef1),
    nodeCache2.getNode(browser.browsingContext, htmlElRef2)
    nodeCache.getNode(browser.browsingContext, divElRef1),
    nodeCache2.getNode(browser.browsingContext, divElRef2)
  );

  equal(nodeCache.getNode(browser.browsingContext, htmlElRef2), null);
  equal(nodeCache.getNode(browser.browsingContext, divElRef2), null);
});

add_task(function clear() {
  const { browser, htmlEl, nodeCache, svgEl } = setupTest();
  const { browser, divEl, nodeCache, svgEl } = setupTest();

  nodeCache.getOrCreateNodeReference(htmlEl);
  nodeCache.getOrCreateNodeReference(divEl);
  nodeCache.getOrCreateNodeReference(svgEl);
  equal(nodeCache.size, 2);

@@ -104,7 +155,7 @@ add_task(function clear() {
  equal(nodeCache.getNode(browser2.browsingContext, imgElRef), imgEl);

  // Clear all references
  nodeCache.getOrCreateNodeReference(htmlEl);
  nodeCache.getOrCreateNodeReference(divEl);
  equal(nodeCache.size, 2);

  nodeCache.clear({ all: true });
@@ -112,35 +163,35 @@ add_task(function clear() {
});

add_task(function getNode_multiple_nodes() {
  const { browser, htmlEl, nodeCache, svgEl } = setupTest();
  const { browser, divEl, nodeCache, svgEl } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  const divElRef = nodeCache.getOrCreateNodeReference(divEl);
  const svgElRef = nodeCache.getOrCreateNodeReference(svgEl);

  equal(nodeCache.getNode(browser.browsingContext, svgElRef), svgEl);
  equal(nodeCache.getNode(browser.browsingContext, htmlElRef), htmlEl);
  equal(nodeCache.getNode(browser.browsingContext, divElRef), divEl);
});

add_task(function getNode_differentBrowsingContextInSameGroup() {
  const { iframeEl, htmlEl, nodeCache } = setupTest();
  const { iframeEl, divEl, nodeCache } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  const divElRef = nodeCache.getOrCreateNodeReference(divEl);
  equal(nodeCache.size, 1);

  equal(
    nodeCache.getNode(iframeEl.contentWindow.browsingContext, htmlElRef),
    htmlEl
    nodeCache.getNode(iframeEl.contentWindow.browsingContext, divElRef),
    divEl
  );
});

add_task(function getNode_differentBrowsingContextInOtherGroup() {
  const { htmlEl, nodeCache } = setupTest();
  const { divEl, nodeCache } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  const divElRef = nodeCache.getOrCreateNodeReference(divEl);
  equal(nodeCache.size, 1);

  const browser2 = Services.appShell.createWindowlessBrowser(false);
  equal(nodeCache.getNode(browser2.browsingContext, htmlElRef), null);
  equal(nodeCache.getNode(browser2.browsingContext, divElRef), null);
});

add_task(async function getNode_nodeDeleted() {
@@ -158,17 +209,17 @@ add_task(async function getNode_nodeDeleted() {
});

add_task(function getNodeDetails_forTopBrowsingContext() {
  const { browser, htmlEl, nodeCache } = setupTest();
  const { browser, divEl, nodeCache } = setupTest();

  const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
  const divElRef = nodeCache.getOrCreateNodeReference(divEl);

  const nodeDetails = nodeCache.getReferenceDetails(htmlElRef);
  const nodeDetails = nodeCache.getReferenceDetails(divElRef);
  equal(nodeDetails.browserId, browser.browsingContext.browserId);
  equal(nodeDetails.browsingContextGroupId, browser.browsingContext.group.id);
  equal(nodeDetails.browsingContextId, browser.browsingContext.id);
  ok(nodeDetails.isTopBrowsingContext);
  ok(nodeDetails.nodeWeakRef);
  equal(nodeDetails.nodeWeakRef.get(), htmlEl);
  equal(nodeDetails.nodeWeakRef.get(), divEl);
});

add_task(async function getNodeDetails_forChildBrowsingContext() {
Loading