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/ElementBinding.h"
18 #include "mozilla/dom/Exceptions.h"
19 #include "mozilla/dom/File.h"
20 #include "mozilla/dom/FunctionBinding.h"
21 #include "mozilla/dom/Performance.h"
22 #include "mozilla/dom/PromiseBinding.h"
23 #include "mozilla/dom/ScriptSettings.h"
24 #include "mozilla/dom/StructuredCloneHolder.h"
25 #include "mozilla/dom/ToJSValue.h"
26 #include "mozilla/dom/WorkerRunnable.h"
27 #include "mozilla/dom/WorkerScope.h"
28 #include "mozilla/dom/WorkletGlobalScope.h"
29 #include "mozilla/dom/WorkletImpl.h"
30 #include "mozilla/dom/WorkletThread.h"
31 #include "mozilla/dom/RootedDictionary.h"
32 #include "mozilla/BasePrincipal.h"
33 #include "mozilla/HoldDropJSObjects.h"
34 #include "mozilla/JSObjectHolder.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/StaticPrefs_devtools.h"
38 #include "mozilla/StaticPrefs_dom.h"
39 #include "nsCycleCollectionParticipant.h"
40 #include "nsDOMNavigationTiming.h"
41 #include "nsGlobalWindowInner.h"
42 #include "nsJSUtils.h"
43 #include "nsNetUtil.h"
44 #include "xpcpublic.h"
45 #include "nsContentUtils.h"
46 #include "nsDocShell.h"
47 #include "nsProxyRelease.h"
48 #include "nsReadableUtils.h"
49 #include "mozilla/ConsoleTimelineMarker.h"
50 #include "mozilla/TimestampTimelineMarker.h"
52 #include "nsIConsoleAPIStorage.h"
53 #include "nsIException.h" // for nsIStackFrame
54 #include "nsIInterfaceRequestorUtils.h"
55 #include "nsILoadContext.h"
56 #include "nsISensitiveInfoHiddenURI.h"
57 #include "nsISupportsPrimitives.h"
58 #include "nsIWebNavigation.h"
59 #include "nsIXPConnect.h"
61 // The maximum allowed number of concurrent timers per page.
62 #define MAX_PAGE_TIMERS 10000
64 // The maximum allowed number of concurrent counters per page.
65 #define MAX_PAGE_COUNTERS 10000
67 // The maximum stacktrace depth when populating the stacktrace array used for
69 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
71 // This tags are used in the Structured Clone Algorithm to move js values from
72 // worker thread to main thread
73 #define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
75 // This value is taken from ConsoleAPIStorage.js
76 #define STORAGE_MAX_EVENTS 1000
78 using namespace mozilla::dom::exceptions
;
80 namespace mozilla::dom
{
82 struct ConsoleStructuredCloneData
{
83 nsCOMPtr
<nsIGlobalObject
> mGlobal
;
84 nsTArray
<RefPtr
<BlobImpl
>> mBlobs
;
87 static void ComposeAndStoreGroupName(JSContext
* aCx
,
88 const Sequence
<JS::Value
>& aData
,
90 nsTArray
<nsString
>* aGroupStack
);
91 static bool UnstoreGroupName(nsAString
& aName
, nsTArray
<nsString
>* aGroupStack
);
93 static bool ProcessArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
,
94 Sequence
<JS::Value
>& aSequence
,
95 Sequence
<nsString
>& aStyles
);
97 static JS::Value
CreateCounterOrResetCounterValue(JSContext
* aCx
,
98 const nsAString
& aCountLabel
,
99 uint32_t aCountValue
);
102 * Console API in workers uses the Structured Clone Algorithm to move any value
103 * from the worker thread to the main-thread. Some object cannot be moved and,
104 * in these cases, we convert them to strings.
105 * It's not the best, but at least we are able to show something.
108 class ConsoleCallData final
{
110 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData
)
112 ConsoleCallData(Console::MethodName aName
, const nsAString
& aString
,
114 : mConsoleID(aConsole
->mConsoleID
),
115 mPrefix(aConsole
->mPrefix
),
117 mMicroSecondTimeStamp(JS_Now()),
119 mStartTimerStatus(Console::eTimerUnknown
),
120 mLogTimerDuration(0),
121 mLogTimerStatus(Console::eTimerUnknown
),
122 mCountValue(MAX_PAGE_COUNTERS
),
126 mMethodString(aString
) {}
128 void SetIDs(uint64_t aOuterID
, uint64_t aInnerID
) {
129 MOZ_ASSERT(mIDType
== eUnknown
);
131 mOuterIDNumber
= aOuterID
;
132 mInnerIDNumber
= aInnerID
;
136 void SetIDs(const nsAString
& aOuterID
, const nsAString
& aInnerID
) {
137 MOZ_ASSERT(mIDType
== eUnknown
);
139 mOuterIDString
= aOuterID
;
140 mInnerIDString
= aInnerID
;
144 void SetOriginAttributes(const OriginAttributes
& aOriginAttributes
) {
145 mOriginAttributes
= aOriginAttributes
;
148 void SetAddonId(nsIPrincipal
* aPrincipal
) {
149 nsAutoString addonId
;
150 aPrincipal
->GetAddonId(addonId
);
155 void AssertIsOnOwningThread() const {
156 NS_ASSERT_OWNINGTHREAD(ConsoleCallData
);
159 const nsString mConsoleID
;
160 const nsString mPrefix
;
162 const Console::MethodName mMethodName
;
163 int64_t mMicroSecondTimeStamp
;
165 // These values are set in the owning thread and they contain the timestamp of
166 // when the new timer has started, the name of it and the status of the
167 // creation of it. If status is false, something went wrong. User
168 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
169 // monotonicTimer from Performance.now();
170 // They will be set on the owning thread and never touched again on that
171 // thread. They will be used in order to create a ConsoleTimerStart dictionary
172 // when console.time() is used.
173 DOMHighResTimeStamp mStartTimerValue
;
174 nsString mStartTimerLabel
;
175 Console::TimerStatus mStartTimerStatus
;
177 // These values are set in the owning thread and they contain the duration,
178 // the name and the status of the LogTimer method. If status is false,
179 // something went wrong. They will be set on the owning thread and never
180 // touched again on that thread. They will be used in order to create a
181 // ConsoleTimerLogOrEnd dictionary. This members are set when
182 // console.timeEnd() or console.timeLog() are called.
183 double mLogTimerDuration
;
184 nsString mLogTimerLabel
;
185 Console::TimerStatus mLogTimerStatus
;
187 // These 2 values are set by IncreaseCounter or ResetCounter on the owning
188 // thread and they are used by CreateCounterOrResetCounterValue.
189 // These members are set when console.count() or console.countReset() are
191 nsString mCountLabel
;
192 uint32_t mCountValue
;
194 // The concept of outerID and innerID is misleading because when a
195 // ConsoleCallData is created from a window, these are the window IDs, but
196 // when the object is created from a SharedWorker, a ServiceWorker or a
197 // subworker of a ChromeWorker these IDs are the type of worker and the
198 // filename of the callee.
199 // In Console.sys.mjs the ID is 'jsm'.
200 enum { eString
, eNumber
, eUnknown
} mIDType
;
202 uint64_t mOuterIDNumber
;
203 nsString mOuterIDString
;
205 uint64_t mInnerIDNumber
;
206 nsString mInnerIDString
;
208 OriginAttributes mOriginAttributes
;
212 const nsString mMethodString
;
214 // Stack management is complicated, because we want to do it as
215 // lazily as possible. Therefore, we have the following behavior:
216 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
217 // 2) mReifiedStack is initialized if we're created in a worker.
218 // 3) mStack is set (possibly to null if there is no JS on the stack) if
219 // we're created on main thread.
220 Maybe
<ConsoleStackEntry
> mTopStackFrame
;
221 Maybe
<nsTArray
<ConsoleStackEntry
>> mReifiedStack
;
222 nsCOMPtr
<nsIStackFrame
> mStack
;
225 ~ConsoleCallData() = default;
227 NS_DECL_OWNINGTHREAD
;
230 // MainThreadConsoleData instances are created on the Console thread and
231 // referenced from both main and Console threads in order to provide the same
232 // object for any ConsoleRunnables relating to the same Console. A Console
233 // owns a MainThreadConsoleData; MainThreadConsoleData does not keep its
235 class MainThreadConsoleData final
{
236 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData
);
238 JSObject
* GetOrCreateSandbox(JSContext
* aCx
, nsIPrincipal
* aPrincipal
);
239 // This method must receive aCx and aArguments in the same JS::Compartment.
240 void ProcessCallData(JSContext
* aCx
, ConsoleCallData
* aData
,
241 const Sequence
<JS::Value
>& aArguments
);
244 ~MainThreadConsoleData() {
245 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
247 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
251 // All members, except for mRefCnt, are accessed only on the main thread,
252 // except in MainThreadConsoleData destruction, at which point there are no
254 nsCOMPtr
<nsIConsoleAPIStorage
> mStorage
;
255 RefPtr
<JSObjectHolder
> mSandbox
;
256 nsTArray
<nsString
> mGroupStack
;
259 // This base class must be extended for Worker and for Worklet.
260 class ConsoleRunnable
: public StructuredCloneHolderBase
{
262 ~ConsoleRunnable() override
{
263 MOZ_ASSERT(!mClonedData
.mGlobal
,
264 "mClonedData.mGlobal is set and cleared in a main thread scope");
265 // Clear the StructuredCloneHolderBase class.
270 JSObject
* CustomReadHandler(JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
271 const JS::CloneDataPolicy
& aCloneDataPolicy
,
272 uint32_t aTag
, uint32_t aIndex
) override
{
273 AssertIsOnMainThread();
275 if (aTag
== CONSOLE_TAG_BLOB
) {
276 MOZ_ASSERT(mClonedData
.mBlobs
.Length() > aIndex
);
278 JS::Rooted
<JS::Value
> val(aCx
);
280 nsCOMPtr
<nsIGlobalObject
> global
= mClonedData
.mGlobal
;
282 Blob::Create(global
, mClonedData
.mBlobs
.ElementAt(aIndex
));
283 if (!ToJSValue(aCx
, blob
, &val
)) {
288 return &val
.toObject();
291 MOZ_CRASH("No other tags are supported.");
295 bool CustomWriteHandler(JSContext
* aCx
, JSStructuredCloneWriter
* aWriter
,
296 JS::Handle
<JSObject
*> aObj
,
297 bool* aSameProcessScopeRequired
) override
{
299 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob
, aObj
, blob
))) {
300 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter
, CONSOLE_TAG_BLOB
,
301 mClonedData
.mBlobs
.Length()))) {
305 mClonedData
.mBlobs
.AppendElement(blob
->Impl());
309 if (!JS_ObjectNotWritten(aWriter
, aObj
)) {
313 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectOrNullValue(aObj
));
314 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
315 if (NS_WARN_IF(!jsString
)) {
319 if (NS_WARN_IF(!JS_WriteString(aWriter
, jsString
))) {
326 // Helper method for CallData
327 void ProcessCallData(JSContext
* aCx
, MainThreadConsoleData
* aConsoleData
,
328 ConsoleCallData
* aCallData
) {
329 AssertIsOnMainThread();
331 ConsoleCommon::ClearException
ce(aCx
);
333 JS::Rooted
<JS::Value
> argumentsValue(aCx
);
334 if (!Read(aCx
, &argumentsValue
)) {
338 MOZ_ASSERT(argumentsValue
.isObject());
340 JS::Rooted
<JSObject
*> argumentsObj(aCx
, &argumentsValue
.toObject());
343 if (!JS::GetArrayLength(aCx
, argumentsObj
, &length
)) {
347 Sequence
<JS::Value
> values
;
348 SequenceRooter
<JS::Value
> arguments(aCx
, &values
);
350 for (uint32_t i
= 0; i
< length
; ++i
) {
351 JS::Rooted
<JS::Value
> value(aCx
);
353 if (!JS_GetElement(aCx
, argumentsObj
, i
, &value
)) {
357 if (!values
.AppendElement(value
, fallible
)) {
362 MOZ_ASSERT(values
.Length() == length
);
364 aConsoleData
->ProcessCallData(aCx
, aCallData
, values
);
368 bool WriteArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
) {
369 ConsoleCommon::ClearException
ce(aCx
);
371 JS::Rooted
<JSObject
*> arguments(
372 aCx
, JS::NewArrayObject(aCx
, aArguments
.Length()));
373 if (NS_WARN_IF(!arguments
)) {
377 JS::Rooted
<JS::Value
> arg(aCx
);
378 for (uint32_t i
= 0; i
< aArguments
.Length(); ++i
) {
381 !JS_DefineElement(aCx
, arguments
, i
, arg
, JSPROP_ENUMERATE
))) {
386 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*arguments
));
387 return WriteData(aCx
, value
);
390 // Helper method for Profile calls
391 void ProcessProfileData(JSContext
* aCx
, Console::MethodName aMethodName
,
392 const nsAString
& aAction
) {
393 AssertIsOnMainThread();
395 ConsoleCommon::ClearException
ce(aCx
);
397 JS::Rooted
<JS::Value
> argumentsValue(aCx
);
398 bool ok
= Read(aCx
, &argumentsValue
);
399 mClonedData
.mGlobal
= nullptr;
405 MOZ_ASSERT(argumentsValue
.isObject());
406 JS::Rooted
<JSObject
*> argumentsObj(aCx
, &argumentsValue
.toObject());
407 if (NS_WARN_IF(!argumentsObj
)) {
412 if (!JS::GetArrayLength(aCx
, argumentsObj
, &length
)) {
416 Sequence
<JS::Value
> arguments
;
418 for (uint32_t i
= 0; i
< length
; ++i
) {
419 JS::Rooted
<JS::Value
> value(aCx
);
421 if (!JS_GetElement(aCx
, argumentsObj
, i
, &value
)) {
425 if (!arguments
.AppendElement(value
, fallible
)) {
430 Console::ProfileMethodMainthread(aCx
, aAction
, arguments
);
433 bool WriteData(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
) {
434 // We use structuredClone to send the JSValue to the main-thread, in order
435 // to store it into the Console API Service. The consumer will be the
436 // console panel in the devtools and, because of this, we want to allow the
437 // cloning of sharedArrayBuffers and WASM modules.
438 JS::CloneDataPolicy cloneDataPolicy
;
439 cloneDataPolicy
.allowIntraClusterClonableSharedObjects();
440 cloneDataPolicy
.allowSharedMemoryObjects();
443 !Write(aCx
, aValue
, JS::UndefinedHandleValue
, cloneDataPolicy
))) {
444 // Ignore the message.
451 ConsoleStructuredCloneData mClonedData
;
454 class ConsoleWorkletRunnable
: public Runnable
, public ConsoleRunnable
{
456 explicit ConsoleWorkletRunnable(Console
* aConsole
)
457 : Runnable("dom::console::ConsoleWorkletRunnable"),
458 mConsoleData(aConsole
->GetOrCreateMainThreadData()) {
459 WorkletThread::AssertIsOnWorkletThread();
460 nsCOMPtr
<WorkletGlobalScope
> global
= do_QueryInterface(aConsole
->mGlobal
);
462 mWorkletImpl
= global
->Impl();
463 MOZ_ASSERT(mWorkletImpl
);
466 ~ConsoleWorkletRunnable() override
= default;
469 RefPtr
<MainThreadConsoleData
> mConsoleData
;
471 RefPtr
<WorkletImpl
> mWorkletImpl
;
474 // This runnable appends a CallData object into the Console queue running on
476 class ConsoleCallDataWorkletRunnable final
: public ConsoleWorkletRunnable
{
478 static already_AddRefed
<ConsoleCallDataWorkletRunnable
> Create(
479 JSContext
* aCx
, Console
* aConsole
, ConsoleCallData
* aConsoleData
,
480 const Sequence
<JS::Value
>& aArguments
) {
481 WorkletThread::AssertIsOnWorkletThread();
483 RefPtr
<ConsoleCallDataWorkletRunnable
> runnable
=
484 new ConsoleCallDataWorkletRunnable(aConsole
, aConsoleData
);
486 if (!runnable
->WriteArguments(aCx
, aArguments
)) {
490 return runnable
.forget();
494 ConsoleCallDataWorkletRunnable(Console
* aConsole
, ConsoleCallData
* aCallData
)
495 : ConsoleWorkletRunnable(aConsole
), mCallData(aCallData
) {
496 WorkletThread::AssertIsOnWorkletThread();
497 MOZ_ASSERT(aCallData
);
498 aCallData
->AssertIsOnOwningThread();
500 const WorkletLoadInfo
& loadInfo
= mWorkletImpl
->LoadInfo();
501 mCallData
->SetIDs(loadInfo
.OuterWindowID(), loadInfo
.InnerWindowID());
504 ~ConsoleCallDataWorkletRunnable() override
= default;
506 NS_IMETHOD
Run() override
{
507 AssertIsOnMainThread();
510 JSContext
* cx
= jsapi
.cx();
513 mConsoleData
->GetOrCreateSandbox(cx
, mWorkletImpl
->Principal());
514 JS::Rooted
<JSObject
*> global(cx
, sandbox
);
515 if (NS_WARN_IF(!global
)) {
516 return NS_ERROR_FAILURE
;
519 // The CreateSandbox call returns a proxy to the actual sandbox object. We
520 // don't need a proxy here.
521 global
= js::UncheckedUnwrap(global
);
523 JSAutoRealm
ar(cx
, global
);
525 // We don't need to set a parent object in mCallData bacause there are not
526 // DOM objects exposed to worklet.
528 ProcessCallData(cx
, mConsoleData
, mCallData
);
533 RefPtr
<ConsoleCallData
> mCallData
;
536 class ConsoleWorkerRunnable
: public WorkerProxyToMainThreadRunnable
,
537 public ConsoleRunnable
{
539 explicit ConsoleWorkerRunnable(Console
* aConsole
)
540 : mConsoleData(aConsole
->GetOrCreateMainThreadData()) {}
542 ~ConsoleWorkerRunnable() override
= default;
544 bool Dispatch(JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
) {
545 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
546 MOZ_ASSERT(workerPrivate
);
548 if (NS_WARN_IF(!WriteArguments(aCx
, aArguments
))) {
549 RunBackOnWorkerThreadForCleanup(workerPrivate
);
553 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate
))) {
554 // RunBackOnWorkerThreadForCleanup() will be called by
555 // WorkerProxyToMainThreadRunnable::Dispatch().
563 void RunOnMainThread(WorkerPrivate
* aWorkerPrivate
) override
{
564 MOZ_ASSERT(aWorkerPrivate
);
565 AssertIsOnMainThread();
567 // Walk up to our containing page
568 WorkerPrivate
* wp
= aWorkerPrivate
;
569 while (wp
->GetParent()) {
570 wp
= wp
->GetParent();
573 nsCOMPtr
<nsPIDOMWindowInner
> window
= wp
->GetWindow();
575 RunWindowless(aWorkerPrivate
);
577 RunWithWindow(aWorkerPrivate
, window
);
581 void RunWithWindow(WorkerPrivate
* aWorkerPrivate
,
582 nsPIDOMWindowInner
* aWindow
) {
583 MOZ_ASSERT(aWorkerPrivate
);
584 AssertIsOnMainThread();
589 RefPtr
<nsGlobalWindowInner
> win
= nsGlobalWindowInner::Cast(aWindow
);
590 if (NS_WARN_IF(!jsapi
.Init(win
))) {
594 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= aWindow
->GetOuterWindow();
595 if (NS_WARN_IF(!outerWindow
)) {
599 RunConsole(jsapi
.cx(), aWindow
->AsGlobal(), aWorkerPrivate
, outerWindow
,
603 void RunWindowless(WorkerPrivate
* aWorkerPrivate
) {
604 MOZ_ASSERT(aWorkerPrivate
);
605 AssertIsOnMainThread();
607 WorkerPrivate
* wp
= aWorkerPrivate
;
608 while (wp
->GetParent()) {
609 wp
= wp
->GetParent();
612 MOZ_ASSERT(!wp
->GetWindow());
617 JSContext
* cx
= jsapi
.cx();
619 JS::Rooted
<JSObject
*> global(
620 cx
, mConsoleData
->GetOrCreateSandbox(cx
, wp
->GetPrincipal()));
621 if (NS_WARN_IF(!global
)) {
625 // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
626 // We don't need a proxy here.
627 global
= js::UncheckedUnwrap(global
);
629 JSAutoRealm
ar(cx
, global
);
631 nsCOMPtr
<nsIGlobalObject
> globalObject
= xpc::NativeGlobal(global
);
632 if (NS_WARN_IF(!globalObject
)) {
636 RunConsole(cx
, globalObject
, aWorkerPrivate
, nullptr, nullptr);
639 void RunBackOnWorkerThreadForCleanup(WorkerPrivate
* aWorkerPrivate
) override
{
640 MOZ_ASSERT(aWorkerPrivate
);
641 aWorkerPrivate
->AssertIsOnWorkerThread();
644 // This method is called in the main-thread.
645 virtual void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
646 WorkerPrivate
* aWorkerPrivate
,
647 nsPIDOMWindowOuter
* aOuterWindow
,
648 nsPIDOMWindowInner
* aInnerWindow
) = 0;
650 bool ForMessaging() const override
{ return true; }
652 RefPtr
<MainThreadConsoleData
> mConsoleData
;
655 // This runnable appends a CallData object into the Console queue running on
657 class ConsoleCallDataWorkerRunnable final
: public ConsoleWorkerRunnable
{
659 ConsoleCallDataWorkerRunnable(Console
* aConsole
, ConsoleCallData
* aCallData
)
660 : ConsoleWorkerRunnable(aConsole
), mCallData(aCallData
) {
661 MOZ_ASSERT(aCallData
);
662 mCallData
->AssertIsOnOwningThread();
666 ~ConsoleCallDataWorkerRunnable() override
= default;
668 void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
669 WorkerPrivate
* aWorkerPrivate
,
670 nsPIDOMWindowOuter
* aOuterWindow
,
671 nsPIDOMWindowInner
* aInnerWindow
) override
{
673 MOZ_ASSERT(aWorkerPrivate
);
674 AssertIsOnMainThread();
676 // The windows have to run in parallel.
677 MOZ_ASSERT(!!aOuterWindow
== !!aInnerWindow
);
680 mCallData
->SetIDs(aOuterWindow
->WindowID(), aInnerWindow
->WindowID());
682 ConsoleStackEntry frame
;
683 if (mCallData
->mTopStackFrame
) {
684 frame
= *mCallData
->mTopStackFrame
;
687 nsString id
= frame
.mFilename
;
689 if (aWorkerPrivate
->IsSharedWorker()) {
690 innerID
= u
"SharedWorker"_ns
;
691 } else if (aWorkerPrivate
->IsServiceWorker()) {
692 innerID
= u
"ServiceWorker"_ns
;
693 // Use scope as ID so the webconsole can decide if the message should
695 CopyASCIItoUTF16(aWorkerPrivate
->ServiceWorkerScope(), id
);
697 innerID
= u
"Worker"_ns
;
700 mCallData
->SetIDs(id
, innerID
);
703 mClonedData
.mGlobal
= aGlobal
;
705 ProcessCallData(aCx
, mConsoleData
, mCallData
);
707 mClonedData
.mGlobal
= nullptr;
710 RefPtr
<ConsoleCallData
> mCallData
;
713 // This runnable calls ProfileMethod() on the console on the main-thread.
714 class ConsoleProfileWorkletRunnable final
: public ConsoleWorkletRunnable
{
716 static already_AddRefed
<ConsoleProfileWorkletRunnable
> Create(
717 JSContext
* aCx
, Console
* aConsole
, Console::MethodName aName
,
718 const nsAString
& aAction
, const Sequence
<JS::Value
>& aArguments
) {
719 WorkletThread::AssertIsOnWorkletThread();
721 RefPtr
<ConsoleProfileWorkletRunnable
> runnable
=
722 new ConsoleProfileWorkletRunnable(aConsole
, aName
, aAction
);
724 if (!runnable
->WriteArguments(aCx
, aArguments
)) {
728 return runnable
.forget();
732 ConsoleProfileWorkletRunnable(Console
* aConsole
, Console::MethodName aName
,
733 const nsAString
& aAction
)
734 : ConsoleWorkletRunnable(aConsole
), mName(aName
), mAction(aAction
) {
735 MOZ_ASSERT(aConsole
);
738 NS_IMETHOD
Run() override
{
739 AssertIsOnMainThread();
743 JSContext
* cx
= jsapi
.cx();
746 mConsoleData
->GetOrCreateSandbox(cx
, mWorkletImpl
->Principal());
747 JS::Rooted
<JSObject
*> global(cx
, sandbox
);
748 if (NS_WARN_IF(!global
)) {
749 return NS_ERROR_FAILURE
;
752 // The CreateSandbox call returns a proxy to the actual sandbox object. We
753 // don't need a proxy here.
754 global
= js::UncheckedUnwrap(global
);
756 JSAutoRealm
ar(cx
, global
);
758 // We don't need to set a parent object in mCallData bacause there are not
759 // DOM objects exposed to worklet.
760 ProcessProfileData(cx
, mName
, mAction
);
765 Console::MethodName mName
;
769 // This runnable calls ProfileMethod() on the console on the main-thread.
770 class ConsoleProfileWorkerRunnable final
: public ConsoleWorkerRunnable
{
772 ConsoleProfileWorkerRunnable(Console
* aConsole
, Console::MethodName aName
,
773 const nsAString
& aAction
)
774 : ConsoleWorkerRunnable(aConsole
), mName(aName
), mAction(aAction
) {
775 MOZ_ASSERT(aConsole
);
779 void RunConsole(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
780 WorkerPrivate
* aWorkerPrivate
,
781 nsPIDOMWindowOuter
* aOuterWindow
,
782 nsPIDOMWindowInner
* aInnerWindow
) override
{
783 AssertIsOnMainThread();
786 mClonedData
.mGlobal
= aGlobal
;
788 ProcessProfileData(aCx
, mName
, mAction
);
790 mClonedData
.mGlobal
= nullptr;
793 Console::MethodName mName
;
797 NS_IMPL_CYCLE_COLLECTION_CLASS(Console
)
799 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console
)
800 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
801 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier
)
802 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction
)
803 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
805 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
807 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console
)
808 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
)
809 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier
)
810 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction
)
811 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
813 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console
)
814 for (uint32_t i
= 0; i
< tmp
->mArgumentStorage
.length(); ++i
) {
815 tmp
->mArgumentStorage
[i
].Trace(aCallbacks
, aClosure
);
817 NS_IMPL_CYCLE_COLLECTION_TRACE_END
819 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console
)
820 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console
)
822 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console
)
823 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
824 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIObserver
)
825 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
829 already_AddRefed
<Console
> Console::Create(JSContext
* aCx
,
830 nsPIDOMWindowInner
* aWindow
,
832 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow
);
834 uint64_t outerWindowID
= 0;
835 uint64_t innerWindowID
= 0;
838 innerWindowID
= aWindow
->WindowID();
840 // Without outerwindow any console message coming from this object will not
841 // shown in the devtools webconsole. But this should be fine because
842 // probably we are shutting down, or the window is CCed/GCed.
843 nsPIDOMWindowOuter
* outerWindow
= aWindow
->GetOuterWindow();
845 outerWindowID
= outerWindow
->WindowID();
849 RefPtr
<Console
> console
= new Console(aCx
, nsGlobalWindowInner::Cast(aWindow
),
850 outerWindowID
, innerWindowID
);
851 console
->Initialize(aRv
);
852 if (NS_WARN_IF(aRv
.Failed())) {
856 return console
.forget();
860 already_AddRefed
<Console
> Console::CreateForWorklet(JSContext
* aCx
,
861 nsIGlobalObject
* aGlobal
,
862 uint64_t aOuterWindowID
,
863 uint64_t aInnerWindowID
,
865 WorkletThread::AssertIsOnWorkletThread();
867 RefPtr
<Console
> console
=
868 new Console(aCx
, aGlobal
, aOuterWindowID
, aInnerWindowID
);
869 console
->Initialize(aRv
);
870 if (NS_WARN_IF(aRv
.Failed())) {
874 return console
.forget();
877 Console::Console(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
878 uint64_t aOuterWindowID
, uint64_t aInnerWindowID
)
880 mOuterID(aOuterWindowID
),
881 mInnerID(aInnerWindowID
),
882 mDumpToStdout(false),
883 mChromeInstance(false),
884 mMaxLogLevel(ConsoleLogLevel::All
),
886 mCreationTimeStamp(TimeStamp::Now()) {
887 // Let's enable the dumping to stdout by default for chrome.
888 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx
)) {
889 mDumpToStdout
= StaticPrefs::devtools_console_stdout_chrome();
891 mDumpToStdout
= StaticPrefs::devtools_console_stdout_content();
894 mozilla::HoldJSObjects(this);
897 Console::~Console() {
898 AssertIsOnOwningThread();
900 mozilla::DropJSObjects(this);
903 void Console::Initialize(ErrorResult
& aRv
) {
904 AssertIsOnOwningThread();
905 MOZ_ASSERT(mStatus
== eUnknown
);
907 if (NS_IsMainThread()) {
908 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
909 if (NS_WARN_IF(!obs
)) {
910 aRv
.Throw(NS_ERROR_FAILURE
);
915 aRv
= obs
->AddObserver(this, "inner-window-destroyed", true);
916 if (NS_WARN_IF(aRv
.Failed())) {
921 aRv
= obs
->AddObserver(this, "memory-pressure", true);
922 if (NS_WARN_IF(aRv
.Failed())) {
927 mStatus
= eInitialized
;
930 void Console::Shutdown() {
931 AssertIsOnOwningThread();
933 if (mStatus
== eUnknown
|| mStatus
== eShuttingDown
) {
937 if (NS_IsMainThread()) {
938 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
940 obs
->RemoveObserver(this, "inner-window-destroyed");
941 obs
->RemoveObserver(this, "memory-pressure");
945 mTimerRegistry
.Clear();
946 mCounterRegistry
.Clear();
949 mCallDataStorage
.Clear();
951 mStatus
= eShuttingDown
;
955 Console::Observe(nsISupports
* aSubject
, const char* aTopic
,
956 const char16_t
* aData
) {
957 AssertIsOnMainThread();
959 if (!strcmp(aTopic
, "inner-window-destroyed")) {
960 nsCOMPtr
<nsISupportsPRUint64
> wrapper
= do_QueryInterface(aSubject
);
961 NS_ENSURE_TRUE(wrapper
, NS_ERROR_FAILURE
);
964 nsresult rv
= wrapper
->GetData(&innerID
);
965 NS_ENSURE_SUCCESS(rv
, rv
);
967 if (innerID
== mInnerID
) {
974 if (!strcmp(aTopic
, "memory-pressure")) {
982 void Console::ClearStorage() {
983 mCallDataStorage
.Clear();
984 mArgumentStorage
.clearAndFree();
987 #define METHOD(name, string) \
988 /* static */ void Console::name(const GlobalObject& aGlobal, \
989 const Sequence<JS::Value>& aData) { \
990 Method(aGlobal, Method##name, nsLiteralString(string), aData); \
994 METHOD(Info
, u
"info")
995 METHOD(Warn
, u
"warn")
996 METHOD(Error
, u
"error")
997 METHOD(Exception
, u
"exception")
998 METHOD(Debug
, u
"debug")
999 METHOD(Table
, u
"table")
1000 METHOD(Trace
, u
"trace")
1002 // Displays an interactive listing of all the properties of an object.
1003 METHOD(Dir
, u
"dir");
1004 METHOD(Dirxml
, u
"dirxml");
1006 METHOD(Group
, u
"group")
1007 METHOD(GroupCollapsed
, u
"groupCollapsed")
1012 void Console::Clear(const GlobalObject
& aGlobal
) {
1013 const Sequence
<JS::Value
> data
;
1014 Method(aGlobal
, MethodClear
, u
"clear"_ns
, data
);
1018 void Console::GroupEnd(const GlobalObject
& aGlobal
) {
1019 const Sequence
<JS::Value
> data
;
1020 Method(aGlobal
, MethodGroupEnd
, u
"groupEnd"_ns
, data
);
1024 void Console::Time(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1025 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodTime
, u
"time"_ns
);
1029 void Console::TimeEnd(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1030 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodTimeEnd
,
1035 void Console::TimeLog(const GlobalObject
& aGlobal
, const nsAString
& aLabel
,
1036 const Sequence
<JS::Value
>& aData
) {
1037 StringMethod(aGlobal
, aLabel
, aData
, MethodTimeLog
, u
"timeLog"_ns
);
1041 void Console::StringMethod(const GlobalObject
& aGlobal
, const nsAString
& aLabel
,
1042 const Sequence
<JS::Value
>& aData
,
1043 MethodName aMethodName
,
1044 const nsAString
& aMethodString
) {
1045 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1050 console
->StringMethodInternal(aGlobal
.Context(), aLabel
, aData
, aMethodName
,
1054 void Console::StringMethodInternal(JSContext
* aCx
, const nsAString
& aLabel
,
1055 const Sequence
<JS::Value
>& aData
,
1056 MethodName aMethodName
,
1057 const nsAString
& aMethodString
) {
1058 ConsoleCommon::ClearException
ce(aCx
);
1060 Sequence
<JS::Value
> data
;
1061 SequenceRooter
<JS::Value
> rooter(aCx
, &data
);
1063 JS::Rooted
<JS::Value
> value(aCx
);
1064 if (!dom::ToJSValue(aCx
, aLabel
, &value
)) {
1068 if (!data
.AppendElement(value
, fallible
)) {
1072 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1073 if (!data
.AppendElement(aData
[i
], fallible
)) {
1078 MethodInternal(aCx
, aMethodName
, aMethodString
, data
);
1082 void Console::TimeStamp(const GlobalObject
& aGlobal
,
1083 const JS::Handle
<JS::Value
> aData
) {
1084 JSContext
* cx
= aGlobal
.Context();
1086 ConsoleCommon::ClearException
ce(cx
);
1088 Sequence
<JS::Value
> data
;
1089 SequenceRooter
<JS::Value
> rooter(cx
, &data
);
1091 if (aData
.isString() && !data
.AppendElement(aData
, fallible
)) {
1095 Method(aGlobal
, MethodTimeStamp
, u
"timeStamp"_ns
, data
);
1099 void Console::Profile(const GlobalObject
& aGlobal
,
1100 const Sequence
<JS::Value
>& aData
) {
1101 ProfileMethod(aGlobal
, MethodProfile
, u
"profile"_ns
, aData
);
1105 void Console::ProfileEnd(const GlobalObject
& aGlobal
,
1106 const Sequence
<JS::Value
>& aData
) {
1107 ProfileMethod(aGlobal
, MethodProfileEnd
, u
"profileEnd"_ns
, aData
);
1111 void Console::ProfileMethod(const GlobalObject
& aGlobal
, MethodName aName
,
1112 const nsAString
& aAction
,
1113 const Sequence
<JS::Value
>& aData
) {
1114 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1119 JSContext
* cx
= aGlobal
.Context();
1120 console
->ProfileMethodInternal(cx
, aName
, aAction
, aData
);
1123 void Console::ProfileMethodInternal(JSContext
* aCx
, MethodName aMethodName
,
1124 const nsAString
& aAction
,
1125 const Sequence
<JS::Value
>& aData
) {
1126 if (!ShouldProceed(aMethodName
)) {
1130 MaybeExecuteDumpFunction(aCx
, aAction
, aData
, nullptr);
1132 if (WorkletThread::IsOnWorkletThread()) {
1133 RefPtr
<ConsoleProfileWorkletRunnable
> runnable
=
1134 ConsoleProfileWorkletRunnable::Create(aCx
, this, aMethodName
, aAction
,
1140 NS_DispatchToMainThread(runnable
.forget());
1144 if (!NS_IsMainThread()) {
1145 // Here we are in a worker thread.
1146 RefPtr
<ConsoleProfileWorkerRunnable
> runnable
=
1147 new ConsoleProfileWorkerRunnable(this, aMethodName
, aAction
);
1149 runnable
->Dispatch(aCx
, aData
);
1153 ProfileMethodMainthread(aCx
, aAction
, aData
);
1157 void Console::ProfileMethodMainthread(JSContext
* aCx
, const nsAString
& aAction
,
1158 const Sequence
<JS::Value
>& aData
) {
1159 MOZ_ASSERT(NS_IsMainThread());
1160 ConsoleCommon::ClearException
ce(aCx
);
1162 RootedDictionary
<ConsoleProfileEvent
> event(aCx
);
1163 event
.mAction
= aAction
;
1164 event
.mChromeContext
= nsContentUtils::ThreadsafeIsSystemCaller(aCx
);
1166 event
.mArguments
.Construct();
1167 Sequence
<JS::Value
>& sequence
= event
.mArguments
.Value();
1169 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1170 if (!sequence
.AppendElement(aData
[i
], fallible
)) {
1175 JS::Rooted
<JS::Value
> eventValue(aCx
);
1176 if (!ToJSValue(aCx
, event
, &eventValue
)) {
1180 JS::Rooted
<JSObject
*> eventObj(aCx
, &eventValue
.toObject());
1181 MOZ_ASSERT(eventObj
);
1183 if (!JS_DefineProperty(aCx
, eventObj
, "wrappedJSObject", eventValue
,
1184 JSPROP_ENUMERATE
)) {
1188 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
1189 nsCOMPtr
<nsISupports
> wrapper
;
1190 const nsIID
& iid
= NS_GET_IID(nsISupports
);
1192 if (NS_FAILED(xpc
->WrapJS(aCx
, eventObj
, iid
, getter_AddRefs(wrapper
)))) {
1196 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
1198 obs
->NotifyObservers(wrapper
, "console-api-profiler", nullptr);
1203 void Console::Assert(const GlobalObject
& aGlobal
, bool aCondition
,
1204 const Sequence
<JS::Value
>& aData
) {
1206 Method(aGlobal
, MethodAssert
, u
"assert"_ns
, aData
);
1211 void Console::Count(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1212 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodCount
,
1217 void Console::CountReset(const GlobalObject
& aGlobal
, const nsAString
& aLabel
) {
1218 StringMethod(aGlobal
, aLabel
, Sequence
<JS::Value
>(), MethodCountReset
,
1224 void StackFrameToStackEntry(JSContext
* aCx
, nsIStackFrame
* aStackFrame
,
1225 ConsoleStackEntry
& aStackEntry
) {
1226 MOZ_ASSERT(aStackFrame
);
1228 aStackFrame
->GetFilename(aCx
, aStackEntry
.mFilename
);
1230 aStackEntry
.mSourceId
= aStackFrame
->GetSourceId(aCx
);
1231 aStackEntry
.mLineNumber
= aStackFrame
->GetLineNumber(aCx
);
1232 aStackEntry
.mColumnNumber
= aStackFrame
->GetColumnNumber(aCx
);
1234 aStackFrame
->GetName(aCx
, aStackEntry
.mFunctionName
);
1237 aStackFrame
->GetAsyncCause(aCx
, cause
);
1238 if (!cause
.IsEmpty()) {
1239 aStackEntry
.mAsyncCause
.Construct(cause
);
1243 void ReifyStack(JSContext
* aCx
, nsIStackFrame
* aStack
,
1244 nsTArray
<ConsoleStackEntry
>& aRefiedStack
) {
1245 nsCOMPtr
<nsIStackFrame
> stack(aStack
);
1248 ConsoleStackEntry
& data
= *aRefiedStack
.AppendElement();
1249 StackFrameToStackEntry(aCx
, stack
, data
);
1251 nsCOMPtr
<nsIStackFrame
> caller
= stack
->GetCaller(aCx
);
1254 caller
= stack
->GetAsyncCaller(aCx
);
1260 } // anonymous namespace
1262 // Queue a call to a console method. See the CALL_DELAY constant.
1264 void Console::Method(const GlobalObject
& aGlobal
, MethodName aMethodName
,
1265 const nsAString
& aMethodString
,
1266 const Sequence
<JS::Value
>& aData
) {
1267 RefPtr
<Console
> console
= GetConsole(aGlobal
);
1272 console
->MethodInternal(aGlobal
.Context(), aMethodName
, aMethodString
, aData
);
1275 void Console::MethodInternal(JSContext
* aCx
, MethodName aMethodName
,
1276 const nsAString
& aMethodString
,
1277 const Sequence
<JS::Value
>& aData
) {
1278 if (!ShouldProceed(aMethodName
)) {
1282 AssertIsOnOwningThread();
1284 ConsoleCommon::ClearException
ce(aCx
);
1286 RefPtr
<ConsoleCallData
> callData
=
1287 new ConsoleCallData(aMethodName
, aMethodString
, this);
1288 if (!StoreCallData(aCx
, callData
, aData
)) {
1292 OriginAttributes oa
;
1294 if (NS_IsMainThread()) {
1296 // Save the principal's OriginAttributes in the console event data
1297 // so that we will be able to filter messages by origin attributes.
1298 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(mGlobal
);
1299 if (NS_WARN_IF(!sop
)) {
1303 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1304 if (NS_WARN_IF(!principal
)) {
1308 oa
= principal
->OriginAttributesRef();
1309 callData
->SetAddonId(principal
);
1312 if (!principal
->IsSystemPrincipal()) {
1313 nsCOMPtr
<nsIWebNavigation
> webNav
= do_GetInterface(mGlobal
);
1315 nsCOMPtr
<nsILoadContext
> loadContext
= do_QueryInterface(webNav
);
1316 MOZ_ASSERT(loadContext
);
1319 if (NS_SUCCEEDED(loadContext
->GetUsePrivateBrowsing(&pb
))) {
1320 MOZ_ASSERT(pb
== !!oa
.mPrivateBrowsingId
);
1326 } else if (WorkletThread::IsOnWorkletThread()) {
1327 nsCOMPtr
<WorkletGlobalScope
> global
= do_QueryInterface(mGlobal
);
1329 oa
= global
->Impl()->OriginAttributesRef();
1331 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
1332 MOZ_ASSERT(workerPrivate
);
1333 oa
= workerPrivate
->GetOriginAttributes();
1336 callData
->SetOriginAttributes(oa
);
1338 JS::StackCapture captureMode
=
1339 ShouldIncludeStackTrace(aMethodName
)
1340 ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH
))
1341 : JS::StackCapture(JS::FirstSubsumedFrame(aCx
));
1342 nsCOMPtr
<nsIStackFrame
> stack
= CreateStack(aCx
, std::move(captureMode
));
1345 callData
->mTopStackFrame
.emplace();
1346 StackFrameToStackEntry(aCx
, stack
, *callData
->mTopStackFrame
);
1349 if (NS_IsMainThread()) {
1350 callData
->mStack
= stack
;
1352 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1353 // before we post our runnable to the main thread.
1354 callData
->mReifiedStack
.emplace();
1355 ReifyStack(aCx
, stack
, *callData
->mReifiedStack
);
1358 DOMHighResTimeStamp monotonicTimer
;
1360 // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1361 if ((aMethodName
== MethodTime
|| aMethodName
== MethodTimeLog
||
1362 aMethodName
== MethodTimeEnd
|| aMethodName
== MethodTimeStamp
) &&
1363 !MonotonicTimer(aCx
, aMethodName
, aData
, &monotonicTimer
)) {
1367 if (aMethodName
== MethodTime
&& !aData
.IsEmpty()) {
1368 callData
->mStartTimerStatus
=
1369 StartTimer(aCx
, aData
[0], monotonicTimer
, callData
->mStartTimerLabel
,
1370 &callData
->mStartTimerValue
);
1373 else if (aMethodName
== MethodTimeEnd
&& !aData
.IsEmpty()) {
1374 callData
->mLogTimerStatus
=
1375 LogTimer(aCx
, aData
[0], monotonicTimer
, callData
->mLogTimerLabel
,
1376 &callData
->mLogTimerDuration
, true /* Cancel timer */);
1379 else if (aMethodName
== MethodTimeLog
&& !aData
.IsEmpty()) {
1380 callData
->mLogTimerStatus
=
1381 LogTimer(aCx
, aData
[0], monotonicTimer
, callData
->mLogTimerLabel
,
1382 &callData
->mLogTimerDuration
, false /* Cancel timer */);
1385 else if (aMethodName
== MethodCount
) {
1386 callData
->mCountValue
= IncreaseCounter(aCx
, aData
, callData
->mCountLabel
);
1387 if (!callData
->mCountValue
) {
1392 else if (aMethodName
== MethodCountReset
) {
1393 callData
->mCountValue
= ResetCounter(aCx
, aData
, callData
->mCountLabel
);
1394 if (callData
->mCountLabel
.IsEmpty()) {
1399 // Before processing this CallData differently, it's time to call the dump
1401 if (aMethodName
== MethodTrace
|| aMethodName
== MethodAssert
) {
1402 MaybeExecuteDumpFunction(aCx
, aMethodString
, aData
, stack
);
1403 } else if ((aMethodName
== MethodTime
|| aMethodName
== MethodTimeEnd
) &&
1405 MaybeExecuteDumpFunctionForTime(aCx
, aMethodName
, aMethodString
,
1406 monotonicTimer
, aData
[0]);
1408 MaybeExecuteDumpFunction(aCx
, aMethodString
, aData
, nullptr);
1411 if (NS_IsMainThread()) {
1413 callData
->SetIDs(mOuterID
, mInnerID
);
1414 } else if (!mPassedInnerID
.IsEmpty()) {
1415 callData
->SetIDs(u
"jsm"_ns
, mPassedInnerID
);
1417 nsAutoString filename
;
1418 if (callData
->mTopStackFrame
.isSome()) {
1419 filename
= callData
->mTopStackFrame
->mFilename
;
1422 callData
->SetIDs(u
"jsm"_ns
, filename
);
1425 GetOrCreateMainThreadData()->ProcessCallData(aCx
, callData
, aData
);
1427 // Just because we don't want to expose
1428 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1429 // cleanup the mCallDataStorage:
1430 UnstoreCallData(callData
);
1434 if (WorkletThread::IsOnWorkletThread()) {
1435 RefPtr
<ConsoleCallDataWorkletRunnable
> runnable
=
1436 ConsoleCallDataWorkletRunnable::Create(aCx
, this, callData
, aData
);
1441 NS_DispatchToMainThread(runnable
);
1445 // We do this only in workers for now.
1446 NotifyHandler(aCx
, aData
, callData
);
1448 if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
1449 RefPtr
<ConsoleCallDataWorkerRunnable
> runnable
=
1450 new ConsoleCallDataWorkerRunnable(this, callData
);
1451 Unused
<< NS_WARN_IF(!runnable
->Dispatch(aCx
, aData
));
1455 MainThreadConsoleData
* Console::GetOrCreateMainThreadData() {
1456 AssertIsOnOwningThread();
1458 if (!mMainThreadData
) {
1459 mMainThreadData
= new MainThreadConsoleData();
1462 return mMainThreadData
;
1465 // We store information to lazily compute the stack in the reserved slots of
1466 // LazyStackGetter. The first slot always stores a JS object: it's either the
1467 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1468 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1469 // reified the stack yet, or an UndefinedValue() otherwise.
1470 enum { SLOT_STACKOBJ
, SLOT_RAW_STACK
};
1472 bool LazyStackGetter(JSContext
* aCx
, unsigned aArgc
, JS::Value
* aVp
) {
1473 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
1474 JS::Rooted
<JSObject
*> callee(aCx
, &args
.callee());
1476 JS::Value v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_RAW_STACK
);
1477 if (v
.isUndefined()) {
1479 args
.rval().set(js::GetFunctionNativeReserved(callee
, SLOT_STACKOBJ
));
1483 nsIStackFrame
* stack
= reinterpret_cast<nsIStackFrame
*>(v
.toPrivate());
1484 nsTArray
<ConsoleStackEntry
> reifiedStack
;
1485 ReifyStack(aCx
, stack
, reifiedStack
);
1487 JS::Rooted
<JS::Value
> stackVal(aCx
);
1488 if (NS_WARN_IF(!ToJSValue(aCx
, reifiedStack
, &stackVal
))) {
1492 MOZ_ASSERT(stackVal
.isObject());
1494 js::SetFunctionNativeReserved(callee
, SLOT_STACKOBJ
, stackVal
);
1495 js::SetFunctionNativeReserved(callee
, SLOT_RAW_STACK
, JS::UndefinedValue());
1497 args
.rval().set(stackVal
);
1501 void MainThreadConsoleData::ProcessCallData(
1502 JSContext
* aCx
, ConsoleCallData
* aData
,
1503 const Sequence
<JS::Value
>& aArguments
) {
1504 AssertIsOnMainThread();
1507 JS::Rooted
<JS::Value
> eventValue(aCx
);
1509 // We want to create a console event object and pass it to our
1510 // nsIConsoleAPIStorage implementation. We want to define some accessor
1511 // properties on this object, and those will need to keep an nsIStackFrame
1512 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1513 // further, passing untrusted objects to system code is likely to run afoul of
1514 // Object Xrays. So we want to wrap in a system-principal scope here. But
1515 // which one? We could cheat and try to get the underlying JSObject* of
1516 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1517 // with explicit permission from the XPConnect module owner. If you're
1518 // tempted to do that anywhere else, talk to said module owner first.
1520 // aCx and aArguments are in the same compartment.
1521 JS::Rooted
<JSObject
*> targetScope(aCx
, xpc::PrivilegedJunkScope());
1522 if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
1523 aCx
, aArguments
, targetScope
, &eventValue
, aData
, &mGroupStack
))) {
1528 mStorage
= do_GetService("@mozilla.org/consoleAPI-storage;1");
1532 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1536 nsAutoString innerID
;
1538 MOZ_ASSERT(aData
->mIDType
!= ConsoleCallData::eUnknown
);
1539 if (aData
->mIDType
== ConsoleCallData::eString
) {
1540 innerID
= aData
->mInnerIDString
;
1542 MOZ_ASSERT(aData
->mIDType
== ConsoleCallData::eNumber
);
1543 innerID
.AppendInt(aData
->mInnerIDNumber
);
1546 if (aData
->mMethodName
== Console::MethodClear
) {
1547 DebugOnly
<nsresult
> rv
= mStorage
->ClearEvents(innerID
);
1548 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "ClearEvents failed");
1551 if (NS_FAILED(mStorage
->RecordEvent(innerID
, eventValue
))) {
1552 NS_WARNING("Failed to record a console event.");
1557 bool Console::PopulateConsoleNotificationInTheTargetScope(
1558 JSContext
* aCx
, const Sequence
<JS::Value
>& aArguments
,
1559 JS::Handle
<JSObject
*> aTargetScope
,
1560 JS::MutableHandle
<JS::Value
> aEventValue
, ConsoleCallData
* aData
,
1561 nsTArray
<nsString
>* aGroupStack
) {
1564 MOZ_ASSERT(aTargetScope
);
1565 MOZ_ASSERT(JS_IsGlobalObject(aTargetScope
));
1567 ConsoleStackEntry frame
;
1568 if (aData
->mTopStackFrame
) {
1569 frame
= *aData
->mTopStackFrame
;
1572 ConsoleCommon::ClearException
ce(aCx
);
1573 RootedDictionary
<ConsoleEvent
> event(aCx
);
1575 event
.mAddonId
= aData
->mAddonId
;
1577 event
.mID
.Construct();
1578 event
.mInnerID
.Construct();
1580 event
.mChromeContext
= nsContentUtils::ThreadsafeIsSystemCaller(aCx
);
1582 if (aData
->mIDType
== ConsoleCallData::eString
) {
1583 event
.mID
.Value().SetAsString() = aData
->mOuterIDString
;
1584 event
.mInnerID
.Value().SetAsString() = aData
->mInnerIDString
;
1585 } else if (aData
->mIDType
== ConsoleCallData::eNumber
) {
1586 event
.mID
.Value().SetAsUnsignedLongLong() = aData
->mOuterIDNumber
;
1587 event
.mInnerID
.Value().SetAsUnsignedLongLong() = aData
->mInnerIDNumber
;
1589 // aData->mIDType can be eUnknown when we dispatch notifications via
1590 // mConsoleEventNotifier.
1591 event
.mID
.Value().SetAsUnsignedLongLong() = 0;
1592 event
.mInnerID
.Value().SetAsUnsignedLongLong() = 0;
1595 event
.mConsoleID
= aData
->mConsoleID
;
1596 event
.mLevel
= aData
->mMethodString
;
1597 event
.mFilename
= frame
.mFilename
;
1598 event
.mPrefix
= aData
->mPrefix
;
1600 nsCOMPtr
<nsIURI
> filenameURI
;
1602 if (NS_IsMainThread() &&
1603 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI
), frame
.mFilename
)) &&
1604 NS_SUCCEEDED(filenameURI
->GetPassword(pass
)) && !pass
.IsEmpty()) {
1605 nsCOMPtr
<nsISensitiveInfoHiddenURI
> safeURI
=
1606 do_QueryInterface(filenameURI
);
1608 if (safeURI
&& NS_SUCCEEDED(safeURI
->GetSensitiveInfoHiddenSpec(spec
))) {
1609 CopyUTF8toUTF16(spec
, event
.mFilename
);
1613 event
.mSourceId
= frame
.mSourceId
;
1614 event
.mLineNumber
= frame
.mLineNumber
;
1615 event
.mColumnNumber
= frame
.mColumnNumber
;
1616 event
.mFunctionName
= frame
.mFunctionName
;
1617 event
.mTimeStamp
= aData
->mMicroSecondTimeStamp
/ PR_USEC_PER_MSEC
;
1618 event
.mMicroSecondTimeStamp
= aData
->mMicroSecondTimeStamp
;
1619 event
.mPrivate
= !!aData
->mOriginAttributes
.mPrivateBrowsingId
;
1621 switch (aData
->mMethodName
) {
1626 case MethodException
:
1630 case MethodGroupCollapsed
:
1632 event
.mArguments
.Construct();
1633 event
.mStyles
.Construct();
1634 if (NS_WARN_IF(!ProcessArguments(aCx
, aArguments
,
1635 event
.mArguments
.Value(),
1636 event
.mStyles
.Value()))) {
1643 event
.mArguments
.Construct();
1645 !event
.mArguments
.Value().AppendElements(aArguments
, fallible
))) {
1650 if (aData
->mMethodName
== MethodGroup
||
1651 aData
->mMethodName
== MethodGroupCollapsed
) {
1652 ComposeAndStoreGroupName(aCx
, event
.mArguments
.Value(), event
.mGroupName
,
1656 else if (aData
->mMethodName
== MethodGroupEnd
) {
1657 if (!UnstoreGroupName(event
.mGroupName
, aGroupStack
)) {
1662 else if (aData
->mMethodName
== MethodTime
&& !aArguments
.IsEmpty()) {
1663 event
.mTimer
= CreateStartTimerValue(aCx
, aData
->mStartTimerLabel
,
1664 aData
->mStartTimerStatus
);
1667 else if ((aData
->mMethodName
== MethodTimeEnd
||
1668 aData
->mMethodName
== MethodTimeLog
) &&
1669 !aArguments
.IsEmpty()) {
1670 event
.mTimer
= CreateLogOrEndTimerValue(aCx
, aData
->mLogTimerLabel
,
1671 aData
->mLogTimerDuration
,
1672 aData
->mLogTimerStatus
);
1675 else if (aData
->mMethodName
== MethodCount
||
1676 aData
->mMethodName
== MethodCountReset
) {
1677 event
.mCounter
= CreateCounterOrResetCounterValue(aCx
, aData
->mCountLabel
,
1678 aData
->mCountValue
);
1681 JSAutoRealm
ar2(aCx
, aTargetScope
);
1683 if (NS_WARN_IF(!ToJSValue(aCx
, event
, aEventValue
))) {
1687 JS::Rooted
<JSObject
*> eventObj(aCx
, &aEventValue
.toObject());
1688 if (NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "wrappedJSObject", eventObj
,
1689 JSPROP_ENUMERATE
))) {
1693 if (ShouldIncludeStackTrace(aData
->mMethodName
)) {
1694 // Now define the "stacktrace" property on eventObj. There are two cases
1695 // here. Either we came from a worker and have a reified stack, or we want
1696 // to define a getter that will lazily reify the stack.
1697 if (aData
->mReifiedStack
) {
1698 JS::Rooted
<JS::Value
> stacktrace(aCx
);
1699 if (NS_WARN_IF(!ToJSValue(aCx
, *aData
->mReifiedStack
, &stacktrace
)) ||
1700 NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "stacktrace", stacktrace
,
1701 JSPROP_ENUMERATE
))) {
1706 js::NewFunctionWithReserved(aCx
, LazyStackGetter
, 0, 0, "stacktrace");
1707 if (NS_WARN_IF(!fun
)) {
1711 JS::Rooted
<JSObject
*> funObj(aCx
, JS_GetFunctionObject(fun
));
1713 // We want to store our stack in the function and have it stay alive. But
1714 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1715 // wrapper and the raw pointer: the former will keep the latter alive.
1716 JS::Rooted
<JS::Value
> stackVal(aCx
);
1717 nsresult rv
= nsContentUtils::WrapNative(aCx
, aData
->mStack
, &stackVal
);
1718 if (NS_WARN_IF(NS_FAILED(rv
))) {
1722 js::SetFunctionNativeReserved(funObj
, SLOT_STACKOBJ
, stackVal
);
1723 js::SetFunctionNativeReserved(funObj
, SLOT_RAW_STACK
,
1724 JS::PrivateValue(aData
->mStack
.get()));
1726 if (NS_WARN_IF(!JS_DefineProperty(aCx
, eventObj
, "stacktrace", funObj
,
1727 nullptr, JSPROP_ENUMERATE
))) {
1738 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1740 bool FlushOutput(JSContext
* aCx
, Sequence
<JS::Value
>& aSequence
,
1741 nsString
& aOutput
) {
1742 if (!aOutput
.IsEmpty()) {
1743 JS::Rooted
<JSString
*> str(
1744 aCx
, JS_NewUCStringCopyN(aCx
, aOutput
.get(), aOutput
.Length()));
1745 if (NS_WARN_IF(!str
)) {
1749 if (NS_WARN_IF(!aSequence
.AppendElement(JS::StringValue(str
), fallible
))) {
1761 static void MakeFormatString(nsCString
& aFormat
, int32_t aInteger
,
1762 int32_t aMantissa
, char aCh
) {
1763 aFormat
.Append('%');
1764 if (aInteger
>= 0) {
1765 aFormat
.AppendInt(aInteger
);
1768 if (aMantissa
>= 0) {
1769 aFormat
.Append('.');
1770 aFormat
.AppendInt(aMantissa
);
1773 aFormat
.Append(aCh
);
1776 // If the first JS::Value of the array is a string, this method uses it to
1777 // format a string. The supported sequences are:
1781 // %o,%O - a JS object.
1782 // %c - style string.
1783 // The output is an array where any object is a separated item, the rest is
1784 // unified in a format string.
1785 // Example if the input is:
1786 // "string: %s, integer: %d, object: %o, double: %d", 's', 1, window, 0.9
1787 // The output will be:
1788 // [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
1790 // The aStyles array is populated with the style strings that the function
1791 // finds based the format string. The index of the styles matches the indexes
1792 // of elements that need the custom styling from aSequence. For elements with
1793 // no custom styling the array is padded with null elements.
1794 static bool ProcessArguments(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
,
1795 Sequence
<JS::Value
>& aSequence
,
1796 Sequence
<nsString
>& aStyles
) {
1797 // This method processes the arguments as format strings (%d, %i, %s...)
1798 // only if the first element of them is a valid and not-empty string.
1800 if (aData
.IsEmpty()) {
1804 if (aData
.Length() == 1 || !aData
[0].isString()) {
1805 return aSequence
.AppendElements(aData
, fallible
);
1808 JS::Rooted
<JS::Value
> format(aCx
, aData
[0]);
1809 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, format
));
1810 if (NS_WARN_IF(!jsString
)) {
1814 nsAutoJSString string
;
1815 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
1819 if (string
.IsEmpty()) {
1820 return aSequence
.AppendElements(aData
, fallible
);
1823 nsString::const_iterator start
, end
;
1824 string
.BeginReading(start
);
1825 string
.EndReading(end
);
1830 while (start
!= end
) {
1831 if (*start
!= '%') {
1832 output
.Append(*start
);
1843 if (*start
== '%') {
1844 output
.Append(*start
);
1852 int32_t integer
= -1;
1853 int32_t mantissa
= -1;
1855 // Let's parse %<number>.<number> for %d and %f
1856 if (*start
>= '0' && *start
<= '9') {
1860 integer
= integer
* 10 + *start
- '0';
1863 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1871 if (*start
== '.') {
1880 // '.' must be followed by a number.
1881 if (*start
< '0' || *start
> '9') {
1889 mantissa
= mantissa
* 10 + *start
- '0';
1892 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1907 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
1911 JS::Rooted
<JS::Value
> v(aCx
);
1912 if (index
< aData
.Length()) {
1916 if (NS_WARN_IF(!aSequence
.AppendElement(v
, fallible
))) {
1924 // If there isn't any output but there's already a style, then
1925 // discard the previous style and use the next one instead.
1926 if (output
.IsEmpty() && !aStyles
.IsEmpty()) {
1927 aStyles
.RemoveLastElement();
1930 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
1934 if (index
< aData
.Length()) {
1935 JS::Rooted
<JS::Value
> v(aCx
, aData
[index
++]);
1936 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, v
));
1937 if (NS_WARN_IF(!jsString
)) {
1941 int32_t diff
= aSequence
.Length() - aStyles
.Length();
1943 for (int32_t i
= 0; i
< diff
; i
++) {
1944 if (NS_WARN_IF(!aStyles
.AppendElement(VoidString(), fallible
))) {
1950 nsAutoJSString string
;
1951 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
1955 if (NS_WARN_IF(!aStyles
.AppendElement(string
, fallible
))) {
1963 if (index
< aData
.Length()) {
1964 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1965 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
1966 if (NS_WARN_IF(!jsString
)) {
1971 if (NS_WARN_IF(!v
.init(aCx
, jsString
))) {
1981 if (index
< aData
.Length()) {
1982 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1984 if (value
.isBigInt()) {
1985 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
1986 if (NS_WARN_IF(!jsString
)) {
1991 if (NS_WARN_IF(!v
.init(aCx
, jsString
))) {
1999 if (NS_WARN_IF(!JS::ToInt32(aCx
, value
, &v
))) {
2004 MakeFormatString(format
, integer
, mantissa
, 'd');
2005 output
.AppendPrintf(format
.get(), v
);
2010 if (index
< aData
.Length()) {
2011 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
2014 if (NS_WARN_IF(!JS::ToNumber(aCx
, value
, &v
))) {
2018 // nspr returns "nan", but we want to expose it as "NaN"
2019 if (std::isnan(v
)) {
2020 output
.AppendFloat(v
);
2023 MakeFormatString(format
, integer
, mantissa
, 'f');
2024 output
.AppendPrintf(format
.get(), v
);
2035 if (NS_WARN_IF(!FlushOutput(aCx
, aSequence
, output
))) {
2039 // Discard trailing style element if there is no output to apply it to.
2040 if (aStyles
.Length() > aSequence
.Length()) {
2041 aStyles
.TruncateLength(aSequence
.Length());
2044 // The rest of the array, if unused by the format string.
2045 for (; index
< aData
.Length(); ++index
) {
2046 if (NS_WARN_IF(!aSequence
.AppendElement(aData
[index
], fallible
))) {
2054 // Stringify and Concat all the JS::Value in a single string using ' ' as
2055 // separator. The new group name will be stored in aGroupStack array.
2056 static void ComposeAndStoreGroupName(JSContext
* aCx
,
2057 const Sequence
<JS::Value
>& aData
,
2059 nsTArray
<nsString
>* aGroupStack
) {
2061 aName
, u
" "_ns
, aData
, [aCx
](nsAString
& dest
, const JS::Value
& valueRef
) {
2062 JS::Rooted
<JS::Value
> value(aCx
, valueRef
);
2063 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2068 nsAutoJSString string
;
2069 if (!string
.init(aCx
, jsString
)) {
2073 dest
.Append(string
);
2076 aGroupStack
->AppendElement(aName
);
2079 // Remove the last group name and return that name. It returns false if
2080 // aGroupStack is empty.
2081 static bool UnstoreGroupName(nsAString
& aName
,
2082 nsTArray
<nsString
>* aGroupStack
) {
2083 if (aGroupStack
->IsEmpty()) {
2087 aName
= aGroupStack
->PopLastElement();
2091 Console::TimerStatus
Console::StartTimer(JSContext
* aCx
, const JS::Value
& aName
,
2092 DOMHighResTimeStamp aTimestamp
,
2093 nsAString
& aTimerLabel
,
2094 DOMHighResTimeStamp
* aTimerValue
) {
2095 AssertIsOnOwningThread();
2096 MOZ_ASSERT(aTimerValue
);
2100 if (NS_WARN_IF(mTimerRegistry
.Count() >= MAX_PAGE_TIMERS
)) {
2101 return eTimerMaxReached
;
2104 JS::Rooted
<JS::Value
> name(aCx
, aName
);
2105 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
2106 if (NS_WARN_IF(!jsString
)) {
2107 return eTimerJSException
;
2110 nsAutoJSString label
;
2111 if (NS_WARN_IF(!label
.init(aCx
, jsString
))) {
2112 return eTimerJSException
;
2115 aTimerLabel
= label
;
2117 if (mTimerRegistry
.WithEntryHandle(label
, [&](auto&& entry
) {
2121 entry
.Insert(aTimestamp
);
2124 return eTimerAlreadyExists
;
2127 *aTimerValue
= aTimestamp
;
2132 JS::Value
Console::CreateStartTimerValue(JSContext
* aCx
,
2133 const nsAString
& aTimerLabel
,
2134 TimerStatus aTimerStatus
) {
2135 MOZ_ASSERT(aTimerStatus
!= eTimerUnknown
);
2137 if (aTimerStatus
!= eTimerDone
) {
2138 return CreateTimerError(aCx
, aTimerLabel
, aTimerStatus
);
2141 RootedDictionary
<ConsoleTimerStart
> timer(aCx
);
2143 timer
.mName
= aTimerLabel
;
2145 JS::Rooted
<JS::Value
> value(aCx
);
2146 if (!ToJSValue(aCx
, timer
, &value
)) {
2147 return JS::UndefinedValue();
2153 Console::TimerStatus
Console::LogTimer(JSContext
* aCx
, const JS::Value
& aName
,
2154 DOMHighResTimeStamp aTimestamp
,
2155 nsAString
& aTimerLabel
,
2156 double* aTimerDuration
,
2157 bool aCancelTimer
) {
2158 AssertIsOnOwningThread();
2159 MOZ_ASSERT(aTimerDuration
);
2161 *aTimerDuration
= 0;
2163 JS::Rooted
<JS::Value
> name(aCx
, aName
);
2164 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
2165 if (NS_WARN_IF(!jsString
)) {
2166 return eTimerJSException
;
2170 if (NS_WARN_IF(!key
.init(aCx
, jsString
))) {
2171 return eTimerJSException
;
2176 DOMHighResTimeStamp value
= 0;
2179 if (!mTimerRegistry
.Remove(key
, &value
)) {
2180 NS_WARNING("mTimerRegistry entry not found");
2181 return eTimerDoesntExist
;
2184 if (!mTimerRegistry
.Get(key
, &value
)) {
2185 NS_WARNING("mTimerRegistry entry not found");
2186 return eTimerDoesntExist
;
2190 *aTimerDuration
= aTimestamp
- value
;
2195 JS::Value
Console::CreateLogOrEndTimerValue(JSContext
* aCx
,
2196 const nsAString
& aLabel
,
2198 TimerStatus aStatus
) {
2199 if (aStatus
!= eTimerDone
) {
2200 return CreateTimerError(aCx
, aLabel
, aStatus
);
2203 RootedDictionary
<ConsoleTimerLogOrEnd
> timer(aCx
);
2204 timer
.mName
= aLabel
;
2205 timer
.mDuration
= aDuration
;
2207 JS::Rooted
<JS::Value
> value(aCx
);
2208 if (!ToJSValue(aCx
, timer
, &value
)) {
2209 return JS::UndefinedValue();
2216 JS::Value
Console::CreateTimerError(JSContext
* aCx
, const nsAString
& aLabel
,
2217 TimerStatus aStatus
) {
2218 MOZ_ASSERT(aStatus
!= eTimerUnknown
&& aStatus
!= eTimerDone
);
2220 RootedDictionary
<ConsoleTimerError
> error(aCx
);
2222 error
.mName
= aLabel
;
2225 case eTimerAlreadyExists
:
2226 error
.mError
.AssignLiteral("timerAlreadyExists");
2229 case eTimerDoesntExist
:
2230 error
.mError
.AssignLiteral("timerDoesntExist");
2233 case eTimerJSException
:
2234 error
.mError
.AssignLiteral("timerJSError");
2237 case eTimerMaxReached
:
2238 error
.mError
.AssignLiteral("maxTimersExceeded");
2242 MOZ_CRASH("Unsupported status");
2246 JS::Rooted
<JS::Value
> value(aCx
);
2247 if (!ToJSValue(aCx
, error
, &value
)) {
2248 return JS::UndefinedValue();
2254 uint32_t Console::IncreaseCounter(JSContext
* aCx
,
2255 const Sequence
<JS::Value
>& aArguments
,
2256 nsAString
& aCountLabel
) {
2257 AssertIsOnOwningThread();
2259 ConsoleCommon::ClearException
ce(aCx
);
2261 MOZ_ASSERT(!aArguments
.IsEmpty());
2263 JS::Rooted
<JS::Value
> labelValue(aCx
, aArguments
[0]);
2264 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, labelValue
));
2266 return 0; // We cannot continue.
2269 nsAutoJSString string
;
2270 if (!string
.init(aCx
, jsString
)) {
2271 return 0; // We cannot continue.
2274 aCountLabel
= string
;
2276 const bool maxCountersReached
= mCounterRegistry
.Count() >= MAX_PAGE_COUNTERS
;
2277 return mCounterRegistry
.WithEntryHandle(
2278 aCountLabel
, [maxCountersReached
](auto&& entry
) -> uint32_t {
2282 if (maxCountersReached
) {
2283 return MAX_PAGE_COUNTERS
;
2287 return entry
.Data();
2291 uint32_t Console::ResetCounter(JSContext
* aCx
,
2292 const Sequence
<JS::Value
>& aArguments
,
2293 nsAString
& aCountLabel
) {
2294 AssertIsOnOwningThread();
2296 ConsoleCommon::ClearException
ce(aCx
);
2298 MOZ_ASSERT(!aArguments
.IsEmpty());
2300 JS::Rooted
<JS::Value
> labelValue(aCx
, aArguments
[0]);
2301 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, labelValue
));
2303 return 0; // We cannot continue.
2306 nsAutoJSString string
;
2307 if (!string
.init(aCx
, jsString
)) {
2308 return 0; // We cannot continue.
2311 aCountLabel
= string
;
2313 if (mCounterRegistry
.Remove(aCountLabel
)) {
2317 // Let's return something different than 0 if the key doesn't exist.
2318 return MAX_PAGE_COUNTERS
;
2321 // This method generates a ConsoleCounter dictionary as JS::Value. If
2322 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
2323 // instead. See IncreaseCounter.
2324 // * aCx - this is the context that will root the returned value.
2325 // * aCountLabel - this label must be what IncreaseCounter received as
2327 // * aCountValue - the return value of IncreaseCounter.
2328 static JS::Value
CreateCounterOrResetCounterValue(JSContext
* aCx
,
2329 const nsAString
& aCountLabel
,
2330 uint32_t aCountValue
) {
2331 ConsoleCommon::ClearException
ce(aCx
);
2333 if (aCountValue
== MAX_PAGE_COUNTERS
) {
2334 RootedDictionary
<ConsoleCounterError
> error(aCx
);
2335 error
.mLabel
= aCountLabel
;
2336 error
.mError
.AssignLiteral("counterDoesntExist");
2338 JS::Rooted
<JS::Value
> value(aCx
);
2339 if (!ToJSValue(aCx
, error
, &value
)) {
2340 return JS::UndefinedValue();
2346 RootedDictionary
<ConsoleCounter
> data(aCx
);
2347 data
.mLabel
= aCountLabel
;
2348 data
.mCount
= aCountValue
;
2350 JS::Rooted
<JS::Value
> value(aCx
);
2351 if (!ToJSValue(aCx
, data
, &value
)) {
2352 return JS::UndefinedValue();
2359 bool Console::ShouldIncludeStackTrace(MethodName aMethodName
) {
2360 switch (aMethodName
) {
2362 case MethodException
:
2371 JSObject
* MainThreadConsoleData::GetOrCreateSandbox(JSContext
* aCx
,
2372 nsIPrincipal
* aPrincipal
) {
2373 AssertIsOnMainThread();
2376 nsIXPConnect
* xpc
= nsContentUtils::XPConnect();
2377 MOZ_ASSERT(xpc
, "This should never be null!");
2379 JS::Rooted
<JSObject
*> sandbox(aCx
);
2380 nsresult rv
= xpc
->CreateSandbox(aCx
, aPrincipal
, sandbox
.address());
2381 if (NS_WARN_IF(NS_FAILED(rv
))) {
2385 mSandbox
= new JSObjectHolder(aCx
, sandbox
);
2388 return mSandbox
->GetJSObject();
2391 bool Console::StoreCallData(JSContext
* aCx
, ConsoleCallData
* aCallData
,
2392 const Sequence
<JS::Value
>& aArguments
) {
2393 AssertIsOnOwningThread();
2395 if (NS_WARN_IF(!mArgumentStorage
.growBy(1))) {
2398 if (!mArgumentStorage
.end()[-1].Initialize(aCx
, aArguments
)) {
2399 mArgumentStorage
.shrinkBy(1);
2403 MOZ_ASSERT(aCallData
);
2404 MOZ_ASSERT(!mCallDataStorage
.Contains(aCallData
));
2406 mCallDataStorage
.AppendElement(aCallData
);
2408 MOZ_ASSERT(mCallDataStorage
.Length() == mArgumentStorage
.length());
2410 if (mCallDataStorage
.Length() > STORAGE_MAX_EVENTS
) {
2411 mCallDataStorage
.RemoveElementAt(0);
2412 mArgumentStorage
.erase(&mArgumentStorage
[0]);
2417 void Console::UnstoreCallData(ConsoleCallData
* aCallData
) {
2418 AssertIsOnOwningThread();
2420 MOZ_ASSERT(aCallData
);
2421 MOZ_ASSERT(mCallDataStorage
.Length() == mArgumentStorage
.length());
2423 size_t index
= mCallDataStorage
.IndexOf(aCallData
);
2424 // It can be that mCallDataStorage has been already cleaned in case the
2425 // processing of the argument of some Console methods triggers the
2427 if (index
== mCallDataStorage
.NoIndex
) {
2431 mCallDataStorage
.RemoveElementAt(index
);
2432 mArgumentStorage
.erase(&mArgumentStorage
[index
]);
2435 void Console::NotifyHandler(JSContext
* aCx
,
2436 const Sequence
<JS::Value
>& aArguments
,
2437 ConsoleCallData
* aCallData
) {
2438 AssertIsOnOwningThread();
2439 MOZ_ASSERT(!NS_IsMainThread());
2440 MOZ_ASSERT(aCallData
);
2442 if (!mConsoleEventNotifier
) {
2446 JS::Rooted
<JS::Value
> value(aCx
);
2448 JS::Rooted
<JSObject
*> callableGlobal(
2449 aCx
, mConsoleEventNotifier
->CallbackGlobalOrNull());
2450 if (NS_WARN_IF(!callableGlobal
)) {
2454 // aCx and aArguments are in the same compartment because this method is
2455 // called directly when a Console.something() runs.
2456 // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
2458 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2459 aCx
, aArguments
, callableGlobal
, &value
, aCallData
, &mGroupStack
))) {
2463 JS::Rooted
<JS::Value
> ignored(aCx
);
2464 RefPtr
<AnyCallback
> notifier(mConsoleEventNotifier
);
2465 notifier
->Call(value
, &ignored
);
2468 void Console::RetrieveConsoleEvents(JSContext
* aCx
,
2469 nsTArray
<JS::Value
>& aEvents
,
2471 AssertIsOnOwningThread();
2473 // We don't want to expose this functionality to main-thread yet.
2474 MOZ_ASSERT(!NS_IsMainThread());
2476 JS::Rooted
<JSObject
*> targetScope(aCx
, JS::CurrentGlobalOrNull(aCx
));
2478 for (uint32_t i
= 0; i
< mArgumentStorage
.length(); ++i
) {
2479 JS::Rooted
<JS::Value
> value(aCx
);
2481 JS::Rooted
<JSObject
*> sequenceScope(aCx
, mArgumentStorage
[i
].Global());
2482 JSAutoRealm
ar(aCx
, sequenceScope
);
2484 Sequence
<JS::Value
> sequence
;
2485 SequenceRooter
<JS::Value
> arguments(aCx
, &sequence
);
2487 if (!mArgumentStorage
[i
].PopulateArgumentsSequence(sequence
)) {
2488 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2492 // Here we have aCx and sequence in the same compartment.
2493 // targetScope is the destination scope and value will be populated in its
2495 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2496 aCx
, sequence
, targetScope
, &value
, mCallDataStorage
[i
],
2498 aRv
.Throw(NS_ERROR_FAILURE
);
2502 aEvents
.AppendElement(value
);
2506 void Console::SetConsoleEventHandler(AnyCallback
* aHandler
) {
2507 AssertIsOnOwningThread();
2509 // We don't want to expose this functionality to main-thread yet.
2510 MOZ_ASSERT(!NS_IsMainThread());
2512 mConsoleEventNotifier
= aHandler
;
2515 void Console::AssertIsOnOwningThread() const {
2516 NS_ASSERT_OWNINGTHREAD(Console
);
2519 bool Console::IsShuttingDown() const {
2520 MOZ_ASSERT(mStatus
!= eUnknown
);
2521 return mStatus
== eShuttingDown
;
2525 already_AddRefed
<Console
> Console::GetConsole(const GlobalObject
& aGlobal
) {
2527 RefPtr
<Console
> console
= GetConsoleInternal(aGlobal
, rv
);
2528 if (NS_WARN_IF(rv
.Failed()) || !console
) {
2529 rv
.SuppressException();
2533 console
->AssertIsOnOwningThread();
2535 if (console
->IsShuttingDown()) {
2539 return console
.forget();
2543 already_AddRefed
<Console
> Console::GetConsoleInternal(
2544 const GlobalObject
& aGlobal
, ErrorResult
& aRv
) {
2546 if (NS_IsMainThread()) {
2547 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
=
2548 do_QueryInterface(aGlobal
.GetAsSupports());
2550 // we are probably running a chrome script.
2552 RefPtr
<Console
> console
= new Console(aGlobal
.Context(), nullptr, 0, 0);
2553 console
->Initialize(aRv
);
2554 if (NS_WARN_IF(aRv
.Failed())) {
2558 return console
.forget();
2561 nsGlobalWindowInner
* window
= nsGlobalWindowInner::Cast(innerWindow
);
2562 return window
->GetConsole(aGlobal
.Context(), aRv
);
2566 nsCOMPtr
<WorkletGlobalScope
> workletScope
=
2567 do_QueryInterface(aGlobal
.GetAsSupports());
2569 WorkletThread::AssertIsOnWorkletThread();
2570 return workletScope
->GetConsole(aGlobal
.Context(), aRv
);
2574 MOZ_ASSERT(!NS_IsMainThread());
2576 JSContext
* cx
= aGlobal
.Context();
2577 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
2578 MOZ_ASSERT(workerPrivate
);
2580 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
2581 if (NS_WARN_IF(!global
)) {
2585 WorkerGlobalScope
* scope
= workerPrivate
->GlobalScope();
2588 // Normal worker scope.
2589 if (scope
== global
) {
2590 return scope
->GetConsole(aRv
);
2593 // Debugger worker scope
2595 WorkerDebuggerGlobalScope
* debuggerScope
=
2596 workerPrivate
->DebuggerGlobalScope();
2597 MOZ_ASSERT(debuggerScope
);
2598 MOZ_ASSERT(debuggerScope
== global
, "Which kind of global do we have?");
2600 return debuggerScope
->GetConsole(aRv
);
2603 bool Console::MonotonicTimer(JSContext
* aCx
, MethodName aMethodName
,
2604 const Sequence
<JS::Value
>& aData
,
2605 DOMHighResTimeStamp
* aTimeStamp
) {
2606 if (nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
= do_QueryInterface(mGlobal
)) {
2607 nsGlobalWindowInner
* win
= nsGlobalWindowInner::Cast(innerWindow
);
2610 RefPtr
<Performance
> performance
= win
->GetPerformance();
2615 *aTimeStamp
= performance
->Now();
2617 nsDocShell
* docShell
= static_cast<nsDocShell
*>(win
->GetDocShell());
2618 bool isTimelineRecording
= TimelineConsumers::HasConsumer(docShell
);
2620 // The 'timeStamp' recordings do not need an argument; use empty string
2621 // if no arguments passed in.
2622 if (isTimelineRecording
&& aMethodName
== MethodTimeStamp
) {
2623 JS::Rooted
<JS::Value
> value(
2624 aCx
, aData
.Length() == 0 ? JS_GetEmptyStringValue(aCx
) : aData
[0]);
2625 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2631 if (!key
.init(aCx
, jsString
)) {
2635 TimelineConsumers::AddMarkerForDocShell(
2636 docShell
, MakeUnique
<TimestampTimelineMarker
>(key
));
2638 // For `console.time(foo)` and `console.timeEnd(foo)`.
2639 else if (isTimelineRecording
&& aData
.Length() == 1) {
2640 JS::Rooted
<JS::Value
> value(aCx
, aData
[0]);
2641 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
2647 if (!key
.init(aCx
, jsString
)) {
2651 TimelineConsumers::AddMarkerForDocShell(
2653 MakeUnique
<ConsoleTimelineMarker
>(key
, aMethodName
== MethodTime
2654 ? MarkerTracingType::START
2655 : MarkerTracingType::END
));
2661 if (NS_IsMainThread()) {
2662 *aTimeStamp
= (TimeStamp::Now() - mCreationTimeStamp
).ToMilliseconds();
2666 if (nsCOMPtr
<WorkletGlobalScope
> workletGlobal
= do_QueryInterface(mGlobal
)) {
2667 *aTimeStamp
= workletGlobal
->TimeStampToDOMHighRes(TimeStamp::Now());
2671 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
2672 MOZ_ASSERT(workerPrivate
);
2674 *aTimeStamp
= workerPrivate
->TimeStampToDOMHighRes(TimeStamp::Now());
2679 already_AddRefed
<ConsoleInstance
> Console::CreateInstance(
2680 const GlobalObject
& aGlobal
, const ConsoleInstanceOptions
& aOptions
) {
2681 RefPtr
<ConsoleInstance
> console
=
2682 new ConsoleInstance(aGlobal
.Context(), aOptions
);
2683 return console
.forget();
2686 void Console::StringifyElement(Element
* aElement
, nsAString
& aOut
) {
2687 aOut
.AppendLiteral("<");
2688 aOut
.Append(aElement
->LocalName());
2689 uint32_t attrCount
= aElement
->GetAttrCount();
2690 nsAutoString idAttr
;
2691 nsAutoString classAttr
;
2692 nsAutoString nameAttr
;
2693 nsAutoString otherAttrs
;
2694 for (uint32_t i
= 0; i
< attrCount
; i
++) {
2695 BorrowedAttrInfo attrInfo
= aElement
->GetAttrInfoAt(i
);
2696 nsAutoString attrValue
;
2697 attrInfo
.mValue
->ToString(attrValue
);
2699 const nsAttrName
* attrName
= attrInfo
.mName
;
2700 if (attrName
->Equals(nsGkAtoms::id
)) {
2701 idAttr
.AppendLiteral(" id=\"");
2702 idAttr
.Append(attrValue
);
2703 idAttr
.AppendLiteral("\"");
2704 } else if (attrName
->Equals(nsGkAtoms::_class
)) {
2705 classAttr
.AppendLiteral(" class=\"");
2706 classAttr
.Append(attrValue
);
2707 classAttr
.AppendLiteral("\"");
2708 } else if (attrName
->Equals(nsGkAtoms::name
)) {
2709 nameAttr
.AppendLiteral(" name=\"");
2710 nameAttr
.Append(attrValue
);
2711 nameAttr
.AppendLiteral("\"");
2713 nsAutoString attrNameStr
;
2714 attrName
->GetQualifiedName(attrNameStr
);
2715 otherAttrs
.AppendLiteral(" ");
2716 otherAttrs
.Append(attrNameStr
);
2717 otherAttrs
.AppendLiteral("=\"");
2718 otherAttrs
.Append(attrValue
);
2719 otherAttrs
.AppendLiteral("\"");
2722 if (!idAttr
.IsEmpty()) {
2723 aOut
.Append(idAttr
);
2725 if (!classAttr
.IsEmpty()) {
2726 aOut
.Append(classAttr
);
2728 if (!nameAttr
.IsEmpty()) {
2729 aOut
.Append(nameAttr
);
2731 if (!otherAttrs
.IsEmpty()) {
2732 aOut
.Append(otherAttrs
);
2734 aOut
.AppendLiteral(">");
2737 void Console::MaybeExecuteDumpFunction(JSContext
* aCx
,
2738 const nsAString
& aMethodName
,
2739 const Sequence
<JS::Value
>& aData
,
2740 nsIStackFrame
* aStack
) {
2741 if (!mDumpFunction
&& !mDumpToStdout
) {
2745 nsAutoString message
;
2746 message
.AssignLiteral("console.");
2747 message
.Append(aMethodName
);
2748 message
.AppendLiteral(": ");
2750 if (!mPrefix
.IsEmpty()) {
2751 message
.Append(mPrefix
);
2752 message
.AppendLiteral(": ");
2755 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
2756 JS::Rooted
<JS::Value
> v(aCx
, aData
[i
]);
2758 Element
* element
= nullptr;
2759 if (NS_SUCCEEDED(UNWRAP_OBJECT(Element
, &v
, element
))) {
2761 message
.AppendLiteral(" ");
2763 StringifyElement(element
, message
);
2768 JS::Rooted
<JSString
*> jsString(aCx
, JS_ValueToSource(aCx
, v
));
2773 nsAutoJSString string
;
2774 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
2779 message
.AppendLiteral(" ");
2782 message
.Append(string
);
2785 message
.AppendLiteral("\n");
2787 // aStack can be null.
2789 nsCOMPtr
<nsIStackFrame
> stack(aStack
);
2792 nsAutoString filename
;
2793 stack
->GetFilename(aCx
, filename
);
2795 message
.Append(filename
);
2796 message
.AppendLiteral(" ");
2798 message
.AppendInt(stack
->GetLineNumber(aCx
));
2799 message
.AppendLiteral(" ");
2801 nsAutoString functionName
;
2802 stack
->GetName(aCx
, functionName
);
2804 message
.Append(functionName
);
2805 message
.AppendLiteral("\n");
2807 nsCOMPtr
<nsIStackFrame
> caller
= stack
->GetCaller(aCx
);
2810 caller
= stack
->GetAsyncCaller(aCx
);
2816 ExecuteDumpFunction(message
);
2819 void Console::MaybeExecuteDumpFunctionForTime(JSContext
* aCx
,
2820 MethodName aMethodName
,
2821 const nsAString
& aMethodString
,
2822 uint64_t aMonotonicTimer
,
2823 const JS::Value
& aData
) {
2824 if (!mDumpFunction
&& !mDumpToStdout
) {
2828 nsAutoString message
;
2829 message
.AssignLiteral("console.");
2830 message
.Append(aMethodString
);
2831 message
.AppendLiteral(": ");
2833 if (!mPrefix
.IsEmpty()) {
2834 message
.Append(mPrefix
);
2835 message
.AppendLiteral(": ");
2838 JS::Rooted
<JS::Value
> v(aCx
, aData
);
2839 JS::Rooted
<JSString
*> jsString(aCx
, JS_ValueToSource(aCx
, v
));
2844 nsAutoJSString string
;
2845 if (NS_WARN_IF(!string
.init(aCx
, jsString
))) {
2849 message
.Append(string
);
2850 message
.AppendLiteral(" @ ");
2851 message
.AppendInt(aMonotonicTimer
);
2853 message
.AppendLiteral("\n");
2854 ExecuteDumpFunction(message
);
2857 void Console::ExecuteDumpFunction(const nsAString
& aMessage
) {
2858 if (mDumpFunction
) {
2859 RefPtr
<ConsoleInstanceDumpCallback
> dumpFunction(mDumpFunction
);
2860 dumpFunction
->Call(aMessage
);
2864 NS_ConvertUTF16toUTF8
str(aMessage
);
2865 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug
, ("%s", str
.get()));
2867 __android_log_print(ANDROID_LOG_INFO
, "Gecko", "%s", str
.get());
2869 fputs(str
.get(), stdout
);
2873 ConsoleLogLevel
PrefToValue(const nsAString
& aPref
,
2874 const ConsoleLogLevel aLevel
) {
2875 if (!NS_IsMainThread()) {
2876 NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
2877 return ConsoleLogLevel::All
;
2879 if (aPref
.IsEmpty()) {
2883 NS_ConvertUTF16toUTF8
pref(aPref
);
2884 nsAutoCString value
;
2885 nsresult rv
= Preferences::GetCString(pref
.get(), value
);
2886 if (NS_WARN_IF(NS_FAILED(rv
))) {
2890 int index
= FindEnumStringIndexImpl(value
.get(), value
.Length(),
2891 ConsoleLogLevelValues::strings
);
2892 if (NS_WARN_IF(index
< 0)) {
2894 message
.AssignLiteral("Invalid Console.maxLogLevelPref value: ");
2895 message
.Append(NS_ConvertUTF8toUTF16(value
));
2897 nsContentUtils::LogSimpleConsoleError(message
, "chrome"_ns
, false,
2898 true /* from chrome context*/);
2902 MOZ_ASSERT(index
< (int)ConsoleLogLevelValues::Count
);
2903 return static_cast<ConsoleLogLevel
>(index
);
2906 bool Console::ShouldProceed(MethodName aName
) const {
2907 ConsoleLogLevel maxLogLevel
= PrefToValue(mMaxLogLevelPref
, mMaxLogLevel
);
2908 return WebIDLLogLevelToInteger(maxLogLevel
) <=
2909 InternalLogLevelToInteger(aName
);
2912 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel
) const {
2914 case ConsoleLogLevel::All
:
2916 case ConsoleLogLevel::Debug
:
2918 case ConsoleLogLevel::Log
:
2920 case ConsoleLogLevel::Info
:
2922 case ConsoleLogLevel::Clear
:
2924 case ConsoleLogLevel::Trace
:
2926 case ConsoleLogLevel::TimeLog
:
2928 case ConsoleLogLevel::TimeEnd
:
2930 case ConsoleLogLevel::Time
:
2932 case ConsoleLogLevel::Group
:
2934 case ConsoleLogLevel::GroupEnd
:
2936 case ConsoleLogLevel::Profile
:
2938 case ConsoleLogLevel::ProfileEnd
:
2940 case ConsoleLogLevel::Dir
:
2942 case ConsoleLogLevel::Dirxml
:
2944 case ConsoleLogLevel::Warn
:
2946 case ConsoleLogLevel::Error
:
2948 case ConsoleLogLevel::Off
:
2952 "ConsoleLogLevel is out of sync with the Console implementation!");
2957 uint32_t Console::InternalLogLevelToInteger(MethodName aName
) const {
2967 case MethodException
:
2981 case MethodGroupCollapsed
:
2983 case MethodGroupEnd
:
2991 case MethodTimeStamp
:
2997 case MethodCountReset
:
3003 case MethodProfileEnd
:
3006 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
3011 bool Console::ArgumentData::Initialize(JSContext
* aCx
,
3012 const Sequence
<JS::Value
>& aArguments
) {
3013 mGlobal
= JS::CurrentGlobalOrNull(aCx
);
3015 if (NS_WARN_IF(!mArguments
.AppendElements(aArguments
, fallible
))) {
3022 void Console::ArgumentData::Trace(const TraceCallbacks
& aCallbacks
,
3024 ArgumentData
* tmp
= this;
3025 for (uint32_t i
= 0; i
< mArguments
.Length(); ++i
) {
3026 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments
[i
])
3029 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal
)
3032 bool Console::ArgumentData::PopulateArgumentsSequence(
3033 Sequence
<JS::Value
>& aSequence
) const {
3034 AssertIsOnOwningThread();
3036 for (uint32_t i
= 0; i
< mArguments
.Length(); ++i
) {
3037 if (NS_WARN_IF(!aSequence
.AppendElement(mArguments
[i
], fallible
))) {
3045 } // namespace mozilla::dom