mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-30 01:43:36 +08:00
* Created a container in AccessibilityTreeView to move overflow responsibility there so that we can have 100% height container with scrollbar in the bottom. (crbug/1341960) * Added `hover` state to the tree node so that now the hovered over item is distinguishable (crbug/1340960) * Removed some non breaking space usages so that the content can be wrapped to a different line. Hover state: https://bugs.chromium.org/p/chromium/issues/attachment?aid=564424&signed_aid=pN3V_6G9BN7lj4AzBfr5pA==&inline=1 Fixed: 1341960, 1340960 Change-Id: I7a53074b4be5899d9745becc588c0d6655eb93ce Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3807506 Reviewed-by: Changhao Han <changhaohan@chromium.org> Commit-Queue: Ergün Erdoğmuş <ergunsh@chromium.org>
165 lines
6.5 KiB
TypeScript
165 lines
6.5 KiB
TypeScript
// Copyright 2020 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import type * as Common from '../../core/common/common.js';
|
|
import * as SDK from '../../core/sdk/sdk.js';
|
|
import * as TreeOutline from '../../ui/components/tree_outline/tree_outline.js';
|
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
import * as AccessibilityTreeUtils from './AccessibilityTreeUtils.js';
|
|
import accessibilityTreeViewStyles from './accessibilityTreeView.css.js';
|
|
import {ElementsPanel} from './ElementsPanel.js';
|
|
|
|
export class AccessibilityTreeView extends UI.Widget.VBox implements
|
|
SDK.TargetManager.SDKModelObserver<SDK.AccessibilityModel.AccessibilityModel> {
|
|
private accessibilityTreeComponent = new TreeOutline.TreeOutline.TreeOutline<AccessibilityTreeUtils.AXTreeNodeData>();
|
|
private readonly toggleButton: HTMLElement;
|
|
private inspectedDOMNode: SDK.DOMModel.DOMNode|null = null;
|
|
private root: SDK.AccessibilityModel.AccessibilityNode|null = null;
|
|
|
|
constructor(toggleButton: HTMLElement) {
|
|
super();
|
|
// toggleButton is bound to a click handler on ElementsPanel to switch between the DOM tree
|
|
// and accessibility tree views.
|
|
this.toggleButton = toggleButton;
|
|
|
|
const container = this.contentElement.createChild('div');
|
|
|
|
container.classList.add('accessibility-tree-view-container');
|
|
container.appendChild(this.toggleButton);
|
|
container.appendChild(this.accessibilityTreeComponent);
|
|
|
|
SDK.TargetManager.TargetManager.instance().observeModels(SDK.AccessibilityModel.AccessibilityModel, this);
|
|
|
|
// The DOM tree and accessibility are kept in sync as much as possible, so
|
|
// on node selection, update the currently inspected node and reveal in the
|
|
// DOM tree.
|
|
this.accessibilityTreeComponent.addEventListener('itemselected', (event: Event) => {
|
|
const evt = event as TreeOutline.TreeOutline.ItemSelectedEvent<AccessibilityTreeUtils.AXTreeNodeData>;
|
|
const axNode = evt.data.node.treeNodeData;
|
|
if (!axNode.isDOMNode()) {
|
|
return;
|
|
}
|
|
const deferredNode = axNode.deferredDOMNode();
|
|
if (deferredNode) {
|
|
deferredNode.resolve(domNode => {
|
|
if (domNode) {
|
|
this.inspectedDOMNode = domNode;
|
|
void ElementsPanel.instance().revealAndSelectNode(domNode, true, false);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
this.accessibilityTreeComponent.addEventListener('itemmouseover', (event: Event) => {
|
|
const evt = event as TreeOutline.TreeOutline.ItemMouseOverEvent<AccessibilityTreeUtils.AXTreeNodeData>;
|
|
evt.data.node.treeNodeData.highlightDOMNode();
|
|
});
|
|
|
|
this.accessibilityTreeComponent.addEventListener('itemmouseout', () => {
|
|
SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
|
|
});
|
|
}
|
|
|
|
async wasShown(): Promise<void> {
|
|
await this.refreshAccessibilityTree();
|
|
if (this.inspectedDOMNode) {
|
|
await this.loadSubTreeIntoAccessibilityModel(this.inspectedDOMNode);
|
|
}
|
|
this.registerCSSFiles([accessibilityTreeViewStyles]);
|
|
}
|
|
|
|
async refreshAccessibilityTree(): Promise<void> {
|
|
if (!this.root) {
|
|
const frameId = SDK.FrameManager.FrameManager.instance().getTopFrame()?.id;
|
|
if (!frameId) {
|
|
throw Error('No top frame');
|
|
}
|
|
this.root = await AccessibilityTreeUtils.getRootNode(frameId);
|
|
if (!this.root) {
|
|
throw Error('No root');
|
|
}
|
|
}
|
|
await this.renderTree();
|
|
await this.accessibilityTreeComponent.expandRecursively(1);
|
|
}
|
|
|
|
async renderTree(): Promise<void> {
|
|
if (!this.root) {
|
|
return;
|
|
}
|
|
const treeData = await AccessibilityTreeUtils.sdkNodeToAXTreeNodes(this.root);
|
|
this.accessibilityTreeComponent.data = {
|
|
defaultRenderer: AccessibilityTreeUtils.accessibilityNodeRenderer,
|
|
tree: treeData,
|
|
filter: (node): TreeOutline.TreeOutline.FilterOption => {
|
|
return node.ignored() || (node.role()?.value === 'generic' && !node.name()?.value) ?
|
|
TreeOutline.TreeOutline.FilterOption.FLATTEN :
|
|
TreeOutline.TreeOutline.FilterOption.SHOW;
|
|
},
|
|
};
|
|
}
|
|
|
|
// Given a selected DOM node, asks the model to load the missing subtree from the root to the
|
|
// selected node and then re-renders the tree.
|
|
async loadSubTreeIntoAccessibilityModel(selectedNode: SDK.DOMModel.DOMNode): Promise<void> {
|
|
const ancestors = await AccessibilityTreeUtils.getNodeAndAncestorsFromDOMNode(selectedNode);
|
|
const inspectedAXNode = ancestors.find(node => node.backendDOMNodeId() === selectedNode.backendNodeId());
|
|
if (!inspectedAXNode) {
|
|
return;
|
|
}
|
|
await this.accessibilityTreeComponent.expandNodeIds(ancestors.map(node => node.getFrameId() + '#' + node.id()));
|
|
await this.accessibilityTreeComponent.focusNodeId(AccessibilityTreeUtils.getNodeId(inspectedAXNode));
|
|
}
|
|
|
|
// A node was revealed through the elements picker.
|
|
async revealAndSelectNode(inspectedNode: SDK.DOMModel.DOMNode): Promise<void> {
|
|
if (inspectedNode === this.inspectedDOMNode) {
|
|
return;
|
|
}
|
|
this.inspectedDOMNode = inspectedNode;
|
|
// We only want to load nodes into the model when the AccessibilityTree is visible.
|
|
if (this.isShowing()) {
|
|
await this.loadSubTreeIntoAccessibilityModel(inspectedNode);
|
|
}
|
|
}
|
|
|
|
// Selected node in the DOM tree has changed.
|
|
async selectedNodeChanged(inspectedNode: SDK.DOMModel.DOMNode): Promise<void> {
|
|
if (this.isShowing() || (inspectedNode === this.inspectedDOMNode)) {
|
|
return;
|
|
}
|
|
if (inspectedNode.ownerDocument && (inspectedNode.nodeName() === 'HTML' || inspectedNode.nodeName() === 'BODY')) {
|
|
this.inspectedDOMNode = inspectedNode.ownerDocument;
|
|
} else {
|
|
this.inspectedDOMNode = inspectedNode;
|
|
}
|
|
}
|
|
|
|
treeUpdated({data}: Common.EventTarget
|
|
.EventTargetEvent<SDK.AccessibilityModel.EventTypes[SDK.AccessibilityModel.Events.TreeUpdated]>):
|
|
void {
|
|
if (!data.root) {
|
|
void this.renderTree();
|
|
return;
|
|
}
|
|
const topFrameId = SDK.FrameManager.FrameManager.instance().getTopFrame()?.id;
|
|
if (data.root?.getFrameId() !== topFrameId) {
|
|
void this.renderTree();
|
|
return;
|
|
}
|
|
this.root = data.root;
|
|
void this.accessibilityTreeComponent.collapseAllNodes();
|
|
|
|
void this.refreshAccessibilityTree();
|
|
}
|
|
|
|
modelAdded(model: SDK.AccessibilityModel.AccessibilityModel): void {
|
|
model.addEventListener(SDK.AccessibilityModel.Events.TreeUpdated, this.treeUpdated, this);
|
|
}
|
|
|
|
modelRemoved(model: SDK.AccessibilityModel.AccessibilityModel): void {
|
|
model.removeEventListener(SDK.AccessibilityModel.Events.TreeUpdated, this.treeUpdated, this);
|
|
}
|
|
}
|