/* 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"; const EventEmitter = require("devtools/shared/event-emitter"); const Telemetry = require("devtools/client/shared/telemetry"); const { Picker } = require("./picker"); const { A11Y_SERVICE_DURATION } = require("./constants"); // The panel's window global is an EventEmitter firing the following events: const EVENTS = { // When the accessibility inspector has a new accessible front selected. NEW_ACCESSIBLE_FRONT_SELECTED: "Accessibility:NewAccessibleFrontSelected", // When the accessibility inspector has a new accessible front highlighted. NEW_ACCESSIBLE_FRONT_HIGHLIGHTED: "Accessibility:NewAccessibleFrontHighlighted", // When the accessibility inspector has a new accessible front inspected. NEW_ACCESSIBLE_FRONT_INSPECTED: "Accessibility:NewAccessibleFrontInspected", // When the accessibility inspector is updated. ACCESSIBILITY_INSPECTOR_UPDATED: "Accessibility:AccessibilityInspectorUpdated", }; /** * This object represents Accessibility panel. It's responsibility is to * render Accessibility Tree of the current debugger target and the sidebar that * displays current relevant accessible details. */ function AccessibilityPanel(iframeWindow, toolbox, startup) { this.panelWin = iframeWindow; this._toolbox = toolbox; this.startup = startup; this.onTabNavigated = this.onTabNavigated.bind(this); this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this); this.onNewAccessibleFrontSelected = this.onNewAccessibleFrontSelected.bind( this ); this.onAccessibilityInspectorUpdated = this.onAccessibilityInspectorUpdated.bind( this ); this.updateA11YServiceDurationTimer = this.updateA11YServiceDurationTimer.bind( this ); this.forceUpdatePickerButton = this.forceUpdatePickerButton.bind(this); EventEmitter.decorate(this); } AccessibilityPanel.prototype = { /** * Open is effectively an asynchronous constructor. */ async open() { if (this._opening) { await this._opening; return this._opening; } let resolver; this._opening = new Promise(resolve => { resolver = resolve; }); this._telemetry = new Telemetry(); this.panelWin.gTelemetry = this._telemetry; this.target.on("navigate", this.onTabNavigated); this._toolbox.on("select", this.onPanelVisibilityChange); this.panelWin.EVENTS = EVENTS; EventEmitter.decorate(this.panelWin); this.panelWin.on( EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED, this.onNewAccessibleFrontSelected ); this.panelWin.on( EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED, this.onAccessibilityInspectorUpdated ); this.shouldRefresh = true; this.panelWin.gToolbox = this._toolbox; await this._toolbox.initInspector(); await this.startup.initAccessibility(); if (this.supports.enableDisable) { this.picker = new Picker(this); } this.updateA11YServiceDurationTimer(); this.front.on("init", this.updateA11YServiceDurationTimer); this.front.on("shutdown", this.updateA11YServiceDurationTimer); this.front.on("init", this.forceUpdatePickerButton); this.front.on("shutdown", this.forceUpdatePickerButton); this.isReady = true; this.emit("ready"); resolver(this); return this._opening; }, onNewAccessibleFrontSelected(selected) { this.emit("new-accessible-front-selected", selected); }, onAccessibilityInspectorUpdated() { this.emit("accessibility-inspector-updated"); }, /** * Make sure the panel is refreshed when the page is reloaded. The panel is * refreshed immediatelly if it's currently selected or lazily when the user * actually selects it. */ onTabNavigated() { this.shouldRefresh = true; this._opening.then(() => this.refresh()); }, /** * Make sure the panel is refreshed (if needed) when it's selected. */ onPanelVisibilityChange() { this._opening.then(() => this.refresh()); }, refresh() { this.cancelPicker(); if (!this.isVisible) { // Do not refresh if the panel isn't visible. return; } // Do not refresh if it isn't necessary. if (!this.shouldRefresh) { return; } // Alright reset the flag we are about to refresh the panel. this.shouldRefresh = false; this.postContentMessage( "initialize", this.front, this.walker, this.supports ); }, updateA11YServiceDurationTimer() { if (this.front.enabled) { this._telemetry.start(A11Y_SERVICE_DURATION, this); } else { this._telemetry.finish(A11Y_SERVICE_DURATION, this, true); } }, selectAccessible(accessibleFront) { this.postContentMessage("selectAccessible", this.walker, accessibleFront); }, selectAccessibleForNode(nodeFront, reason) { if (reason) { this._telemetry.keyedScalarAdd( "devtools.accessibility.select_accessible_for_node", reason, 1 ); } this.postContentMessage( "selectNodeAccessible", this.walker, nodeFront, this.supports ); }, highlightAccessible(accessibleFront) { this.postContentMessage( "highlightAccessible", this.walker, accessibleFront ); }, postContentMessage(type, ...args) { const event = new this.panelWin.MessageEvent("devtools/chrome/message", { bubbles: true, cancelable: true, data: { type, args }, }); this.panelWin.dispatchEvent(event); }, updatePickerButton() { this.picker && this.picker.updateButton(); }, forceUpdatePickerButton() { // Only update picker button when the panel is selected. if (!this.isVisible) { return; } this.updatePickerButton(); // Calling setToolboxButtons to make sure toolbar is forced to re-render. this._toolbox.component.setToolboxButtons(this._toolbox.toolbarButtons); }, togglePicker(focus) { this.picker && this.picker.toggle(); }, cancelPicker() { this.picker && this.picker.cancel(); }, stopPicker() { this.picker && this.picker.stop(); }, get front() { return this.startup.accessibility; }, get walker() { return this.startup.walker; }, get supports() { return this.startup._supports; }, /** * Return true if the Accessibility panel is currently selected. */ get isVisible() { return this._toolbox.currentToolId === "accessibility"; }, get target() { return this._toolbox.target; }, destroy() { if (this._destroyed) { return; } this._destroyed = true; this.target.off("navigate", this.onTabNavigated); this._toolbox.off("select", this.onPanelVisibilityChange); this.panelWin.off( EVENTS.NEW_ACCESSIBLE_FRONT_SELECTED, this.onNewAccessibleFrontSelected ); this.panelWin.off( EVENTS.ACCESSIBILITY_INSPECTOR_UPDATED, this.onAccessibilityInspectorUpdated ); // Older versions of debugger server do not support picker functionality. if (this.picker) { this.picker.release(); this.picker = null; } if (this.front) { this.front.off("init", this.updateA11YServiceDurationTimer); this.front.off("shutdown", this.updateA11YServiceDurationTimer); this.front.off("init", this.forceUpdatePickerButton); this.front.off("shutdown", this.forceUpdatePickerButton); } this._telemetry = null; this.panelWin.gToolbox = null; this.panelWin.gTelemetry = null; this.emit("destroyed"); }, }; // Exports from this module exports.AccessibilityPanel = AccessibilityPanel;