Bug 1274274 - Refactor seen element store; r=automatedtester
authorAndreas Tolfsen <ato@mozilla.com>
Fri, 20 May 2016 15:07:21 +0100
changeset 298883 84c33f8de011c03a27501236ea3def5688f33964
parent 298882 02d31625ba1406044fb706e87bc579c8fb222263
child 298884 7d0cf052c1cea39c798d8efdf95da80a882670fc
push id77358
push useratolfsen@mozilla.com
push dateWed, 25 May 2016 10:34:09 +0000
treeherdermozilla-inbound@769533c99dd8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester
bugs1274274
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -1407,68 +1407,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;
@@ -1668,17 +1665,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;
 
@@ -1711,17 +1708,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;
 
@@ -1746,17 +1743,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
@@ -1779,17 +1776,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;
   }
 };
@@ -1826,17 +1823,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;
@@ -1850,17 +1847,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;
   }
 };
@@ -1872,18 +1869,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;
   }
@@ -1898,17 +1894,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;
   }
@@ -1922,18 +1918,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;
   }
@@ -1947,35 +1942,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;
@@ -1999,18 +1993,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);
@@ -2060,17 +2053,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
@@ -37,17 +37,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,
@@ -639,17 +639,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 => {
@@ -764,34 +764,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;
 
@@ -1023,17 +1023,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;
 }
 
@@ -1043,17 +1043,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;
 }
 
@@ -1074,77 +1074,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.
  *
@@ -1152,32 +1152,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
   };
 }
@@ -1187,41 +1187,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",
@@ -1234,17 +1234,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
@@ -1279,17 +1279,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;
 }
 
 /**
@@ -1349,48 +1349,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);
         }
@@ -1559,29 +1561,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;