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