1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Console.h"
8 #include "mozilla/dom/ConsoleInstance.h"
9 #include "mozilla/dom/ConsoleBinding.h"
10 #include "ConsoleCommon.h"
12 #include "js/Array.h" // JS::GetArrayLength, JS::NewArrayObject
13 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_GetElement
14 #include "mozilla/dom/BlobBinding.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/Exceptions.h"
18 #include "mozilla/dom/File.h"
19 #include "mozilla/dom/FunctionBinding.h"
20 #include "mozilla/dom/Performance.h"
21 #include "mozilla/dom/PromiseBinding.h"
22 #include "mozilla/dom/ScriptSettings.h"
23 #include "mozilla/dom/StructuredCloneHolder.h"
24 #include "mozilla/dom/ToJSValue.h"
25 #include "mozilla/dom/WorkerRunnable.h"
26 #include "mozilla/dom/WorkerScope.h"
27 #include "mozilla/dom/WorkletGlobalScope.h"
28 #include "mozilla/dom/WorkletImpl.h"
29 #include "mozilla/dom/WorkletThread.h"
30 #include "mozilla/dom/RootedDictionary.h"
31 #include "mozilla/BasePrincipal.h"
32 #include "mozilla/HoldDropJSObjects.h"
33 #include "mozilla/JSObjectHolder.h"
34 #include "mozilla/Maybe.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/StaticPrefs_devtools.h"
37 #include "mozilla/StaticPrefs_dom.h"
38 #include "nsCycleCollectionParticipant.h"
39 #include "nsDOMNavigationTiming.h"
40 #include "nsGlobalWindow.h"
41 #include "nsJSUtils.h"
42 #include "nsNetUtil.h"
43 #include "xpcpublic.h"
44 #include "nsContentUtils.h"
45 #include "nsDocShell.h"
46 #include "nsProxyRelease.h"
47 #include "nsReadableUtils.h"
48 #include "mozilla/ConsoleTimelineMarker.h"
49 #include "mozilla/TimestampTimelineMarker.h"
51 #include "nsIConsoleAPIStorage.h"
52 #include "nsIException.h" // for nsIStackFrame
53 #include "nsIInterfaceRequestorUtils.h"
54 #include "nsILoadContext.h"
55 #include "nsISensitiveInfoHiddenURI.h"
56 #include "nsISupportsPrimitives.h"
57 #include "nsIWebNavigation.h"
58 #include "nsIXPConnect.h"
60 // The maximum allowed number of concurrent timers per page.
61 #define MAX_PAGE_TIMERS 10000
63 // The maximum allowed number of concurrent counters per page.
64 #define MAX_PAGE_COUNTERS 10000
66 // The maximum stacktrace depth when populating the stacktrace array used for
68 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
70 // This tags are used in the Structured Clone Algorithm to move js values from
71 // worker thread to main thread
72 #define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
74 // This value is taken from ConsoleAPIStorage.js
75 #define STORAGE_MAX_EVENTS 1000
77 using namespace mozilla::dom::exceptions
;
79 namespace mozilla::dom
{
81 struct ConsoleStructuredCloneData
{
82 nsCOMPtr
<nsIGlobalObject
> mGlobal
;
83 nsTArray
<RefPtr
<BlobImpl
>> mBlobs
;
86 static void ComposeAndStoreGroupName(JSContext
* aCx
,
87 const Sequence
<JS::Value
>& aData
,
89 nsTArray
<nsString
>* aGroupStack
);
90 static bool UnstoreGroupName(nsAString
& aName
, nsTArray
<nsString
>* aGroupStack
);
92 static bool ProcessArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
,
93 Sequence
<JS::Value
>& aSequence
,
94 Sequence
<nsString
>& aStyles
);
96 static JS::Value
CreateCounterOrResetCounterValue(JSContext
* aCx
,
97 const nsAString
& aCountLabel
,
98 uint32_t aCountValue
);
101 * Console API in workers uses the Structured Clone Algorithm to move any value
102 * from the worker thread to the main-thread. Some object cannot be moved and,
103 * in these cases, we convert them to strings.
104 * It's not the best, but at least we are able to show something.
107 class ConsoleCallData final
{
109 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData
)
111 ConsoleCallData(Console::MethodName aName
, const nsAString
& aString
,
113 : mConsoleID(aConsole
->mConsoleID
),
114 mPrefix(aConsole
->mPrefix
),
116 mMicroSecondTimeStamp(JS_Now()),
118 mStartTimerStatus(Console::eTimerUnknown
),
119 mLogTimerDuration(0),
120 mLogTimerStatus(Console::eTimerUnknown
),
121 mCountValue(MAX_PAGE_COUNTERS
),
125 mMethodString(aString
) {}
127 void SetIDs(uint64_t aOuterID
, uint64_t aInnerID
) {
128 MOZ_ASSERT(mIDType
== eUnknown
);
130 mOuterIDNumber
= aOuterID
;
131 mInnerIDNumber
= aInnerID
;
135 void SetIDs(const nsAString
& aOuterID
, const nsAString
& aInnerID
) {
136 MOZ_ASSERT(mIDType
== eUnknown
);
138 mOuterIDString
= aOuterID
;
139 mInnerIDString
= aInnerID
;
143 void SetOriginAttributes(const OriginAttributes
& aOriginAttributes
) {
144 mOriginAttributes
= aOriginAttributes
;
147 void SetAddonId(nsIPrincipal
* aPrincipal
) {
148 nsAutoString addonId
;
149 aPrincipal
->GetAddonId(addonId
);
154 void AssertIsOnOwningThread() const {
155 NS_ASSERT_OWNINGTHREAD(ConsoleCallData
);
158 const nsString mConsoleID
;
159 const nsString mPrefix
;
161 const Console::MethodName mMethodName
;
162 int64_t mMicroSecondTimeStamp
;
164 // These values are set in the owning thread and they contain the timestamp of
165 // when the new timer has started, the name of it and the status of the
166 // creation of it. If status is false, something went wrong. User
167 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
168 // monotonicTimer from Performance.now();
169 // They will be set on the owning thread and never touched again on that
170 // thread. They will be used in order to create a ConsoleTimerStart dictionary
171 // when console.time() is used.
172 DOMHighResTimeStamp mStartTimerValue
;
173 nsString mStartTimerLabel
;
174 Console::TimerStatus mStartTimerStatus
;
176 // These values are set in the owning thread and they contain the duration,
177 // the name and the status of the LogTimer method. If status is false,
178 // something went wrong. They will be set on the owning thread and never
179 // touched again on that thread. They will be used in order to create a
180 // ConsoleTimerLogOrEnd dictionary. This members are set when
181 // console.timeEnd() or console.timeLog() are called.
182 double mLogTimerDuration
;
183 nsString mLogTimerLabel
;
184 Console::TimerStatus mLogTimerStatus
;
186 // These 2 values are set by IncreaseCounter or ResetCounter on the owning
187 // thread and they are used by CreateCounterOrResetCounterValue.
188 // These members are set when console.count() or console.countReset() are
190 nsString mCountLabel
;
191 uint32_t mCountValue
;
193 // The concept of outerID and innerID is misleading because when a
194 // ConsoleCallData is created from a window, these are the window IDs, but
195 // when the object is created from a SharedWorker, a ServiceWorker or a
196 // subworker of a ChromeWorker these IDs are the type of worker and the
197 // filename of the callee.
198 // In Console.sys.mjs the ID is 'jsm'.
199 enum { eString
, eNumber
, eUnknown
} mIDType
;
201 uint64_t mOuterIDNumber
;
202 nsString mOuterIDString
;
204 uint64_t mInnerIDNumber
;
205 nsString mInnerIDString
;
207 OriginAttributes mOriginAttributes
;
211 const nsString mMethodString
;
213 // Stack management is complicated, because we want to do it as
214 // lazily as possible. Therefore, we have the following behavior:
215 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
216 // 2) mReifiedStack is initialized if we're created in a worker.
217 // 3) mStack is set (possibly to null if there is no JS on the stack) if
218 // we're created on main thread.
219 Maybe
<ConsoleStackEntry
> mTopStackFrame
;
220 Maybe
<nsTArray
<ConsoleStackEntry
>> mReifiedStack
;
221 nsCOMPtr
<nsIStackFrame
> mStack
;
224 ~ConsoleCallData() = default;
226 NS_DECL_OWNINGTHREAD
;
229 // MainThreadConsoleData instances are created on the Console thread and
230 // referenced from both main and Console threads in order to provide the same
231 // object for any ConsoleRunnables relating to the same Console. A Console
232 // owns a MainThreadConsoleData; MainThreadConsoleData does not keep its
234 class MainThreadConsoleData final
{
235 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData
);
237 JSObject
* GetOrCreateSandbox(JSContext
* aCx
, nsIPrincipal
* aPrincipal
);
238 // This method must receive aCx and aArguments in the same JS::Compartment.
239 void ProcessCallData(JSContext
* aCx
, ConsoleCallData
* aData
,
240 const Sequence
<JS::Value
>& aArguments
);
243 ~MainThreadConsoleData() {
244 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
246 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
250 // All members, except for mRefCnt, are accessed only on the main thread,
251 // except in MainThreadConsoleData destruction, at which point there are no
253 nsCOMPtr
<nsIConsoleAPIStorage
> mStorage
;
254 RefPtr
<JSObjectHolder
> mSandbox
;
255 nsTArray
<nsString
> mGroupStack
;
258 // This base class must be extended for Worker and for Worklet.
259 class ConsoleRunnable
: public StructuredCloneHolderBase
{
261 ~ConsoleRunnable() override
{
262 MOZ_ASSERT(!mClonedData
.mGlobal
,
263 "mClonedData.mGlobal is set and cleared in a main thread scope");
264 // Clear the StructuredCloneHolderBase class.
269 JSObject
* CustomReadHandler(JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
270 const JS::CloneDataPolicy
& aCloneDataPolicy
,
271 uint32_t aTag
, uint32_t aIndex
) override
{
272 AssertIsOnMainThread();
274 if (aTag
== CONSOLE_TAG_BLOB
) {
275 MOZ_ASSERT(mClonedData
.mBlobs
.Length() > aIndex
);
277 JS::Rooted
<JS::Value
> val(aCx
);
279 nsCOMPtr
<nsIGlobalObject
> global
= mClonedData
.mGlobal
;
281 Blob::Create(global
, mClonedData
.mBlobs
.ElementAt(aIndex
));
282 if (!ToJSValue(aCx
, blob
, &val
)) {
287 return &val
.toObject();
290 MOZ_CRASH("No other tags are supported.");
294 bool CustomWriteHandler(JSContext
* aCx
, JSStructuredCloneWriter
* aWriter
,
295 JS::Handle
<JSObject
*> aObj
,
296 bool* aSameProcessScopeRequired
) override
{
298 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob
, aObj
, blob
))) {
299 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter
, CONSOLE_TAG_BLOB
,
300 mClonedData
.mBlobs
.Length()))) {
304 mClonedData
.mBlobs
.AppendElement(blob
->Impl());
308 if (!JS_ObjectNotWritten(aWriter
, aObj
)) {
312 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectOrNullValue(aObj
));
313 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
314 if (NS_WARN_IF(!jsString
)) {
318 if (NS_WARN_IF(!JS_WriteString(aWriter
, jsString
))) {
325 // Helper method for CallData
326 void ProcessCallData(JSContext
* aCx
, MainThreadConsoleData
* aConsoleData
,
327 ConsoleCallData
* aCallData
) {
328 AssertIsOnMainThread();
330 ConsoleCommon::ClearException
ce(aCx
);
332 JS::Rooted
<JS::Value
> argumentsValue(aCx
);
333 if (!Read(aCx
, &argumentsValue
)) {
337 MOZ_ASSERT(argumentsValue
.isObject());
339 JS::Rooted
<JSObject
*> argumentsObj(aCx
, &argumentsValue
.toObject());
342 if (!JS::GetArrayLength(aCx
, argumentsObj
, &length
)) {
346 Sequence
<JS::Value
> values
;
347 SequenceRooter
<JS::Value
> arguments(aCx
, &values
);
349 for (uint32_t i
= 0; i
< length
; ++i
) {
350 JS::Rooted
<JS::Value
> value(aCx
);
352 if (!JS_GetElement(aCx
, argumentsObj
, i
, &value
)) {
356 if (!values
.AppendElement(value
, fallible
)) {
361 MOZ_ASSERT(values
.Length() == length
);
363 aConsoleData
->ProcessCallData(aCx
, aCallData
, values
);
367 bool WriteArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
) {
368 ConsoleCommon::ClearException
ce(aCx
);
370 JS::Rooted
<JSObject
*> arguments(
371 aCx
, JS::NewArrayObject(aCx
, aArguments
.Length()));
372 if (NS_WARN_IF(!arguments
)) {
376 JS::Rooted
<JS::Value
> arg(aCx
);
377 for (uint32_t i
= 0; i
< aArguments
.Length(); ++i
) {
380 !JS_DefineElement(aCx
, arguments
, i
, arg
, JSPROP_ENUMERATE
))) {
385 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*arguments
));
386 return WriteData(aCx
, value
);
389 // Helper method for Profile calls
390 void ProcessProfileData(JSContext
* aCx
, Console::MethodName aMethodName
,
391 const nsAString
& aAction
) {
392 AssertIsOnMainThread();
394 ConsoleCommon::ClearException
ce(aCx
);
396 JS::Rooted
<JS::Value
> argumentsValue(aCx
);
397 bool ok
= Read(aCx
, &argumentsValue
);
398 mClonedData
.mGlobal
= nullptr;
404 MOZ_ASSERT(argumentsValue
.isObject());
405 JS::Rooted
<JSObject
*> argumentsObj(aCx
, &argumentsValue
.toObject());
406 if (NS_WARN_IF(!argumentsObj
)) {
411 if (!JS::GetArrayLength(aCx
, argumentsObj
, &length
)) {
415 Sequence
<JS::Value
> arguments
;
417 for (uint32_t i
= 0; i
< length
; ++i
) {
418 JS::Rooted
<JS::Value
> value(aCx
);
420 if (!JS_GetElement(aCx
, argumentsObj
, i
, &value
)) {
424 if (!arguments
.AppendElement(value
, fallible
)) {
429 Console::ProfileMethodMainthread(aCx
, aAction
, arguments
);
432 bool WriteData(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
433 // We use structuredClone to send the JSValue to the main-thread, in order
434 // to store it into the Console API Service. The consumer will be the
435 // console panel in the devtools and, because of this, we want to allow the
436 // cloning of sharedArrayBuffers and WASM modules.
437 JS::CloneDataPolicy cloneDataPolicy
;
438 cloneDataPolicy
.allowIntraClusterClonableSharedObjects();
439 cloneDataPolicy
.allowSharedMemoryObjects();
442 !Write(aCx
, aValue
, JS::UndefinedHandleValue
, cloneDataPolicy
))) {
443 // Ignore the message.
450 ConsoleStructuredCloneData mClonedData
;
453 class ConsoleWorkletRunnable
: public Runnable
, public ConsoleRunnable
{
455 explicit ConsoleWorkletRunnable(Console
* aConsole
)
456 : Runnable("dom::console::ConsoleWorkletRunnable"),
457 mConsoleData(aConsole
->GetOrCreateMainThreadData()) {
458 WorkletThread::AssertIsOnWorkletThread();
459 nsCOMPtr
<WorkletGlobalScope
> global
= do_QueryInterface(aConsole
->mGlobal
);
461 mWorkletImpl
= global
->Impl();
462 MOZ_ASSERT(mWorkletImpl
);
465 ~ConsoleWorkletRunnable() override
= default;
468 RefPtr
<MainThreadConsoleData
> mConsoleData
;
470 RefPtr
<WorkletImpl
> mWorkletImpl
;
473 // This runnable appends a CallData object into the Console queue running on
475 class ConsoleCallDataWorkletRunnable final
: public ConsoleWorkletRunnable
{
477 static already_AddRefed
<ConsoleCallDataWorkletRunnable
> Create(
478 JSContext
* aCx
, Console
* aConsole
, ConsoleCallData
* aConsoleData
,
479 const Sequence
<JS::Value
>& aArguments
) {
480 WorkletThread::AssertIsOnWorkletThread();
482 RefPtr
<ConsoleCallDataWorkletRunnable
> runnable
=
483 new ConsoleCallDataWorkletRunnable(aConsole
, aConsoleData
);
485 if (!runnable
->WriteArguments(aCx
, aArguments
)) {
489 return runnable
.forget();
493 ConsoleCallDataWorkletRunnable(Console
* aConsole
, ConsoleCallData
* aCallData
)
494 : ConsoleWorkletRunnable(aConsole
), mCallData(aCallData
) {
495 WorkletThread::AssertIsOnWorkletThread();
496 MOZ_ASSERT(aCallData
);
497 aCallData
->AssertIsOnOwningThread();
499 const WorkletLoadInfo
& loadInfo
= mWorkletImpl
->LoadInfo();
500 mCallData
->SetIDs(loadInfo
.OuterWindowID(), loadInfo
.InnerWindowID());
503 ~ConsoleCallDataWorkletRunnable() override
= default;
505 NS_IMETHOD
Run() override
{
506 AssertIsOnMainThread();
509 JSContext
* cx
= jsapi
.cx();
512 mConsoleData
->GetOrCreateSandbox(cx
, mWorkletImpl
->Principal());
513 JS::Rooted
<JSObject
*> global(cx
, sandbox
);
514 if (NS_WARN_IF(!global
)) {
515 return NS_ERROR_FAILURE
;
518 // The CreateSandbox call returns a proxy to the actual sandbox object. We
519 // don't need a proxy here.
520 global
= js::UncheckedUnwrap(global
);
522 JSAutoRealm
ar(cx
, global
);
524 // We don't need to set a parent object in mCallData bacause there are not
525 // DOM objects exposed to worklet.
527 ProcessCallData(cx
, mConsoleData
, mCallData
);
532 RefPtr
<ConsoleCallData
> mCallData
;
535 class ConsoleWorkerRunnable
: public WorkerProxyToMainThreadRunnable
,
536 public ConsoleRunnable
{
538 explicit ConsoleWorkerRunnable(Console
* aConsole
)
539 : mConsoleData(aConsole
->GetOrCreateMainThreadData()) {}
541 ~ConsoleWorkerRunnable() override
= default;
543 bool Dispatch(JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
) {
544 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
545 MOZ_ASSERT(workerPrivate
);
547 if (NS_WARN_IF(!WriteArguments(aCx
, aArguments
))) {
548 RunBackOnWorkerThreadForCleanup(workerPrivate
);
552 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate
))) {
553 // RunBackOnWorkerThreadForCleanup() will be called by
554 // WorkerProxyToMainThreadRunnable::Dispatch().
562 void RunOnMainThread(WorkerPrivate
* aWorkerPrivate
) override
{
563 MOZ_ASSERT(aWorkerPrivate
);
564 AssertIsOnMainThread();
566 // Walk up to our containing page
567 WorkerPrivate
* wp
= aWorkerPrivate
;
568 while (wp
->GetParent()) {
569 wp
= wp
->GetParent();
572 nsCOMPtr
<nsPIDOMWindowInner
> window
= wp
->GetWindow();
574 RunWindowless(aWorkerPrivate
);
576 RunWithWindow(aWorkerPrivate
, window
);
580 void RunWithWindow(WorkerPrivate
* aWorkerPrivate
,
581 nsPIDOMWindowInner
* aWindow
) {
582 MOZ_ASSERT(aWorkerPrivate
);
583 AssertIsOnMainThread();
588 RefPtr
<nsGlobalWindowInner
> win
= nsGlobalWindowInner::Cast(aWindow
);
589 if (NS_WARN_IF(!jsapi
.Init(win
))) {
593 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= aWindow
->GetOuterWindow();
594 if (NS_WARN_IF(!outerWindow
)) {
598 RunConsole(jsapi
.cx(), aWindow
->AsGlobal(), aWorkerPrivate
, outerWindow
,
602 void RunWindowless(WorkerPrivate
* aWorkerPrivate
) {
603 MOZ_ASSERT(aWorkerPrivate
);
604 AssertIsOnMainThread();
606 WorkerPrivate
* wp
= aWorkerPrivate
;
607 while (wp
->GetParent()) {
608 wp
= wp
->GetParent();
611 MOZ_ASSERT(!wp
->GetWindow());
616 JSContext
* cx
= jsapi
.cx();
618 JS::Rooted
<JSObject
*> global(
619 cx
, mConsoleData
->GetOrCreateSandbox(cx
, wp
->GetPrincipal()));
620 if (NS_WARN_IF(!global
)) {
624 // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
625 // We don't need a proxy here.
626 global
= js::UncheckedUnwrap(global
);
628 JSAutoRealm
ar(cx
, global
);
630 nsCOMPtr
<nsIGlobalObject
> globalObject
= xpc::NativeGlobal(global
);
631 if (NS_WARN_IF(!globalObject
)) {
635 RunConsole(cx
, globalObject
, aWorkerPrivate
, nullptr, nullptr);
638 void RunBackOnWorkerThreadForCleanup(WorkerPrivate
* aWorkerPrivate
) override
{
639 MOZ_ASSERT(aWorkerPrivate
);
640 aWorkerPrivate
->AssertIsOnWorkerThread();
643 // This method is called in the main-thread.
644 virtual void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
645 WorkerPrivate
* aWorkerPrivate
,
646 nsPIDOMWindowOuter
* aOuterWindow
,
647 nsPIDOMWindowInner
* aInnerWindow
) = 0;
649 bool ForMessaging() const override
{ return true; }
651 RefPtr
<MainThreadConsoleData
> mConsoleData
;
654 // This runnable appends a CallData object into the Console queue running on
656 class ConsoleCallDataWorkerRunnable final
: public ConsoleWorkerRunnable
{
658 ConsoleCallDataWorkerRunnable(Console
* aConsole
, ConsoleCallData
* aCallData
)
659 : ConsoleWorkerRunnable(aConsole
), mCallData(aCallData
) {
660 MOZ_ASSERT(aCallData
);
661 mCallData
->AssertIsOnOwningThread();
665 ~ConsoleCallDataWorkerRunnable() override
= default;
667 void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
668 WorkerPrivate
* aWorkerPrivate
,
669 nsPIDOMWindowOuter
* aOuterWindow
,
670 nsPIDOMWindowInner
* aInnerWindow
) override
{
672 MOZ_ASSERT(aWorkerPrivate
);
673 AssertIsOnMainThread();
675 // The windows have to run in parallel.
676 MOZ_ASSERT(!!aOuterWindow
== !!aInnerWindow
);
679 mCallData
->SetIDs(aOuterWindow
->WindowID(), aInnerWindow
->WindowID());
681 ConsoleStackEntry frame
;
682 if (mCallData
->mTopStackFrame
) {
683 frame
= *mCallData
->mTopStackFrame
;
686 nsString id
= frame
.mFilename
;
688 if (aWorkerPrivate
->IsSharedWorker()) {
689 innerID
= u
"SharedWorker"_ns
;
690 } else if (aWorkerPrivate
->IsServiceWorker()) {
691 innerID
= u
"ServiceWorker"_ns
;
692 // Use scope as ID so the webconsole can decide if the message should
694 CopyASCIItoUTF16(aWorkerPrivate
->ServiceWorkerScope(), id
);
696 innerID
= u
"Worker"_ns
;
699 mCallData
->SetIDs(id
, innerID
);
702 mClonedData
.mGlobal
= aGlobal
;
704 ProcessCallData(aCx
, mConsoleData
, mCallData
);
706 mClonedData
.mGlobal
= nullptr;
709 RefPtr
<ConsoleCallData
> mCallData
;
712 // This runnable calls ProfileMethod() on the console on the main-thread.
713 class ConsoleProfileWorkletRunnable final
: public ConsoleWorkletRunnable
{
715 static already_AddRefed
<ConsoleProfileWorkletRunnable
> Create(
716 JSContext
* aCx
, Console
* aConsole
, Console::MethodName aName
,
717 const nsAString
& aAction
, const Sequence
<JS::Value
>& aArguments
) {
718 WorkletThread::AssertIsOnWorkletThread();
720 RefPtr
<ConsoleProfileWorkletRunnable
> runnable
=
721 new ConsoleProfileWorkletRunnable(aConsole
, aName
, aAction
);
723 if (!runnable
->WriteArguments(aCx
, aArguments
)) {
727 return runnable
.forget();
731 ConsoleProfileWorkletRunnable(Console
* aConsole
, Console::MethodName aName
,
732 const nsAString
& aAction
)
733 : ConsoleWorkletRunnable(aConsole
), mName(aName
), mAction(aAction
) {
734 MOZ_ASSERT(aConsole
);
737 NS_IMETHOD
Run() override
{
738 AssertIsOnMainThread();
742 JSContext
* cx
= jsapi
.cx();
745 mConsoleData
->GetOrCreateSandbox(cx
, mWorkletImpl
->Principal());
746 JS::Rooted
<JSObject
*> global(cx
, sandbox
);
747 if (NS_WARN_IF(!global
)) {
748 return NS_ERROR_FAILURE
;
751 // The CreateSandbox call returns a proxy to the actual sandbox object. We
752 // don't need a proxy here.
753 global
= js::UncheckedUnwrap(global
);
755 JSAutoRealm
ar(cx
, global
);
757 // We don't need to set a parent object in mCallData bacause there are not
758 // DOM objects exposed to worklet.
759 ProcessProfileData(cx
, mName
, mAction
);
764 Console::MethodName mName
;
768 // This runnable calls ProfileMethod() on the console on the main-thread.
769 class ConsoleProfileWorkerRunnable final
: public ConsoleWorkerRunnable
{
771 ConsoleProfileWorkerRunnable(Console
* aConsole
, Console::MethodName aName
,
772 const nsAString
& aAction
)
773 : ConsoleWorkerRunnable(aConsole
), mName(aName
), mAction(aAction
) {
774 MOZ_ASSERT(aConsole
);
778 void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
779 WorkerPrivate
* aWorkerPrivate
,
780 nsPIDOMWindowOuter
* aOuterWindow
,
781 nsPIDOMWindowInner
* aInnerWindow
) override
{
782 AssertIsOnMainThread();
785 mClonedData
.mGlobal
= aGlobal
;
787 ProcessProfileData(aCx
, mName
, mAction
);
789 mClonedData
.mGlobal
= nullptr;
792 Console::MethodName mName
;
796 NS_IMPL_CYCLE_COLLECTION_CLASS(Console
)
798 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console
)
799 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
800 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier
)
801 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction
)
802 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
804 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
806 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console
)
807 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
)
808 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier
)
809 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction
)
810 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
812 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console
)
813 for (uint32_t i
= 0; i
< tmp
->mArgumentStorage
.length(); ++i
) {
814 tmp
->mArgumentStorage
[i
].Trace(aCallbacks
, aClosure
);
816 NS_IMPL_CYCLE_COLLECTION_TRACE_END
818 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console
)
819 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console
)
821 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console
)
822 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
823 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIObserver
)
824 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
828 already_AddRefed
<Console
> Console::Create(JSContext
* aCx
,
829 nsPIDOMWindowInner
* aWindow
,
831 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow
);
833 uint64_t outerWindowID
= 0;
834 uint64_t innerWindowID
= 0;
837 innerWindowID
= aWindow
->WindowID();
839 // Without outerwindow any console message coming from this object will not
840 // shown in the devtools webconsole. But this should be fine because
841 // probably we are shutting down, or the window is CCed/GCed.
842 nsPIDOMWindowOuter
* outerWindow
= aWindow
->GetOuterWindow();
844 outerWindowID
= outerWindow
->WindowID();
848 RefPtr
<Console
> console
= new Console(aCx
, nsGlobalWindowInner::Cast(aWindow
),
849 outerWindowID
, innerWindowID
);
850 console
->Initialize(aRv
);
851 if (NS_WARN_IF(aRv
.Failed())) {
855 return console
.forget();
859 already_AddRefed
<Console
> Console::CreateForWorklet(JSContext
* aCx
,
860 nsIGlobalObject
* aGlobal
,
861 uint64_t aOuterWindowID
,
862 uint64_t aInnerWindowID
,
864 WorkletThread::AssertIsOnWorkletThread();
866 RefPtr
<Console
> console
=
867 new Console(aCx
, aGlobal
, aOuterWindowID
, aInnerWindowID
);
868 console
->Initialize(aRv
);
869 if (NS_WARN_IF(aRv
.Failed())) {
873 return console
.forget();
876 Console::Console(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
877 uint64_t aOuterWindowID
, uint64_t aInnerWindowID
)
879 mOuterID(aOuterWindowID
),
880 mInnerID(aInnerWindowID
),
881 mDumpToStdout(false),
882 mChromeInstance(false),
883 mMaxLogLevel(ConsoleLogLevel::All
),
885 mCreationTimeStamp(TimeStamp::Now()) {
886 // Let's enable the dumping to stdout by default for chrome.
887 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx
)) {
888 mDumpToStdout
= StaticPrefs::devtools_console_stdout_chrome();
890 mDumpToStdout
= StaticPrefs::devtools_console_stdout_content();
893 mozilla::HoldJSObjects(this);
896 Console::~Console() {
897 AssertIsOnOwningThread();
899 mozilla::DropJSObjects(this);
902 void Console::Initialize(ErrorResult
& aRv
) {
903 AssertIsOnOwningThread();
904 MOZ_ASSERT(mStatus
== eUnknown
);
906 if (NS_IsMainThread()) {
907 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
908 if (NS_WARN_IF(!obs
)) {
909 aRv
.Throw(NS_ERROR_FAILURE
);
914 aRv
= obs
->AddObserver(this, "inner-window-destroyed", true);
915 if (NS_WARN_IF(aRv
.Failed())) {
920 aRv
= obs
->AddObserver(this, "memory-pressure", true);
921 if (NS_WARN_IF(aRv
.Failed())) {
926 mStatus
= eInitialized
;
929 void Console::Shutdown() {
930 AssertIsOnOwningThread();
932 if (mStatus
== eUnknown
|| mStatus
== eShuttingDown
) {
936 if (NS_IsMainThread()) {
937 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
939 obs
->RemoveObserver(this, "inner-window-destroyed");
940 obs
->RemoveObserver(this, "memory-pressure");
944 mTimerRegistry
.Clear();
945 mCounterRegistry
.Clear();
948 mCallDataStorage
.Clear();
950 mStatus
= eShuttingDown
;
954 Console::Observe(nsISupports
* aSubject
, const char* aTopic
,
955 const char16_t
* aData
) {
956 AssertIsOnMainThread();
958 if (!strcmp(aTopic
, "inner-window-destroyed")) {
959 nsCOMPtr
<nsISupportsPRUint64
> wrapper
= do_QueryInterface(aSubject
);
960 NS_ENSURE_TRUE(wrapper
, NS_ERROR_FAILURE
);
963 nsresult rv
= wrapper
->GetData(&innerID
);
964 NS_ENSURE_SUCCESS(rv
, rv
);
966 if (innerID
== mInnerID
) {
973 if (!strcmp(aTopic
, "memory-pressure")) {
981 void Console::ClearStorage() {
982 mCallDataStorage
.Clear();
983 mArgumentStorage
.clearAndFree();
986 #define METHOD(name, string) \
987 /* static */ void Console::name(const GlobalObject& aGlobal, \
988 const Sequence<JS::Value>& aData) { \
989 Method(aGlobal, Method##name, nsLiteralString(string), aData); \
993 METHOD(Info
, u
"info")
994 METHOD(Warn
, u
"warn")
995 METHOD(Error
, u
"error")
996 METHOD(Exception
, u
"exception")
997 METHOD(Debug
, u
"debug")
998 METHOD(Table
, u
"table")
999 METHOD(Trace
, u
"trace")
1001 // Displays an interactive listing of all the properties of an object.
1002 METHOD(Dir
, u
"dir");
1003 METHOD(Dirxml
, u
"dirxml");
1005 METHOD(Group
, u
"group")
1006 METHOD(GroupCollapsed
, u
"groupCollapsed")
1011 void Console::Clear(const GlobalObject
& aGlobal
) {
1012 const Sequence
<JS::Value
> data
;
1013 Method(aGlobal
, MethodClear
, u
"clear"_ns
, data
);
1017 void Console::GroupEnd(const GlobalObject
& aGlobal
) {
1018 const Sequence
<JS::Value
> data
;
1019 Method(aGlobal
, MethodGroupEnd
, u
"groupEnd"_ns
, data
);
1023 void Console::Time(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1024 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodTime
, u
"time"_ns
);
1028 void Console::TimeEnd(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1029 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodTimeEnd
,
1034 void Console::TimeLog(const GlobalObject
& aGlobal
, const nsAString
& aLabel
,
1035 const Sequence
<JS::Value
>& aData
) {
1036 StringMethod(aGlobal
, aLabel
, aData
, MethodTimeLog
, u
"timeLog"_ns
);
1040 void Console::StringMethod(const GlobalObject
& aGlobal
, const nsAString
& aLabel
,
1041 const Sequence
<JS::Value
>& aData
,
1042 MethodName aMethodName
,
1043 const nsAString
& aMethodString
) {
1044 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1049 console
->StringMethodInternal(aGlobal
.Context(), aLabel
, aData
, aMethodName
,
1053 void Console::StringMethodInternal(JSContext
* aCx
, const nsAString
& aLabel
,
1054 const Sequence
<JS::Value
>& aData
,
1055 MethodName aMethodName
,
1056 const nsAString
& aMethodString
) {
1057 ConsoleCommon::ClearException
ce(aCx
);
1059 Sequence
<JS::Value
> data
;
1060 SequenceRooter
<JS::Value
> rooter(aCx
, &data
);
1062 JS::Rooted
<JS::Value
> value(aCx
);
1063 if (!dom::ToJSValue(aCx
, aLabel
, &value
)) {
1067 if (!data
.AppendElement(value
, fallible
)) {
1071 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1072 if (!data
.AppendElement(aData
[i
], fallible
)) {
1077 MethodInternal(aCx
, aMethodName
, aMethodString
, data
);
1081 void Console::TimeStamp(const GlobalObject
& aGlobal
,
1082 const JS::Handle
<JS::Value
> aData
) {
1083 JSContext
* cx
= aGlobal
.Context();
1085 ConsoleCommon::ClearException
ce(cx
);
1087 Sequence
<JS::Value
> data
;
1088 SequenceRooter
<JS::Value
> rooter(cx
, &data
);
1090 if (aData
.isString() && !data
.AppendElement(aData
, fallible
)) {
1094 Method(aGlobal
, MethodTimeStamp
, u
"timeStamp"_ns
, data
);
1098 void Console::Profile(const GlobalObject
& aGlobal
,
1099 const Sequence
<JS::Value
>& aData
) {
1100 ProfileMethod(aGlobal
, MethodProfile
, u
"profile"_ns
, aData
);
1104 void Console::ProfileEnd(const GlobalObject
& aGlobal
,
1105 const Sequence
<JS::Value
>& aData
) {
1106 ProfileMethod(aGlobal
, MethodProfileEnd
, u
"profileEnd"_ns
, aData
);
1110 void Console::ProfileMethod(const GlobalObject
& aGlobal
, MethodName aName
,
1111 const nsAString
& aAction
,
1112 const Sequence
<JS::Value
>& aData
) {
1113 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1118 JSContext
* cx
= aGlobal
.Context();
1119 console
->ProfileMethodInternal(cx
, aName
, aAction
, aData
);
1122 void Console::ProfileMethodInternal(JSContext
* aCx
, MethodName aMethodName
,
1123 const nsAString
& aAction
,
1124 const Sequence
<JS::Value
>& aData
) {
1125 if (!ShouldProceed(aMethodName
)) {
1129 MaybeExecuteDumpFunction(aCx
, aAction
, aData
, nullptr);
1131 if (WorkletThread::IsOnWorkletThread()) {
1132 RefPtr
<ConsoleProfileWorkletRunnable
> runnable
=
1133 ConsoleProfileWorkletRunnable::Create(aCx
, this, aMethodName
, aAction
,
1139 NS_DispatchToMainThread(runnable
.forget());
1143 if (!NS_IsMainThread()) {
1144 // Here we are in a worker thread.
1145 RefPtr
<ConsoleProfileWorkerRunnable
> runnable
=
1146 new ConsoleProfileWorkerRunnable(this, aMethodName
, aAction
);
1148 runnable
->Dispatch(aCx
, aData
);
1152 ProfileMethodMainthread(aCx
, aAction
, aData
);
1156 void Console::ProfileMethodMainthread(JSContext
* aCx
, const nsAString
& aAction
,
1157 const Sequence
<JS::Value
>& aData
) {
1158 MOZ_ASSERT(NS_IsMainThread());
1159 ConsoleCommon::ClearException
ce(aCx
);
1161 RootedDictionary
<ConsoleProfileEvent
> event(aCx
);
1162 event
.mAction
= aAction
;
1163 event
.mChromeContext
= nsContentUtils::ThreadsafeIsSystemCaller(aCx
);
1165 event
.mArguments
.Construct();
1166 Sequence
<JS::Value
>& sequence
= event
.mArguments
.Value();
1168 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1169 if (!sequence
.AppendElement(aData
[i
], fallible
)) {
1174 JS::Rooted
<JS::Value
> eventValue(aCx
);
1175 if (!ToJSValue(aCx
, event
, &eventValue
)) {
1179 JS::Rooted
<JSObject
*> eventObj(aCx
, &eventValue
.toObject());
1180 MOZ_ASSERT(eventObj
);
1182 if (!JS_DefineProperty(aCx
, eventObj
, "wrappedJSObject", eventValue
,
1183 JSPROP_ENUMERATE
)) {
1187 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
1188 nsCOMPtr
<nsISupports
> wrapper
;
1189 const nsIID
& iid
= NS_GET_IID(nsISupports
);
1191 if (NS_FAILED(xpc
->WrapJS(aCx
, eventObj
, iid
, getter_AddRefs(wrapper
)))) {
1195 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
1197 obs
->NotifyObservers(wrapper
, "console-api-profiler", nullptr);
1202 void Console::Assert(const GlobalObject
& aGlobal
, bool aCondition
,
1203 const Sequence
<JS::Value
>& aData
) {
1205 Method(aGlobal
, MethodAssert
, u
"assert"_ns
, aData
);
1210 void Console::Count(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1211 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodCount
,
1216 void Console::CountReset(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1217 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodCountReset
,
1223 void StackFrameToStackEntry(JSContext
* aCx
, nsIStackFrame
* aStackFrame
,
1224 ConsoleStackEntry
& aStackEntry
) {
1225 MOZ_ASSERT(aStackFrame
);
1227 aStackFrame
->GetFilename(aCx
, aStackEntry
.mFilename
);
1229 aStackEntry
.mSourceId
= aStackFrame
->GetSourceId(aCx
);
1230 aStackEntry
.mLineNumber
= aStackFrame
->GetLineNumber(aCx
);
1231 aStackEntry
.mColumnNumber
= aStackFrame
->GetColumnNumber(aCx
);
1233 aStackFrame
->GetName(aCx
, aStackEntry
.mFunctionName
);
1236 aStackFrame
->GetAsyncCause(aCx
, cause
);
1237 if (!cause
.IsEmpty()) {
1238 aStackEntry
.mAsyncCause
.Construct(cause
);
1242 void ReifyStack(JSContext
* aCx
, nsIStackFrame
* aStack
,
1243 nsTArray
<ConsoleStackEntry
>& aRefiedStack
) {
1244 nsCOMPtr
<nsIStackFrame
> stack(aStack
);
1247 ConsoleStackEntry
& data
= *aRefiedStack
.AppendElement();
1248 StackFrameToStackEntry(aCx
, stack
, data
);
1250 nsCOMPtr
<nsIStackFrame
> caller
= stack
->GetCaller(aCx
);
1253 caller
= stack
->GetAsyncCaller(aCx
);
1259 } // anonymous namespace
1261 // Queue a call to a console method. See the CALL_DELAY constant.
1263 void Console::Method(const GlobalObject
& aGlobal
, MethodName aMethodName
,
1264 const nsAString
& aMethodString
,
1265 const Sequence
<JS::Value
>& aData
) {
1266 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1271 console
->MethodInternal(aGlobal
.Context(), aMethodName
, aMethodString
, aData
);
1274 void Console::MethodInternal(JSContext
* aCx
, MethodName aMethodName
,
1275 const nsAString
& aMethodString
,
1276 const Sequence
<JS::Value
>& aData
) {
1277 if (!ShouldProceed(aMethodName
)) {
1281 AssertIsOnOwningThread();
1283 ConsoleCommon::ClearException
ce(aCx
);
1285 RefPtr
<ConsoleCallData
> callData
=
1286 new ConsoleCallData(aMethodName
, aMethodString
, this);
1287 if (!StoreCallData(aCx
, callData
, aData
)) {
1291 OriginAttributes oa
;
1293 if (NS_IsMainThread()) {
1295 // Save the principal's OriginAttributes in the console event data
1296 // so that we will be able to filter messages by origin attributes.
1297 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(mGlobal
);
1298 if (NS_WARN_IF(!sop
)) {
1302 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1303 if (NS_WARN_IF(!principal
)) {
1307 oa
= principal
->OriginAttributesRef();
1308 callData
->SetAddonId(principal
);
1311 if (!principal
->IsSystemPrincipal()) {
1312 nsCOMPtr
<nsIWebNavigation
> webNav
= do_GetInterface(mGlobal
);
1314 nsCOMPtr
<nsILoadContext
> loadContext
= do_QueryInterface(webNav
);
1315 MOZ_ASSERT(loadContext
);
1318 if (NS_SUCCEEDED(loadContext
->GetUsePrivateBrowsing(&pb
))) {
1319 MOZ_ASSERT(pb
== !!oa
.mPrivateBrowsingId
);
1325 } else if (WorkletThread::IsOnWorkletThread()) {
1326 nsCOMPtr
<WorkletGlobalScope
> global
= do_QueryInterface(mGlobal
);
1328 oa
= global
->Impl()->OriginAttributesRef();
1330 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
1331 MOZ_ASSERT(workerPrivate
);
1332 oa
= workerPrivate
->GetOriginAttributes();
1335 callData
->SetOriginAttributes(oa
);
1337 JS::StackCapture captureMode
=
1338 ShouldIncludeStackTrace(aMethodName
)
1339 ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH
))
1340 : JS::StackCapture(JS::FirstSubsumedFrame(aCx
));
1341 nsCOMPtr
<nsIStackFrame
> stack
= CreateStack(aCx
, std::move(captureMode
));
1344 callData
->mTopStackFrame
.emplace();
1345 StackFrameToStackEntry(aCx
, stack
, *callData
->mTopStackFrame
);
1348 if (NS_IsMainThread()) {
1349 callData
->mStack
= stack
;
1351 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1352 // before we post our runnable to the main thread.
1353 callData
->mReifiedStack
.emplace();
1354 ReifyStack(aCx
, stack
, *callData
->mReifiedStack
);
1357 DOMHighResTimeStamp monotonicTimer
;
1359 // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1360 if ((aMethodName
== MethodTime
|| aMethodName
== MethodTimeLog
||
1361 aMethodName
== MethodTimeEnd
|| aMethodName
== MethodTimeStamp
) &&
1362 !MonotonicTimer(aCx
, aMethodName
, aData
, &monotonicTimer
)) {
1366 if (aMethodName
== MethodTime
&& !aData
.IsEmpty()) {
1367 callData
->mStartTimerStatus
=
1368 StartTimer(aCx
, aData
[0], monotonicTimer
, callData
->mStartTimerLabel
,
1369 &callData
->mStartTimerValue
);
1372 else if (aMethodName
== MethodTimeEnd
&& !aData
.IsEmpty()) {
1373 callData
->mLogTimerStatus
=
1374 LogTimer(aCx
, aData
[0], monotonicTimer
, callData
->mLogTimerLabel
,
1375 &callData
->mLogTimerDuration
, true /* Cancel timer */);
1378 else if (aMethodName
== MethodTimeLog
&& !aData
.IsEmpty()) {
1379 callData
->mLogTimerStatus
=
1380 LogTimer(aCx
, aData
[0], monotonicTimer
, callData
->mLogTimerLabel
,
1381 &callData
->mLogTimerDuration
, false /* Cancel timer */);
1384 else if (aMethodName
== MethodCount
) {
1385 callData
->mCountValue
= IncreaseCounter(aCx
, aData
, callData
->mCountLabel
);
1386 if (!callData
->mCountValue
) {
1391 else if (aMethodName
== MethodCountReset
) {
1392 callData
->mCountValue
= ResetCounter(aCx
, aData
, callData
->mCountLabel
);
1393 if (callData
->mCountLabel
.IsEmpty()) {
1398 // Before processing this CallData differently, it's time to call the dump
1400 if (aMethodName
== MethodTrace
|| aMethodName
== MethodAssert
) {
1401 MaybeExecuteDumpFunction(aCx
, aMethodString
, aData
, stack
);
1402 } else if ((aMethodName
== MethodTime
|| aMethodName
== MethodTimeEnd
) &&
1404 MaybeExecuteDumpFunctionForTime(aCx
, aMethodName
, aMethodString
,
1405 monotonicTimer
, aData
[0]);
1407 MaybeExecuteDumpFunction(aCx
, aMethodString
, aData
, nullptr);
1410 if (NS_IsMainThread()) {
1412 callData
->SetIDs(mOuterID
, mInnerID
);
1413 } else if (!mPassedInnerID
.IsEmpty()) {
1414 callData
->SetIDs(u
"jsm"_ns
, mPassedInnerID
);
1416 nsAutoString filename
;
1417 if (callData
->mTopStackFrame
.isSome()) {
1418 filename
= callData
->mTopStackFrame
->mFilename
;
1421 callData
->SetIDs(u
"jsm"_ns
, filename
);
1424 GetOrCreateMainThreadData()->ProcessCallData(aCx
, callData
, aData
);
1426 // Just because we don't want to expose
1427 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1428 // cleanup the mCallDataStorage:
1429 UnstoreCallData(callData
);
1433 if (WorkletThread::IsOnWorkletThread()) {
1434 RefPtr
<ConsoleCallDataWorkletRunnable
> runnable
=
1435 ConsoleCallDataWorkletRunnable::Create(aCx
, this, callData
, aData
);
1440 NS_DispatchToMainThread(runnable
);
1444 // We do this only in workers for now.
1445 NotifyHandler(aCx
, aData
, callData
);
1447 if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
1448 RefPtr
<ConsoleCallDataWorkerRunnable
> runnable
=
1449 new ConsoleCallDataWorkerRunnable(this, callData
);
1450 Unused
<< NS_WARN_IF(!runnable
->Dispatch(aCx
, aData
));
1454 MainThreadConsoleData
* Console::GetOrCreateMainThreadData() {
1455 AssertIsOnOwningThread();
1457 if (!mMainThreadData
) {
1458 mMainThreadData
= new MainThreadConsoleData();
1461 return mMainThreadData
;
1464 // We store information to lazily compute the stack in the reserved slots of
1465 // LazyStackGetter. The first slot always stores a JS object: it's either the
1466 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1467 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1468 // reified the stack yet, or an UndefinedValue() otherwise.
1469 enum { SLOT_STACKOBJ
, SLOT_RAW_STACK
};
1471 bool LazyStackGetter(JSContext
* aCx
, unsigned aArgc
, JS::Value
* aVp
) {
1472 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
1473 JS::Rooted
<JSObject
*> callee(aCx
, &args
.callee());
1475 JS::Value v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_RAW_STACK
);
1476 if (v
.isUndefined()) {
1478 args
.rval().set(js::GetFunctionNativeReserved(callee
, SLOT_STACKOBJ
));
1482 nsIStackFrame
* stack
= reinterpret_cast<nsIStackFrame
*>(v
.toPrivate());
1483 nsTArray
<ConsoleStackEntry
> reifiedStack
;
1484 ReifyStack(aCx
, stack
, reifiedStack
);
1486 JS::Rooted
<JS::Value
> stackVal(aCx
);
1487 if (NS_WARN_IF(!ToJSValue(aCx
, reifiedStack
, &stackVal
))) {
1491 MOZ_ASSERT(stackVal
.isObject());
1493 js::SetFunctionNativeReserved(callee
, SLOT_STACKOBJ
, stackVal
);
1494 js::SetFunctionNativeReserved(callee
, SLOT_RAW_STACK
, JS::UndefinedValue());
1496 args
.rval().set(stackVal
);
1500 void MainThreadConsoleData::ProcessCallData(
1501 JSContext
* aCx
, ConsoleCallData
* aData
,
1502 const Sequence
<JS::Value
>& aArguments
) {
1503 AssertIsOnMainThread();
1506 JS::Rooted
<JS::Value
> eventValue(aCx
);
1508 // We want to create a console event object and pass it to our
1509 // nsIConsoleAPIStorage implementation. We want to define some accessor
1510 // properties on this object, and those will need to keep an nsIStackFrame
1511 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1512 // further, passing untrusted objects to system code is likely to run afoul of
1513 // Object Xrays. So we want to wrap in a system-principal scope here. But
1514 // which one? We could cheat and try to get the underlying JSObject* of
1515 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1516 // with explicit permission from the XPConnect module owner. If you're
1517 // tempted to do that anywhere else, talk to said module owner first.
1519 // aCx and aArguments are in the same compartment.
1520 JS::Rooted
<JSObject
*> targetScope(aCx
, xpc::PrivilegedJunkScope());
1521 if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
1522 aCx
, aArguments
, targetScope
, &eventValue
, aData
, &mGroupStack
))) {
1527 mStorage
= do_GetService("@mozilla.org/consoleAPI-storage;1");
1531 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1535 nsAutoString innerID
;
1537 MOZ_ASSERT(aData
->mIDType
!= ConsoleCallData::eUnknown
);
1538 if (aData
->mIDType
== ConsoleCallData::eString
) {
1539 innerID
= aData
->mInnerIDString
;
1541 MOZ_ASSERT(aData
->mIDType
== ConsoleCallData::eNumber
);
1542 innerID
.AppendInt(aData
->mInnerIDNumber
);
1545 if (aData
->mMethodName
== Console::MethodClear
) {
1546 DebugOnly
<nsresult
> rv
= mStorage
->ClearEvents(innerID
);
1547 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "ClearEvents failed");
1550 if (NS_FAILED(mStorage
->RecordEvent(innerID
, eventValue
))) {
1551 NS_WARNING("Failed to record a console event.");
1556 bool Console::PopulateConsoleNotificationInTheTargetScope(
1557 JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
,
1558 JS::Handle
<JSObject
*> aTargetScope
,
1559 JS::MutableHandle
<JS::Value
> aEventValue
, ConsoleCallData
* aData
,
1560 nsTArray
<nsString
>* aGroupStack
) {
1563 MOZ_ASSERT(aTargetScope
);
1564 MOZ_ASSERT(JS_IsGlobalObject(aTargetScope
));
1566 ConsoleStackEntry frame
;
1567 if (aData
->mTopStackFrame
) {
1568 frame
= *aData
->mTopStackFrame
;
1571 ConsoleCommon::ClearException
ce(aCx
);
1572 RootedDictionary
<ConsoleEvent
> event(aCx
);
1574 event
.mAddonId
= aData
->mAddonId
;
1576 event
.mID
.Construct();
1577 event
.mInnerID
.Construct();
1579 event
.mChromeContext
= nsContentUtils::ThreadsafeIsSystemCaller(aCx
);
1581 if (aData
->mIDType
== ConsoleCallData::eString
) {
1582 event
.mID
.Value().SetAsString() = aData
->mOuterIDString
;
1583 event
.mInnerID
.Value().SetAsString() = aData
->mInnerIDString
;
1584 } else if (aData
->mIDType
== ConsoleCallData::eNumber
) {
1585 event
.mID
.Value().SetAsUnsignedLongLong() = aData
->mOuterIDNumber
;
1586 event
.mInnerID
.Value().SetAsUnsignedLongLong() = aData
->mInnerIDNumber
;
1588 // aData->mIDType can be eUnknown when we dispatch notifications via
1589 // mConsoleEventNotifier.
1590 event
.mID
.Value().SetAsUnsignedLongLong() = 0;
1591 event
.mInnerID
.Value().SetAsUnsignedLongLong() = 0;
1594 event
.mConsoleID
= aData
->mConsoleID
;
1595 event
.mLevel
= aData
->mMethodString
;
1596 event
.mFilename
= frame
.mFilename
;
1597 event
.mPrefix
= aData
->mPrefix
;
1599 nsCOMPtr
<nsIURI
> filenameURI
;
1601 if (NS_IsMainThread() &&
1602 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI
), frame
.mFilename
)) &&
1603 NS_SUCCEEDED(filenameURI
->GetPassword(pass
)) && !pass
.IsEmpty()) {
1604 nsCOMPtr
<nsISensitiveInfoHiddenURI
> safeURI
=
1605 do_QueryInterface(filenameURI
);
1607 if (safeURI
&& NS_SUCCEEDED(safeURI
->GetSensitiveInfoHiddenSpec(spec
))) {
1608 CopyUTF8toUTF16(spec
, event
.mFilename
);
1612 event
.mSourceId
= frame
.mSourceId
;
1613 event
.mLineNumber
= frame
.mLineNumber
;
1614 event
.mColumnNumber
= frame
.mColumnNumber
;
1615 event
.mFunctionName
= frame
.mFunctionName
;
1616 event
.mTimeStamp
= aData
->mMicroSecondTimeStamp
/ PR_USEC_PER_MSEC
;
1617 event
.mMicroSecondTimeStamp
= aData
->mMicroSecondTimeStamp
;
1618 event
.mPrivate
= !!aData
->mOriginAttributes
.mPrivateBrowsingId
;
1620 switch (aData
->mMethodName
) {
1625 case MethodException
:
1629 case MethodGroupCollapsed
:
1631 event
.mArguments
.Construct();
1632 event
.mStyles
.Construct();
1633 if (NS_WARN_IF(!ProcessArguments(aCx
, aArguments
,
1634 event
.mArguments
.Value(),
1635 event
.mStyles
.Value()))) {
1642 event
.mArguments
.Construct();
1644 !event
.mArguments
.Value().AppendElements(aArguments
, fallible
))) {
1649 if (aData
->mMethodName
== MethodGroup
||
1650 aData
->mMethodName
== MethodGroupCollapsed
) {
1651 ComposeAndStoreGroupName(aCx
, event
.mArguments
.Value(), event
.mGroupName
,
1655 else if (aData
->mMethodName
== MethodGroupEnd
) {
1656 if (!UnstoreGroupName(event
.mGroupName
, aGroupStack
)) {
1661 else if (aData
->mMethodName
== MethodTime
&& !aArguments
.IsEmpty()) {
1662 event
.mTimer
= CreateStartTimerValue(aCx
, aData
->mStartTimerLabel
,
1663 aData
->mStartTimerStatus
);
1666 else if ((aData
->mMethodName
== MethodTimeEnd
||
1667 aData
->mMethodName
== MethodTimeLog
) &&
1668 !aArguments
.IsEmpty()) {
1669 event
.mTimer
= CreateLogOrEndTimerValue(aCx
, aData
->mLogTimerLabel
,
1670 aData
->mLogTimerDuration
,
1671 aData
->mLogTimerStatus
);
1674 else if (aData
->mMethodName
== MethodCount
||
1675 aData
->mMethodName
== MethodCountReset
) {
1676 event
.mCounter
= CreateCounterOrResetCounterValue(aCx
, aData
->mCountLabel
,
1677 aData
->mCountValue
);
1680 JSAutoRealm
ar2(aCx
, aTargetScope
);
1682 if (NS_WARN_IF(!ToJSValue(aCx
, event
, aEventValue
))) {
1686 JS::Rooted
<JSObject
*> eventObj(aCx
, &aEventValue
.toObject());
1687 if (NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "wrappedJSObject", eventObj
,
1688 JSPROP_ENUMERATE
))) {
1692 if (ShouldIncludeStackTrace(aData
->mMethodName
)) {
1693 // Now define the "stacktrace" property on eventObj. There are two cases
1694 // here. Either we came from a worker and have a reified stack, or we want
1695 // to define a getter that will lazily reify the stack.
1696 if (aData
->mReifiedStack
) {
1697 JS::Rooted
<JS::Value
> stacktrace(aCx
);
1698 if (NS_WARN_IF(!ToJSValue(aCx
, *aData
->mReifiedStack
, &stacktrace
)) ||
1699 NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "stacktrace", stacktrace
,
1700 JSPROP_ENUMERATE
))) {
1705 js::NewFunctionWithReserved(aCx
, LazyStackGetter
, 0, 0, "stacktrace");
1706 if (NS_WARN_IF(!fun
)) {
1710 JS::Rooted
<JSObject
*> funObj(aCx
, JS_GetFunctionObject(fun
));
1712 // We want to store our stack in the function and have it stay alive. But
1713 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1714 // wrapper and the raw pointer: the former will keep the latter alive.
1715 JS::Rooted
<JS::Value
> stackVal(aCx
);
1716 nsresult rv
= nsContentUtils::WrapNative(aCx
, aData
->mStack
, &stackVal
);
1717 if (NS_WARN_IF(NS_FAILED(rv
))) {
1721 js::SetFunctionNativeReserved(funObj
, SLOT_STACKOBJ
, stackVal
);
1722 js::SetFunctionNativeReserved(funObj
, SLOT_RAW_STACK
,
1723 JS::PrivateValue(aData
->mStack
.get()));
1725 if (NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "stacktrace", funObj
,
1726 nullptr, JSPROP_ENUMERATE
))) {
1737 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1739 bool FlushOutput(JSContext
* aCx
, Sequence
<JS::Value
>& aSequence
,
1740 nsString
& aOutput
) {
1741 if (!aOutput
.IsEmpty()) {
1742 JS::Rooted
<JSString
*> str(
1743 aCx
, JS_NewUCStringCopyN(aCx
, aOutput
.get(), aOutput
.Length()));
1744 if (NS_WARN_IF(!str
)) {
1748 if (NS_WARN_IF(!aSequence
.AppendElement(JS::StringValue(str
), fallible
))) {
1760 static void MakeFormatString(nsCString
& aFormat
, int32_t aInteger
,
1761 int32_t aMantissa
, char aCh
) {
1762 aFormat
.Append('%');
1763 if (aInteger
>= 0) {
1764 aFormat
.AppendInt(aInteger
);
1767 if (aMantissa
>= 0) {
1768 aFormat
.Append('.');
1769 aFormat
.AppendInt(aMantissa
);
1772 aFormat
.Append(aCh
);
1775 // If the first JS::Value of the array is a string, this method uses it to
1776 // format a string. The supported sequences are:
1780 // %o,%O - a JS object.
1781 // %c - style string.
1782 // The output is an array where any object is a separated item, the rest is
1783 // unified in a format string.
1784 // Example if the input is:
1785 // "string: %s, integer: %d, object: %o, double: %d", 's', 1, window, 0.9
1786 // The output will be:
1787 // [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
1789 // The aStyles array is populated with the style strings that the function
1790 // finds based the format string. The index of the styles matches the indexes
1791 // of elements that need the custom styling from aSequence. For elements with
1792 // no custom styling the array is padded with null elements.
1793 static bool ProcessArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
,
1794 Sequence
<JS::Value
>& aSequence
,
1795 Sequence
<nsString
>& aStyles
) {
1796 // This method processes the arguments as format strings (%d, %i, %s...)
1797 // only if the first element of them is a valid and not-empty string.
1799 if (aData
.IsEmpty()) {
1803 if (aData
.Length() == 1 || !aData
[0].isString()) {
1804 return aSequence
.AppendElements(aData
, fallible
);
1807 JS::Rooted
<JS::Value
> format(aCx
, aData
[0]);
1808 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, format
));
1809 if (NS_WARN_IF(!jsString
)) {
1813 nsAutoJSString string
;
1814 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
1818 if (string
.IsEmpty()) {
1819 return aSequence
.AppendElements(aData
, fallible
);
1822 nsString::const_iterator start
, end
;
1823 string
.BeginReading(start
);
1824 string
.EndReading(end
);
1829 while (start
!= end
) {
1830 if (*start
!= '%') {
1831 output
.Append(*start
);
1842 if (*start
== '%') {
1843 output
.Append(*start
);
1851 int32_t integer
= -1;
1852 int32_t mantissa
= -1;
1854 // Let's parse %<number>.<number> for %d and %f
1855 if (*start
>= '0' && *start
<= '9') {
1859 integer
= integer
* 10 + *start
- '0';
1862 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1870 if (*start
== '.') {
1879 // '.' must be followed by a number.
1880 if (*start
< '0' || *start
> '9') {
1888 mantissa
= mantissa
* 10 + *start
- '0';
1891 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1906 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
1910 JS::Rooted
<JS::Value
> v(aCx
);
1911 if (index
< aData
.Length()) {
1915 if (NS_WARN_IF(!aSequence
.AppendElement(v
, fallible
))) {
1923 // If there isn't any output but there's already a style, then
1924 // discard the previous style and use the next one instead.
1925 if (output
.IsEmpty() && !aStyles
.IsEmpty()) {
1926 aStyles
.RemoveLastElement();
1929 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
1933 if (index
< aData
.Length()) {
1934 JS::Rooted
<JS::Value
> v(aCx
, aData
[index
++]);
1935 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, v
));
1936 if (NS_WARN_IF(!jsString
)) {
1940 int32_t diff
= aSequence
.Length() - aStyles
.Length();
1942 for (int32_t i
= 0; i
< diff
; i
++) {
1943 if (NS_WARN_IF(!aStyles
.AppendElement(VoidString(), fallible
))) {
1949 nsAutoJSString string
;
1950 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
1954 if (NS_WARN_IF(!aStyles
.AppendElement(string
, fallible
))) {
1962 if (index
< aData
.Length()) {
1963 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1964 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
1965 if (NS_WARN_IF(!jsString
)) {
1970 if (NS_WARN_IF(!v
.init(aCx
, jsString
))) {
1980 if (index
< aData
.Length()) {
1981 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1984 if (NS_WARN_IF(!JS::ToInt32(aCx
, value
, &v
))) {
1989 MakeFormatString(format
, integer
, mantissa
, 'd');
1990 output
.AppendPrintf(format
.get(), v
);
1995 if (index
< aData
.Length()) {
1996 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1999 if (NS_WARN_IF(!JS::ToNumber(aCx
, value
, &v
))) {
2003 // nspr returns "nan", but we want to expose it as "NaN"
2004 if (std::isnan(v
)) {
2005 output
.AppendFloat(v
);
2008 MakeFormatString(format
, integer
, mantissa
, 'f');
2009 output
.AppendPrintf(format
.get(), v
);
2020 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
2024 // Discard trailing style element if there is no output to apply it to.
2025 if (aStyles
.Length() > aSequence
.Length()) {
2026 aStyles
.TruncateLength(aSequence
.Length());
2029 // The rest of the array, if unused by the format string.
2030 for (; index
< aData
.Length(); ++index
) {
2031 if (NS_WARN_IF(!aSequence
.AppendElement(aData
[index
], fallible
))) {
2039 // Stringify and Concat all the JS::Value in a single string using ' ' as
2040 // separator. The new group name will be stored in aGroupStack array.
2041 static void ComposeAndStoreGroupName(JSContext
* aCx
,
2042 const Sequence
<JS::Value
>& aData
,
2044 nsTArray
<nsString
>* aGroupStack
) {
2046 aName
, u
" "_ns
, aData
, [aCx
](nsAString
& dest
, const JS::Value
& valueRef
) {
2047 JS::Rooted
<JS::Value
> value(aCx
, valueRef
);
2048 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2053 nsAutoJSString string
;
2054 if (!string
.init(aCx
, jsString
)) {
2058 dest
.Append(string
);
2061 aGroupStack
->AppendElement(aName
);
2064 // Remove the last group name and return that name. It returns false if
2065 // aGroupStack is empty.
2066 static bool UnstoreGroupName(nsAString
& aName
,
2067 nsTArray
<nsString
>* aGroupStack
) {
2068 if (aGroupStack
->IsEmpty()) {
2072 aName
= aGroupStack
->PopLastElement();
2076 Console::TimerStatus
Console::StartTimer(JSContext
* aCx
, const JS::Value
& aName
,
2077 DOMHighResTimeStamp aTimestamp
,
2078 nsAString
& aTimerLabel
,
2079 DOMHighResTimeStamp
* aTimerValue
) {
2080 AssertIsOnOwningThread();
2081 MOZ_ASSERT(aTimerValue
);
2085 if (NS_WARN_IF(mTimerRegistry
.Count() >= MAX_PAGE_TIMERS
)) {
2086 return eTimerMaxReached
;
2089 JS::Rooted
<JS::Value
> name(aCx
, aName
);
2090 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
2091 if (NS_WARN_IF(!jsString
)) {
2092 return eTimerJSException
;
2095 nsAutoJSString label
;
2096 if (NS_WARN_IF(!label
.init(aCx
, jsString
))) {
2097 return eTimerJSException
;
2100 aTimerLabel
= label
;
2102 if (mTimerRegistry
.WithEntryHandle(label
, [&](auto&& entry
) {
2106 entry
.Insert(aTimestamp
);
2109 return eTimerAlreadyExists
;
2112 *aTimerValue
= aTimestamp
;
2117 JS::Value
Console::CreateStartTimerValue(JSContext
* aCx
,
2118 const nsAString
& aTimerLabel
,
2119 TimerStatus aTimerStatus
) {
2120 MOZ_ASSERT(aTimerStatus
!= eTimerUnknown
);
2122 if (aTimerStatus
!= eTimerDone
) {
2123 return CreateTimerError(aCx
, aTimerLabel
, aTimerStatus
);
2126 RootedDictionary
<ConsoleTimerStart
> timer(aCx
);
2128 timer
.mName
= aTimerLabel
;
2130 JS::Rooted
<JS::Value
> value(aCx
);
2131 if (!ToJSValue(aCx
, timer
, &value
)) {
2132 return JS::UndefinedValue();
2138 Console::TimerStatus
Console::LogTimer(JSContext
* aCx
, const JS::Value
& aName
,
2139 DOMHighResTimeStamp aTimestamp
,
2140 nsAString
& aTimerLabel
,
2141 double* aTimerDuration
,
2142 bool aCancelTimer
) {
2143 AssertIsOnOwningThread();
2144 MOZ_ASSERT(aTimerDuration
);
2146 *aTimerDuration
= 0;
2148 JS::Rooted
<JS::Value
> name(aCx
, aName
);
2149 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
2150 if (NS_WARN_IF(!jsString
)) {
2151 return eTimerJSException
;
2155 if (NS_WARN_IF(!key
.init(aCx
, jsString
))) {
2156 return eTimerJSException
;
2161 DOMHighResTimeStamp value
= 0;
2164 if (!mTimerRegistry
.Remove(key
, &value
)) {
2165 NS_WARNING("mTimerRegistry entry not found");
2166 return eTimerDoesntExist
;
2169 if (!mTimerRegistry
.Get(key
, &value
)) {
2170 NS_WARNING("mTimerRegistry entry not found");
2171 return eTimerDoesntExist
;
2175 *aTimerDuration
= aTimestamp
- value
;
2180 JS::Value
Console::CreateLogOrEndTimerValue(JSContext
* aCx
,
2181 const nsAString
& aLabel
,
2183 TimerStatus aStatus
) {
2184 if (aStatus
!= eTimerDone
) {
2185 return CreateTimerError(aCx
, aLabel
, aStatus
);
2188 RootedDictionary
<ConsoleTimerLogOrEnd
> timer(aCx
);
2189 timer
.mName
= aLabel
;
2190 timer
.mDuration
= aDuration
;
2192 JS::Rooted
<JS::Value
> value(aCx
);
2193 if (!ToJSValue(aCx
, timer
, &value
)) {
2194 return JS::UndefinedValue();
2201 JS::Value
Console::CreateTimerError(JSContext
* aCx
, const nsAString
& aLabel
,
2202 TimerStatus aStatus
) {
2203 MOZ_ASSERT(aStatus
!= eTimerUnknown
&& aStatus
!= eTimerDone
);
2205 RootedDictionary
<ConsoleTimerError
> error(aCx
);
2207 error
.mName
= aLabel
;
2210 case eTimerAlreadyExists
:
2211 error
.mError
.AssignLiteral("timerAlreadyExists");
2214 case eTimerDoesntExist
:
2215 error
.mError
.AssignLiteral("timerDoesntExist");
2218 case eTimerJSException
:
2219 error
.mError
.AssignLiteral("timerJSError");
2222 case eTimerMaxReached
:
2223 error
.mError
.AssignLiteral("maxTimersExceeded");
2227 MOZ_CRASH("Unsupported status");
2231 JS::Rooted
<JS::Value
> value(aCx
);
2232 if (!ToJSValue(aCx
, error
, &value
)) {
2233 return JS::UndefinedValue();
2239 uint32_t Console::IncreaseCounter(JSContext
* aCx
,
2240 const Sequence
<JS::Value
>& aArguments
,
2241 nsAString
& aCountLabel
) {
2242 AssertIsOnOwningThread();
2244 ConsoleCommon::ClearException
ce(aCx
);
2246 MOZ_ASSERT(!aArguments
.IsEmpty());
2248 JS::Rooted
<JS::Value
> labelValue(aCx
, aArguments
[0]);
2249 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, labelValue
));
2251 return 0; // We cannot continue.
2254 nsAutoJSString string
;
2255 if (!string
.init(aCx
, jsString
)) {
2256 return 0; // We cannot continue.
2259 aCountLabel
= string
;
2261 const bool maxCountersReached
= mCounterRegistry
.Count() >= MAX_PAGE_COUNTERS
;
2262 return mCounterRegistry
.WithEntryHandle(
2263 aCountLabel
, [maxCountersReached
](auto&& entry
) -> uint32_t {
2267 if (maxCountersReached
) {
2268 return MAX_PAGE_COUNTERS
;
2272 return entry
.Data();
2276 uint32_t Console::ResetCounter(JSContext
* aCx
,
2277 const Sequence
<JS::Value
>& aArguments
,
2278 nsAString
& aCountLabel
) {
2279 AssertIsOnOwningThread();
2281 ConsoleCommon::ClearException
ce(aCx
);
2283 MOZ_ASSERT(!aArguments
.IsEmpty());
2285 JS::Rooted
<JS::Value
> labelValue(aCx
, aArguments
[0]);
2286 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, labelValue
));
2288 return 0; // We cannot continue.
2291 nsAutoJSString string
;
2292 if (!string
.init(aCx
, jsString
)) {
2293 return 0; // We cannot continue.
2296 aCountLabel
= string
;
2298 if (mCounterRegistry
.Remove(aCountLabel
)) {
2302 // Let's return something different than 0 if the key doesn't exist.
2303 return MAX_PAGE_COUNTERS
;
2306 // This method generates a ConsoleCounter dictionary as JS::Value. If
2307 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
2308 // instead. See IncreaseCounter.
2309 // * aCx - this is the context that will root the returned value.
2310 // * aCountLabel - this label must be what IncreaseCounter received as
2312 // * aCountValue - the return value of IncreaseCounter.
2313 static JS::Value
CreateCounterOrResetCounterValue(JSContext
* aCx
,
2314 const nsAString
& aCountLabel
,
2315 uint32_t aCountValue
) {
2316 ConsoleCommon::ClearException
ce(aCx
);
2318 if (aCountValue
== MAX_PAGE_COUNTERS
) {
2319 RootedDictionary
<ConsoleCounterError
> error(aCx
);
2320 error
.mLabel
= aCountLabel
;
2321 error
.mError
.AssignLiteral("counterDoesntExist");
2323 JS::Rooted
<JS::Value
> value(aCx
);
2324 if (!ToJSValue(aCx
, error
, &value
)) {
2325 return JS::UndefinedValue();
2331 RootedDictionary
<ConsoleCounter
> data(aCx
);
2332 data
.mLabel
= aCountLabel
;
2333 data
.mCount
= aCountValue
;
2335 JS::Rooted
<JS::Value
> value(aCx
);
2336 if (!ToJSValue(aCx
, data
, &value
)) {
2337 return JS::UndefinedValue();
2344 bool Console::ShouldIncludeStackTrace(MethodName aMethodName
) {
2345 switch (aMethodName
) {
2347 case MethodException
:
2356 JSObject
* MainThreadConsoleData::GetOrCreateSandbox(JSContext
* aCx
,
2357 nsIPrincipal
* aPrincipal
) {
2358 AssertIsOnMainThread();
2361 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
2362 MOZ_ASSERT(xpc
, "This should never be null!");
2364 JS::Rooted
<JSObject
*> sandbox(aCx
);
2365 nsresult rv
= xpc
->CreateSandbox(aCx
, aPrincipal
, sandbox
.address());
2366 if (NS_WARN_IF(NS_FAILED(rv
))) {
2370 mSandbox
= new JSObjectHolder(aCx
, sandbox
);
2373 return mSandbox
->GetJSObject();
2376 bool Console::StoreCallData(JSContext
* aCx
, ConsoleCallData
* aCallData
,
2377 const Sequence
<JS::Value
>& aArguments
) {
2378 AssertIsOnOwningThread();
2380 if (NS_WARN_IF(!mArgumentStorage
.growBy(1))) {
2383 if (!mArgumentStorage
.end()[-1].Initialize(aCx
, aArguments
)) {
2384 mArgumentStorage
.shrinkBy(1);
2388 MOZ_ASSERT(aCallData
);
2389 MOZ_ASSERT(!mCallDataStorage
.Contains(aCallData
));
2391 mCallDataStorage
.AppendElement(aCallData
);
2393 MOZ_ASSERT(mCallDataStorage
.Length() == mArgumentStorage
.length());
2395 if (mCallDataStorage
.Length() > STORAGE_MAX_EVENTS
) {
2396 mCallDataStorage
.RemoveElementAt(0);
2397 mArgumentStorage
.erase(&mArgumentStorage
[0]);
2402 void Console::UnstoreCallData(ConsoleCallData
* aCallData
) {
2403 AssertIsOnOwningThread();
2405 MOZ_ASSERT(aCallData
);
2406 MOZ_ASSERT(mCallDataStorage
.Length() == mArgumentStorage
.length());
2408 size_t index
= mCallDataStorage
.IndexOf(aCallData
);
2409 // It can be that mCallDataStorage has been already cleaned in case the
2410 // processing of the argument of some Console methods triggers the
2412 if (index
== mCallDataStorage
.NoIndex
) {
2416 mCallDataStorage
.RemoveElementAt(index
);
2417 mArgumentStorage
.erase(&mArgumentStorage
[index
]);
2420 void Console::NotifyHandler(JSContext
* aCx
,
2421 const Sequence
<JS::Value
>& aArguments
,
2422 ConsoleCallData
* aCallData
) {
2423 AssertIsOnOwningThread();
2424 MOZ_ASSERT(!NS_IsMainThread());
2425 MOZ_ASSERT(aCallData
);
2427 if (!mConsoleEventNotifier
) {
2431 JS::Rooted
<JS::Value
> value(aCx
);
2433 JS::Rooted
<JSObject
*> callableGlobal(
2434 aCx
, mConsoleEventNotifier
->CallbackGlobalOrNull());
2435 if (NS_WARN_IF(!callableGlobal
)) {
2439 // aCx and aArguments are in the same compartment because this method is
2440 // called directly when a Console.something() runs.
2441 // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
2443 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2444 aCx
, aArguments
, callableGlobal
, &value
, aCallData
, &mGroupStack
))) {
2448 JS::Rooted
<JS::Value
> ignored(aCx
);
2449 RefPtr
<AnyCallback
> notifier(mConsoleEventNotifier
);
2450 notifier
->Call(value
, &ignored
);
2453 void Console::RetrieveConsoleEvents(JSContext
* aCx
,
2454 nsTArray
<JS::Value
>& aEvents
,
2456 AssertIsOnOwningThread();
2458 // We don't want to expose this functionality to main-thread yet.
2459 MOZ_ASSERT(!NS_IsMainThread());
2461 JS::Rooted
<JSObject
*> targetScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
2463 for (uint32_t i
= 0; i
< mArgumentStorage
.length(); ++i
) {
2464 JS::Rooted
<JS::Value
> value(aCx
);
2466 JS::Rooted
<JSObject
*> sequenceScope(aCx
, mArgumentStorage
[i
].Global());
2467 JSAutoRealm
ar(aCx
, sequenceScope
);
2469 Sequence
<JS::Value
> sequence
;
2470 SequenceRooter
<JS::Value
> arguments(aCx
, &sequence
);
2472 if (!mArgumentStorage
[i
].PopulateArgumentsSequence(sequence
)) {
2473 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2477 // Here we have aCx and sequence in the same compartment.
2478 // targetScope is the destination scope and value will be populated in its
2480 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2481 aCx
, sequence
, targetScope
, &value
, mCallDataStorage
[i
],
2483 aRv
.Throw(NS_ERROR_FAILURE
);
2487 aEvents
.AppendElement(value
);
2491 void Console::SetConsoleEventHandler(AnyCallback
* aHandler
) {
2492 AssertIsOnOwningThread();
2494 // We don't want to expose this functionality to main-thread yet.
2495 MOZ_ASSERT(!NS_IsMainThread());
2497 mConsoleEventNotifier
= aHandler
;
2500 void Console::AssertIsOnOwningThread() const {
2501 NS_ASSERT_OWNINGTHREAD(Console
);
2504 bool Console::IsShuttingDown() const {
2505 MOZ_ASSERT(mStatus
!= eUnknown
);
2506 return mStatus
== eShuttingDown
;
2510 already_AddRefed
<Console
> Console::GetConsole(const GlobalObject
& aGlobal
) {
2512 RefPtr
<Console
> console
= GetConsoleInternal(aGlobal
, rv
);
2513 if (NS_WARN_IF(rv
.Failed()) || !console
) {
2514 rv
.SuppressException();
2518 console
->AssertIsOnOwningThread();
2520 if (console
->IsShuttingDown()) {
2524 return console
.forget();
2528 already_AddRefed
<Console
> Console::GetConsoleInternal(
2529 const GlobalObject
& aGlobal
, ErrorResult
& aRv
) {
2531 if (NS_IsMainThread()) {
2532 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2533 do_QueryInterface(aGlobal
.GetAsSupports());
2535 // we are probably running a chrome script.
2537 RefPtr
<Console
> console
= new Console(aGlobal
.Context(), nullptr, 0, 0);
2538 console
->Initialize(aRv
);
2539 if (NS_WARN_IF(aRv
.Failed())) {
2543 return console
.forget();
2546 nsGlobalWindowInner
* window
= nsGlobalWindowInner::Cast(innerWindow
);
2547 return window
->GetConsole(aGlobal
.Context(), aRv
);
2551 nsCOMPtr
<WorkletGlobalScope
> workletScope
=
2552 do_QueryInterface(aGlobal
.GetAsSupports());
2554 WorkletThread::AssertIsOnWorkletThread();
2555 return workletScope
->GetConsole(aGlobal
.Context(), aRv
);
2559 MOZ_ASSERT(!NS_IsMainThread());
2561 JSContext
* cx
= aGlobal
.Context();
2562 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
2563 MOZ_ASSERT(workerPrivate
);
2565 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
2566 if (NS_WARN_IF(!global
)) {
2570 WorkerGlobalScope
* scope
= workerPrivate
->GlobalScope();
2573 // Normal worker scope.
2574 if (scope
== global
) {
2575 return scope
->GetConsole(aRv
);
2578 // Debugger worker scope
2580 WorkerDebuggerGlobalScope
* debuggerScope
=
2581 workerPrivate
->DebuggerGlobalScope();
2582 MOZ_ASSERT(debuggerScope
);
2583 MOZ_ASSERT(debuggerScope
== global
, "Which kind of global do we have?");
2585 return debuggerScope
->GetConsole(aRv
);
2588 bool Console::MonotonicTimer(JSContext
* aCx
, MethodName aMethodName
,
2589 const Sequence
<JS::Value
>& aData
,
2590 DOMHighResTimeStamp
* aTimeStamp
) {
2591 if (nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
= do_QueryInterface(mGlobal
)) {
2592 nsGlobalWindowInner
* win
= nsGlobalWindowInner::Cast(innerWindow
);
2595 RefPtr
<Performance
> performance
= win
->GetPerformance();
2600 *aTimeStamp
= performance
->Now();
2602 nsDocShell
* docShell
= static_cast<nsDocShell
*>(win
->GetDocShell());
2603 bool isTimelineRecording
= TimelineConsumers::HasConsumer(docShell
);
2605 // The 'timeStamp' recordings do not need an argument; use empty string
2606 // if no arguments passed in.
2607 if (isTimelineRecording
&& aMethodName
== MethodTimeStamp
) {
2608 JS::Rooted
<JS::Value
> value(
2609 aCx
, aData
.Length() == 0 ? JS_GetEmptyStringValue(aCx
) : aData
[0]);
2610 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2616 if (!key
.init(aCx
, jsString
)) {
2620 TimelineConsumers::AddMarkerForDocShell(
2621 docShell
, MakeUnique
<TimestampTimelineMarker
>(key
));
2623 // For `console.time(foo)` and `console.timeEnd(foo)`.
2624 else if (isTimelineRecording
&& aData
.Length() == 1) {
2625 JS::Rooted
<JS::Value
> value(aCx
, aData
[0]);
2626 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2632 if (!key
.init(aCx
, jsString
)) {
2636 TimelineConsumers::AddMarkerForDocShell(
2638 MakeUnique
<ConsoleTimelineMarker
>(key
, aMethodName
== MethodTime
2639 ? MarkerTracingType::START
2640 : MarkerTracingType::END
));
2646 if (NS_IsMainThread()) {
2647 *aTimeStamp
= (TimeStamp::Now() - mCreationTimeStamp
).ToMilliseconds();
2651 if (nsCOMPtr
<WorkletGlobalScope
> workletGlobal
= do_QueryInterface(mGlobal
)) {
2652 *aTimeStamp
= workletGlobal
->TimeStampToDOMHighRes(TimeStamp::Now());
2656 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
2657 MOZ_ASSERT(workerPrivate
);
2659 *aTimeStamp
= workerPrivate
->TimeStampToDOMHighRes(TimeStamp::Now());
2664 already_AddRefed
<ConsoleInstance
> Console::CreateInstance(
2665 const GlobalObject
& aGlobal
, const ConsoleInstanceOptions
& aOptions
) {
2666 RefPtr
<ConsoleInstance
> console
=
2667 new ConsoleInstance(aGlobal
.Context(), aOptions
);
2668 return console
.forget();
2671 void Console::MaybeExecuteDumpFunction(JSContext
* aCx
,
2672 const nsAString
& aMethodName
,
2673 const Sequence
<JS::Value
>& aData
,
2674 nsIStackFrame
* aStack
) {
2675 if (!mDumpFunction
&& !mDumpToStdout
) {
2679 nsAutoString message
;
2680 message
.AssignLiteral("console.");
2681 message
.Append(aMethodName
);
2682 message
.AppendLiteral(": ");
2684 if (!mPrefix
.IsEmpty()) {
2685 message
.Append(mPrefix
);
2686 message
.AppendLiteral(": ");
2689 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
2690 JS::Rooted
<JS::Value
> v(aCx
, aData
[i
]);
2691 JS::Rooted
<JSString
*> jsString(aCx
, JS_ValueToSource(aCx
, v
));
2696 nsAutoJSString string
;
2697 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
2702 message
.AppendLiteral(" ");
2705 message
.Append(string
);
2708 message
.AppendLiteral("\n");
2710 // aStack can be null.
2712 nsCOMPtr
<nsIStackFrame
> stack(aStack
);
2715 nsAutoString filename
;
2716 stack
->GetFilename(aCx
, filename
);
2718 message
.Append(filename
);
2719 message
.AppendLiteral(" ");
2721 message
.AppendInt(stack
->GetLineNumber(aCx
));
2722 message
.AppendLiteral(" ");
2724 nsAutoString functionName
;
2725 stack
->GetName(aCx
, functionName
);
2727 message
.Append(functionName
);
2728 message
.AppendLiteral("\n");
2730 nsCOMPtr
<nsIStackFrame
> caller
= stack
->GetCaller(aCx
);
2733 caller
= stack
->GetAsyncCaller(aCx
);
2739 ExecuteDumpFunction(message
);
2742 void Console::MaybeExecuteDumpFunctionForTime(JSContext
* aCx
,
2743 MethodName aMethodName
,
2744 const nsAString
& aMethodString
,
2745 uint64_t aMonotonicTimer
,
2746 const JS::Value
& aData
) {
2747 if (!mDumpFunction
&& !mDumpToStdout
) {
2751 nsAutoString message
;
2752 message
.AssignLiteral("console.");
2753 message
.Append(aMethodString
);
2754 message
.AppendLiteral(": ");
2756 if (!mPrefix
.IsEmpty()) {
2757 message
.Append(mPrefix
);
2758 message
.AppendLiteral(": ");
2761 JS::Rooted
<JS::Value
> v(aCx
, aData
);
2762 JS::Rooted
<JSString
*> jsString(aCx
, JS_ValueToSource(aCx
, v
));
2767 nsAutoJSString string
;
2768 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
2772 message
.Append(string
);
2773 message
.AppendLiteral(" @ ");
2774 message
.AppendInt(aMonotonicTimer
);
2776 message
.AppendLiteral("\n");
2777 ExecuteDumpFunction(message
);
2780 void Console::ExecuteDumpFunction(const nsAString
& aMessage
) {
2781 if (mDumpFunction
) {
2782 RefPtr
<ConsoleInstanceDumpCallback
> dumpFunction(mDumpFunction
);
2783 dumpFunction
->Call(aMessage
);
2787 NS_ConvertUTF16toUTF8
str(aMessage
);
2788 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug
, ("%s", str
.get()));
2790 __android_log_print(ANDROID_LOG_INFO
, "Gecko", "%s", str
.get());
2792 fputs(str
.get(), stdout
);
2796 ConsoleLogLevel
PrefToValue(const nsAString
& aPref
,
2797 const ConsoleLogLevel aLevel
) {
2798 if (!NS_IsMainThread()) {
2799 NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
2800 return ConsoleLogLevel::All
;
2802 if (aPref
.IsEmpty()) {
2806 NS_ConvertUTF16toUTF8
pref(aPref
);
2807 nsAutoCString value
;
2808 nsresult rv
= Preferences::GetCString(pref
.get(), value
);
2809 if (NS_WARN_IF(NS_FAILED(rv
))) {
2813 int index
= FindEnumStringIndexImpl(value
.get(), value
.Length(),
2814 ConsoleLogLevelValues::strings
);
2815 if (NS_WARN_IF(index
< 0)) {
2817 message
.AssignLiteral("Invalid Console.maxLogLevelPref value: ");
2818 message
.Append(NS_ConvertUTF8toUTF16(value
));
2820 nsContentUtils::LogSimpleConsoleError(message
, "chrome"_ns
, false,
2821 true /* from chrome context*/);
2825 MOZ_ASSERT(index
< (int)ConsoleLogLevelValues::Count
);
2826 return static_cast<ConsoleLogLevel
>(index
);
2829 bool Console::ShouldProceed(MethodName aName
) const {
2830 ConsoleLogLevel maxLogLevel
= PrefToValue(mMaxLogLevelPref
, mMaxLogLevel
);
2831 return WebIDLLogLevelToInteger(maxLogLevel
) <=
2832 InternalLogLevelToInteger(aName
);
2835 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel
) const {
2837 case ConsoleLogLevel::All
:
2839 case ConsoleLogLevel::Debug
:
2841 case ConsoleLogLevel::Log
:
2843 case ConsoleLogLevel::Info
:
2845 case ConsoleLogLevel::Clear
:
2847 case ConsoleLogLevel::Trace
:
2849 case ConsoleLogLevel::TimeLog
:
2851 case ConsoleLogLevel::TimeEnd
:
2853 case ConsoleLogLevel::Time
:
2855 case ConsoleLogLevel::Group
:
2857 case ConsoleLogLevel::GroupEnd
:
2859 case ConsoleLogLevel::Profile
:
2861 case ConsoleLogLevel::ProfileEnd
:
2863 case ConsoleLogLevel::Dir
:
2865 case ConsoleLogLevel::Dirxml
:
2867 case ConsoleLogLevel::Warn
:
2869 case ConsoleLogLevel::Error
:
2871 case ConsoleLogLevel::Off
:
2875 "ConsoleLogLevel is out of sync with the Console implementation!");
2880 uint32_t Console::InternalLogLevelToInteger(MethodName aName
) const {
2890 case MethodException
:
2904 case MethodGroupCollapsed
:
2906 case MethodGroupEnd
:
2914 case MethodTimeStamp
:
2920 case MethodCountReset
:
2926 case MethodProfileEnd
:
2929 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
2934 bool Console::ArgumentData::Initialize(JSContext
* aCx
,
2935 const Sequence
<JS::Value
>& aArguments
) {
2936 mGlobal
= JS::CurrentGlobalOrNull(aCx
);
2938 if (NS_WARN_IF(!mArguments
.AppendElements(aArguments
, fallible
))) {
2945 void Console::ArgumentData::Trace(const TraceCallbacks
& aCallbacks
,
2947 ArgumentData
* tmp
= this;
2948 for (uint32_t i
= 0; i
< mArguments
.Length(); ++i
) {
2949 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments
[i
])
2952 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal
)
2955 bool Console::ArgumentData::PopulateArgumentsSequence(
2956 Sequence
<JS::Value
>& aSequence
) const {
2957 AssertIsOnOwningThread();
2959 for (uint32_t i
= 0; i
< mArguments
.Length(); ++i
) {
2960 if (NS_WARN_IF(!aSequence
.AppendElement(mArguments
[i
], fallible
))) {
2968 } // namespace mozilla::dom