/* 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"); Cu.importGlobalProperties(["File"]); 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. * * The element is scrolled into view before visibility- or interactability * checks are performed. * * Selenium-style visibility checks will be performed if |specCompat| * is false (default). Otherwise pointer-interactability checks will be * performed. If either of these fail an {@code ElementNotVisibleError} * is returned. * * If |strict| is enabled (defaults to disabled), further accessibility * checks will be performed, and these may result in an {@code * ElementNotAccessibleError} being returned. * * When |el| is not enabled, an {@code InvalidElementStateError} * is returned. * * @param {DOMElement|XULElement} el * Element to click. * @param {boolean=} strict * Enforce strict accessibility tests. * @param {boolean=} specCompat * Use WebDriver specification compatible interactability definition. * * @throws {ElementNotVisibleError} * If either Selenium-style visibility check or * pointer-interactability check fails. * @throws {ElementNotAccessibleError} * If |strict| is true and element is not accessible. * @throws {InvalidElementStateError} * If |el| is not enabled. */ interaction.clickElement = function*(el, strict = false, specCompat = false) { let win = getWindow(el); let a11y = accessibility.get(strict); let visibilityCheckEl = el; if (el.localName == "option") { visibilityCheckEl = interaction.getSelectForOptionElement(el); } let interactable = false; if (specCompat) { if (!element.isPointerInteractable(visibilityCheckEl)) { element.scrollIntoView(el); } interactable = element.isPointerInteractable(visibilityCheckEl); } else { interactable = element.isVisible(visibilityCheckEl); } if (!interactable) { throw new ElementNotVisibleError(); } if (!atom.isElementEnabled(el)) { throw new InvalidElementStateError("Element is not enabled"); } yield a11y.getAccessible(el, true).then(acc => { a11y.assertVisible(acc, el, interactable); a11y.assertEnabled(acc, el, true); a11y.assertActionable(acc, el); }); // chrome elements if (element.isXULElement(el)) { if (el.localName == "option") { interaction.selectOption(el); } else { el.click(); } // content elements } else { if (el.localName == "option") { interaction.selectOption(el); } else { let centre = interaction.calculateCentreCoords(el); let opts = {}; event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win); } } }; /** * Calculate the in-view centre point, that is the centre point of the * area of the first DOM client rectangle that is inside the viewport. * * @param {DOMElement} el * Element to calculate the visible centre point of. * * @return {Object.} * X- and Y-position. */ interaction.calculateCentreCoords = function (el) { let rects = el.getClientRects(); return { x: rects[0].left + rects[0].width / 2.0, y: rects[0].top + rects[0].height / 2.0, }; }; /** * Select