Files
tubestation/accessible/tests/mochitest/common.js
James Teh dc02759a3b Bug 1926541 part 2: Change the getAccessibleDOMNodeID test harness functions to simply use the id property. r=morgan
This keeps id retrieval code in one place: the accessibility engine.
It thus fixes some edge cases that were previously broken; e.g. SVG documents.
It would have been nice to remove these functions altogether and have everything use the id property directly.
Unfortunately, some code depends on getAccessibleDOMNodeID returning null when an accessible has died, rather than throwing an exception.
Changing all the impacted callers to catch exceptions is a bit ugly, but having the XPCOM function just return null itself in this case seemed like hiding reality.
This way, callers have a choice: they can use the id property directly if they care about the failure case (or if the failure case isn't relevant for their use case), or they can call getAccessibleDOMNodeID if they explicitly want to ignore the failure.

It turns out that some tests were unwittingly relying on the nsIAccessibleDocument interface having been queried on the document accessible by getAccessibleDOMNodeID when waiting for the doc load complete event.
To fix this, the harness now explicitly queries for nsIAccessibleDocument before passing the document to test tasks.

Differential Revision: https://phabricator.services.mozilla.com/D244708
2025-04-10 00:41:26 +00:00

1033 lines
26 KiB
JavaScript

// This file has circular dependencies that may require other files. Rather
// than use import-globals-from, we list the globals individually here to save
// confusing ESLint.
// actions.js
/* globals testActionNames */
// attributes.js
/* globals testAttrs, testAbsentAttrs, testTextAttrs */
// relations.js
/* globals testRelation */
// role.js
/* globals isRole */
// state.js
/* globals testStates */
// //////////////////////////////////////////////////////////////////////////////
// Interfaces
const nsIAccessibilityService = Ci.nsIAccessibilityService;
const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent;
const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent;
const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent;
const nsIAccessibleTextSelectionChangeEvent =
Ci.nsIAccessibleTextSelectionChangeEvent;
const nsIAccessibleObjectAttributeChangedEvent =
Ci.nsIAccessibleObjectAttributeChangedEvent;
const nsIAccessibleAnnouncementEvent = Ci.nsIAccessibleAnnouncementEvent;
const nsIAccessibleStates = Ci.nsIAccessibleStates;
const nsIAccessibleRole = Ci.nsIAccessibleRole;
const nsIAccessibleScrollType = Ci.nsIAccessibleScrollType;
const nsIAccessibleCoordinateType = Ci.nsIAccessibleCoordinateType;
const nsIAccessibleRelation = Ci.nsIAccessibleRelation;
const nsIAccessibleTextRange = Ci.nsIAccessibleTextRange;
const nsIAccessible = Ci.nsIAccessible;
const nsIAccessibleDocument = Ci.nsIAccessibleDocument;
const nsIAccessibleApplication = Ci.nsIAccessibleApplication;
const nsIAccessibleText = Ci.nsIAccessibleText;
const nsIAccessibleEditableText = Ci.nsIAccessibleEditableText;
const nsIAccessibleHyperLink = Ci.nsIAccessibleHyperLink;
const nsIAccessibleHyperText = Ci.nsIAccessibleHyperText;
const nsIAccessibleImage = Ci.nsIAccessibleImage;
const nsIAccessiblePivot = Ci.nsIAccessiblePivot;
const nsIAccessibleSelectable = Ci.nsIAccessibleSelectable;
const nsIAccessibleTable = Ci.nsIAccessibleTable;
const nsIAccessibleTableCell = Ci.nsIAccessibleTableCell;
const nsIAccessibleTraversalRule = Ci.nsIAccessibleTraversalRule;
const nsIAccessibleValue = Ci.nsIAccessibleValue;
const nsIObserverService = Ci.nsIObserverService;
const nsIDOMWindow = Ci.nsIDOMWindow;
const nsIPropertyElement = Ci.nsIPropertyElement;
// //////////////////////////////////////////////////////////////////////////////
// OS detect
const MAC = navigator.platform.includes("Mac");
const LINUX = navigator.platform.includes("Linux");
const SOLARIS = navigator.platform.includes("SunOS");
const WIN = navigator.platform.includes("Win");
// //////////////////////////////////////////////////////////////////////////////
// Application detect
// Firefox is assumed by default.
const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//);
// //////////////////////////////////////////////////////////////////////////////
// Accessible general
const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
const SCROLL_TYPE_TOP_EDGE = nsIAccessibleScrollType.SCROLL_TYPE_TOP_EDGE;
const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE;
const COORDTYPE_SCREEN_RELATIVE =
nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE;
const COORDTYPE_WINDOW_RELATIVE =
nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE;
const COORDTYPE_PARENT_RELATIVE =
nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE;
const kEmbedChar = String.fromCharCode(0xfffc);
const kDiscBulletChar = String.fromCharCode(0x2022);
const kDiscBulletText = kDiscBulletChar + " ";
const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
const kSquareBulletText = String.fromCharCode(0x25aa) + " ";
const MAX_TRIM_LENGTH = 100;
/**
* Services to determine if e10s is enabled.
*/
/**
* nsIAccessibilityService service.
*/
var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
nsIAccessibilityService
);
/**
* Enable/disable logging.
*/
function enableLogging(aModules) {
gAccService.setLogging(aModules);
}
function disableLogging() {
gAccService.setLogging("");
}
function isLogged(aModule) {
return gAccService.isLogged(aModule);
}
/**
* Dumps the accessible tree into console.
*/
function dumpTree(aId, aMsg) {
function dumpTreeIntl(acc, indent) {
dump(indent + prettyName(acc) + "\n");
var children = acc.children;
for (var i = 0; i < children.length; i++) {
var child = children.queryElementAt(i, nsIAccessible);
dumpTreeIntl(child, indent + " ");
}
}
function dumpDOMTreeIntl(node, indent) {
dump(indent + prettyName(node) + "\n");
var children = node.childNodes;
for (var i = 0; i < children.length; i++) {
var child = children.item(i);
dumpDOMTreeIntl(child, indent + " ");
}
}
dump(aMsg + "\n");
var root = getAccessible(aId);
dumpTreeIntl(root, " ");
dump("DOM tree:\n");
dumpDOMTreeIntl(getNode(aId), " ");
}
/**
* Invokes the given function when document is loaded and focused. Preferable
* to mochitests 'addLoadEvent' function -- additionally ensures state of the
* document accessible is not busy.
*
* @param aFunc the function to invoke
*/
function addA11yLoadEvent(aFunc, aWindow) {
function waitForDocLoad() {
window.setTimeout(function () {
var targetDocument = aWindow ? aWindow.document : document;
var accDoc = getAccessible(targetDocument);
var state = {};
accDoc.getState(state, {});
if (state.value & STATE_BUSY) {
waitForDocLoad();
return;
}
window.setTimeout(aFunc, 0);
}, 0);
}
if (
aWindow &&
aWindow.document.activeElement &&
aWindow.document.activeElement.localName == "browser"
) {
waitForDocLoad();
} else {
SimpleTest.waitForFocus(waitForDocLoad, aWindow);
}
}
/**
* Analogy of SimpleTest.is function used to compare objects.
*/
function isObject(aObj, aExpectedObj, aMsg) {
if (aObj == aExpectedObj) {
ok(true, aMsg);
return;
}
ok(
false,
aMsg +
" - got '" +
prettyName(aObj) +
"', expected '" +
prettyName(aExpectedObj) +
"'"
);
}
/**
* is() function checking the expected value is within the range.
*/
function isWithin(aExpected, aGot, aWithin, aMsg) {
if (Math.abs(aGot - aExpected) <= aWithin) {
ok(true, `${aMsg} - Got ${aGot}`);
} else {
ok(
false,
`${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}`
);
}
}
// //////////////////////////////////////////////////////////////////////////////
// Helpers for getting DOM node/accessible
/**
* Return the DOM node by identifier (may be accessible, DOM node or ID).
*/
function getNode(aAccOrNodeOrID, aDocument) {
if (!aAccOrNodeOrID) {
return null;
}
if (Node.isInstance(aAccOrNodeOrID)) {
return aAccOrNodeOrID;
}
if (aAccOrNodeOrID instanceof nsIAccessible) {
return aAccOrNodeOrID.DOMNode;
}
var node = (aDocument || document).getElementById(aAccOrNodeOrID);
if (!node) {
ok(false, "Can't get DOM element for " + aAccOrNodeOrID);
return null;
}
return node;
}
/**
* Constants indicates getAccessible doesn't fail if there is no accessible.
*/
const DONOTFAIL_IF_NO_ACC = 1;
/**
* Constants indicates getAccessible won't fail if accessible doesn't implement
* the requested interfaces.
*/
const DONOTFAIL_IF_NO_INTERFACE = 2;
/**
* Return accessible for the given identifier (may be ID attribute or DOM
* element or accessible object) or null.
*
* @param aAccOrElmOrID [in] identifier to get an accessible implementing
* the given interfaces
* @param aInterfaces [in, optional] the interface or an array interfaces
* to query it/them from obtained accessible
* @param aElmObj [out, optional] object to store DOM element which
* accessible is obtained for
* @param aDoNotFailIf [in, optional] no error for special cases (see
* constants above)
*/
function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) {
if (!aAccOrElmOrID) {
return null;
}
var elm = null;
if (aAccOrElmOrID instanceof nsIAccessible) {
try {
elm = aAccOrElmOrID.DOMNode;
} catch (e) {}
} else if (Node.isInstance(aAccOrElmOrID)) {
elm = aAccOrElmOrID;
} else {
elm = document.getElementById(aAccOrElmOrID);
if (!elm) {
ok(false, "Can't get DOM element for " + aAccOrElmOrID);
return null;
}
}
if (aElmObj && typeof aElmObj == "object") {
aElmObj.value = elm;
}
var acc = aAccOrElmOrID instanceof nsIAccessible ? aAccOrElmOrID : null;
if (!acc) {
try {
acc = gAccService.getAccessibleFor(elm);
} catch (e) {}
if (!acc) {
if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) {
ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID));
}
return null;
}
}
if (!aInterfaces) {
return acc;
}
if (!(aInterfaces instanceof Array)) {
aInterfaces = [aInterfaces];
}
for (var index = 0; index < aInterfaces.length; index++) {
if (acc instanceof aInterfaces[index]) {
continue;
}
try {
acc.QueryInterface(aInterfaces[index]);
} catch (e) {
if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) {
ok(
false,
"Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID
);
}
return null;
}
}
return acc;
}
/**
* Return true if the given identifier has an accessible, or exposes the wanted
* interfaces.
*/
function isAccessible(aAccOrElmOrID, aInterfaces) {
return !!getAccessible(
aAccOrElmOrID,
aInterfaces,
null,
DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE
);
}
/**
* Return an accessible that contains the DOM node for the given identifier.
*/
function getContainerAccessible(aAccOrElmOrID) {
var node = getNode(aAccOrElmOrID);
if (!node) {
return null;
}
// eslint-disable-next-line no-empty
while ((node = node.parentNode) && !isAccessible(node)) {}
return node ? getAccessible(node) : null;
}
/**
* Return root accessible for the given identifier.
*/
function getRootAccessible(aAccOrElmOrID) {
var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null;
}
/**
* Return tab document accessible the given accessible is contained by.
*/
function getTabDocAccessible(aAccOrElmOrID) {
var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
var docAcc = acc.document.QueryInterface(nsIAccessible);
var containerDocAcc = docAcc.parent.document;
// Test is running is stand-alone mode.
if (acc.rootDocument == containerDocAcc) {
return docAcc;
}
// In the case of running all tests together.
return containerDocAcc.QueryInterface(nsIAccessible);
}
/**
* Return application accessible.
*/
function getApplicationAccessible() {
return gAccService
.getApplicationAccessible()
.QueryInterface(nsIAccessibleApplication);
}
/**
* A version of accessible tree testing, doesn't fail if tree is not complete.
*/
function testElm(aID, aTreeObj) {
testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
}
/**
* Flags used for testAccessibleTree
*/
const kSkipTreeFullCheck = 1;
/**
* Compare expected and actual accessibles trees.
*
* @param aAccOrElmOrID [in] accessible identifier
* @param aAccTree [in] JS object, each field corresponds to property of
* accessible object. Additionally special properties
* are presented:
* children - an array of JS objects representing
* children of accessible
* states - an object having states and extraStates
* fields
* @param aFlags [in, optional] flags, see constants above
*/
// eslint-disable-next-line complexity
function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) {
var acc = getAccessible(aAccOrElmOrID);
if (!acc) {
return;
}
var accTree = aAccTree;
// Support of simplified accessible tree object.
accTree = normalizeAccTreeObj(accTree);
// Test accessible properties.
for (var prop in accTree) {
var msg =
"Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
switch (prop) {
case "actions": {
testActionNames(acc, accTree.actions);
break;
}
case "attributes":
testAttrs(acc, accTree[prop], true);
break;
case "absentAttributes":
testAbsentAttrs(acc, accTree[prop]);
break;
case "interfaces": {
var ifaces =
accTree[prop] instanceof Array ? accTree[prop] : [accTree[prop]];
for (let i = 0; i < ifaces.length; i++) {
ok(
acc instanceof ifaces[i],
"No " + ifaces[i] + " interface on " + prettyName(acc)
);
}
break;
}
case "relations": {
for (var rel in accTree[prop]) {
testRelation(acc, window[rel], accTree[prop][rel]);
}
break;
}
case "role":
isRole(acc, accTree[prop], msg);
break;
case "states":
case "extraStates":
case "absentStates":
case "absentExtraStates": {
testStates(
acc,
accTree.states,
accTree.extraStates,
accTree.absentStates,
accTree.absentExtraStates
);
break;
}
case "tagName":
is(accTree[prop], acc.DOMNode.tagName, msg);
break;
case "textAttrs": {
var prevOffset = -1;
for (var offset in accTree[prop]) {
if (prevOffset != -1) {
let attrs = accTree[prop][prevOffset];
testTextAttrs(
acc,
prevOffset,
attrs,
{},
prevOffset,
+offset,
true
);
}
prevOffset = +offset;
}
if (prevOffset != -1) {
var charCount = getAccessible(acc, [
nsIAccessibleText,
]).characterCount;
let attrs = accTree[prop][prevOffset];
testTextAttrs(
acc,
prevOffset,
attrs,
{},
prevOffset,
charCount,
true
);
}
break;
}
default:
if (prop.indexOf("todo_") == 0) {
todo(false, msg);
} else if (prop != "children") {
is(acc[prop], accTree[prop], msg);
}
}
}
// Test children.
if ("children" in accTree && accTree.children instanceof Array) {
var children = acc.children;
var childCount = children.length;
if (accTree.children.length != childCount) {
for (let i = 0; i < Math.max(accTree.children.length, childCount); i++) {
var accChild = null,
testChild = null;
try {
testChild = accTree.children[i];
accChild = children.queryElementAt(i, nsIAccessible);
if (!testChild) {
ok(
false,
prettyName(acc) +
" has an extra child at index " +
i +
" : " +
prettyName(accChild)
);
continue;
}
testChild = normalizeAccTreeObj(testChild);
if (accChild.role !== testChild.role) {
ok(
false,
prettyName(accTree) +
" and " +
prettyName(acc) +
" have different children at index " +
i +
" : " +
prettyName(testChild) +
", " +
prettyName(accChild)
);
}
info(
"Matching " +
prettyName(accTree) +
" and " +
prettyName(acc) +
" child at index " +
i +
" : " +
prettyName(accChild)
);
} catch (e) {
ok(
false,
prettyName(accTree) +
" is expected to have a child at index " +
i +
" : " +
prettyName(testChild) +
", original tested: " +
prettyName(aAccOrElmOrID) +
", " +
e
);
}
}
} else {
if (aFlags & kSkipTreeFullCheck) {
for (let i = 0; i < childCount; i++) {
let child = children.queryElementAt(i, nsIAccessible);
testAccessibleTree(child, accTree.children[i], aFlags);
}
return;
}
// nsIAccessible::firstChild
var expectedFirstChild =
childCount > 0 ? children.queryElementAt(0, nsIAccessible) : null;
var firstChild = null;
try {
firstChild = acc.firstChild;
} catch (e) {}
is(
firstChild,
expectedFirstChild,
"Wrong first child of " + prettyName(acc)
);
// nsIAccessible::lastChild
var expectedLastChild =
childCount > 0
? children.queryElementAt(childCount - 1, nsIAccessible)
: null;
var lastChild = null;
try {
lastChild = acc.lastChild;
} catch (e) {}
is(
lastChild,
expectedLastChild,
"Wrong last child of " + prettyName(acc)
);
for (var i = 0; i < childCount; i++) {
let child = children.queryElementAt(i, nsIAccessible);
// nsIAccessible::parent
var parent = null;
try {
parent = child.parent;
} catch (e) {}
is(parent, acc, "Wrong parent of " + prettyName(child));
// nsIAccessible::indexInParent
var indexInParent = -1;
try {
indexInParent = child.indexInParent;
} catch (e) {}
is(indexInParent, i, "Wrong index in parent of " + prettyName(child));
// nsIAccessible::nextSibling
var expectedNextSibling =
i < childCount - 1
? children.queryElementAt(i + 1, nsIAccessible)
: null;
var nextSibling = null;
try {
nextSibling = child.nextSibling;
} catch (e) {}
is(
nextSibling,
expectedNextSibling,
"Wrong next sibling of " + prettyName(child)
);
// nsIAccessible::previousSibling
var expectedPrevSibling =
i > 0 ? children.queryElementAt(i - 1, nsIAccessible) : null;
var prevSibling = null;
try {
prevSibling = child.previousSibling;
} catch (e) {}
is(
prevSibling,
expectedPrevSibling,
"Wrong previous sibling of " + prettyName(child)
);
// Go down through subtree
testAccessibleTree(child, accTree.children[i], aFlags);
}
}
}
}
/**
* Return true if accessible for the given node is in cache.
*/
function isAccessibleInCache(aNodeOrId) {
var node = getNode(aNodeOrId);
return !!gAccService.getAccessibleFromCache(node);
}
/**
* Test accessible tree for defunct accessible.
*
* @param aAcc [in] the defunct accessible
* @param aNodeOrId [in] the DOM node identifier for the defunct accessible
*/
function testDefunctAccessible(aAcc, aNodeOrId) {
if (aNodeOrId) {
ok(
!isAccessible(aNodeOrId),
"Accessible for " + aNodeOrId + " wasn't properly shut down!"
);
}
var msg =
" doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
// firstChild
var success = false;
try {
aAcc.firstChild;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "firstChild" + msg);
// lastChild
success = false;
try {
aAcc.lastChild;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "lastChild" + msg);
// childCount
success = false;
try {
aAcc.childCount;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "childCount" + msg);
// children
success = false;
try {
aAcc.children;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "children" + msg);
// nextSibling
success = false;
try {
aAcc.nextSibling;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "nextSibling" + msg);
// previousSibling
success = false;
try {
aAcc.previousSibling;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "previousSibling" + msg);
// parent
success = false;
try {
aAcc.parent;
} catch (e) {
success = e.result == Cr.NS_ERROR_FAILURE;
}
ok(success, "parent" + msg);
}
/**
* Convert role to human readable string.
*/
function roleToString(aRole) {
return gAccService.getStringRole(aRole);
}
/**
* Convert states to human readable string.
*/
function statesToString(aStates, aExtraStates) {
var list = gAccService.getStringStates(aStates, aExtraStates);
var str = "";
for (var index = 0; index < list.length - 1; index++) {
str += list.item(index) + ", ";
}
if (list.length) {
str += list.item(index);
}
return str;
}
/**
* Convert event type to human readable string.
*/
function eventTypeToString(aEventType) {
return gAccService.getStringEventType(aEventType);
}
/**
* Convert relation type to human readable string.
*/
function relationTypeToString(aRelationType) {
return gAccService.getStringRelationType(aRelationType);
}
function getLoadContext() {
return window.docShell.QueryInterface(Ci.nsILoadContext);
}
/**
* Return text from clipboard.
*/
function getTextFromClipboard() {
var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
Ci.nsITransferable
);
trans.init(getLoadContext());
if (!trans) {
return "";
}
trans.addDataFlavor("text/plain");
Services.clipboard.getData(
trans,
Services.clipboard.kGlobalClipboard,
SpecialPowers.wrap(window).browsingContext.currentWindowContext
);
var str = {};
trans.getTransferData("text/plain", str);
if (str) {
str = str.value.QueryInterface(Ci.nsISupportsString);
}
if (str) {
return str.data;
}
return "";
}
/**
* Obtain DOMNode id from an accessible. This simply queries the .id property
* on the accessible, but it catches exceptions which might occur if the
* accessible has died.
* @param {nsIAccessible} accessible accessible
* @return {String?} DOMNode id if available
*/
function getAccessibleDOMNodeID(accessible) {
try {
return accessible.id;
} catch (e) {
// This will fail if the accessible has died.
}
return null;
}
/**
* Return pretty name for identifier, it may be ID, DOM node or accessible.
*/
function prettyName(aIdentifier) {
if (aIdentifier instanceof Array) {
let msg = "";
for (var idx = 0; idx < aIdentifier.length; idx++) {
if (msg != "") {
msg += ", ";
}
msg += prettyName(aIdentifier[idx]);
}
return msg;
}
if (aIdentifier instanceof nsIAccessible) {
var acc = getAccessible(aIdentifier);
var domID = getAccessibleDOMNodeID(acc);
let msg = "[";
try {
if (Services.appinfo.browserTabsRemoteAutostart) {
if (domID) {
msg += `DOM node id: ${domID}, `;
}
} else {
msg += `${getNodePrettyName(acc.DOMNode)}, `;
}
msg += "role: " + roleToString(acc.role);
if (acc.name) {
msg += ", name: '" + shortenString(acc.name) + "'";
}
} catch (e) {
msg += "defunct";
}
if (acc) {
msg += ", address: " + getObjAddress(acc);
}
msg += "]";
return msg;
}
if (Node.isInstance(aIdentifier)) {
return "[ " + getNodePrettyName(aIdentifier) + " ]";
}
if (aIdentifier && typeof aIdentifier === "object") {
var treeObj = normalizeAccTreeObj(aIdentifier);
if ("role" in treeObj) {
function stringifyTree(aObj) {
var text = roleToString(aObj.role) + ": [ ";
if ("children" in aObj) {
for (var i = 0; i < aObj.children.length; i++) {
var c = normalizeAccTreeObj(aObj.children[i]);
text += stringifyTree(c);
if (i < aObj.children.length - 1) {
text += ", ";
}
}
}
return text + "] ";
}
return `{ ${stringifyTree(treeObj)} }`;
}
return JSON.stringify(aIdentifier);
}
return " '" + aIdentifier + "' ";
}
/**
* Shorten a long string if it exceeds MAX_TRIM_LENGTH.
* @param aString the string to shorten.
* @returns the shortened string.
*/
function shortenString(aString) {
if (aString.length <= MAX_TRIM_LENGTH) {
return aString;
}
// Trim the string if its length is > MAX_TRIM_LENGTH characters.
var trimOffset = MAX_TRIM_LENGTH / 2;
return (
aString.substring(0, trimOffset - 1) +
"..." +
aString.substring(aString.length - trimOffset, aString.length)
);
}
// //////////////////////////////////////////////////////////////////////////////
// General Utils
// //////////////////////////////////////////////////////////////////////////////
/**
* Return main chrome window (crosses chrome boundary)
*/
function getMainChromeWindow(aWindow) {
return aWindow.browsingContext.topChromeWindow;
}
// //////////////////////////////////////////////////////////////////////////////
// Private
// //////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
// Accessible general
function getNodePrettyName(aNode) {
try {
var tag = "";
if (aNode.nodeType == Node.DOCUMENT_NODE) {
tag = "document";
} else {
tag = aNode.localName;
if (aNode.nodeType == Node.ELEMENT_NODE && aNode.hasAttribute("id")) {
tag += '@id="' + aNode.getAttribute("id") + '"';
}
}
return "'" + tag + " node', address: " + getObjAddress(aNode);
} catch (e) {
return "' no node info '";
}
}
function getObjAddress(aObj) {
var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
var match = exp.exec(aObj.toString());
if (match) {
return match[1];
}
return aObj.toString();
}
function normalizeAccTreeObj(aObj) {
var key = Object.keys(aObj)[0];
var roleName = "ROLE_" + key;
if (roleName in nsIAccessibleRole) {
return {
role: nsIAccessibleRole[roleName],
children: aObj[key],
};
}
return aObj;
}