summaryrefslogtreecommitdiffstats
path: root/remote/marionette/accessibility.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--remote/marionette/accessibility.sys.mjs479
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);
- }
-};