Bug 1394881 - Use Node.isConnected for web element staleness check. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Wed, 30 Aug 2017 14:22:39 +0100
changeset 655954 227605cc5cf5af7529bff422a30b5ade8705f9f1
parent 655774 ab2d700fda2b4934d24227216972dce9fac19b74
child 656543 5081b00022651289a5e008ad8730055cff1cd5fc
push id77014
push userbmo:ato@sny.no
push dateWed, 30 Aug 2017 16:05:33 +0000
reviewersautomatedtester
bugs1394881
milestone57.0a1
Bug 1394881 - Use Node.isConnected for web element staleness check. r?automatedtester It turns out that Node.isConnected (described in https://dom.spec.whatwg.org/#dom-node-isconnected) handles an element’s shadow root, which element.isDisconnected tries to replicate. element.isDisconnected and element.isStale are both long and error-prone and can be removed entirely in favour of this web platform API. The relevant change to the WebDriver specification landed in https://github.com/w3c/webdriver/commit/32a477b023d155d0ee348cd87bcbc3e2b65eda1b. MozReview-Commit-ID: 5Q0gWLvw8KL
testing/marionette/action.js
testing/marionette/driver.js
testing/marionette/element.js
testing/marionette/evaluate.js
testing/marionette/interaction.js
testing/marionette/legacyaction.js
testing/marionette/listener.js
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -1318,17 +1318,17 @@ function dispatchPointerMove(a, inputSta
   // interval between pointermove increments in ms, based on common vsync
   const fps60 = 17;
 
   return new Promise(resolve => {
     const start = Date.now();
     const [startX, startY] = [inputState.x, inputState.y];
 
     let target = action.computePointerDestination(a, inputState,
-        getElementCenter(a.origin, seenEls, window));
+        getElementCenter(a.origin, seenEls));
     const [targetX, targetY] = [target.x, target.y];
 
     if (!inViewPort(targetX, targetY, window)) {
       throw new MoveTargetOutOfBoundsError(
           `(${targetX}, ${targetY}) is out of bounds of viewport ` +
           `width (${window.innerWidth}) ` +
           `and height (${window.innerHeight})`);
     }
@@ -1427,17 +1427,17 @@ function capitalize(str) {
 
 function inViewPort(x, y, win) {
   assert.number(x, `Expected x to be finite number`);
   assert.number(y, `Expected y to be finite number`);
   // Viewport includes scrollbars if rendered.
   return !(x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight);
 }
 
-function getElementCenter(elementReference, seenEls, window) {
+function getElementCenter(elementReference, seenEls) {
   if (element.isWebElementReference(elementReference)) {
     let uuid = elementReference[element.Key] ||
         elementReference[element.LegacyKey];
-    let el = seenEls.get(uuid, {frame: window});
+    let el = seenEls.get(uuid);
     return element.coordinates(el);
   }
   return {};
 }
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1801,18 +1801,17 @@ GeckoDriver.prototype.switchToFrame = as
       checkTimer.initWithCallback(
           checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
       return;
     }
 
     // by element
     if (this.curBrowser.seenEls.has(element)) {
       // HTMLIFrameElement
-      let wantedFrame = this.curBrowser.seenEls.get(
-          element, {frame: curWindow});
+      let wantedFrame = this.curBrowser.seenEls.get(element);
       // Deal with an embedded xul:browser case
       if (wantedFrame.tagName == "xul:browser" ||
           wantedFrame.tagName == "browser") {
         curWindow = wantedFrame.contentWindow;
         this.curFrame = curWindow;
         if (focus) {
           this.curFrame.focus();
         }
@@ -2110,18 +2109,17 @@ GeckoDriver.prototype.findElement = asyn
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
       let container = {frame: win};
       if (opts.startNode) {
-        opts.startNode = this.curBrowser.seenEls.get(
-            opts.startNode, container);
+        opts.startNode = this.curBrowser.seenEls.get(opts.startNode);
       }
       let el = await element.find(container, strategy, expr, opts);
       let elRef = this.curBrowser.seenEls.add(el);
       let webEl = element.makeWebElement(elRef);
 
       resp.body.value = webEl;
       break;
 
@@ -2156,18 +2154,17 @@ GeckoDriver.prototype.findElements = asy
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
       let container = {frame: win};
       if (opts.startNode) {
-        opts.startNode = this.curBrowser.seenEls.get(
-            opts.startNode, container);
+        opts.startNode = this.curBrowser.seenEls.get(opts.startNode);
       }
       let els = await element.find(container, strategy, expr, opts);
 
       let elRefs = this.curBrowser.seenEls.addAll(els);
       let webEls = elRefs.map(element.makeWebElement);
       resp.body = webEls;
       break;
 
@@ -2208,24 +2205,24 @@ GeckoDriver.prototype.getActiveElement =
  *     Reference ID to the element that will be clicked.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.clickElement = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       await interaction.clickElement(el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       // We need to protect against the click causing an OOP frame
       // to close.  This fires the mozbrowserclose event when it closes
       // so we need to listen for it and then just send an error back.
       // The person making the call should be aware something is not right
@@ -2267,24 +2264,24 @@ GeckoDriver.prototype.clickElement = asy
  *     Value of the attribute.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.getElementAttribute = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = el.getAttribute(name);
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.getElementAttribute(id, name);
       break;
   }
 };
@@ -2301,24 +2298,24 @@ GeckoDriver.prototype.getElementAttribut
  *     Value of the property.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.getElementProperty = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = el[name];
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.getElementProperty(id, name);
       break;
   }
 };
@@ -2334,25 +2331,25 @@ GeckoDriver.prototype.getElementProperty
  *     Element's text "as rendered".
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.getElementText = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // for chrome, we look at text nodes, and any node with a "label" field
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       let lines = [];
       this.getVisibleText(el, lines);
       resp.body.value = lines.join("\n");
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.getElementText(id);
       break;
@@ -2369,24 +2366,24 @@ GeckoDriver.prototype.getElementText = a
  *     Local tag name of element.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.getElementTagName = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = el.tagName.toLowerCase();
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.getElementTagName(id);
       break;
   }
 };
@@ -2401,24 +2398,24 @@ GeckoDriver.prototype.getElementTagName 
  *     True if displayed, false otherwise.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.isElementDisplayed = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = await interaction.isElementDisplayed(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.isElementDisplayed(id);
       break;
   }
@@ -2444,17 +2441,17 @@ GeckoDriver.prototype.getElementValueOfC
     cmd, resp) {
   const win = assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let {id, propertyName: prop} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       let sty = win.document.defaultView.getComputedStyle(el);
       resp.body.value = sty.getPropertyValue(prop);
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener
           .getElementValueOfCssProperty(id, prop);
       break;
@@ -2471,25 +2468,25 @@ GeckoDriver.prototype.getElementValueOfC
  *     True if enabled, false if disabled.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.isElementEnabled = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = await interaction.isElementEnabled(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.isElementEnabled(id);
       break;
   }
@@ -2505,25 +2502,25 @@ GeckoDriver.prototype.isElementEnabled =
  *     True if selected, false if unselected.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.isElementSelected = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       resp.body.value = await interaction.isElementSelected(
           el, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       resp.body.value = await this.listener.isElementSelected(id);
       break;
   }
@@ -2538,17 +2535,17 @@ GeckoDriver.prototype.isElementSelected 
 GeckoDriver.prototype.getElementRect = async function(cmd, resp) {
   const win = assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       let rect = el.getBoundingClientRect();
       resp.body = {
         x: rect.x + win.pageXOffset,
         y: rect.y + win.pageYOffset,
         width: rect.width,
         height: rect.height,
       };
       break;
@@ -2568,25 +2565,25 @@ GeckoDriver.prototype.getElementRect = a
  *     Value to send to the element.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.sendKeysToElement = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let {id, text} = cmd.parameters;
   assert.string(text);
 
   switch (this.context) {
     case Context.CHROME:
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       await interaction.sendKeysToElement(
           el, text, true, this.a11yChecks);
       break;
 
     case Context.CONTENT:
       await this.listener.sendKeysToElement(id, text);
       break;
   }
@@ -2599,25 +2596,25 @@ GeckoDriver.prototype.sendKeysToElement 
  *     Reference ID to the element that will be cleared.
  *
  * @throws {NoSuchWindowError}
  *     Top-level browsing context has been discarded.
  * @throws {UnexpectedAlertOpenError}
  *     A modal dialog is open, blocking this operation.
  */
 GeckoDriver.prototype.clearElement = async function(cmd, resp) {
-  const win = assert.window(this.getCurrentWindow());
+  assert.window(this.getCurrentWindow());
   assert.noUserPrompt(this.dialog);
 
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // the selenium atom doesn't work here
-      let el = this.curBrowser.seenEls.get(id, {frame: win});
+      let el = this.curBrowser.seenEls.get(id);
       if (el.nodeName == "textbox") {
         el.value = "";
       } else if (el.nodeName == "checkbox") {
         el.checked = false;
       }
       break;
 
     case Context.CONTENT:
@@ -2925,33 +2922,31 @@ GeckoDriver.prototype.takeScreenshot = f
   let win = assert.window(this.getCurrentWindow());
 
   let {id, highlights, full, hash} = cmd.parameters;
   highlights = highlights || [];
   let format = hash ? capture.Format.Hash : capture.Format.Base64;
 
   switch (this.context) {
     case Context.CHROME:
-      let container = {frame: win.document.defaultView};
-
       let highlightEls = highlights.map(
-          ref => this.curBrowser.seenEls.get(ref, container));
+          ref => this.curBrowser.seenEls.get(ref));
 
       // viewport
       let canvas;
       if (!id && !full) {
-        canvas = capture.viewport(container.frame, highlightEls);
+        canvas = capture.viewport(win, highlightEls);
 
       // element or full document element
       } else {
         let node;
         if (id) {
-          node = this.curBrowser.seenEls.get(id, container);
+          node = this.curBrowser.seenEls.get(id);
         } else {
-          node = container.frame.document.documentElement;
+          node = win.document.documentElement;
         }
 
         canvas = capture.element(node, highlightEls);
       }
 
       switch (format) {
         case capture.Format.Hash:
           return capture.toHash(canvas);
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -150,43 +150,40 @@ element.Store = class {
     return Object.keys(this.els).includes(uuid);
   }
 
   /**
    * Retrieve a DOM element by its unique web element reference/UUID.
    *
    * @param {string} uuid
    *     Web element reference, or UUID.
-   * @param {Object.<string, (WindowProxy|Element)} container
-   *     Window and an optional shadow root that contains the element.
    *
    * @returns {Element}
    *     Element associated with reference.
    *
    * @throws {NoSuchElementError}
    *     If the provided reference is unknown.
    * @throws {StaleElementReferenceError}
    *     If element has gone stale, indicating it is no longer attached to
    *     the DOM provided in the container.
    */
-  get(uuid, container) {
+  get(uuid) {
     let el = this.els[uuid];
     if (!el) {
-      throw new NoSuchElementError(`Element reference not seen before: ` +
-                                   `${uuid}`);
+      throw new NoSuchElementError("Element reference not seen before: " + uuid);
     }
 
     try {
       el = el.get();
     } catch (e) {
       el = null;
       delete this.els[uuid];
     }
 
-    if (element.isStale(el, container.frame, container.shadowRoot)) {
+    if (element.isStale(el)) {
       throw new StaleElementReferenceError(
           error.pprint`The element reference of ${el} stale; ` +
               "either the element is no longer attached to the DOM " +
               "or the document has been refreshed");
     }
 
     return el;
   }
@@ -621,88 +618,31 @@ element.isWebElementReference = function
 element.generateUUID = function() {
   let uuid = uuidGen.generateUUID().toString();
   return uuid.substring(1, uuid.length - 1);
 };
 
 /**
  * Determines if <var>el</var> is stale.
  *
- * A stale element is an element no longer attached to the DOM, which
- * is provided through <var>window</var>.
+ * A stale element is an element no longer attached to the DOM or which
+ * <code>ownerDocument</coded> is not the same as {@link WindowProxy}'s
+ * document.
  *
  * @param {Element} el
  *     DOM element to check for staleness.
- * @param {WindowProxy} window
- *     Window global to check if <var>el</var> is still part of.
- * @param {Element=} shadowRoot
- *     Current shadow root element.
  *
  * @return {boolean}
  *     True if <var>el</var> is stale, false otherwise.
  */
-element.isStale = function(el, window, shadowRoot = undefined) {
-  if (typeof window == "undefined") {
-    throw new TypeError("Window global must be provided for staleness check");
-  }
-
-  // use XPCNativeWrapper to compare elements (see bug 834266)
-  let wrappedElement, wrappedWindow, wrappedShadowRoot;
-
-  wrappedElement = new XPCNativeWrapper(el);
-  wrappedWindow = new XPCNativeWrapper(window);
-  if (shadowRoot) {
-    wrappedShadowRoot = new XPCNativeWrapper(shadowRoot);
-  }
-
-  let sameDoc = wrappedElement.ownerDocument === wrappedWindow.document;
-  let disconn = element.isDisconnected(
-      wrappedElement,
-      wrappedWindow,
-      wrappedShadowRoot);
-
-  return !el || !sameDoc || disconn;
-};
-
-/**
- * Check if the element is detached from the current frame as well as
- * the optional shadow root (when inside a Shadow DOM context).
- *
- * @param {Element} el
- *     Element to be checked.
- * @param {WindowProxy} window
- *     Window to find out if <var>el</var> is still connected to.
- * @param {Element=} shadowRoot
- *     Current shadow root element, if any.
- *
- * @return {boolean}
- *     Flag indicating that the element is disconnected.
- */
-element.isDisconnected = function(el, window, shadowRoot = undefined) {
-  assert.defined(window);
-
-  // shadow DOM
-  if (window.ShadowRoot && shadowRoot) {
-    if (el.compareDocumentPosition(shadowRoot) &
-        DOCUMENT_POSITION_DISCONNECTED) {
-      return true;
-    }
-
-    // looking for next possible ShadowRoot ancestor
-    let parent = shadowRoot.host;
-    while (parent && !(parent instanceof window.ShadowRoot)) {
-      parent = parent.parentNode;
-    }
-    return element.isDisconnected(shadowRoot.host, window, parent);
-  }
-
-  // outside shadow DOM
-  let docEl = window.document.documentElement;
-  return el.compareDocumentPosition(docEl) &
-      DOCUMENT_POSITION_DISCONNECTED;
+element.isStale = function(el) {
+  let doc = el.ownerDocument;
+  let win = doc.defaultView;
+  let sameDoc = el.ownerDocument === win.document;
+  return !sameDoc || !el.isConnected;
 };
 
 /**
  * This function generates a pair of coordinates relative to the viewport
  * given a target element and coordinates relative to that element's
  * top-left corner.
  *
  * @param {Node} node
@@ -956,17 +896,17 @@ element.getPointerInteractablePaintTree 
 
   // Include shadow DOM host only if the element's root node is not the
   // owner document.
   if (rootNode !== doc) {
     container.shadowRoot = rootNode;
   }
 
   // pointer-interactable elements tree, step 1
-  if (element.isDisconnected(el, container.frame, container.shadowRoot)) {
+  if (!el.isConnected) {
     return [];
   }
 
   // steps 2-3
   let rects = el.getClientRects();
   if (rects.length == 0) {
     return [];
   }
--- a/testing/marionette/evaluate.js
+++ b/testing/marionette/evaluate.js
@@ -218,17 +218,17 @@ evaluate.fromJSON = function(obj, seenEl
       } else if (Array.isArray(obj)) {
         return obj.map(e => evaluate.fromJSON(e, seenEls, win, shadowRoot));
 
       // web elements
       } else if (Object.keys(obj).includes(element.Key) ||
           Object.keys(obj).includes(element.LegacyKey)) {
         /* eslint-disable */
         let uuid = obj[element.Key] || obj[element.LegacyKey];
-        let el = seenEls.get(uuid, {frame: win, shadowRoot: shadowRoot});
+        let el = seenEls.get(uuid);
         /* eslint-enable */
         if (!el) {
           throw new WebDriverError(`Unknown element: ${uuid}`);
         }
         return el;
 
       }
 
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -513,10 +513,10 @@ interaction.isElementSelected = function
   let a11y = accessibility.get(strict);
   return a11y.getAccessible(el).then(acc => {
     a11y.assertSelected(acc, el, selected);
     return selected;
   });
 };
 
 function getWindow(el) {
-  return el.ownerGlobal;
+  return el.ownerDocument.defaultView;  // eslint-disable-line
 }
--- a/testing/marionette/legacyaction.js
+++ b/testing/marionette/legacyaction.js
@@ -197,17 +197,17 @@ action.Chain.prototype.actions = functio
       break;
 
     case "keyUp":
       event.sendKeyUp(pack[1], keyModifiers, this.container.frame);
       this.actions(chain, touchId, i, keyModifiers, cb);
       break;
 
     case "click":
-      el = this.seenEls.get(pack[1], this.container);
+      el = this.seenEls.get(pack[1]);
       let button = pack[2];
       let clickCount = pack[3];
       c = element.coordinates(el);
       this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers);
       if (button == 2) {
         this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y,
             button, clickCount, keyModifiers);
       }
@@ -228,17 +228,17 @@ action.Chain.prototype.actions = functio
             "Invalid Command: press cannot follow an active touch event");
       }
 
       // look ahead to check if we're scrolling,
       // needed for APZ touch dispatching
       if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
         this.scrolling = true;
       }
-      el = this.seenEls.get(pack[1], this.container);
+      el = this.seenEls.get(pack[1]);
       c = element.coordinates(el, pack[2], pack[3]);
       touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers);
       this.actions(chain, touchId, i, keyModifiers, cb);
       break;
 
     case "release":
       this.generateEvents(
           "release",
@@ -247,17 +247,17 @@ action.Chain.prototype.actions = functio
           touchId,
           null,
           keyModifiers);
       this.actions(chain, null, i, keyModifiers, cb);
       this.scrolling =  false;
       break;
 
     case "move":
