Bug 1805294 [wpt PR 37463] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / dom / console / Console.cpp
blob9ae139a78e8e1b13070d40054a55989fa922165c
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
67 // console.trace().
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,
88 nsAString& aName,
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 {
108 public:
109 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData)
111 ConsoleCallData(Console::MethodName aName, const nsAString& aString,
112 Console* aConsole)
113 : mConsoleID(aConsole->mConsoleID),
114 mPrefix(aConsole->mPrefix),
115 mMethodName(aName),
116 mMicroSecondTimeStamp(JS_Now()),
117 mStartTimerValue(0),
118 mStartTimerStatus(Console::eTimerUnknown),
119 mLogTimerDuration(0),
120 mLogTimerStatus(Console::eTimerUnknown),
121 mCountValue(MAX_PAGE_COUNTERS),
122 mIDType(eUnknown),
123 mOuterIDNumber(0),
124 mInnerIDNumber(0),
125 mMethodString(aString) {}
127 void SetIDs(uint64_t aOuterID, uint64_t aInnerID) {
128 MOZ_ASSERT(mIDType == eUnknown);
130 mOuterIDNumber = aOuterID;
131 mInnerIDNumber = aInnerID;
132 mIDType = eNumber;
135 void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) {
136 MOZ_ASSERT(mIDType == eUnknown);
138 mOuterIDString = aOuterID;
139 mInnerIDString = aInnerID;
140 mIDType = eString;
143 void SetOriginAttributes(const OriginAttributes& aOriginAttributes) {
144 mOriginAttributes = aOriginAttributes;
147 void SetAddonId(nsIPrincipal* aPrincipal) {
148 nsAutoString addonId;
149 aPrincipal->GetAddonId(addonId);
151 mAddonId = 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
189 // called.
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;
209 nsString mAddonId;
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;
223 private:
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
233 // Console alive.
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);
242 private:
243 ~MainThreadConsoleData() {
244 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
245 mStorage.forget());
246 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
247 mSandbox.forget());
250 // All members, except for mRefCnt, are accessed only on the main thread,
251 // except in MainThreadConsoleData destruction, at which point there are no
252 // other references.
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 {
260 public:
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.
265 Clear();
268 protected:
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;
280 RefPtr<Blob> blob =
281 Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex));
282 if (!ToJSValue(aCx, blob, &val)) {
283 return nullptr;
287 return &val.toObject();
290 MOZ_CRASH("No other tags are supported.");
291 return nullptr;
294 bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
295 JS::Handle<JSObject*> aObj,
296 bool* aSameProcessScopeRequired) override {
297 RefPtr<Blob> blob;
298 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
299 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
300 mClonedData.mBlobs.Length()))) {
301 return false;
304 mClonedData.mBlobs.AppendElement(blob->Impl());
305 return true;
308 if (!JS_ObjectNotWritten(aWriter, aObj)) {
309 return false;
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)) {
315 return false;
318 if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
319 return false;
322 return true;
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)) {
334 return;
337 MOZ_ASSERT(argumentsValue.isObject());
339 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
341 uint32_t length;
342 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
343 return;
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)) {
353 return;
356 if (!values.AppendElement(value, fallible)) {
357 return;
361 MOZ_ASSERT(values.Length() == length);
363 aConsoleData->ProcessCallData(aCx, aCallData, values);
366 // Generic
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)) {
373 return false;
376 JS::Rooted<JS::Value> arg(aCx);
377 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
378 arg = aArguments[i];
379 if (NS_WARN_IF(
380 !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
381 return false;
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;
400 if (!ok) {
401 return;
404 MOZ_ASSERT(argumentsValue.isObject());
405 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
406 if (NS_WARN_IF(!argumentsObj)) {
407 return;
410 uint32_t length;
411 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
412 return;
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)) {
421 return;
424 if (!arguments.AppendElement(value, fallible)) {
425 return;
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();
441 if (NS_WARN_IF(
442 !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) {
443 // Ignore the message.
444 return false;
447 return true;
450 ConsoleStructuredCloneData mClonedData;
453 class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
454 protected:
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);
460 MOZ_ASSERT(global);
461 mWorkletImpl = global->Impl();
462 MOZ_ASSERT(mWorkletImpl);
465 ~ConsoleWorkletRunnable() override = default;
467 protected:
468 RefPtr<MainThreadConsoleData> mConsoleData;
470 RefPtr<WorkletImpl> mWorkletImpl;
473 // This runnable appends a CallData object into the Console queue running on
474 // the main-thread.
475 class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
476 public:
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)) {
486 return nullptr;
489 return runnable.forget();
492 private:
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();
507 AutoJSAPI jsapi;
508 jsapi.Init();
509 JSContext* cx = jsapi.cx();
511 JSObject* sandbox =
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);
529 return NS_OK;
532 RefPtr<ConsoleCallData> mCallData;
535 class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
536 public ConsoleRunnable {
537 public:
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);
549 return false;
552 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
553 // RunBackOnWorkerThreadForCleanup() will be called by
554 // WorkerProxyToMainThreadRunnable::Dispatch().
555 return false;
558 return true;
561 protected:
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();
573 if (!window) {
574 RunWindowless(aWorkerPrivate);
575 } else {
576 RunWithWindow(aWorkerPrivate, window);
580 void RunWithWindow(WorkerPrivate* aWorkerPrivate,
581 nsPIDOMWindowInner* aWindow) {
582 MOZ_ASSERT(aWorkerPrivate);
583 AssertIsOnMainThread();
585 AutoJSAPI jsapi;
586 MOZ_ASSERT(aWindow);
588 RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
589 if (NS_WARN_IF(!jsapi.Init(win))) {
590 return;
593 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
594 if (NS_WARN_IF(!outerWindow)) {
595 return;
598 RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
599 aWindow);
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());
613 AutoJSAPI jsapi;
614 jsapi.Init();
616 JSContext* cx = jsapi.cx();
618 JS::Rooted<JSObject*> global(
619 cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal()));
620 if (NS_WARN_IF(!global)) {
621 return;
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)) {
632 return;
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
655 // the main-thread.
656 class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
657 public:
658 ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
659 : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
660 MOZ_ASSERT(aCallData);
661 mCallData->AssertIsOnOwningThread();
664 private:
665 ~ConsoleCallDataWorkerRunnable() override = default;
667 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
668 WorkerPrivate* aWorkerPrivate,
669 nsPIDOMWindowOuter* aOuterWindow,
670 nsPIDOMWindowInner* aInnerWindow) override {
671 MOZ_ASSERT(aGlobal);
672 MOZ_ASSERT(aWorkerPrivate);
673 AssertIsOnMainThread();
675 // The windows have to run in parallel.
676 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
678 if (aOuterWindow) {
679 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
680 } else {
681 ConsoleStackEntry frame;
682 if (mCallData->mTopStackFrame) {
683 frame = *mCallData->mTopStackFrame;
686 nsString id = frame.mFilename;
687 nsString innerID;
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
693 // show up per tab
694 CopyASCIItoUTF16(aWorkerPrivate->ServiceWorkerScope(), id);
695 } else {
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 {
714 public:
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)) {
724 return nullptr;
727 return runnable.forget();
730 private:
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();
740 AutoJSAPI jsapi;
741 jsapi.Init();
742 JSContext* cx = jsapi.cx();
744 JSObject* sandbox =
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);
761 return NS_OK;
764 Console::MethodName mName;
765 nsString mAction;
768 // This runnable calls ProfileMethod() on the console on the main-thread.
769 class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
770 public:
771 ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
772 const nsAString& aAction)
773 : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) {
774 MOZ_ASSERT(aConsole);
777 private:
778 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
779 WorkerPrivate* aWorkerPrivate,
780 nsPIDOMWindowOuter* aOuterWindow,
781 nsPIDOMWindowInner* aInnerWindow) override {
782 AssertIsOnMainThread();
783 MOZ_ASSERT(aGlobal);
785 mClonedData.mGlobal = aGlobal;
787 ProcessProfileData(aCx, mName, mAction);
789 mClonedData.mGlobal = nullptr;
792 Console::MethodName mName;
793 nsString mAction;
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
803 tmp->Shutdown();
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)
825 NS_INTERFACE_MAP_END
827 /* static */
828 already_AddRefed<Console> Console::Create(JSContext* aCx,
829 nsPIDOMWindowInner* aWindow,
830 ErrorResult& aRv) {
831 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
833 uint64_t outerWindowID = 0;
834 uint64_t innerWindowID = 0;
836 if (aWindow) {
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();
843 if (outerWindow) {
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())) {
852 return nullptr;
855 return console.forget();
858 /* static */
859 already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
860 nsIGlobalObject* aGlobal,
861 uint64_t aOuterWindowID,
862 uint64_t aInnerWindowID,
863 ErrorResult& aRv) {
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())) {
870 return nullptr;
873 return console.forget();
876 Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
877 uint64_t aOuterWindowID, uint64_t aInnerWindowID)
878 : mGlobal(aGlobal),
879 mOuterID(aOuterWindowID),
880 mInnerID(aInnerWindowID),
881 mDumpToStdout(false),
882 mChromeInstance(false),
883 mMaxLogLevel(ConsoleLogLevel::All),
884 mStatus(eUnknown),
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();
889 } else {
890 mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
893 mozilla::HoldJSObjects(this);
896 Console::~Console() {
897 AssertIsOnOwningThread();
898 Shutdown();
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);
910 return;
913 if (mInnerID) {
914 aRv = obs->AddObserver(this, "inner-window-destroyed", true);
915 if (NS_WARN_IF(aRv.Failed())) {
916 return;
920 aRv = obs->AddObserver(this, "memory-pressure", true);
921 if (NS_WARN_IF(aRv.Failed())) {
922 return;
926 mStatus = eInitialized;
929 void Console::Shutdown() {
930 AssertIsOnOwningThread();
932 if (mStatus == eUnknown || mStatus == eShuttingDown) {
933 return;
936 if (NS_IsMainThread()) {
937 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
938 if (obs) {
939 obs->RemoveObserver(this, "inner-window-destroyed");
940 obs->RemoveObserver(this, "memory-pressure");
944 mTimerRegistry.Clear();
945 mCounterRegistry.Clear();
947 ClearStorage();
948 mCallDataStorage.Clear();
950 mStatus = eShuttingDown;
953 NS_IMETHODIMP
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);
962 uint64_t innerID;
963 nsresult rv = wrapper->GetData(&innerID);
964 NS_ENSURE_SUCCESS(rv, rv);
966 if (innerID == mInnerID) {
967 Shutdown();
970 return NS_OK;
973 if (!strcmp(aTopic, "memory-pressure")) {
974 ClearStorage();
975 return NS_OK;
978 return NS_OK;
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); \
992 METHOD(Log, u"log")
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")
1008 #undef METHOD
1010 /* static */
1011 void Console::Clear(const GlobalObject& aGlobal) {
1012 const Sequence<JS::Value> data;
1013 Method(aGlobal, MethodClear, u"clear"_ns, data);
1016 /* static */
1017 void Console::GroupEnd(const GlobalObject& aGlobal) {
1018 const Sequence<JS::Value> data;
1019 Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data);
1022 /* static */
1023 void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
1024 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns);
1027 /* static */
1028 void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
1029 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
1030 u"timeEnd"_ns);
1033 /* static */
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);
1039 /* static */
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);
1045 if (!console) {
1046 return;
1049 console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
1050 aMethodString);
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)) {
1064 return;
1067 if (!data.AppendElement(value, fallible)) {
1068 return;
1071 for (uint32_t i = 0; i < aData.Length(); ++i) {
1072 if (!data.AppendElement(aData[i], fallible)) {
1073 return;
1077 MethodInternal(aCx, aMethodName, aMethodString, data);
1080 /* static */
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)) {
1091 return;
1094 Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data);
1097 /* static */
1098 void Console::Profile(const GlobalObject& aGlobal,
1099 const Sequence<JS::Value>& aData) {
1100 ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData);
1103 /* static */
1104 void Console::ProfileEnd(const GlobalObject& aGlobal,
1105 const Sequence<JS::Value>& aData) {
1106 ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData);
1109 /* static */
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);
1114 if (!console) {
1115 return;
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)) {
1126 return;
1129 MaybeExecuteDumpFunction(aCx, aAction, aData, nullptr);
1131 if (WorkletThread::IsOnWorkletThread()) {
1132 RefPtr<ConsoleProfileWorkletRunnable> runnable =
1133 ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
1134 aData);
1135 if (!runnable) {
1136 return;
1139 NS_DispatchToMainThread(runnable.forget());
1140 return;
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);
1149 return;
1152 ProfileMethodMainthread(aCx, aAction, aData);
1155 // static
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)) {
1170 return;
1174 JS::Rooted<JS::Value> eventValue(aCx);
1175 if (!ToJSValue(aCx, event, &eventValue)) {
1176 return;
1179 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1180 MOZ_ASSERT(eventObj);
1182 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1183 JSPROP_ENUMERATE)) {
1184 return;
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)))) {
1192 return;
1195 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1196 if (obs) {
1197 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1201 /* static */
1202 void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1203 const Sequence<JS::Value>& aData) {
1204 if (!aCondition) {
1205 Method(aGlobal, MethodAssert, u"assert"_ns, aData);
1209 /* static */
1210 void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
1211 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
1212 u"count"_ns);
1215 /* static */
1216 void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
1217 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
1218 u"countReset"_ns);
1221 namespace {
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);
1235 nsString cause;
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);
1246 while (stack) {
1247 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1248 StackFrameToStackEntry(aCx, stack, data);
1250 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
1252 if (!caller) {
1253 caller = stack->GetAsyncCaller(aCx);
1255 stack.swap(caller);
1259 } // anonymous namespace
1261 // Queue a call to a console method. See the CALL_DELAY constant.
1262 /* static */
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);
1267 if (!console) {
1268 return;
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)) {
1278 return;
1281 AssertIsOnOwningThread();
1283 ConsoleCommon::ClearException ce(aCx);
1285 RefPtr<ConsoleCallData> callData =
1286 new ConsoleCallData(aMethodName, aMethodString, this);
1287 if (!StoreCallData(aCx, callData, aData)) {
1288 return;
1291 OriginAttributes oa;
1293 if (NS_IsMainThread()) {
1294 if (mGlobal) {
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)) {
1299 return;
1302 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1303 if (NS_WARN_IF(!principal)) {
1304 return;
1307 oa = principal->OriginAttributesRef();
1308 callData->SetAddonId(principal);
1310 #ifdef DEBUG
1311 if (!principal->IsSystemPrincipal()) {
1312 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
1313 if (webNav) {
1314 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1315 MOZ_ASSERT(loadContext);
1317 bool pb;
1318 if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
1319 MOZ_ASSERT(pb == !!oa.mPrivateBrowsingId);
1323 #endif
1325 } else if (WorkletThread::IsOnWorkletThread()) {
1326 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
1327 MOZ_ASSERT(global);
1328 oa = global->Impl()->OriginAttributesRef();
1329 } else {
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));
1343 if (stack) {
1344 callData->mTopStackFrame.emplace();
1345 StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
1348 if (NS_IsMainThread()) {
1349 callData->mStack = stack;
1350 } else {
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)) {
1363 return;
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) {
1387 return;
1391 else if (aMethodName == MethodCountReset) {
1392 callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
1393 if (callData->mCountLabel.IsEmpty()) {
1394 return;
1398 // Before processing this CallData differently, it's time to call the dump
1399 // function.
1400 if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
1401 MaybeExecuteDumpFunction(aCx, aMethodString, aData, stack);
1402 } else if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) &&
1403 !aData.IsEmpty()) {
1404 MaybeExecuteDumpFunctionForTime(aCx, aMethodName, aMethodString,
1405 monotonicTimer, aData[0]);
1406 } else {
1407 MaybeExecuteDumpFunction(aCx, aMethodString, aData, nullptr);
1410 if (NS_IsMainThread()) {
1411 if (mInnerID) {
1412 callData->SetIDs(mOuterID, mInnerID);
1413 } else if (!mPassedInnerID.IsEmpty()) {
1414 callData->SetIDs(u"jsm"_ns, mPassedInnerID);
1415 } else {
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);
1430 return;
1433 if (WorkletThread::IsOnWorkletThread()) {
1434 RefPtr<ConsoleCallDataWorkletRunnable> runnable =
1435 ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData);
1436 if (!runnable) {
1437 return;
1440 NS_DispatchToMainThread(runnable);
1441 return;
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()) {
1477 // Already reified.
1478 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1479 return true;
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))) {
1488 return false;
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);
1497 return true;
1500 void MainThreadConsoleData::ProcessCallData(
1501 JSContext* aCx, ConsoleCallData* aData,
1502 const Sequence<JS::Value>& aArguments) {
1503 AssertIsOnMainThread();
1504 MOZ_ASSERT(aData);
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))) {
1523 return;
1526 if (!mStorage) {
1527 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1530 if (!mStorage) {
1531 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1532 return;
1535 nsAutoString innerID;
1537 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1538 if (aData->mIDType == ConsoleCallData::eString) {
1539 innerID = aData->mInnerIDString;
1540 } else {
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.");
1555 /* static */
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) {
1561 MOZ_ASSERT(aCx);
1562 MOZ_ASSERT(aData);
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;
1587 } else {
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;
1600 nsAutoCString pass;
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);
1606 nsAutoCString spec;
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) {
1621 case MethodLog:
1622 case MethodInfo:
1623 case MethodWarn:
1624 case MethodError:
1625 case MethodException:
1626 case MethodDebug:
1627 case MethodAssert:
1628 case MethodGroup:
1629 case MethodGroupCollapsed:
1630 case MethodTrace:
1631 event.mArguments.Construct();
1632 event.mStyles.Construct();
1633 if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1634 event.mArguments.Value(),
1635 event.mStyles.Value()))) {
1636 return false;
1639 break;
1641 default:
1642 event.mArguments.Construct();
1643 if (NS_WARN_IF(
1644 !event.mArguments.Value().AppendElements(aArguments, fallible))) {
1645 return false;
1649 if (aData->mMethodName == MethodGroup ||
1650 aData->mMethodName == MethodGroupCollapsed) {
1651 ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName,
1652 aGroupStack);
1655 else if (aData->mMethodName == MethodGroupEnd) {
1656 if (!UnstoreGroupName(event.mGroupName, aGroupStack)) {
1657 return false;
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))) {
1683 return false;
1686 JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
1687 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
1688 JSPROP_ENUMERATE))) {
1689 return false;
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))) {
1701 return false;
1703 } else {
1704 JSFunction* fun =
1705 js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace");
1706 if (NS_WARN_IF(!fun)) {
1707 return false;
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))) {
1718 return false;
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))) {
1727 return false;
1732 return true;
1735 namespace {
1737 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1738 // aSequence.
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)) {
1745 return false;
1748 if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
1749 return false;
1752 aOutput.Truncate();
1755 return true;
1758 } // namespace
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:
1777 // %s - string
1778 // %d,%i - integer
1779 // %f - double
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()) {
1800 return true;
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)) {
1810 return false;
1813 nsAutoJSString string;
1814 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1815 return false;
1818 if (string.IsEmpty()) {
1819 return aSequence.AppendElements(aData, fallible);
1822 nsString::const_iterator start, end;
1823 string.BeginReading(start);
1824 string.EndReading(end);
1826 nsString output;
1827 uint32_t index = 1;
1829 while (start != end) {
1830 if (*start != '%') {
1831 output.Append(*start);
1832 ++start;
1833 continue;
1836 ++start;
1837 if (start == end) {
1838 output.Append('%');
1839 break;
1842 if (*start == '%') {
1843 output.Append(*start);
1844 ++start;
1845 continue;
1848 nsAutoString tmp;
1849 tmp.Append('%');
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') {
1856 integer = 0;
1858 do {
1859 integer = integer * 10 + *start - '0';
1860 tmp.Append(*start);
1861 ++start;
1862 } while (*start >= '0' && *start <= '9' && start != end);
1865 if (start == end) {
1866 output.Append(tmp);
1867 break;
1870 if (*start == '.') {
1871 tmp.Append(*start);
1872 ++start;
1874 if (start == end) {
1875 output.Append(tmp);
1876 break;
1879 // '.' must be followed by a number.
1880 if (*start < '0' || *start > '9') {
1881 output.Append(tmp);
1882 continue;
1885 mantissa = 0;
1887 do {
1888 mantissa = mantissa * 10 + *start - '0';
1889 tmp.Append(*start);
1890 ++start;
1891 } while (*start >= '0' && *start <= '9' && start != end);
1893 if (start == end) {
1894 output.Append(tmp);
1895 break;
1899 char ch = *start;
1900 tmp.Append(ch);
1901 ++start;
1903 switch (ch) {
1904 case 'o':
1905 case 'O': {
1906 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1907 return false;
1910 JS::Rooted<JS::Value> v(aCx);
1911 if (index < aData.Length()) {
1912 v = aData[index++];
1915 if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
1916 return false;
1919 break;
1922 case 'c': {
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))) {
1930 return false;
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)) {
1937 return false;
1940 int32_t diff = aSequence.Length() - aStyles.Length();
1941 if (diff > 0) {
1942 for (int32_t i = 0; i < diff; i++) {
1943 if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
1944 return false;
1949 nsAutoJSString string;
1950 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1951 return false;
1954 if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
1955 return false;
1958 break;
1961 case 's':
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)) {
1966 return false;
1969 nsAutoJSString v;
1970 if (NS_WARN_IF(!v.init(aCx, jsString))) {
1971 return false;
1974 output.Append(v);
1976 break;
1978 case 'd':
1979 case 'i':
1980 if (index < aData.Length()) {
1981 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1983 int32_t v;
1984 if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
1985 return false;
1988 nsCString format;
1989 MakeFormatString(format, integer, mantissa, 'd');
1990 output.AppendPrintf(format.get(), v);
1992 break;
1994 case 'f':
1995 if (index < aData.Length()) {
1996 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1998 double v;
1999 if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
2000 return false;
2003 // nspr returns "nan", but we want to expose it as "NaN"
2004 if (std::isnan(v)) {
2005 output.AppendFloat(v);
2006 } else {
2007 nsCString format;
2008 MakeFormatString(format, integer, mantissa, 'f');
2009 output.AppendPrintf(format.get(), v);
2012 break;
2014 default:
2015 output.Append(tmp);
2016 break;
2020 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
2021 return false;
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))) {
2032 return false;
2036 return true;
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,
2043 nsAString& aName,
2044 nsTArray<nsString>* aGroupStack) {
2045 StringJoinAppend(
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));
2049 if (!jsString) {
2050 return;
2053 nsAutoJSString string;
2054 if (!string.init(aCx, jsString)) {
2055 return;
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()) {
2069 return false;
2072 aName = aGroupStack->PopLastElement();
2073 return true;
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);
2083 *aTimerValue = 0;
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) {
2103 if (entry) {
2104 return true;
2106 entry.Insert(aTimestamp);
2107 return false;
2108 })) {
2109 return eTimerAlreadyExists;
2112 *aTimerValue = aTimestamp;
2113 return eTimerDone;
2116 /* static */
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();
2135 return value;
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;
2154 nsAutoJSString key;
2155 if (NS_WARN_IF(!key.init(aCx, jsString))) {
2156 return eTimerJSException;
2159 aTimerLabel = key;
2161 DOMHighResTimeStamp value = 0;
2163 if (aCancelTimer) {
2164 if (!mTimerRegistry.Remove(key, &value)) {
2165 NS_WARNING("mTimerRegistry entry not found");
2166 return eTimerDoesntExist;
2168 } else {
2169 if (!mTimerRegistry.Get(key, &value)) {
2170 NS_WARNING("mTimerRegistry entry not found");
2171 return eTimerDoesntExist;
2175 *aTimerDuration = aTimestamp - value;
2176 return eTimerDone;
2179 /* static */
2180 JS::Value Console::CreateLogOrEndTimerValue(JSContext* aCx,
2181 const nsAString& aLabel,
2182 double aDuration,
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();
2197 return value;
2200 /* static */
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;
2209 switch (aStatus) {
2210 case eTimerAlreadyExists:
2211 error.mError.AssignLiteral("timerAlreadyExists");
2212 break;
2214 case eTimerDoesntExist:
2215 error.mError.AssignLiteral("timerDoesntExist");
2216 break;
2218 case eTimerJSException:
2219 error.mError.AssignLiteral("timerJSError");
2220 break;
2222 case eTimerMaxReached:
2223 error.mError.AssignLiteral("maxTimersExceeded");
2224 break;
2226 default:
2227 MOZ_CRASH("Unsupported status");
2228 break;
2231 JS::Rooted<JS::Value> value(aCx);
2232 if (!ToJSValue(aCx, error, &value)) {
2233 return JS::UndefinedValue();
2236 return value;
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));
2250 if (!jsString) {
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 {
2264 if (entry) {
2265 ++entry.Data();
2266 } else {
2267 if (maxCountersReached) {
2268 return MAX_PAGE_COUNTERS;
2270 entry.Insert(1);
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));
2287 if (!jsString) {
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)) {
2299 return 0;
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
2311 // aTimerLabel.
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();
2328 return value;
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();
2340 return value;
2343 /* static */
2344 bool Console::ShouldIncludeStackTrace(MethodName aMethodName) {
2345 switch (aMethodName) {
2346 case MethodError:
2347 case MethodException:
2348 case MethodAssert:
2349 case MethodTrace:
2350 return true;
2351 default:
2352 return false;
2356 JSObject* MainThreadConsoleData::GetOrCreateSandbox(JSContext* aCx,
2357 nsIPrincipal* aPrincipal) {
2358 AssertIsOnMainThread();
2360 if (!mSandbox) {
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))) {
2367 return nullptr;
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))) {
2381 return false;
2383 if (!mArgumentStorage.end()[-1].Initialize(aCx, aArguments)) {
2384 mArgumentStorage.shrinkBy(1);
2385 return false;
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]);
2399 return true;
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
2411 // window.close().
2412 if (index == mCallDataStorage.NoIndex) {
2413 return;
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) {
2428 return;
2431 JS::Rooted<JS::Value> value(aCx);
2433 JS::Rooted<JSObject*> callableGlobal(
2434 aCx, mConsoleEventNotifier->CallbackGlobalOrNull());
2435 if (NS_WARN_IF(!callableGlobal)) {
2436 return;
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
2442 // sent to.
2443 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2444 aCx, aArguments, callableGlobal, &value, aCallData, &mGroupStack))) {
2445 return;
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,
2455 ErrorResult& aRv) {
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);
2474 return;
2477 // Here we have aCx and sequence in the same compartment.
2478 // targetScope is the destination scope and value will be populated in its
2479 // compartment.
2480 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2481 aCx, sequence, targetScope, &value, mCallDataStorage[i],
2482 &mGroupStack))) {
2483 aRv.Throw(NS_ERROR_FAILURE);
2484 return;
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;
2509 /* static */
2510 already_AddRefed<Console> Console::GetConsole(const GlobalObject& aGlobal) {
2511 ErrorResult rv;
2512 RefPtr<Console> console = GetConsoleInternal(aGlobal, rv);
2513 if (NS_WARN_IF(rv.Failed()) || !console) {
2514 rv.SuppressException();
2515 return nullptr;
2518 console->AssertIsOnOwningThread();
2520 if (console->IsShuttingDown()) {
2521 return nullptr;
2524 return console.forget();
2527 /* static */
2528 already_AddRefed<Console> Console::GetConsoleInternal(
2529 const GlobalObject& aGlobal, ErrorResult& aRv) {
2530 // Window
2531 if (NS_IsMainThread()) {
2532 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2533 do_QueryInterface(aGlobal.GetAsSupports());
2535 // we are probably running a chrome script.
2536 if (!innerWindow) {
2537 RefPtr<Console> console = new Console(aGlobal.Context(), nullptr, 0, 0);
2538 console->Initialize(aRv);
2539 if (NS_WARN_IF(aRv.Failed())) {
2540 return nullptr;
2543 return console.forget();
2546 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow);
2547 return window->GetConsole(aGlobal.Context(), aRv);
2550 // Worklet
2551 nsCOMPtr<WorkletGlobalScope> workletScope =
2552 do_QueryInterface(aGlobal.GetAsSupports());
2553 if (workletScope) {
2554 WorkletThread::AssertIsOnWorkletThread();
2555 return workletScope->GetConsole(aGlobal.Context(), aRv);
2558 // Workers
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)) {
2567 return nullptr;
2570 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
2571 MOZ_ASSERT(scope);
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);
2593 MOZ_ASSERT(win);
2595 RefPtr<Performance> performance = win->GetPerformance();
2596 if (!performance) {
2597 return false;
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));
2611 if (!jsString) {
2612 return false;
2615 nsAutoJSString key;
2616 if (!key.init(aCx, jsString)) {
2617 return false;
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));
2627 if (!jsString) {
2628 return false;
2631 nsAutoJSString key;
2632 if (!key.init(aCx, jsString)) {
2633 return false;
2636 TimelineConsumers::AddMarkerForDocShell(
2637 docShell,
2638 MakeUnique<ConsoleTimelineMarker>(key, aMethodName == MethodTime
2639 ? MarkerTracingType::START
2640 : MarkerTracingType::END));
2643 return true;
2646 if (NS_IsMainThread()) {
2647 *aTimeStamp = (TimeStamp::Now() - mCreationTimeStamp).ToMilliseconds();
2648 return true;
2651 if (nsCOMPtr<WorkletGlobalScope> workletGlobal = do_QueryInterface(mGlobal)) {
2652 *aTimeStamp = workletGlobal->TimeStampToDOMHighRes(TimeStamp::Now());
2653 return true;
2656 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2657 MOZ_ASSERT(workerPrivate);
2659 *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
2660 return true;
2663 /* static */
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) {
2676 return;
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));
2692 if (!jsString) {
2693 continue;
2696 nsAutoJSString string;
2697 if (NS_WARN_IF(!string.init(aCx, jsString))) {
2698 return;
2701 if (i != 0) {
2702 message.AppendLiteral(" ");
2705 message.Append(string);
2708 message.AppendLiteral("\n");
2710 // aStack can be null.
2712 nsCOMPtr<nsIStackFrame> stack(aStack);
2714 while (stack) {
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);
2732 if (!caller) {
2733 caller = stack->GetAsyncCaller(aCx);
2736 stack.swap(caller);
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) {
2748 return;
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));
2763 if (!jsString) {
2764 return;
2767 nsAutoJSString string;
2768 if (NS_WARN_IF(!string.init(aCx, jsString))) {
2769 return;
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);
2784 return;
2787 NS_ConvertUTF16toUTF8 str(aMessage);
2788 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
2789 #ifdef ANDROID
2790 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
2791 #endif
2792 fputs(str.get(), stdout);
2793 fflush(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()) {
2803 return aLevel;
2806 NS_ConvertUTF16toUTF8 pref(aPref);
2807 nsAutoCString value;
2808 nsresult rv = Preferences::GetCString(pref.get(), value);
2809 if (NS_WARN_IF(NS_FAILED(rv))) {
2810 return aLevel;
2813 int index = FindEnumStringIndexImpl(value.get(), value.Length(),
2814 ConsoleLogLevelValues::strings);
2815 if (NS_WARN_IF(index < 0)) {
2816 nsString message;
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*/);
2822 return aLevel;
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 {
2836 switch (aLevel) {
2837 case ConsoleLogLevel::All:
2838 return 0;
2839 case ConsoleLogLevel::Debug:
2840 return 2;
2841 case ConsoleLogLevel::Log:
2842 return 3;
2843 case ConsoleLogLevel::Info:
2844 return 3;
2845 case ConsoleLogLevel::Clear:
2846 return 3;
2847 case ConsoleLogLevel::Trace:
2848 return 3;
2849 case ConsoleLogLevel::TimeLog:
2850 return 3;
2851 case ConsoleLogLevel::TimeEnd:
2852 return 3;
2853 case ConsoleLogLevel::Time:
2854 return 3;
2855 case ConsoleLogLevel::Group:
2856 return 3;
2857 case ConsoleLogLevel::GroupEnd:
2858 return 3;
2859 case ConsoleLogLevel::Profile:
2860 return 3;
2861 case ConsoleLogLevel::ProfileEnd:
2862 return 3;
2863 case ConsoleLogLevel::Dir:
2864 return 3;
2865 case ConsoleLogLevel::Dirxml:
2866 return 3;
2867 case ConsoleLogLevel::Warn:
2868 return 4;
2869 case ConsoleLogLevel::Error:
2870 return 5;
2871 case ConsoleLogLevel::Off:
2872 return UINT32_MAX;
2873 default:
2874 MOZ_CRASH(
2875 "ConsoleLogLevel is out of sync with the Console implementation!");
2876 return 0;
2880 uint32_t Console::InternalLogLevelToInteger(MethodName aName) const {
2881 switch (aName) {
2882 case MethodLog:
2883 return 3;
2884 case MethodInfo:
2885 return 3;
2886 case MethodWarn:
2887 return 4;
2888 case MethodError:
2889 return 5;
2890 case MethodException:
2891 return 5;
2892 case MethodDebug:
2893 return 2;
2894 case MethodTable:
2895 return 3;
2896 case MethodTrace:
2897 return 3;
2898 case MethodDir:
2899 return 3;
2900 case MethodDirxml:
2901 return 3;
2902 case MethodGroup:
2903 return 3;
2904 case MethodGroupCollapsed:
2905 return 3;
2906 case MethodGroupEnd:
2907 return 3;
2908 case MethodTime:
2909 return 3;
2910 case MethodTimeLog:
2911 return 3;
2912 case MethodTimeEnd:
2913 return 3;
2914 case MethodTimeStamp:
2915 return 3;
2916 case MethodAssert:
2917 return 3;
2918 case MethodCount:
2919 return 3;
2920 case MethodCountReset:
2921 return 3;
2922 case MethodClear:
2923 return 3;
2924 case MethodProfile:
2925 return 3;
2926 case MethodProfileEnd:
2927 return 3;
2928 default:
2929 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
2930 return 0;
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))) {
2939 return false;
2942 return true;
2945 void Console::ArgumentData::Trace(const TraceCallbacks& aCallbacks,
2946 void* aClosure) {
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))) {
2961 return false;
2965 return true;
2968 } // namespace mozilla::dom