accessibility-view.js (10398B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 /* global EVENTS */ 7 8 const nodeConstants = require("resource://devtools/shared/dom-node-constants.js"); 9 10 // React & Redux 11 const { 12 createFactory, 13 createElement, 14 } = require("resource://devtools/client/shared/vendor/react.mjs"); 15 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.mjs"); 16 const { 17 Provider, 18 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 19 20 // Accessibility Panel 21 const MainFrame = createFactory( 22 require("resource://devtools/client/accessibility/components/MainFrame.js") 23 ); 24 25 // Store 26 const createStore = require("resource://devtools/client/shared/redux/create-store.js"); 27 28 // Reducers 29 const { 30 reducers, 31 } = require("resource://devtools/client/accessibility/reducers/index.js"); 32 const thunkOptions = { options: {} }; 33 const store = createStore(reducers, { 34 // Thunk options will be updated, when we [re]initialize the accessibility 35 // view. 36 thunkOptions, 37 }); 38 39 // Actions 40 const { 41 reset, 42 } = require("resource://devtools/client/accessibility/actions/ui.js"); 43 const { 44 select, 45 highlight, 46 } = require("resource://devtools/client/accessibility/actions/accessibles.js"); 47 48 /** 49 * This object represents view of the Accessibility panel and is responsible 50 * for rendering the content. It renders the top level ReactJS 51 * component: the MainFrame. 52 */ 53 class AccessibilityView { 54 constructor(localStore) { 55 addEventListener( 56 "devtools/chrome/message", 57 this.onMessage.bind(this), 58 true 59 ); 60 this.store = localStore; 61 } 62 /** 63 * Initialize accessibility view, create its top level component and set the 64 * data store. 65 * 66 * @param {object} 67 * Object that contains the following properties: 68 * - supports {JSON} 69 * a collection of flags indicating 70 * which accessibility panel features 71 * are supported by the current 72 * serverside version. 73 * - fluentBundles {Array} 74 * array of FluentBundles elements 75 * for localization 76 * - toolbox {Object} 77 * devtools toolbox. 78 * - getAccessibilityTreeRoot {Function} 79 * Returns the topmost accessibiliity 80 * walker that is used as the root of 81 * the accessibility tree. 82 * - startListeningForAccessibilityEvents {Function} 83 * Add listeners for specific 84 * accessibility events. 85 * - stopListeningForAccessibilityEvents {Function} 86 * Remove listeners for specific 87 * accessibility events. 88 * - audit {Function} 89 * Audit function that will start 90 * accessibility audit for given types 91 * of accessibility issues. 92 * - simulate {null|Function} 93 * Apply simulation of a given type 94 * (by setting color matrices in 95 * docShell). 96 * - toggleDisplayTabbingOrder {Function} 97 * Toggle the highlight of focusable 98 * elements along with their tabbing 99 * index. 100 * - enableAccessibility {Function} 101 * Enable accessibility services. 102 * - resetAccessiblity {Function} 103 * Reset the state of the 104 * accessibility services. 105 * - startListeningForLifecycleEvents {Function} 106 * Add listeners for accessibility 107 * service lifecycle events. 108 * - stopListeningForLifecycleEvents {Function} 109 * Remove listeners for accessibility 110 * service lifecycle events. 111 * - startListeningForParentLifecycleEvents {Function} 112 * Add listeners for parent process 113 * accessibility service lifecycle 114 * events. 115 * - stopListeningForParentLifecycleEvents {Function} 116 * Remove listeners for parent 117 * process accessibility service 118 * lifecycle events. 119 * - highlightAccessible {Function} 120 * Highlight accessible object. 121 * - unhighlightAccessible {Function} 122 * Unhighlight accessible object. 123 */ 124 async initialize({ 125 supports, 126 fluentBundles, 127 toolbox, 128 getAccessibilityTreeRoot, 129 startListeningForAccessibilityEvents, 130 stopListeningForAccessibilityEvents, 131 audit, 132 simulate, 133 toggleDisplayTabbingOrder, 134 enableAccessibility, 135 resetAccessiblity, 136 startListeningForLifecycleEvents, 137 stopListeningForLifecycleEvents, 138 startListeningForParentLifecycleEvents, 139 stopListeningForParentLifecycleEvents, 140 highlightAccessible, 141 unhighlightAccessible, 142 }) { 143 // Make sure state is reset every time accessibility panel is initialized. 144 await this.store.dispatch(reset(resetAccessiblity, supports)); 145 const container = document.getElementById("content"); 146 const mainFrame = MainFrame({ 147 fluentBundles, 148 toolbox, 149 getAccessibilityTreeRoot, 150 startListeningForAccessibilityEvents, 151 stopListeningForAccessibilityEvents, 152 audit, 153 simulate, 154 enableAccessibility, 155 resetAccessiblity, 156 startListeningForLifecycleEvents, 157 stopListeningForLifecycleEvents, 158 startListeningForParentLifecycleEvents, 159 stopListeningForParentLifecycleEvents, 160 highlightAccessible, 161 unhighlightAccessible, 162 }); 163 thunkOptions.options.toggleDisplayTabbingOrder = toggleDisplayTabbingOrder; 164 // Render top level component 165 const provider = createElement(Provider, { store: this.store }, mainFrame); 166 167 // If the AccessibilityService can not be turned on, directly emit the INITIALIZED event 168 // so the panel can be rendered (with the Description element). 169 if (!this.store.getState().ui.canBeEnabled) { 170 window.emit(EVENTS.INITIALIZED); 171 } else { 172 window.once(EVENTS.PROPERTIES_UPDATED).then(() => { 173 window.emit(EVENTS.INITIALIZED); 174 }); 175 } 176 this.mainFrame = ReactDOM.render(provider, container); 177 } 178 179 destroy() { 180 const container = document.getElementById("content"); 181 ReactDOM.unmountComponentAtNode(container); 182 } 183 184 async selectAccessible(accessible) { 185 await this.store.dispatch(select(accessible)); 186 window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED); 187 } 188 189 async highlightAccessible(accessible) { 190 await this.store.dispatch(highlight(accessible)); 191 window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED); 192 } 193 194 async selectNodeAccessible(node) { 195 if (!node) { 196 return; 197 } 198 199 const accessibilityFront = await node.targetFront.getFront("accessibility"); 200 const accessibleWalkerFront = await accessibilityFront.getWalker(); 201 let accessible = await accessibleWalkerFront.getAccessibleFor(node); 202 if (accessible) { 203 await accessible.hydrate(); 204 } 205 206 // If node does not have an accessible object, try to find node's child text node and 207 // try to retrieve an accessible object for that child instead. This is the best 208 // effort approach until there's accessibility API to retrieve accessible object at 209 // point. 210 if (!accessible || accessible.indexInParent < 0) { 211 const { nodes: children } = await node.walkerFront.children(node); 212 for (const child of children) { 213 if (child.nodeType === nodeConstants.TEXT_NODE) { 214 accessible = await accessibleWalkerFront.getAccessibleFor(child); 215 // indexInParent property is only available with additional request 216 // for data (hydration) about the accessible object. 217 if (accessible) { 218 await accessible.hydrate(); 219 if (accessible.indexInParent >= 0) { 220 break; 221 } 222 } 223 } 224 } 225 } 226 227 // Attempt to find closest accessible ancestor for a given node. 228 if (!accessible || accessible.indexInParent < 0) { 229 let parentNode = node.parentNode(); 230 while (parentNode) { 231 accessible = await accessibleWalkerFront.getAccessibleFor(parentNode); 232 if (accessible) { 233 await accessible.hydrate(); 234 if (accessible.indexInParent >= 0) { 235 break; 236 } 237 } 238 239 parentNode = parentNode.parentNode(); 240 } 241 } 242 243 // Do not set the selected state if there is no corresponding accessible. 244 if (!accessible) { 245 console.warn( 246 `No accessible object found for a node or a node in its ancestry: ${node.actorID}` 247 ); 248 return; 249 } 250 251 await this.store.dispatch(select(accessible)); 252 window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_INSPECTED); 253 } 254 255 /** 256 * Process message from accessibility panel. 257 * 258 * @param {object} event message type and data. 259 */ 260 onMessage(event) { 261 const data = event.data; 262 const method = data.type; 263 264 if (typeof this[method] === "function") { 265 this[method](...data.args); 266 } 267 } 268 } 269 270 window.view = new AccessibilityView(store);