diff --git a/testing/marionette/element.js b/testing/marionette/element.js index 207c1cd1dad7..38546d42c5cf 100644 --- a/testing/marionette/element.js +++ b/testing/marionette/element.js @@ -34,6 +34,15 @@ const { 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"; @@ -834,6 +843,60 @@ element.isDisabled = function(el) { } }; +/** + * An editing host is a node that is either an HTML element with a + * contenteditable attribute, or the HTML element child + * of a document whose designMode 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: + * + * + * + * @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 diff --git a/testing/marionette/test_element.js b/testing/marionette/test_element.js index fdd514944ddc..db22375173ff 100644 --- a/testing/marionette/test_element.js +++ b/testing/marionette/test_element.js @@ -45,6 +45,9 @@ class DOMElement extends Element { super(tagName, attrs); this.namespaceURI = XHTMLNS; + if (typeof this.ownerDocument == "undefined") { + this.ownerDocument = {designMode: "off"}; + } if (this.localName == "option") { this.selected = false; @@ -228,6 +231,33 @@ add_test(function test_isDisabled() { 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"));