Skip to content

Commit e1bf190

Browse files
johanbayDevtools-frontend LUCI CQ
authored and
Devtools-frontend LUCI CQ
committed
Encapsulate accessibility tree node styling using components
This CL introduces `AccessibilityTreeNode` components for nodes in the accessibility tree. It also moves AccessibilityTreeUtils.ts out of `components/` since it isn't a component. Bug: 1226486 Change-Id: I05c2e6e2bf6e1794efaf5c5c99a0bd21678eae79 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3015735 Commit-Queue: Johan Bay <[email protected]> Reviewed-by: Jack Franklin <[email protected]> Reviewed-by: Changhao Han <[email protected]>
1 parent dfc8e51 commit e1bf190

14 files changed

+183
-144
lines changed

config/gni/devtools_grd_files.gni

+3-1
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ grd_files_debug_sources = [
823823
"front_end/panels/css_overview/CSSOverviewUnusedDeclarations.js",
824824
"front_end/panels/developer_resources/DeveloperResourcesListView.js",
825825
"front_end/panels/developer_resources/DeveloperResourcesView.js",
826+
"front_end/panels/elements/AccessibilityTreeUtils.js",
826827
"front_end/panels/elements/AccessibilityTreeView.js",
827828
"front_end/panels/elements/ClassesPaneWidget.js",
828829
"front_end/panels/elements/ColorSwatchPopoverIcon.js",
@@ -850,7 +851,7 @@ grd_files_debug_sources = [
850851
"front_end/panels/elements/StylePropertyHighlighter.js",
851852
"front_end/panels/elements/StylePropertyTreeElement.js",
852853
"front_end/panels/elements/StylesSidebarPane.js",
853-
"front_end/panels/elements/components/AccessibilityTreeUtils.js",
854+
"front_end/panels/elements/components/AccessibilityTreeNode.js",
854855
"front_end/panels/elements/components/AdornerManager.js",
855856
"front_end/panels/elements/components/AdornerSettingsPane.js",
856857
"front_end/panels/elements/components/CSSPropertyIconResolver.js",
@@ -866,6 +867,7 @@ grd_files_debug_sources = [
866867
"front_end/panels/elements/components/NodeText.js",
867868
"front_end/panels/elements/components/QueryContainer.js",
868869
"front_end/panels/elements/components/StylePropertyEditor.js",
870+
"front_end/panels/elements/components/accessibilityTreeNode.css.js",
869871
"front_end/panels/elements/components/adornerSettingsPane.css.js",
870872
"front_end/panels/elements/components/computedStyleProperty.css.js",
871873
"front_end/panels/elements/components/computedStyleTrace.css.js",

front_end/core/i18n/locales/en-US.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3842,7 +3842,7 @@
38423842
"panels/elements/ColorSwatchPopoverIcon.ts | openShadowEditor": {
38433843
"message": "Open shadow editor"
38443844
},
3845-
"panels/elements/components/AccessibilityTreeUtils.ts | ignored": {
3845+
"panels/elements/components/AccessibilityTreeNode.ts | ignored": {
38463846
"message": "Ignored"
38473847
},
38483848
"panels/elements/components/AdornerSettingsPane.ts | closeButton": {

front_end/core/i18n/locales/en-XL.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3842,7 +3842,7 @@
38423842
"panels/elements/ColorSwatchPopoverIcon.ts | openShadowEditor": {
38433843
"message": "Ôṕêń ŝh́âd́ôẃ êd́ît́ôŕ"
38443844
},
3845-
"panels/elements/components/AccessibilityTreeUtils.ts | ignored": {
3845+
"panels/elements/components/AccessibilityTreeNode.ts | ignored": {
38463846
"message": "Îǵn̂ór̂éd̂"
38473847
},
38483848
"panels/elements/components/AdornerSettingsPane.ts | closeButton": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import type * as SDK from '../../core/sdk/sdk.js';
6+
import type * as TreeOutline from '../../ui/components/tree_outline/tree_outline.js';
7+
import * as ElementsComponents from './components/components.js';
8+
import * as LitHtml from '../../ui/lit-html/lit-html.js';
9+
10+
export type AXTreeNodeData = SDK.AccessibilityModel.AccessibilityNode;
11+
export type AXTreeNode = TreeOutline.TreeOutlineUtils.TreeNode<AXTreeNodeData>;
12+
13+
export function sdkNodeToAXTreeNode(sdkNode: SDK.AccessibilityModel.AccessibilityNode): AXTreeNode {
14+
const treeNodeData = sdkNode;
15+
if (!sdkNode.numChildren()) {
16+
return {
17+
treeNodeData,
18+
id: sdkNode.id(),
19+
};
20+
}
21+
22+
return {
23+
treeNodeData,
24+
children: async(): Promise<AXTreeNode[]> => {
25+
if (sdkNode.numChildren() === sdkNode.children().length) {
26+
return Promise.resolve(sdkNode.children().map(child => sdkNodeToAXTreeNode(child)));
27+
}
28+
// numChildren returns the number of children that this node has, whereas node.children()
29+
// returns only children that have been loaded. If these two don't match, that means that
30+
// there are backend children that need to be loaded into the model, so request them now.
31+
await sdkNode.accessibilityModel().requestAXChildren(sdkNode.id());
32+
33+
if (sdkNode.numChildren() !== sdkNode.children().length) {
34+
throw new Error('Once loaded, number of children and length of children must match.');
35+
}
36+
37+
const treeNodeChildren: AXTreeNode[] = [];
38+
39+
for (const child of sdkNode.children()) {
40+
treeNodeChildren.push(sdkNodeToAXTreeNode(child));
41+
}
42+
43+
return Promise.resolve(treeNodeChildren);
44+
},
45+
id: sdkNode.id(),
46+
};
47+
}
48+
49+
type Data = ElementsComponents.AccessibilityTreeNode.AccessibilityTreeNodeData;
50+
51+
export function accessibilityNodeRenderer(node: AXTreeNode): LitHtml.TemplateResult {
52+
const tag = ElementsComponents.AccessibilityTreeNode.AccessibilityTreeNode.litTagName;
53+
const sdkNode = node.treeNodeData;
54+
const name = sdkNode.name()?.value || '';
55+
const role = sdkNode.role()?.value || '';
56+
const ignored = sdkNode.ignored();
57+
return LitHtml.html`<${tag} .data=${{name, role, ignored} as Data}></${tag}>`;
58+
}
59+
60+
export interface AccessibilityTreeNodeData {
61+
ignored: boolean;
62+
name: string;
63+
role: string;
64+
}

front_end/panels/elements/AccessibilityTreeView.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@ import * as Common from '../../core/common/common.js';
66
import * as SDK from '../../core/sdk/sdk.js';
77
import * as TreeOutline from '../../ui/components/tree_outline/tree_outline.js';
88
import * as UI from '../../ui/legacy/legacy.js';
9+
import * as AccessibilityTreeUtils from './AccessibilityTreeUtils.js';
910
import type * as LitHtml from '../../ui/lit-html/lit-html.js';
1011

11-
import * as ElementsComponents from './components/components.js';
12-
1312
export class AccessibilityTreeView extends UI.Widget.VBox {
1413
private readonly accessibilityTreeComponent =
15-
new TreeOutline.TreeOutline.TreeOutline<SDK.AccessibilityModel.AccessibilityNode>();
16-
private treeData: ElementsComponents.AccessibilityTreeUtils.AXTreeNode[] = [];
14+
new TreeOutline.TreeOutline.TreeOutline<AccessibilityTreeUtils.AXTreeNodeData>();
15+
private treeData: AccessibilityTreeUtils.AXTreeNode[] = [];
1716
private readonly toggleButton: HTMLButtonElement;
1817
private accessibilityModel: SDK.AccessibilityModel.AccessibilityModel|null = null;
1918
private rootAXNode: SDK.AccessibilityModel.AccessibilityNode|null = null;
20-
private selectedTreeNode: ElementsComponents.AccessibilityTreeUtils.AXTreeNode|null = null;
19+
private selectedTreeNode: AccessibilityTreeUtils.AXTreeNode|null = null;
2120
private inspectedDOMNode: SDK.DOMModel.DOMNode|null = null;
2221

2322
constructor(toggleButton: HTMLButtonElement) {
@@ -32,7 +31,7 @@ export class AccessibilityTreeView extends UI.Widget.VBox {
3231
// on node selection, update the currently inspected node and reveal in the
3332
// DOM tree.
3433
this.accessibilityTreeComponent.addEventListener('itemselected', (event: Event) => {
35-
const evt = event as TreeOutline.TreeOutline.ItemSelectedEvent<SDK.AccessibilityModel.AccessibilityNode>;
34+
const evt = event as TreeOutline.TreeOutline.ItemSelectedEvent<AccessibilityTreeUtils.AXTreeNodeData>;
3635
const axNode = evt.data.node.treeNodeData;
3736
if (!axNode.isDOMNode()) {
3837
return;
@@ -48,11 +47,11 @@ export class AccessibilityTreeView extends UI.Widget.VBox {
4847
}
4948

5049
// Highlight the node as well, for keyboard navigation.
51-
evt.data.node.treeNodeData.highlightDOMNode();
50+
axNode.highlightDOMNode();
5251
});
5352

5453
this.accessibilityTreeComponent.addEventListener('itemmouseover', (event: Event) => {
55-
const evt = event as TreeOutline.TreeOutline.ItemMouseOverEvent<SDK.AccessibilityModel.AccessibilityNode>;
54+
const evt = event as TreeOutline.TreeOutline.ItemMouseOverEvent<AccessibilityTreeUtils.AXTreeNodeData>;
5655
evt.data.node.treeNodeData.highlightDOMNode();
5756
});
5857

@@ -83,11 +82,10 @@ export class AccessibilityTreeView extends UI.Widget.VBox {
8382
}
8483

8584
this.rootAXNode = root;
86-
this.treeData = [ElementsComponents.AccessibilityTreeUtils.sdkNodeToAXTreeNode(this.rootAXNode)];
85+
this.treeData = [AccessibilityTreeUtils.sdkNodeToAXTreeNode(this.rootAXNode)];
8786

8887
this.accessibilityTreeComponent.data = {
89-
defaultRenderer: (node): LitHtml.TemplateResult =>
90-
ElementsComponents.AccessibilityTreeUtils.accessibilityNodeRenderer(node),
88+
defaultRenderer: (node): LitHtml.TemplateResult => AccessibilityTreeUtils.accessibilityNodeRenderer(node),
9189
tree: this.treeData,
9290
};
9391

@@ -122,8 +120,7 @@ export class AccessibilityTreeView extends UI.Widget.VBox {
122120
}
123121

124122
this.accessibilityTreeComponent.data = {
125-
defaultRenderer: (node): LitHtml.TemplateResult =>
126-
ElementsComponents.AccessibilityTreeUtils.accessibilityNodeRenderer(node),
123+
defaultRenderer: (node): LitHtml.TemplateResult => AccessibilityTreeUtils.accessibilityNodeRenderer(node),
127124
tree: this.treeData,
128125
};
129126

@@ -138,7 +135,7 @@ export class AccessibilityTreeView extends UI.Widget.VBox {
138135
return;
139136
}
140137

141-
this.selectedTreeNode = ElementsComponents.AccessibilityTreeUtils.sdkNodeToAXTreeNode(inspectedAXNode);
138+
this.selectedTreeNode = AccessibilityTreeUtils.sdkNodeToAXTreeNode(inspectedAXNode);
142139
this.accessibilityTreeComponent.expandToAndSelectTreeNode(this.selectedTreeNode);
143140
}
144141

front_end/panels/elements/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import("../visibility.gni")
99

1010
devtools_module("elements") {
1111
sources = [
12+
"AccessibilityTreeUtils.ts",
1213
"AccessibilityTreeView.ts",
1314
"ClassesPaneWidget.ts",
1415
"ColorSwatchPopoverIcon.ts",

front_end/panels/elements/ElementsPanel.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,7 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
318318
this._accessibilityTreeView.setAccessibilityModel(
319319
domModel.target().model(SDK.AccessibilityModel.AccessibilityModel));
320320
}
321-
let treeOutline: ElementsTreeOutline|(ElementsTreeOutline | null) =
322-
parentModel ? ElementsTreeOutline.forDOMModel(parentModel) : null;
321+
let treeOutline: ElementsTreeOutline|null = parentModel ? ElementsTreeOutline.forDOMModel(parentModel) : null;
323322
if (!treeOutline) {
324323
treeOutline = new ElementsTreeOutline(true, true);
325324
treeOutline.setWordWrap(Common.Settings.Settings.instance().moduleSetting('domWordWrap').get());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2021 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as i18n from '../../../core/i18n/i18n.js';
6+
import * as Platform from '../../../core/platform/platform.js';
7+
8+
import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
9+
import * as Coordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
10+
import * as LitHtml from '../../../ui/lit-html/lit-html.js';
11+
12+
import accessibilityTreeNodeStyles from './accessibilityTreeNode.css.js';
13+
14+
15+
const UIStrings = {
16+
/**
17+
*@description Ignored node element text content in Accessibility Tree View of the Elements panel
18+
*/
19+
ignored: 'Ignored',
20+
};
21+
const str_ = i18n.i18n.registerUIStrings('panels/elements/components/AccessibilityTreeNode.ts', UIStrings);
22+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
23+
24+
// TODO(jobay) move this to Platform.StringUtilities if still needed.
25+
// This function is a variant of setTextContentTruncatedIfNeeded found in DOMExtension.
26+
function truncateTextIfNeeded(text: string): string {
27+
const maxTextContentLength = 10000;
28+
29+
if (text.length > maxTextContentLength) {
30+
return Platform.StringUtilities.trimMiddle(text, maxTextContentLength);
31+
}
32+
return text;
33+
}
34+
35+
36+
export interface AccessibilityTreeNodeData {
37+
ignored: boolean;
38+
name: string;
39+
role: string;
40+
}
41+
42+
export class AccessibilityTreeNode extends HTMLElement {
43+
static readonly litTagName = LitHtml.literal`devtools-accessibility-tree-node`;
44+
private readonly shadow = this.attachShadow({mode: 'open'});
45+
46+
private ignored = true;
47+
private name = '';
48+
private role = '';
49+
50+
set data(data: AccessibilityTreeNodeData) {
51+
this.ignored = data.ignored;
52+
this.name = data.name;
53+
this.role = data.role;
54+
this.render();
55+
}
56+
57+
connectedCallback(): void {
58+
this.shadow.adoptedStyleSheets = [accessibilityTreeNodeStyles];
59+
}
60+
61+
private async render(): Promise<void> {
62+
await Coordinator.RenderCoordinator.RenderCoordinator.instance().write('Accessibility node render', () => {
63+
// clang-format off
64+
LitHtml.render(LitHtml.html`
65+
${this.ignored?
66+
LitHtml.html`<span class='monospace ignored-node'>${i18nString(UIStrings.ignored)}</span>`:
67+
LitHtml.html`
68+
<span class='monospace'>${truncateTextIfNeeded(this.role)}</span>
69+
<span class='separator'>\xA0</span>
70+
<span class='ax-readable-string'>"${this.name}"</span>
71+
`}
72+
`, this.shadow, {
73+
host: this,
74+
});
75+
// clang-format on
76+
});
77+
}
78+
}
79+
80+
ComponentHelpers.CustomElements.defineComponent('devtools-accessibility-tree-node', AccessibilityTreeNode);
81+
82+
declare global {
83+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
84+
interface HTMLElementTagNameMap {
85+
'devtools-accessibility-tree-node': AccessibilityTreeNode;
86+
}
87+
}

0 commit comments

Comments
 (0)