Bug 1936278 - Prevent search mode chiclet from being dismissed when clicking in page...
[gecko.git] / dom / console / Console.cpp
blobd518c691644ff0e779f1ce41709f45d240cb0d7a
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/Mutex.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/StaticPrefs_devtools.h"
39 #include "mozilla/StaticPrefs_dom.h"
40 #include "nsCycleCollectionParticipant.h"
41 #include "nsDOMNavigationTiming.h"
42 #include "nsGlobalWindowInner.h"
43 #include "nsJSUtils.h"
44 #include "nsNetUtil.h"
45 #include "xpcpublic.h"
46 #include "nsContentUtils.h"
47 #include "nsDocShell.h"
48 #include "nsProxyRelease.h"
49 #include "nsReadableUtils.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 : mMutex("ConsoleCallData"),
114 mConsoleID(aConsole->mConsoleID),
115 mPrefix(aConsole->mPrefix),
116 mMethodName(aName),
117 mMicroSecondTimeStamp(JS_Now()),
118 mStartTimerValue(0),
119 mStartTimerStatus(Console::eTimerUnknown),
120 mLogTimerDuration(0),
121 mLogTimerStatus(Console::eTimerUnknown),
122 mCountValue(MAX_PAGE_COUNTERS),
123 mIDType(eUnknown),
124 mOuterIDNumber(0),
125 mInnerIDNumber(0),
126 mMethodString(aString) {}
128 void SetIDs(uint64_t aOuterID, uint64_t aInnerID) MOZ_REQUIRES(mMutex) {
129 MOZ_ASSERT(mIDType == eUnknown);
131 mOuterIDNumber = aOuterID;
132 mInnerIDNumber = aInnerID;
133 mIDType = eNumber;
136 void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
137 MOZ_REQUIRES(mMutex) {
138 MOZ_ASSERT(mIDType == eUnknown);
140 mOuterIDString = aOuterID;
141 mInnerIDString = aInnerID;
142 mIDType = eString;
145 void SetOriginAttributes(const OriginAttributes& aOriginAttributes)
146 MOZ_REQUIRES(mMutex) {
147 mOriginAttributes = aOriginAttributes;
150 void SetAddonId(nsIPrincipal* aPrincipal) MOZ_REQUIRES(mMutex) {
151 nsAutoString addonId;
152 aPrincipal->GetAddonId(addonId);
154 mAddonId = addonId;
157 void AssertIsOnOwningThread() const {
158 NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
161 Mutex mMutex;
163 const nsString mConsoleID MOZ_GUARDED_BY(mMutex);
164 const nsString mPrefix MOZ_GUARDED_BY(mMutex);
166 const Console::MethodName mMethodName MOZ_GUARDED_BY(mMutex);
167 int64_t mMicroSecondTimeStamp MOZ_GUARDED_BY(mMutex);
169 // These values are set in the owning thread and they contain the timestamp of
170 // when the new timer has started, the name of it and the status of the
171 // creation of it. If status is false, something went wrong. User
172 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
173 // monotonicTimer from Performance.now();
174 // They will be set on the owning thread and never touched again on that
175 // thread. They will be used in order to create a ConsoleTimerStart dictionary
176 // when console.time() is used.
177 DOMHighResTimeStamp mStartTimerValue MOZ_GUARDED_BY(mMutex);
178 nsString mStartTimerLabel MOZ_GUARDED_BY(mMutex);
179 Console::TimerStatus mStartTimerStatus MOZ_GUARDED_BY(mMutex);
181 // These values are set in the owning thread and they contain the duration,
182 // the name and the status of the LogTimer method. If status is false,
183 // something went wrong. They will be set on the owning thread and never
184 // touched again on that thread. They will be used in order to create a
185 // ConsoleTimerLogOrEnd dictionary. This members are set when
186 // console.timeEnd() or console.timeLog() are called.
187 double mLogTimerDuration MOZ_GUARDED_BY(mMutex);
188 nsString mLogTimerLabel MOZ_GUARDED_BY(mMutex);
189 Console::TimerStatus mLogTimerStatus MOZ_GUARDED_BY(mMutex);
191 // These 2 values are set by IncreaseCounter or ResetCounter on the owning
192 // thread and they are used by CreateCounterOrResetCounterValue.
193 // These members are set when console.count() or console.countReset() are
194 // called.
195 nsString mCountLabel MOZ_GUARDED_BY(mMutex);
196 uint32_t mCountValue MOZ_GUARDED_BY(mMutex);
198 // The concept of outerID and innerID is misleading because when a
199 // ConsoleCallData is created from a window, these are the window IDs, but
200 // when the object is created from a SharedWorker, a ServiceWorker or a
201 // subworker of a ChromeWorker these IDs are the type of worker and the
202 // filename of the callee.
203 // In Console.sys.mjs the ID is 'jsm'.
204 enum { eString, eNumber, eUnknown } mIDType MOZ_GUARDED_BY(mMutex);
206 uint64_t mOuterIDNumber MOZ_GUARDED_BY(mMutex);
207 nsString mOuterIDString MOZ_GUARDED_BY(mMutex);
209 uint64_t mInnerIDNumber MOZ_GUARDED_BY(mMutex);
210 nsString mInnerIDString MOZ_GUARDED_BY(mMutex);
212 OriginAttributes mOriginAttributes MOZ_GUARDED_BY(mMutex);
214 nsString mAddonId MOZ_GUARDED_BY(mMutex);
216 const nsString mMethodString MOZ_GUARDED_BY(mMutex);
218 // Stack management is complicated, because we want to do it as
219 // lazily as possible. Therefore, we have the following behavior:
220 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
221 // 2) mReifiedStack is initialized if we're created in a worker.
222 // 3) mStack is set (possibly to null if there is no JS on the stack) if
223 // we're created on main thread.
224 Maybe<ConsoleStackEntry> mTopStackFrame MOZ_GUARDED_BY(mMutex);
225 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack MOZ_GUARDED_BY(mMutex);
226 nsCOMPtr<nsIStackFrame> mStack MOZ_GUARDED_BY(mMutex);
228 private:
229 ~ConsoleCallData() = default;
231 NS_DECL_OWNINGTHREAD;
234 // MainThreadConsoleData instances are created on the Console thread and
235 // referenced from both main and Console threads in order to provide the same
236 // object for any ConsoleRunnables relating to the same Console. A Console
237 // owns a MainThreadConsoleData; MainThreadConsoleData does not keep its
238 // Console alive.
239 class MainThreadConsoleData final {
240 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData);
242 JSObject* GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
243 // This method must receive aCx and aArguments in the same JS::Compartment.
244 void ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
245 const Sequence<JS::Value>& aArguments);
247 private:
248 ~MainThreadConsoleData() {
249 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
250 mStorage.forget());
251 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
252 mSandbox.forget());
255 // All members, except for mRefCnt, are accessed only on the main thread,
256 // except in MainThreadConsoleData destruction, at which point there are no
257 // other references.
258 nsCOMPtr<nsIConsoleAPIStorage> mStorage;
259 RefPtr<JSObjectHolder> mSandbox;
260 nsTArray<nsString> mGroupStack;
263 // This base class must be extended for Worker and for Worklet.
264 class ConsoleRunnable : public StructuredCloneHolderBase {
265 public:
266 ~ConsoleRunnable() override {
267 MOZ_ASSERT(!mClonedData.mGlobal,
268 "mClonedData.mGlobal is set and cleared in a main thread scope");
269 // Clear the StructuredCloneHolderBase class.
270 Clear();
273 protected:
274 JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
275 const JS::CloneDataPolicy& aCloneDataPolicy,
276 uint32_t aTag, uint32_t aIndex) override {
277 AssertIsOnMainThread();
279 if (aTag == CONSOLE_TAG_BLOB) {
280 MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
282 JS::Rooted<JS::Value> val(aCx);
284 nsCOMPtr<nsIGlobalObject> global = mClonedData.mGlobal;
285 RefPtr<Blob> blob =
286 Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex));
287 if (!ToJSValue(aCx, blob, &val)) {
288 return nullptr;
292 return &val.toObject();
295 MOZ_CRASH("No other tags are supported.");
296 return nullptr;
299 bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
300 JS::Handle<JSObject*> aObj,
301 bool* aSameProcessScopeRequired) override {
302 RefPtr<Blob> blob;
303 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
304 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
305 mClonedData.mBlobs.Length()))) {
306 return false;
309 mClonedData.mBlobs.AppendElement(blob->Impl());
310 return true;
313 if (!JS_ObjectNotWritten(aWriter, aObj)) {
314 return false;
317 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
318 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
319 if (NS_WARN_IF(!jsString)) {
320 return false;
323 if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
324 return false;
327 return true;
330 // Helper method for CallData
331 void ProcessCallData(JSContext* aCx, MainThreadConsoleData* aConsoleData,
332 ConsoleCallData* aCallData) {
333 AssertIsOnMainThread();
335 ConsoleCommon::ClearException ce(aCx);
337 // This is the same policy as when writing from the other side, in
338 // WriteData.
339 JS::CloneDataPolicy cloneDataPolicy;
340 cloneDataPolicy.allowIntraClusterClonableSharedObjects();
341 cloneDataPolicy.allowSharedMemoryObjects();
343 JS::Rooted<JS::Value> argumentsValue(aCx);
344 if (!Read(aCx, &argumentsValue, cloneDataPolicy)) {
345 return;
348 MOZ_ASSERT(argumentsValue.isObject());
350 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
352 uint32_t length;
353 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
354 return;
357 Sequence<JS::Value> values;
358 SequenceRooter<JS::Value> arguments(aCx, &values);
360 for (uint32_t i = 0; i < length; ++i) {
361 JS::Rooted<JS::Value> value(aCx);
363 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
364 return;
367 if (!values.AppendElement(value, fallible)) {
368 return;
372 MOZ_ASSERT(values.Length() == length);
374 aConsoleData->ProcessCallData(aCx, aCallData, values);
377 // Generic
378 bool WriteArguments(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
379 ConsoleCommon::ClearException ce(aCx);
381 JS::Rooted<JSObject*> arguments(
382 aCx, JS::NewArrayObject(aCx, aArguments.Length()));
383 if (NS_WARN_IF(!arguments)) {
384 return false;
387 JS::Rooted<JS::Value> arg(aCx);
388 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
389 arg = aArguments[i];
390 if (NS_WARN_IF(
391 !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
392 return false;
396 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
397 return WriteData(aCx, value);
400 // Helper method for Profile calls
401 void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName,
402 const nsAString& aAction) {
403 AssertIsOnMainThread();
405 ConsoleCommon::ClearException ce(aCx);
407 JS::Rooted<JS::Value> argumentsValue(aCx);
408 bool ok = Read(aCx, &argumentsValue);
409 mClonedData.mGlobal = nullptr;
411 if (!ok) {
412 return;
415 MOZ_ASSERT(argumentsValue.isObject());
416 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
417 if (NS_WARN_IF(!argumentsObj)) {
418 return;
421 uint32_t length;
422 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
423 return;
426 Sequence<JS::Value> arguments;
428 for (uint32_t i = 0; i < length; ++i) {
429 JS::Rooted<JS::Value> value(aCx);
431 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
432 return;
435 if (!arguments.AppendElement(value, fallible)) {
436 return;
440 Console::ProfileMethodMainthread(aCx, aAction, arguments);
443 bool WriteData(JSContext* aCx, JS::Handle<JS::Value> aValue) {
444 // We use structuredClone to send the JSValue to the main-thread, in order
445 // to store it into the Console API Service. The consumer will be the
446 // console panel in the devtools and, because of this, we want to allow the
447 // cloning of sharedArrayBuffers and WASM modules.
448 JS::CloneDataPolicy cloneDataPolicy;
449 cloneDataPolicy.allowIntraClusterClonableSharedObjects();
450 cloneDataPolicy.allowSharedMemoryObjects();
452 if (NS_WARN_IF(
453 !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) {
454 // Ignore the message.
455 return false;
458 return true;
461 ConsoleStructuredCloneData mClonedData;
464 class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
465 protected:
466 explicit ConsoleWorkletRunnable(Console* aConsole)
467 : Runnable("dom::console::ConsoleWorkletRunnable"),
468 mConsoleData(aConsole->GetOrCreateMainThreadData()) {
469 WorkletThread::AssertIsOnWorkletThread();
470 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(aConsole->mGlobal);
471 MOZ_ASSERT(global);
472 mWorkletImpl = global->Impl();
473 MOZ_ASSERT(mWorkletImpl);
476 ~ConsoleWorkletRunnable() override = default;
478 protected:
479 RefPtr<MainThreadConsoleData> mConsoleData;
481 RefPtr<WorkletImpl> mWorkletImpl;
484 // This runnable appends a CallData object into the Console queue running on
485 // the main-thread.
486 class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
487 public:
488 static already_AddRefed<ConsoleCallDataWorkletRunnable> Create(
489 JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData,
490 const Sequence<JS::Value>& aArguments) {
491 WorkletThread::AssertIsOnWorkletThread();
493 RefPtr<ConsoleCallDataWorkletRunnable> runnable =
494 new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData);
496 if (!runnable->WriteArguments(aCx, aArguments)) {
497 return nullptr;
500 return runnable.forget();
503 private:
504 ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData)
505 : ConsoleWorkletRunnable(aConsole), mCallData(aCallData) {
506 WorkletThread::AssertIsOnWorkletThread();
507 MOZ_ASSERT(aCallData);
508 aCallData->AssertIsOnOwningThread();
510 const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo();
511 mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID());
514 ~ConsoleCallDataWorkletRunnable() override = default;
516 NS_IMETHOD Run() override {
517 AssertIsOnMainThread();
518 AutoJSAPI jsapi;
519 jsapi.Init();
520 JSContext* cx = jsapi.cx();
523 MutexAutoLock lock(mCallData->mMutex);
525 JSObject* sandbox =
526 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
527 JS::Rooted<JSObject*> global(cx, sandbox);
528 if (NS_WARN_IF(!global)) {
529 return NS_ERROR_FAILURE;
532 // The CreateSandbox call returns a proxy to the actual sandbox object. We
533 // don't need a proxy here.
534 global = js::UncheckedUnwrap(global);
535 JSAutoRealm ar(cx, global);
537 // We don't need to set a parent object in mCallData bacause there are not
538 // DOM objects exposed to worklet.
540 ProcessCallData(cx, mConsoleData, mCallData);
543 return NS_OK;
546 RefPtr<ConsoleCallData> mCallData;
549 class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
550 public ConsoleRunnable {
551 public:
552 explicit ConsoleWorkerRunnable(Console* aConsole)
553 : mConsoleData(aConsole->GetOrCreateMainThreadData()) {}
555 ~ConsoleWorkerRunnable() override = default;
557 bool Dispatch(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
558 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
559 MOZ_ASSERT(workerPrivate);
561 if (NS_WARN_IF(!WriteArguments(aCx, aArguments))) {
562 RunBackOnWorkerThreadForCleanup(workerPrivate);
563 return false;
566 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
567 // RunBackOnWorkerThreadForCleanup() will be called by
568 // WorkerProxyToMainThreadRunnable::Dispatch().
569 return false;
572 return true;
575 protected:
576 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
577 MOZ_ASSERT(aWorkerPrivate);
578 AssertIsOnMainThread();
580 // Walk up to our containing page
581 WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker();
583 nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow();
584 if (!window) {
585 RunWindowless(aWorkerPrivate);
586 } else {
587 RunWithWindow(aWorkerPrivate, window);
591 void RunWithWindow(WorkerPrivate* aWorkerPrivate,
592 nsPIDOMWindowInner* aWindow) {
593 MOZ_ASSERT(aWorkerPrivate);
594 AssertIsOnMainThread();
596 AutoJSAPI jsapi;
597 MOZ_ASSERT(aWindow);
599 RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
600 if (NS_WARN_IF(!jsapi.Init(win))) {
601 return;
604 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
605 if (NS_WARN_IF(!outerWindow)) {
606 return;
609 RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
610 aWindow);
613 void RunWindowless(WorkerPrivate* aWorkerPrivate) {
614 MOZ_ASSERT(aWorkerPrivate);
615 AssertIsOnMainThread();
617 WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker();
619 MOZ_ASSERT(!wp->GetWindow());
621 AutoJSAPI jsapi;
622 jsapi.Init();
624 JSContext* cx = jsapi.cx();
626 JS::Rooted<JSObject*> global(
627 cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal()));
628 if (NS_WARN_IF(!global)) {
629 return;
632 // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
633 // We don't need a proxy here.
634 global = js::UncheckedUnwrap(global);
636 JSAutoRealm ar(cx, global);
638 nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global);
639 if (NS_WARN_IF(!globalObject)) {
640 return;
643 RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr);
646 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
647 MOZ_ASSERT(aWorkerPrivate);
648 aWorkerPrivate->AssertIsOnWorkerThread();
651 // This method is called in the main-thread.
652 virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
653 WorkerPrivate* aWorkerPrivate,
654 nsPIDOMWindowOuter* aOuterWindow,
655 nsPIDOMWindowInner* aInnerWindow) = 0;
657 bool ForMessaging() const override { return true; }
659 RefPtr<MainThreadConsoleData> mConsoleData;
662 // This runnable appends a CallData object into the Console queue running on
663 // the main-thread.
664 class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
665 public:
666 ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
667 : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
668 MOZ_ASSERT(aCallData);
669 mCallData->AssertIsOnOwningThread();
672 private:
673 ~ConsoleCallDataWorkerRunnable() override = default;
675 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
676 WorkerPrivate* aWorkerPrivate,
677 nsPIDOMWindowOuter* aOuterWindow,
678 nsPIDOMWindowInner* aInnerWindow) override {
679 MOZ_ASSERT(aGlobal);
680 MOZ_ASSERT(aWorkerPrivate);
681 AssertIsOnMainThread();
683 // The windows have to run in parallel.
684 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
687 MutexAutoLock lock(mCallData->mMutex);
688 if (aOuterWindow) {
689 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
690 } else {
691 ConsoleStackEntry frame;
692 if (mCallData->mTopStackFrame) {
693 frame = *mCallData->mTopStackFrame;
696 nsCString id = frame.mFilename;
697 nsString innerID;
698 if (aWorkerPrivate->IsSharedWorker()) {
699 innerID = u"SharedWorker"_ns;
700 } else if (aWorkerPrivate->IsServiceWorker()) {
701 innerID = u"ServiceWorker"_ns;
702 // Use scope as ID so the webconsole can decide if the message should
703 // show up per tab
704 id = aWorkerPrivate->ServiceWorkerScope();
705 } else {
706 innerID = u"Worker"_ns;
709 mCallData->SetIDs(NS_ConvertUTF8toUTF16(id), innerID);
712 mClonedData.mGlobal = aGlobal;
714 ProcessCallData(aCx, mConsoleData, mCallData);
716 mClonedData.mGlobal = nullptr;
720 RefPtr<ConsoleCallData> mCallData;
723 // This runnable calls ProfileMethod() on the console on the main-thread.
724 class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable {
725 public:
726 static already_AddRefed<ConsoleProfileWorkletRunnable> Create(
727 JSContext* aCx, Console* aConsole, Console::MethodName aName,
728 const nsAString& aAction, const Sequence<JS::Value>& aArguments) {
729 WorkletThread::AssertIsOnWorkletThread();
731 RefPtr<ConsoleProfileWorkletRunnable> runnable =
732 new ConsoleProfileWorkletRunnable(aConsole, aName, aAction);
734 if (!runnable->WriteArguments(aCx, aArguments)) {
735 return nullptr;
738 return runnable.forget();
741 private:
742 ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName,
743 const nsAString& aAction)
744 : ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) {
745 MOZ_ASSERT(aConsole);
748 NS_IMETHOD Run() override {
749 AssertIsOnMainThread();
751 AutoJSAPI jsapi;
752 jsapi.Init();
753 JSContext* cx = jsapi.cx();
755 JSObject* sandbox =
756 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
757 JS::Rooted<JSObject*> global(cx, sandbox);
758 if (NS_WARN_IF(!global)) {
759 return NS_ERROR_FAILURE;
762 // The CreateSandbox call returns a proxy to the actual sandbox object. We
763 // don't need a proxy here.
764 global = js::UncheckedUnwrap(global);
766 JSAutoRealm ar(cx, global);
768 // We don't need to set a parent object in mCallData bacause there are not
769 // DOM objects exposed to worklet.
770 ProcessProfileData(cx, mName, mAction);
772 return NS_OK;
775 Console::MethodName mName;
776 nsString mAction;
779 // This runnable calls ProfileMethod() on the console on the main-thread.
780 class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
781 public:
782 ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
783 const nsAString& aAction)
784 : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) {
785 MOZ_ASSERT(aConsole);
788 private:
789 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
790 WorkerPrivate* aWorkerPrivate,
791 nsPIDOMWindowOuter* aOuterWindow,
792 nsPIDOMWindowInner* aInnerWindow) override {
793 AssertIsOnMainThread();
794 MOZ_ASSERT(aGlobal);
796 mClonedData.mGlobal = aGlobal;
798 ProcessProfileData(aCx, mName, mAction);
800 mClonedData.mGlobal = nullptr;
803 Console::MethodName mName;
804 nsString mAction;
807 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
809 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
810 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
811 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
812 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
813 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
814 tmp->Shutdown();
815 tmp->mArgumentStorage.clearAndFree();
816 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
818 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
819 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
820 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
821 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
822 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
824 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
825 for (uint32_t i = 0; i < tmp->mArgumentStorage.length(); ++i) {
826 tmp->mArgumentStorage[i].Trace(aCallbacks, aClosure);
828 NS_IMPL_CYCLE_COLLECTION_TRACE_END
830 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
831 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
833 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
834 NS_INTERFACE_MAP_ENTRY(nsIObserver)
835 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
836 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
837 NS_INTERFACE_MAP_END
839 /* static */
840 already_AddRefed<Console> Console::Create(JSContext* aCx,
841 nsPIDOMWindowInner* aWindow,
842 ErrorResult& aRv) {
843 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
845 uint64_t outerWindowID = 0;
846 uint64_t innerWindowID = 0;
848 if (aWindow) {
849 innerWindowID = aWindow->WindowID();
851 // Without outerwindow any console message coming from this object will not
852 // shown in the devtools webconsole. But this should be fine because
853 // probably we are shutting down, or the window is CCed/GCed.
854 nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
855 if (outerWindow) {
856 outerWindowID = outerWindow->WindowID();
860 RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow),
861 outerWindowID, innerWindowID);
862 console->Initialize(aRv);
863 if (NS_WARN_IF(aRv.Failed())) {
864 return nullptr;
867 return console.forget();
870 /* static */
871 already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
872 nsIGlobalObject* aGlobal,
873 uint64_t aOuterWindowID,
874 uint64_t aInnerWindowID,
875 ErrorResult& aRv) {
876 WorkletThread::AssertIsOnWorkletThread();
878 RefPtr<Console> console =
879 new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID);
880 console->Initialize(aRv);
881 if (NS_WARN_IF(aRv.Failed())) {
882 return nullptr;
885 return console.forget();
888 Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
889 uint64_t aOuterWindowID, uint64_t aInnerWindowID,
890 const nsAString& aPrefix)
891 : mGlobal(aGlobal),
892 mOuterID(aOuterWindowID),
893 mInnerID(aInnerWindowID),
894 mDumpToStdout(false),
895 mLogModule(nullptr),
896 mPrefix(aPrefix),
897 mChromeInstance(false),
898 mCurrentLogLevel(WebIDLLogLevelToInteger(ConsoleLogLevel::All)),
899 mStatus(eUnknown),
900 mCreationTimeStamp(TimeStamp::Now()) {
901 // Let's enable the dumping to stdout by default for chrome.
902 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
903 mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
904 } else {
905 mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
908 // By default, the console uses "console" MOZ_LOG module name,
909 // but ConsoleInstance may pass a custom prefix which we will use a module
910 // name.
911 mLogModule = mPrefix.IsEmpty()
912 ? LogModule::Get("console")
913 : LogModule::Get(NS_ConvertUTF16toUTF8(mPrefix).get());
915 mozilla::HoldJSObjects(this);
918 Console::~Console() {
919 AssertIsOnOwningThread();
920 Shutdown();
921 mozilla::DropJSObjects(this);
924 void Console::Initialize(ErrorResult& aRv) {
925 AssertIsOnOwningThread();
926 MOZ_ASSERT(mStatus == eUnknown);
928 if (NS_IsMainThread()) {
929 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
930 if (NS_WARN_IF(!obs)) {
931 aRv.Throw(NS_ERROR_FAILURE);
932 return;
935 if (mInnerID) {
936 aRv = obs->AddObserver(this, "inner-window-destroyed", true);
937 if (NS_WARN_IF(aRv.Failed())) {
938 return;
942 aRv = obs->AddObserver(this, "memory-pressure", true);
943 if (NS_WARN_IF(aRv.Failed())) {
944 return;
948 mStatus = eInitialized;
951 void Console::Shutdown() {
952 AssertIsOnOwningThread();
954 if (mStatus == eUnknown || mStatus == eShuttingDown) {
955 return;
958 if (NS_IsMainThread()) {
959 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
960 if (obs) {
961 obs->RemoveObserver(this, "inner-window-destroyed");
962 obs->RemoveObserver(this, "memory-pressure");
966 mTimerRegistry.Clear();
967 mCounterRegistry.Clear();
969 ClearStorage();
970 mCallDataStorage.Clear();
972 mStatus = eShuttingDown;
975 NS_IMETHODIMP
976 Console::Observe(nsISupports* aSubject, const char* aTopic,
977 const char16_t* aData) {
978 AssertIsOnMainThread();
980 if (!strcmp(aTopic, "inner-window-destroyed")) {
981 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
982 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
984 uint64_t innerID;
985 nsresult rv = wrapper->GetData(&innerID);
986 NS_ENSURE_SUCCESS(rv, rv);
988 if (innerID == mInnerID) {
989 Shutdown();
992 return NS_OK;
995 if (!strcmp(aTopic, "memory-pressure")) {
996 ClearStorage();
997 return NS_OK;
1000 return NS_OK;
1003 void Console::ClearStorage() {
1004 mCallDataStorage.Clear();
1005 mArgumentStorage.clearAndFree();
1008 #define METHOD(name, string) \
1009 /* static */ void Console::name(const GlobalObject& aGlobal, \
1010 const Sequence<JS::Value>& aData) { \
1011 Method(aGlobal, Method##name, nsLiteralString(string), aData); \
1014 METHOD(Log, u"log")
1015 METHOD(Info, u"info")
1016 METHOD(Warn, u"warn")
1017 METHOD(Error, u"error")
1018 METHOD(Exception, u"exception")
1019 METHOD(Debug, u"debug")
1020 METHOD(Table, u"table")
1021 METHOD(Trace, u"trace")
1023 // Displays an interactive listing of all the properties of an object.
1024 METHOD(Dir, u"dir");
1025 METHOD(Dirxml, u"dirxml");
1027 METHOD(Group, u"group")
1028 METHOD(GroupCollapsed, u"groupCollapsed")
1030 #undef METHOD
1032 /* static */
1033 void Console::Clear(const GlobalObject& aGlobal) {
1034 const Sequence<JS::Value> data;
1035 Method(aGlobal, MethodClear, u"clear"_ns, data);
1038 /* static */
1039 void Console::GroupEnd(const GlobalObject& aGlobal) {
1040 const Sequence<JS::Value> data;
1041 Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data);
1044 /* static */
1045 void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
1046 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns);
1049 /* static */
1050 void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
1051 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
1052 u"timeEnd"_ns);
1055 /* static */
1056 void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
1057 const Sequence<JS::Value>& aData) {
1058 StringMethod(aGlobal, aLabel, aData, MethodTimeLog, u"timeLog"_ns);
1061 /* static */
1062 void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
1063 const Sequence<JS::Value>& aData,
1064 MethodName aMethodName,
1065 const nsAString& aMethodString) {
1066 RefPtr<Console> console = GetConsole(aGlobal);
1067 if (!console) {
1068 return;
1071 console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
1072 aMethodString);
1075 void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
1076 const Sequence<JS::Value>& aData,
1077 MethodName aMethodName,
1078 const nsAString& aMethodString) {
1079 ConsoleCommon::ClearException ce(aCx);
1081 Sequence<JS::Value> data;
1082 SequenceRooter<JS::Value> rooter(aCx, &data);
1084 JS::Rooted<JS::Value> value(aCx);
1085 if (!dom::ToJSValue(aCx, aLabel, &value)) {
1086 return;
1089 if (!data.AppendElement(value, fallible)) {
1090 return;
1093 for (uint32_t i = 0; i < aData.Length(); ++i) {
1094 if (!data.AppendElement(aData[i], fallible)) {
1095 return;
1099 MethodInternal(aCx, aMethodName, aMethodString, data);
1102 /* static */
1103 void Console::TimeStamp(const GlobalObject& aGlobal,
1104 const JS::Handle<JS::Value> aData) {
1105 JSContext* cx = aGlobal.Context();
1107 ConsoleCommon::ClearException ce(cx);
1109 Sequence<JS::Value> data;
1110 SequenceRooter<JS::Value> rooter(cx, &data);
1112 if (aData.isString() && !data.AppendElement(aData, fallible)) {
1113 return;
1116 Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data);
1119 /* static */
1120 void Console::Profile(const GlobalObject& aGlobal,
1121 const Sequence<JS::Value>& aData) {
1122 ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData);
1125 /* static */
1126 void Console::ProfileEnd(const GlobalObject& aGlobal,
1127 const Sequence<JS::Value>& aData) {
1128 ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData);
1131 /* static */
1132 void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
1133 const nsAString& aAction,
1134 const Sequence<JS::Value>& aData) {
1135 RefPtr<Console> console = GetConsole(aGlobal);
1136 if (!console) {
1137 return;
1140 JSContext* cx = aGlobal.Context();
1141 console->ProfileMethodInternal(cx, aName, aAction, aData);
1144 void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
1145 const nsAString& aAction,
1146 const Sequence<JS::Value>& aData) {
1147 if (!ShouldProceed(aMethodName)) {
1148 return;
1151 MaybeExecuteDumpFunction(aCx, aMethodName, aAction, aData, nullptr,
1152 DOMHighResTimeStamp(0.0));
1154 if (WorkletThread::IsOnWorkletThread()) {
1155 RefPtr<ConsoleProfileWorkletRunnable> runnable =
1156 ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
1157 aData);
1158 if (!runnable) {
1159 return;
1162 NS_DispatchToMainThread(runnable.forget());
1163 return;
1166 if (!NS_IsMainThread()) {
1167 // Here we are in a worker thread.
1168 RefPtr<ConsoleProfileWorkerRunnable> runnable =
1169 new ConsoleProfileWorkerRunnable(this, aMethodName, aAction);
1171 runnable->Dispatch(aCx, aData);
1172 return;
1175 ProfileMethodMainthread(aCx, aAction, aData);
1178 // static
1179 void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction,
1180 const Sequence<JS::Value>& aData) {
1181 MOZ_ASSERT(NS_IsMainThread());
1182 ConsoleCommon::ClearException ce(aCx);
1184 RootedDictionary<ConsoleProfileEvent> event(aCx);
1185 event.mAction = aAction;
1186 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1188 event.mArguments.Construct();
1189 Sequence<JS::Value>& sequence = event.mArguments.Value();
1191 for (uint32_t i = 0; i < aData.Length(); ++i) {
1192 if (!sequence.AppendElement(aData[i], fallible)) {
1193 return;
1197 JS::Rooted<JS::Value> eventValue(aCx);
1198 if (!ToJSValue(aCx, event, &eventValue)) {
1199 return;
1202 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1203 MOZ_ASSERT(eventObj);
1205 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1206 JSPROP_ENUMERATE)) {
1207 return;
1210 nsIXPConnect* xpc = nsContentUtils::XPConnect();
1211 nsCOMPtr<nsISupports> wrapper;
1212 const nsIID& iid = NS_GET_IID(nsISupports);
1214 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
1215 return;
1218 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1219 if (obs) {
1220 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1224 /* static */
1225 void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1226 const Sequence<JS::Value>& aData) {
1227 if (!aCondition) {
1228 Method(aGlobal, MethodAssert, u"assert"_ns, aData);
1232 /* static */
1233 void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
1234 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
1235 u"count"_ns);
1238 /* static */
1239 void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
1240 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
1241 u"countReset"_ns);
1244 namespace {
1246 void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
1247 ConsoleStackEntry& aStackEntry) {
1248 MOZ_ASSERT(aStackFrame);
1250 aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
1251 aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx);
1252 aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx);
1253 aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx);
1255 aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
1257 nsString cause;
1258 aStackFrame->GetAsyncCause(aCx, cause);
1259 if (!cause.IsEmpty()) {
1260 aStackEntry.mAsyncCause.Construct(cause);
1264 void ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
1265 nsTArray<ConsoleStackEntry>& aRefiedStack) {
1266 nsCOMPtr<nsIStackFrame> stack(aStack);
1268 while (stack) {
1269 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1270 StackFrameToStackEntry(aCx, stack, data);
1272 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
1274 if (!caller) {
1275 caller = stack->GetAsyncCaller(aCx);
1277 stack.swap(caller);
1281 } // anonymous namespace
1283 // Queue a call to a console method. See the CALL_DELAY constant.
1284 /* static */
1285 void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
1286 const nsAString& aMethodString,
1287 const Sequence<JS::Value>& aData) {
1288 RefPtr<Console> console = GetConsole(aGlobal);
1289 if (!console) {
1290 return;
1293 console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData);
1296 void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
1297 const nsAString& aMethodString,
1298 const Sequence<JS::Value>& aData) {
1299 if (!ShouldProceed(aMethodName)) {
1300 return;
1303 AssertIsOnOwningThread();
1305 ConsoleCommon::ClearException ce(aCx);
1307 RefPtr<ConsoleCallData> callData =
1308 new ConsoleCallData(aMethodName, aMethodString, this);
1310 MutexAutoLock lock(callData->mMutex);
1312 if (!StoreCallData(aCx, callData, aData)) {
1313 return;
1316 OriginAttributes oa;
1318 if (NS_IsMainThread()) {
1319 if (mGlobal) {
1320 // Save the principal's OriginAttributes in the console event data
1321 // so that we will be able to filter messages by origin attributes.
1322 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
1323 if (NS_WARN_IF(!sop)) {
1324 return;
1327 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1328 if (NS_WARN_IF(!principal)) {
1329 return;
1332 oa = principal->OriginAttributesRef();
1333 callData->SetAddonId(principal);
1335 #ifdef DEBUG
1336 if (!principal->IsSystemPrincipal()) {
1337 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
1338 if (webNav) {
1339 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1340 MOZ_ASSERT(loadContext);
1342 bool pb;
1343 if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
1344 MOZ_ASSERT(pb == oa.IsPrivateBrowsing());
1348 #endif
1350 } else if (WorkletThread::IsOnWorkletThread()) {
1351 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
1352 MOZ_ASSERT(global);
1353 oa = global->Impl()->OriginAttributesRef();
1354 } else {
1355 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1356 MOZ_ASSERT(workerPrivate);
1357 oa = workerPrivate->GetOriginAttributes();
1360 callData->SetOriginAttributes(oa);
1362 JS::StackCapture captureMode =
1363 ShouldIncludeStackTrace(aMethodName)
1364 ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH))
1365 : JS::StackCapture(JS::FirstSubsumedFrame(aCx));
1366 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode));
1368 if (stack) {
1369 callData->mTopStackFrame.emplace();
1370 StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
1373 if (NS_IsMainThread()) {
1374 callData->mStack = stack;
1375 } else {
1376 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1377 // before we post our runnable to the main thread.
1378 callData->mReifiedStack.emplace();
1379 ReifyStack(aCx, stack, *callData->mReifiedStack);
1382 DOMHighResTimeStamp monotonicTimer = 0.0;
1384 // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1385 if ((aMethodName == MethodTime || aMethodName == MethodTimeLog ||
1386 aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) &&
1387 !MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
1388 return;
1391 if (aMethodName == MethodTime && !aData.IsEmpty()) {
1392 callData->mStartTimerStatus =
1393 StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel,
1394 &callData->mStartTimerValue);
1397 else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
1398 callData->mLogTimerStatus =
1399 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1400 &callData->mLogTimerDuration, true /* Cancel timer */);
1403 else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
1404 callData->mLogTimerStatus =
1405 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1406 &callData->mLogTimerDuration, false /* Cancel timer */);
1409 else if (aMethodName == MethodCount) {
1410 callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
1411 if (!callData->mCountValue) {
1412 return;
1416 else if (aMethodName == MethodCountReset) {
1417 callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
1418 if (callData->mCountLabel.IsEmpty()) {
1419 return;
1423 // Before processing this CallData differently, it's time to call the dump
1424 // function.
1426 // Only log the stack trace for console.trace() and console.assert()
1427 if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
1428 MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, stack,
1429 monotonicTimer);
1430 } else {
1431 MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, nullptr,
1432 monotonicTimer);
1435 if (NS_IsMainThread()) {
1436 if (mInnerID) {
1437 callData->SetIDs(mOuterID, mInnerID);
1438 } else if (!mPassedInnerID.IsEmpty()) {
1439 callData->SetIDs(u"jsm"_ns, mPassedInnerID);
1440 } else {
1441 nsAutoCString filename;
1442 if (callData->mTopStackFrame.isSome()) {
1443 filename = callData->mTopStackFrame->mFilename;
1445 callData->SetIDs(u"jsm"_ns, NS_ConvertUTF8toUTF16(filename));
1448 GetOrCreateMainThreadData()->ProcessCallData(aCx, callData, aData);
1450 // Just because we don't want to expose
1451 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1452 // cleanup the mCallDataStorage:
1453 UnstoreCallData(callData);
1454 return;
1457 if (WorkletThread::IsOnWorkletThread()) {
1458 RefPtr<ConsoleCallDataWorkletRunnable> runnable =
1459 ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData);
1460 if (!runnable) {
1461 return;
1464 NS_DispatchToMainThread(runnable);
1465 return;
1468 // We do this only in workers for now.
1469 NotifyHandler(aCx, aData, callData);
1471 if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
1472 RefPtr<ConsoleCallDataWorkerRunnable> runnable =
1473 new ConsoleCallDataWorkerRunnable(this, callData);
1474 Unused << NS_WARN_IF(!runnable->Dispatch(aCx, aData));
1478 MainThreadConsoleData* Console::GetOrCreateMainThreadData() {
1479 AssertIsOnOwningThread();
1481 if (!mMainThreadData) {
1482 mMainThreadData = new MainThreadConsoleData();
1485 return mMainThreadData;
1488 // We store information to lazily compute the stack in the reserved slots of
1489 // LazyStackGetter. The first slot always stores a JS object: it's either the
1490 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1491 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1492 // reified the stack yet, or an UndefinedValue() otherwise.
1493 enum { SLOT_STACKOBJ, SLOT_RAW_STACK };
1495 bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
1496 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1497 JS::Rooted<JSObject*> callee(aCx, &args.callee());
1499 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1500 if (v.isUndefined()) {
1501 // Already reified.
1502 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1503 return true;
1506 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1507 nsTArray<ConsoleStackEntry> reifiedStack;
1508 ReifyStack(aCx, stack, reifiedStack);
1510 JS::Rooted<JS::Value> stackVal(aCx);
1511 if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
1512 return false;
1515 MOZ_ASSERT(stackVal.isObject());
1517 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1518 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1520 args.rval().set(stackVal);
1521 return true;
1524 void MainThreadConsoleData::ProcessCallData(
1525 JSContext* aCx, ConsoleCallData* aData,
1526 const Sequence<JS::Value>& aArguments) {
1527 AssertIsOnMainThread();
1528 MOZ_ASSERT(aData);
1529 aData->mMutex.AssertCurrentThreadOwns();
1531 JS::Rooted<JS::Value> eventValue(aCx);
1533 // We want to create a console event object and pass it to our
1534 // nsIConsoleAPIStorage implementation. We want to define some accessor
1535 // properties on this object, and those will need to keep an nsIStackFrame
1536 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1537 // further, passing untrusted objects to system code is likely to run afoul of
1538 // Object Xrays. So we want to wrap in a system-principal scope here. But
1539 // which one? We could cheat and try to get the underlying JSObject* of
1540 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1541 // with explicit permission from the XPConnect module owner. If you're
1542 // tempted to do that anywhere else, talk to said module owner first.
1544 // aCx and aArguments are in the same compartment.
1545 JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
1546 if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
1547 aCx, aArguments, targetScope, &eventValue, aData, &mGroupStack))) {
1548 return;
1551 if (!mStorage) {
1552 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1555 if (!mStorage) {
1556 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1557 return;
1560 nsAutoString innerID;
1562 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1563 if (aData->mIDType == ConsoleCallData::eString) {
1564 innerID = aData->mInnerIDString;
1565 } else {
1566 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1567 innerID.AppendInt(aData->mInnerIDNumber);
1570 if (aData->mMethodName == Console::MethodClear) {
1571 DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
1572 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
1575 if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
1576 NS_WARNING("Failed to record a console event.");
1580 /* static */
1581 bool Console::PopulateConsoleNotificationInTheTargetScope(
1582 JSContext* aCx, const Sequence<JS::Value>& aArguments,
1583 JS::Handle<JSObject*> aTargetScope,
1584 JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData,
1585 nsTArray<nsString>* aGroupStack) {
1586 MOZ_ASSERT(aCx);
1587 MOZ_ASSERT(aData);
1588 MOZ_ASSERT(aTargetScope);
1589 MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
1591 aData->mMutex.AssertCurrentThreadOwns();
1593 ConsoleStackEntry frame;
1594 if (aData->mTopStackFrame) {
1595 frame = *aData->mTopStackFrame;
1598 ConsoleCommon::ClearException ce(aCx);
1599 RootedDictionary<ConsoleEvent> event(aCx);
1601 event.mAddonId = aData->mAddonId;
1603 event.mID.Construct();
1604 event.mInnerID.Construct();
1606 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1608 if (aData->mIDType == ConsoleCallData::eString) {
1609 event.mID.Value().SetAsString() = aData->mOuterIDString;
1610 event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1611 } else if (aData->mIDType == ConsoleCallData::eNumber) {
1612 event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
1613 event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
1614 } else {
1615 // aData->mIDType can be eUnknown when we dispatch notifications via
1616 // mConsoleEventNotifier.
1617 event.mID.Value().SetAsUnsignedLongLong() = 0;
1618 event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
1621 event.mConsoleID = aData->mConsoleID;
1622 event.mLevel = aData->mMethodString;
1623 event.mFilename = frame.mFilename;
1624 event.mPrefix = aData->mPrefix;
1626 nsCOMPtr<nsIURI> filenameURI;
1627 nsAutoCString pass;
1628 if (NS_IsMainThread() &&
1629 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
1630 NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
1631 nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI =
1632 do_QueryInterface(filenameURI);
1633 nsAutoCString spec;
1634 if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
1635 event.mFilename = spec;
1639 event.mSourceId = frame.mSourceId;
1640 event.mLineNumber = frame.mLineNumber;
1641 event.mColumnNumber = frame.mColumnNumber;
1642 event.mFunctionName = frame.mFunctionName;
1643 event.mTimeStamp = aData->mMicroSecondTimeStamp / PR_USEC_PER_MSEC;
1644 event.mMicroSecondTimeStamp = aData->mMicroSecondTimeStamp;
1645 event.mPrivate = aData->mOriginAttributes.IsPrivateBrowsing();
1647 switch (aData->mMethodName) {
1648 case MethodLog:
1649 case MethodInfo:
1650 case MethodWarn:
1651 case MethodError:
1652 case MethodException:
1653 case MethodDebug:
1654 case MethodAssert:
1655 case MethodGroup:
1656 case MethodGroupCollapsed:
1657 case MethodTrace:
1658 event.mArguments.Construct();
1659 event.mStyles.Construct();
1660 if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1661 event.mArguments.Value(),
1662 event.mStyles.Value()))) {
1663 return false;
1666 break;
1668 default:
1669 event.mArguments.Construct();
1670 if (NS_WARN_IF(
1671 !event.mArguments.Value().AppendElements(aArguments, fallible))) {
1672 return false;
1676 if (aData->mMethodName == MethodGroup ||
1677 aData->mMethodName == MethodGroupCollapsed) {
1678 ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName,
1679 aGroupStack);
1682 else if (aData->mMethodName == MethodGroupEnd) {
1683 if (!UnstoreGroupName(event.mGroupName, aGroupStack)) {
1684 return false;
1688 else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
1689 event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
1690 aData->mStartTimerStatus);
1693 else if ((aData->mMethodName == MethodTimeEnd ||
1694 aData->mMethodName == MethodTimeLog) &&
1695 !aArguments.IsEmpty()) {
1696 event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
1697 aData->mLogTimerDuration,
1698 aData->mLogTimerStatus);
1701 else if (aData->mMethodName == MethodCount ||
1702 aData->mMethodName == MethodCountReset) {
1703 event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
1704 aData->mCountValue);
1707 JSAutoRealm ar2(aCx, aTargetScope);
1709 if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
1710 return false;
1713 JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
1714 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
1715 JSPROP_ENUMERATE))) {
1716 return false;
1719 if (ShouldIncludeStackTrace(aData->mMethodName)) {
1720 // Now define the "stacktrace" property on eventObj. There are two cases
1721 // here. Either we came from a worker and have a reified stack, or we want
1722 // to define a getter that will lazily reify the stack.
1723 if (aData->mReifiedStack) {
1724 JS::Rooted<JS::Value> stacktrace(aCx);
1725 if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
1726 NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
1727 JSPROP_ENUMERATE))) {
1728 return false;
1730 } else {
1731 JSFunction* fun =
1732 js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace");
1733 if (NS_WARN_IF(!fun)) {
1734 return false;
1737 JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
1739 // We want to store our stack in the function and have it stay alive. But
1740 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1741 // wrapper and the raw pointer: the former will keep the latter alive.
1742 JS::Rooted<JS::Value> stackVal(aCx);
1743 nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack, &stackVal);
1744 if (NS_WARN_IF(NS_FAILED(rv))) {
1745 return false;
1748 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1749 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1750 JS::PrivateValue(aData->mStack.get()));
1752 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", funObj,
1753 nullptr, JSPROP_ENUMERATE))) {
1754 return false;
1759 return true;
1762 namespace {
1764 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1765 // aSequence.
1766 bool FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence,
1767 nsString& aOutput) {
1768 if (!aOutput.IsEmpty()) {
1769 JS::Rooted<JSString*> str(
1770 aCx, JS_NewUCStringCopyN(aCx, aOutput.get(), aOutput.Length()));
1771 if (NS_WARN_IF(!str)) {
1772 return false;
1775 if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
1776 return false;
1779 aOutput.Truncate();
1782 return true;
1785 } // namespace
1787 static void MakeFormatString(nsCString& aFormat, int32_t aInteger,
1788 int32_t aMantissa, char aCh) {
1789 aFormat.Append('%');
1790 if (aInteger >= 0) {
1791 aFormat.AppendInt(aInteger);
1794 if (aMantissa >= 0) {
1795 aFormat.Append('.');
1796 aFormat.AppendInt(aMantissa);
1799 aFormat.Append(aCh);
1802 // If the first JS::Value of the array is a string, this method uses it to
1803 // format a string. The supported sequences are:
1804 // %s - string
1805 // %d,%i - integer
1806 // %f - double
1807 // %o,%O - a JS object.
1808 // %c - style string.
1809 // The output is an array where any object is a separated item, the rest is
1810 // unified in a format string.
1811 // Example if the input is:
1812 // "string: %s, integer: %d, object: %o, double: %f", 's', 1, window, 0.9
1813 // The output will be:
1814 // [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
1816 // The aStyles array is populated with the style strings that the function
1817 // finds based the format string. The index of the styles matches the indexes
1818 // of elements that need the custom styling from aSequence. For elements with
1819 // no custom styling the array is padded with null elements.
1820 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
1821 Sequence<JS::Value>& aSequence,
1822 Sequence<nsString>& aStyles) {
1823 // This method processes the arguments as format strings (%d, %i, %s...)
1824 // only if the first element of them is a valid and not-empty string.
1826 if (aData.IsEmpty()) {
1827 return true;
1830 if (aData.Length() == 1 || !aData[0].isString()) {
1831 return aSequence.AppendElements(aData, fallible);
1834 JS::Rooted<JS::Value> format(aCx, aData[0]);
1835 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1836 if (NS_WARN_IF(!jsString)) {
1837 return false;
1840 nsAutoJSString string;
1841 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1842 return false;
1845 if (string.IsEmpty()) {
1846 return aSequence.AppendElements(aData, fallible);
1849 nsString::const_iterator start, end;
1850 string.BeginReading(start);
1851 string.EndReading(end);
1853 nsString output;
1854 uint32_t index = 1;
1856 while (start != end) {
1857 if (*start != '%') {
1858 output.Append(*start);
1859 ++start;
1860 continue;
1863 ++start;
1864 if (start == end) {
1865 output.Append('%');
1866 break;
1869 if (*start == '%') {
1870 output.Append(*start);
1871 ++start;
1872 continue;
1875 nsAutoString tmp;
1876 tmp.Append('%');
1878 int32_t integer = -1;
1879 int32_t mantissa = -1;
1881 // Let's parse %<number>.<number> for %d and %f
1882 if (*start >= '0' && *start <= '9') {
1883 integer = 0;
1885 do {
1886 integer = integer * 10 + *start - '0';
1887 tmp.Append(*start);
1888 ++start;
1889 } while (*start >= '0' && *start <= '9' && start != end);
1892 if (start == end) {
1893 output.Append(tmp);
1894 break;
1897 if (*start == '.') {
1898 tmp.Append(*start);
1899 ++start;
1901 if (start == end) {
1902 output.Append(tmp);
1903 break;
1906 // '.' must be followed by a number.
1907 if (*start < '0' || *start > '9') {
1908 output.Append(tmp);
1909 continue;
1912 mantissa = 0;
1914 do {
1915 mantissa = mantissa * 10 + *start - '0';
1916 tmp.Append(*start);
1917 ++start;
1918 } while (*start >= '0' && *start <= '9' && start != end);
1920 if (start == end) {
1921 output.Append(tmp);
1922 break;
1926 char ch = *start;
1927 tmp.Append(ch);
1928 ++start;
1930 switch (ch) {
1931 case 'o':
1932 case 'O': {
1933 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1934 return false;
1937 JS::Rooted<JS::Value> v(aCx);
1938 if (index < aData.Length()) {
1939 v = aData[index++];
1942 if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
1943 return false;
1946 break;
1949 case 'c': {
1950 // If there isn't any output but there's already a style, then
1951 // discard the previous style and use the next one instead.
1952 if (output.IsEmpty() && !aStyles.IsEmpty()) {
1953 aStyles.RemoveLastElement();
1956 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1957 return false;
1960 if (index < aData.Length()) {
1961 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1962 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1963 if (NS_WARN_IF(!jsString)) {
1964 return false;
1967 int32_t diff = aSequence.Length() - aStyles.Length();
1968 if (diff > 0) {
1969 for (int32_t i = 0; i < diff; i++) {
1970 if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
1971 return false;
1976 nsAutoJSString string;
1977 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1978 return false;
1981 if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
1982 return false;
1985 break;
1988 case 's':
1989 if (index < aData.Length()) {
1990 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1991 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1992 if (NS_WARN_IF(!jsString)) {
1993 return false;
1996 nsAutoJSString v;
1997 if (NS_WARN_IF(!v.init(aCx, jsString))) {
1998 return false;
2001 output.Append(v);
2003 break;
2005 case 'd':
2006 case 'i':
2007 if (index < aData.Length()) {
2008 JS::Rooted<JS::Value> value(aCx, aData[index++]);
2010 if (value.isBigInt()) {
2011 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2012 if (NS_WARN_IF(!jsString)) {
2013 return false;
2016 nsAutoJSString v;
2017 if (NS_WARN_IF(!v.init(aCx, jsString))) {
2018 return false;
2020 output.Append(v);
2021 break;
2024 int32_t v;
2025 if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
2026 return false;
2029 nsCString format;
2030 MakeFormatString(format, integer, mantissa, 'd');
2031 output.AppendPrintf(format.get(), v);
2033 break;
2035 case 'f':
2036 if (index < aData.Length()) {
2037 JS::Rooted<JS::Value> value(aCx, aData[index++]);
2039 double v;
2040 if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
2041 return false;
2044 // nspr returns "nan", but we want to expose it as "NaN"
2045 if (std::isnan(v)) {
2046 output.AppendFloat(v);
2047 } else {
2048 nsCString format;
2049 MakeFormatString(format, integer, mantissa, 'f');
2050 output.AppendPrintf(format.get(), v);
2053 break;
2055 default:
2056 output.Append(tmp);
2057 break;
2061 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
2062 return false;
2065 // Discard trailing style element if there is no output to apply it to.
2066 if (aStyles.Length() > aSequence.Length()) {
2067 aStyles.TruncateLength(aSequence.Length());
2070 // The rest of the array, if unused by the format string.
2071 for (; index < aData.Length(); ++index) {
2072 if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
2073 return false;
2077 return true;
2080 // Stringify and Concat all the JS::Value in a single string using ' ' as
2081 // separator. The new group name will be stored in aGroupStack array.
2082 static void ComposeAndStoreGroupName(JSContext* aCx,
2083 const Sequence<JS::Value>& aData,
2084 nsAString& aName,
2085 nsTArray<nsString>* aGroupStack) {
2086 StringJoinAppend(
2087 aName, u" "_ns, aData, [aCx](nsAString& dest, const JS::Value& valueRef) {
2088 JS::Rooted<JS::Value> value(aCx, valueRef);
2089 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2090 if (!jsString) {
2091 return;
2094 nsAutoJSString string;
2095 if (!string.init(aCx, jsString)) {
2096 return;
2099 dest.Append(string);
2102 aGroupStack->AppendElement(aName);
2105 // Remove the last group name and return that name. It returns false if
2106 // aGroupStack is empty.
2107 static bool UnstoreGroupName(nsAString& aName,
2108 nsTArray<nsString>* aGroupStack) {
2109 if (aGroupStack->IsEmpty()) {
2110 return false;
2113 aName = aGroupStack->PopLastElement();
2114 return true;
2117 Console::TimerStatus Console::StartTimer(JSContext* aCx, const JS::Value& aName,
2118 DOMHighResTimeStamp aTimestamp,
2119 nsAString& aTimerLabel,
2120 DOMHighResTimeStamp* aTimerValue) {
2121 AssertIsOnOwningThread();
2122 MOZ_ASSERT(aTimerValue);
2124 *aTimerValue = 0;
2126 if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
2127 return eTimerMaxReached;
2130 JS::Rooted<JS::Value> name(aCx, aName);
2131 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2132 if (NS_WARN_IF(!jsString)) {
2133 return eTimerJSException;
2136 nsAutoJSString label;
2137 if (NS_WARN_IF(!label.init(aCx, jsString))) {
2138 return eTimerJSException;
2141 aTimerLabel = label;
2143 if (mTimerRegistry.WithEntryHandle(label, [&](auto&& entry) {
2144 if (entry) {
2145 return true;
2147 entry.Insert(aTimestamp);
2148 return false;
2149 })) {
2150 return eTimerAlreadyExists;
2153 *aTimerValue = aTimestamp;
2154 return eTimerDone;
2157 /* static */
2158 JS::Value Console::CreateStartTimerValue(JSContext* aCx,
2159 const nsAString& aTimerLabel,
2160 TimerStatus aTimerStatus) {
2161 MOZ_ASSERT(aTimerStatus != eTimerUnknown);
2163 if (aTimerStatus != eTimerDone) {
2164 return CreateTimerError(aCx, aTimerLabel, aTimerStatus);
2167 RootedDictionary<ConsoleTimerStart> timer(aCx);
2169 timer.mName = aTimerLabel;
2171 JS::Rooted<JS::Value> value(aCx);
2172 if (!ToJSValue(aCx, timer, &value)) {
2173 return JS::UndefinedValue();
2176 return value;
2179 Console::TimerStatus Console::LogTimer(JSContext* aCx, const JS::Value& aName,
2180 DOMHighResTimeStamp aTimestamp,
2181 nsAString& aTimerLabel,
2182 double* aTimerDuration,
2183 bool aCancelTimer) {
2184 AssertIsOnOwningThread();
2185 MOZ_ASSERT(aTimerDuration);
2187 *aTimerDuration = 0;
2189 JS::Rooted<JS::Value> name(aCx, aName);
2190 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2191 if (NS_WARN_IF(!jsString)) {
2192 return eTimerJSException;
2195 nsAutoJSString key;
2196 if (NS_WARN_IF(!key.init(aCx, jsString))) {
2197 return eTimerJSException;
2200 aTimerLabel = key;
2202 DOMHighResTimeStamp value = 0;
2204 if (aCancelTimer) {
2205 if (!mTimerRegistry.Remove(key, &value)) {
2206 NS_WARNING("mTimerRegistry entry not found");
2207 return eTimerDoesntExist;
2209 } else {
2210 if (!mTimerRegistry.Get(key, &value)) {
2211 NS_WARNING("mTimerRegistry entry not found");
2212 return eTimerDoesntExist;
2216 *aTimerDuration = aTimestamp - value;
2217 return eTimerDone;
2220 /* static */
2221 JS::Value Console::CreateLogOrEndTimerValue(JSContext* aCx,
2222 const nsAString& aLabel,
2223 double aDuration,
2224 TimerStatus aStatus) {
2225 if (aStatus != eTimerDone) {
2226 return CreateTimerError(aCx, aLabel, aStatus);
2229 RootedDictionary<ConsoleTimerLogOrEnd> timer(aCx);
2230 timer.mName = aLabel;
2231 timer.mDuration = aDuration;
2233 JS::Rooted<JS::Value> value(aCx);
2234 if (!ToJSValue(aCx, timer, &value)) {
2235 return JS::UndefinedValue();
2238 return value;
2241 /* static */
2242 JS::Value Console::CreateTimerError(JSContext* aCx, const nsAString& aLabel,
2243 TimerStatus aStatus) {
2244 MOZ_ASSERT(aStatus != eTimerUnknown && aStatus != eTimerDone);
2246 RootedDictionary<ConsoleTimerError> error(aCx);
2248 error.mName = aLabel;
2250 switch (aStatus) {
2251 case eTimerAlreadyExists:
2252 error.mError.AssignLiteral("timerAlreadyExists");
2253 break;
2255 case eTimerDoesntExist:
2256 error.mError.AssignLiteral("timerDoesntExist");
2257 break;
2259 case eTimerJSException:
2260 error.mError.AssignLiteral("timerJSError");
2261 break;
2263 case eTimerMaxReached:
2264 error.mError.AssignLiteral("maxTimersExceeded");
2265 break;
2267 default:
2268 MOZ_CRASH("Unsupported status");
2269 break;
2272 JS::Rooted<JS::Value> value(aCx);
2273 if (!ToJSValue(aCx, error, &value)) {
2274 return JS::UndefinedValue();
2277 return value;
2280 uint32_t Console::IncreaseCounter(JSContext* aCx,
2281 const Sequence<JS::Value>& aArguments,
2282 nsAString& aCountLabel) {
2283 AssertIsOnOwningThread();
2285 ConsoleCommon::ClearException ce(aCx);
2287 MOZ_ASSERT(!aArguments.IsEmpty());
2289 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2290 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2291 if (!jsString) {
2292 return 0; // We cannot continue.
2295 nsAutoJSString string;
2296 if (!string.init(aCx, jsString)) {
2297 return 0; // We cannot continue.
2300 aCountLabel = string;
2302 const bool maxCountersReached = mCounterRegistry.Count() >= MAX_PAGE_COUNTERS;
2303 return mCounterRegistry.WithEntryHandle(
2304 aCountLabel, [maxCountersReached](auto&& entry) -> uint32_t {
2305 if (entry) {
2306 ++entry.Data();
2307 } else {
2308 if (maxCountersReached) {
2309 return MAX_PAGE_COUNTERS;
2311 entry.Insert(1);
2313 return entry.Data();
2317 uint32_t Console::ResetCounter(JSContext* aCx,
2318 const Sequence<JS::Value>& aArguments,
2319 nsAString& aCountLabel) {
2320 AssertIsOnOwningThread();
2322 ConsoleCommon::ClearException ce(aCx);
2324 MOZ_ASSERT(!aArguments.IsEmpty());
2326 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2327 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2328 if (!jsString) {
2329 return 0; // We cannot continue.
2332 nsAutoJSString string;
2333 if (!string.init(aCx, jsString)) {
2334 return 0; // We cannot continue.
2337 aCountLabel = string;
2339 if (mCounterRegistry.Remove(aCountLabel)) {
2340 return 0;
2343 // Let's return something different than 0 if the key doesn't exist.
2344 return MAX_PAGE_COUNTERS;
2347 // This method generates a ConsoleCounter dictionary as JS::Value. If
2348 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
2349 // instead. See IncreaseCounter.
2350 // * aCx - this is the context that will root the returned value.
2351 // * aCountLabel - this label must be what IncreaseCounter received as
2352 // aTimerLabel.
2353 // * aCountValue - the return value of IncreaseCounter.
2354 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
2355 const nsAString& aCountLabel,
2356 uint32_t aCountValue) {
2357 ConsoleCommon::ClearException ce(aCx);
2359 if (aCountValue == MAX_PAGE_COUNTERS) {
2360 RootedDictionary<ConsoleCounterError> error(aCx);
2361 error.mLabel = aCountLabel;
2362 error.mError.AssignLiteral("counterDoesntExist");
2364 JS::Rooted<JS::Value> value(aCx);
2365 if (!ToJSValue(aCx, error, &value)) {
2366 return JS::UndefinedValue();
2369 return value;
2372 RootedDictionary<ConsoleCounter> data(aCx);
2373 data.mLabel = aCountLabel;
2374 data.mCount = aCountValue;
2376 JS::Rooted<JS::Value> value(aCx);
2377 if (!ToJSValue(aCx, data, &value)) {
2378 return JS::UndefinedValue();
2381 return value;
2384 /* static */
2385 bool Console::ShouldIncludeStackTrace(MethodName aMethodName) {
2386 switch (aMethodName) {
2387 case MethodError:
2388 case MethodException:
2389 case MethodAssert:
2390 case MethodTrace:
2391 return true;
2392 default:
2393 return false;
2397 JSObject* MainThreadConsoleData::GetOrCreateSandbox(JSContext* aCx,
2398 nsIPrincipal* aPrincipal) {
2399 AssertIsOnMainThread();
2401 if (!mSandbox) {
2402 nsIXPConnect* xpc = nsContentUtils::XPConnect();
2403 MOZ_ASSERT(xpc, "This should never be null!");
2405 JS::Rooted<JSObject*> sandbox(aCx);
2406 nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
2407 if (NS_WARN_IF(NS_FAILED(rv))) {
2408 return nullptr;
2411 mSandbox = new JSObjectHolder(aCx, sandbox);
2414 return mSandbox->GetJSObject();
2417 bool Console::StoreCallData(JSContext* aCx, ConsoleCallData* aCallData,
2418 const Sequence<JS::Value>& aArguments) {
2419 AssertIsOnOwningThread();
2421 if (NS_WARN_IF(!mArgumentStorage.growBy(1))) {
2422 return false;
2424 if (!mArgumentStorage.end()[-1].Initialize(aCx, aArguments)) {
2425 mArgumentStorage.shrinkBy(1);
2426 return false;
2429 MOZ_ASSERT(aCallData);
2430 MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
2432 mCallDataStorage.AppendElement(aCallData);
2434 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2436 if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
2437 mCallDataStorage.RemoveElementAt(0);
2438 mArgumentStorage.erase(&mArgumentStorage[0]);
2440 return true;
2443 void Console::UnstoreCallData(ConsoleCallData* aCallData) {
2444 AssertIsOnOwningThread();
2446 MOZ_ASSERT(aCallData);
2447 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2449 size_t index = mCallDataStorage.IndexOf(aCallData);
2450 // It can be that mCallDataStorage has been already cleaned in case the
2451 // processing of the argument of some Console methods triggers the
2452 // window.close().
2453 if (index == mCallDataStorage.NoIndex) {
2454 return;
2457 mCallDataStorage.RemoveElementAt(index);
2458 mArgumentStorage.erase(&mArgumentStorage[index]);
2461 void Console::NotifyHandler(JSContext* aCx,
2462 const Sequence<JS::Value>& aArguments,
2463 ConsoleCallData* aCallData) {
2464 AssertIsOnOwningThread();
2465 MOZ_ASSERT(!NS_IsMainThread());
2466 MOZ_ASSERT(aCallData);
2468 if (!mConsoleEventNotifier) {
2469 return;
2472 JS::Rooted<JS::Value> value(aCx);
2474 JS::Rooted<JSObject*> callableGlobal(
2475 aCx, mConsoleEventNotifier->CallbackGlobalOrNull());
2476 if (NS_WARN_IF(!callableGlobal)) {
2477 return;
2480 // aCx and aArguments are in the same compartment because this method is
2481 // called directly when a Console.something() runs.
2482 // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
2483 // sent to.
2484 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2485 aCx, aArguments, callableGlobal, &value, aCallData, &mGroupStack))) {
2486 return;
2489 JS::Rooted<JS::Value> ignored(aCx);
2490 RefPtr<AnyCallback> notifier(mConsoleEventNotifier);
2491 notifier->Call(value, &ignored);
2494 void Console::RetrieveConsoleEvents(JSContext* aCx,
2495 nsTArray<JS::Value>& aEvents,
2496 ErrorResult& aRv) {
2497 AssertIsOnOwningThread();
2499 // We don't want to expose this functionality to main-thread yet.
2500 MOZ_ASSERT(!NS_IsMainThread());
2502 JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
2504 for (uint32_t i = 0; i < mArgumentStorage.length(); ++i) {
2505 JS::Rooted<JS::Value> value(aCx);
2507 JS::Rooted<JSObject*> sequenceScope(aCx, mArgumentStorage[i].Global());
2508 JSAutoRealm ar(aCx, sequenceScope);
2510 Sequence<JS::Value> sequence;
2511 SequenceRooter<JS::Value> arguments(aCx, &sequence);
2513 if (!mArgumentStorage[i].PopulateArgumentsSequence(sequence)) {
2514 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2515 return;
2518 // Here we have aCx and sequence in the same compartment.
2519 // targetScope is the destination scope and value will be populated in its
2520 // compartment.
2522 MutexAutoLock lock(mCallDataStorage[i]->mMutex);
2523 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2524 aCx, sequence, targetScope, &value, mCallDataStorage[i],
2525 &mGroupStack))) {
2526 aRv.Throw(NS_ERROR_FAILURE);
2527 return;
2531 aEvents.AppendElement(value);
2535 void Console::SetConsoleEventHandler(AnyCallback* aHandler) {
2536 AssertIsOnOwningThread();
2538 // We don't want to expose this functionality to main-thread yet.
2539 MOZ_ASSERT(!NS_IsMainThread());
2541 mConsoleEventNotifier = aHandler;
2544 void Console::AssertIsOnOwningThread() const {
2545 NS_ASSERT_OWNINGTHREAD(Console);
2548 bool Console::IsShuttingDown() const {
2549 MOZ_ASSERT(mStatus != eUnknown);
2550 return mStatus == eShuttingDown;
2553 /* static */
2554 already_AddRefed<Console> Console::GetConsole(const GlobalObject& aGlobal) {
2555 ErrorResult rv;
2556 RefPtr<Console> console = GetConsoleInternal(aGlobal, rv);
2557 if (NS_WARN_IF(rv.Failed()) || !console) {
2558 rv.SuppressException();
2559 return nullptr;
2562 console->AssertIsOnOwningThread();
2564 if (console->IsShuttingDown()) {
2565 return nullptr;
2568 return console.forget();
2571 /* static */
2572 already_AddRefed<Console> Console::GetConsoleInternal(
2573 const GlobalObject& aGlobal, ErrorResult& aRv) {
2574 // Window
2575 if (NS_IsMainThread()) {
2576 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2577 do_QueryInterface(aGlobal.GetAsSupports());
2579 // we are probably running a chrome script.
2580 if (!innerWindow) {
2581 RefPtr<Console> console = new Console(aGlobal.Context(), nullptr, 0, 0);
2582 console->Initialize(aRv);
2583 if (NS_WARN_IF(aRv.Failed())) {
2584 return nullptr;
2587 return console.forget();
2590 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow);
2591 return window->GetConsole(aGlobal.Context(), aRv);
2594 // Worklet
2595 nsCOMPtr<WorkletGlobalScope> workletScope =
2596 do_QueryInterface(aGlobal.GetAsSupports());
2597 if (workletScope) {
2598 WorkletThread::AssertIsOnWorkletThread();
2599 return workletScope->GetConsole(aGlobal.Context(), aRv);
2602 // Workers
2603 MOZ_ASSERT(!NS_IsMainThread());
2605 JSContext* cx = aGlobal.Context();
2606 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
2607 MOZ_ASSERT(workerPrivate);
2609 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
2610 if (NS_WARN_IF(!global)) {
2611 return nullptr;
2614 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
2615 MOZ_ASSERT(scope);
2617 // Normal worker scope.
2618 if (scope == global) {
2619 return scope->GetConsole(aRv);
2622 // Debugger worker scope
2624 WorkerDebuggerGlobalScope* debuggerScope =
2625 workerPrivate->DebuggerGlobalScope();
2626 MOZ_ASSERT(debuggerScope);
2627 MOZ_ASSERT(debuggerScope == global, "Which kind of global do we have?");
2629 return debuggerScope->GetConsole(aRv);
2632 bool Console::MonotonicTimer(JSContext* aCx, MethodName aMethodName,
2633 const Sequence<JS::Value>& aData,
2634 DOMHighResTimeStamp* aTimeStamp) {
2635 if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(mGlobal)) {
2636 nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(innerWindow);
2637 MOZ_ASSERT(win);
2639 RefPtr<Performance> performance = win->GetPerformance();
2640 if (!performance) {
2641 return false;
2644 *aTimeStamp = performance->Now();
2645 return true;
2648 if (NS_IsMainThread()) {
2649 *aTimeStamp = (TimeStamp::Now() - mCreationTimeStamp).ToMilliseconds();
2650 return true;
2653 if (nsCOMPtr<WorkletGlobalScope> workletGlobal = do_QueryInterface(mGlobal)) {
2654 *aTimeStamp = workletGlobal->TimeStampToDOMHighRes(TimeStamp::Now());
2655 return true;
2658 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2659 MOZ_ASSERT(workerPrivate);
2661 *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
2662 return true;
2665 /* static */
2666 already_AddRefed<ConsoleInstance> Console::CreateInstance(
2667 const GlobalObject& aGlobal, const ConsoleInstanceOptions& aOptions) {
2668 RefPtr<ConsoleInstance> console =
2669 new ConsoleInstance(aGlobal.Context(), aOptions);
2670 return console.forget();
2673 void Console::StringifyElement(Element* aElement, nsAString& aOut) {
2674 aOut.AppendLiteral("<");
2675 aOut.Append(aElement->LocalName());
2676 uint32_t attrCount = aElement->GetAttrCount();
2677 nsAutoString idAttr;
2678 nsAutoString classAttr;
2679 nsAutoString nameAttr;
2680 nsAutoString otherAttrs;
2681 for (uint32_t i = 0; i < attrCount; i++) {
2682 BorrowedAttrInfo attrInfo = aElement->GetAttrInfoAt(i);
2683 nsAutoString attrValue;
2684 attrInfo.mValue->ToString(attrValue);
2686 const nsAttrName* attrName = attrInfo.mName;
2687 if (attrName->Equals(nsGkAtoms::id)) {
2688 idAttr.AppendLiteral(" id=\"");
2689 idAttr.Append(attrValue);
2690 idAttr.AppendLiteral("\"");
2691 } else if (attrName->Equals(nsGkAtoms::_class)) {
2692 classAttr.AppendLiteral(" class=\"");
2693 classAttr.Append(attrValue);
2694 classAttr.AppendLiteral("\"");
2695 } else if (attrName->Equals(nsGkAtoms::name)) {
2696 nameAttr.AppendLiteral(" name=\"");
2697 nameAttr.Append(attrValue);
2698 nameAttr.AppendLiteral("\"");
2699 } else {
2700 nsAutoString attrNameStr;
2701 attrName->GetQualifiedName(attrNameStr);
2702 otherAttrs.AppendLiteral(" ");
2703 otherAttrs.Append(attrNameStr);
2704 otherAttrs.AppendLiteral("=\"");
2705 otherAttrs.Append(attrValue);
2706 otherAttrs.AppendLiteral("\"");
2709 if (!idAttr.IsEmpty()) {
2710 aOut.Append(idAttr);
2712 if (!classAttr.IsEmpty()) {
2713 aOut.Append(classAttr);
2715 if (!nameAttr.IsEmpty()) {
2716 aOut.Append(nameAttr);
2718 if (!otherAttrs.IsEmpty()) {
2719 aOut.Append(otherAttrs);
2721 aOut.AppendLiteral(">");
2724 void Console::MaybeExecuteDumpFunction(JSContext* aCx, MethodName aMethodName,
2725 const nsAString& aMethodString,
2726 const Sequence<JS::Value>& aData,
2727 nsIStackFrame* aStack,
2728 DOMHighResTimeStamp aMonotonicTimer) {
2729 if (mLogModule->ShouldLog(InternalLogLevelToMozLog(aMethodName))) {
2730 nsString message = GetDumpMessage(aCx, aMethodName, aMethodString, aData,
2731 aStack, aMonotonicTimer, true);
2733 MOZ_LOG(mLogModule, InternalLogLevelToMozLog(aMethodName),
2734 ("%s", NS_ConvertUTF16toUTF8(message).get()));
2737 if (!mDumpFunction && !mDumpToStdout) {
2738 return;
2740 nsString message = GetDumpMessage(aCx, aMethodName, aMethodString, aData,
2741 aStack, aMonotonicTimer, false);
2743 ExecuteDumpFunction(message);
2746 nsString Console::GetDumpMessage(JSContext* aCx, MethodName aMethodName,
2747 const nsAString& aMethodString,
2748 const Sequence<JS::Value>& aData,
2749 nsIStackFrame* aStack,
2750 DOMHighResTimeStamp aMonotonicTimer,
2751 bool aIsForMozLog) {
2752 nsString message;
2753 // MOZ_LOG already logs either console or the prefix
2754 if (!aIsForMozLog) {
2755 message.AssignLiteral("console.");
2756 } else {
2757 message.AssignLiteral("");
2759 message.Append(aMethodString);
2760 message.AppendLiteral(": ");
2762 if (!aIsForMozLog && !mPrefix.IsEmpty()) {
2763 message.Append(mPrefix);
2764 message.AppendLiteral(": ");
2767 for (uint32_t i = 0; i < aData.Length(); ++i) {
2768 JS::Rooted<JS::Value> v(aCx, aData[i]);
2769 if (v.isObject()) {
2770 Element* element = nullptr;
2771 if (NS_SUCCEEDED(UNWRAP_OBJECT(Element, &v, element))) {
2772 if (i != 0) {
2773 message.AppendLiteral(" ");
2775 StringifyElement(element, message);
2776 continue;
2780 JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
2781 if (!jsString) {
2782 continue;
2785 nsAutoJSString string;
2786 if (NS_WARN_IF(!string.init(aCx, jsString))) {
2787 return message;
2790 if (i != 0) {
2791 message.AppendLiteral(" ");
2794 message.Append(string);
2797 if (aMethodName == MethodTime || aMethodName == MethodTimeEnd) {
2798 message.AppendLiteral(" @ ");
2799 message.AppendFloat(aMonotonicTimer);
2802 message.AppendLiteral("\n");
2804 // aStack can be null.
2806 nsCOMPtr<nsIStackFrame> stack(aStack);
2808 while (stack) {
2809 nsAutoCString filename;
2810 stack->GetFilename(aCx, filename);
2812 AppendUTF8toUTF16(filename, message);
2813 message.AppendLiteral(" ");
2815 message.AppendInt(stack->GetLineNumber(aCx));
2816 message.AppendLiteral(" ");
2818 nsAutoString functionName;
2819 stack->GetName(aCx, functionName);
2821 message.Append(functionName);
2822 message.AppendLiteral("\n");
2824 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
2826 if (!caller) {
2827 caller = stack->GetAsyncCaller(aCx);
2830 stack.swap(caller);
2833 return message;
2836 void Console::ExecuteDumpFunction(const nsAString& aMessage) {
2837 if (mDumpFunction) {
2838 RefPtr<ConsoleInstanceDumpCallback> dumpFunction(mDumpFunction);
2839 dumpFunction->Call(aMessage);
2840 return;
2843 NS_ConvertUTF16toUTF8 str(aMessage);
2844 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
2845 #ifdef ANDROID
2846 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
2847 #endif
2848 fputs(str.get(), stdout);
2849 fflush(stdout);
2852 bool Console::ShouldProceed(MethodName aName) const {
2853 return mCurrentLogLevel <= InternalLogLevelToInteger(aName);
2856 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const {
2857 switch (aLevel) {
2858 case ConsoleLogLevel::All:
2859 return 0;
2860 case ConsoleLogLevel::Debug:
2861 return 2;
2862 case ConsoleLogLevel::Log:
2863 return 3;
2864 case ConsoleLogLevel::Info:
2865 return 3;
2866 case ConsoleLogLevel::Clear:
2867 return 3;
2868 case ConsoleLogLevel::Trace:
2869 return 3;
2870 case ConsoleLogLevel::TimeLog:
2871 return 3;
2872 case ConsoleLogLevel::TimeEnd:
2873 return 3;
2874 case ConsoleLogLevel::Time:
2875 return 3;
2876 case ConsoleLogLevel::Group:
2877 return 3;
2878 case ConsoleLogLevel::GroupEnd:
2879 return 3;
2880 case ConsoleLogLevel::Profile:
2881 return 3;
2882 case ConsoleLogLevel::ProfileEnd:
2883 return 3;
2884 case ConsoleLogLevel::Dir:
2885 return 3;
2886 case ConsoleLogLevel::Dirxml:
2887 return 3;
2888 case ConsoleLogLevel::Warn:
2889 return 4;
2890 case ConsoleLogLevel::Error:
2891 return 5;
2892 case ConsoleLogLevel::Off:
2893 return UINT32_MAX;
2894 default:
2895 MOZ_CRASH(
2896 "ConsoleLogLevel is out of sync with the Console implementation!");
2897 return 0;
2901 uint32_t Console::InternalLogLevelToInteger(MethodName aName) const {
2902 switch (aName) {
2903 case MethodLog:
2904 return 3;
2905 case MethodInfo:
2906 return 3;
2907 case MethodWarn:
2908 return 4;
2909 case MethodError:
2910 return 5;
2911 case MethodException:
2912 return 5;
2913 case MethodDebug:
2914 return 2;
2915 case MethodTable:
2916 return 3;
2917 case MethodTrace:
2918 return 3;
2919 case MethodDir:
2920 return 3;
2921 case MethodDirxml:
2922 return 3;
2923 case MethodGroup:
2924 return 3;
2925 case MethodGroupCollapsed:
2926 return 3;
2927 case MethodGroupEnd:
2928 return 3;
2929 case MethodTime:
2930 return 3;
2931 case MethodTimeLog:
2932 return 3;
2933 case MethodTimeEnd:
2934 return 3;
2935 case MethodTimeStamp:
2936 return 3;
2937 case MethodAssert:
2938 return 3;
2939 case MethodCount:
2940 return 3;
2941 case MethodCountReset:
2942 return 3;
2943 case MethodClear:
2944 return 3;
2945 case MethodProfile:
2946 return 3;
2947 case MethodProfileEnd:
2948 return 3;
2949 default:
2950 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
2951 return 0;
2955 LogLevel Console::InternalLogLevelToMozLog(MethodName aName) const {
2956 switch (aName) {
2957 case MethodLog:
2958 return LogLevel::Info;
2959 case MethodInfo:
2960 return LogLevel::Info;
2961 case MethodWarn:
2962 return LogLevel::Warning;
2963 case MethodError:
2964 return LogLevel::Error;
2965 case MethodException:
2966 return LogLevel::Error;
2967 case MethodDebug:
2968 return LogLevel::Debug;
2969 case MethodTable:
2970 return LogLevel::Info;
2971 case MethodTrace:
2972 return LogLevel::Info;
2973 case MethodDir:
2974 return LogLevel::Info;
2975 case MethodDirxml:
2976 return LogLevel::Info;
2977 case MethodGroup:
2978 return LogLevel::Info;
2979 case MethodGroupCollapsed:
2980 return LogLevel::Info;
2981 case MethodGroupEnd:
2982 return LogLevel::Info;
2983 case MethodTime:
2984 return LogLevel::Info;
2985 case MethodTimeLog:
2986 return LogLevel::Info;
2987 case MethodTimeEnd:
2988 return LogLevel::Info;
2989 case MethodTimeStamp:
2990 return LogLevel::Info;
2991 case MethodAssert:
2992 return LogLevel::Error;
2993 case MethodCount:
2994 return LogLevel::Info;
2995 case MethodCountReset:
2996 return LogLevel::Info;
2997 case MethodClear:
2998 return LogLevel::Info;
2999 case MethodProfile:
3000 return LogLevel::Info;
3001 case MethodProfileEnd:
3002 return LogLevel::Info;
3003 default:
3004 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
3005 return LogLevel::Disabled;
3009 bool Console::ArgumentData::Initialize(JSContext* aCx,
3010 const Sequence<JS::Value>& aArguments) {
3011 mGlobal = JS::CurrentGlobalOrNull(aCx);
3013 if (NS_WARN_IF(!mArguments.AppendElements(aArguments, fallible))) {
3014 return false;
3017 return true;
3020 void Console::ArgumentData::Trace(const TraceCallbacks& aCallbacks,
3021 void* aClosure) {
3022 ArgumentData* tmp = this;
3023 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
3024 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments[i])
3027 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
3030 bool Console::ArgumentData::PopulateArgumentsSequence(
3031 Sequence<JS::Value>& aSequence) const {
3032 AssertIsOnOwningThread();
3034 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
3035 if (NS_WARN_IF(!aSequence.AppendElement(mArguments[i], fallible))) {
3036 return false;
3040 return true;
3043 } // namespace mozilla::dom