| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /* eslint-disable @devtools/no-imperative-dom-api */ |
| |
| import type * as Common from '../../core/common/common.js'; |
| import * as Root from '../../core/root/root.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| |
| import {AXNodeSubPane} from './AccessibilityNodeView.js'; |
| import {ARIAAttributesPane} from './ARIAAttributesView.js'; |
| import {AXBreadcrumbsPane} from './AXBreadcrumbsPane.js'; |
| import {SourceOrderPane} from './SourceOrderView.js'; |
| |
| let accessibilitySidebarViewInstance: AccessibilitySidebarView; |
| |
| export class AccessibilitySidebarView extends UI.Widget.VBox { |
| #node: SDK.DOMModel.DOMNode|null; |
| #axNode: SDK.AccessibilityModel.AccessibilityNode|null; |
| private skipNextPullNode: boolean; |
| private readonly sidebarPaneStack: UI.View.ViewLocation; |
| private readonly breadcrumbsSubPane: AXBreadcrumbsPane; |
| private readonly ariaSubPane: ARIAAttributesPane; |
| private readonly axNodeSubPane: AXNodeSubPane; |
| private readonly sourceOrderSubPane: SourceOrderPane; |
| private constructor() { |
| super(); |
| this.element.classList.add('accessibility-sidebar-view'); |
| this.#node = null; |
| this.#axNode = null; |
| this.skipNextPullNode = false; |
| this.sidebarPaneStack = UI.ViewManager.ViewManager.instance().createStackLocation(); |
| this.breadcrumbsSubPane = new AXBreadcrumbsPane(this); |
| void this.sidebarPaneStack.showView(this.breadcrumbsSubPane); |
| this.ariaSubPane = new ARIAAttributesPane(); |
| void this.sidebarPaneStack.showView(this.ariaSubPane); |
| this.axNodeSubPane = new AXNodeSubPane(); |
| void this.sidebarPaneStack.showView(this.axNodeSubPane); |
| this.sourceOrderSubPane = new SourceOrderPane(); |
| void this.sidebarPaneStack.showView(this.sourceOrderSubPane); |
| this.sidebarPaneStack.widget().show(this.element); |
| UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, this.pullNode, this); |
| this.pullNode(); |
| } |
| |
| static instance(opts?: {forceNew: boolean}): AccessibilitySidebarView { |
| if (!accessibilitySidebarViewInstance || opts?.forceNew) { |
| accessibilitySidebarViewInstance = new AccessibilitySidebarView(); |
| } |
| return accessibilitySidebarViewInstance; |
| } |
| |
| node(): SDK.DOMModel.DOMNode|null { |
| return this.#node; |
| } |
| |
| axNode(): SDK.AccessibilityModel.AccessibilityNode|null { |
| return this.#axNode; |
| } |
| |
| setNode(node: SDK.DOMModel.DOMNode|null, fromAXTree?: boolean): void { |
| this.skipNextPullNode = Boolean(fromAXTree); |
| this.#node = node; |
| this.requestUpdate(); |
| } |
| |
| accessibilityNodeCallback(axNode: SDK.AccessibilityModel.AccessibilityNode|null): void { |
| if (!axNode) { |
| return; |
| } |
| |
| this.#axNode = axNode; |
| |
| if (axNode.isDOMNode()) { |
| void this.sidebarPaneStack.showView(this.ariaSubPane, this.axNodeSubPane); |
| } else { |
| this.sidebarPaneStack.removeView(this.ariaSubPane); |
| } |
| |
| this.axNodeSubPane.setAXNode(axNode); |
| this.breadcrumbsSubPane.setAXNode(axNode); |
| } |
| |
| override async performUpdate(): Promise<void> { |
| const node = this.node(); |
| this.axNodeSubPane.setNode(node); |
| this.ariaSubPane.setNode(node); |
| this.breadcrumbsSubPane.setNode(node); |
| void this.sourceOrderSubPane.setNodeAsync(node); |
| if (!node) { |
| return; |
| } |
| const accessibilityModel = node.domModel().target().model(SDK.AccessibilityModel.AccessibilityModel); |
| if (!accessibilityModel) { |
| return; |
| } |
| if (!Root.Runtime.experiments.isEnabled('full-accessibility-tree')) { |
| accessibilityModel.clear(); |
| } |
| await accessibilityModel.requestPartialAXTree(node); |
| this.accessibilityNodeCallback(accessibilityModel.axNodeForDOMNode(node)); |
| } |
| |
| override wasShown(): void { |
| super.wasShown(); |
| |
| // Pull down the latest date for this node. |
| void this.performUpdate(); |
| |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.AttrModified, this.onNodeChange, this, {scoped: true}); |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.AttrRemoved, this.onNodeChange, this, {scoped: true}); |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.CharacterDataModified, this.onNodeChange, this, {scoped: true}); |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.ChildNodeCountUpdated, this.onNodeChange, this, {scoped: true}); |
| } |
| |
| override willHide(): void { |
| super.willHide(); |
| SDK.TargetManager.TargetManager.instance().removeModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.AttrModified, this.onNodeChange, this); |
| SDK.TargetManager.TargetManager.instance().removeModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.AttrRemoved, this.onNodeChange, this); |
| SDK.TargetManager.TargetManager.instance().removeModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.CharacterDataModified, this.onNodeChange, this); |
| SDK.TargetManager.TargetManager.instance().removeModelListener( |
| SDK.DOMModel.DOMModel, SDK.DOMModel.Events.ChildNodeCountUpdated, this.onNodeChange, this); |
| } |
| |
| private pullNode(): void { |
| if (this.skipNextPullNode) { |
| this.skipNextPullNode = false; |
| return; |
| } |
| this.setNode(UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode)); |
| } |
| |
| private onNodeChange( |
| event: Common.EventTarget.EventTargetEvent<{node: SDK.DOMModel.DOMNode, name: string}|SDK.DOMModel.DOMNode>): |
| void { |
| if (!this.node()) { |
| return; |
| } |
| const data = event.data; |
| const node = (data instanceof SDK.DOMModel.DOMNode ? data : data.node); |
| if (this.node() !== node) { |
| return; |
| } |
| this.requestUpdate(); |
| } |
| } |