Bug 1274274 - Refactor seen element store; r?automatedtester draft
authorAndreas Tolfsen <ato@mozilla.com>
Fri, 20 May 2016 15:07:21 +0100
changeset 370717 701e18bb990227960df41d2f3ebf492b46f3aef6
parent 370716 78079075037133904c128a65d5481aee8d9ebeb5
child 370718 601af89c48ac20edefa1ec16fa107de1edb75b3e
push id19137
push userbmo:ato@mozilla.com
push dateWed, 25 May 2016 09:03:54 +0000
reviewersautomatedtester
bugs1274274
milestone49.0a1
Bug 1274274 - Refactor seen element store; r?automatedtester Renames ElementManager to element.Store, exposing it on the testing/marionette/element.js module. Shortens getKnownElement(uuid) to get(uuid). Introduces new method has(uuid) to replace some unnecessary checks in testing/marionette/driver.js and testing/marionette/listener.js. MozReview-Commit-ID: D5qAlqrIxi
testing/marionette/action.js
testing/marionette/browser.js
testing/marionette/driver.js
testing/marionette/element.js
testing/marionette/listener.js
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -194,17 +194,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.elementManager.getKnownElement(pack[1], this.container);
+      el = this.elementManager.get(pack[1], this.container);
       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);
       }
@@ -225,17 +225,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.elementManager.getKnownElement(pack[1], this.container);
+      el = this.elementManager.get(pack[1], this.container);
       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",
@@ -244,17 +244,17 @@ action.Chain.prototype.actions = functio
           touchId,
           null,
           keyModifiers);
       this.actions(chain, null, i, keyModifiers, cb);
       this.scrolling =  false;
       break;
 
     case "move":
-      el = this.elementManager.getKnownElement(pack[1], this.container);
+      el = this.elementManager.get(pack[1], this.container);
       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/browser.js
+++ b/testing/marionette/browser.js
@@ -37,21 +37,17 @@ browser.Context = class {
     this.window = win;
     this.driver = driver;
     this.knownFrames = [];
     this.startPage = "about:blank";
     // used in B2G to identify the homescreen content page
     this.mainContentId = null;
     // used to set curFrameId upon new session
     this.newSession = true;
-    this.elementManager = new ElementManager([
-      element.Strategy.Name,
-      element.Strategy.LinkText,
-      element.Strategy.PartialLinkText,
-    ]);
+    this.elementManager = new element.Store();
     this.setBrowser(win);
 
     // A reference to the tab corresponding to the current window handle, if any.
     this.tab = null;
     this.pendingCommands = [];
 
     // we should have one FM per BO so that we can handle modals in each Browser
     this.frameManager = new frame.Manager(driver);
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1415,68 +1415,65 @@ GeckoDriver.prototype.switchToFrame = fu
       if (focus) {
         this.mainFrame.focus();
       }
       checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
       return;
     }
 
     // by element
