2018-03-28 18:40:49 +03:00
|
|
|
/* 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";
|
|
|
|
|
2019-08-28 14:54:42 +03:00
|
|
|
/* global EVENTS */
|
2018-04-13 20:16:53 +03:00
|
|
|
|
|
|
|
const nodeConstants = require("devtools/shared/dom-node-constants");
|
2018-03-28 18:40:49 +03:00
|
|
|
|
|
|
|
// React & Redux
|
|
|
|
const {
|
|
|
|
createFactory,
|
|
|
|
createElement,
|
|
|
|
} = require("devtools/client/shared/vendor/react");
|
|
|
|
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
|
|
|
|
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
|
|
|
|
|
|
|
// Accessibility Panel
|
2019-12-27 15:01:24 +03:00
|
|
|
const MainFrame = createFactory(
|
|
|
|
require("devtools/client/accessibility/components/MainFrame")
|
|
|
|
);
|
2018-03-28 18:40:49 +03:00
|
|
|
|
|
|
|
// Store
|
2019-08-09 04:27:53 +03:00
|
|
|
const createStore = require("devtools/client/shared/redux/create-store");
|
2018-03-28 18:40:49 +03:00
|
|
|
|
|
|
|
// Reducers
|
2019-12-27 15:01:24 +03:00
|
|
|
const { reducers } = require("devtools/client/accessibility/reducers/index");
|
2020-11-03 18:33:16 +03:00
|
|
|
const thunkOptions = { options: {} };
|
|
|
|
const store = createStore(reducers, {
|
|
|
|
// Thunk options will be updated, when we [re]initialize the accessibility
|
|
|
|
// view.
|
|
|
|
thunkOptions,
|
|
|
|
});
|
2018-03-28 18:40:49 +03:00
|
|
|
|
|
|
|
// Actions
|
2019-12-27 15:01:24 +03:00
|
|
|
const { reset } = require("devtools/client/accessibility/actions/ui");
|
|
|
|
const {
|
|
|
|
select,
|
|
|
|
highlight,
|
|
|
|
} = require("devtools/client/accessibility/actions/accessibles");
|
2018-03-28 18:40:49 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*
|
2019-08-29 08:01:52 +03:00
|
|
|
* @param {Object}
|
|
|
|
* Object that contains the following properties:
|
2020-05-29 22:18:19 +03:00
|
|
|
* - 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).
|
2020-11-03 18:33:16 +03:00
|
|
|
* - toggleDisplayTabbingOrder {Function}
|
|
|
|
* Toggle the highlight of focusable
|
|
|
|
* elements along with their tabbing
|
|
|
|
* index.
|
2020-05-29 22:18:19 +03:00
|
|
|
* - 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.
|
2018-03-28 18:40:49 +03:00
|
|
|
*/
|
2019-11-04 09:38:37 +03:00
|
|
|
async initialize({
|
|
|
|
supports,
|
|
|
|
fluentBundles,
|
2019-11-28 18:37:25 +03:00
|
|
|
toolbox,
|
2020-02-21 07:35:15 +03:00
|
|
|
getAccessibilityTreeRoot,
|
|
|
|
startListeningForAccessibilityEvents,
|
|
|
|
stopListeningForAccessibilityEvents,
|
2020-02-21 07:35:30 +03:00
|
|
|
audit,
|
2020-02-21 07:35:45 +03:00
|
|
|
simulate,
|
2020-11-03 18:33:16 +03:00
|
|
|
toggleDisplayTabbingOrder,
|
2020-03-06 19:27:59 +03:00
|
|
|
enableAccessibility,
|
|
|
|
resetAccessiblity,
|
|
|
|
startListeningForLifecycleEvents,
|
|
|
|
stopListeningForLifecycleEvents,
|
2020-05-29 22:18:19 +03:00
|
|
|
startListeningForParentLifecycleEvents,
|
|
|
|
stopListeningForParentLifecycleEvents,
|
2020-05-20 19:42:06 +03:00
|
|
|
highlightAccessible,
|
|
|
|
unhighlightAccessible,
|
2019-11-04 09:38:37 +03:00
|
|
|
}) {
|
2018-03-28 18:40:49 +03:00
|
|
|
// Make sure state is reset every time accessibility panel is initialized.
|
2020-03-06 19:27:59 +03:00
|
|
|
await this.store.dispatch(reset(resetAccessiblity, supports));
|
2018-03-28 18:40:49 +03:00
|
|
|
const container = document.getElementById("content");
|
2019-08-12 03:21:56 +03:00
|
|
|
const mainFrame = MainFrame({
|
|
|
|
fluentBundles,
|
2019-11-28 18:37:25 +03:00
|
|
|
toolbox,
|
2020-02-21 07:35:15 +03:00
|
|
|
getAccessibilityTreeRoot,
|
|
|
|
startListeningForAccessibilityEvents,
|
|
|
|
stopListeningForAccessibilityEvents,
|
2020-02-21 07:35:30 +03:00
|
|
|
audit,
|
2020-02-21 07:35:45 +03:00
|
|
|
simulate,
|
2020-03-06 19:27:59 +03:00
|
|
|
enableAccessibility,
|
|
|
|
resetAccessiblity,
|
|
|
|
startListeningForLifecycleEvents,
|
|
|
|
stopListeningForLifecycleEvents,
|
2020-05-29 22:18:19 +03:00
|
|
|
startListeningForParentLifecycleEvents,
|
|
|
|
stopListeningForParentLifecycleEvents,
|
2020-05-20 19:42:06 +03:00
|
|
|
highlightAccessible,
|
|
|
|
unhighlightAccessible,
|
2019-08-12 03:21:56 +03:00
|
|
|
});
|
2020-11-03 18:33:16 +03:00
|
|
|
thunkOptions.options.toggleDisplayTabbingOrder = toggleDisplayTabbingOrder;
|
2018-03-28 18:40:49 +03:00
|
|
|
// Render top level component
|
|
|
|
const provider = createElement(Provider, { store: this.store }, mainFrame);
|
2020-06-30 13:26:19 +03:00
|
|
|
window.once(EVENTS.PROPERTIES_UPDATED).then(() => {
|
|
|
|
window.emit(EVENTS.INITIALIZED);
|
|
|
|
});
|
2018-03-28 18:40:49 +03:00
|
|
|
this.mainFrame = ReactDOM.render(provider, container);
|
|
|
|
},
|
|
|
|
|
2020-01-03 18:33:00 +03:00
|
|
|
destroy() {
|
|
|
|
const container = document.getElementById("content");
|
|
|
|
ReactDOM.unmountComponentAtNode(container);
|
|
|
|
},
|
|
|
|
|
2020-02-21 07:35:12 +03:00
|
|
|
async selectAccessible(accessible) {
|
|
|
|
await this.store.dispatch(select(accessible));
|
2018-03-28 18:40:49 +03:00
|
|
|
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
|
|
|
|
},
|
|
|
|
|
2020-02-21 07:35:12 +03:00
|
|
|
async highlightAccessible(accessible) {
|
|
|
|
await this.store.dispatch(highlight(accessible));
|
2018-03-28 18:40:49 +03:00
|
|
|
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
|
|
|
|
},
|
|
|
|
|
2020-02-21 07:35:12 +03:00
|
|
|
async selectNodeAccessible(node) {
|
2020-05-06 05:19:22 +03:00
|
|
|
if (!node) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-02-21 07:35:12 +03:00
|
|
|
const accessibilityFront = await node.targetFront.getFront("accessibility");
|
|
|
|
const accessibleWalkerFront = await accessibilityFront.getWalker();
|
|
|
|
let accessible = await accessibleWalkerFront.getAccessibleFor(node);
|
2019-11-28 18:37:18 +03:00
|
|
|
if (accessible) {
|
2019-04-27 16:33:17 +03:00
|
|
|
await accessible.hydrate();
|
|
|
|
}
|
|
|
|
|
2018-04-13 20:16:53 +03:00
|
|
|
// 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) {
|
2019-08-28 14:54:42 +03:00
|
|
|
const { nodes: children } = await node.walkerFront.children(node);
|
2018-06-01 13:36:09 +03:00
|
|
|
for (const child of children) {
|
2018-04-13 20:16:53 +03:00
|
|
|
if (child.nodeType === nodeConstants.TEXT_NODE) {
|
2020-02-21 07:35:12 +03:00
|
|
|
accessible = await accessibleWalkerFront.getAccessibleFor(child);
|
2019-04-27 16:33:17 +03:00
|
|
|
// indexInParent property is only available with additional request
|
|
|
|
// for data (hydration) about the accessible object.
|
2019-11-28 18:37:18 +03:00
|
|
|
if (accessible) {
|
2019-04-27 16:33:17 +03:00
|
|
|
await accessible.hydrate();
|
2020-05-06 18:55:06 +03:00
|
|
|
if (accessible.indexInParent >= 0) {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-27 16:33:17 +03:00
|
|
|
}
|
2020-05-06 18:55:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-27 16:33:17 +03:00
|
|
|
|
2020-05-06 18:55:06 +03:00
|
|
|
// 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();
|
2019-04-27 16:33:17 +03:00
|
|
|
if (accessible.indexInParent >= 0) {
|
2018-04-13 20:16:53 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-05-06 18:55:06 +03:00
|
|
|
|
|
|
|
parentNode = parentNode.parentNode();
|
2018-04-13 20:16:53 +03:00
|
|
|
}
|
|
|
|
}
|
2018-03-28 18:40:49 +03:00
|
|
|
|
2020-05-06 18:55:06 +03:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2020-02-21 07:35:12 +03:00
|
|
|
await this.store.dispatch(select(accessible));
|
2019-12-11 01:28:52 +03:00
|
|
|
window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED);
|
2018-03-28 18:40:49 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|