-      el = this.seenEls.get(pack[1], this.container);
+      el = this.seenEls.get(pack[1]);
       c = element.coordinates(el);
       this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers);
       this.actions(chain, touchId, i, keyModifiers, cb);
       break;
 
     case "moveByOffset":
       this.generateEvents(
           "move",
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -876,17 +876,17 @@ function emitTouchEvent(type, touch) {
         0);
   }
 }
 
 /**
  * Function that perform a single tap
  */
 async function singleTap(id, corx, cory) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   // after this block, the element will be scrolled into view
   let visible = element.isVisible(el, corx, cory);
   if (!visible) {
     throw new ElementNotInteractableError(
         "Element is not currently visible and may not be manipulated");
   }
 
   let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
@@ -1026,17 +1026,17 @@ function setDispatch(batches, touches, b
   batchIndex++;
   for (let i = 0; i < batch.length; i++) {
     pack = batch[i];
     touchId = pack[0];
     command = pack[1];
 
     switch (command) {
       case "press":
-        el = seenEls.get(pack[2], curContainer);
+        el = seenEls.get(pack[2]);
         c = element.coordinates(el, pack[3], pack[4]);
         touch = createATouch(el, c.x, c.y, touchId);
         multiLast[touchId] = touch;
         touches.push(touch);
         emitMultiEvents("touchstart", touch, touches);
         break;
 
       case "release":
@@ -1044,17 +1044,17 @@ function setDispatch(batches, touches, b
         // the index of the previous touch for the finger may change in
         // the touches array
         touchIndex = touches.indexOf(touch);
         touches.splice(touchIndex, 1);
         emitMultiEvents("touchend", touch, touches);
         break;
 
       case "move":
-        el = seenEls.get(pack[2], curContainer);
+        el = seenEls.get(pack[2]);
         c = element.coordinates(el);
         touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
         touchIndex = touches.indexOf(lastTouch);
         touches[touchIndex] = touch;
         multiLast[touchId] = touch;
         emitMultiEvents("touchmove", touch, touches);
         break;
 
@@ -1299,17 +1299,17 @@ function getPageSource() {
  */
 async function findElementContent(strategy, selector, opts = {}) {
   if (!SUPPORTED_STRATEGIES.has(strategy)) {
     throw new InvalidSelectorError("Strategy not supported: " + strategy);
   }
 
   opts.all = false;
   if (opts.startNode) {
-    opts.startNode = seenEls.get(opts.startNode, curContainer);
+    opts.startNode = seenEls.get(opts.startNode);
   }
 
   let el = await element.find(curContainer, strategy, selector, opts);
   let elRef = seenEls.add(el);
   let webEl = element.makeWebElement(elRef);
   return webEl;
 }
 
@@ -1319,17 +1319,17 @@ async function findElementContent(strate
  */
 async function findElementsContent(strategy, selector, opts = {}) {
   if (!SUPPORTED_STRATEGIES.has(strategy)) {
     throw new InvalidSelectorError("Strategy not supported: " + strategy);
   }
 
   opts.all = true;
   if (opts.startNode) {
-    opts.startNode = seenEls.get(opts.startNode, curContainer);
+    opts.startNode = seenEls.get(opts.startNode);
   }
 
   let els = await element.find(curContainer, strategy, selector, opts);
   let elRefs = seenEls.addAll(els);
   let webEls = elRefs.map(element.makeWebElement);
   return webEls;
 }
 
@@ -1360,79 +1360,79 @@ function clickElement(msg) {
     let target = getElementAttribute(id, "target");
 
     if (target === "_blank") {
       loadEventExpected = false;
     }
 
     loadListener.navigate(() => {
       return interaction.clickElement(
-          seenEls.get(id, curContainer),
+          seenEls.get(id),
           capabilities.get("moz:accessibilityChecks"),
           capabilities.get("specificationLevel") >= 1
       );
     }, commandID, pageTimeout, loadEventExpected, true);
 
   } catch (e) {
     sendError(e, commandID);
   }
 }
 
 function getElementAttribute(id, name) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   if (element.isBooleanAttribute(el, name)) {
     if (el.hasAttribute(name)) {
       return "true";
     }
     return null;
   }
   return el.getAttribute(name);
 }
 
 function getElementProperty(id, name) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return typeof el[name] != "undefined" ? el[name] : null;
 }
 
 /**
  * Get the text of this element. This includes text from child elements.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {string}
  *     Text of element.
  */
 function getElementText(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return atom.getElementText(el, curContainer.frame);
 }
 
 /**
  * Get the tag name of an element.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {string}
  *     Tag name of element.
  */
 function getElementTagName(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return el.tagName.toLowerCase();
 }
 
 /**
  * Determine the element displayedness of the given web element.
  *
  * Also performs additional accessibility checks if enabled by session
  * capability.
  */
 function isElementDisplayed(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return interaction.isElementDisplayed(
       el, capabilities.get("moz:accessibilityChecks"));
 }
 
 /**
  * Retrieves the computed value of the given CSS property of the given
  * web element.
  *
@@ -1440,32 +1440,32 @@ function isElementDisplayed(id) {
  *     Web element reference.
  * @param {String} prop
  *     The CSS property to get.
  *
  * @return {String}
  *     Effective value of the requested CSS property.
  */
 function getElementValueOfCssProperty(id, prop) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   let st = curContainer.frame.document.defaultView.getComputedStyle(el);
   return st.getPropertyValue(prop);
 }
 
 /**
  * Get the position and dimensions of the element.
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {Object.<string, number>}
  *     The x, y, width, and height properties of the element.
  */
 function getElementRect(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   let clientRect = el.getBoundingClientRect();
   return {
     x: clientRect.x + curContainer.frame.pageXOffset,
     y: clientRect.y + curContainer.frame.pageYOffset,
     width: clientRect.width,
     height: clientRect.height,
   };
 }
@@ -1475,50 +1475,50 @@ function getElementRect(id) {
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {boolean}
  *     True if enabled, false otherwise.
  */
 function isElementEnabled(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return interaction.isElementEnabled(
       el, capabilities.get("moz:accessibilityChecks"));
 }
 
 /**
  * Determines if the referenced element is selected or not.
  *
  * This operation only makes sense on input elements of the Checkbox-
  * and Radio Button states, or option elements.
  */
 function isElementSelected(id) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   return interaction.isElementSelected(
       el, capabilities.get("moz:accessibilityChecks"));
 }
 
 async function sendKeysToElement(id, val) {
-  let el = seenEls.get(id, curContainer);
+  let el = seenEls.get(id);
   if (el.type == "file") {
     await interaction.uploadFile(el, val);
   } else if ((el.type == "date" || el.type == "time") &&
       Preferences.get("dom.forms.datetime")) {
     interaction.setFormControlValue(el, val);
   } else {
     await interaction.sendKeysToElement(
         el, val, false, capabilities.get("moz:accessibilityChecks"));
   }
 }
 
 /** Clear the text of an element. */
 function clearElement(id) {
   try {
-    let el = seenEls.get(id, curContainer);
+    let el = seenEls.get(id);
     if (el.type == "file") {
       el.value = null;
     } else {
       atom.clearElement(el, curContainer.frame);
     }
   } catch (e) {
     // Bug 964738: Newer atoms contain status codes which makes wrapping
     // this in an error prototype that has a status property unnecessary
@@ -1554,17 +1554,17 @@ function switchToShadowRoot(id) {
         parent = parent.parentNode;
       }
       curContainer.shadowRoot = parent;
     }
     return;
   }
 
   let foundShadowRoot;
-  let hostEl = seenEls.get(id, curContainer);
+  let hostEl = seenEls.get(id);
   foundShadowRoot = hostEl.shadowRoot;
   if (!foundShadowRoot) {
     throw new NoSuchElementError("Unable to locate shadow root: " + id);
   }
   curContainer.shadowRoot = foundShadowRoot;
 }
 
 /**
@@ -1621,17 +1621,17 @@ function switchToFrame(msg) {
     sendOk(commandID);
     return;
   }
 
   let id = msg.json.element;
   if (seenEls.has(id)) {
     let wantedFrame;
     try {
-      wantedFrame = seenEls.get(id, curContainer);
+      wantedFrame = seenEls.get(id);
     } catch (e) {
       sendError(e, commandID);
     }
 
     if (frames.length > 0) {
       for (let i = 0; i < frames.length; i++) {
         // use XPCNativeWrapper to compare elements; see bug 834266
         let frameEl = frames[i].frameElement;
@@ -1754,29 +1754,29 @@ function switchToFrame(msg) {
  *     Base64 encoded string or a SHA-256 hash of the screenshot.
  */
 function takeScreenshot(format, opts = {}) {
   let id = opts.id;
   let full = !!opts.full;
   let highlights = opts.highlights || [];
   let scroll = !!opts.scroll;
 
-  let highlightEls = highlights.map(ref => seenEls.get(ref, curContainer));
+  let highlightEls = highlights.map(ref => seenEls.get(ref));
 
   let canvas;
 
   // viewport
   if (!id && !full) {
     canvas = capture.viewport(curContainer.frame, highlightEls);
 
   // element or full document element
   } else {
     let el;
     if (id) {
-      el = seenEls.get(id, curContainer);
+      el = seenEls.get(id);
       if (scroll) {
         element.scrollIntoView(el);
       }
     } else {
       el = curContainer.frame.document.documentElement;
     }
 
     canvas = capture.element(el, highlightEls);