-    if (typeof element != "undefined") {
-      if (this.curBrowser.elementManager.seenItems[element]) {
-        // HTMLIFrameElement
-        let wantedFrame = this.curBrowser.elementManager
-            .getKnownElement(element, {frame: curWindow});
-        // Deal with an embedded xul:browser case
-        if (wantedFrame.tagName == "xul:browser" || wantedFrame.tagName == "browser") {
-          curWindow = wantedFrame.contentWindow;
+    if (this.curBrowser.seenEls.has(element)) {
+      // HTMLIFrameElement
+      let wantedFrame = this.curBrowser.elementManager.get(element, {frame: curWindow});
+      // 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();
+        }
+        checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+        return;
+      }
+
+      // Check if the frame is XBL anonymous
+      let parent = curWindow.document.getBindingParent(wantedFrame);
+      // Shadow nodes also show up in getAnonymousNodes, we should ignore them.
+      if (parent && !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
+        let anonNodes = [...curWindow.document.getAnonymousNodes(parent) || []];
+        if (anonNodes.length > 0) {
+          let el = wantedFrame;
+          while (el) {
+            if (anonNodes.indexOf(el) > -1) {
+              curWindow = wantedFrame.contentWindow;
+              this.curFrame = curWindow;
+              if (focus) {
+                this.curFrame.focus();
+              }
+              checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+              return;
+            }
+            el = el.parentNode;
+          }
+        }
+      }
+
+      // else, assume iframe
+      let frames = curWindow.document.getElementsByTagName("iframe");
+      let numFrames = frames.length;
+      for (let i = 0; i < numFrames; i++) {
+        if (new XPCNativeWrapper(frames[i]) == new XPCNativeWrapper(wantedFrame)) {
+          curWindow = frames[i].contentWindow;
           this.curFrame = curWindow;
           if (focus) {
             this.curFrame.focus();
           }
           checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
           return;
         }
-
-        // Check if the frame is XBL anonymous
-        let parent = curWindow.document.getBindingParent(wantedFrame);
-        // Shadow nodes also show up in getAnonymousNodes, we should ignore them.
-        if (parent && !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
-          let anonNodes = [...curWindow.document.getAnonymousNodes(parent) || []];
-          if (anonNodes.length > 0) {
-            let el = wantedFrame;
-            while (el) {
-              if (anonNodes.indexOf(el) > -1) {
-                curWindow = wantedFrame.contentWindow;
-                this.curFrame = curWindow;
-                if (focus) {
-                  this.curFrame.focus();
-                }
-                checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
-                return;
-              }
-              el = el.parentNode;
-            }
-          }
-        }
-
-        // else, assume iframe
-        let frames = curWindow.document.getElementsByTagName("iframe");
-        let numFrames = frames.length;
-        for (let i = 0; i < numFrames; i++) {
-          if (new XPCNativeWrapper(frames[i]) == new XPCNativeWrapper(wantedFrame)) {
-            curWindow = frames[i].contentWindow;
-            this.curFrame = curWindow;
-            if (focus) {
-              this.curFrame.focus();
-            }
-            checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
-            return;
-          }
-        }
       }
     }
 
     switch (typeof id) {
       case "string" :
         let foundById = null;
         let frames = curWindow.document.getElementsByTagName("iframe");
         let numFrames = frames.length;
@@ -1676,17 +1673,17 @@ GeckoDriver.prototype.findElement = func
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
       let container = {frame: this.getCurrentWindow()};
       if (opts.startNode) {
-        opts.startNode = this.curBrowser.elementManager.getKnownElement(opts.startNode, container);
+        opts.startNode = this.curBrowser.elementManager.get(opts.startNode, container);
       }
       let el = yield element.find(container, strategy, expr, opts);
       let elRef = this.curBrowser.elementManager.add(el);
       let webEl = element.makeWebElement(elRef);
 
       resp.body.value = webEl;
       break;
 
@@ -1719,17 +1716,17 @@ GeckoDriver.prototype.findElements = fun
   switch (this.context) {
     case Context.CHROME:
       if (!SUPPORTED_STRATEGIES.has(strategy)) {
         throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
       }
 
       let container = {frame: this.getCurrentWindow()};
       if (opts.startNode) {
-        opts.startNode = this.curBrowser.elementManager.getKnownElement(opts.startNode, container);
+        opts.startNode = this.curBrowser.elementManager.get(opts.startNode, container);
       }
       let els = yield element.find(container, strategy, expr, opts);
 
       let elRefs = this.curBrowser.elementManager.addAll(els);
       let webEls = elRefs.map(element.makeWebElement);
       resp.body = webEls;
       break;
 
@@ -1754,17 +1751,17 @@ GeckoDriver.prototype.getActiveElement =
  *     Reference ID to the element that will be clicked.
  */
 GeckoDriver.prototype.clickElement = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       yield interaction.clickElement(
           el, this.sessionCapabilities.raisesAccessibilityExceptions);
       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
@@ -1787,17 +1784,17 @@ GeckoDriver.prototype.clickElement = fun
  *     Value of the attribute.
  */
 GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
   let {id, name} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       resp.body.value = atom.getElementAttribute(el, name, this.getCurrentWindow());
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementAttribute(id, name);
       break;
   }
 };
@@ -1834,17 +1831,17 @@ GeckoDriver.prototype.getElementProperty
  */
 GeckoDriver.prototype.getElementText = function*(cmd, resp) {
   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 win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       let lines = [];
       this.getVisibleText(el, lines);
       resp.body.value = lines.join("\n");
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementText(id);
       break;
@@ -1858,17 +1855,17 @@ GeckoDriver.prototype.getElementText = f
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.getElementTagName = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       resp.body.value = el.tagName.toLowerCase();
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementTagName(id);
       break;
   }
 };
