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
--- 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><textarea></code> element.
+ *
+ * <li>It is an <code><input></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));