/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { Actor } = require("resource://devtools/shared/protocol.js"); const { accessibleSpec, } = require("resource://devtools/shared/specs/accessibility.js"); const { accessibility: { AUDIT_TYPE }, } = require("resource://devtools/shared/constants.js"); loader.lazyRequireGetter( this, "getContrastRatioFor", "resource://devtools/server/actors/accessibility/audit/contrast.js", true ); loader.lazyRequireGetter( this, "auditKeyboard", "resource://devtools/server/actors/accessibility/audit/keyboard.js", true ); loader.lazyRequireGetter( this, "auditTextLabel", "resource://devtools/server/actors/accessibility/audit/text-label.js", true ); loader.lazyRequireGetter( this, "isDefunct", "resource://devtools/server/actors/utils/accessibility.js", true ); loader.lazyRequireGetter( this, "findCssSelector", "resource://devtools/shared/inspector/css-logic.js", true ); loader.lazyRequireGetter( this, "events", "resource://devtools/shared/event-emitter.js" ); loader.lazyRequireGetter( this, "getBounds", "resource://devtools/server/actors/highlighters/utils/accessibility.js", true ); loader.lazyRequireGetter( this, "isFrameWithChildTarget", "resource://devtools/shared/layout/utils.js", true ); const lazy = {}; loader.lazyGetter( lazy, "ContentDOMReference", () => ChromeUtils.importESModule( "resource://gre/modules/ContentDOMReference.sys.mjs", { // ContentDOMReference needs to be retrieved from the shared global // since it is a shared singleton. loadInDevToolsLoader: false, } ).ContentDOMReference ); const RELATIONS_TO_IGNORE = new Set([ Ci.nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION, Ci.nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE, Ci.nsIAccessibleRelation.RELATION_CONTAINING_WINDOW, Ci.nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF, Ci.nsIAccessibleRelation.RELATION_SUBWINDOW_OF, ]); const nsIAccessibleRole = Ci.nsIAccessibleRole; const TEXT_ROLES = new Set([ nsIAccessibleRole.ROLE_TEXT_LEAF, nsIAccessibleRole.ROLE_STATICTEXT, ]); const STATE_DEFUNCT = Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT; const CSS_TEXT_SELECTOR = "#text"; /** * Get node inforamtion such as nodeType and the unique CSS selector for the node. * @param {DOMNode} node * Node for which to get the information. * @return {Object} * Information about the type of the node and how to locate it. */ function getNodeDescription(node) { if (!node || Cu.isDeadWrapper(node)) { return { nodeType: undefined, nodeCssSelector: "" }; } const { nodeType } = node; return { nodeType, // If node is a text node, we find a unique CSS selector for its parent and add a // CSS_TEXT_SELECTOR postfix to indicate that it's a text node. nodeCssSelector: nodeType === Node.TEXT_NODE ? `${findCssSelector(node.parentNode)}${CSS_TEXT_SELECTOR}` : findCssSelector(node), }; } /** * Get a snapshot of the nsIAccessible object including its subtree. None of the subtree * queried here is cached via accessible walker's refMap. * @param {nsIAccessible} acc * Accessible object to take a snapshot of. * @param {nsIAccessibilityService} a11yService * Accessibility service instance in the current process, used to get localized * string representation of various accessible properties. * @param {WindowGlobalTargetActor} targetActor * @return {JSON} * JSON snapshot of the accessibility tree with root at current accessible. */ function getSnapshot(acc, a11yService, targetActor) { if (isDefunct(acc)) { return { states: [a11yService.getStringStates(0, STATE_DEFUNCT)], }; } const actions = []; for (let i = 0; i < acc.actionCount; i++) { actions.push(acc.getActionDescription(i)); } const attributes = {}; if (acc.attributes) { for (const { key, value } of acc.attributes.enumerate()) { attributes[key] = value; } } const state = {}; const extState = {}; acc.getState(state, extState); const states = [...a11yService.getStringStates(state.value, extState.value)]; const children = []; for (let child = acc.firstChild; child; child = child.nextSibling) { // Ignore children from different documents when we have targets for every documents. if ( targetActor.ignoreSubFrames && child.DOMNode.ownerDocument !== targetActor.contentDocument ) { continue; } children.push(getSnapshot(child, a11yService, targetActor)); } const { nodeType, nodeCssSelector } = getNodeDescription(acc.DOMNode); const snapshot = { name: acc.name, role: getStringRole(acc, a11yService), actions, value: acc.value, nodeCssSelector, nodeType, description: acc.description, keyboardShortcut: acc.accessKey || acc.keyboardShortcut, childCount: acc.childCount, indexInParent: acc.indexInParent, states, children, attributes, }; const useChildTargetToFetchChildren = acc.role === Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME && isFrameWithChildTarget(targetActor, acc.DOMNode); if (useChildTargetToFetchChildren) { snapshot.useChildTargetToFetchChildren = useChildTargetToFetchChildren; snapshot.childCount = 1; snapshot.contentDOMReference = lazy.ContentDOMReference.get(acc.DOMNode); } return snapshot; } /** * Get a string indicating the role of the nsIAccessible object. * An ARIA role token will be returned unless the role can't be mapped to an * ARIA role (e.g.