Bug 1354211 - Add element.findClosest for finding ancestor by CSS selector. r=automatedtester
authorAndreas Tolfsen <ato@sny.no>
Sun, 31 Dec 2017 14:39:14 +0000
changeset 453274 9350c2e8e01f8ed1e3fb6f88e291264fa0f68bdb
parent 453273 4cdb2c2eb4c4739b8d54843d30b09f11418da38e
child 453275 1c2ba38f6989bc548a970687a43dc7675aec2791
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester
bugs1354211
milestone59.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 1354211 - Add element.findClosest for finding ancestor by CSS selector. r=automatedtester Introduces a new function, element.findClosest, that finds the closest parent node by a CSS selector expression. This is useful to find arbitrary elements in the tree above us and is quite possibly reusable for element.getContainer. MozReview-Commit-ID: 8rBEepmDdPm
testing/marionette/element.js
testing/marionette/test_element.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -651,16 +651,40 @@ function findElements(strategy, selector
       return [];
 
     default:
       throw new InvalidSelectorError(`No such strategy: ${strategy}`);
   }
 }
 
 /**
+ * Finds the closest parent node of <var>startNode</var> by CSS a
+ * <var>selector</var> expression.
+ *
+ * @param {Node} startNode
+ *     Cyce through <var>startNode</var>'s parent nodes in tree-order
+ *     and return the first match to <var>selector</var>.
+ * @param {string} selector
+ *     CSS selector expression.
+ *
+ * @return {Node=}
+ *     First match to <var>selector</var>, or null if no match was found.
+ */
+element.findClosest = function(startNode, selector) {
+  let node = startNode;
+  while (node.parentNode && node.parentNode.nodeType == ELEMENT_NODE) {
+    node = node.parentNode;
+    if (node.matches(selector)) {
+      return node;
+    }
+  }
+  return null;
+};
+
+/**
  * Determines if <var>obj<var> is an HTML or JS collection.
  *
  * @param {*} seq
  *     Type to determine.
  *
  * @return {boolean}
  *     True if <var>seq</va> is collection.
  */
--- a/testing/marionette/test_element.js
+++ b/testing/marionette/test_element.js
@@ -26,16 +26,23 @@ class Element {
 
     for (let attr in attrs) {
       this[attr] = attrs[attr];
     }
   }
 
   get nodeType() { return 1; }
   get ELEMENT_NODE() { return 1; }
+
+  // this is a severely limited CSS selector
+  // that only supports lists of tag names
+  matches(selector) {
+    let tags = selector.split(",");
+    return tags.includes(this.localName);
+  }
 }
 
 class DOMElement extends Element {
   constructor(tagName, attrs = {}) {
     super(tagName, attrs);
 
     this.namespaceURI = XHTMLNS;
 
@@ -89,16 +96,27 @@ class WindowProxy {
   get self() { return this; }
   toString() { return "[object Window]"; }
 }
 const domWin = new WindowProxy();
 const domFrame = new class extends WindowProxy {
   get parent() { return domWin; }
 };
 
+add_test(function test_findClosest() {
+  equal(element.findClosest(domEl, "foo"), null);
+
+  let foo = new DOMElement("foo");
+  let bar = new DOMElement("bar");
+  bar.parentNode = foo;
+  equal(element.findClosest(bar, "foo"), foo);
+
+  run_next_test();
+});
+
 add_test(function test_isSelected() {
   let checkbox = new DOMElement("input", {type: "checkbox"});
   ok(!element.isSelected(checkbox));
   checkbox.checked = true;
   ok(element.isSelected(checkbox));
 
   // selected is not a property of <input type=checkbox>
   checkbox.selected = true;