tor-browser

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

DevToolsUtils.js (31855B)


      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 /* globals setImmediate, rpc */
      6 
      7 "use strict";
      8 
      9 /* General utilities used throughout devtools. */
     10 
     11 var flags = require("resource://devtools/shared/flags.js");
     12 var {
     13  getStack,
     14  callFunctionWithAsyncStack,
     15 } = require("resource://devtools/shared/platform/stack.js");
     16 
     17 const lazy = {};
     18 
     19 if (!isWorker) {
     20  ChromeUtils.defineESModuleGetters(
     21    lazy,
     22    {
     23      DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
     24      FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
     25      NetworkHelper:
     26        "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
     27      ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
     28    },
     29    { global: "contextual" }
     30  );
     31 }
     32 
     33 // Native getters which are considered to be side effect free.
     34 ChromeUtils.defineLazyGetter(lazy, "sideEffectFreeGetters", () => {
     35  const {
     36    getters,
     37  } = require("resource://devtools/server/actors/webconsole/eager-ecma-allowlist.js");
     38 
     39  const map = new Map();
     40  for (const n of getters) {
     41    if (!map.has(n.name)) {
     42      map.set(n.name, []);
     43    }
     44    map.get(n.name).push(n);
     45  }
     46 
     47  return map;
     48 });
     49 
     50 // Using this name lets the eslint plugin know about lazy defines in
     51 // this file.
     52 var DevToolsUtils = exports;
     53 
     54 // Re-export the thread-safe utils.
     55 const ThreadSafeDevToolsUtils = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
     56 for (const key of Object.keys(ThreadSafeDevToolsUtils)) {
     57  exports[key] = ThreadSafeDevToolsUtils[key];
     58 }
     59 
     60 /**
     61 * Waits for the next tick in the event loop to execute a callback.
     62 */
     63 exports.executeSoon = function (fn) {
     64  if (isWorker) {
     65    setImmediate(fn);
     66  } else {
     67    let executor;
     68    // Only enable async stack reporting when DEBUG_JS_MODULES is set
     69    // (customized local builds) to avoid a performance penalty.
     70    if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
     71      const stack = getStack();
     72      executor = () => {
     73        callFunctionWithAsyncStack(fn, stack, "DevToolsUtils.executeSoon");
     74      };
     75    } else {
     76      executor = fn;
     77    }
     78    Services.tm.dispatchToMainThread({
     79      run: exports.makeInfallible(executor),
     80    });
     81  }
     82 };
     83 
     84 /**
     85 * Similar to executeSoon, but enters microtask before executing the callback
     86 * if this is called on the main thread.
     87 */
     88 exports.executeSoonWithMicroTask = function (fn) {
     89  if (isWorker) {
     90    setImmediate(fn);
     91  } else {
     92    let executor;
     93    // Only enable async stack reporting when DEBUG_JS_MODULES is set
     94    // (customized local builds) to avoid a performance penalty.
     95    if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
     96      const stack = getStack();
     97      executor = () => {
     98        callFunctionWithAsyncStack(
     99          fn,
    100          stack,
    101          "DevToolsUtils.executeSoonWithMicroTask"
    102        );
    103      };
    104    } else {
    105      executor = fn;
    106    }
    107    Services.tm.dispatchToMainThreadWithMicroTask({
    108      run: exports.makeInfallible(executor),
    109    });
    110  }
    111 };
    112 
    113 /**
    114 * Waits for the next tick in the event loop.
    115 *
    116 * @return Promise
    117 *         A promise that is resolved after the next tick in the event loop.
    118 */
    119 exports.waitForTick = function () {
    120  return new Promise(resolve => {
    121    exports.executeSoon(resolve);
    122  });
    123 };
    124 
    125 /**
    126 * Waits for the specified amount of time to pass.
    127 *
    128 * @param number delay
    129 *        The amount of time to wait, in milliseconds.
    130 * @return Promise
    131 *         A promise that is resolved after the specified amount of time passes.
    132 */
    133 exports.waitForTime = function (delay) {
    134  return new Promise(resolve => setTimeout(resolve, delay));
    135 };
    136 
    137 /**
    138 * Like ChromeUtils.defineLazyGetter, but with a |this| sensitive getter that
    139 * allows the lazy getter to be defined on a prototype and work correctly with
    140 * instances.
    141 *
    142 * @param Object object
    143 *        The prototype object to define the lazy getter on.
    144 * @param String key
    145 *        The key to define the lazy getter on.
    146 * @param Function callback
    147 *        The callback that will be called to determine the value. Will be
    148 *        called with the |this| value of the current instance.
    149 */
    150 exports.defineLazyPrototypeGetter = function (object, key, callback) {
    151  Object.defineProperty(object, key, {
    152    configurable: true,
    153    get() {
    154      const value = callback.call(this);
    155 
    156      Object.defineProperty(this, key, {
    157        configurable: true,
    158        writable: true,
    159        value,
    160      });
    161 
    162      return value;
    163    },
    164  });
    165 };
    166 
    167 /**
    168 * Safely get the property value from a Debugger.Object for a given key. Walks
    169 * the prototype chain until the property is found.
    170 *
    171 * @param {Debugger.Object} object
    172 *        The Debugger.Object to get the value from.
    173 * @param {string} key
    174 *        The key to look for.
    175 * @param {boolean} invokeUnsafeGetter (defaults to false).
    176 *        Optional boolean to indicate if the function should execute unsafe getter
    177 *        in order to retrieve its result's properties.
    178 *        ⚠️ This should be set to true *ONLY* on user action as it may cause side-effects
    179 *        in the content page ⚠️
    180 * @return Any
    181 */
    182 exports.getProperty = function (object, key, invokeUnsafeGetters = false) {
    183  const root = object;
    184  while (object && exports.isSafeDebuggerObject(object)) {
    185    let desc;
    186    try {
    187      desc = object.getOwnPropertyDescriptor(key);
    188    } catch (e) {
    189      // The above can throw when the debuggee does not subsume the object's
    190      // compartment, or for some WrappedNatives like Cu.Sandbox.
    191      return undefined;
    192    }
    193    if (desc) {
    194      if ("value" in desc) {
    195        return desc.value;
    196      }
    197      // Call the getter if it's safe.
    198      if (exports.hasSafeGetter(desc) || invokeUnsafeGetters === true) {
    199        try {
    200          return desc.get.call(root).return;
    201        } catch (e) {
    202          // If anything goes wrong report the error and return undefined.
    203          exports.reportException("getProperty", e);
    204        }
    205      }
    206      return undefined;
    207    }
    208    object = object.proto;
    209  }
    210  return undefined;
    211 };
    212 
    213 /**
    214 * Removes all the non-opaque security wrappers of a debuggee object.
    215 *
    216 * @param obj Debugger.Object
    217 *        The debuggee object to be unwrapped.
    218 * @return Debugger.Object|null|undefined
    219 *      - If the object has no wrapper, the same `obj` is returned. Note DeadObject
    220 *        objects belong to this case.
    221 *      - Otherwise, if the debuggee doesn't subsume object's compartment, returns `null`.
    222 *      - Otherwise, if the object belongs to an invisible-to-debugger compartment,
    223 *        returns `undefined`.
    224 *      - Otherwise, returns the unwrapped object.
    225 */
    226 exports.unwrap = function unwrap(obj) {
    227  // Check if `obj` has an opaque wrapper.
    228  if (obj.class === "Opaque") {
    229    return obj;
    230  }
    231 
    232  // Attempt to unwrap via `obj.unwrap()`. Note that:
    233  // - This will return `null` if the debuggee does not subsume object's compartment.
    234  // - This will throw if the object belongs to an invisible-to-debugger compartment.
    235  // - This will return `obj` if there is no wrapper.
    236  let unwrapped;
    237  try {
    238    unwrapped = obj.unwrap();
    239  } catch (err) {
    240    return undefined;
    241  }
    242 
    243  // Check if further unwrapping is not possible.
    244  if (!unwrapped || unwrapped === obj) {
    245    return unwrapped;
    246  }
    247 
    248  // Recursively remove additional security wrappers.
    249  return unwrap(unwrapped);
    250 };
    251 
    252 /**
    253 * Checks whether a debuggee object is safe. Unsafe objects may run proxy traps or throw
    254 * when using `proto`, `isExtensible`, `isFrozen` or `isSealed`. Note that safe objects
    255 * may still throw when calling `getOwnPropertyNames`, `getOwnPropertyDescriptor`, etc.
    256 * Also note DeadObject objects are considered safe.
    257 *
    258 * @param obj Debugger.Object
    259 *        The debuggee object to be checked.
    260 * @return boolean
    261 */
    262 exports.isSafeDebuggerObject = function (obj) {
    263  const unwrapped = exports.unwrap(obj);
    264 
    265  // Objects belonging to an invisible-to-debugger compartment might be proxies,
    266  // so just in case consider them unsafe.
    267  if (unwrapped === undefined) {
    268    return false;
    269  }
    270 
    271  // If the debuggee does not subsume the object's compartment, most properties won't
    272  // be accessible. Cross-origin Window and Location objects might expose some, though.
    273  // Therefore, it must be considered safe. Note that proxy objects have fully opaque
    274  // security wrappers, so proxy traps won't run in this case.
    275  if (unwrapped === null) {
    276    return true;
    277  }
    278 
    279  // Proxy objects can run traps when accessed. `isProxy` getter is called on `unwrapped`
    280  // instead of on `obj` in order to detect proxies behind transparent wrappers.
    281  if (unwrapped.isProxy) {
    282    return false;
    283  }
    284 
    285  return true;
    286 };
    287 
    288 /**
    289 * Determines if a descriptor has a getter which doesn't call into JavaScript.
    290 *
    291 * @param Object desc
    292 *        The descriptor to check for a safe getter.
    293 * @return Boolean
    294 *         Whether a safe getter was found.
    295 */
    296 exports.hasSafeGetter = function (desc) {
    297  // Scripted functions that are CCWs will not appear scripted until after
    298  // unwrapping.
    299  let fn = desc.get;
    300  fn = fn && exports.unwrap(fn);
    301  if (!fn) {
    302    return false;
    303  }
    304  if (!fn.callable || fn.class !== "Function") {
    305    return false;
    306  }
    307  if (fn.script !== undefined) {
    308    // This is scripted function.
    309    return false;
    310  }
    311 
    312  // This is a getter with native function.
    313 
    314  // We assume all DOM getters have no major side effect, and they are
    315  // eagerly-evaluateable.
    316  //
    317  // JitInfo is used only by methods/accessors in WebIDL, and being
    318  // "a getter with JitInfo" can be used as a condition to check if given
    319  // function is DOM getter.
    320  //
    321  // This includes privileged interfaces in addition to standard web APIs.
    322  if (fn.isNativeGetterWithJitInfo()) {
    323    return true;
    324  }
    325 
    326  // Apply explicit allowlist.
    327  const natives = lazy.sideEffectFreeGetters.get(fn.name);
    328  return natives && natives.some(n => fn.isSameNative(n));
    329 };
    330 
    331 /**
    332 * Check that the property value from a Debugger.Object for a given key is an unsafe
    333 * getter or not. Walks the prototype chain until the property is found.
    334 *
    335 * @param {Debugger.Object} object
    336 *        The Debugger.Object to check on.
    337 * @param {string} key
    338 *        The key to look for.
    339 * @param {boolean} invokeUnsafeGetter (defaults to false).
    340 *        Optional boolean to indicate if the function should execute unsafe getter
    341 *        in order to retrieve its result's properties.
    342 * @return Boolean
    343 */
    344 exports.isUnsafeGetter = function (object, key) {
    345  while (object && exports.isSafeDebuggerObject(object)) {
    346    let desc;
    347    try {
    348      desc = object.getOwnPropertyDescriptor(key);
    349    } catch (e) {
    350      // The above can throw when the debuggee does not subsume the object's
    351      // compartment, or for some WrappedNatives like Cu.Sandbox.
    352      return false;
    353    }
    354    if (desc) {
    355      if (Object.getOwnPropertyNames(desc).includes("get")) {
    356        return !exports.hasSafeGetter(desc);
    357      }
    358    }
    359    object = object.proto;
    360  }
    361 
    362  return false;
    363 };
    364 
    365 /**
    366 * Check if it is safe to read properties and execute methods from the given JS
    367 * object. Safety is defined as being protected from unintended code execution
    368 * from content scripts (or cross-compartment code).
    369 *
    370 * See bugs 945920 and 946752 for discussion.
    371 *
    372 * @type Object obj
    373 *       The object to check.
    374 * @return Boolean
    375 *         True if it is safe to read properties from obj, or false otherwise.
    376 */
    377 exports.isSafeJSObject = function (obj) {
    378  // If we are running on a worker thread, Cu is not available. In this case,
    379  // we always return false, just to be on the safe side.
    380  if (isWorker) {
    381    return false;
    382  }
    383 
    384  if (
    385    Cu.getGlobalForObject(obj) == Cu.getGlobalForObject(exports.isSafeJSObject)
    386  ) {
    387    // obj is not a cross-compartment wrapper.
    388    return true;
    389  }
    390 
    391  // Xray wrappers protect against unintended code execution.
    392  if (Cu.isXrayWrapper(obj)) {
    393    return true;
    394  }
    395 
    396  // If there aren't Xrays, only allow chrome objects.
    397  const principal = Cu.getObjectPrincipal(obj);
    398  if (!principal.isSystemPrincipal) {
    399    return false;
    400  }
    401 
    402  // Scripted proxy objects without Xrays can run their proxy traps.
    403  if (Cu.isProxy(obj)) {
    404    return false;
    405  }
    406 
    407  // Even if `obj` looks safe, an unsafe object in its prototype chain may still
    408  // run unintended code, e.g. when using the `instanceof` operator.
    409  const proto = Object.getPrototypeOf(obj);
    410  if (proto && !exports.isSafeJSObject(proto)) {
    411    return false;
    412  }
    413 
    414  // Allow non-problematic chrome objects.
    415  return true;
    416 };
    417 
    418 /**
    419 * Dump with newline - This is a logging function that will only output when
    420 * the preference "devtools.debugger.log" is set to true. Typically it is used
    421 * for logging the remote debugging protocol calls.
    422 */
    423 exports.dumpn = function (str) {
    424  if (flags.wantLogging) {
    425    dump("DBG-SERVER: " + str + "\n");
    426  }
    427 };
    428 
    429 /**
    430 * Dump verbose - This is a verbose logger for low-level tracing, that is typically
    431 * used to provide information about the remote debugging protocol's transport
    432 * mechanisms. The logging can be enabled by changing the preferences
    433 * "devtools.debugger.log" and "devtools.debugger.log.verbose" to true.
    434 */
    435 exports.dumpv = function (msg) {
    436  if (flags.wantVerbose) {
    437    exports.dumpn(msg);
    438  }
    439 };
    440 
    441 /**
    442 * Defines a getter on a specified object that will be created upon first use.
    443 *
    444 * @param object
    445 *        The object to define the lazy getter on.
    446 * @param name
    447 *        The name of the getter to define on object.
    448 * @param lambda
    449 *        A function that returns what the getter should return.  This will
    450 *        only ever be called once.
    451 */
    452 exports.defineLazyGetter = function (object, name, lambda) {
    453  Object.defineProperty(object, name, {
    454    get() {
    455      delete object[name];
    456      object[name] = lambda.apply(object);
    457      return object[name];
    458    },
    459    configurable: true,
    460    enumerable: true,
    461  });
    462 };
    463 
    464 DevToolsUtils.defineLazyGetter(this, "AppConstants", () => {
    465  if (isWorker) {
    466    return {};
    467  }
    468  return ChromeUtils.importESModule(
    469    "resource://gre/modules/AppConstants.sys.mjs",
    470    { global: "contextual" }
    471  ).AppConstants;
    472 });
    473 
    474 /**
    475 * No operation. The empty function.
    476 */
    477 exports.noop = function () {};
    478 
    479 let assertionFailureCount = 0;
    480 
    481 Object.defineProperty(exports, "assertionFailureCount", {
    482  get() {
    483    return assertionFailureCount;
    484  },
    485 });
    486 
    487 function reallyAssert(condition, message) {
    488  if (!condition) {
    489    assertionFailureCount++;
    490    const err = new Error("Assertion failure: " + message);
    491    exports.reportException("DevToolsUtils.assert", err);
    492    throw err;
    493  }
    494 }
    495 
    496 /**
    497 * DevToolsUtils.assert(condition, message)
    498 *
    499 * @param Boolean condition
    500 * @param String message
    501 *
    502 * Assertions are enabled when any of the following are true:
    503 *   - This is a DEBUG_JS_MODULES build
    504 *   - flags.testing is set to true
    505 *
    506 * If assertions are enabled, then `condition` is checked and if false-y, the
    507 * assertion failure is logged and then an error is thrown.
    508 *
    509 * If assertions are not enabled, then this function is a no-op.
    510 */
    511 Object.defineProperty(exports, "assert", {
    512  get: () =>
    513    AppConstants.DEBUG_JS_MODULES || flags.testing
    514      ? reallyAssert
    515      : exports.noop,
    516 });
    517 
    518 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
    519  return ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs", {
    520    global: "contextual",
    521  }).NetUtil;
    522 });
    523 
    524 /**
    525 * Performs a request to load the desired URL and returns a promise.
    526 *
    527 * @param urlIn String
    528 *        The URL we will request.
    529 * @param aOptions Object
    530 *        An object with the following optional properties:
    531 *        - loadFromCache: if false, will bypass the cache and
    532 *          always load fresh from the network (default: true)
    533 *        - policy: the nsIContentPolicy type to apply when fetching the URL
    534 *                  (only works when loading from system principal)
    535 *        - window: the window to get the loadGroup from
    536 *        - charset: the charset to use if the channel doesn't provide one
    537 *        - principal: the principal to use, if omitted, the request is loaded
    538 *                     with a content principal corresponding to the url being
    539 *                     loaded, using the origin attributes of the window, if any.
    540 *        - headers: extra headers
    541 *        - cacheKey: when loading from cache, use this key to retrieve a cache
    542 *                    specific to a given SHEntry. (Allows loading POST
    543 *                    requests from cache)
    544 * @returns Promise that resolves with an object with the following members on
    545 *          success:
    546 *           - content: the document at that URL, as a string,
    547 *           - contentType: the content type of the document
    548 *
    549 *          If an error occurs, the promise is rejected with that error.
    550 *
    551 * XXX: It may be better to use nsITraceableChannel to get to the sources
    552 * without relying on caching when we can (not for eval, etc.):
    553 * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
    554 */
    555 function mainThreadFetch(
    556  urlIn,
    557  aOptions = {
    558    loadFromCache: true,
    559    policy: Ci.nsIContentPolicy.TYPE_OTHER,
    560    window: null,
    561    charset: null,
    562    principal: null,
    563    headers: null,
    564    cacheKey: 0,
    565  }
    566 ) {
    567  return new Promise((resolve, reject) => {
    568    // Create a channel.
    569    const url = urlIn.split(" -> ").pop();
    570    let channel;
    571    try {
    572      channel = newChannelForURL(url, aOptions);
    573    } catch (ex) {
    574      reject(ex);
    575      return;
    576    }
    577 
    578    channel.loadInfo.isInDevToolsContext = true;
    579 
    580    // Set the channel options.
    581    channel.loadFlags = aOptions.loadFromCache
    582      ? channel.LOAD_FROM_CACHE
    583      : channel.LOAD_BYPASS_CACHE;
    584 
    585    if (aOptions.loadFromCache && channel instanceof Ci.nsICacheInfoChannel) {
    586      // If DevTools intents to load the content from the cache,
    587      // we make the LOAD_FROM_CACHE flag preferred over LOAD_BYPASS_CACHE.
    588      channel.preferCacheLoadOverBypass = true;
    589 
    590      // When loading from cache, the cacheKey allows us to target a specific
    591      // SHEntry and offer ways to restore POST requests from cache.
    592      if (aOptions.cacheKey != 0) {
    593        channel.cacheKey = aOptions.cacheKey;
    594      }
    595    }
    596 
    597    if (aOptions.headers && channel instanceof Ci.nsIHttpChannel) {
    598      for (const h in aOptions.headers) {
    599        channel.setRequestHeader(h, aOptions.headers[h], /* aMerge = */ false);
    600      }
    601    }
    602 
    603    if (aOptions.window) {
    604      // Respect private browsing.
    605      channel.loadGroup = aOptions.window.docShell.QueryInterface(
    606        Ci.nsIDocumentLoader
    607      ).loadGroup;
    608    }
    609 
    610    // eslint-disable-next-line complexity
    611    const onResponse = (stream, status, request) => {
    612      if (!Components.isSuccessCode(status)) {
    613        reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
    614        return;
    615      }
    616 
    617      try {
    618        // We cannot use NetUtil to do the charset conversion as if charset
    619        // information is not available and our default guess is wrong the method
    620        // might fail and we lose the stream data. This means we can't fall back
    621        // to using the locale default encoding (bug 1181345).
    622 
    623        // Read and decode the data according to the locale default encoding.
    624 
    625        let available;
    626        try {
    627          available = stream.available();
    628        } catch (ex) {
    629          if (ex.name === "NS_BASE_STREAM_CLOSED") {
    630            // Empty files cause NS_BASE_STREAM_CLOSED exception.
    631            // If there was a real stream error, we would have already rejected above.
    632            resolve({
    633              content: "",
    634              contentType: "text/plain",
    635            });
    636            return;
    637          }
    638 
    639          reject(ex);
    640        }
    641        let source = NetUtil.readInputStreamToString(stream, available);
    642        stream.close();
    643 
    644        // We do our own BOM sniffing here because there's no convenient
    645        // implementation of the "decode" algorithm
    646        // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
    647        let bomCharset = null;
    648        if (
    649          available >= 3 &&
    650          source.codePointAt(0) == 0xef &&
    651          source.codePointAt(1) == 0xbb &&
    652          source.codePointAt(2) == 0xbf
    653        ) {
    654          bomCharset = "UTF-8";
    655          source = source.slice(3);
    656        } else if (
    657          available >= 2 &&
    658          source.codePointAt(0) == 0xfe &&
    659          source.codePointAt(1) == 0xff
    660        ) {
    661          bomCharset = "UTF-16BE";
    662          source = source.slice(2);
    663        } else if (
    664          available >= 2 &&
    665          source.codePointAt(0) == 0xff &&
    666          source.codePointAt(1) == 0xfe
    667        ) {
    668          bomCharset = "UTF-16LE";
    669          source = source.slice(2);
    670        }
    671 
    672        // If the channel or the caller has correct charset information, the
    673        // content will be decoded correctly. If we have to fall back to UTF-8 and
    674        // the guess is wrong, the conversion fails and convertToUnicode returns
    675        // the input unmodified. Essentially we try to decode the data as UTF-8
    676        // and if that fails, we use the locale specific default encoding. This is
    677        // the best we can do if the source does not provide charset info.
    678        let charset = bomCharset;
    679        if (!charset) {
    680          try {
    681            charset = channel.contentCharset;
    682          } catch (e) {
    683            // Accessing `contentCharset` on content served by a service worker in
    684            // non-e10s may throw.
    685          }
    686        }
    687        if (!charset) {
    688          charset = aOptions.charset || "UTF-8";
    689        }
    690        const unicodeSource = lazy.NetworkHelper.convertToUnicode(
    691          source,
    692          charset
    693        );
    694 
    695        // Look for any source map URL in the response.
    696        let sourceMapURL;
    697        if (request instanceof Ci.nsIHttpChannel) {
    698          try {
    699            sourceMapURL = request.getResponseHeader("SourceMap");
    700          } catch (e) {}
    701          if (!sourceMapURL) {
    702            try {
    703              sourceMapURL = request.getResponseHeader("X-SourceMap");
    704            } catch (e) {}
    705          }
    706        }
    707 
    708        resolve({
    709          content: unicodeSource,
    710          contentType: request.contentType,
    711          sourceMapURL,
    712        });
    713      } catch (ex) {
    714        reject(ex);
    715      }
    716    };
    717 
    718    // Open the channel
    719    try {
    720      NetUtil.asyncFetch(channel, onResponse);
    721    } catch (ex) {
    722      reject(ex);
    723    }
    724  });
    725 }
    726 
    727 /**
    728 * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
    729 *
    730 * @param {string} url - The URL to open a channel for.
    731 * @param {object} options - The options object passed to @method fetch.
    732 * @return {nsIChannel} - The newly created channel. Throws on failure.
    733 */
    734 function newChannelForURL(url, { policy, window, principal }) {
    735  const securityFlags =
    736    Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
    737 
    738  let uri;
    739  try {
    740    uri = Services.io.newURI(url);
    741  } catch (e) {
    742    // In the xpcshell tests, the script url is the absolute path of the test
    743    // file, which will make a malformed URI error be thrown. Add the file
    744    // scheme to see if it helps.
    745    uri = Services.io.newURI("file://" + url);
    746  }
    747 
    748  // In xpcshell tests on Windows, opening the channel
    749  // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't
    750  // supported by Windows, so we also need to handle that case here if
    751  // parsing the URL above doesn't throw.
    752  const handler = Services.io.getProtocolHandler(uri.scheme);
    753  if (
    754    handler instanceof Ci.nsIExternalProtocolHandler &&
    755    !handler.externalAppExistsForScheme(uri.scheme)
    756  ) {
    757    uri = Services.io.newURI("file://" + url);
    758  }
    759 
    760  const channelOptions = {
    761    contentPolicyType: policy,
    762    securityFlags,
    763    uri,
    764  };
    765 
    766  // Ensure that we have some contentPolicyType type set if one was
    767  // not provided.
    768  if (!channelOptions.contentPolicyType) {
    769    channelOptions.contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
    770  }
    771 
    772  // If a window is provided, always use it's document as the loadingNode.
    773  // This will provide the correct principal, origin attributes, service
    774  // worker controller, etc.
    775  if (window) {
    776    channelOptions.loadingNode = window.document;
    777  } else {
    778    // If a window is not provided, then we must set a loading principal.
    779 
    780    // If the caller did not provide a principal, then we use the URI
    781    // to create one.  Note, it's not clear what use cases require this
    782    // and it may not be correct.
    783    let prin = principal;
    784    if (!prin) {
    785      prin = Services.scriptSecurityManager.createContentPrincipal(uri, {});
    786    }
    787 
    788    channelOptions.loadingPrincipal = prin;
    789  }
    790 
    791  return NetUtil.newChannel(channelOptions);
    792 }
    793 
    794 // Fetch is defined differently depending on whether we are on the main thread
    795 // or a worker thread.
    796 if (this.isWorker) {
    797  // Services is not available in worker threads, nor is there any other way
    798  // to fetch a URL. We need to enlist the help from the main thread here, by
    799  // issuing an rpc request, to fetch the URL on our behalf.
    800  exports.fetch = function (url, options) {
    801    return rpc("fetch", url, options);
    802  };
    803 } else {
    804  exports.fetch = mainThreadFetch;
    805 }
    806 
    807 /**
    808 * Open the file at the given path for reading.
    809 *
    810 * @param {string} filePath
    811 *
    812 * @returns Promise<nsIInputStream>
    813 */
    814 exports.openFileStream = function (filePath) {
    815  return new Promise((resolve, reject) => {
    816    const uri = NetUtil.newURI(new lazy.FileUtils.File(filePath));
    817    NetUtil.asyncFetch(
    818      { uri, loadUsingSystemPrincipal: true },
    819      (stream, result) => {
    820        if (!Components.isSuccessCode(result)) {
    821          reject(new Error(`Could not open "${filePath}": result = ${result}`));
    822          return;
    823        }
    824 
    825        resolve(stream);
    826      }
    827    );
    828  });
    829 };
    830 
    831 /**
    832 * Save the given data to disk after asking the user where to do so.
    833 *
    834 * @param {Window} parentWindow
    835 *        The parent window to use to display the filepicker.
    836 * @param {UInt8Array} dataArray
    837 *        The data to write to the file.
    838 * @param {string} fileName
    839 *        The suggested filename.
    840 * @param {Array} filters
    841 *        An array of object of the following shape:
    842 *          - pattern: A pattern for accepted files (example: "*.js")
    843 *          - label: The label that will be displayed in the save file dialog.
    844 * @return {string | null}
    845 *        The path to the local saved file, if saved.
    846 */
    847 exports.saveAs = async function (
    848  parentWindow,
    849  dataArray,
    850  fileName = "",
    851  filters = []
    852 ) {
    853  let returnFile;
    854  try {
    855    returnFile = await exports.showSaveFileDialog(
    856      parentWindow,
    857      lazy.DownloadPaths.sanitize(fileName),
    858      filters
    859    );
    860  } catch (ex) {
    861    return null;
    862  }
    863 
    864  // Sanitize the filename again in case the user renamed the file to use a
    865  // vulnerable extension.
    866  returnFile.leafName = lazy.DownloadPaths.sanitize(returnFile.leafName);
    867 
    868  await IOUtils.write(returnFile.path, dataArray, {
    869    tmpPath: returnFile.path + ".tmp",
    870  });
    871 
    872  return returnFile.path;
    873 };
    874 
    875 /**
    876 * Show file picker and return the file user selected.
    877 *
    878 * @param {nsIWindow} parentWindow
    879 *        Optional parent window. If null the parent window of the file picker
    880 *        will be the window of the attached input element.
    881 * @param {string} suggestedFilename
    882 *        The suggested filename.
    883 * @param {Array} filters
    884 *        An array of object of the following shape:
    885 *          - pattern: A pattern for accepted files (example: "*.js")
    886 *          - label: The label that will be displayed in the save file dialog.
    887 * @return {Promise}
    888 *         A promise that is resolved after the file is selected by the file picker
    889 */
    890 exports.showSaveFileDialog = function (
    891  parentWindow,
    892  suggestedFilename,
    893  filters = []
    894 ) {
    895  const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    896 
    897  if (suggestedFilename) {
    898    fp.defaultString = suggestedFilename;
    899  }
    900 
    901  fp.init(parentWindow.browsingContext, null, fp.modeSave);
    902  if (Array.isArray(filters) && filters.length) {
    903    for (const { pattern, label } of filters) {
    904      fp.appendFilter(label, pattern);
    905    }
    906  } else {
    907    fp.appendFilters(fp.filterAll);
    908  }
    909 
    910  return new Promise((resolve, reject) => {
    911    fp.open(result => {
    912      if (result == Ci.nsIFilePicker.returnCancel) {
    913        reject();
    914      } else {
    915        resolve(fp.file);
    916      }
    917    });
    918  });
    919 };
    920 
    921 /*
    922 * All of the flags have been moved to a different module. Make sure
    923 * nobody is accessing them anymore, and don't write new code using
    924 * them. We can remove this code after a while.
    925 */
    926 function errorOnFlag(exports, name) {
    927  Object.defineProperty(exports, name, {
    928    get: () => {
    929      const msg =
    930        `Cannot get the flag ${name}. ` +
    931        `Use the "devtools/shared/flags" module instead`;
    932      console.error(msg);
    933      throw new Error(msg);
    934    },
    935    set: () => {
    936      const msg =
    937        `Cannot set the flag ${name}. ` +
    938        `Use the "devtools/shared/flags" module instead`;
    939      console.error(msg);
    940      throw new Error(msg);
    941    },
    942  });
    943 }
    944 
    945 errorOnFlag(exports, "testing");
    946 errorOnFlag(exports, "wantLogging");
    947 errorOnFlag(exports, "wantVerbose");
    948 
    949 // Calls the property with the given `name` on the given `object`, where
    950 // `name` is a string, and `object` a Debugger.Object instance.
    951 //
    952 // This function uses only the Debugger.Object API to call the property. It
    953 // avoids the use of unsafeDeference. This is useful for example in workers,
    954 // where unsafeDereference will return an opaque security wrapper to the
    955 // referent.
    956 function callPropertyOnObject(object, name, ...args) {
    957  // Find the property.
    958  let descriptor;
    959  let proto = object;
    960  do {
    961    descriptor = proto.getOwnPropertyDescriptor(name);
    962    if (descriptor !== undefined) {
    963      break;
    964    }
    965    proto = proto.proto;
    966  } while (proto !== null);
    967  if (descriptor === undefined) {
    968    throw new Error("No such property");
    969  }
    970  const value = descriptor.value;
    971  if (typeof value !== "object" || value === null || !("callable" in value)) {
    972    throw new Error("Not a callable object.");
    973  }
    974 
    975  if (value.script !== undefined) {
    976    throw new Error(
    977      "The property isn't a native function and will execute code in the debuggee"
    978    );
    979  }
    980 
    981  // Call the property.
    982  const result = value.call(object, ...args);
    983  if (result === null) {
    984    throw new Error("Code was terminated.");
    985  }
    986  if ("throw" in result) {
    987    throw result.throw;
    988  }
    989  return result.return;
    990 }
    991 
    992 exports.callPropertyOnObject = callPropertyOnObject;
    993 
    994 // Convert a Debugger.Object wrapping an iterator into an iterator in the
    995 // debugger's realm.
    996 function* makeDebuggeeIterator(object) {
    997  while (true) {
    998    const nextValue = callPropertyOnObject(object, "next");
    999    if (exports.getProperty(nextValue, "done")) {
   1000      break;
   1001    }
   1002    yield exports.getProperty(nextValue, "value");
   1003  }
   1004 }
   1005 
   1006 exports.makeDebuggeeIterator = makeDebuggeeIterator;
   1007 
   1008 /**
   1009 * Shared helper to retrieve the topmost window. This can be used to retrieve the chrome
   1010 * window embedding the DevTools frame.
   1011 */
   1012 function getTopWindow(win) {
   1013  return win.windowRoot ? win.windowRoot.ownerGlobal : win.top;
   1014 }
   1015 
   1016 exports.getTopWindow = getTopWindow;
   1017 
   1018 /**
   1019 * Check whether two objects are identical by performing
   1020 * a deep equality check on their properties and values.
   1021 * See toolkit/modules/ObjectUtils.jsm for implementation.
   1022 *
   1023 * @param {object} a
   1024 * @param {object} b
   1025 * @return {boolean}
   1026 */
   1027 exports.deepEqual = (a, b) => {
   1028  return lazy.ObjectUtils.deepEqual(a, b);
   1029 };
   1030 
   1031 function isWorkerDebuggerAlive(dbg) {
   1032  // Some workers are zombies. `isClosed` is false, but nothing works.
   1033  // `postMessage` is a noop, `addListener`'s `onClosed` doesn't work.
   1034  // (Ignore dbg without `window` as they aren't related to docShell
   1035  //  and probably do not suffer form this issue)
   1036  return !dbg.isClosed && (!dbg.window || dbg.window.docShell);
   1037 }
   1038 exports.isWorkerDebuggerAlive = isWorkerDebuggerAlive;