mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-07-05 16:38:31 +00:00
afc187cc3f
Compared with what Pale Moon had for Solaris originally, this is mostly the same zero point I started patching from, but I've made the following changes here after reviewing all this initial code I never looked at closely before. 1. In package-manifest.in for both Basilisk and Pale Moon, I've made the SPARC code for libfreebl not interefere with the x86 code, use the proper build flags, and also updated it to allow a SPARC64 build which is more likely to be used than the 32-bit SPARC code we had there. 2. See Mozilla bug #832272 and the old rules.mk patch from around Firefox 30 in oracle/solaris-userland. I believe they screwed up NSINSTALL on Solaris when they were trying to streamline the NSS buildsystem, because they started having unexplained issues with it around that time after Firefox 22 that they never properly resolved until Mozilla began building NSS with gyp files. I'm actually not even sure how relevant the thing they broke actually is to Solaris at this point, bug 665509 is so old it predates Firefox itself and goes back to the Mozilla suite days. I believe $(INSTALL) -t was wrong, and they meant $(NSINSTALL) -t because that makes more sense and is closer to what was there originally. It's what they have for WINNT, and it's possible a fix more like that could serve for Solaris as well. Alternatively, we could get rid of all these half-broken Makefiles and start building NSS with gyp files like Mozilla did. 3. I've completely cut out support for the Sun compiler and taken into account the reality that everyone builds Firefox (and therefore its forks) with GCC now on Solaris. This alone helped clean up a lot of the uglier parts of the code. 4. I've updated all remaining SOLARIS build flags to the newer XP_SOLARIS, because the SOLARIS flag is no longer set when building Solaris. 5. I've confirmed the workaround in gtxFontconfigFonts.cpp is no longer necessary. The Solaris people got impatient about implementing a half-baked patch for a fontconfig feature that wasn't ready yet back in 2009, and somehow convinced Mozilla to patch their software to work around it when really they should have just fixed or removed their broken fontconfig patch. The feature they wanted has since been implemented properly, and no version of Solaris still uses the broken patch that required this fix. If anyone had ever properly audited this code, it would have been removed a long time ago.
953 lines
27 KiB
JavaScript
953 lines
27 KiB
JavaScript
////////////////////////////////////////////////////////////////////////////////
|
|
// Interfaces
|
|
|
|
const nsIAccessibilityService = Components.interfaces.nsIAccessibilityService;
|
|
|
|
const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent;
|
|
const nsIAccessibleStateChangeEvent =
|
|
Components.interfaces.nsIAccessibleStateChangeEvent;
|
|
const nsIAccessibleCaretMoveEvent =
|
|
Components.interfaces.nsIAccessibleCaretMoveEvent;
|
|
const nsIAccessibleTextChangeEvent =
|
|
Components.interfaces.nsIAccessibleTextChangeEvent;
|
|
const nsIAccessibleVirtualCursorChangeEvent =
|
|
Components.interfaces.nsIAccessibleVirtualCursorChangeEvent;
|
|
const nsIAccessibleObjectAttributeChangedEvent =
|
|
Components.interfaces.nsIAccessibleObjectAttributeChangedEvent;
|
|
|
|
const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates;
|
|
const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole;
|
|
const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType;
|
|
const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType;
|
|
|
|
const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation;
|
|
const nsIAccessibleTextRange = Components.interfaces.nsIAccessibleTextRange;
|
|
|
|
const nsIAccessible = Components.interfaces.nsIAccessible;
|
|
|
|
const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument;
|
|
const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication;
|
|
|
|
const nsIAccessibleText = Components.interfaces.nsIAccessibleText;
|
|
const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableText;
|
|
|
|
const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink;
|
|
const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText;
|
|
|
|
const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage;
|
|
const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot;
|
|
const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable;
|
|
const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable;
|
|
const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell;
|
|
const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule;
|
|
const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue;
|
|
|
|
const nsIObserverService = Components.interfaces.nsIObserverService;
|
|
|
|
const nsIDOMDocument = Components.interfaces.nsIDOMDocument;
|
|
const nsIDOMEvent = Components.interfaces.nsIDOMEvent;
|
|
const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument;
|
|
const nsIDOMNode = Components.interfaces.nsIDOMNode;
|
|
const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement;
|
|
const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
|
|
const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement;
|
|
|
|
const nsIPropertyElement = Components.interfaces.nsIPropertyElement;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// OS detect
|
|
|
|
const MAC = (navigator.platform.indexOf("Mac") != -1);
|
|
const LINUX = (navigator.platform.indexOf("Linux") != -1);
|
|
const SOLARIS = (navigator.platform.indexOf("SunOS") != -1);
|
|
const WIN = (navigator.platform.indexOf("Win") != -1);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Application detect
|
|
// Firefox is assumed by default.
|
|
|
|
const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//);
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Accessible general
|
|
|
|
const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
|
|
|
|
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(0x25fe) + " ";
|
|
|
|
const MAX_TRIM_LENGTH = 100;
|
|
|
|
/**
|
|
* Services to determine if e10s is enabled.
|
|
*/
|
|
Components.utils.import('resource://gre/modules/Services.jsm');
|
|
|
|
/**
|
|
* nsIAccessibilityService service.
|
|
*/
|
|
var gAccService = Components.classes["@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)
|
|
return waitForDocLoad();
|
|
|
|
window.setTimeout(aFunc, 0);
|
|
},
|
|
0
|
|
);
|
|
}
|
|
|
|
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) + "'");
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// 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 (aAccOrNodeOrID instanceof nsIDOMNode)
|
|
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 (aAccOrElmOrID instanceof nsIDOMNode) {
|
|
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) ?
|
|
true : false;
|
|
}
|
|
|
|
/**
|
|
* Return an accessible that contains the DOM node for the given identifier.
|
|
*/
|
|
function getContainerAccessible(aAccOrElmOrID)
|
|
{
|
|
var node = getNode(aAccOrElmOrID);
|
|
if (!node)
|
|
return null;
|
|
|
|
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
|
|
*/
|
|
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 (var 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) {
|
|
var attrs = accTree[prop][prevOffset];
|
|
testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, +offset, true);
|
|
}
|
|
prevOffset = +offset;
|
|
}
|
|
|
|
if (prevOffset != -1) {
|
|
var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount;
|
|
var 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 (var 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 (var i = 0; i < childCount; i++) {
|
|
var 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++) {
|
|
var 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) ? true : false;
|
|
}
|
|
|
|
/**
|
|
* 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 == Components.results.NS_ERROR_FAILURE)
|
|
}
|
|
ok(success, "firstChild" + msg);
|
|
|
|
// lastChild
|
|
success = false;
|
|
try {
|
|
aAcc.lastChild;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.NS_ERROR_FAILURE)
|
|
}
|
|
ok(success, "lastChild" + msg);
|
|
|
|
// childCount
|
|
success = false;
|
|
try {
|
|
aAcc.childCount;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.NS_ERROR_FAILURE)
|
|
}
|
|
ok(success, "childCount" + msg);
|
|
|
|
// children
|
|
success = false;
|
|
try {
|
|
aAcc.children;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.NS_ERROR_FAILURE)
|
|
}
|
|
ok(success, "children" + msg);
|
|
|
|
// nextSibling
|
|
success = false;
|
|
try {
|
|
aAcc.nextSibling;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.NS_ERROR_FAILURE);
|
|
}
|
|
ok(success, "nextSibling" + msg);
|
|
|
|
// previousSibling
|
|
success = false;
|
|
try {
|
|
aAcc.previousSibling;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.NS_ERROR_FAILURE);
|
|
}
|
|
ok(success, "previousSibling" + msg);
|
|
|
|
// parent
|
|
success = false;
|
|
try {
|
|
aAcc.parent;
|
|
} catch (e) {
|
|
success = (e.result == Components.results.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 != 0)
|
|
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() {
|
|
const Ci = Components.interfaces;
|
|
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
.getInterface(Ci.nsIWebNavigation)
|
|
.QueryInterface(Ci.nsILoadContext);
|
|
}
|
|
|
|
/**
|
|
* Return text from clipboard.
|
|
*/
|
|
function getTextFromClipboard()
|
|
{
|
|
var clip = Components.classes["@mozilla.org/widget/clipboard;1"].
|
|
getService(Components.interfaces.nsIClipboard);
|
|
if (!clip)
|
|
return "";
|
|
|
|
var trans = Components.classes["@mozilla.org/widget/transferable;1"].
|
|
createInstance(Components.interfaces.nsITransferable);
|
|
trans.init(getLoadContext());
|
|
if (!trans)
|
|
return "";
|
|
|
|
trans.addDataFlavor("text/unicode");
|
|
clip.getData(trans, clip.kGlobalClipboard);
|
|
|
|
var str = new Object();
|
|
var strLength = new Object();
|
|
trans.getTransferData("text/unicode", str, strLength);
|
|
|
|
if (str)
|
|
str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
|
|
if (str)
|
|
return str.data.substring(0, strLength.value / 2);
|
|
|
|
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 pretty name for identifier, it may be ID, DOM node or accessible.
|
|
*/
|
|
function prettyName(aIdentifier)
|
|
{
|
|
if (aIdentifier instanceof Array) {
|
|
var 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);
|
|
var 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 (aIdentifier instanceof nsIDOMNode)
|
|
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, aMaxLength)
|
|
{
|
|
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.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIWebNavigation)
|
|
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
|
|
.rootTreeItem
|
|
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
|
.getInterface(Components.interfaces.nsIDOMWindow);
|
|
}
|
|
|
|
/** Sets the test plugin(s) initially expected enabled state.
|
|
* It will automatically be reset to it's previous value after the test
|
|
* ends.
|
|
* @param aNewEnabledState [in] the enabled state, e.g. SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED
|
|
* @param aPluginName [in, optional] The name of the plugin, defaults to "Test Plug-in"
|
|
*/
|
|
function setTestPluginEnabledState(aNewEnabledState, aPluginName)
|
|
{
|
|
var plugin = getTestPluginTag(aPluginName);
|
|
var oldEnabledState = plugin.enabledState;
|
|
plugin.enabledState = aNewEnabledState;
|
|
SimpleTest.registerCleanupFunction(function() {
|
|
getTestPluginTag(aPluginName).enabledState = oldEnabledState;
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Private
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Accessible general
|
|
|
|
function getNodePrettyName(aNode)
|
|
{
|
|
try {
|
|
var tag = "";
|
|
if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) {
|
|
tag = "document";
|
|
} else {
|
|
tag = aNode.localName;
|
|
if (aNode.nodeType == nsIDOMNode.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 getTestPluginTag(aPluginName)
|
|
{
|
|
var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
|
|
.getService(SpecialPowers.Ci.nsIPluginHost);
|
|
var tags = ph.getPluginTags();
|
|
var name = aPluginName || "Test Plug-in";
|
|
for (var tag of tags) {
|
|
if (tag.name == name) {
|
|
return tag;
|
|
}
|
|
}
|
|
|
|
ok(false, "Could not find plugin tag with plugin name '" + name + "'");
|
|
return null;
|
|
}
|
|
|
|
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;
|
|
}
|