@@ -1880,18 +1877,17 @@ GeckoDriver.prototype.getElementTagName 
  *     Reference ID to the element that will be inspected.
  */
 GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(
-          id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       resp.body.value = yield interaction.isElementDisplayed(
           el, this.sessionCapabilities.raisesAccessibilityExceptions);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementDisplayed(id);
       break;
   }
@@ -1906,17 +1902,17 @@ GeckoDriver.prototype.isElementDisplayed
  *     CSS rule that is being requested.
  */
 GeckoDriver.prototype.getElementValueOfCssProperty = function*(cmd, resp) {
   let {id, propertyName: prop} = cmd.parameters;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       let sty = win.document.defaultView.getComputedStyle(el, null);
       resp.body.value = sty.getPropertyValue(prop);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.getElementValueOfCssProperty(id, prop);
       break;
   }
@@ -1930,18 +1926,17 @@ GeckoDriver.prototype.getElementValueOfC
  */
 GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(
-          id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       resp.body.value = yield interaction.isElementEnabled(
           el, this.sessionCapabilities.raisesAccessibilityExceptions);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementEnabled(id);
       break;
   }
@@ -1955,35 +1950,34 @@ GeckoDriver.prototype.isElementEnabled =
  */
 GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(
-          id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       resp.body.value = yield interaction.isElementSelected(
           el, this.sessionCapabilities.raisesAccessibilityExceptions);
       break;
 
     case Context.CONTENT:
       resp.body.value = yield this.listener.isElementSelected(id);
       break;
   }
 };
 
 GeckoDriver.prototype.getElementRect = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       let rect = el.getBoundingClientRect();
       resp.body = {
         x: rect.x + win.pageXOffset,
         y: rect.y + win.pageYOffset,
         width: rect.width,
         height: rect.height
       };
       break;
