tor-browser

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

Common.sys.mjs (11383B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { Assert } from "resource://testing-common/Assert.sys.mjs";
      6 
      7 const MAX_TRIM_LENGTH = 100;
      8 
      9 export const CommonUtils = {
     10  /**
     11   * Constant passed to getAccessible to indicate that it shouldn't fail if
     12   * there is no accessible.
     13   */
     14  DONOTFAIL_IF_NO_ACC: 1,
     15 
     16  /**
     17   * Constant passed to getAccessible to indicate that it shouldn't fail if it
     18   * does not support an interface.
     19   */
     20  DONOTFAIL_IF_NO_INTERFACE: 2,
     21 
     22  /**
     23   * nsIAccessibilityService service.
     24   */
     25  get accService() {
     26    if (!this._accService) {
     27      this._accService = Cc["@mozilla.org/accessibilityService;1"].getService(
     28        Ci.nsIAccessibilityService
     29      );
     30    }
     31 
     32    return this._accService;
     33  },
     34 
     35  clearAccService() {
     36    this._accService = null;
     37    Cu.forceGC();
     38  },
     39 
     40  /**
     41   * Adds an observer for an 'a11y-consumers-changed' event.
     42   */
     43  addAccConsumersChangedObserver() {
     44    const deferred = {};
     45    this._accConsumersChanged = new Promise(resolve => {
     46      deferred.resolve = resolve;
     47    });
     48    const observe = (subject, topic, data) => {
     49      Services.obs.removeObserver(observe, "a11y-consumers-changed");
     50      deferred.resolve(JSON.parse(data));
     51    };
     52    Services.obs.addObserver(observe, "a11y-consumers-changed");
     53  },
     54 
     55  /**
     56   * Returns a promise that resolves when 'a11y-consumers-changed' event is
     57   * fired.
     58   *
     59   * @return {Promise}
     60   *         event promise evaluating to event's data
     61   */
     62  observeAccConsumersChanged() {
     63    return this._accConsumersChanged;
     64  },
     65 
     66  /**
     67   * Adds an observer for an 'a11y-init-or-shutdown' event with a value of "1"
     68   * which indicates that an accessibility service is initialized in the current
     69   * process.
     70   */
     71  addAccServiceInitializedObserver() {
     72    const deferred = {};
     73    this._accServiceInitialized = new Promise((resolve, reject) => {
     74      deferred.resolve = resolve;
     75      deferred.reject = reject;
     76    });
     77    const observe = (subject, topic, data) => {
     78      if (data === "1") {
     79        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
     80        deferred.resolve();
     81      } else {
     82        deferred.reject("Accessibility service is shutdown unexpectedly.");
     83      }
     84    };
     85    Services.obs.addObserver(observe, "a11y-init-or-shutdown");
     86  },
     87 
     88  /**
     89   * Returns a promise that resolves when an accessibility service is
     90   * initialized in the current process. Otherwise (if the service is shutdown)
     91   * the promise is rejected.
     92   */
     93  observeAccServiceInitialized() {
     94    return this._accServiceInitialized;
     95  },
     96 
     97  /**
     98   * Adds an observer for an 'a11y-init-or-shutdown' event with a value of "0"
     99   * which indicates that an accessibility service is shutdown in the current
    100   * process.
    101   */
    102  addAccServiceShutdownObserver() {
    103    const deferred = {};
    104    this._accServiceShutdown = new Promise((resolve, reject) => {
    105      deferred.resolve = resolve;
    106      deferred.reject = reject;
    107    });
    108    const observe = (subject, topic, data) => {
    109      if (data === "0") {
    110        Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
    111        deferred.resolve();
    112      } else {
    113        deferred.reject("Accessibility service is initialized unexpectedly.");
    114      }
    115    };
    116    Services.obs.addObserver(observe, "a11y-init-or-shutdown");
    117  },
    118 
    119  /**
    120   * Returns a promise that resolves when an accessibility service is shutdown
    121   * in the current process. Otherwise (if the service is initialized) the
    122   * promise is rejected.
    123   */
    124  observeAccServiceShutdown() {
    125    return this._accServiceShutdown;
    126  },
    127 
    128  /**
    129   * Obtain DOMNode id from an accessible. This simply queries the .id property
    130   * on the accessible, but it catches exceptions which might occur if the
    131   * accessible has died or was constructed from a pseudoelement
    132   * like ::details-content.
    133   *
    134   * @param  {nsIAccessible} accessible  accessible
    135   * @return {string?}                   DOMNode id if available
    136   */
    137  getAccessibleDOMNodeID(accessible) {
    138    try {
    139      return accessible.id;
    140    } catch (e) {
    141      // This will fail if the accessible has died, or if
    142      // the accessible was constructed from a pseudoelement
    143      // like ::details-content.
    144    }
    145    return null;
    146  },
    147 
    148  getObjAddress(obj) {
    149    const exp = /native\s*@\s*(0x[a-f0-9]+)/g;
    150    const match = exp.exec(obj.toString());
    151    if (match) {
    152      return match[1];
    153    }
    154 
    155    return obj.toString();
    156  },
    157 
    158  getNodePrettyName(node) {
    159    try {
    160      let tag = "";
    161      if (node.nodeType == Node.DOCUMENT_NODE) {
    162        tag = "document";
    163      } else {
    164        tag = node.localName;
    165        if (node.nodeType == Node.ELEMENT_NODE && node.hasAttribute("id")) {
    166          tag += `@id="${node.getAttribute("id")}"`;
    167        }
    168      }
    169 
    170      return `"${tag} node", address: ${this.getObjAddress(node)}`;
    171    } catch (e) {
    172      return `" no node info "`;
    173    }
    174  },
    175 
    176  /**
    177   * Convert role to human readable string.
    178   */
    179  roleToString(role) {
    180    return this.accService.getStringRole(role);
    181  },
    182 
    183  /**
    184   * Shorten a long string if it exceeds MAX_TRIM_LENGTH.
    185   *
    186   * @param aString the string to shorten.
    187   *
    188   * @returns the shortened string.
    189   */
    190  shortenString(str) {
    191    if (str.length <= MAX_TRIM_LENGTH) {
    192      return str;
    193    }
    194 
    195    // Trim the string if its length is > MAX_TRIM_LENGTH characters.
    196    const trimOffset = MAX_TRIM_LENGTH / 2;
    197 
    198    return `${str.substring(0, trimOffset - 1)}${str.substring(
    199      str.length - trimOffset,
    200      str.length
    201    )}`;
    202  },
    203 
    204  normalizeAccTreeObj(obj) {
    205    const key = Object.keys(obj)[0];
    206    const roleName = `ROLE_${key}`;
    207    if (roleName in Ci.nsIAccessibleRole) {
    208      return {
    209        role: Ci.nsIAccessibleRole[roleName],
    210        children: obj[key],
    211      };
    212    }
    213 
    214    return obj;
    215  },
    216 
    217  stringifyTree(obj) {
    218    let text = this.roleToString(obj.role) + ": [ ";
    219    if ("children" in obj) {
    220      for (let i = 0; i < obj.children.length; i++) {
    221        const c = this.normalizeAccTreeObj(obj.children[i]);
    222        text += this.stringifyTree(c);
    223        if (i < obj.children.length - 1) {
    224          text += ", ";
    225        }
    226      }
    227    }
    228 
    229    return `${text}] `;
    230  },
    231 
    232  /**
    233   * Return pretty name for identifier, it may be ID, DOM node or accessible.
    234   */
    235  prettyName(identifier) {
    236    if (identifier instanceof Array) {
    237      let msg = "";
    238      for (let idx = 0; idx < identifier.length; idx++) {
    239        if (msg != "") {
    240          msg += ", ";
    241        }
    242 
    243        msg += this.prettyName(identifier[idx]);
    244      }
    245      return msg;
    246    }
    247 
    248    if (identifier instanceof Ci.nsIAccessible) {
    249      const acc = this.getAccessible(identifier);
    250      const domID = this.getAccessibleDOMNodeID(acc);
    251      let msg = "[";
    252      try {
    253        if (Services.appinfo.browserTabsRemoteAutostart) {
    254          if (domID) {
    255            msg += `DOM node id: ${domID}, `;
    256          }
    257        } else {
    258          msg += `${this.getNodePrettyName(acc.DOMNode)}, `;
    259        }
    260        msg += `role: ${this.roleToString(acc.role)}`;
    261        if (acc.name) {
    262          msg += `, name: "${this.shortenString(acc.name)}"`;
    263        }
    264      } catch (e) {
    265        msg += "defunct";
    266      }
    267 
    268      if (acc) {
    269        msg += `, address: ${this.getObjAddress(acc)}`;
    270      }
    271      msg += "]";
    272 
    273      return msg;
    274    }
    275 
    276    if (Node.isInstance(identifier)) {
    277      return `[ ${this.getNodePrettyName(identifier)} ]`;
    278    }
    279 
    280    if (identifier && typeof identifier === "object") {
    281      const treeObj = this.normalizeAccTreeObj(identifier);
    282      if ("role" in treeObj) {
    283        return `{ ${this.stringifyTree(treeObj)} }`;
    284      }
    285 
    286      return JSON.stringify(identifier);
    287    }
    288 
    289    return ` "${identifier}" `;
    290  },
    291 
    292  /**
    293   * Return accessible for the given identifier (may be ID attribute or DOM
    294   * element or accessible object) or null.
    295   *
    296   * @param accOrElmOrID
    297   *        identifier to get an accessible implementing the given interfaces
    298   * @param aInterfaces
    299   *        [optional] the interface or an array interfaces to query it/them
    300   *        from obtained accessible
    301   * @param elmObj
    302   *        [optional] object to store DOM element which accessible is obtained
    303   *        for
    304   * @param doNotFailIf
    305   *        [optional] no error for special cases (see DONOTFAIL_IF_NO_ACC,
    306   *        DONOTFAIL_IF_NO_INTERFACE)
    307   * @param doc
    308   *        [optional] document for when accOrElmOrID is an ID.
    309   */
    310  getAccessible(accOrElmOrID, interfaces, elmObj, doNotFailIf, doc) {
    311    if (!accOrElmOrID) {
    312      return null;
    313    }
    314 
    315    let elm = null;
    316    if (accOrElmOrID instanceof Ci.nsIAccessible) {
    317      try {
    318        elm = accOrElmOrID.DOMNode;
    319      } catch (e) {}
    320    } else if (Node.isInstance(accOrElmOrID)) {
    321      elm = accOrElmOrID;
    322    } else {
    323      elm = doc.getElementById(accOrElmOrID);
    324      if (!elm) {
    325        Assert.ok(false, `Can't get DOM element for ${accOrElmOrID}`);
    326        return null;
    327      }
    328    }
    329 
    330    if (elmObj && typeof elmObj == "object") {
    331      elmObj.value = elm;
    332    }
    333 
    334    let acc = accOrElmOrID instanceof Ci.nsIAccessible ? accOrElmOrID : null;
    335    if (!acc) {
    336      try {
    337        acc = this.accService.getAccessibleFor(elm);
    338      } catch (e) {}
    339 
    340      if (!acc) {
    341        if (!(doNotFailIf & this.DONOTFAIL_IF_NO_ACC)) {
    342          Assert.ok(
    343            false,
    344            `Can't get accessible for ${this.prettyName(accOrElmOrID)}`
    345          );
    346        }
    347 
    348        return null;
    349      }
    350    }
    351 
    352    if (!interfaces) {
    353      return acc;
    354    }
    355 
    356    if (!(interfaces instanceof Array)) {
    357      interfaces = [interfaces];
    358    }
    359 
    360    for (let index = 0; index < interfaces.length; index++) {
    361      if (acc instanceof interfaces[index]) {
    362        continue;
    363      }
    364 
    365      try {
    366        acc.QueryInterface(interfaces[index]);
    367      } catch (e) {
    368        if (!(doNotFailIf & this.DONOTFAIL_IF_NO_INTERFACE)) {
    369          Assert.ok(
    370            false,
    371            `Can't query ${interfaces[index]} for ${accOrElmOrID}`
    372          );
    373        }
    374 
    375        return null;
    376      }
    377    }
    378 
    379    return acc;
    380  },
    381 
    382  /**
    383   * Return the DOM node by identifier (may be accessible, DOM node or ID).
    384   */
    385  getNode(accOrNodeOrID, doc) {
    386    if (!accOrNodeOrID) {
    387      return null;
    388    }
    389 
    390    if (Node.isInstance(accOrNodeOrID)) {
    391      return accOrNodeOrID;
    392    }
    393 
    394    if (accOrNodeOrID instanceof Ci.nsIAccessible) {
    395      return accOrNodeOrID.DOMNode;
    396    }
    397 
    398    const node = doc.getElementById(accOrNodeOrID);
    399    if (!node) {
    400      Assert.ok(false, `Can't get DOM element for ${accOrNodeOrID}`);
    401      return null;
    402    }
    403 
    404    return node;
    405  },
    406 
    407  /**
    408   * Return root accessible.
    409   *
    410   * @param  {DOMNode} doc
    411   *         Chrome document.
    412   *
    413   * @return {nsIAccessible}
    414   *         Accessible object for chrome window.
    415   */
    416  getRootAccessible(doc) {
    417    const acc = this.getAccessible(doc);
    418    return acc ? acc.rootDocument.QueryInterface(Ci.nsIAccessible) : null;
    419  },
    420 
    421  /**
    422   * Analogy of SimpleTest.is function used to compare objects.
    423   */
    424  isObject(obj, expectedObj, msg) {
    425    if (obj == expectedObj) {
    426      Assert.ok(true, msg);
    427      return;
    428    }
    429 
    430    Assert.ok(
    431      false,
    432      `${msg} - got "${this.prettyName(obj)}", expected "${this.prettyName(
    433        expectedObj
    434      )}"`
    435    );
    436  },
    437 };