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");
20 ChromeUtils
.defineESModuleGetters(
23 FileUtils
: "resource://gre/modules/FileUtils.sys.mjs",
25 "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
26 ObjectUtils
: "resource://gre/modules/ObjectUtils.sys.mjs",
28 { global
: "contextual" }
32 // Native getters which are considered to be side effect free.
33 ChromeUtils
.defineLazyGetter(lazy
, "sideEffectFreeGetters", () => {
36 } = require("resource://devtools/server/actors/webconsole/eager-ecma-allowlist.js");
38 const map
= new Map();
39 for (const n
of getters
) {
40 if (!map
.has(n
.name
)) {
43 map
.get(n
.name
).push(n
);
49 // Using this name lets the eslint plugin know about lazy defines in
51 var DevToolsUtils
= exports
;
53 // Re-export the thread-safe utils.
54 const ThreadSafeDevToolsUtils
= require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
55 for (const key
of Object
.keys(ThreadSafeDevToolsUtils
)) {
56 exports
[key
] = ThreadSafeDevToolsUtils
[key
];
60 * Waits for the next tick in the event loop to execute a callback.
62 exports
.executeSoon = function (fn
) {
67 // Only enable async stack reporting when DEBUG_JS_MODULES is set
68 // (customized local builds) to avoid a performance penalty.
69 if (AppConstants
.DEBUG_JS_MODULES
|| flags
.testing
) {
70 const stack
= getStack();
72 callFunctionWithAsyncStack(fn
, stack
, "DevToolsUtils.executeSoon");
77 Services
.tm
.dispatchToMainThread({
78 run
: exports
.makeInfallible(executor
),
84 * Similar to executeSoon, but enters microtask before executing the callback
85 * if this is called on the main thread.
87 exports
.executeSoonWithMicroTask = function (fn
) {
92 // Only enable async stack reporting when DEBUG_JS_MODULES is set
93 // (customized local builds) to avoid a performance penalty.
94 if (AppConstants
.DEBUG_JS_MODULES
|| flags
.testing
) {
95 const stack
= getStack();
97 callFunctionWithAsyncStack(
100 "DevToolsUtils.executeSoonWithMicroTask"
106 Services
.tm
.dispatchToMainThreadWithMicroTask({
107 run
: exports
.makeInfallible(executor
),
113 * Waits for the next tick in the event loop.
116 * A promise that is resolved after the next tick in the event loop.
118 exports
.waitForTick = function () {
119 return new Promise(resolve
=> {
120 exports
.executeSoon(resolve
);
125 * Waits for the specified amount of time to pass.
127 * @param number delay
128 * The amount of time to wait, in milliseconds.
130 * A promise that is resolved after the specified amount of time passes.
132 exports
.waitForTime = function (delay
) {
133 return new Promise(resolve
=> setTimeout(resolve
, delay
));
137 * Like ChromeUtils.defineLazyGetter, but with a |this| sensitive getter that
138 * allows the lazy getter to be defined on a prototype and work correctly with
141 * @param Object object
142 * The prototype object to define the lazy getter on.
144 * The key to define the lazy getter on.
145 * @param Function callback
146 * The callback that will be called to determine the value. Will be
147 * called with the |this| value of the current instance.
149 exports
.defineLazyPrototypeGetter = function (object
, key
, callback
) {
150 Object
.defineProperty(object
, key
, {
153 const value
= callback
.call(this);
155 Object
.defineProperty(this, key
, {
167 * Safely get the property value from a Debugger.Object for a given key. Walks
168 * the prototype chain until the property is found.
170 * @param {Debugger.Object} object
171 * The Debugger.Object to get the value from.
172 * @param {String} key
173 * The key to look for.
174 * @param {Boolean} invokeUnsafeGetter (defaults to false).
175 * Optional boolean to indicate if the function should execute unsafe getter
176 * in order to retrieve its result's properties.
177 * ⚠️ This should be set to true *ONLY* on user action as it may cause side-effects
178 * in the content page ⚠️
181 exports
.getProperty = function (object
, key
, invokeUnsafeGetters
= false) {
183 while (object
&& exports
.isSafeDebuggerObject(object
)) {
186 desc
= object
.getOwnPropertyDescriptor(key
);
188 // The above can throw when the debuggee does not subsume the object's
189 // compartment, or for some WrappedNatives like Cu.Sandbox.
193 if ("value" in desc
) {
196 // Call the getter if it's safe.
197 if (exports
.hasSafeGetter(desc
) || invokeUnsafeGetters
=== true) {
199 return desc
.get.call(root
).return;
201 // If anything goes wrong report the error and return undefined.
202 exports
.reportException("getProperty", e
);
207 object
= object
.proto
;
213 * Removes all the non-opaque security wrappers of a debuggee object.
215 * @param obj Debugger.Object
216 * The debuggee object to be unwrapped.
217 * @return Debugger.Object|null|undefined
218 * - If the object has no wrapper, the same `obj` is returned. Note DeadObject
219 * objects belong to this case.
220 * - Otherwise, if the debuggee doesn't subsume object's compartment, returns `null`.
221 * - Otherwise, if the object belongs to an invisible-to-debugger compartment,
222 * returns `undefined`.
223 * - Otherwise, returns the unwrapped object.
225 exports
.unwrap
= function unwrap(obj
) {
226 // Check if `obj` has an opaque wrapper.
227 if (obj
.class === "Opaque") {
231 // Attempt to unwrap via `obj.unwrap()`. Note that:
232 // - This will return `null` if the debuggee does not subsume object's compartment.
233 // - This will throw if the object belongs to an invisible-to-debugger compartment.
234 // - This will return `obj` if there is no wrapper.
237 unwrapped
= obj
.unwrap();
242 // Check if further unwrapping is not possible.
243 if (!unwrapped
|| unwrapped
=== obj
) {
247 // Recursively remove additional security wrappers.
248 return unwrap(unwrapped
);
252 * Checks whether a debuggee object is safe. Unsafe objects may run proxy traps or throw
253 * when using `proto`, `isExtensible`, `isFrozen` or `isSealed`. Note that safe objects
254 * may still throw when calling `getOwnPropertyNames`, `getOwnPropertyDescriptor`, etc.
255 * Also note DeadObject objects are considered safe.
257 * @param obj Debugger.Object
258 * The debuggee object to be checked.
261 exports
.isSafeDebuggerObject = function (obj
) {
262 const unwrapped
= exports
.unwrap(obj
);
264 // Objects belonging to an invisible-to-debugger compartment might be proxies,
265 // so just in case consider them unsafe.
266 if (unwrapped
=== undefined) {
270 // If the debuggee does not subsume the object's compartment, most properties won't
271 // be accessible. Cross-origin Window and Location objects might expose some, though.
272 // Therefore, it must be considered safe. Note that proxy objects have fully opaque
273 // security wrappers, so proxy traps won't run in this case.
274 if (unwrapped
=== null) {
278 // Proxy objects can run traps when accessed. `isProxy` getter is called on `unwrapped`
279 // instead of on `obj` in order to detect proxies behind transparent wrappers.
280 if (unwrapped
.isProxy
) {
288 * Determines if a descriptor has a getter which doesn't call into JavaScript.
291 * The descriptor to check for a safe getter.
293 * Whether a safe getter was found.
295 exports
.hasSafeGetter = function (desc
) {
296 // Scripted functions that are CCWs will not appear scripted until after
299 fn
= fn
&& exports
.unwrap(fn
);
303 if (!fn
.callable
|| fn
.class !== "Function") {
306 if (fn
.script
!== undefined) {
307 // This is scripted function.
311 // This is a getter with native function.
313 // We assume all DOM getters have no major side effect, and they are
314 // eagerly-evaluateable.
316 // JitInfo is used only by methods/accessors in WebIDL, and being
317 // "a getter with JitInfo" can be used as a condition to check if given
318 // function is DOM getter.
320 // This includes privileged interfaces in addition to standard web APIs.
321 if (fn
.isNativeGetterWithJitInfo()) {
325 // Apply explicit allowlist.
326 const natives
= lazy
.sideEffectFreeGetters
.get(fn
.name
);
327 return natives
&& natives
.some(n
=> fn
.isSameNative(n
));
331 * Check that the property value from a Debugger.Object for a given key is an unsafe
332 * getter or not. Walks the prototype chain until the property is found.
334 * @param {Debugger.Object} object
335 * The Debugger.Object to check on.
336 * @param {String} key
337 * The key to look for.
338 * @param {Boolean} invokeUnsafeGetter (defaults to false).
339 * Optional boolean to indicate if the function should execute unsafe getter
340 * in order to retrieve its result's properties.
343 exports
.isUnsafeGetter = function (object
, key
) {
344 while (object
&& exports
.isSafeDebuggerObject(object
)) {
347 desc
= object
.getOwnPropertyDescriptor(key
);
349 // The above can throw when the debuggee does not subsume the object's
350 // compartment, or for some WrappedNatives like Cu.Sandbox.
354 if (Object
.getOwnPropertyNames(desc
).includes("get")) {
355 return !exports
.hasSafeGetter(desc
);
358 object
= object
.proto
;
365 * Check if it is safe to read properties and execute methods from the given JS
366 * object. Safety is defined as being protected from unintended code execution
367 * from content scripts (or cross-compartment code).
369 * See bugs 945920 and 946752 for discussion.
372 * The object to check.
374 * True if it is safe to read properties from obj, or false otherwise.
376 exports
.isSafeJSObject = function (obj
) {
377 // If we are running on a worker thread, Cu is not available. In this case,
378 // we always return false, just to be on the safe side.
384 Cu
.getGlobalForObject(obj
) == Cu
.getGlobalForObject(exports
.isSafeJSObject
)
386 // obj is not a cross-compartment wrapper.
390 // Xray wrappers protect against unintended code execution.
391 if (Cu
.isXrayWrapper(obj
)) {
395 // If there aren't Xrays, only allow chrome objects.
396 const principal
= Cu
.getObjectPrincipal(obj
);
397 if (!principal
.isSystemPrincipal
) {
401 // Scripted proxy objects without Xrays can run their proxy traps.
402 if (Cu
.isProxy(obj
)) {
406 // Even if `obj` looks safe, an unsafe object in its prototype chain may still
407 // run unintended code, e.g. when using the `instanceof` operator.
408 const proto
= Object
.getPrototypeOf(obj
);
409 if (proto
&& !exports
.isSafeJSObject(proto
)) {
413 // Allow non-problematic chrome objects.
418 * Dump with newline - This is a logging function that will only output when
419 * the preference "devtools.debugger.log" is set to true. Typically it is used
420 * for logging the remote debugging protocol calls.
422 exports
.dumpn = function (str
) {
423 if (flags
.wantLogging
) {
424 dump("DBG-SERVER: " + str
+ "\n");
429 * Dump verbose - This is a verbose logger for low-level tracing, that is typically
430 * used to provide information about the remote debugging protocol's transport
431 * mechanisms. The logging can be enabled by changing the preferences
432 * "devtools.debugger.log" and "devtools.debugger.log.verbose" to true.
434 exports
.dumpv = function (msg
) {
435 if (flags
.wantVerbose
) {
441 * Defines a getter on a specified object that will be created upon first use.
444 * The object to define the lazy getter on.
446 * The name of the getter to define on object.
448 * A function that returns what the getter should return. This will
449 * only ever be called once.
451 exports
.defineLazyGetter = function (object
, name
, lambda
) {
452 Object
.defineProperty(object
, name
, {
455 object
[name
] = lambda
.apply(object
);
463 DevToolsUtils
.defineLazyGetter(this, "AppConstants", () => {
467 return ChromeUtils
.importESModule(
468 "resource://gre/modules/AppConstants.sys.mjs",
469 { global
: "contextual" }
474 * No operation. The empty function.
476 exports
.noop = function () {};
478 let assertionFailureCount
= 0;
480 Object
.defineProperty(exports
, "assertionFailureCount", {
482 return assertionFailureCount
;
486 function reallyAssert(condition
, message
) {
488 assertionFailureCount
++;
489 const err
= new Error("Assertion failure: " + message
);
490 exports
.reportException("DevToolsUtils.assert", err
);
496 * DevToolsUtils.assert(condition, message)
498 * @param Boolean condition
499 * @param String message
501 * Assertions are enabled when any of the following are true:
502 * - This is a DEBUG_JS_MODULES build
503 * - flags.testing is set to true
505 * If assertions are enabled, then `condition` is checked and if false-y, the
506 * assertion failure is logged and then an error is thrown.
508 * If assertions are not enabled, then this function is a no-op.
510 Object
.defineProperty(exports
, "assert", {
512 AppConstants
.DEBUG_JS_MODULES
|| flags
.testing
517 DevToolsUtils
.defineLazyGetter(this, "NetUtil", () => {
518 return ChromeUtils
.importESModule("resource://gre/modules/NetUtil.sys.mjs", {
519 global
: "contextual",
524 * Performs a request to load the desired URL and returns a promise.
526 * @param urlIn String
527 * The URL we will request.
528 * @param aOptions Object
529 * An object with the following optional properties:
530 * - loadFromCache: if false, will bypass the cache and
531 * always load fresh from the network (default: true)
532 * - policy: the nsIContentPolicy type to apply when fetching the URL
533 * (only works when loading from system principal)
534 * - window: the window to get the loadGroup from
535 * - charset: the charset to use if the channel doesn't provide one
536 * - principal: the principal to use, if omitted, the request is loaded
537 * with a content principal corresponding to the url being
538 * loaded, using the origin attributes of the window, if any.
539 * - headers: extra headers
540 * - cacheKey: when loading from cache, use this key to retrieve a cache
541 * specific to a given SHEntry. (Allows loading POST
542 * requests from cache)
543 * @returns Promise that resolves with an object with the following members on
545 * - content: the document at that URL, as a string,
546 * - contentType: the content type of the document
548 * If an error occurs, the promise is rejected with that error.
550 * XXX: It may be better to use nsITraceableChannel to get to the sources
551 * without relying on caching when we can (not for eval, etc.):
552 * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
554 function mainThreadFetch(
558 policy
: Ci
.nsIContentPolicy
.TYPE_OTHER
,
566 return new Promise((resolve
, reject
) => {
568 const url
= urlIn
.split(" -> ").pop();
571 channel
= newChannelForURL(url
, aOptions
);
577 channel
.loadInfo
.isInDevToolsContext
= true;
579 // Set the channel options.
580 channel
.loadFlags
= aOptions
.loadFromCache
581 ? channel
.LOAD_FROM_CACHE
582 : channel
.LOAD_BYPASS_CACHE
;
584 if (aOptions
.loadFromCache
&& channel
instanceof Ci
.nsICacheInfoChannel
) {
585 // If DevTools intents to load the content from the cache,
586 // we make the LOAD_FROM_CACHE flag preferred over LOAD_BYPASS_CACHE.
587 channel
.preferCacheLoadOverBypass
= true;
589 // When loading from cache, the cacheKey allows us to target a specific
590 // SHEntry and offer ways to restore POST requests from cache.
591 if (aOptions
.cacheKey
!= 0) {
592 channel
.cacheKey
= aOptions
.cacheKey
;
596 if (aOptions
.headers
&& channel
instanceof Ci
.nsIHttpChannel
) {
597 for (const h
in aOptions
.headers
) {
598 channel
.setRequestHeader(h
, aOptions
.headers
[h
], /* aMerge = */ false);
602 if (aOptions
.window
) {
603 // Respect private browsing.
604 channel
.loadGroup
= aOptions
.window
.docShell
.QueryInterface(
609 // eslint-disable-next-line complexity
610 const onResponse
= (stream
, status
, request
) => {
611 if (!Components
.isSuccessCode(status
)) {
612 reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
617 // We cannot use NetUtil to do the charset conversion as if charset
618 // information is not available and our default guess is wrong the method
619 // might fail and we lose the stream data. This means we can't fall back
620 // to using the locale default encoding (bug 1181345).
622 // Read and decode the data according to the locale default encoding.
626 available
= stream
.available();
628 if (ex
.name
=== "NS_BASE_STREAM_CLOSED") {
629 // Empty files cause NS_BASE_STREAM_CLOSED exception.
630 // If there was a real stream error, we would have already rejected above.
633 contentType
: "text/plain",
640 let source
= NetUtil
.readInputStreamToString(stream
, available
);
643 // We do our own BOM sniffing here because there's no convenient
644 // implementation of the "decode" algorithm
645 // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
646 let bomCharset
= null;
649 source
.codePointAt(0) == 0xef &&
650 source
.codePointAt(1) == 0xbb &&
651 source
.codePointAt(2) == 0xbf
653 bomCharset
= "UTF-8";
654 source
= source
.slice(3);
657 source
.codePointAt(0) == 0xfe &&
658 source
.codePointAt(1) == 0xff
660 bomCharset
= "UTF-16BE";
661 source
= source
.slice(2);
664 source
.codePointAt(0) == 0xff &&
665 source
.codePointAt(1) == 0xfe
667 bomCharset
= "UTF-16LE";
668 source
= source
.slice(2);
671 // If the channel or the caller has correct charset information, the
672 // content will be decoded correctly. If we have to fall back to UTF-8 and
673 // the guess is wrong, the conversion fails and convertToUnicode returns
674 // the input unmodified. Essentially we try to decode the data as UTF-8
675 // and if that fails, we use the locale specific default encoding. This is
676 // the best we can do if the source does not provide charset info.
677 let charset
= bomCharset
;
680 charset
= channel
.contentCharset
;
682 // Accessing `contentCharset` on content served by a service worker in
683 // non-e10s may throw.
687 charset
= aOptions
.charset
|| "UTF-8";
689 const unicodeSource
= lazy
.NetworkHelper
.convertToUnicode(
694 // Look for any source map URL in the response.
696 if (request
instanceof Ci
.nsIHttpChannel
) {
698 sourceMapURL
= request
.getResponseHeader("SourceMap");
702 sourceMapURL
= request
.getResponseHeader("X-SourceMap");
708 content
: unicodeSource
,
709 contentType
: request
.contentType
,
719 NetUtil
.asyncFetch(channel
, onResponse
);
727 * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
729 * @param {String} url - The URL to open a channel for.
730 * @param {Object} options - The options object passed to @method fetch.
731 * @return {nsIChannel} - The newly created channel. Throws on failure.
733 function newChannelForURL(url
, { policy
, window
, principal
}) {
734 const securityFlags
=
735 Ci
.nsILoadInfo
.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
;
739 uri
= Services
.io
.newURI(url
);
741 // In the xpcshell tests, the script url is the absolute path of the test
742 // file, which will make a malformed URI error be thrown. Add the file
743 // scheme to see if it helps.
744 uri
= Services
.io
.newURI("file://" + url
);
747 // In xpcshell tests on Windows, opening the channel
748 // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't
749 // supported by Windows, so we also need to handle that case here if
750 // parsing the URL above doesn't throw.
751 const handler
= Services
.io
.getProtocolHandler(uri
.scheme
);
753 handler
instanceof Ci
.nsIExternalProtocolHandler
&&
754 !handler
.externalAppExistsForScheme(uri
.scheme
)
756 uri
= Services
.io
.newURI("file://" + url
);
759 const channelOptions
= {
760 contentPolicyType
: policy
,
765 // Ensure that we have some contentPolicyType type set if one was
767 if (!channelOptions
.contentPolicyType
) {
768 channelOptions
.contentPolicyType
= Ci
.nsIContentPolicy
.TYPE_OTHER
;
771 // If a window is provided, always use it's document as the loadingNode.
772 // This will provide the correct principal, origin attributes, service
773 // worker controller, etc.
775 channelOptions
.loadingNode
= window
.document
;
777 // If a window is not provided, then we must set a loading principal.
779 // If the caller did not provide a principal, then we use the URI
780 // to create one. Note, it's not clear what use cases require this
781 // and it may not be correct.
782 let prin
= principal
;
784 prin
= Services
.scriptSecurityManager
.createContentPrincipal(uri
, {});
787 channelOptions
.loadingPrincipal
= prin
;
790 return NetUtil
.newChannel(channelOptions
);
793 // Fetch is defined differently depending on whether we are on the main thread
794 // or a worker thread.
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
);
803 exports
.fetch
= mainThreadFetch
;
807 * Open the file at the given path for reading.
809 * @param {String} filePath
811 * @returns Promise<nsIInputStream>
813 exports
.openFileStream = function (filePath
) {
814 return new Promise((resolve
, reject
) => {
815 const uri
= NetUtil
.newURI(new lazy
.FileUtils
.File(filePath
));
817 { uri
, loadUsingSystemPrincipal
: true },
818 (stream
, result
) => {
819 if (!Components
.isSuccessCode(result
)) {
820 reject(new Error(`Could not open "${filePath}": result = ${result}`));
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.
846 exports
.saveAs
= async
function (
854 returnFile
= await exports
.showSaveFileDialog(
863 await IOUtils
.write(returnFile
.path
, dataArray
, {
864 tmpPath
: returnFile
.path
+ ".tmp",
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.
883 * A promise that is resolved after the file is selected by the file picker
885 exports
.showSaveFileDialog = function (
890 const fp
= Cc
["@mozilla.org/filepicker;1"].createInstance(Ci
.nsIFilePicker
);
892 if (suggestedFilename
) {
893 fp
.defaultString
= suggestedFilename
;
896 fp
.init(parentWindow
.browsingContext
, null, fp
.modeSave
);
897 if (Array
.isArray(filters
) && filters
.length
) {
898 for (const { pattern
, label
} of filters
) {
899 fp
.appendFilter(label
, pattern
);
902 fp
.appendFilters(fp
.filterAll
);
905 return new Promise((resolve
, reject
) => {
907 if (result
== Ci
.nsIFilePicker
.returnCancel
) {
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.
921 function errorOnFlag(exports
, name
) {
922 Object
.defineProperty(exports
, name
, {
925 `Cannot get the flag ${name}. ` +
926 `Use the "devtools/shared/flags" module instead`;
928 throw new Error(msg
);
932 `Cannot set the flag ${name}. ` +
933 `Use the "devtools/shared/flags" module instead`;
935 throw new Error(msg
);
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
951 function callPropertyOnObject(object
, name
, ...args
) {
952 // Find the property.
956 descriptor
= proto
.getOwnPropertyDescriptor(name
);
957 if (descriptor
!== undefined) {
961 } while (proto
!== null);
962 if (descriptor
=== undefined) {
963 throw new Error("No such property");
965 const value
= descriptor
.value
;
966 if (typeof value
!== "object" || value
=== null || !("callable" in value
)) {
967 throw new Error("Not a callable object.");
970 if (value
.script
!== undefined) {
972 "The property isn't a native function and will execute code in the debuggee"
976 // Call the property.
977 const result
= value
.call(object
, ...args
);
978 if (result
=== null) {
979 throw new Error("Code was terminated.");
981 if ("throw" in result
) {
984 return result
.return;
987 exports
.callPropertyOnObject
= callPropertyOnObject
;
989 // Convert a Debugger.Object wrapping an iterator into an iterator in the
991 function* makeDebuggeeIterator(object
) {
993 const nextValue
= callPropertyOnObject(object
, "next");
994 if (exports
.getProperty(nextValue
, "done")) {
997 yield exports
.getProperty(nextValue
, "value");
1001 exports
.makeDebuggeeIterator
= makeDebuggeeIterator
;
1004 * Shared helper to retrieve the topmost window. This can be used to retrieve the chrome
1005 * window embedding the DevTools frame.
1007 function getTopWindow(win
) {
1008 return win
.windowRoot
? win
.windowRoot
.ownerGlobal
: win
.top
;
1011 exports
.getTopWindow
= getTopWindow
;
1014 * Check whether two objects are identical by performing
1015 * a deep equality check on their properties and values.
1016 * See toolkit/modules/ObjectUtils.jsm for implementation.
1022 exports
.deepEqual
= (a
, b
) => {
1023 return lazy
.ObjectUtils
.deepEqual(a
, b
);
1026 function isWorkerDebuggerAlive(dbg
) {
1027 // Some workers are zombies. `isClosed` is false, but nothing works.
1028 // `postMessage` is a noop, `addListener`'s `onClosed` doesn't work.
1029 // (Ignore dbg without `window` as they aren't related to docShell
1030 // and probably do not suffer form this issue)
1031 return !dbg
.isClosed
&& (!dbg
.window
|| dbg
.window
.docShell
);
1033 exports
.isWorkerDebuggerAlive
= isWorkerDebuggerAlive
;