chrome-devtools-frontend
Version:
Chrome DevTools UI
142 lines (126 loc) • 5.07 kB
text/typescript
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import './components/components.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import type * as TreeOutline from '../../ui/components/tree_outline/tree_outline.js';
import * as Lit from '../../ui/lit/lit.js';
const {html} = Lit;
export type AXTreeNodeData = SDK.AccessibilityModel.AccessibilityNode;
export type AXTreeNode = TreeOutline.TreeOutlineUtils.TreeNode<AXTreeNodeData>;
function isLeafNode(node: SDK.AccessibilityModel.AccessibilityNode): boolean {
return node.numChildren() === 0 && node.role()?.value !== 'Iframe';
}
function getModel(frameId: Protocol.Page.FrameId,
frameManager: SDK.FrameManager.FrameManager =
SDK.FrameManager.FrameManager.instance()): SDK.AccessibilityModel.AccessibilityModel {
const frame = frameManager.getFrame(frameId);
const model = frame?.resourceTreeModel().target().model(SDK.AccessibilityModel.AccessibilityModel);
if (!model) {
throw new Error('Could not instantiate model for frameId');
}
return model;
}
export async function getRootNode(
frameId: Protocol.Page.FrameId,
frameManager: SDK.FrameManager.FrameManager =
SDK.FrameManager.FrameManager.instance()): Promise<SDK.AccessibilityModel.AccessibilityNode> {
const model = getModel(frameId, frameManager);
const root = await model.requestRootNode(frameId);
if (!root) {
throw new Error('No accessibility root for frame');
}
return root;
}
function getFrameIdForNodeOrDocument(node: SDK.DOMModel.DOMNode): Protocol.Page.FrameId {
let frameId;
if (node instanceof SDK.DOMModel.DOMDocument) {
frameId = node.body?.frameId();
} else {
frameId = node.frameId();
}
if (!frameId) {
throw new Error('No frameId for DOM node');
}
return frameId;
}
export async function getNodeAndAncestorsFromDOMNode(
domNode: SDK.DOMModel.DOMNode,
frameManager: SDK.FrameManager.FrameManager =
SDK.FrameManager.FrameManager.instance()): Promise<SDK.AccessibilityModel.AccessibilityNode[]> {
let frameId = getFrameIdForNodeOrDocument(domNode);
const model = getModel(frameId, frameManager);
const result = await model.requestAndLoadSubTreeToNode(domNode);
if (!result) {
throw new Error('Could not retrieve accessibility node for inspected DOM node');
}
const outermostFrameId = frameManager.getOutermostFrame()?.id;
if (!outermostFrameId) {
return result;
}
while (frameId !== outermostFrameId) {
const node = await frameManager.getFrame(frameId)?.getOwnerDOMNodeOrDocument();
if (!node) {
break;
}
frameId = getFrameIdForNodeOrDocument(node);
const model = getModel(frameId, frameManager);
const ancestors = await model.requestAndLoadSubTreeToNode(node);
result.push(...ancestors || []);
}
return result;
}
async function getChildren(node: SDK.AccessibilityModel.AccessibilityNode,
frameManager: SDK.FrameManager.FrameManager = SDK.FrameManager.FrameManager.instance()):
Promise<SDK.AccessibilityModel.AccessibilityNode[]> {
if (node.role()?.value === 'Iframe') {
const domNode = await node.deferredDOMNode()?.resolvePromise();
if (!domNode) {
throw new Error('Could not find corresponding DOMNode');
}
const frameId = domNode.frameOwnerFrameId();
if (!frameId) {
throw new Error('No owner frameId on iframe node');
}
const localRoot = await getRootNode(frameId, frameManager);
return [localRoot];
}
return await node.accessibilityModel().requestAXChildren(node.id(), node.getFrameId() || undefined);
}
export async function sdkNodeToAXTreeNodes(
sdkNode: SDK.AccessibilityModel.AccessibilityNode,
frameManager: SDK.FrameManager.FrameManager = SDK.FrameManager.FrameManager.instance()): Promise<AXTreeNode[]> {
const treeNodeData = sdkNode;
if (isLeafNode(sdkNode)) {
return [{
treeNodeData,
id: getNodeId(sdkNode),
}];
}
return [{
treeNodeData,
children: async () => {
const childNodes = await getChildren(sdkNode, frameManager);
const childTreeNodes =
await Promise.all(childNodes.map(childNode => sdkNodeToAXTreeNodes(childNode, frameManager)));
return childTreeNodes.flat(1);
},
id: getNodeId(sdkNode),
}];
}
export function accessibilityNodeRenderer(node: AXTreeNode): Lit.TemplateResult {
const sdkNode = node.treeNodeData;
const name = sdkNode.name()?.value || '';
const role = sdkNode.role()?.value || '';
const properties = sdkNode.properties() || [];
const ignored = sdkNode.ignored();
const id = getNodeId(sdkNode);
return html`<devtools-accessibility-tree-node .data=${{
name, role, ignored, properties, id,
}
}></devtools-accessibility-tree-node>`;
}
export function getNodeId(node: SDK.AccessibilityModel.AccessibilityNode): string {
return node.getFrameId() + '#' + node.id();
}