Bug 1354211 - Add element.isEditingHost and element.isEditable. r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Sun, 31 Dec 2017 14:52:46 +0000
changeset 719575 c32906294b1b1bc5f089957ea06cee91c63619f8
parent 719574 355b838c62d040190dd39a8d572e9a04da6f593d
child 719576 5cba9c9af3c7412d4dd5d62586741d33a4405174
push id95293
push userbmo:ato@sny.no
push dateFri, 12 Jan 2018 10:32:36 +0000
reviewersautomatedtester
bugs1354211
milestone59.0a1
Bug 1354211 - Add element.isEditingHost and element.isEditable. r?automatedtester Introduces two new functions called element.isEditingHost, for determining if an element is content-editable or part of a document in design mode, and element.isEditable, which implements the WebDriver-defined concept of what it means for an element to be editable. WebDriver consideres an element editable if it is not read-only or disabled, and one of the following conditions are met: - It is a <textarea> element. - It is an element that is not of the checkbox, radio, hidden, submit, button, or image types. - It is content-editable. - It belongs to a document in design mode. MozReview-Commit-ID: 6U71uSUNwoI
testing/marionette/element.js
testing/marionette/test_element.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -29,16 +29,25 @@ this.EXPORTED_SYMBOLS = [
 
 const {
   FIRST_ORDERED_NODE_TYPE,
   ORDERED_NODE_ITERATOR_TYPE,
 } = Ci.nsIDOMXPathResult;
 const ELEMENT_NODE = 1;
 const DOCUMENT_NODE = 9;
 
+const UNEDITABLE_INPUTS = new Set([
+  "checkbox",
+  "radio",
+  "hidden",
+  "submit",
+  "button",
+  "image",
+]);
+
 const XBLNS = "http://www.mozilla.org/xbl";
 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",
@@ -830,16 +839,70 @@ element.isDisabled = function(el) {
       return el.disabled;
 
     default:
       return false;
   }
 };
 
 /**
+ * An editing host is a node that is either an HTML element with a
+ * <code>contenteditable</code> attribute, or the HTML element child
+ * of a document whose <code>designMode</code> is enabled.
+ *
+ * @param {Element} el
+ *     Element to determine if is an editing host.
+ *
+ * @return {boolean}
+ *     True if editing host, false otherwise.
+ */
+element.isEditingHost = function(el) {
+  return element.isDOMElement(el) &&
+      (el.isContentEditable || el.ownerDocument.designMode == "on");
+};
+
+/**
+ * Determines if an element is editable according to WebDriver.
+ *
+ * An element is considered editable if it is not read-only or
+ * disabled, and one of the following conditions are met:
+ *
+ * <ul>
+ * <li>It is a <code>&lt;textarea&gt;</code> element.
+ *
+ * <li>It is an <code>&lt;input&gt;</code> element that is not of
+ * the <code>checkbox</code>, <code>radio</code>, <code>hidden</code>,
+ * <code>submit</code>, <code>button</code>, or <code>image</code> types.
+ *
+ * <li>It is content-editable.
+ *
+ * <li>It belongs to a document in design mode.
+ * </ul>
+ *
+ * @param {Element}
+ *     Element to test if editable.
+ *
+ * @return {boolean}
+ *     True if editable, false otherwise.
+ */
+element.isEditable = function(el) {
+  if (!element.isDOMElement(el)) {
+    return false;
+  }
+
+  if (element.isReadOnly(el) || element.isDisabled(el)) {
+    return false;
+  }
+
+  return (el.localName == "input" && !UNEDITABLE_INPUTS.has(el.type)) ||
+      el.localName == "textarea" ||
+      element.isEditingHost(el);
+};
+
+/**
  * This function generates a pair of coordinates relative to the viewport
  * given a target element and coordinates relative to that element's
  * top-left corner.
  *
  * @param {Node} node
  *     Target node.
  * @param {number=} xOffset
  *     Horizontal offset relative to target's top-left corner.
--- a/testing/marionette/test_element.js
+++ b/testing/marionette/test_element.js
@@ -40,16 +40,19 @@ class Element {
   }
 }
 
 class DOMElement extends Element {
   constructor(tagName, attrs = {}) {
     super(tagName, attrs);
 
     this.namespaceURI = XHTMLNS;
+    if (typeof this.ownerDocument == "undefined") {
+      this.ownerDocument = {designMode: "off"};
+    }
 
     if (this.localName == "option") {
       this.selected = false;
     }
 
     if (this.localName == "input" && ["checkbox", "radio"].includes(this.type)) {
       this.checked = false;
     }
@@ -223,16 +226,43 @@ add_test(function test_isDisabled() {
   ok(element.isDisabled(new DOMElement("button", {disabled: true})));
   ok(element.isDisabled(new DOMElement("input", {disabled: true})));
   ok(element.isDisabled(new DOMElement("select", {disabled: true})));
   ok(element.isDisabled(new DOMElement("textarea", {disabled: true})));
 
   run_next_test();
 });
 
+add_test(function test_isEditingHost() {
+  ok(!element.isEditingHost(null));
+  ok(element.isEditingHost(new DOMElement("p", {isContentEditable: true})));
+  ok(element.isEditingHost(new DOMElement("p", {ownerDocument: {designMode: "on"}})));
+
+  run_next_test();
+});
+
+add_test(function test_isEditable() {
+  ok(!element.isEditable(null));
+  ok(!element.isEditable(domEl));
+  ok(!element.isEditable(new DOMElement("textarea", {readOnly: true})));
+  ok(!element.isEditable(new DOMElement("textarea", {disabled: true})));
+
+  for (let type of ["checkbox", "radio", "hidden", "submit", "button", "image"]) {
+    ok(!element.isEditable(new DOMElement("input", {type})));
+  }
+  ok(element.isEditable(new DOMElement("input", {type: "text"})));
+  ok(element.isEditable(new DOMElement("input")));
+
+  ok(element.isEditable(new DOMElement("textarea")));
+  ok(element.isEditable(new DOMElement("p", {ownerDocument: {designMode: "on"}})));
+  ok(element.isEditable(new DOMElement("p", {isContentEditable: true})));
+
+  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));