/* 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/. */ "use strict"; const {utils: Cu} = Components; Cu.import("chrome://marionette/content/accessibility.js"); Cu.import("chrome://marionette/content/atom.js"); Cu.import("chrome://marionette/content/error.js"); Cu.import("chrome://marionette/content/element.js"); Cu.import("chrome://marionette/content/event.js"); this.EXPORTED_SYMBOLS = ["interaction"]; /** * XUL elements that support disabled attribute. */ const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([ "ARROWSCROLLBOX", "BUTTON", "CHECKBOX", "COLORPICKER", "COMMAND", "DATEPICKER", "DESCRIPTION", "KEY", "KEYSET", "LABEL", "LISTBOX", "LISTCELL", "LISTHEAD", "LISTHEADER", "LISTITEM", "MENU", "MENUITEM", "MENULIST", "MENUSEPARATOR", "PREFERENCE", "RADIO", "RADIOGROUP", "RICHLISTBOX", "RICHLISTITEM", "SCALE", "TAB", "TABS", "TEXTBOX", "TIMEPICKER", "TOOLBARBUTTON", "TREE", ]); /** * XUL elements that support checked property. */ const CHECKED_PROPERTY_SUPPORTED_XUL = new Set([ "BUTTON", "CHECKBOX", "LISTITEM", "TOOLBARBUTTON", ]); /** * XUL elements that support selected property. */ const SELECTED_PROPERTY_SUPPORTED_XUL = new Set([ "LISTITEM", "MENU", "MENUITEM", "MENUSEPARATOR", "RADIO", "RICHLISTITEM", "TAB", ]); this.interaction = {}; /** * Interact with an element by clicking it. * * @param {DOMElement|XULElement} el * Element to click. * @param {boolean=} strict * Enforce strict accessibility tests. * @param {boolean=} specCompat * Use WebDriver specification compatible interactability definition. */ interaction.clickElement = function(el, strict = false, specCompat = false) { let win = getWindow(el); let visible = false; if (specCompat) { visible = element.isInteractable(el); if (visible) { el.scrollIntoView(false); } } else { visible = element.isVisible(el); } if (!visible) { throw new ElementNotVisibleError("Element is not visible"); } let a11y = accessibility.get(strict); return a11y.getAccessible(el, true).then(acc => { a11y.checkVisible(acc, el, visible); if (atom.isElementEnabled(el)) { a11y.checkEnabled(acc, el, true); a11y.checkActionable(acc, el); if (element.isXULElement(el)) { el.click(); } else { let rects = el.getClientRects(); event.synthesizeMouseAtPoint( rects[0].left + rects[0].width / 2, rects[0].top + rects[0].height / 2, {} /* opts */, win); } } else { throw new InvalidElementStateError("Element is not enabled"); } }); }; /** * Send keys to element. * * @param {DOMElement|XULElement} el * Element to send key events to. * @param {Array.} value * Sequence of keystrokes to send to the element. * @param {boolean} ignoreVisibility * Flag to enable or disable element visibility tests. * @param {boolean=} strict * Enforce strict accessibility tests. */ interaction.sendKeysToElement = function(el, value, ignoreVisibility, strict = false) { let win = getWindow(el); let a11y = accessibility.get(strict); return a11y.getAccessible(el, true).then(acc => { a11y.checkActionable(acc, el); event.sendKeysToElement(value, el, {ignoreVisibility: false}, win); }); }; /** * Determine the element displayedness of an element. * * @param {DOMElement|XULElement} el * Element to determine displayedness of. * @param {boolean=} strict * Enforce strict accessibility tests. * * @return {boolean} * True if element is displayed, false otherwise. */ interaction.isElementDisplayed = function(el, strict = false) { let win = getWindow(el); let displayed = atom.isElementDisplayed(el, win); let a11y = accessibility.get(strict); return a11y.getAccessible(el).then(acc => { a11y.checkVisible(acc, el, displayed); return displayed; }); }; /** * Check if element is enabled. * * @param {DOMElement|XULElement} el * Element to test if is enabled. * * @return {boolean} * True if enabled, false otherwise. */ interaction.isElementEnabled = function(el, strict = false) { let enabled = true; let win = getWindow(el); if (element.isXULElement(el)) { // check if XUL element supports disabled attribute if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) { let disabled = atom.getElementAttribute(el, "disabled", win); if (disabled && disabled === "true") { enabled = false; } } } else { enabled = atom.isElementEnabled(el, {frame: win}); } let a11y = accessibility.get(strict); return a11y.getAccessible(el).then(acc => { a11y.checkEnabled(acc, el, enabled); return enabled; }); }; /** * Determines if the referenced element is selected or not. * * This operation only makes sense on input elements of the Checkbox- * and Radio Button states, or option elements. * * @param {DOMElement|XULElement} el * Element to test if is selected. * @param {boolean=} strict * Enforce strict accessibility tests. * * @return {boolean} * True if element is selected, false otherwise. */ interaction.isElementSelected = function(el, strict = false) { let selected = true; let win = getWindow(el); if (element.isXULElement(el)) { let tagName = el.tagName.toUpperCase(); if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) { selected = el.checked; } if (SELECTED_PROPERTY_SUPPORTED_XUL.has(tagName)) { selected = el.selected; } } else { selected = atom.isElementSelected(el, win); } let a11y = accessibility.get(strict); return a11y.getAccessible(el).then(acc => { a11y.checkSelected(acc, el, selected); return selected; }); }; function getWindow(el) { return el.ownerDocument.defaultView; }