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 */
9 /* General utilities used throughout devtools. */
11 var flags = require("resource://devtools/shared/flags.js");
14 callFunctionWithAsyncStack,
15 } = require("resource://devtools/shared/platform/stack.js");
19 ChromeUtils.defineESModuleGetters(lazy, {
20 FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
22 "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
23 ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
26 // Native getters which are considered to be side effect free.
27 ChromeUtils.defineLazyGetter(lazy, "sideEffectFreeGetters", () => {
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)) {
37 map.get(n.name).push(n);
43 // Using this name lets the eslint plugin know about lazy defines in
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];
54 * Waits for the next tick in the event loop to execute a callback.
56 exports.executeSoon = function (fn) {
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();
66 callFunctionWithAsyncStack(fn, stack, "DevToolsUtils.executeSoon");
71 Services.tm.dispatchToMainThread({
72 run: exports.makeInfallible(executor),
78 * Similar to executeSoon, but enters microtask before executing the callback
79 * if this is called on the main thread.
81 exports.executeSoonWithMicroTask = function (fn) {
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();
91 callFunctionWithAsyncStack(
94 "DevToolsUtils.executeSoonWithMicroTask"
100 Services.tm.dispatchToMainThreadWithMicroTask({
101 run: exports.makeInfallible(executor),
107 * Waits for the next tick in the event loop.
110 * A promise that is resolved after the next tick in the event loop.
112 exports.waitForTick = function () {
113 return new Promise(resolve => {
114 exports.executeSoon(resolve);
119 * Waits for the specified amount of time to pass.
121 * @param number delay
122 * The amount of time to wait, in milliseconds.
124 * A promise that is resolved after the specified amount of time passes.
126 exports.waitForTime = function (delay) {
127 return new Promise(resolve => setTimeout(resolve, delay));
131 * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
132 * allows the lazy getter to be defined on a prototype and work correctly with
135 * @param Object object
136 * The prototype object to define the lazy getter on.
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.
143 exports.defineLazyPrototypeGetter = function (object, key, callback) {
144 Object.defineProperty(object, key, {
147 const value = callback.call(this);
149 Object.defineProperty(this, key, {
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 ⚠️
175 exports.getProperty = function (object, key, invokeUnsafeGetters = false) {
177 while (object && exports.isSafeDebuggerObject(object)) {
180 desc = object.getOwnPropertyDescriptor(key);
182 // The above can throw when the debuggee does not subsume the object's
183 // compartment, or for some WrappedNatives like Cu.Sandbox.
187 if ("value" in desc) {
190 // Call the getter if it's safe.
191 if (exports.hasSafeGetter(desc) || invokeUnsafeGetters === true) {
193 return desc.get.call(root).return;
195 // If anything goes wrong report the error and return undefined.
196 exports.reportException("getProperty", e);
201 object = object.proto;
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.
219 exports.unwrap = function unwrap(obj) {
220 // Check if `obj` has an opaque wrapper.
221 if (obj.class === "Opaque") {
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.
231 unwrapped = obj.unwrap();
236 // Check if further unwrapping is not possible.
237 if (!unwrapped || unwrapped === obj) {
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.
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) {
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) {
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) {
282 * Determines if a descriptor has a getter which doesn't call into JavaScript.
285 * The descriptor to check for a safe getter.
287 * Whether a safe getter was found.
289 exports.hasSafeGetter = function (desc) {
290 // Scripted functions that are CCWs will not appear scripted until after
293 fn = fn && exports.unwrap(fn);
297 if (!fn.callable || fn.class !== "Function") {
300 if (fn.script !== undefined) {
301 // This is scripted function.
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.
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.
314 // This includes privileged interfaces in addition to standard web APIs.
315 if (fn.isNativeGetterWithJitInfo()) {
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.
337 exports.isUnsafeGetter = function (object, key) {
338 while (object && exports.isSafeDebuggerObject(object)) {
341 desc = object.getOwnPropertyDescriptor(key);
343 // The above can throw when the debuggee does not subsume the object's
344 // compartment, or for some WrappedNatives like Cu.Sandbox.
348 if (Object.getOwnPropertyNames(desc).includes("get")) {
349 return !exports.hasSafeGetter(desc);
352 object = object.proto;
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.
366 * The object to check.
368 * True if it is safe to read properties from obj, or false otherwise.
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.
378 Cu.getGlobalForObject(obj) == Cu.getGlobalForObject(exports.isSafeJSObject)
380 // obj is not a cross-compartment wrapper.
384 // Xray wrappers protect against unintended code execution.
385 if (Cu.isXrayWrapper(obj)) {
389 // If there aren't Xrays, only allow chrome objects.
390 const principal = Cu.getObjectPrincipal(obj);
391 if (!principal.isSystemPrincipal) {
395 // Scripted proxy objects without Xrays can run their proxy traps.
396 if (Cu.isProxy(obj)) {
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)) {
407 // Allow non-problematic chrome objects.
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.
416 exports.dumpn = function (str) {
417 if (flags.wantLogging) {
418 dump("DBG-SERVER: " + str + "\n");
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.
428 exports.dumpv = function (msg) {
429 if (flags.wantVerbose) {
435 * Defines a getter on a specified object that will be created upon first use.
438 * The object to define the lazy getter on.
440 * The name of the getter to define on object.
442 * A function that returns what the getter should return. This will
443 * only ever be called once.
445 exports.defineLazyGetter = function (object, name, lambda) {
446 Object.defineProperty(object, name, {
449 object[name] = lambda.apply(object);
457 DevToolsUtils.defineLazyGetter(this, "AppConstants", () => {
461 return ChromeUtils.importESModule(
462 "resource://gre/modules/AppConstants.sys.mjs"
467 * No operation. The empty function.
469 exports.noop = function () {};
471 let assertionFailureCount = 0;
473 Object.defineProperty(exports, "assertionFailureCount", {
475 return assertionFailureCount;
479 function reallyAssert(condition, message) {
481 assertionFailureCount++;
482 const err = new Error("Assertion failure: " + message);
483 exports.reportException("DevToolsUtils.assert", err);
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.
503 Object.defineProperty(exports, "assert", {
505 AppConstants.DEBUG_JS_MODULES || flags.testing
510 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
511 return ChromeUtils.importESModule("resource://gre/modules/NetUtil.sys.mjs")
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 * - cacheKey: when loading from cache, use this key to retrieve a cache
532 * specific to a given SHEntry. (Allows loading POST
533 * requests from cache)
534 * @returns Promise that resolves with an object with the following members on
536 * - content: the document at that URL, as a string,
537 * - contentType: the content type of the document
539 * If an error occurs, the promise is rejected with that error.
541 * XXX: It may be better to use nsITraceableChannel to get to the sources
542 * without relying on caching when we can (not for eval, etc.):
543 * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
545 function mainThreadFetch(
549 policy: Ci.nsIContentPolicy.TYPE_OTHER,
556 return new Promise((resolve, reject) => {
558 const url = urlIn.split(" -> ").pop();
561 channel = newChannelForURL(url, aOptions);
567 channel.loadInfo.isInDevToolsContext = true;
569 // Set the channel options.
570 channel.loadFlags = aOptions.loadFromCache
571 ? channel.LOAD_FROM_CACHE
572 : channel.LOAD_BYPASS_CACHE;
574 if (aOptions.loadFromCache && channel instanceof Ci.nsICacheInfoChannel) {
575 // If DevTools intents to load the content from the cache,
576 // we make the LOAD_FROM_CACHE flag preferred over LOAD_BYPASS_CACHE.
577 channel.preferCacheLoadOverBypass = true;
579 // When loading from cache, the cacheKey allows us to target a specific
580 // SHEntry and offer ways to restore POST requests from cache.
581 if (aOptions.cacheKey != 0) {
582 channel.cacheKey = aOptions.cacheKey;
586 if (aOptions.window) {
587 // Respect private browsing.
588 channel.loadGroup = aOptions.window.docShell.QueryInterface(
593 // eslint-disable-next-line complexity
594 const onResponse = (stream, status, request) => {
595 if (!Components.isSuccessCode(status)) {
596 reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
601 // We cannot use NetUtil to do the charset conversion as if charset
602 // information is not available and our default guess is wrong the method
603 // might fail and we lose the stream data. This means we can't fall back
604 // to using the locale default encoding (bug 1181345).
606 // Read and decode the data according to the locale default encoding.
610 available = stream.available();
612 if (ex.name === "NS_BASE_STREAM_CLOSED") {
613 // Empty files cause NS_BASE_STREAM_CLOSED exception.
614 // If there was a real stream error, we would have already rejected above.
617 contentType: "text/plain",
624 let source = NetUtil.readInputStreamToString(stream, available);
627 // We do our own BOM sniffing here because there's no convenient
628 // implementation of the "decode" algorithm
629 // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
630 let bomCharset = null;
633 source.codePointAt(0) == 0xef &&
634 source.codePointAt(1) == 0xbb &&
635 source.codePointAt(2) == 0xbf
637 bomCharset = "UTF-8";
638 source = source.slice(3);
641 source.codePointAt(0) == 0xfe &&
642 source.codePointAt(1) == 0xff
644 bomCharset = "UTF-16BE";
645 source = source.slice(2);
648 source.codePointAt(0) == 0xff &&
649 source.codePointAt(1) == 0xfe
651 bomCharset = "UTF-16LE";
652 source = source.slice(2);
655 // If the channel or the caller has correct charset information, the
656 // content will be decoded correctly. If we have to fall back to UTF-8 and
657 // the guess is wrong, the conversion fails and convertToUnicode returns
658 // the input unmodified. Essentially we try to decode the data as UTF-8
659 // and if that fails, we use the locale specific default encoding. This is
660 // the best we can do if the source does not provide charset info.
661 let charset = bomCharset;
664 charset = channel.contentCharset;
666 // Accessing `contentCharset` on content served by a service worker in
667 // non-e10s may throw.
671 charset = aOptions.charset || "UTF-8";
673 const unicodeSource = lazy.NetworkHelper.convertToUnicode(
678 // Look for any source map URL in the response.
680 if (request instanceof Ci.nsIHttpChannel) {
682 sourceMapURL = request.getResponseHeader("SourceMap");
686 sourceMapURL = request.getResponseHeader("X-SourceMap");
692 content: unicodeSource,
693 contentType: request.contentType,
703 NetUtil.asyncFetch(channel, onResponse);
711 * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
713 * @param {String} url - The URL to open a channel for.
714 * @param {Object} options - The options object passed to @method fetch.
715 * @return {nsIChannel} - The newly created channel. Throws on failure.
717 function newChannelForURL(
719 { policy, window, principal },
722 const securityFlags =
723 Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
727 uri = Services.io.newURI(url);
729 // In the xpcshell tests, the script url is the absolute path of the test
730 // file, which will make a malformed URI error be thrown. Add the file
731 // scheme to see if it helps.
732 uri = Services.io.newURI("file://" + url);
734 const channelOptions = {
735 contentPolicyType: policy,
740 // Ensure that we have some contentPolicyType type set if one was
742 if (!channelOptions.contentPolicyType) {
743 channelOptions.contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
746 // If a window is provided, always use it's document as the loadingNode.
747 // This will provide the correct principal, origin attributes, service
748 // worker controller, etc.
750 channelOptions.loadingNode = window.document;
752 // If a window is not provided, then we must set a loading principal.
754 // If the caller did not provide a principal, then we use the URI
755 // to create one. Note, it's not clear what use cases require this
756 // and it may not be correct.
757 let prin = principal;
759 prin = Services.scriptSecurityManager.createContentPrincipal(uri, {});
762 channelOptions.loadingPrincipal = prin;
766 return NetUtil.newChannel(channelOptions);
768 // Don't infinitely recurse if newChannel keeps throwing.
773 // In xpcshell tests on Windows, nsExternalProtocolHandler::NewChannel()
774 // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't
775 // supported by Windows, so we also need to handle the exception here if
776 // parsing the URL above doesn't throw.
777 return newChannelForURL(
779 { policy, window, principal },
785 // Fetch is defined differently depending on whether we are on the main thread
786 // or a worker thread.
788 // Services is not available in worker threads, nor is there any other way
789 // to fetch a URL. We need to enlist the help from the main thread here, by
790 // issuing an rpc request, to fetch the URL on our behalf.
791 exports.fetch = function (url, options) {
792 return rpc("fetch", url, options);
795 exports.fetch = mainThreadFetch;
799 * Open the file at the given path for reading.
801 * @param {String} filePath
803 * @returns Promise<nsIInputStream>
805 exports.openFileStream = function (filePath) {
806 return new Promise((resolve, reject) => {
807 const uri = NetUtil.newURI(new lazy.FileUtils.File(filePath));
809 { uri, loadUsingSystemPrincipal: true },
810 (stream, result) => {
811 if (!Components.isSuccessCode(result)) {
812 reject(new Error(`Could not open "${filePath}": result = ${result}`));
823 * Save the given data to disk after asking the user where to do so.
825 * @param {Window} parentWindow
826 * The parent window to use to display the filepicker.
827 * @param {UInt8Array} dataArray
828 * The data to write to the file.
829 * @param {String} fileName
830 * The suggested filename.
831 * @param {Array} filters
832 * An array of object of the following shape:
833 * - pattern: A pattern for accepted files (example: "*.js")
834 * - label: The label that will be displayed in the save file dialog.
835 * @return {String|null}
836 * The path to the local saved file, if saved.
838 exports.saveAs = async function (
846 returnFile = await exports.showSaveFileDialog(
855 await IOUtils.write(returnFile.path, dataArray, {
856 tmpPath: returnFile.path + ".tmp",
859 return returnFile.path;
863 * Show file picker and return the file user selected.
865 * @param {nsIWindow} parentWindow
866 * Optional parent window. If null the parent window of the file picker
867 * will be the window of the attached input element.
868 * @param {String} suggestedFilename
869 * The suggested filename.
870 * @param {Array} filters
871 * An array of object of the following shape:
872 * - pattern: A pattern for accepted files (example: "*.js")
873 * - label: The label that will be displayed in the save file dialog.
875 * A promise that is resolved after the file is selected by the file picker
877 exports.showSaveFileDialog = function (
882 const fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
884 if (suggestedFilename) {
885 fp.defaultString = suggestedFilename;
888 fp.init(parentWindow, null, fp.modeSave);
889 if (Array.isArray(filters) && filters.length) {
890 for (const { pattern, label } of filters) {
891 fp.appendFilter(label, pattern);
894 fp.appendFilters(fp.filterAll);
897 return new Promise((resolve, reject) => {
899 if (result == Ci.nsIFilePicker.returnCancel) {
909 * All of the flags have been moved to a different module. Make sure
910 * nobody is accessing them anymore, and don't write new code using
911 * them. We can remove this code after a while.
913 function errorOnFlag(exports, name) {
914 Object.defineProperty(exports, name, {
917 `Cannot get the flag ${name}. ` +
918 `Use the "devtools/shared/flags" module instead`;
920 throw new Error(msg);
924 `Cannot set the flag ${name}. ` +
925 `Use the "devtools/shared/flags" module instead`;
927 throw new Error(msg);
932 errorOnFlag(exports, "testing");
933 errorOnFlag(exports, "wantLogging");
934 errorOnFlag(exports, "wantVerbose");
936 // Calls the property with the given `name` on the given `object`, where
937 // `name` is a string, and `object` a Debugger.Object instance.
939 // This function uses only the Debugger.Object API to call the property. It
940 // avoids the use of unsafeDeference. This is useful for example in workers,
941 // where unsafeDereference will return an opaque security wrapper to the
943 function callPropertyOnObject(object, name, ...args) {
944 // Find the property.
948 descriptor = proto.getOwnPropertyDescriptor(name);
949 if (descriptor !== undefined) {
953 } while (proto !== null);
954 if (descriptor === undefined) {
955 throw new Error("No such property");
957 const value = descriptor.value;
958 if (typeof value !== "object" || value === null || !("callable" in value)) {
959 throw new Error("Not a callable object.");
962 // Call the property.
963 const result = value.call(object, ...args);
964 if (result === null) {
965 throw new Error("Code was terminated.");
967 if ("throw" in result) {
970 return result.return;
973 exports.callPropertyOnObject = callPropertyOnObject;
975 // Convert a Debugger.Object wrapping an iterator into an iterator in the
977 function* makeDebuggeeIterator(object) {
979 const nextValue = callPropertyOnObject(object, "next");
980 if (exports.getProperty(nextValue, "done")) {
983 yield exports.getProperty(nextValue, "value");
987 exports.makeDebuggeeIterator = makeDebuggeeIterator;
990 * Shared helper to retrieve the topmost window. This can be used to retrieve the chrome
991 * window embedding the DevTools frame.
993 function getTopWindow(win) {
994 return win.windowRoot ? win.windowRoot.ownerGlobal : win.top;
997 exports.getTopWindow = getTopWindow;
1000 * Check whether two objects are identical by performing
1001 * a deep equality check on their properties and values.
1002 * See toolkit/modules/ObjectUtils.jsm for implementation.
1008 exports.deepEqual = (a, b) => {
1009 return lazy.ObjectUtils.deepEqual(a, b);
1012 function isWorkerDebuggerAlive(dbg) {
1013 // Some workers are zombies. `isClosed` is false, but nothing works.
1014 // `postMessage` is a noop, `addListener`'s `onClosed` doesn't work.
1015 // (Ignore dbg without `window` as they aren't related to docShell
1016 // and probably do not suffer form this issue)
1017 return !dbg.isClosed && (!dbg.window || dbg.window.docShell);
1019 exports.isWorkerDebuggerAlive = isWorkerDebuggerAlive;