tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

common.js (26824B)


      1 // This file has circular dependencies that may require other files. Rather
      2 // than use import-globals-from, we list the globals individually here to save
      3 // confusing ESLint.
      4 // actions.js
      5 /* globals testActionNames */
      6 // attributes.js
      7 /* globals testAttrs, testAbsentAttrs, testTextAttrs */
      8 // relations.js
      9 /* globals testRelation */
     10 // role.js
     11 /* globals isRole */
     12 // state.js
     13 /* globals testStates */
     14 
     15 // //////////////////////////////////////////////////////////////////////////////
     16 // Interfaces
     17 
     18 const nsIAccessibilityService = Ci.nsIAccessibilityService;
     19 
     20 const nsIAccessibleEvent = Ci.nsIAccessibleEvent;
     21 const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent;
     22 const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent;
     23 const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent;
     24 const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent;
     25 const nsIAccessibleTextSelectionChangeEvent =
     26  Ci.nsIAccessibleTextSelectionChangeEvent;
     27 const nsIAccessibleObjectAttributeChangedEvent =
     28  Ci.nsIAccessibleObjectAttributeChangedEvent;
     29 const nsIAccessibleAnnouncementEvent = Ci.nsIAccessibleAnnouncementEvent;
     30 
     31 const nsIAccessibleStates = Ci.nsIAccessibleStates;
     32 const nsIAccessibleRole = Ci.nsIAccessibleRole;
     33 const nsIAccessibleScrollType = Ci.nsIAccessibleScrollType;
     34 const nsIAccessibleCoordinateType = Ci.nsIAccessibleCoordinateType;
     35 
     36 const nsIAccessibleRelation = Ci.nsIAccessibleRelation;
     37 const nsIAccessibleTextRange = Ci.nsIAccessibleTextRange;
     38 
     39 const nsIAccessible = Ci.nsIAccessible;
     40 
     41 const nsIAccessibleDocument = Ci.nsIAccessibleDocument;
     42 const nsIAccessibleApplication = Ci.nsIAccessibleApplication;
     43 
     44 const nsIAccessibleText = Ci.nsIAccessibleText;
     45 const nsIAccessibleEditableText = Ci.nsIAccessibleEditableText;
     46 
     47 const nsIAccessibleHyperLink = Ci.nsIAccessibleHyperLink;
     48 const nsIAccessibleHyperText = Ci.nsIAccessibleHyperText;
     49 
     50 const nsIAccessibleImage = Ci.nsIAccessibleImage;
     51 const nsIAccessiblePivot = Ci.nsIAccessiblePivot;
     52 const nsIAccessibleSelectable = Ci.nsIAccessibleSelectable;
     53 const nsIAccessibleTable = Ci.nsIAccessibleTable;
     54 const nsIAccessibleTableCell = Ci.nsIAccessibleTableCell;
     55 const nsIAccessibleTraversalRule = Ci.nsIAccessibleTraversalRule;
     56 const nsIAccessibleValue = Ci.nsIAccessibleValue;
     57 
     58 const nsIObserverService = Ci.nsIObserverService;
     59 
     60 const nsIDOMWindow = Ci.nsIDOMWindow;
     61 
     62 const nsIPropertyElement = Ci.nsIPropertyElement;
     63 
     64 // //////////////////////////////////////////////////////////////////////////////
     65 // OS detect
     66 
     67 const MAC = navigator.platform.includes("Mac");
     68 const LINUX = navigator.platform.includes("Linux");
     69 const SOLARIS = navigator.platform.includes("SunOS");
     70 const WIN = navigator.platform.includes("Win");
     71 
     72 // //////////////////////////////////////////////////////////////////////////////
     73 // Application detect
     74 // Firefox is assumed by default.
     75 
     76 const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//);
     77 
     78 // //////////////////////////////////////////////////////////////////////////////
     79 // Accessible general
     80 
     81 const STATE_BUSY = nsIAccessibleStates.STATE_BUSY;
     82 
     83 const SCROLL_TYPE_TOP_EDGE = nsIAccessibleScrollType.SCROLL_TYPE_TOP_EDGE;
     84 const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE;
     85 
     86 const COORDTYPE_SCREEN_RELATIVE =
     87  nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE;
     88 const COORDTYPE_WINDOW_RELATIVE =
     89  nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE;
     90 const COORDTYPE_PARENT_RELATIVE =
     91  nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE;
     92 
     93 const kEmbedChar = String.fromCharCode(0xfffc);
     94 
     95 const kDiscBulletChar = String.fromCharCode(0x2022);
     96 const kDiscBulletText = kDiscBulletChar + " ";
     97 const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
     98 const kSquareBulletText = String.fromCharCode(0x25aa) + " ";
     99 
    100 const MAX_TRIM_LENGTH = 100;
    101 
    102 /**
    103 * Services to determine if e10s is enabled.
    104 */
    105 
    106 /**
    107 * nsIAccessibilityService service.
    108 */
    109 var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService(
    110  nsIAccessibilityService
    111 );
    112 
    113 /**
    114 * Enable/disable logging.
    115 */
    116 function enableLogging(aModules) {
    117  gAccService.setLogging(aModules);
    118 }
    119 function disableLogging() {
    120  gAccService.setLogging("");
    121 }
    122 function isLogged(aModule) {
    123  return gAccService.isLogged(aModule);
    124 }
    125 
    126 /**
    127 * Dumps the accessible tree into console.
    128 */
    129 function dumpTree(aId, aMsg) {
    130  function dumpTreeIntl(acc, indent) {
    131    dump(indent + prettyName(acc) + "\n");
    132 
    133    var children = acc.children;
    134    for (var i = 0; i < children.length; i++) {
    135      var child = children.queryElementAt(i, nsIAccessible);
    136      dumpTreeIntl(child, indent + "  ");
    137    }
    138  }
    139 
    140  function dumpDOMTreeIntl(node, indent) {
    141    dump(indent + prettyName(node) + "\n");
    142 
    143    var children = node.childNodes;
    144    for (var i = 0; i < children.length; i++) {
    145      var child = children.item(i);
    146      dumpDOMTreeIntl(child, indent + "  ");
    147    }
    148  }
    149 
    150  dump(aMsg + "\n");
    151  var root = getAccessible(aId);
    152  dumpTreeIntl(root, "  ");
    153 
    154  dump("DOM tree:\n");
    155  dumpDOMTreeIntl(getNode(aId), "  ");
    156 }
    157 
    158 /**
    159 * Invokes the given function when document is loaded and focused. Preferable
    160 * to mochitests 'addLoadEvent' function -- additionally ensures state of the
    161 * document accessible is not busy.
    162 *
    163 * @param aFunc  the function to invoke
    164 */
    165 function addA11yLoadEvent(aFunc, aWindow) {
    166  function waitForDocLoad() {
    167    window.setTimeout(function () {
    168      var targetDocument = aWindow ? aWindow.document : document;
    169      var accDoc = getAccessible(targetDocument);
    170      var state = {};
    171      accDoc.getState(state, {});
    172      if (state.value & STATE_BUSY) {
    173        waitForDocLoad();
    174        return;
    175      }
    176 
    177      window.setTimeout(aFunc, 0);
    178    }, 0);
    179  }
    180 
    181  if (
    182    aWindow &&
    183    aWindow.document.activeElement &&
    184    aWindow.document.activeElement.localName == "browser"
    185  ) {
    186    waitForDocLoad();
    187  } else {
    188    SimpleTest.waitForFocus(waitForDocLoad, aWindow);
    189  }
    190 }
    191 
    192 /**
    193 * Analogy of SimpleTest.is function used to compare objects.
    194 */
    195 function isObject(aObj, aExpectedObj, aMsg) {
    196  if (aObj == aExpectedObj) {
    197    ok(true, aMsg);
    198    return;
    199  }
    200 
    201  ok(
    202    false,
    203    aMsg +
    204      " - got '" +
    205      prettyName(aObj) +
    206      "', expected '" +
    207      prettyName(aExpectedObj) +
    208      "'"
    209  );
    210 }
    211 
    212 /**
    213 * is() function checking the expected value is within the range.
    214 */
    215 function isWithin(aExpected, aGot, aWithin, aMsg) {
    216  if (Math.abs(aGot - aExpected) <= aWithin) {
    217    ok(true, `${aMsg} - Got ${aGot}`);
    218  } else {
    219    ok(
    220      false,
    221      `${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}`
    222    );
    223  }
    224 }
    225 
    226 // //////////////////////////////////////////////////////////////////////////////
    227 // Helpers for getting DOM node/accessible
    228 
    229 /**
    230 * Return the DOM node by identifier (may be accessible, DOM node or ID).
    231 */
    232 function getNode(aAccOrNodeOrID, aDocument) {
    233  if (!aAccOrNodeOrID) {
    234    return null;
    235  }
    236 
    237  if (Node.isInstance(aAccOrNodeOrID)) {
    238    return aAccOrNodeOrID;
    239  }
    240 
    241  if (aAccOrNodeOrID instanceof nsIAccessible) {
    242    return aAccOrNodeOrID.DOMNode;
    243  }
    244 
    245  var node = (aDocument || document).getElementById(aAccOrNodeOrID);
    246  if (!node) {
    247    ok(false, "Can't get DOM element for " + aAccOrNodeOrID);
    248    return null;
    249  }
    250 
    251  return node;
    252 }
    253 
    254 /**
    255 * Constants indicates getAccessible doesn't fail if there is no accessible.
    256 */
    257 const DONOTFAIL_IF_NO_ACC = 1;
    258 
    259 /**
    260 * Constants indicates getAccessible won't fail if accessible doesn't implement
    261 * the requested interfaces.
    262 */
    263 const DONOTFAIL_IF_NO_INTERFACE = 2;
    264 
    265 /**
    266 * Return accessible for the given identifier (may be ID attribute or DOM
    267 * element or accessible object) or null.
    268 *
    269 * @param aAccOrElmOrID      [in] identifier to get an accessible implementing
    270 *                           the given interfaces
    271 * @param aInterfaces        [in, optional] the interface or an array interfaces
    272 *                           to query it/them from obtained accessible
    273 * @param aElmObj            [out, optional] object to store DOM element which
    274 *                           accessible is obtained for
    275 * @param aDoNotFailIf       [in, optional] no error for special cases (see
    276 *                            constants above)
    277 */
    278 function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) {
    279  if (!aAccOrElmOrID) {
    280    return null;
    281  }
    282 
    283  var elm = null;
    284 
    285  if (aAccOrElmOrID instanceof nsIAccessible) {
    286    try {
    287      elm = aAccOrElmOrID.DOMNode;
    288    } catch (e) {}
    289  } else if (Node.isInstance(aAccOrElmOrID)) {
    290    elm = aAccOrElmOrID;
    291  } else {
    292    elm = document.getElementById(aAccOrElmOrID);
    293    if (!elm) {
    294      ok(false, "Can't get DOM element for " + aAccOrElmOrID);
    295      return null;
    296    }
    297  }
    298 
    299  if (aElmObj && typeof aElmObj == "object") {
    300    aElmObj.value = elm;
    301  }
    302 
    303  var acc = aAccOrElmOrID instanceof nsIAccessible ? aAccOrElmOrID : null;
    304  if (!acc) {
    305    try {
    306      acc = gAccService.getAccessibleFor(elm);
    307    } catch (e) {}
    308 
    309    if (!acc) {
    310      if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) {
    311        ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID));
    312      }
    313 
    314      return null;
    315    }
    316  }
    317 
    318  if (!aInterfaces) {
    319    return acc;
    320  }
    321 
    322  if (!(aInterfaces instanceof Array)) {
    323    aInterfaces = [aInterfaces];
    324  }
    325 
    326  for (var index = 0; index < aInterfaces.length; index++) {
    327    if (acc instanceof aInterfaces[index]) {
    328      continue;
    329    }
    330    try {
    331      acc.QueryInterface(aInterfaces[index]);
    332    } catch (e) {
    333      if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) {
    334        ok(
    335          false,
    336          "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID
    337        );
    338      }
    339 
    340      return null;
    341    }
    342  }
    343 
    344  return acc;
    345 }
    346 
    347 /**
    348 * Return true if the given identifier has an accessible, or exposes the wanted
    349 * interfaces.
    350 */
    351 function isAccessible(aAccOrElmOrID, aInterfaces) {
    352  return !!getAccessible(
    353    aAccOrElmOrID,
    354    aInterfaces,
    355    null,
    356    DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE
    357  );
    358 }
    359 
    360 /**
    361 * Return an accessible that contains the DOM node for the given identifier.
    362 */
    363 function getContainerAccessible(aAccOrElmOrID) {
    364  var node = getNode(aAccOrElmOrID);
    365  if (!node) {
    366    return null;
    367  }
    368 
    369  // eslint-disable-next-line no-empty
    370  while ((node = node.parentNode) && !isAccessible(node)) {}
    371  return node ? getAccessible(node) : null;
    372 }
    373 
    374 /**
    375 * Return root accessible for the given identifier.
    376 */
    377 function getRootAccessible(aAccOrElmOrID) {
    378  var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
    379  return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null;
    380 }
    381 
    382 /**
    383 * Return tab document accessible the given accessible is contained by.
    384 */
    385 function getTabDocAccessible(aAccOrElmOrID) {
    386  var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document);
    387 
    388  var docAcc = acc.document.QueryInterface(nsIAccessible);
    389  var containerDocAcc = docAcc.parent.document;
    390 
    391  // Test is running is stand-alone mode.
    392  if (acc.rootDocument == containerDocAcc) {
    393    return docAcc;
    394  }
    395 
    396  // In the case of running all tests together.
    397  return containerDocAcc.QueryInterface(nsIAccessible);
    398 }
    399 
    400 /**
    401 * Return application accessible.
    402 */
    403 function getApplicationAccessible() {
    404  return gAccService
    405    .getApplicationAccessible()
    406    .QueryInterface(nsIAccessibleApplication);
    407 }
    408 
    409 /**
    410 * A version of accessible tree testing, doesn't fail if tree is not complete.
    411 */
    412 function testElm(aID, aTreeObj) {
    413  testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
    414 }
    415 
    416 /**
    417 * Flags used for testAccessibleTree
    418 */
    419 const kSkipTreeFullCheck = 1;
    420 
    421 /**
    422 * Compare expected and actual accessibles trees.
    423 *
    424 * @param  aAccOrElmOrID  [in] accessible identifier
    425 * @param  aAccTree       [in] JS object, each field corresponds to property of
    426 *                         accessible object. Additionally special properties
    427 *                         are presented:
    428 *                          children - an array of JS objects representing
    429 *                                      children of accessible
    430 *                          states   - an object having states and extraStates
    431 *                                      fields
    432 * @param aFlags          [in, optional] flags, see constants above
    433 */
    434 // eslint-disable-next-line complexity
    435 function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) {
    436  var acc = getAccessible(aAccOrElmOrID);
    437  if (!acc) {
    438    return;
    439  }
    440 
    441  var accTree = aAccTree;
    442 
    443  // Support of simplified accessible tree object.
    444  accTree = normalizeAccTreeObj(accTree);
    445 
    446  // Test accessible properties.
    447  for (var prop in accTree) {
    448    var msg =
    449      "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
    450 
    451    switch (prop) {
    452      case "actions": {
    453        testActionNames(acc, accTree.actions);
    454        break;
    455      }
    456 
    457      case "attributes":
    458        testAttrs(acc, accTree[prop], true);
    459        break;
    460 
    461      case "absentAttributes":
    462        testAbsentAttrs(acc, accTree[prop]);
    463        break;
    464 
    465      case "interfaces": {
    466        var ifaces =
    467          accTree[prop] instanceof Array ? accTree[prop] : [accTree[prop]];
    468        for (let i = 0; i < ifaces.length; i++) {
    469          ok(
    470            acc instanceof ifaces[i],
    471            "No " + ifaces[i] + " interface on " + prettyName(acc)
    472          );
    473        }
    474        break;
    475      }
    476 
    477      case "relations": {
    478        for (var rel in accTree[prop]) {
    479          testRelation(acc, window[rel], accTree[prop][rel]);
    480        }
    481        break;
    482      }
    483 
    484      case "role":
    485        isRole(acc, accTree[prop], msg);
    486        break;
    487 
    488      case "states":
    489      case "extraStates":
    490      case "absentStates":
    491      case "absentExtraStates": {
    492        testStates(
    493          acc,
    494          accTree.states,
    495          accTree.extraStates,
    496          accTree.absentStates,
    497          accTree.absentExtraStates
    498        );
    499        break;
    500      }
    501 
    502      case "tagName":
    503        is(accTree[prop], acc.DOMNode.tagName, msg);
    504        break;
    505 
    506      case "textAttrs": {
    507        var prevOffset = -1;
    508        for (var offset in accTree[prop]) {
    509          if (prevOffset != -1) {
    510            let attrs = accTree[prop][prevOffset];
    511            testTextAttrs(
    512              acc,
    513              prevOffset,
    514              attrs,
    515              {},
    516              prevOffset,
    517              +offset,
    518              true
    519            );
    520          }
    521          prevOffset = +offset;
    522        }
    523 
    524        if (prevOffset != -1) {
    525          var charCount = getAccessible(acc, [
    526            nsIAccessibleText,
    527          ]).characterCount;
    528          let attrs = accTree[prop][prevOffset];
    529          testTextAttrs(
    530            acc,
    531            prevOffset,
    532            attrs,
    533            {},
    534            prevOffset,
    535            charCount,
    536            true
    537          );
    538        }
    539 
    540        break;
    541      }
    542 
    543      default:
    544        if (prop.indexOf("todo_") == 0) {
    545          todo(false, msg);
    546        } else if (prop != "children") {
    547          is(acc[prop], accTree[prop], msg);
    548        }
    549    }
    550  }
    551 
    552  // Test children.
    553  if ("children" in accTree && accTree.children instanceof Array) {
    554    var children = acc.children;
    555    var childCount = children.length;
    556 
    557    if (accTree.children.length != childCount) {
    558      for (let i = 0; i < Math.max(accTree.children.length, childCount); i++) {
    559        var accChild = null,
    560          testChild = null;
    561        try {
    562          testChild = accTree.children[i];
    563          accChild = children.queryElementAt(i, nsIAccessible);
    564 
    565          if (!testChild) {
    566            ok(
    567              false,
    568              prettyName(acc) +
    569                " has an extra child at index " +
    570                i +
    571                " : " +
    572                prettyName(accChild)
    573            );
    574            continue;
    575          }
    576 
    577          testChild = normalizeAccTreeObj(testChild);
    578          if (accChild.role !== testChild.role) {
    579            ok(
    580              false,
    581              prettyName(accTree) +
    582                " and " +
    583                prettyName(acc) +
    584                " have different children at index " +
    585                i +
    586                " : " +
    587                prettyName(testChild) +
    588                ", " +
    589                prettyName(accChild)
    590            );
    591          }
    592          info(
    593            "Matching " +
    594              prettyName(accTree) +
    595              " and " +
    596              prettyName(acc) +
    597              " child at index " +
    598              i +
    599              " : " +
    600              prettyName(accChild)
    601          );
    602        } catch (e) {
    603          ok(
    604            false,
    605            prettyName(accTree) +
    606              " is expected to have a child at index " +
    607              i +
    608              " : " +
    609              prettyName(testChild) +
    610              ", original tested: " +
    611              prettyName(aAccOrElmOrID) +
    612              ", " +
    613              e
    614          );
    615        }
    616      }
    617    } else {
    618      if (aFlags & kSkipTreeFullCheck) {
    619        for (let i = 0; i < childCount; i++) {
    620          let child = children.queryElementAt(i, nsIAccessible);
    621          testAccessibleTree(child, accTree.children[i], aFlags);
    622        }
    623        return;
    624      }
    625 
    626      // nsIAccessible::firstChild
    627      var expectedFirstChild =
    628        childCount > 0 ? children.queryElementAt(0, nsIAccessible) : null;
    629      var firstChild = null;
    630      try {
    631        firstChild = acc.firstChild;
    632      } catch (e) {}
    633      is(
    634        firstChild,
    635        expectedFirstChild,
    636        "Wrong first child of " + prettyName(acc)
    637      );
    638 
    639      // nsIAccessible::lastChild
    640      var expectedLastChild =
    641        childCount > 0
    642          ? children.queryElementAt(childCount - 1, nsIAccessible)
    643          : null;
    644      var lastChild = null;
    645      try {
    646        lastChild = acc.lastChild;
    647      } catch (e) {}
    648      is(
    649        lastChild,
    650        expectedLastChild,
    651        "Wrong last child of " + prettyName(acc)
    652      );
    653 
    654      for (var i = 0; i < childCount; i++) {
    655        let child = children.queryElementAt(i, nsIAccessible);
    656 
    657        // nsIAccessible::parent
    658        var parent = null;
    659        try {
    660          parent = child.parent;
    661        } catch (e) {}
    662        is(parent, acc, "Wrong parent of " + prettyName(child));
    663 
    664        // nsIAccessible::indexInParent
    665        var indexInParent = -1;
    666        try {
    667          indexInParent = child.indexInParent;
    668        } catch (e) {}
    669        is(indexInParent, i, "Wrong index in parent of " + prettyName(child));
    670 
    671        // nsIAccessible::nextSibling
    672        var expectedNextSibling =
    673          i < childCount - 1
    674            ? children.queryElementAt(i + 1, nsIAccessible)
    675            : null;
    676        var nextSibling = null;
    677        try {
    678          nextSibling = child.nextSibling;
    679        } catch (e) {}
    680        is(
    681          nextSibling,
    682          expectedNextSibling,
    683          "Wrong next sibling of " + prettyName(child)
    684        );
    685 
    686        // nsIAccessible::previousSibling
    687        var expectedPrevSibling =
    688          i > 0 ? children.queryElementAt(i - 1, nsIAccessible) : null;
    689        var prevSibling = null;
    690        try {
    691          prevSibling = child.previousSibling;
    692        } catch (e) {}
    693        is(
    694          prevSibling,
    695          expectedPrevSibling,
    696          "Wrong previous sibling of " + prettyName(child)
    697        );
    698 
    699        // Go down through subtree
    700        testAccessibleTree(child, accTree.children[i], aFlags);
    701      }
    702    }
    703  }
    704 }
    705 
    706 /**
    707 * Return true if accessible for the given node is in cache.
    708 */
    709 function isAccessibleInCache(aNodeOrId) {
    710  var node = getNode(aNodeOrId);
    711  return !!gAccService.getAccessibleFromCache(node);
    712 }
    713 
    714 /**
    715 * Test accessible tree for defunct accessible.
    716 *
    717 * @param  aAcc       [in] the defunct accessible
    718 * @param  aNodeOrId  [in] the DOM node identifier for the defunct accessible
    719 */
    720 function testDefunctAccessible(aAcc, aNodeOrId) {
    721  if (aNodeOrId) {
    722    ok(
    723      !isAccessible(aNodeOrId),
    724      "Accessible for " + aNodeOrId + " wasn't properly shut down!"
    725    );
    726  }
    727 
    728  var msg =
    729    " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!";
    730 
    731  // firstChild
    732  var success = false;
    733  try {
    734    aAcc.firstChild;
    735  } catch (e) {
    736    success = e.result == Cr.NS_ERROR_FAILURE;
    737  }
    738  ok(success, "firstChild" + msg);
    739 
    740  // lastChild
    741  success = false;
    742  try {
    743    aAcc.lastChild;
    744  } catch (e) {
    745    success = e.result == Cr.NS_ERROR_FAILURE;
    746  }
    747  ok(success, "lastChild" + msg);
    748 
    749  // childCount
    750  success = false;
    751  try {
    752    aAcc.childCount;
    753  } catch (e) {
    754    success = e.result == Cr.NS_ERROR_FAILURE;
    755  }
    756  ok(success, "childCount" + msg);
    757 
    758  // children
    759  success = false;
    760  try {
    761    aAcc.children;
    762  } catch (e) {
    763    success = e.result == Cr.NS_ERROR_FAILURE;
    764  }
    765  ok(success, "children" + msg);
    766 
    767  // nextSibling
    768  success = false;
    769  try {
    770    aAcc.nextSibling;
    771  } catch (e) {
    772    success = e.result == Cr.NS_ERROR_FAILURE;
    773  }
    774  ok(success, "nextSibling" + msg);
    775 
    776  // previousSibling
    777  success = false;
    778  try {
    779    aAcc.previousSibling;
    780  } catch (e) {
    781    success = e.result == Cr.NS_ERROR_FAILURE;
    782  }
    783  ok(success, "previousSibling" + msg);
    784 
    785  // parent
    786  success = false;
    787  try {
    788    aAcc.parent;
    789  } catch (e) {
    790    success = e.result == Cr.NS_ERROR_FAILURE;
    791  }
    792  ok(success, "parent" + msg);
    793 }
    794 
    795 /**
    796 * Convert role to human readable string.
    797 */
    798 function roleToString(aRole) {
    799  return gAccService.getStringRole(aRole);
    800 }
    801 
    802 /**
    803 * Convert states to human readable string.
    804 */
    805 function statesToString(aStates, aExtraStates) {
    806  var list = gAccService.getStringStates(aStates, aExtraStates);
    807 
    808  var str = "";
    809  for (var index = 0; index < list.length - 1; index++) {
    810    str += list.item(index) + ", ";
    811  }
    812 
    813  if (list.length) {
    814    str += list.item(index);
    815  }
    816 
    817  return str;
    818 }
    819 
    820 /**
    821 * Convert event type to human readable string.
    822 */
    823 function eventTypeToString(aEventType) {
    824  return gAccService.getStringEventType(aEventType);
    825 }
    826 
    827 /**
    828 * Convert relation type to human readable string.
    829 */
    830 function relationTypeToString(aRelationType) {
    831  return gAccService.getStringRelationType(aRelationType);
    832 }
    833 
    834 function getLoadContext() {
    835  return window.docShell.QueryInterface(Ci.nsILoadContext);
    836 }
    837 
    838 /**
    839 * Return text from clipboard.
    840 */
    841 function getTextFromClipboard() {
    842  var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
    843    Ci.nsITransferable
    844  );
    845  trans.init(getLoadContext());
    846  if (!trans) {
    847    return "";
    848  }
    849 
    850  trans.addDataFlavor("text/plain");
    851  Services.clipboard.getData(
    852    trans,
    853    Services.clipboard.kGlobalClipboard,
    854    SpecialPowers.wrap(window).browsingContext.currentWindowContext
    855  );
    856 
    857  var str = {};
    858  trans.getTransferData("text/plain", str);
    859 
    860  if (str) {
    861    str = str.value.QueryInterface(Ci.nsISupportsString);
    862  }
    863  if (str) {
    864    return str.data;
    865  }
    866 
    867  return "";
    868 }
    869 
    870 /**
    871 * Obtain DOMNode id from an accessible. This simply queries the .id property
    872 * on the accessible, but it catches exceptions which might occur if the
    873 * accessible has died.
    874 *
    875 * @param  {nsIAccessible} accessible  accessible
    876 * @return {string?}                   DOMNode id if available
    877 */
    878 function getAccessibleDOMNodeID(accessible) {
    879  try {
    880    return accessible.id;
    881  } catch (e) {
    882    // This will fail if the accessible has died.
    883  }
    884  return null;
    885 }
    886 
    887 /**
    888 * Return pretty name for identifier, it may be ID, DOM node or accessible.
    889 */
    890 function prettyName(aIdentifier) {
    891  if (aIdentifier instanceof Array) {
    892    let msg = "";
    893    for (var idx = 0; idx < aIdentifier.length; idx++) {
    894      if (msg != "") {
    895        msg += ", ";
    896      }
    897 
    898      msg += prettyName(aIdentifier[idx]);
    899    }
    900    return msg;
    901  }
    902 
    903  if (aIdentifier instanceof nsIAccessible) {
    904    var acc = getAccessible(aIdentifier);
    905    var domID = getAccessibleDOMNodeID(acc);
    906    let msg = "[";
    907    try {
    908      if (Services.appinfo.browserTabsRemoteAutostart) {
    909        if (domID) {
    910          msg += `DOM node id: ${domID}, `;
    911        }
    912      } else {
    913        msg += `${getNodePrettyName(acc.DOMNode)}, `;
    914      }
    915      msg += "role: " + roleToString(acc.role);
    916      if (acc.name) {
    917        msg += ", name: '" + shortenString(acc.name) + "'";
    918      }
    919    } catch (e) {
    920      msg += "defunct";
    921    }
    922 
    923    if (acc) {
    924      msg += ", address: " + getObjAddress(acc);
    925    }
    926    msg += "]";
    927 
    928    return msg;
    929  }
    930 
    931  if (Node.isInstance(aIdentifier)) {
    932    return "[ " + getNodePrettyName(aIdentifier) + " ]";
    933  }
    934 
    935  if (aIdentifier && typeof aIdentifier === "object") {
    936    var treeObj = normalizeAccTreeObj(aIdentifier);
    937    if ("role" in treeObj) {
    938      function stringifyTree(aObj) {
    939        var text = roleToString(aObj.role) + ": [ ";
    940        if ("children" in aObj) {
    941          for (var i = 0; i < aObj.children.length; i++) {
    942            var c = normalizeAccTreeObj(aObj.children[i]);
    943            text += stringifyTree(c);
    944            if (i < aObj.children.length - 1) {
    945              text += ", ";
    946            }
    947          }
    948        }
    949        return text + "] ";
    950      }
    951      return `{ ${stringifyTree(treeObj)} }`;
    952    }
    953    return JSON.stringify(aIdentifier);
    954  }
    955 
    956  return " '" + aIdentifier + "' ";
    957 }
    958 
    959 /**
    960 * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
    961 *
    962 * @param aString the string to shorten.
    963 * @returns the shortened string.
    964 */
    965 function shortenString(aString) {
    966  if (aString.length <= MAX_TRIM_LENGTH) {
    967    return aString;
    968  }
    969 
    970  // Trim the string if its length is > MAX_TRIM_LENGTH characters.
    971  var trimOffset = MAX_TRIM_LENGTH / 2;
    972  return (
    973    aString.substring(0, trimOffset - 1) +
    974    "..." +
    975    aString.substring(aString.length - trimOffset, aString.length)
    976  );
    977 }
    978 
    979 // //////////////////////////////////////////////////////////////////////////////
    980 // General Utils
    981 // //////////////////////////////////////////////////////////////////////////////
    982 /**
    983 * Return main chrome window (crosses chrome boundary)
    984 */
    985 function getMainChromeWindow(aWindow) {
    986  return aWindow.browsingContext.topChromeWindow;
    987 }
    988 
    989 // //////////////////////////////////////////////////////////////////////////////
    990 // Private
    991 // //////////////////////////////////////////////////////////////////////////////
    992 
    993 // //////////////////////////////////////////////////////////////////////////////
    994 // Accessible general
    995 
    996 function getNodePrettyName(aNode) {
    997  try {
    998    var tag = "";
    999    if (aNode.nodeType == Node.DOCUMENT_NODE) {
   1000      tag = "document";
   1001    } else {
   1002      tag = aNode.localName;
   1003      if (aNode.nodeType == Node.ELEMENT_NODE && aNode.hasAttribute("id")) {
   1004        tag += '@id="' + aNode.getAttribute("id") + '"';
   1005      }
   1006    }
   1007 
   1008    return "'" + tag + " node', address: " + getObjAddress(aNode);
   1009  } catch (e) {
   1010    return "' no node info '";
   1011  }
   1012 }
   1013 
   1014 function getObjAddress(aObj) {
   1015  var exp = /native\s*@\s*(0x[a-f0-9]+)/g;
   1016  var match = exp.exec(aObj.toString());
   1017  if (match) {
   1018    return match[1];
   1019  }
   1020 
   1021  return aObj.toString();
   1022 }
   1023 
   1024 function normalizeAccTreeObj(aObj) {
   1025  var key = Object.keys(aObj)[0];
   1026  var roleName = "ROLE_" + key;
   1027  if (roleName in nsIAccessibleRole) {
   1028    return {
   1029      role: nsIAccessibleRole[roleName],
   1030      children: aObj[key],
   1031    };
   1032  }
   1033  return aObj;
   1034 }