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