tor-browser

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

utils.js (8024B)


      1 /**
      2 * GlobalOverrider - Utility that allows you to override properties on the global object.
      3 *                   See unit-entry.js for example usage.
      4 */
      5 export class GlobalOverrider {
      6  constructor() {
      7    this.originalGlobals = new Map();
      8    this.sandbox = sinon.createSandbox();
      9  }
     10 
     11  /**
     12   * _override - Internal method to override properties on the global object.
     13   *             The first time a given key is overridden, we cache the original
     14   *             value in this.originalGlobals so that later it can be restored.
     15   *
     16   * @param  {string} key The identifier of the property
     17   * @param  {any} value The value to which the property should be reassigned
     18   */
     19  _override(key, value) {
     20    if (!this.originalGlobals.has(key)) {
     21      this.originalGlobals.set(key, global[key]);
     22    }
     23    global[key] = value;
     24  }
     25 
     26  /**
     27   * set - Override a given property, or all properties on an object
     28   *
     29   * @param  {string|object} key If a string, the identifier of the property
     30   *                             If an object, a number of properties and values to which they should be reassigned.
     31   * @param  {any} value The value to which the property should be reassigned
     32   * @return {type}       description
     33   */
     34  set(key, value) {
     35    if (!value && typeof key === "object") {
     36      const overrides = key;
     37      Object.keys(overrides).forEach(k => this._override(k, overrides[k]));
     38    } else {
     39      this._override(key, value);
     40    }
     41    return value;
     42  }
     43 
     44  /**
     45   * reset - Reset the global sandbox, so all state on spies, stubs etc. is cleared.
     46   *         You probably want to call this after each test.
     47   */
     48  reset() {
     49    this.sandbox.reset();
     50  }
     51 
     52  /**
     53   * restore - Restore the global sandbox and reset all overriden properties to
     54   *           their original values. You should call this after all tests have completed.
     55   */
     56  restore() {
     57    this.sandbox.restore();
     58    this.originalGlobals.forEach((value, key) => {
     59      global[key] = value;
     60    });
     61  }
     62 }
     63 
     64 /**
     65 * A map of mocked preference names and values, used by `FakensIPrefBranch`,
     66 * `FakensIPrefService`, and `FakePrefs`.
     67 *
     68 * Tests should add entries to this map for any preferences they'd like to set,
     69 * and remove any entries during teardown for preferences that shouldn't be
     70 * shared between tests.
     71 */
     72 export const FAKE_GLOBAL_PREFS = new Map();
     73 
     74 /**
     75 * Very simple fake for the most basic semantics of nsIPrefBranch. Lots of
     76 * things aren't yet supported.  Feel free to add them in.
     77 *
     78 * @param {object} args - optional arguments
     79 * @param {Function} args.initHook - if present, will be called back
     80 *                   inside the constructor. Typically used from tests
     81 *                   to save off a pointer to the created instance so that
     82 *                   stubs and spies can be inspected by the test code.
     83 */
     84 export class FakensIPrefBranch {
     85  PREF_INVALID = "invalid";
     86  PREF_INT = "integer";
     87  PREF_BOOL = "boolean";
     88  PREF_STRING = "string";
     89 
     90  constructor(args) {
     91    if (args) {
     92      if ("initHook" in args) {
     93        args.initHook.call(this);
     94      }
     95      if (args.defaultBranch) {
     96        this.prefs = new Map();
     97      } else {
     98        this.prefs = FAKE_GLOBAL_PREFS;
     99      }
    100    } else {
    101      this.prefs = FAKE_GLOBAL_PREFS;
    102    }
    103    this._prefBranch = {};
    104    this.observers = new Map();
    105  }
    106  addObserver(prefix, callback) {
    107    this.observers.set(prefix, callback);
    108  }
    109  removeObserver(prefix, callback) {
    110    this.observers.delete(prefix, callback);
    111  }
    112  setStringPref(prefName, value) {
    113    this.set(prefName, value);
    114  }
    115  getStringPref(prefName, defaultValue) {
    116    return this.get(prefName, defaultValue);
    117  }
    118  setBoolPref(prefName, value) {
    119    this.set(prefName, value);
    120  }
    121  getBoolPref(prefName) {
    122    return this.get(prefName);
    123  }
    124  setIntPref(prefName, value) {
    125    this.set(prefName, value);
    126  }
    127  getIntPref(prefName) {
    128    return this.get(prefName);
    129  }
    130  setCharPref(prefName, value) {
    131    this.set(prefName, value);
    132  }
    133  getCharPref(prefName) {
    134    return this.get(prefName);
    135  }
    136  clearUserPref(prefName) {
    137    this.prefs.delete(prefName);
    138  }
    139  get(prefName, defaultValue) {
    140    let value = this.prefs.get(prefName);
    141    return typeof value === "undefined" ? defaultValue : value;
    142  }
    143  getPrefType(prefName) {
    144    let value = this.prefs.get(prefName);
    145    switch (typeof value) {
    146      case "number":
    147        return this.PREF_INT;
    148 
    149      case "boolean":
    150        return this.PREF_BOOL;
    151 
    152      case "string":
    153        return this.PREF_STRING;
    154 
    155      default:
    156        return this.PREF_INVALID;
    157    }
    158  }
    159  set(prefName, value) {
    160    this.prefs.set(prefName, value);
    161 
    162    // Trigger all observers for prefixes of the changed pref name. This matches
    163    // the semantics of `nsIPrefBranch`.
    164    let observerPrefixes = [...this.observers.keys()].filter(prefix =>
    165      prefName.startsWith(prefix)
    166    );
    167    for (let observerPrefix of observerPrefixes) {
    168      this.observers.get(observerPrefix)("", "", prefName);
    169    }
    170  }
    171  getChildList(prefix) {
    172    return [...this.prefs.keys()].filter(prefName =>
    173      prefName.startsWith(prefix)
    174    );
    175  }
    176  prefHasUserValue(prefName) {
    177    return this.prefs.has(prefName);
    178  }
    179  prefIsLocked(_prefName) {
    180    return false;
    181  }
    182 }
    183 
    184 /**
    185 * A fake `Services.prefs` implementation that extends `FakensIPrefBranch`
    186 * with methods specific to `nsIPrefService`.
    187 */
    188 export class FakensIPrefService extends FakensIPrefBranch {
    189  getBranch() {}
    190  getDefaultBranch(_prefix) {
    191    return {
    192      setBoolPref() {},
    193      setIntPref() {},
    194      setStringPref() {},
    195      clearUserPref() {},
    196    };
    197  }
    198 }
    199 
    200 /**
    201 * Very simple fake for the most basic semantics of Preferences.sys.mjs.
    202 * Extends FakensIPrefBranch.
    203 */
    204 export class FakePrefs extends FakensIPrefBranch {
    205  observe(prefName, callback) {
    206    super.addObserver(prefName, callback);
    207  }
    208  ignore(prefName, callback) {
    209    super.removeObserver(prefName, callback);
    210  }
    211  observeBranch(_listener) {}
    212  ignoreBranch(_listener) {}
    213  set(prefName, value) {
    214    this.prefs.set(prefName, value);
    215 
    216    // Trigger observers for just the changed pref name, not any of its
    217    // prefixes. This matches the semantics of `Preferences.sys.mjs`.
    218    if (this.observers.has(prefName)) {
    219      this.observers.get(prefName)(value);
    220    }
    221  }
    222 }
    223 
    224 export class FakeConsoleAPI {
    225  static LOG_LEVELS = {
    226    all: Number.MIN_VALUE,
    227    debug: 2,
    228    log: 3,
    229    info: 3,
    230    clear: 3,
    231    trace: 3,
    232    timeEnd: 3,
    233    time: 3,
    234    assert: 3,
    235    group: 3,
    236    groupEnd: 3,
    237    profile: 3,
    238    profileEnd: 3,
    239    dir: 3,
    240    dirxml: 3,
    241    warn: 4,
    242    error: 5,
    243    off: Number.MAX_VALUE,
    244  };
    245 
    246  constructor({ prefix = "", maxLogLevel = "all" } = {}) {
    247    this.prefix = prefix;
    248    this.prefixStr = prefix ? `${prefix}: ` : "";
    249    this.maxLogLevel = maxLogLevel;
    250 
    251    for (const level of Object.keys(FakeConsoleAPI.LOG_LEVELS)) {
    252      // eslint-disable-next-line no-console
    253      if (typeof console[level] === "function") {
    254        this[level] = this.shouldLog(level)
    255          ? this._log.bind(this, level)
    256          : () => {};
    257      }
    258    }
    259  }
    260  shouldLog(level) {
    261    return (
    262      FakeConsoleAPI.LOG_LEVELS[this.maxLogLevel] <=
    263      FakeConsoleAPI.LOG_LEVELS[level]
    264    );
    265  }
    266  _log(level, ...args) {
    267    console[level](this.prefixStr, ...args); // eslint-disable-line no-console
    268  }
    269 }
    270 
    271 export function FakeNimbusFeature() {
    272  return {
    273    getEnrollmentMetadata() {},
    274    getVariable() {},
    275    getAllVariables() {},
    276    onUpdate() {},
    277    offUpdate() {},
    278    recordExposureEvent() {},
    279  };
    280 }
    281 
    282 export function FakeNimbusFeatures(featureIds) {
    283  return Object.fromEntries(
    284    featureIds.map(featureId => [featureId, FakeNimbusFeature()])
    285  );
    286 }
    287 
    288 export class FakeLogger extends FakeConsoleAPI {
    289  constructor() {
    290    super({
    291      // Don't use a prefix because the first instance gets cached and reused by
    292      // other consumers that would otherwise pass their own identifying prefix.
    293      maxLogLevel: "off", // Change this to "debug" or "all" to get more logging in tests
    294    });
    295  }
    296 }