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/. */
6 const EventEmitter
= require("resource://devtools/shared/event-emitter.js");
8 loader
.lazyRequireGetter(
11 "resource://devtools/client/accessibility/accessibility-proxy.js",
14 loader
.lazyRequireGetter(
17 "resource://devtools/client/accessibility/picker.js",
21 A11Y_SERVICE_DURATION
,
22 } = require("resource://devtools/client/accessibility/constants.js");
24 // The panel's window global is an EventEmitter firing the following events:
26 // When the accessibility inspector has a new accessible front selected.
27 NEW_ACCESSIBLE_FRONT_SELECTED
: "Accessibility:NewAccessibleFrontSelected",
28 // When the accessibility inspector has a new accessible front highlighted.
29 NEW_ACCESSIBLE_FRONT_HIGHLIGHTED
:
30 "Accessibility:NewAccessibleFrontHighlighted",
31 // When the accessibility inspector has a new accessible front inspected.
32 NEW_ACCESSIBLE_FRONT_INSPECTED
: "Accessibility:NewAccessibleFrontInspected",
33 // When the accessibility inspector is updated.
34 ACCESSIBILITY_INSPECTOR_UPDATED
:
35 "Accessibility:AccessibilityInspectorUpdated",
36 // When accessibility panel UI is initialized (rendered).
37 INITIALIZED
: "Accessibility:Initialized",
38 // When accessibile object properties are updated in the panel sidebar for a
39 // new accessible object.
40 PROPERTIES_UPDATED
: "Accessibility:PropertiesUpdated",
44 * This object represents Accessibility panel. It's responsibility is to
45 * render Accessibility Tree of the current debugger target and the sidebar that
46 * displays current relevant accessible details.
48 function AccessibilityPanel(iframeWindow
, toolbox
, commands
) {
49 this.panelWin
= iframeWindow
;
50 this._toolbox
= toolbox
;
51 this._commands
= commands
;
53 this.onPanelVisibilityChange
= this.onPanelVisibilityChange
.bind(this);
54 this.onNewAccessibleFrontSelected
=
55 this.onNewAccessibleFrontSelected
.bind(this);
56 this.onAccessibilityInspectorUpdated
=
57 this.onAccessibilityInspectorUpdated
.bind(this);
58 this.updateA11YServiceDurationTimer
=
59 this.updateA11YServiceDurationTimer
.bind(this);
60 this.forceUpdatePickerButton
= this.forceUpdatePickerButton
.bind(this);
61 this.onLifecycleEvent
= this.onLifecycleEvent
.bind(this);
63 EventEmitter
.decorate(this);
66 AccessibilityPanel
.prototype = {
68 * Open is effectively an asynchronous constructor.
76 // This first promise includes initialization of proxy *and* the call to forceRefresh
78 this._opening
= new Promise(resolve
=> {
82 // This second promise only include the initialization of proxy and few other things,
83 // but not the call to forceRefresh.
84 const { promise
, resolve
} = Promise
.withResolvers();
85 this.initializedPromise
= promise
;
87 this._telemetry
= this._toolbox
.telemetry
;
88 this.panelWin
.gTelemetry
= this._telemetry
;
90 this._toolbox
.on("select", this.onPanelVisibilityChange
);
92 this.panelWin
.EVENTS
= EVENTS
;
93 EventEmitter
.decorate(this.panelWin
);
95 EVENTS
.NEW_ACCESSIBLE_FRONT_SELECTED
,
96 this.onNewAccessibleFrontSelected
99 EVENTS
.ACCESSIBILITY_INSPECTOR_UPDATED
,
100 this.onAccessibilityInspectorUpdated
103 this.picker
= new Picker(this);
104 this.fluentBundles
= await
this.createFluentBundles();
106 this.accessibilityProxy
= new AccessibilityProxy(this._commands
, this);
108 await
this.accessibilityProxy
.initialize();
110 this.accessibilityProxy
.startListeningForLifecycleEvents({
111 init
: this.onLifecycleEvent
,
112 shutdown
: this.onLifecycleEvent
,
115 // Start recording the duration where a11y service is enabled via the proxy.
116 this.updateA11YServiceDurationTimer();
118 // Resolve the `this.initializedPromise`
121 // Force rendering the panel once everything is initialized
122 await
this.forceRefresh();
125 return this._opening
;
129 * Retrieve message contexts for the current locales, and return them as an
130 * array of FluentBundles elements.
132 async
createFluentBundles() {
133 const locales
= Services
.locale
.appLocalesAsBCP47
;
134 const generator
= L10nRegistry
.getInstance().generateBundles(locales
, [
135 "devtools/client/accessibility.ftl",
138 // Return value of generateBundles is a generator and should be converted to
139 // a sync iterable before using it with React.
141 for await (const message
of generator
) {
142 contexts
.push(message
);
149 this.updateA11YServiceDurationTimer();
150 this.forceUpdatePickerButton();
153 onNewAccessibleFrontSelected(selected
) {
154 this.emit("new-accessible-front-selected", selected
);
157 onAccessibilityInspectorUpdated() {
158 this.emit("accessibility-inspector-updated");
162 * Make sure the panel is refreshed when the page is reloaded. The panel is
163 * refreshed immediatelly if it's currently selected or lazily when the user
164 * actually selects it.
166 async
forceRefresh() {
167 this.shouldRefresh
= true;
169 // Wait for initialization to be done, in case this is called early on.
170 await
this.initializedPromise
;
171 const onUpdated
= this.panelWin
.once(EVENTS
.INITIALIZED
);
175 this.emit("reloaded");
179 * Make sure the panel is refreshed (if needed) when it's selected.
181 onPanelVisibilityChange() {
182 this._opening
.then(() => this.refresh());
188 if (!this.isVisible
) {
189 // Do not refresh if the panel isn't visible.
193 // Do not refresh if it isn't necessary.
194 if (!this.shouldRefresh
) {
197 // Alright reset the flag we are about to refresh the panel.
198 this.shouldRefresh
= false;
201 getAccessibilityTreeRoot
,
202 startListeningForAccessibilityEvents
,
203 stopListeningForAccessibilityEvents
,
206 toggleDisplayTabbingOrder
,
209 startListeningForLifecycleEvents
,
210 stopListeningForLifecycleEvents
,
211 startListeningForParentLifecycleEvents
,
212 stopListeningForParentLifecycleEvents
,
214 unhighlightAccessible
,
215 } = this.accessibilityProxy
;
216 this.postContentMessage("initialize", {
217 fluentBundles
: this.fluentBundles
,
218 toolbox
: this._toolbox
,
220 getAccessibilityTreeRoot
,
221 startListeningForAccessibilityEvents
,
222 stopListeningForAccessibilityEvents
,
225 toggleDisplayTabbingOrder
,
228 startListeningForLifecycleEvents
,
229 stopListeningForLifecycleEvents
,
230 startListeningForParentLifecycleEvents
,
231 stopListeningForParentLifecycleEvents
,
233 unhighlightAccessible
,
237 updateA11YServiceDurationTimer() {
238 if (this.accessibilityProxy
.enabled
) {
239 this._telemetry
.start(A11Y_SERVICE_DURATION
, this);
241 this._telemetry
.finish(A11Y_SERVICE_DURATION
, this, true);
245 selectAccessible(accessibleFront
) {
246 this.postContentMessage("selectAccessible", accessibleFront
);
249 selectAccessibleForNode(nodeFront
, reason
) {
251 Glean
.devtoolsAccessibility
.selectAccessibleForNode
[reason
].add(1);
254 this.postContentMessage("selectNodeAccessible", nodeFront
);
257 highlightAccessible(accessibleFront
) {
258 this.postContentMessage("highlightAccessible", accessibleFront
);
261 postContentMessage(type
, ...args
) {
262 const event
= new this.panelWin
.MessageEvent("devtools/chrome/message", {
265 data
: { type
, args
},
268 this.panelWin
.dispatchEvent(event
);
271 updatePickerButton() {
272 this.picker
&& this.picker
.updateButton();
275 forceUpdatePickerButton() {
276 // Only update picker button when the panel is selected.
277 if (!this.isVisible
) {
281 this.updatePickerButton();
282 // Calling setToolboxButtons to make sure toolbar is forced to re-render.
283 this._toolbox
.component
.setToolboxButtons(this._toolbox
.toolbarButtons
);
287 this.picker
&& this.picker
.toggle();
291 this.picker
&& this.picker
.cancel();
295 this.picker
&& this.picker
.stop();
299 * Return true if the Accessibility panel is currently selected.
302 return this._toolbox
.currentToolId
=== "accessibility";
306 if (this._destroyed
) {
309 this._destroyed
= true;
311 this.postContentMessage("destroy");
313 if (this.accessibilityProxy
) {
314 this.accessibilityProxy
.stopListeningForLifecycleEvents({
315 init
: this.onLifecycleEvent
,
316 shutdown
: this.onLifecycleEvent
,
318 this.accessibilityProxy
.destroy();
319 this.accessibilityProxy
= null;
320 this.initializedPromise
= null;
323 this._toolbox
.off("select", this.onPanelVisibilityChange
);
326 EVENTS
.NEW_ACCESSIBLE_FRONT_SELECTED
,
327 this.onNewAccessibleFrontSelected
330 EVENTS
.ACCESSIBILITY_INSPECTOR_UPDATED
,
331 this.onAccessibilityInspectorUpdated
334 // Older versions of devtools server do not support picker functionality.
336 this.picker
.release();
340 this._telemetry
= null;
341 this.panelWin
.gTelemetry
= null;
343 this.emit("destroyed");
347 // Exports from this module
348 exports
.AccessibilityPanel
= AccessibilityPanel
;