diff options
Diffstat (limited to 'devtools/client/accessibility/accessibility-view.js')
-rw-r--r-- | devtools/client/accessibility/accessibility-view.js | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/devtools/client/accessibility/accessibility-view.js b/devtools/client/accessibility/accessibility-view.js new file mode 100644 index 0000000000..36b5aa9e6e --- /dev/null +++ b/devtools/client/accessibility/accessibility-view.js @@ -0,0 +1,260 @@ +/* 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"; + +/* global EVENTS */ + +const nodeConstants = require("resource://devtools/shared/dom-node-constants.js"); + +// React & Redux +const { + createFactory, + createElement, +} = require("resource://devtools/client/shared/vendor/react.js"); +const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.js"); +const { + Provider, +} = require("resource://devtools/client/shared/vendor/react-redux.js"); + +// Accessibility Panel +const MainFrame = createFactory( + require("resource://devtools/client/accessibility/components/MainFrame.js") +); + +// Store +const createStore = require("resource://devtools/client/shared/redux/create-store.js"); + +// Reducers +const { + reducers, +} = require("resource://devtools/client/accessibility/reducers/index.js"); +const thunkOptions = { options: {} }; +const store = createStore(reducers, { + // Thunk options will be updated, when we [re]initialize the accessibility + // view. + thunkOptions, +}); + +// Actions +const { + reset, +} = require("resource://devtools/client/accessibility/actions/ui.js"); +const { + select, + highlight, +} = require("resource://devtools/client/accessibility/actions/accessibles.js"); + +/** + * This object represents view of the Accessibility panel and is responsible + * for rendering the content. It renders the top level ReactJS + * component: the MainFrame. + */ +function AccessibilityView(localStore) { + addEventListener("devtools/chrome/message", this.onMessage.bind(this), true); + this.store = localStore; +} + +AccessibilityView.prototype = { + /** + * Initialize accessibility view, create its top level component and set the + * data store. + * + * @param {Object} + * Object that contains the following properties: + * - supports {JSON} + * a collection of flags indicating + * which accessibility panel features + * are supported by the current + * serverside version. + * - fluentBundles {Array} + * array of FluentBundles elements + * for localization + * - toolbox {Object} + * devtools toolbox. + * - getAccessibilityTreeRoot {Function} + * Returns the topmost accessibiliity + * walker that is used as the root of + * the accessibility tree. + * - startListeningForAccessibilityEvents {Function} + * Add listeners for specific + * accessibility events. + * - stopListeningForAccessibilityEvents {Function} + * Remove listeners for specific + * accessibility events. + * - audit {Function} + * Audit function that will start + * accessibility audit for given types + * of accessibility issues. + * - simulate {null|Function} + * Apply simulation of a given type + * (by setting color matrices in + * docShell). + * - toggleDisplayTabbingOrder {Function} + * Toggle the highlight of focusable + * elements along with their tabbing + * index. + * - enableAccessibility {Function} + * Enable accessibility services. + * - resetAccessiblity {Function} + * Reset the state of the + * accessibility services. + * - startListeningForLifecycleEvents {Function} + * Add listeners for accessibility + * service lifecycle events. + * - stopListeningForLifecycleEvents {Function} + * Remove listeners for accessibility + * service lifecycle events. + * - startListeningForParentLifecycleEvents {Function} + * Add listeners for parent process + * accessibility service lifecycle + * events. + * - stopListeningForParentLifecycleEvents {Function} + * Remove listeners for parent + * process accessibility service + * lifecycle events. + * - highlightAccessible {Function} + * Highlight accessible object. + * - unhighlightAccessible {Function} + * Unhighlight accessible object. + */ + async initialize({ + supports, + fluentBundles, + toolbox, + getAccessibilityTreeRoot, + startListeningForAccessibilityEvents, + stopListeningForAccessibilityEvents, + audit, + simulate, + toggleDisplayTabbingOrder, + enableAccessibility, + resetAccessiblity, + startListeningForLifecycleEvents, + stopListeningForLifecycleEvents, + startListeningForParentLifecycleEvents, + stopListeningForParentLifecycleEvents, + highlightAccessible, + unhighlightAccessible, + }) { + // Make sure state is reset every time accessibility panel is initialized. + await this.store.dispatch(reset(resetAccessiblity, supports)); + const container = document.getElementById("content"); + const mainFrame = MainFrame({ + fluentBundles, + toolbox, + getAccessibilityTreeRoot, + startListeningForAccessibilityEvents, + stopListeningForAccessibilityEvents, + audit, + simulate, + enableAccessibility, + resetAccessiblity, + startListeningForLifecycleEvents, + stopListeningForLifecycleEvents, + startListeningForParentLifecycleEvents, + stopListeningForParentLifecycleEvents, + highlightAccessible, + unhighlightAccessible, + }); + thunkOptions.options.toggleDisplayTabbingOrder = toggleDisplayTabbingOrder; + // Render top level component + const provider = createElement(Provider, { store: this.store }, mainFrame); + window.once(EVENTS.PROPERTIES_UPDATED).then(() => { + window.emit(EVENTS.INITIALIZED); + }); + this.mainFrame = ReactDOM.render(provider, container); + }, + + destroy() { + const container = document.getElementById("content"); + ReactDOM.unmountComponentAtNode(container); + }, + + async selectAccessible(accessible) { + await this.store.dispatch(select(accessible)); + window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED); + }, + + async highlightAccessible(accessible) { + await this.store.dispatch(highlight(accessible)); + window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED); + }, + + async selectNodeAccessible(node) { + if (!node) { + return; + } + + const accessibilityFront = await node.targetFront.getFront("accessibility"); + const accessibleWalkerFront = await accessibilityFront.getWalker(); + let accessible = await accessibleWalkerFront.getAccessibleFor(node); + if (accessible) { + await accessible.hydrate(); + } + + // If node does not have an accessible object, try to find node's child text node and + // try to retrieve an accessible object for that child instead. This is the best + // effort approach until there's accessibility API to retrieve accessible object at + // point. + if (!accessible || accessible.indexInParent < 0) { + const { nodes: children } = await node.walkerFront.children(node); + for (const child of children) { + if (child.nodeType === nodeConstants.TEXT_NODE) { + accessible = await accessibleWalkerFront.getAccessibleFor(child); + // indexInParent property is only available with additional request + // for data (hydration) about the accessible object. + if (accessible) { + await accessible.hydrate(); + if (accessible.indexInParent >= 0) { + break; + } + } + } + } + } + + // Attempt to find closest accessible ancestor for a given node. + if (!accessible || accessible.indexInParent < 0) { + let parentNode = node.parentNode(); + while (parentNode) { + accessible = await accessibleWalkerFront.getAccessibleFor(parentNode); + if (accessible) { + await accessible.hydrate(); + if (accessible.indexInParent >= 0) { + break; + } + } + + parentNode = parentNode.parentNode(); + } + } + + // Do not set the selected state if there is no corresponding accessible. + if (!accessible) { + console.warn( + `No accessible object found for a node or a node in its ancestry: ${node.actorID}` + ); + return; + } + + await this.store.dispatch(select(accessible)); + window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED); + }, + + /** + * Process message from accessibility panel. + * + * @param {Object} event message type and data. + */ + onMessage(event) { + const data = event.data; + const method = data.type; + + if (typeof this[method] === "function") { + this[method](...data.args); + } + }, +}; + +window.view = new AccessibilityView(store); |