@@ -2007,18 +2001,17 @@ GeckoDriver.prototype.sendKeysToElement 
 
   if (!value) {
     throw new InvalidArgumentError(`Expected character sequence: ${value}`);
   }
 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(
-          id, {frame: win});
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       yield interaction.sendKeysToElement(
           el, value, true, this.sessionCapabilities.raisesAccessibilityExceptions);
       break;
 
     case Context.CONTENT:
       let err;
       let listener = function(msg) {
         this.mm.removeMessageListener("Marionette:setElementValue", listener);
@@ -2068,17 +2061,17 @@ GeckoDriver.prototype.setTestName = func
  */
 GeckoDriver.prototype.clearElement = function*(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
     case Context.CHROME:
       // the selenium atom doesn't work here
       let win = this.getCurrentWindow();
-      let el = this.curBrowser.elementManager.getKnownElement(id, { frame: win });
+      let el = this.curBrowser.elementManager.get(id, {frame: win});
       if (el.nodeName == "textbox") {
         el.value = "";
       } else if (el.nodeName == "checkbox") {
         el.checked = false;
       }
       break;
 
     case Context.CONTENT:
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -54,27 +54,31 @@ element.Strategy = {
   LinkText: "link text",
   PartialLinkText: "partial link text",
   TagName: "tag name",
   XPath: "xpath",
   Anon: "anon",
   AnonAttribute: "anon attribute",
 };
 
-this.ElementManager = class {
+/**
+ * Stores known/seen elements and their associated web element
+ * references.
+ *
+ * Elements are added by calling |add(el)| or |addAll(elements)|, and
+ * may be queried by their web element reference using |get(element)|.
+ */
+element.Store = class {
   constructor() {
-    this.seenItems = {};
+    this.els = {};
     this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   }
 
-  /**
-   * Reset values
-   */
   reset() {
-    this.seenItems = {};
+    this.els = {};
   }
 
   /**
    * Make a collection of elements seen.
    *
    * The oder of the returned web element references is guaranteed to
    * match that of the collection passed in.
    *
@@ -95,105 +99,136 @@ this.ElementManager = class {
    *
    * @param {nsIDOMElement} el
    *    Element to add to set of seen elements.
    *
    * @return {string}
    *     Web element reference associated with element.
    */
   add(el) {
-    for (let i in this.seenItems) {
+    for (let i in this.els) {
       let foundEl;
       try {
-        foundEl = this.seenItems[i].get();
+        foundEl = this.els[i].get();
       } catch (e) {}
 
       if (foundEl) {
-        if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(el)) {
+        if (new XPCNativeWrapper(foundEl) == new XPCNativeWrapper(el)) {
           return i;
         }
+
+      // cleanup reference to gc'd element
       } else {
-        // cleanup reference to GC'd element
-        delete this.seenItems[i];
+        delete this.els[i];
       }
     }
 
     let id = element.generateUUID();
-    this.seenItems[id] = Cu.getWeakReference(el);
+    this.els[id] = Cu.getWeakReference(el);
     return id;
   }
 
   /**
-   * Retrieve element from its unique ID
+   * Determine if the provided web element reference has been seen
+   * before/is in the element store.
+   *
+   * @param {string} uuid
+   *     Element's associated web element reference.
    *
-   * @param String id
-   *        The DOM reference ID
-   * @param nsIDOMWindow, ShadowRoot container
-   *        The window and an optional shadow root that contains the element
+   * @return {boolean}
+   *     True if element is in the store, false otherwise.
+   */
+  has(uuid) {
+    return Object.keys(this.els).includes(uuid);
+  }
+
+  /**
+   * Retrieve a DOM element by its unique web element reference/UUID.
    *
-   * @returns nsIDOMElement
-   *        Returns the element or throws Exception if not found
+   * @param {string} uuid
+   *     Web element reference, or UUID.
+   * @param {(nsIDOMWindow|ShadowRoot)} container
+   * Window and an optional shadow root that contains the element.
+   *
+   * @returns {nsIDOMElement}
+   *     Element associated with reference.
+   *
+   * @throws {JavaScriptError}
+   *     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.
    */
-  getKnownElement(id, container) {
-    let el = this.seenItems[id];
+  get(uuid, container) {
+    let el = this.els[uuid];
     if (!el) {
-      throw new JavaScriptError(`Element has not been seen before. Id given was ${id}`);
+      throw new JavaScriptError(`Element reference not seen before: ${uuid}`);
     }
+
     try {
       el = el.get();
-    }
-    catch(e) {
+    } catch (e) {
       el = null;
-      delete this.seenItems[id];
+      delete this.els[id];
     }
-    // use XPCNativeWrapper to compare elements; see bug 834266
-    let wrappedFrame = XPCNativeWrapper(container.frame);
+
+    // use XPCNativeWrapper to compare elements (see bug 834266)
+    let wrappedFrame = new XPCNativeWrapper(container.frame);
     let wrappedShadowRoot;
     if (container.shadowRoot) {
-      wrappedShadowRoot = XPCNativeWrapper(container.shadowRoot);
+      wrappedShadowRoot = new XPCNativeWrapper(container.shadowRoot);
     }
 
+    let wrappedEl = new XPCNativeWrapper(el);
     if (!el ||
-        !(XPCNativeWrapper(el).ownerDocument == wrappedFrame.document) ||
-        this.isDisconnected(XPCNativeWrapper(el), wrappedShadowRoot,
-          wrappedFrame)) {
+        !(wrappedEl.ownerDocument == wrappedFrame.document) ||
+        this.isDisconnected(wrappedEl, wrappedFrame, wrappedShadowRoot)) {
       throw new StaleElementReferenceError(
           "The element reference is stale. Either the element " +
           "is no longer attached to the DOM or the page has been refreshed.");
     }
+
     return el;
   }
 
   /**
-   * Check if the element is detached from the current frame as well as the
-   * optional shadow root (when inside a Shadow DOM context).
-   * @param nsIDOMElement el
-   *        element to be checked
-   * @param ShadowRoot shadowRoot
-   *        an optional shadow root containing an element
+   * Check if the element is detached from the current frame as well as
+   * the optional shadow root (when inside a Shadow DOM context).
+   *
+   * @param {nsIDOMElement} el
+   *     Element to be checked.
    * @param nsIDOMWindow frame
-   *        window that contains the element or the current host of the shadow
-   *        root.
-   * @return {Boolean} a flag indicating that the element is disconnected
+   *     Window object that contains the element or the current host
+   *     of the shadow root.
+   * @param {ShadowRoot=} shadowRoot
+   *     An optional shadow root containing an element.
+   *
+   * @return {boolean}
+   *     Flag indicating that the element is disconnected.
    */
-  isDisconnected(el, shadowRoot, frame) {
+  isDisconnected(el, frame, shadowRoot = undefined) {
+    // shadow dom
     if (shadowRoot && frame.ShadowRoot) {
       if (el.compareDocumentPosition(shadowRoot) &
-        DOCUMENT_POSITION_DISCONNECTED) {
+          DOCUMENT_POSITION_DISCONNECTED) {
         return true;
       }
-      // Looking for next possible ShadowRoot ancestor
+
+      // looking for next possible ShadowRoot ancestor
       let parent = shadowRoot.host;
       while (parent && !(parent instanceof frame.ShadowRoot)) {
         parent = parent.parentNode;
       }
       return this.isDisconnected(shadowRoot.host, parent, frame);
+
+    // outside shadow dom
     } else {
-      return el.compareDocumentPosition(frame.document.documentElement) &
-        DOCUMENT_POSITION_DISCONNECTED;
+      let docEl = frame.document.documentElement;
+      return el.compareDocumentPosition(docEl) &
+          DOCUMENT_POSITION_DISCONNECTED;
     }
   }
 
   /**
    * Convert values to primitives that can be transported over the
    * Marionette protocol.
    *
    * This function implements the marshaling algorithm defined in the
@@ -283,17 +318,17 @@ this.ElementManager = class {
           for (let i in args) {
             converted.push(this.convertWrappedArguments(args[i], container));
           }
         }
         else if (((typeof(args[element.LegacyKey]) === 'string') && args.hasOwnProperty(element.LegacyKey)) ||
                  ((typeof(args[element.Key]) === 'string') &&
                      args.hasOwnProperty(element.Key))) {
           let elementUniqueIdentifier = args[element.Key] ? args[element.Key] : args[element.LegacyKey];
-          converted = this.getKnownElement(elementUniqueIdentifier, container);
+          converted = this.get(elementUniqueIdentifier, container);
           if (converted == null) {
             throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`);
           }
         }
         else {
           converted = {};
           for (let prop in args) {
             converted[prop] = this.convertWrappedArguments(args[prop], container);
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -38,17 +38,17 @@ var isB2G = false;
 var marionetteTestName;
 var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindowUtils);
 var listenerId = null; // unique ID of this listener
 var curContainer = { frame: content, shadowRoot: null };
 var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
 var previousContainer = null;
 
-var elementManager = new ElementManager();
+var elementManager = new element.Store();
 var SUPPORTED_STRATEGIES = new Set([
   element.Strategy.ClassName,
   element.Strategy.Selector,
   element.Strategy.ID,
   element.Strategy.Name,
   element.Strategy.LinkText,
   element.Strategy.PartialLinkText,
   element.Strategy.TagName,
@@ -642,17 +642,17 @@ function emitTouchEvent(type, touch) {
     domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
   }
 }
 
 /**
  * Function that perform a single tap
  */
 function singleTap(id, corx, cory) {
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   // after this block, the element will be scrolled into view
   let visible = element.isVisible(el, corx, cory);
   if (!visible) {
     throw new ElementNotVisibleError("Element is not currently visible and may not be manipulated");
   }
 
   let a11y = accessibility.get(capabilities.raisesAccessibilityExceptions);
   return a11y.getAccessible(el, true).then(acc => {
@@ -767,34 +767,34 @@ 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 = elementManager.getKnownElement(pack[2], curContainer);
+        el = elementManager.get(pack[2], curContainer);
         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":
         touch = multiLast[touchId];
         // 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 = elementManager.getKnownElement(pack[2], curContainer);
+        el = elementManager.get(pack[2], curContainer);
         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;
 
@@ -1026,17 +1026,17 @@ function refresh(msg) {
  */
 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 = elementManager.getKnownElement(opts.startNode, curContainer);
+    opts.startNode = elementManager.get(opts.startNode, curContainer);
   }
 
   let el = yield element.find(curContainer, strategy, selector, opts);
   let elRef = elementManager.add(el);
   let webEl = element.makeWebElement(elRef);
   return webEl;
 }
 
@@ -1046,17 +1046,17 @@ function* findElementContent(strategy, s
  */
 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 = elementManager.getKnownElement(opts.startNode, curContainer);
+    opts.startNode = elementManager.get(opts.startNode, curContainer);
   }
 
   let els = yield element.find(curContainer, strategy, selector, opts);
   let elRefs = elementManager.addAll(els);
   let webEls = elRefs.map(element.makeWebElement);
   return webEls;
 }
 
@@ -1077,77 +1077,77 @@ function getActiveElement() {
 
 /**
  * Send click event to element.
  *
  * @param {WebElement} id
  *     Reference to the web element to click.
  */
 function clickElement(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   return interaction.clickElement(
       el,
       !!capabilities.raisesAccessibilityExceptions,
       capabilities.specificationLevel >= 1);
 }
 
 function getElementAttribute(id, name) {
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   if (element.isBooleanAttribute(el, name)) {
     if (el.hasAttribute(name)) {
       return "true";
     } else {
       return null;
     }
   } else {
     return el.getAttribute(name);
   }
 }
 
 function getElementProperty(id, name) {
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   return el[name];
 }
 
 /**
  * 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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   return interaction.isElementDisplayed(
       el, capabilities.raisesAccessibilityExceptions);
 }
 
 /**
  * Retrieves the computed value of the given CSS property of the given
  * web element.
  *
@@ -1155,32 +1155,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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   let st = curContainer.frame.document.defaultView.getComputedStyle(el, null);
   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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   let clientRect = el.getBoundingClientRect();
   return {
     x: clientRect.x + curContainer.frame.pageXOffset,
     y: clientRect.y  + curContainer.frame.pageYOffset,
     width: clientRect.width,
     height: clientRect.height
   };
 }
@@ -1190,41 +1190,41 @@ function getElementRect(id) {
  *
  * @param {WebElement} id
  *     Reference to web element.
  *
  * @return {boolean}
  *     True if enabled, false otherwise.
  */
 function isElementEnabled(id) {
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   return interaction.isElementEnabled(
       el, capabilities.raisesAccessibilityExceptions);
 }
 
 /**
  * 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 = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
   return interaction.isElementSelected(
       el, capabilities.raisesAccessibilityExceptions);
 }
 
 /**
  * Send keys to element
  */
 function sendKeysToElement(msg) {
   let command_id = msg.json.command_id;
   let val = msg.json.value;
   let id = msg.json.id;
-  let el = elementManager.getKnownElement(id, curContainer);
+  let el = elementManager.get(id, curContainer);
 
   if (el.type == "file") {
     let p = val.join("");
         fileInputElement = el;
         // In e10s, we can only construct File objects in the parent process,
         // so pass the filename to driver.js, which in turn passes them back
         // to this frame script in receiveFiles.
         sendSyncMessage("Marionette:getFiles",
@@ -1237,17 +1237,17 @@ function sendKeysToElement(msg) {
   }
 }
 
 /**
  * Clear the text of an element.
  */
 function clearElement(id) {
   try {
-    let el = elementManager.getKnownElement(id, curContainer);
+    let el = elementManager.get(id, curContainer);
     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
@@ -1282,17 +1282,17 @@ function switchToShadowRoot(id) {
         parent = parent.parentNode;
       }
       curContainer.shadowRoot = parent;
     }
     return;
   }
 
   let foundShadowRoot;
-  let hostEl = elementManager.getKnownElement(id, curContainer);
+  let hostEl = elementManager.get(id, curContainer);
   foundShadowRoot = hostEl.shadowRoot;
   if (!foundShadowRoot) {
     throw new NoSuchElementError('Unable to locate shadow root: ' + id);
   }
   curContainer.shadowRoot = foundShadowRoot;
 }
 
 /**
@@ -1352,48 +1352,50 @@ function switchToFrame(msg) {
     curContainer.frame = content;
     if(msg.json.focus == true) {
       curContainer.frame.focus();
     }
 
     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
     return;
   }
-  if (msg.json.element != undefined) {
-    if (elementManager.seenItems[msg.json.element] != undefined) {
-      let wantedFrame;
-      try {
-        wantedFrame = elementManager.getKnownElement(msg.json.element, curContainer); //Frame Element
-      } catch (e) {
-        sendError(e, command_id);
-      }
 
-      if (frames.length > 0) {
-        for (let i = 0; i < frames.length; i++) {
-          // use XPCNativeWrapper to compare elements; see bug 834266
-          if (XPCNativeWrapper(frames[i].frameElement) == XPCNativeWrapper(wantedFrame)) {
-            curContainer.frame = frames[i].frameElement;
-            foundFrame = i;
-          }
+  let id = msg.json.element;
+  if (elementManager.has(id)) {
+    let wantedFrame;
+    try {
+      wantedFrame = elementManager.get(id, curContainer);
+    } catch (e) {
+      sendError(e, command_id);
+    }
+
+    if (frames.length > 0) {
+      for (let i = 0; i < frames.length; i++) {
+        // use XPCNativeWrapper to compare elements; see bug 834266
+        if (XPCNativeWrapper(frames[i].frameElement) == XPCNativeWrapper(wantedFrame)) {
+          curContainer.frame = frames[i].frameElement;
+          foundFrame = i;
         }
       }
-      if (foundFrame === null) {
-        // Either the frame has been removed or we have a OOP frame
-        // so lets just get all the iframes and do a quick loop before
-        // throwing in the towel
-        let iframes = curContainer.frame.document.getElementsByTagName("iframe");
-        for (var i = 0; i < iframes.length; i++) {
-          if (XPCNativeWrapper(iframes[i]) == XPCNativeWrapper(wantedFrame)) {
-            curContainer.frame = iframes[i];
-            foundFrame = i;
-          }
+    }
+
+    if (foundFrame === null) {
+      // Either the frame has been removed or we have a OOP frame
+      // so lets just get all the iframes and do a quick loop before
+      // throwing in the towel
+      let iframes = curContainer.frame.document.getElementsByTagName("iframe");
+      for (var i = 0; i < iframes.length; i++) {
+        if (XPCNativeWrapper(iframes[i]) == XPCNativeWrapper(wantedFrame)) {
+          curContainer.frame = iframes[i];
+          foundFrame = i;
         }
       }
     }
   }
+
   if (foundFrame === null) {
     if (typeof(msg.json.id) === 'number') {
       try {
         foundFrame = frames[msg.json.id].frameElement;
         if (foundFrame !== null) {
           curContainer.frame = foundFrame;
           foundFrame = elementManager.add(curContainer.frame);
         }
@@ -1562,29 +1564,29 @@ function getScreenshotHash(id, full=true
 * @return {HTMLCanvasElement}
 *     The canvas element to be encoded or hashed.
 */
 function screenshot(id, full=true, highlights=[]) {
   let canvas;
 
   let highlightEls = [];
   for (let h of highlights) {
-    let el = elementManager.getKnownElement(h, curContainer);
+    let el = elementManager.get(h, curContainer);
     highlightEls.push(el);
   }
 
   // viewport
   if (!id && !full) {
     canvas = capture.viewport(curContainer.frame.document, highlightEls);
 
   // element or full document element
   } else {
     let node;
     if (id) {
-      node = elementManager.getKnownElement(id, curContainer);
+      node = elementManager.get(id, curContainer);
     } else {
       node = curContainer.frame.document.documentElement;
     }
 
     canvas = capture.element(node, highlightEls);
   }
 
   return canvas;