diff options
Diffstat (limited to 'remote/marionette/accessibility.sys.mjs')
-rw-r--r-- | remote/marionette/accessibility.sys.mjs | 479 |
1 files changed, 0 insertions, 479 deletions
diff --git a/remote/marionette/accessibility.sys.mjs b/remote/marionette/accessibility.sys.mjs deleted file mode 100644 index c500f2121e..0000000000 --- a/remote/marionette/accessibility.sys.mjs +++ /dev/null @@ -1,479 +0,0 @@ -/* 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/. */ - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", - Log: "chrome://remote/content/shared/Log.sys.mjs", - waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs", -}); - -ChromeUtils.defineLazyGetter(lazy, "logger", () => - lazy.Log.get(lazy.Log.TYPES.MARIONETTE) -); - -ChromeUtils.defineLazyGetter(lazy, "service", () => { - try { - return Cc["@mozilla.org/accessibilityService;1"].getService( - Ci.nsIAccessibilityService - ); - } catch (e) { - lazy.logger.warn("Accessibility module is not present"); - return undefined; - } -}); - -/** @namespace */ -export const accessibility = { - get service() { - return lazy.service; - }, -}; - -/** - * Accessible states used to check element"s state from the accessiblity API - * perspective. - * - * Note: if gecko is built with --disable-accessibility, the interfaces - * are not defined. This is why we use getters instead to be able to use - * these statically. - */ -accessibility.State = { - get Unavailable() { - return Ci.nsIAccessibleStates.STATE_UNAVAILABLE; - }, - get Focusable() { - return Ci.nsIAccessibleStates.STATE_FOCUSABLE; - }, - get Selectable() { - return Ci.nsIAccessibleStates.STATE_SELECTABLE; - }, - get Selected() { - return Ci.nsIAccessibleStates.STATE_SELECTED; - }, -}; - -/** - * Accessible object roles that support some action. - */ -accessibility.ActionableRoles = new Set([ - "checkbutton", - "check menu item", - "check rich option", - "combobox", - "combobox option", - "entry", - "key", - "link", - "listbox option", - "listbox rich option", - "menuitem", - "option", - "outlineitem", - "pagetab", - "pushbutton", - "radiobutton", - "radio menu item", - "rowheader", - "slider", - "spinbutton", - "switch", -]); - -/** - * Factory function that constructs a new {@code accessibility.Checks} - * object with enforced strictness or not. - */ -accessibility.get = function (strict = false) { - return new accessibility.Checks(!!strict); -}; - -/** - * Wait for the document accessibility state to be different from STATE_BUSY. - * - * @param {Document} doc - * The document to wait for. - * @returns {Promise} - * A promise which resolves when the document's accessibility state is no - * longer busy. - */ -function waitForDocumentAccessibility(doc) { - const documentAccessible = accessibility.service.getAccessibleFor(doc); - const state = {}; - documentAccessible.getState(state, {}); - if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) { - return Promise.resolve(); - } - - // Accessibility for the doc is busy, so wait for the state to change. - return lazy.waitForObserverTopic("accessible-event", { - checkFn: subject => { - // If event type does not match expected type, skip the event. - // If event's accessible does not match expected accessible, - // skip the event. - const event = subject.QueryInterface(Ci.nsIAccessibleEvent); - return ( - event.eventType === Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE && - event.accessible === documentAccessible - ); - }, - }); -} - -/** - * Retrieve the Accessible for the provided element. - * - * @param {Element} element - * The element for which we need to retrieve the accessible. - * - * @returns {nsIAccessible|null} - * The Accessible object corresponding to the provided element or null if - * the accessibility service is not available. - */ -accessibility.getAccessible = async function (element) { - if (!accessibility.service) { - return null; - } - - // First, wait for accessibility to be ready for the element's document. - await waitForDocumentAccessibility(element.ownerDocument); - - const acc = accessibility.service.getAccessibleFor(element); - if (acc) { - return acc; - } - - // The Accessible doesn't exist yet. This can happen because a11y tree - // mutations happen during refresh driver ticks. Stop the refresh driver from - // doing its regular ticks and force two refresh driver ticks: the first to - // let layout update and notify a11y, and the second to let a11y process - // updates. - const windowUtils = element.ownerGlobal.windowUtils; - windowUtils.advanceTimeAndRefresh(0); - windowUtils.advanceTimeAndRefresh(0); - // Go back to normal refresh driver ticks. - windowUtils.restoreNormalRefresh(); - return accessibility.service.getAccessibleFor(element); -}; - -/** - * Component responsible for interacting with platform accessibility - * API. - * - * Its methods serve as wrappers for testing content and chrome - * accessibility as well as accessibility of user interactions. - */ -accessibility.Checks = class { - /** - * @param {boolean} strict - * Flag indicating whether the accessibility issue should be logged - * or cause an error to be thrown. Default is to log to stdout. - */ - constructor(strict) { - this.strict = strict; - } - - /** - * Assert that the element has a corresponding accessible object, and retrieve - * this accessible. Note that if the accessibility.Checks component was - * created in non-strict mode, this helper will not attempt to resolve the - * accessible at all and will simply return null. - * - * @param {DOMElement|XULElement} element - * Element to get the accessible object for. - * @param {boolean=} mustHaveAccessible - * Flag indicating that the element must have an accessible object. - * Defaults to not require this. - * - * @returns {Promise.<nsIAccessible>} - * Promise with an accessibility object for the given element. - */ - async assertAccessible(element, mustHaveAccessible = false) { - if (!this.strict) { - return null; - } - - const accessible = await accessibility.getAccessible(element); - if (!accessible && mustHaveAccessible) { - this.error("Element does not have an accessible object", element); - } - - return accessible; - } - - /** - * Test if the accessible has a role that supports some arbitrary - * action. - * - * @param {nsIAccessible} accessible - * Accessible object. - * - * @returns {boolean} - * True if an actionable role is found on the accessible, false - * otherwise. - */ - isActionableRole(accessible) { - return accessibility.ActionableRoles.has( - accessibility.service.getStringRole(accessible.role) - ); - } - - /** - * Test if an accessible has at least one action that it supports. - * - * @param {nsIAccessible} accessible - * Accessible object. - * - * @returns {boolean} - * True if the accessible has at least one supported action, - * false otherwise. - */ - hasActionCount(accessible) { - return accessible.actionCount > 0; - } - - /** - * Test if an accessible has a valid name. - * - * @param {nsIAccessible} accessible - * Accessible object. - * - * @returns {boolean} - * True if the accessible has a non-empty valid name, or false if - * this is not the case. - */ - hasValidName(accessible) { - return accessible.name && accessible.name.trim(); - } - - /** - * Test if an accessible has a {@code hidden} attribute. - * - * @param {nsIAccessible} accessible - * Accessible object. - * - * @returns {boolean} - * True if the accessible object has a {@code hidden} attribute, - * false otherwise. - */ - hasHiddenAttribute(accessible) { - let hidden = false; - try { - hidden = accessible.attributes.getStringProperty("hidden"); - } catch (e) {} - // if the property is missing, error will be thrown - return hidden && hidden === "true"; - } - - /** - * Verify if an accessible has a given state. - * Test if an accessible has a given state. - * - * @param {nsIAccessible} accessible - * Accessible object to test. - * @param {number} stateToMatch - * State to match. - * - * @returns {boolean} - * True if |accessible| has |stateToMatch|, false otherwise. - */ - matchState(accessible, stateToMatch) { - let state = {}; - accessible.getState(state, {}); - return !!(state.value & stateToMatch); - } - - /** - * Test if an accessible is hidden from the user. - * - * @param {nsIAccessible} accessible - * Accessible object. - * - * @returns {boolean} - * True if element is hidden from user, false otherwise. - */ - isHidden(accessible) { - if (!accessible) { - return true; - } - - while (accessible) { - if (this.hasHiddenAttribute(accessible)) { - return true; - } - accessible = accessible.parent; - } - return false; - } - - /** - * Test if the element's visible state corresponds to its accessibility - * API visibility. - * - * @param {nsIAccessible} accessible - * Accessible object. - * @param {DOMElement|XULElement} element - * Element associated with |accessible|. - * @param {boolean} visible - * Visibility state of |element|. - * - * @throws ElementNotAccessibleError - * If |element|'s visibility state does not correspond to - * |accessible|'s. - */ - assertVisible(accessible, element, visible) { - let hiddenAccessibility = this.isHidden(accessible); - - let message; - if (visible && hiddenAccessibility) { - message = - "Element is not currently visible via the accessibility API " + - "and may not be manipulated by it"; - } else if (!visible && !hiddenAccessibility) { - message = - "Element is currently only visible via the accessibility API " + - "and can be manipulated by it"; - } - this.error(message, element); - } - - /** - * Test if the element's unavailable accessibility state matches the - * enabled state. - * - * @param {nsIAccessible} accessible - * Accessible object. - * @param {DOMElement|XULElement} element - * Element associated with |accessible|. - * @param {boolean} enabled - * Enabled state of |element|. - * - * @throws ElementNotAccessibleError - * If |element|'s enabled state does not match |accessible|'s. - */ - assertEnabled(accessible, element, enabled) { - if (!accessible) { - return; - } - - let win = element.ownerGlobal; - let disabledAccessibility = this.matchState( - accessible, - accessibility.State.Unavailable - ); - let explorable = - win.getComputedStyle(element).getPropertyValue("pointer-events") !== - "none"; - - let message; - if (!explorable && !disabledAccessibility) { - message = - "Element is enabled but is not explorable via the " + - "accessibility API"; - } else if (enabled && disabledAccessibility) { - message = "Element is enabled but disabled via the accessibility API"; - } else if (!enabled && !disabledAccessibility) { - message = "Element is disabled but enabled via the accessibility API"; - } - this.error(message, element); - } - - /** - * Test if it is possible to activate an element with the accessibility - * API. - * - * @param {nsIAccessible} accessible - * Accessible object. - * @param {DOMElement|XULElement} element - * Element associated with |accessible|. - * - * @throws ElementNotAccessibleError - * If it is impossible to activate |element| with |accessible|. - */ - assertActionable(accessible, element) { - if (!accessible) { - return; - } - - let message; - if (!this.hasActionCount(accessible)) { - message = "Element does not support any accessible actions"; - } else if (!this.isActionableRole(accessible)) { - message = - "Element does not have a correct accessibility role " + - "and may not be manipulated via the accessibility API"; - } else if (!this.hasValidName(accessible)) { - message = "Element is missing an accessible name"; - } else if (!this.matchState(accessible, accessibility.State.Focusable)) { - message = "Element is not focusable via the accessibility API"; - } - - this.error(message, element); - } - - /** - * Test that an element's selected state corresponds to its - * accessibility API selected state. - * - * @param {nsIAccessible} accessible - * Accessible object. - * @param {DOMElement|XULElement} element - * Element associated with |accessible|. - * @param {boolean} selected - * The |element|s selected state. - * - * @throws ElementNotAccessibleError - * If |element|'s selected state does not correspond to - * |accessible|'s. - */ - assertSelected(accessible, element, selected) { - if (!accessible) { - return; - } - - // element is not selectable via the accessibility API - if (!this.matchState(accessible, accessibility.State.Selectable)) { - return; - } - - let selectedAccessibility = this.matchState( - accessible, - accessibility.State.Selected - ); - - let message; - if (selected && !selectedAccessibility) { - message = - "Element is selected but not selected via the accessibility API"; - } else if (!selected && selectedAccessibility) { - message = - "Element is not selected but selected via the accessibility API"; - } - this.error(message, element); - } - - /** - * Throw an error if strict accessibility checks are enforced and log - * the error to the log. - * - * @param {string} message - * @param {DOMElement|XULElement} element - * Element that caused an error. - * - * @throws ElementNotAccessibleError - * If |strict| is true. - */ - error(message, element) { - if (!message || !this.strict) { - return; - } - if (element) { - let { id, tagName, className } = element; - message += `: id: ${id}, tagName: ${tagName}, className: ${className}`; - } - - throw new lazy.error.ElementNotAccessibleError(message); - } -}; |