Commit 377ff8fb authored by Henrik Skupin's avatar Henrik Skupin
Browse files

Bug 1822466 - [marionette] Check Navigable's seen nodes map for known nodes....

Bug 1822466 - [marionette] Check Navigable's seen nodes map for known nodes. r=webdriver-reviewers,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D177492
parent cb7c4b36
Loading
Loading
Loading
Loading
+28 −25
Original line number Diff line number Diff line
@@ -87,9 +87,9 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
      let waitForNextTick = false;

      const { name, data: serializedData } = msg;
      const data = lazy.json.deserialize({
      const data = await lazy.json.deserialize({
        value: serializedData,
        win: this.contentWindow,
        browsingContext: this.contentWindow.browsingContext,
        getKnownElement: this.#getKnownElement.bind(this),
        getKnownShadowRoot: this.#getKnownShadowRoot.bind(this),
      });
@@ -190,9 +190,10 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
      }

      return {
        data: lazy.json.clone({
        data: await lazy.json.clone({
          value: result,
          getOrCreateNodeReference: this.#getOrCreateNodeReference.bind(this),
          browsingContext: this.contentWindow.browsingContext,
        }),
      };
    } catch (e) {
@@ -634,8 +635,9 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
   *     If the element has gone stale, indicating its node document is no
   *     longer the active document or it is no longer attached to the DOM.
   */
  #getKnownElement(browsingContext, nodeId) {
    if (!this.#isNodeReferenceKnown(browsingContext, nodeId)) {
  async #getKnownElement(browsingContext, nodeId) {
    const isKnown = await this.#isNodeReferenceKnown(browsingContext, nodeId);
    if (!isKnown) {
      throw new lazy.error.NoSuchElementError(
        `The element with the reference ${nodeId} is not known in the current browsing context`
      );
@@ -680,8 +682,9 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
   *     If the ShadowRoot is detached, indicating its node document is no
   *     longer the active document or it is no longer attached to the DOM.
   */
  #getKnownShadowRoot(browsingContext, nodeId) {
    if (!this.#isNodeReferenceKnown(browsingContext, nodeId)) {
  async #getKnownShadowRoot(browsingContext, nodeId) {
    const isKnown = await this.#isNodeReferenceKnown(browsingContext, nodeId);
    if (!isKnown) {
      throw new lazy.error.NoSuchShadowRootError(
        `The shadow root with the reference ${nodeId} is not known in the current browsing context`
      );
@@ -716,14 +719,23 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
   * node cache, and returns it. Otherwise it creates a new reference and
   * adds it to the cache.
   *
   * @param {BrowsingContext} browsingContext
   *     The browsing context the element is part of.
   * @param {Node} node
   *     The node to create or get a WebReference for.
   *
   * @returns {WebReference} The web reference for the node.
   */
  #getOrCreateNodeReference(node) {
  async #getOrCreateNodeReference(browsingContext, node) {
    const nodeRef = this.#nodeCache.getOrCreateNodeReference(node);
    return lazy.WebReference.from(node, nodeRef);
    const webRef = lazy.WebReference.from(node, nodeRef);

    await this.sendQuery("MarionetteCommandsChild:addNodeToSeenNodes", {
      browsingContext,
      nodeId: webRef.uuid,
    });

    return webRef;
  }

  /**
@@ -737,23 +749,14 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
   * @param {ElementIdentifier} nodeId
   *     The WebElement reference identifier for a DOM element.
   *
   * @returns {boolean}
   *     True if the element is known in the given browsing context.
   * @returns {Promise<boolean>}
   *     A promise that resolved to True if the element is known in the given
   *     browsing context.
   */
  #isNodeReferenceKnown(browsingContext, nodeId) {
    const nodeDetails = this.#nodeCache.getReferenceDetails(nodeId);
    if (nodeDetails === null) {
      return false;
    }

    if (nodeDetails.isTopBrowsingContext) {
      // As long as Navigables are not available any cross-group navigation will
      // cause a swap of the current top-level browsing context. The only unique
      // identifier in such a case is the browser id the top-level browsing
      // context actually lives in.
      return nodeDetails.browserId === browsingContext.browserId;
    }

    return nodeDetails.browsingContextId === browsingContext.id;
    return this.sendQuery("MarionetteCommandsChild:isNodeReferenceKnown", {
      browsingContext,
      nodeId,
    });
  }
}
+23 −1
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
  capture: "chrome://remote/content/shared/Capture.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  session: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
});

XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
@@ -18,7 +19,6 @@ XPCOMUtils.defineLazyGetter(lazy, "logger", () =>

// Because Marionette supports a single session only we store its id
// globally so that the parent actor can access it.
// eslint-disable-next-line no-unused-vars
let webDriverSessionId = null;

export class MarionetteCommandsParent extends JSWindowActorParent {
@@ -32,6 +32,28 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
    });
  }

  async receiveMessage(msg) {
    const { name, data } = msg;

    switch (name) {
      case "MarionetteCommandsChild:addNodeToSeenNodes":
        return lazy.session.addNodeToSeenNodes(
          webDriverSessionId,
          data.browsingContext,
          data.nodeId
        );

      case "MarionetteCommandsChild:isNodeReferenceKnown":
        return lazy.session.isNodeReferenceKnown(
          webDriverSessionId,
          data.browsingContext,
          data.nodeId
        );
    }

    return null;
  }

  async sendQuery(name, data) {
    // return early if a dialog is opened
    const result = await Promise.race([
+58 −27
Original line number Diff line number Diff line
@@ -34,10 +34,10 @@ export const json = {};
 *     The clone algorithm to invoke for individual list entries or object
 *     properties.
 *
 * @returns {object}
 *     The cloned object.
 * @returns {Promise<object>}
 *     A promise that resolves to the cloned object.
 */
function cloneObject(value, seen, cloneAlgorithm) {
async function cloneObject(value, seen, cloneAlgorithm) {
  // Only proceed with cloning an object if it hasn't been seen yet.
  if (seen.has(value)) {
    throw new lazy.error.JavaScriptError("Cyclic object value");
@@ -47,13 +47,15 @@ function cloneObject(value, seen, cloneAlgorithm) {
  let result;

  if (lazy.element.isCollection(value)) {
    result = [...value].map(entry => cloneAlgorithm(entry, seen));
    result = await Promise.all(
      [...value].map(entry => cloneAlgorithm(entry, seen))
    );
  } else {
    // arbitrary objects
    result = {};
    for (let prop in value) {
      try {
        result[prop] = cloneAlgorithm(value[prop], seen);
        result[prop] = await cloneAlgorithm(value[prop], seen);
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
          lazy.logger.debug(`Skipping ${prop}: ${e.message}`);
@@ -91,15 +93,17 @@ function cloneObject(value, seen, cloneAlgorithm) {
 * - If a cyclic references is detected a JavaScriptError is thrown.
 *
 * @param {object} options
 * @param {object} options.value
 *     Object to be cloned.
 * @param {BrowsingContext} options.browsingContext
 *     Current browsing context.
 * @param {Function} options.getOrCreateNodeReference
 *     Callback that tries to use a known node reference from the node cache,
 *     or creates a new one if not present yet.
 * @param {object} options.value
 *     Object to be cloned.
 *
 * @returns {object}
 *     Same object as provided by `value` with the WebDriver specific
 *     elements replaced by WebReference's.
 * @returns {Promise<object>}
 *     Promise that resolves to the same object as provided by `value` with
 *     the WebDriver specific elements replaced by WebReference's.
 *
 * @throws {JavaScriptError}
 *     If an object contains cyclic references.
@@ -107,10 +111,18 @@ function cloneObject(value, seen, cloneAlgorithm) {
 *     If the element has gone stale, indicating it is no longer
 *     attached to the DOM.
 */
json.clone = function(options) {
  const { getOrCreateNodeReference, value } = options;
json.clone = async function(options) {
  const { browsingContext, getOrCreateNodeReference, value } = options;

  if (typeof browsingContext === "undefined") {
    throw new TypeError("Browsing context not specified");
  }

  if (typeof getOrCreateNodeReference !== "function") {
    throw new TypeError("Invalid callback for 'getOrCreateNodeReference'");
  }

  function cloneJSON(value, seen) {
  async function cloneJSON(value, seen) {
    if (seen === undefined) {
      seen = new Set();
    }
@@ -147,7 +159,8 @@ json.clone = function(options) {
        );
      }

      return getOrCreateNodeReference(value).toJSON();
      const ref = await getOrCreateNodeReference(browsingContext, value);
      return ref.toJSON();
    }

    if (isNode && lazy.element.isShadowRoot(value)) {
@@ -160,7 +173,8 @@ json.clone = function(options) {
        );
      }

      return getOrCreateNodeReference(value).toJSON();
      const ref = await getOrCreateNodeReference(browsingContext, value);
      return ref.toJSON();
    }

    if (typeof value.toJSON == "function") {
@@ -186,18 +200,18 @@ json.clone = function(options) {
 * Deserialize an arbitrary object.
 *
 * @param {object} options
 * @param {object} options.value
 *     Arbitrary object.
 * @param {WindowProxy} options.win
 *     Current window.
 * @param {BrowsingContext} options.browsingContext
 *     Current browsing context.
 * @param {Function} options.getKnownElement
 *     Callback that will try to resolve a WebElement reference to an Element node.
 * @param {Function} options.getKnownShadowRoot
 *     Callback that will try to resolve a ShadowRoot reference to a ShadowRoot node.
 * @param {object} options.value
 *     Arbitrary object.
 *
 * @returns {object}
 *     Same object as provided by `value` with the WebDriver specific
 *     references replaced with real JavaScript objects.
 * @returns {Promise<object>}
 *     Promise that resolves to the same object as provided by `value` with the
 *     WebDriver specific references replaced with real JavaScript objects.
 *
 * @throws {DetachedShadowRootError}
 *     If the ShadowRoot is detached, indicating it is no longer attached to the DOM.
@@ -208,10 +222,27 @@ json.clone = function(options) {
 * @throws {StaleElementReferenceError}
 *     If the element is stale, indicating it is no longer attached to the DOM.
 */
json.deserialize = function(options) {
  const { value, win, getKnownElement, getKnownShadowRoot } = options;
json.deserialize = async function(options) {
  const {
    browsingContext,
    getKnownElement,
    getKnownShadowRoot,
    value,
  } = options;

  if (typeof browsingContext === "undefined") {
    throw new TypeError("Browsing context not specified");
  }

  if (typeof getKnownElement !== "function") {
    throw new TypeError("Invalid callback for 'getKnownElement'");
  }

  if (typeof getKnownShadowRoot !== "function") {
    throw new TypeError("Invalid callback for 'getKnownShadowRoot'");
  }

  function deserializeJSON(value, seen) {
  async function deserializeJSON(value, seen) {
    if (seen === undefined) {
      seen = new Set();
    }
@@ -233,11 +264,11 @@ json.deserialize = function(options) {
          const webRef = lazy.WebReference.fromJSON(value);

          if (webRef instanceof lazy.ShadowRoot) {
            return getKnownShadowRoot(win.browsingContext, webRef.uuid);
            return getKnownShadowRoot(browsingContext, webRef.uuid);
          }

          if (webRef instanceof lazy.WebElement) {
            return getKnownElement(win.browsingContext, webRef.uuid);
            return getKnownElement(browsingContext, webRef.uuid);
          }

          // WebFrame and WebWindow not supported yet
+244 −106

File changed.

Preview size limit exceeded, changes collapsed.

+19 −0
Original line number Diff line number Diff line
@@ -253,6 +253,25 @@ export var TabManager = {
    return browsingContext.id.toString();
  },

  /**
   * Get the navigable for the given browsing context.
   *
   * Because Gecko doesn't support the Navigable concept in content
   * scope the content browser could be used to uniquely identify
   * top-level browsing contexts.
   *
   * @param {BrowsingContext} browsingContext
   *
   * @returns {BrowsingContext|XULBrowser} The navigable
   */
  getNavigableForBrowsingContext(browsingContext) {
    if (browsingContext.isContent && browsingContext.parent === null) {
      return browsingContext.embedderElement;
    }

    return browsingContext;
  },

  getTabCount() {
    let count = 0;
    for (const win of this.windows) {
Loading