Bug 1354211 - Add element.findClosest for finding ancestor by CSS selector. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Sun, 31 Dec 2017 14:39:14 +0000
changeset 719571 328157a76ddd6a3ea61ba139f6c45a93bee4f70b
parent 719570 74258eee3a9e229b4bf29928389241a0b3ad064d
child 719572 f48ce49bc3e39c9d08657c3f4601a2ab67044550
push id95293
push userbmo:ato@sny.no
push dateFri, 12 Jan 2018 10:32:36 +0000
reviewersautomatedtester
bugs1354211
milestone59.0a1
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;