Source code

Revision control

Copy as Markdown

Other Tools

// 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 "";
}
/**
* Extract DOMNode id from an accessible. If e10s is enabled, DOMNode is not
* present in parent process but, if available, DOMNode id is attached to an
* accessible object.
* @param {nsIAccessible} accessible accessible
* @return {String?} DOMNode id if available
*/
function getAccessibleDOMNodeID(accessible) {
if (accessible instanceof nsIAccessibleDocument) {
// If accessible is a document, trying to find its document body id.
try {
return accessible.DOMNode.body.id;
} catch (e) {
/* This only works if accessible is not a proxy. */
}
}
try {
return accessible.DOMNode.id;
} catch (e) {
/* This will fail if DOMNode is in different process. */
}
try {
// When e10s is enabled, accessible will have an "id" property if its
// corresponding DOMNode has an id. If accessible is a document, its "id"
// property corresponds to the "id" of its body element.
return accessible.id;
} catch (e) {
/* This will fail if accessible is not a proxy. */
}
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;
}