Bug 1430575 - Adjust editable definition to match WebDriver. r?automatedtester
Introduces a new function, isMutableFormControl, to the element
module in Marionette that tests if an element is a form control
that can be edited by the user. This replaces the proprietary
UNEDITABLE_INPUTS set used previously.
An editable element is, according to the WebDriver standard, an
element which belongs to the two subcategories of editable elements.
This patch implements the first category of the mutable form controls.
MozReview-Commit-ID: Aix19mq3lcb
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -29,25 +29,16 @@ 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",
@@ -839,16 +830,70 @@ element.isDisabled = function(el) {
return el.disabled;
default:
return false;
}
};
/**
+ * Denotes elements that can be used for typing and clearing.
+ *
+ * Elements that are considered WebDriver-editable are non-readonly
+ * and non-disabled <code><input></code> elements in the Text,
+ * Search, URL, Telephone, Email, Password, Date, Month, Date and
+ * Time Local, Number, Range, Color, and File Upload states, and
+ * <code><textarea></code> elements.
+ *
+ * @param {Element} el
+ * Element to test.
+ *
+ * @return {boolean}
+ * True if editable, false otherwise.
+ */
+element.isMutableFormControl = function(el) {
+ if (!element.isDOMElement(el)) {
+ return false;
+ }
+ if (element.isReadOnly(el) || element.isDisabled(el)) {
+ return false;
+ }
+
+ if (el.localName == "textarea") {
+ return true;
+ }
+
+ if (el.localName != "input") {
+ return false;
+ }
+
+ switch (el.type) {
+ case "color":
+ case "date":
+ case "datetime-local":
+ case "email":
+ case "file":
+ case "month":
+ case "number":
+ case "password":
+ case "range":
+ case "search":
+ case "tel":
+ case "text":
+ case "time":
+ case "url":
+ case "week":
+ return true;
+
+ 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}
@@ -887,19 +932,17 @@ 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);
+ return element.isMutableFormControl(el) || 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
--- a/testing/marionette/test_element.js
+++ b/testing/marionette/test_element.js
@@ -39,20 +39,25 @@ class Element {
return tags.includes(this.localName);
}
}
class DOMElement extends Element {
constructor(tagName, attrs = {}) {
super(tagName, attrs);
- this.namespaceURI = XHTMLNS;
+ if (typeof this.namespaceURI == "undefined") {
+ this.namespaceURI = XHTMLNS;
+ }
if (typeof this.ownerDocument == "undefined") {
this.ownerDocument = {designMode: "off"};
}
+ if (typeof this.type == "undefined") {
+ this.type = "text";
+ }
if (this.localName == "option") {
this.selected = false;
}
if (this.localName == "input" && ["checkbox", "radio"].includes(this.type)) {
this.checked = false;
}
@@ -253,16 +258,50 @@ add_test(function test_isEditable() {
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_isMutableFormControlElement() {
+ ok(!element.isMutableFormControl(null));
+ ok(!element.isMutableFormControl(new DOMElement("textarea", {readOnly: true})));
+ ok(!element.isMutableFormControl(new DOMElement("textarea", {disabled: true})));
+
+ const mutableStates = new Set([
+ "color",
+ "date",
+ "datetime-local",
+ "email",
+ "file",
+ "month",
+ "number",
+ "password",
+ "range",
+ "search",
+ "tel",
+ "text",
+ "url",
+ "week",
+ ]);
+ for (let type of mutableStates) {
+ ok(element.isMutableFormControl(new DOMElement("input", {type})));
+ }
+ ok(element.isMutableFormControl(new DOMElement("textarea")));
+
+ ok(!element.isMutableFormControl(new DOMElement("input", {type: "hidden"})));
+ ok(!element.isMutableFormControl(new DOMElement("p")));
+ ok(!element.isMutableFormControl(new DOMElement("p", {isContentEditable: true})));
+ ok(!element.isMutableFormControl(new DOMElement("p", {ownerDocument: {designMode: "on"}})));
+
+ 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));