Bug 1409040 - Make element.isXULElement more resiliant. r?maja_zf draft
authorAndreas Tolfsen <ato@sny.no>
Sat, 16 Sep 2017 14:37:21 +0100
changeset 682758 370f1858ff14efbc49ff0b65c927332d9d679385
parent 682254 a29052590fc6538b89641e690d11731ca8e78120
child 682759 dc5b9d0e522971ebe77cc3f52f117df03784df8a
push id85133
push userbmo:ato@sny.no
push dateWed, 18 Oct 2017 18:09:06 +0000
reviewersmaja_zf
bugs1409040
milestone58.0a1
Bug 1409040 - Make element.isXULElement more resiliant. r?maja_zf Fixes isXULElement to recognise XBL elements, such as <xbl:framebox>. Also tightens up the input checks so that arbitrary objects can be tested, as checking node.namespaceURI directly could cause a JS error if node is not an object. Before checking the namespace we also ensure it's an element node so that text- and comment nodes are not picked up. This patch also introduces tests, which were sorely missing. MozReview-Commit-ID: 8LNF1z3X1gP
testing/marionette/element.js
testing/marionette/test_element.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -14,16 +14,17 @@ const {
   NoSuchElementError,
   StaleElementReferenceError,
 } = Cu.import("chrome://marionette/content/error.js", {});
 const {pprint} = Cu.import("chrome://marionette/content/format.js", {});
 const {PollPromise} = Cu.import("chrome://marionette/content/sync.js", {});
 
 this.EXPORTED_SYMBOLS = ["element"];
 
+const XBLNS = "http://www.mozilla.org/xbl";
 const XMLNS = "http://www.w3.org/1999/xhtml";
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 /** XUL elements that support checked property. */
 const XUL_CHECKED_ELS = new Set([
   "button",
   "checkbox",
   "listitem",
@@ -1045,18 +1046,31 @@ element.isKeyboardInteractable = () => t
  *     Element to scroll into view.
  */
 element.scrollIntoView = function(el) {
   if (el.scrollIntoView) {
     el.scrollIntoView({block: "end", inline: "nearest", behavior: "instant"});
   }
 };
 
-element.isXULElement = function(el) {
-  return el.namespaceURI === XULNS;
+/**
+ * Ascertains whether <var>el</var> is a XUL- or XBL element.
+ *
+ * @param {*} node
+ *     Element thought to be a XUL- or XBL element.
+ *
+ * @return {boolean}
+ *     True if <var>node</var> is a XULElement or XBLElement,
+ *     false otherwise.
+ */
+element.isXULElement = function(node) {
+  return typeof node == "object" &&
+      node !== null &&
+      node.nodeType === node.ELEMENT_NODE &&
+      [XBLNS, XULNS].includes(node.namespaceURI);
 };
 
 const boolEls = {
   audio: ["autoplay", "controls", "loop", "muted"],
   button: ["autofocus", "disabled", "formnovalidate"],
   details: ["open"],
   dialog: ["open"],
   fieldset: ["disabled"],
--- a/testing/marionette/test_element.js
+++ b/testing/marionette/test_element.js
@@ -1,48 +1,73 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {utils: Cu} = Components;
 
 Cu.import("chrome://marionette/content/element.js");
 
-class DOMElement {
+const XBLNS = "http://www.mozilla.org/xbl";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+class Element {
   constructor(tagName, attrs = {}) {
     this.tagName = tagName;
     this.localName = tagName;
 
     for (let attr in attrs) {
       this[attr] = attrs[attr];
     }
+  }
+
+  get nodeType() { return 1; }
+  get ELEMENT_NODE() { return 1; }
+}
+
+class DOMElement extends Element {
+  constructor(tagName, attrs = {}) {
+    super(tagName, attrs);
 
     if (this.localName == "option") {
       this.selected = false;
     }
 
     if (this.localName == "input" && ["checkbox", "radio"].includes(this.type)) {
       this.checked = false;
     }
   }
 
-  get nodeType() { return 1; }
-  get ELEMENT_NODE() { return 1; }
-
   getBoundingClientRect() {
     return {
       top: 0,
       left: 0,
       width: 100,
       height: 100,
     };
   }
-};
+}
+
+class XULElement extends Element {
+  constructor(tagName, attrs = {}) {
+    super(tagName, attrs);
+    this.namespaceURI = XULNS;
+  }
+}
+
+class XBLElement extends XULElement {
+  constructor(tagName, attrs = {}) {
+    super(tagName, attrs);
+    this.namespaceURI = XBLNS;
+  }
+}
 
 const domEl = new DOMElement("p");
+const xulEl = new XULElement("browser");
+const xblEl = new XBLElement("framebox");
 
 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>
@@ -63,16 +88,26 @@ add_test(function test_isSelected() {
   // anything else should not be selected
   for (let typ of [domEl, undefined, null, "foo", true, [], {}]) {
     ok(!element.isSelected(typ));
   }
 
   run_next_test();
 });
 
+add_test(function test_isXULElement() {
+  ok(element.isXULElement(xulEl));
+  ok(element.isXULElement(xblEl));
+  for (let typ of [true, 42, {}, [], undefined, null]) {
+    ok(!element.isXULElement(typ));
+  }
+
+  run_next_test();
+});
+
 add_test(function test_coordinates() {
   let p = element.coordinates(domEl);
   ok(p.hasOwnProperty("x"));
   ok(p.hasOwnProperty("y"));
   equal("number", typeof p.x);
   equal("number", typeof p.y);
 
   deepEqual({x: 50, y: 50}, element.coordinates(domEl));