| // Copyright 2015 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. |
| |
| (function initialize_DumpAccessibilityNodesTest(testRunner, session) { |
| |
| function trackGetChildNodesEvents(nodeInfo, callback) |
| { |
| session.protocol.DOM.onSetChildNodes(setChildNodes); |
| |
| function setChildNodes(message) |
| { |
| var nodes = message.params.nodes; |
| for (var i = 0; i < nodes.length; ++i) |
| addNode(nodeInfo, nodes[i]); |
| if (callback) |
| callback(); |
| } |
| } |
| |
| function addNode(nodeInfo, node) |
| { |
| nodeInfo[node.nodeId] = node; |
| delete node.nodeId; |
| var children = node.children || []; |
| for (var i = 0; i < children.length; ++i) |
| addNode(nodeInfo, children[i]); |
| var shadowRoots = node.shadowRoots || []; |
| for (var i = 0; i < shadowRoots.length; ++i) |
| addNode(nodeInfo, shadowRoots[i]); |
| } |
| |
| async function requestDocumentNodeId(callback) |
| { |
| var result = (await session.protocol.DOM.getDocument()).result; |
| if (callback) |
| callback(result.root.nodeId); |
| return result.root.nodeId; |
| }; |
| |
| async function requestNodeId(documentNodeId, selector, callback) |
| { |
| var result = (await session.protocol.DOM.querySelector({"nodeId": documentNodeId , "selector": selector})).result; |
| if (callback) |
| callback(result.nodeId); |
| return result.nodeId; |
| }; |
| |
| var nodeInfo = {}; |
| trackGetChildNodesEvents(nodeInfo); |
| |
| function dumpAccessibilityNodesBySelectorAndCompleteTest(selector, fetchRelatives, msg) { |
| if (msg.error) { |
| testRunner.log(msg.error.message); |
| testRunner.completeTest(); |
| return; |
| } |
| |
| var rootNode = msg.result.root; |
| var rootNodeId = rootNode.nodeId; |
| addNode(nodeInfo, rootNode); |
| |
| sendQuerySelectorAll(rootNodeId, selector) |
| .then((msg) => { return getAXNodes(msg, fetchRelatives || false) } ) |
| .then(() => { done(); }) |
| .then(() => { |
| testRunner.completeTest(); |
| }) |
| .catch((msg) => { testRunner.log("Error: " + JSON.stringify(msg)); }) |
| } |
| |
| function done() |
| { |
| session.protocol.Runtime.evaluate({expression: "done();"}); |
| } |
| |
| function sendQuerySelectorAll(nodeId, selector) |
| { |
| return session.protocol.DOM.querySelectorAll({"nodeId": nodeId, "selector": selector }); |
| } |
| |
| function getAXNodes(msg, fetchRelatives) |
| { |
| var promise = Promise.resolve(); |
| if (!msg.result || !msg.result.nodeIds) { |
| testRunner.log("Unexpected result: " + JSON.stringify(msg)); |
| testRunner.completeTest(); |
| } |
| msg.result.nodeIds.forEach((id) => { |
| if (fetchRelatives) { |
| promise = promise.then(() => { |
| return session.protocol.Accessibility.getPartialAXTree({ "nodeId": id, "fetchRelatives": true }); |
| }); |
| promise = promise.then((msg) => { return rewriteRelatedNodes(msg, id); }) |
| .then((msg) => { return dumpTreeStructure(msg); }); |
| |
| } |
| promise = promise.then(() => { return session.protocol.Accessibility.getPartialAXTree({ "nodeId": id, "fetchRelatives": false }); }) |
| .then((msg) => { return rewriteRelatedNodes(msg, id); }) |
| .then((msg) => { return dumpNode(msg); }); |
| |
| }); |
| return promise; |
| } |
| |
| function describeDomNode(nodeData) |
| { |
| var description = nodeData.nodeName.toLowerCase(); |
| switch (nodeData.nodeType) { |
| case Node.ELEMENT_NODE: |
| var p = nodeData.attributes.indexOf("id"); |
| if (p >= 0) |
| description += "#" + nodeData.attributes[p + 1]; |
| } |
| return description; |
| } |
| |
| function rewriteBackendDomNodeId(axNode, selectedNodeId, promises) |
| { |
| if (!("backendDOMNodeId" in axNode)) |
| return; |
| |
| function rewriteBackendDomNodeIdPromise(resolve, reject) |
| { |
| if (!("backendDOMNodeId" in axNode)) { |
| resolve(); |
| return; |
| } |
| var backendDOMNodeId = axNode.backendDOMNodeId; |
| |
| function onDomNodeResolved(backendDOMNodeId, message) |
| { |
| if (!message.result || !message.result.nodeIds) { |
| testRunner.log("Unexpected result for pushNodesByBackendIdsToFrontend: " + JSON.stringify(message)); |
| testRunner.completeTest(); |
| return; |
| } |
| var nodeId = message.result.nodeIds[0]; |
| if (!(nodeId in nodeInfo)) { |
| axNode.domNode = "[NODE NOT FOUND]"; |
| resolve(); |
| return; |
| } |
| var domNode = nodeInfo[nodeId]; |
| delete axNode.backendDOMNodeId; |
| axNode.domNode = describeDomNode(domNode); |
| if (nodeId === selectedNodeId) |
| axNode.selected = true; |
| resolve(); |
| } |
| |
| var params = { "backendNodeIds": [ backendDOMNodeId ] }; |
| session.protocol.DOM.pushNodesByBackendIdsToFrontend(params).then(onDomNodeResolved.bind(null, backendDOMNodeId)); |
| } |
| promises.push(new Promise(rewriteBackendDomNodeIdPromise)); |
| } |
| |
| function rewriteRelatedNode(relatedNode) |
| { |
| function rewriteRelatedNodePromise(resolve, reject) |
| { |
| if (!("backendDOMNodeId" in relatedNode)) { |
| reject("Could not find backendDOMNodeId in " + JSON.stringify(relatedNode)); |
| return; |
| } |
| var backendDOMNodeId = relatedNode.backendDOMNodeId; |
| |
| function onNodeResolved(backendDOMNodeId, message) |
| { |
| if (!message.result || !message.result.nodeIds) { |
| testRunner.log("Unexpected result for pushNodesByBackendIdsToFrontend: " + JSON.stringify(message)); |
| testRunner.completeTest(); |
| return; |
| } |
| var nodeId = message.result.nodeIds[0]; |
| if (!(nodeId in nodeInfo)) { |
| relatedNode.nodeResult = "[NODE NOT FOUND]"; |
| resolve(); |
| return; |
| } |
| var domNode = nodeInfo[nodeId]; |
| delete relatedNode.backendDOMNodeId; |
| relatedNode.nodeResult = describeDomNode(domNode); |
| resolve(); |
| } |
| var params = { "backendNodeIds": [ backendDOMNodeId ] }; |
| session.protocol.DOM.pushNodesByBackendIdsToFrontend(params).then(onNodeResolved.bind(null, backendDOMNodeId)); |
| |
| } |
| return new Promise(rewriteRelatedNodePromise); |
| } |
| |
| function checkExists(path, obj) |
| { |
| var pathComponents = path.split("."); |
| var currentPath = []; |
| var currentObject = obj; |
| for (var component of pathComponents) { |
| var isArray = false; |
| var index = -1; |
| var matches = component.match(/(\w+)\[(\d+)\]/); |
| if (matches) { |
| isArray = true; |
| component = matches[1]; |
| index = Number.parseInt(matches[2], 10); |
| } |
| currentPath.push(component); |
| if (!(component in currentObject)) { |
| testRunner.log("Could not find " + currentPath.join(".") + " in " + JSON.stringify(obj, null, " ")); |
| testRunner.completeTest(); |
| } |
| if (isArray) |
| currentObject = currentObject[component][index]; |
| else |
| currentObject = currentObject[component]; |
| } |
| return true; |
| } |
| |
| function check(condition, errorMsg, obj) |
| { |
| if (condition) |
| return true; |
| throw new Error(errorMsg + " in " + JSON.stringify(obj, null, " ")); |
| } |
| |
| function rewriteRelatedNodeValue(value, promises) |
| { |
| checkExists("relatedNodes", value); |
| var relatedNodeArray = value.relatedNodes; |
| check(Array.isArray(relatedNodeArray), "relatedNodes should be an array", JSON.stringify(value)); |
| for (var relatedNode of relatedNodeArray) { |
| promises.push(rewriteRelatedNode(relatedNode)); |
| } |
| } |
| |
| function rewriteRelatedNodes(msg, nodeId) |
| { |
| if (msg.error) { |
| throw new Error(msg.error.message); |
| } |
| |
| var promises = []; |
| for (var node of msg.result.nodes) { |
| if (node.ignored) { |
| checkExists("ignoredReasons", node); |
| var properties = node.ignoredReasons; |
| } else { |
| checkExists("properties", node); |
| var properties = node.properties; |
| } |
| if (node.name && node.name.sources) { |
| for (var source of node.name.sources) { |
| var value; |
| if (source.value) |
| value = source.value; |
| if (source.attributeValue) |
| value = source.attributeValue; |
| if (!value) |
| continue; |
| if (value.type === "idrefList" || |
| value.type === "idref" || |
| value.type === "nodeList") |
| rewriteRelatedNodeValue(value, promises); |
| } |
| } |
| for (var property of properties) { |
| if (property.value.type === "idrefList" || |
| property.value.type === "idref" || |
| property.value.type === "nodeList") |
| rewriteRelatedNodeValue(property.value, promises); |
| } |
| rewriteBackendDomNodeId(node, nodeId, promises); |
| } |
| return Promise.all(promises).then(() => { return msg; }); |
| } |
| |
| function dumpNode(msg) |
| { |
| function stripIds(key, value) |
| { |
| var stripKeys = ["id", "backendDOMNodeId", "nodeId", "parentId"]; |
| if (stripKeys.indexOf(key) !== -1) |
| return "<" + typeof(value) + ">"; |
| var deleteKeys = ["selected"]; |
| if (deleteKeys.indexOf(key) !== -1) |
| return undefined; |
| return value; |
| } |
| if (!msg.result || !msg.result.nodes || msg.result.nodes.length !== 1) { |
| testRunner.log("Expected exactly one node in " + JSON.stringify(msg, null, " ")); |
| return; |
| } |
| testRunner.log(JSON.stringify(msg.result.nodes[0], stripIds, " ")); |
| } |
| |
| function dumpTreeStructure(msg) |
| { |
| function printNodeAndChildren(node, leadingSpace) |
| { |
| leadingSpace = leadingSpace || ""; |
| var string = leadingSpace; |
| if (node.selected) |
| string += "*"; |
| if (node.role) |
| string += node.role.value; |
| else |
| string += "<no role>"; |
| string += (node.name && node.name.value !== "" ? " \"" + node.name.value + "\"" : ""); |
| if (node.children) { |
| for (var child of node.children) |
| string += "\n" + printNodeAndChildren(child, leadingSpace + " "); |
| } |
| return string; |
| } |
| |
| var rawMsg = JSON.stringify(msg, null, " "); |
| var nodeMap = {}; |
| if ("result" in msg && "nodes" in msg.result) { |
| for (var node of msg.result.nodes) |
| nodeMap[node.nodeId] = node; |
| } |
| for (var nodeId in nodeMap) { |
| var node = nodeMap[nodeId]; |
| if (node.childIds) { |
| node.children = []; |
| for (var i = 0; i < node.childIds.length && node.childIds.length > 0;) { |
| var childId = node.childIds[i]; |
| if (childId in nodeMap) { |
| var child = nodeMap[childId]; |
| child.parentId = nodeId; |
| node.children.push(child); |
| |
| node.childIds.splice(i, 1); |
| } else { |
| node.childIds[i] = "<string>"; |
| i++; |
| } |
| } |
| if (!node.childIds.length) |
| delete node.childIds; |
| if (!node.children.length) |
| delete node.children; |
| } |
| } |
| var rootNode = Object.values(nodeMap).find((node) => !("parentId" in node)); |
| for (var node of Object.values(nodeMap)) |
| delete node.parentId; |
| |
| testRunner.log("\n" + printNodeAndChildren(rootNode)); |
| } |
| |
| return dumpAccessibilityNodesBySelectorAndCompleteTest; |
| |
| }) |