summaryrefslogtreecommitdiffstats
path: root/devtools/client/accessibility/accessibility-view.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/accessibility/accessibility-view.js')
-rw-r--r--devtools/client/accessibility/accessibility-view.js260
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);