Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / console / Console.cpp
blob252ddcd08f42288254460521ca3ce33e16d21c80
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Console.h"
8 #include "mozilla/dom/ConsoleInstance.h"
9 #include "mozilla/dom/ConsoleBinding.h"
10 #include "ConsoleCommon.h"
12 #include "js/Array.h" // JS::GetArrayLength, JS::NewArrayObject
13 #include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineProperty, JS_GetElement
14 #include "mozilla/dom/BlobBinding.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/ElementBinding.h"
18 #include "mozilla/dom/Exceptions.h"
19 #include "mozilla/dom/File.h"
20 #include "mozilla/dom/FunctionBinding.h"
21 #include "mozilla/dom/Performance.h"
22 #include "mozilla/dom/PromiseBinding.h"
23 #include "mozilla/dom/ScriptSettings.h"
24 #include "mozilla/dom/StructuredCloneHolder.h"
25 #include "mozilla/dom/ToJSValue.h"
26 #include "mozilla/dom/WorkerRunnable.h"
27 #include "mozilla/dom/WorkerScope.h"
28 #include "mozilla/dom/WorkletGlobalScope.h"
29 #include "mozilla/dom/WorkletImpl.h"
30 #include "mozilla/dom/WorkletThread.h"
31 #include "mozilla/dom/RootedDictionary.h"
32 #include "mozilla/BasePrincipal.h"
33 #include "mozilla/HoldDropJSObjects.h"
34 #include "mozilla/JSObjectHolder.h"
35 #include "mozilla/Maybe.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/StaticPrefs_devtools.h"
38 #include "mozilla/StaticPrefs_dom.h"
39 #include "nsCycleCollectionParticipant.h"
40 #include "nsDOMNavigationTiming.h"
41 #include "nsGlobalWindowInner.h"
42 #include "nsJSUtils.h"
43 #include "nsNetUtil.h"
44 #include "xpcpublic.h"
45 #include "nsContentUtils.h"
46 #include "nsDocShell.h"
47 #include "nsProxyRelease.h"
48 #include "nsReadableUtils.h"
50 #include "nsIConsoleAPIStorage.h"
51 #include "nsIException.h" // for nsIStackFrame
52 #include "nsIInterfaceRequestorUtils.h"
53 #include "nsILoadContext.h"
54 #include "nsISensitiveInfoHiddenURI.h"
55 #include "nsISupportsPrimitives.h"
56 #include "nsIWebNavigation.h"
57 #include "nsIXPConnect.h"
59 // The maximum allowed number of concurrent timers per page.
60 #define MAX_PAGE_TIMERS 10000
62 // The maximum allowed number of concurrent counters per page.
63 #define MAX_PAGE_COUNTERS 10000
65 // The maximum stacktrace depth when populating the stacktrace array used for
66 // console.trace().
67 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
69 // This tags are used in the Structured Clone Algorithm to move js values from
70 // worker thread to main thread
71 #define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
73 // This value is taken from ConsoleAPIStorage.js
74 #define STORAGE_MAX_EVENTS 1000
76 using namespace mozilla::dom::exceptions;
78 namespace mozilla::dom {
80 struct ConsoleStructuredCloneData {
81 nsCOMPtr<nsIGlobalObject> mGlobal;
82 nsTArray<RefPtr<BlobImpl>> mBlobs;
85 static void ComposeAndStoreGroupName(JSContext* aCx,
86 const Sequence<JS::Value>& aData,
87 nsAString& aName,
88 nsTArray<nsString>* aGroupStack);
89 static bool UnstoreGroupName(nsAString& aName, nsTArray<nsString>* aGroupStack);
91 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
92 Sequence<JS::Value>& aSequence,
93 Sequence<nsString>& aStyles);
95 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
96 const nsAString& aCountLabel,
97 uint32_t aCountValue);
99 /**
100 * Console API in workers uses the Structured Clone Algorithm to move any value
101 * from the worker thread to the main-thread. Some object cannot be moved and,
102 * in these cases, we convert them to strings.
103 * It's not the best, but at least we are able to show something.
106 class ConsoleCallData final {
107 public:
108 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData)
110 ConsoleCallData(Console::MethodName aName, const nsAString& aString,
111 Console* aConsole)
112 : mConsoleID(aConsole->mConsoleID),
113 mPrefix(aConsole->mPrefix),
114 mMethodName(aName),
115 mMicroSecondTimeStamp(JS_Now()),
116 mStartTimerValue(0),
117 mStartTimerStatus(Console::eTimerUnknown),
118 mLogTimerDuration(0),
119 mLogTimerStatus(Console::eTimerUnknown),
120 mCountValue(MAX_PAGE_COUNTERS),
121 mIDType(eUnknown),
122 mOuterIDNumber(0),
123 mInnerIDNumber(0),
124 mMethodString(aString) {}
126 void SetIDs(uint64_t aOuterID, uint64_t aInnerID) {
127 MOZ_ASSERT(mIDType == eUnknown);
129 mOuterIDNumber = aOuterID;
130 mInnerIDNumber = aInnerID;
131 mIDType = eNumber;
134 void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) {
135 MOZ_ASSERT(mIDType == eUnknown);
137 mOuterIDString = aOuterID;
138 mInnerIDString = aInnerID;
139 mIDType = eString;
142 void SetOriginAttributes(const OriginAttributes& aOriginAttributes) {
143 mOriginAttributes = aOriginAttributes;
146 void SetAddonId(nsIPrincipal* aPrincipal) {
147 nsAutoString addonId;
148 aPrincipal->GetAddonId(addonId);
150 mAddonId = addonId;
153 void AssertIsOnOwningThread() const {
154 NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
157 const nsString mConsoleID;
158 const nsString mPrefix;
160 const Console::MethodName mMethodName;
161 int64_t mMicroSecondTimeStamp;
163 // These values are set in the owning thread and they contain the timestamp of
164 // when the new timer has started, the name of it and the status of the
165 // creation of it. If status is false, something went wrong. User
166 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
167 // monotonicTimer from Performance.now();
168 // They will be set on the owning thread and never touched again on that
169 // thread. They will be used in order to create a ConsoleTimerStart dictionary
170 // when console.time() is used.
171 DOMHighResTimeStamp mStartTimerValue;
172 nsString mStartTimerLabel;
173 Console::TimerStatus mStartTimerStatus;
175 // These values are set in the owning thread and they contain the duration,
176 // the name and the status of the LogTimer method. If status is false,
177 // something went wrong. They will be set on the owning thread and never
178 // touched again on that thread. They will be used in order to create a
179 // ConsoleTimerLogOrEnd dictionary. This members are set when
180 // console.timeEnd() or console.timeLog() are called.
181 double mLogTimerDuration;
182 nsString mLogTimerLabel;
183 Console::TimerStatus mLogTimerStatus;
185 // These 2 values are set by IncreaseCounter or ResetCounter on the owning
186 // thread and they are used by CreateCounterOrResetCounterValue.
187 // These members are set when console.count() or console.countReset() are
188 // called.
189 nsString mCountLabel;
190 uint32_t mCountValue;
192 // The concept of outerID and innerID is misleading because when a
193 // ConsoleCallData is created from a window, these are the window IDs, but
194 // when the object is created from a SharedWorker, a ServiceWorker or a
195 // subworker of a ChromeWorker these IDs are the type of worker and the
196 // filename of the callee.
197 // In Console.sys.mjs the ID is 'jsm'.
198 enum { eString, eNumber, eUnknown } mIDType;
200 uint64_t mOuterIDNumber;
201 nsString mOuterIDString;
203 uint64_t mInnerIDNumber;
204 nsString mInnerIDString;
206 OriginAttributes mOriginAttributes;
208 nsString mAddonId;
210 const nsString mMethodString;
212 // Stack management is complicated, because we want to do it as
213 // lazily as possible. Therefore, we have the following behavior:
214 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
215 // 2) mReifiedStack is initialized if we're created in a worker.
216 // 3) mStack is set (possibly to null if there is no JS on the stack) if
217 // we're created on main thread.
218 Maybe<ConsoleStackEntry> mTopStackFrame;
219 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
220 nsCOMPtr<nsIStackFrame> mStack;
222 private:
223 ~ConsoleCallData() = default;
225 NS_DECL_OWNINGTHREAD;
228 // MainThreadConsoleData instances are created on the Console thread and
229 // referenced from both main and Console threads in order to provide the same
230 // object for any ConsoleRunnables relating to the same Console. A Console
231 // owns a MainThreadConsoleData; MainThreadConsoleData does not keep its
232 // Console alive.
233 class MainThreadConsoleData final {
234 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData);
236 JSObject* GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
237 // This method must receive aCx and aArguments in the same JS::Compartment.
238 void ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
239 const Sequence<JS::Value>& aArguments);
241 private:
242 ~MainThreadConsoleData() {
243 NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
244 mStorage.forget());
245 NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
246 mSandbox.forget());
249 // All members, except for mRefCnt, are accessed only on the main thread,
250 // except in MainThreadConsoleData destruction, at which point there are no
251 // other references.
252 nsCOMPtr<nsIConsoleAPIStorage> mStorage;
253 RefPtr<JSObjectHolder> mSandbox;
254 nsTArray<nsString> mGroupStack;
257 // This base class must be extended for Worker and for Worklet.
258 class ConsoleRunnable : public StructuredCloneHolderBase {
259 public:
260 ~ConsoleRunnable() override {
261 MOZ_ASSERT(!mClonedData.mGlobal,
262 "mClonedData.mGlobal is set and cleared in a main thread scope");
263 // Clear the StructuredCloneHolderBase class.
264 Clear();
267 protected:
268 JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
269 const JS::CloneDataPolicy& aCloneDataPolicy,
270 uint32_t aTag, uint32_t aIndex) override {
271 AssertIsOnMainThread();
273 if (aTag == CONSOLE_TAG_BLOB) {
274 MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
276 JS::Rooted<JS::Value> val(aCx);
278 nsCOMPtr<nsIGlobalObject> global = mClonedData.mGlobal;
279 RefPtr<Blob> blob =
280 Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex));
281 if (!ToJSValue(aCx, blob, &val)) {
282 return nullptr;
286 return &val.toObject();
289 MOZ_CRASH("No other tags are supported.");
290 return nullptr;
293 bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
294 JS::Handle<JSObject*> aObj,
295 bool* aSameProcessScopeRequired) override {
296 RefPtr<Blob> blob;
297 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
298 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
299 mClonedData.mBlobs.Length()))) {
300 return false;
303 mClonedData.mBlobs.AppendElement(blob->Impl());
304 return true;
307 if (!JS_ObjectNotWritten(aWriter, aObj)) {
308 return false;
311 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
312 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
313 if (NS_WARN_IF(!jsString)) {
314 return false;
317 if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
318 return false;
321 return true;
324 // Helper method for CallData
325 void ProcessCallData(JSContext* aCx, MainThreadConsoleData* aConsoleData,
326 ConsoleCallData* aCallData) {
327 AssertIsOnMainThread();
329 ConsoleCommon::ClearException ce(aCx);
331 // This is the same policy as when writing from the other side, in
332 // WriteData.
333 JS::CloneDataPolicy cloneDataPolicy;
334 cloneDataPolicy.allowIntraClusterClonableSharedObjects();
335 cloneDataPolicy.allowSharedMemoryObjects();
337 JS::Rooted<JS::Value> argumentsValue(aCx);
338 if (!Read(aCx, &argumentsValue, cloneDataPolicy)) {
339 return;
342 MOZ_ASSERT(argumentsValue.isObject());
344 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
346 uint32_t length;
347 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
348 return;
351 Sequence<JS::Value> values;
352 SequenceRooter<JS::Value> arguments(aCx, &values);
354 for (uint32_t i = 0; i < length; ++i) {
355 JS::Rooted<JS::Value> value(aCx);
357 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
358 return;
361 if (!values.AppendElement(value, fallible)) {
362 return;
366 MOZ_ASSERT(values.Length() == length);
368 aConsoleData->ProcessCallData(aCx, aCallData, values);
371 // Generic
372 bool WriteArguments(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
373 ConsoleCommon::ClearException ce(aCx);
375 JS::Rooted<JSObject*> arguments(
376 aCx, JS::NewArrayObject(aCx, aArguments.Length()));
377 if (NS_WARN_IF(!arguments)) {
378 return false;
381 JS::Rooted<JS::Value> arg(aCx);
382 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
383 arg = aArguments[i];
384 if (NS_WARN_IF(
385 !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
386 return false;
390 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
391 return WriteData(aCx, value);
394 // Helper method for Profile calls
395 void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName,
396 const nsAString& aAction) {
397 AssertIsOnMainThread();
399 ConsoleCommon::ClearException ce(aCx);
401 JS::Rooted<JS::Value> argumentsValue(aCx);
402 bool ok = Read(aCx, &argumentsValue);
403 mClonedData.mGlobal = nullptr;
405 if (!ok) {
406 return;
409 MOZ_ASSERT(argumentsValue.isObject());
410 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
411 if (NS_WARN_IF(!argumentsObj)) {
412 return;
415 uint32_t length;
416 if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
417 return;
420 Sequence<JS::Value> arguments;
422 for (uint32_t i = 0; i < length; ++i) {
423 JS::Rooted<JS::Value> value(aCx);
425 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
426 return;
429 if (!arguments.AppendElement(value, fallible)) {
430 return;
434 Console::ProfileMethodMainthread(aCx, aAction, arguments);
437 bool WriteData(JSContext* aCx, JS::Handle<JS::Value> aValue) {
438 // We use structuredClone to send the JSValue to the main-thread, in order
439 // to store it into the Console API Service. The consumer will be the
440 // console panel in the devtools and, because of this, we want to allow the
441 // cloning of sharedArrayBuffers and WASM modules.
442 JS::CloneDataPolicy cloneDataPolicy;
443 cloneDataPolicy.allowIntraClusterClonableSharedObjects();
444 cloneDataPolicy.allowSharedMemoryObjects();
446 if (NS_WARN_IF(
447 !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) {
448 // Ignore the message.
449 return false;
452 return true;
455 ConsoleStructuredCloneData mClonedData;
458 class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
459 protected:
460 explicit ConsoleWorkletRunnable(Console* aConsole)
461 : Runnable("dom::console::ConsoleWorkletRunnable"),
462 mConsoleData(aConsole->GetOrCreateMainThreadData()) {
463 WorkletThread::AssertIsOnWorkletThread();
464 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(aConsole->mGlobal);
465 MOZ_ASSERT(global);
466 mWorkletImpl = global->Impl();
467 MOZ_ASSERT(mWorkletImpl);
470 ~ConsoleWorkletRunnable() override = default;
472 protected:
473 RefPtr<MainThreadConsoleData> mConsoleData;
475 RefPtr<WorkletImpl> mWorkletImpl;
478 // This runnable appends a CallData object into the Console queue running on
479 // the main-thread.
480 class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
481 public:
482 static already_AddRefed<ConsoleCallDataWorkletRunnable> Create(
483 JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData,
484 const Sequence<JS::Value>& aArguments) {
485 WorkletThread::AssertIsOnWorkletThread();
487 RefPtr<ConsoleCallDataWorkletRunnable> runnable =
488 new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData);
490 if (!runnable->WriteArguments(aCx, aArguments)) {
491 return nullptr;
494 return runnable.forget();
497 private:
498 ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData)
499 : ConsoleWorkletRunnable(aConsole), mCallData(aCallData) {
500 WorkletThread::AssertIsOnWorkletThread();
501 MOZ_ASSERT(aCallData);
502 aCallData->AssertIsOnOwningThread();
504 const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo();
505 mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID());
508 ~ConsoleCallDataWorkletRunnable() override = default;
510 NS_IMETHOD Run() override {
511 AssertIsOnMainThread();
512 AutoJSAPI jsapi;
513 jsapi.Init();
514 JSContext* cx = jsapi.cx();
516 JSObject* sandbox =
517 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
518 JS::Rooted<JSObject*> global(cx, sandbox);
519 if (NS_WARN_IF(!global)) {
520 return NS_ERROR_FAILURE;
523 // The CreateSandbox call returns a proxy to the actual sandbox object. We
524 // don't need a proxy here.
525 global = js::UncheckedUnwrap(global);
527 JSAutoRealm ar(cx, global);
529 // We don't need to set a parent object in mCallData bacause there are not
530 // DOM objects exposed to worklet.
532 ProcessCallData(cx, mConsoleData, mCallData);
534 return NS_OK;
537 RefPtr<ConsoleCallData> mCallData;
540 class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
541 public ConsoleRunnable {
542 public:
543 explicit ConsoleWorkerRunnable(Console* aConsole)
544 : mConsoleData(aConsole->GetOrCreateMainThreadData()) {}
546 ~ConsoleWorkerRunnable() override = default;
548 bool Dispatch(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
549 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
550 MOZ_ASSERT(workerPrivate);
552 if (NS_WARN_IF(!WriteArguments(aCx, aArguments))) {
553 RunBackOnWorkerThreadForCleanup(workerPrivate);
554 return false;
557 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
558 // RunBackOnWorkerThreadForCleanup() will be called by
559 // WorkerProxyToMainThreadRunnable::Dispatch().
560 return false;
563 return true;
566 protected:
567 void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
568 MOZ_ASSERT(aWorkerPrivate);
569 AssertIsOnMainThread();
571 // Walk up to our containing page
572 WorkerPrivate* wp = aWorkerPrivate;
573 while (wp->GetParent()) {
574 wp = wp->GetParent();
577 nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow();
578 if (!window) {
579 RunWindowless(aWorkerPrivate);
580 } else {
581 RunWithWindow(aWorkerPrivate, window);
585 void RunWithWindow(WorkerPrivate* aWorkerPrivate,
586 nsPIDOMWindowInner* aWindow) {
587 MOZ_ASSERT(aWorkerPrivate);
588 AssertIsOnMainThread();
590 AutoJSAPI jsapi;
591 MOZ_ASSERT(aWindow);
593 RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
594 if (NS_WARN_IF(!jsapi.Init(win))) {
595 return;
598 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
599 if (NS_WARN_IF(!outerWindow)) {
600 return;
603 RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
604 aWindow);
607 void RunWindowless(WorkerPrivate* aWorkerPrivate) {
608 MOZ_ASSERT(aWorkerPrivate);
609 AssertIsOnMainThread();
611 WorkerPrivate* wp = aWorkerPrivate;
612 while (wp->GetParent()) {
613 wp = wp->GetParent();
616 MOZ_ASSERT(!wp->GetWindow());
618 AutoJSAPI jsapi;
619 jsapi.Init();
621 JSContext* cx = jsapi.cx();
623 JS::Rooted<JSObject*> global(
624 cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal()));
625 if (NS_WARN_IF(!global)) {
626 return;
629 // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
630 // We don't need a proxy here.
631 global = js::UncheckedUnwrap(global);
633 JSAutoRealm ar(cx, global);
635 nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global);
636 if (NS_WARN_IF(!globalObject)) {
637 return;
640 RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr);
643 void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
644 MOZ_ASSERT(aWorkerPrivate);
645 aWorkerPrivate->AssertIsOnWorkerThread();
648 // This method is called in the main-thread.
649 virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
650 WorkerPrivate* aWorkerPrivate,
651 nsPIDOMWindowOuter* aOuterWindow,
652 nsPIDOMWindowInner* aInnerWindow) = 0;
654 bool ForMessaging() const override { return true; }
656 RefPtr<MainThreadConsoleData> mConsoleData;
659 // This runnable appends a CallData object into the Console queue running on
660 // the main-thread.
661 class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
662 public:
663 ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
664 : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
665 MOZ_ASSERT(aCallData);
666 mCallData->AssertIsOnOwningThread();
669 private:
670 ~ConsoleCallDataWorkerRunnable() override = default;
672 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
673 WorkerPrivate* aWorkerPrivate,
674 nsPIDOMWindowOuter* aOuterWindow,
675 nsPIDOMWindowInner* aInnerWindow) override {
676 MOZ_ASSERT(aGlobal);
677 MOZ_ASSERT(aWorkerPrivate);
678 AssertIsOnMainThread();
680 // The windows have to run in parallel.
681 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
683 if (aOuterWindow) {
684 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
685 } else {
686 ConsoleStackEntry frame;
687 if (mCallData->mTopStackFrame) {
688 frame = *mCallData->mTopStackFrame;
691 nsString id = frame.mFilename;
692 nsString innerID;
693 if (aWorkerPrivate->IsSharedWorker()) {
694 innerID = u"SharedWorker"_ns;
695 } else if (aWorkerPrivate->IsServiceWorker()) {
696 innerID = u"ServiceWorker"_ns;
697 // Use scope as ID so the webconsole can decide if the message should
698 // show up per tab
699 CopyASCIItoUTF16(aWorkerPrivate->ServiceWorkerScope(), id);
700 } else {
701 innerID = u"Worker"_ns;
704 mCallData->SetIDs(id, innerID);
707 mClonedData.mGlobal = aGlobal;
709 ProcessCallData(aCx, mConsoleData, mCallData);
711 mClonedData.mGlobal = nullptr;
714 RefPtr<ConsoleCallData> mCallData;
717 // This runnable calls ProfileMethod() on the console on the main-thread.
718 class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable {
719 public:
720 static already_AddRefed<ConsoleProfileWorkletRunnable> Create(
721 JSContext* aCx, Console* aConsole, Console::MethodName aName,
722 const nsAString& aAction, const Sequence<JS::Value>& aArguments) {
723 WorkletThread::AssertIsOnWorkletThread();
725 RefPtr<ConsoleProfileWorkletRunnable> runnable =
726 new ConsoleProfileWorkletRunnable(aConsole, aName, aAction);
728 if (!runnable->WriteArguments(aCx, aArguments)) {
729 return nullptr;
732 return runnable.forget();
735 private:
736 ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName,
737 const nsAString& aAction)
738 : ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) {
739 MOZ_ASSERT(aConsole);
742 NS_IMETHOD Run() override {
743 AssertIsOnMainThread();
745 AutoJSAPI jsapi;
746 jsapi.Init();
747 JSContext* cx = jsapi.cx();
749 JSObject* sandbox =
750 mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
751 JS::Rooted<JSObject*> global(cx, sandbox);
752 if (NS_WARN_IF(!global)) {
753 return NS_ERROR_FAILURE;
756 // The CreateSandbox call returns a proxy to the actual sandbox object. We
757 // don't need a proxy here.
758 global = js::UncheckedUnwrap(global);
760 JSAutoRealm ar(cx, global);
762 // We don't need to set a parent object in mCallData bacause there are not
763 // DOM objects exposed to worklet.
764 ProcessProfileData(cx, mName, mAction);
766 return NS_OK;
769 Console::MethodName mName;
770 nsString mAction;
773 // This runnable calls ProfileMethod() on the console on the main-thread.
774 class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
775 public:
776 ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
777 const nsAString& aAction)
778 : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) {
779 MOZ_ASSERT(aConsole);
782 private:
783 void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
784 WorkerPrivate* aWorkerPrivate,
785 nsPIDOMWindowOuter* aOuterWindow,
786 nsPIDOMWindowInner* aInnerWindow) override {
787 AssertIsOnMainThread();
788 MOZ_ASSERT(aGlobal);
790 mClonedData.mGlobal = aGlobal;
792 ProcessProfileData(aCx, mName, mAction);
794 mClonedData.mGlobal = nullptr;
797 Console::MethodName mName;
798 nsString mAction;
801 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
803 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
804 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
805 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
806 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
807 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
808 tmp->Shutdown();
809 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
811 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
812 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
813 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
814 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
815 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
817 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
818 for (uint32_t i = 0; i < tmp->mArgumentStorage.length(); ++i) {
819 tmp->mArgumentStorage[i].Trace(aCallbacks, aClosure);
821 NS_IMPL_CYCLE_COLLECTION_TRACE_END
823 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
824 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
826 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
827 NS_INTERFACE_MAP_ENTRY(nsIObserver)
828 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
829 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
830 NS_INTERFACE_MAP_END
832 /* static */
833 already_AddRefed<Console> Console::Create(JSContext* aCx,
834 nsPIDOMWindowInner* aWindow,
835 ErrorResult& aRv) {
836 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
838 uint64_t outerWindowID = 0;
839 uint64_t innerWindowID = 0;
841 if (aWindow) {
842 innerWindowID = aWindow->WindowID();
844 // Without outerwindow any console message coming from this object will not
845 // shown in the devtools webconsole. But this should be fine because
846 // probably we are shutting down, or the window is CCed/GCed.
847 nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
848 if (outerWindow) {
849 outerWindowID = outerWindow->WindowID();
853 RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow),
854 outerWindowID, innerWindowID);
855 console->Initialize(aRv);
856 if (NS_WARN_IF(aRv.Failed())) {
857 return nullptr;
860 return console.forget();
863 /* static */
864 already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
865 nsIGlobalObject* aGlobal,
866 uint64_t aOuterWindowID,
867 uint64_t aInnerWindowID,
868 ErrorResult& aRv) {
869 WorkletThread::AssertIsOnWorkletThread();
871 RefPtr<Console> console =
872 new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID);
873 console->Initialize(aRv);
874 if (NS_WARN_IF(aRv.Failed())) {
875 return nullptr;
878 return console.forget();
881 Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
882 uint64_t aOuterWindowID, uint64_t aInnerWindowID)
883 : mGlobal(aGlobal),
884 mOuterID(aOuterWindowID),
885 mInnerID(aInnerWindowID),
886 mDumpToStdout(false),
887 mChromeInstance(false),
888 mCurrentLogLevel(WebIDLLogLevelToInteger(ConsoleLogLevel::All)),
889 mStatus(eUnknown),
890 mCreationTimeStamp(TimeStamp::Now()) {
891 // Let's enable the dumping to stdout by default for chrome.
892 if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
893 mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
894 } else {
895 mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
898 mozilla::HoldJSObjects(this);
901 Console::~Console() {
902 AssertIsOnOwningThread();
903 Shutdown();
904 mozilla::DropJSObjects(this);
907 void Console::Initialize(ErrorResult& aRv) {
908 AssertIsOnOwningThread();
909 MOZ_ASSERT(mStatus == eUnknown);
911 if (NS_IsMainThread()) {
912 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
913 if (NS_WARN_IF(!obs)) {
914 aRv.Throw(NS_ERROR_FAILURE);
915 return;
918 if (mInnerID) {
919 aRv = obs->AddObserver(this, "inner-window-destroyed", true);
920 if (NS_WARN_IF(aRv.Failed())) {
921 return;
925 aRv = obs->AddObserver(this, "memory-pressure", true);
926 if (NS_WARN_IF(aRv.Failed())) {
927 return;
931 mStatus = eInitialized;
934 void Console::Shutdown() {
935 AssertIsOnOwningThread();
937 if (mStatus == eUnknown || mStatus == eShuttingDown) {
938 return;
941 if (NS_IsMainThread()) {
942 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
943 if (obs) {
944 obs->RemoveObserver(this, "inner-window-destroyed");
945 obs->RemoveObserver(this, "memory-pressure");
949 mTimerRegistry.Clear();
950 mCounterRegistry.Clear();
952 ClearStorage();
953 mCallDataStorage.Clear();
955 mStatus = eShuttingDown;
958 NS_IMETHODIMP
959 Console::Observe(nsISupports* aSubject, const char* aTopic,
960 const char16_t* aData) {
961 AssertIsOnMainThread();
963 if (!strcmp(aTopic, "inner-window-destroyed")) {
964 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
965 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
967 uint64_t innerID;
968 nsresult rv = wrapper->GetData(&innerID);
969 NS_ENSURE_SUCCESS(rv, rv);
971 if (innerID == mInnerID) {
972 Shutdown();
975 return NS_OK;
978 if (!strcmp(aTopic, "memory-pressure")) {
979 ClearStorage();
980 return NS_OK;
983 return NS_OK;
986 void Console::ClearStorage() {
987 mCallDataStorage.Clear();
988 mArgumentStorage.clearAndFree();
991 #define METHOD(name, string) \
992 /* static */ void Console::name(const GlobalObject& aGlobal, \
993 const Sequence<JS::Value>& aData) { \
994 Method(aGlobal, Method##name, nsLiteralString(string), aData); \
997 METHOD(Log, u"log")
998 METHOD(Info, u"info")
999 METHOD(Warn, u"warn")
1000 METHOD(Error, u"error")
1001 METHOD(Exception, u"exception")
1002 METHOD(Debug, u"debug")
1003 METHOD(Table, u"table")
1004 METHOD(Trace, u"trace")
1006 // Displays an interactive listing of all the properties of an object.
1007 METHOD(Dir, u"dir");
1008 METHOD(Dirxml, u"dirxml");
1010 METHOD(Group, u"group")
1011 METHOD(GroupCollapsed, u"groupCollapsed")
1013 #undef METHOD
1015 /* static */
1016 void Console::Clear(const GlobalObject& aGlobal) {
1017 const Sequence<JS::Value> data;
1018 Method(aGlobal, MethodClear, u"clear"_ns, data);
1021 /* static */
1022 void Console::GroupEnd(const GlobalObject& aGlobal) {
1023 const Sequence<JS::Value> data;
1024 Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data);
1027 /* static */
1028 void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
1029 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns);
1032 /* static */
1033 void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
1034 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
1035 u"timeEnd"_ns);
1038 /* static */
1039 void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
1040 const Sequence<JS::Value>& aData) {
1041 StringMethod(aGlobal, aLabel, aData, MethodTimeLog, u"timeLog"_ns);
1044 /* static */
1045 void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
1046 const Sequence<JS::Value>& aData,
1047 MethodName aMethodName,
1048 const nsAString& aMethodString) {
1049 RefPtr<Console> console = GetConsole(aGlobal);
1050 if (!console) {
1051 return;
1054 console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
1055 aMethodString);
1058 void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
1059 const Sequence<JS::Value>& aData,
1060 MethodName aMethodName,
1061 const nsAString& aMethodString) {
1062 ConsoleCommon::ClearException ce(aCx);
1064 Sequence<JS::Value> data;
1065 SequenceRooter<JS::Value> rooter(aCx, &data);
1067 JS::Rooted<JS::Value> value(aCx);
1068 if (!dom::ToJSValue(aCx, aLabel, &value)) {
1069 return;
1072 if (!data.AppendElement(value, fallible)) {
1073 return;
1076 for (uint32_t i = 0; i < aData.Length(); ++i) {
1077 if (!data.AppendElement(aData[i], fallible)) {
1078 return;
1082 MethodInternal(aCx, aMethodName, aMethodString, data);
1085 /* static */
1086 void Console::TimeStamp(const GlobalObject& aGlobal,
1087 const JS::Handle<JS::Value> aData) {
1088 JSContext* cx = aGlobal.Context();
1090 ConsoleCommon::ClearException ce(cx);
1092 Sequence<JS::Value> data;
1093 SequenceRooter<JS::Value> rooter(cx, &data);
1095 if (aData.isString() && !data.AppendElement(aData, fallible)) {
1096 return;
1099 Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data);
1102 /* static */
1103 void Console::Profile(const GlobalObject& aGlobal,
1104 const Sequence<JS::Value>& aData) {
1105 ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData);
1108 /* static */
1109 void Console::ProfileEnd(const GlobalObject& aGlobal,
1110 const Sequence<JS::Value>& aData) {
1111 ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData);
1114 /* static */
1115 void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
1116 const nsAString& aAction,
1117 const Sequence<JS::Value>& aData) {
1118 RefPtr<Console> console = GetConsole(aGlobal);
1119 if (!console) {
1120 return;
1123 JSContext* cx = aGlobal.Context();
1124 console->ProfileMethodInternal(cx, aName, aAction, aData);
1127 void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
1128 const nsAString& aAction,
1129 const Sequence<JS::Value>& aData) {
1130 if (!ShouldProceed(aMethodName)) {
1131 return;
1134 MaybeExecuteDumpFunction(aCx, aAction, aData, nullptr);
1136 if (WorkletThread::IsOnWorkletThread()) {
1137 RefPtr<ConsoleProfileWorkletRunnable> runnable =
1138 ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
1139 aData);
1140 if (!runnable) {
1141 return;
1144 NS_DispatchToMainThread(runnable.forget());
1145 return;
1148 if (!NS_IsMainThread()) {
1149 // Here we are in a worker thread.
1150 RefPtr<ConsoleProfileWorkerRunnable> runnable =
1151 new ConsoleProfileWorkerRunnable(this, aMethodName, aAction);
1153 runnable->Dispatch(aCx, aData);
1154 return;
1157 ProfileMethodMainthread(aCx, aAction, aData);
1160 // static
1161 void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction,
1162 const Sequence<JS::Value>& aData) {
1163 MOZ_ASSERT(NS_IsMainThread());
1164 ConsoleCommon::ClearException ce(aCx);
1166 RootedDictionary<ConsoleProfileEvent> event(aCx);
1167 event.mAction = aAction;
1168 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1170 event.mArguments.Construct();
1171 Sequence<JS::Value>& sequence = event.mArguments.Value();
1173 for (uint32_t i = 0; i < aData.Length(); ++i) {
1174 if (!sequence.AppendElement(aData[i], fallible)) {
1175 return;
1179 JS::Rooted<JS::Value> eventValue(aCx);
1180 if (!ToJSValue(aCx, event, &eventValue)) {
1181 return;
1184 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1185 MOZ_ASSERT(eventObj);
1187 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1188 JSPROP_ENUMERATE)) {
1189 return;
1192 nsIXPConnect* xpc = nsContentUtils::XPConnect();
1193 nsCOMPtr<nsISupports> wrapper;
1194 const nsIID& iid = NS_GET_IID(nsISupports);
1196 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
1197 return;
1200 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1201 if (obs) {
1202 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1206 /* static */
1207 void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1208 const Sequence<JS::Value>& aData) {
1209 if (!aCondition) {
1210 Method(aGlobal, MethodAssert, u"assert"_ns, aData);
1214 /* static */
1215 void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
1216 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
1217 u"count"_ns);
1220 /* static */
1221 void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
1222 StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
1223 u"countReset"_ns);
1226 namespace {
1228 void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
1229 ConsoleStackEntry& aStackEntry) {
1230 MOZ_ASSERT(aStackFrame);
1232 aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
1234 aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx);
1235 aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx);
1236 aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx);
1238 aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
1240 nsString cause;
1241 aStackFrame->GetAsyncCause(aCx, cause);
1242 if (!cause.IsEmpty()) {
1243 aStackEntry.mAsyncCause.Construct(cause);
1247 void ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
1248 nsTArray<ConsoleStackEntry>& aRefiedStack) {
1249 nsCOMPtr<nsIStackFrame> stack(aStack);
1251 while (stack) {
1252 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1253 StackFrameToStackEntry(aCx, stack, data);
1255 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
1257 if (!caller) {
1258 caller = stack->GetAsyncCaller(aCx);
1260 stack.swap(caller);
1264 } // anonymous namespace
1266 // Queue a call to a console method. See the CALL_DELAY constant.
1267 /* static */
1268 void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
1269 const nsAString& aMethodString,
1270 const Sequence<JS::Value>& aData) {
1271 RefPtr<Console> console = GetConsole(aGlobal);
1272 if (!console) {
1273 return;
1276 console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData);
1279 void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
1280 const nsAString& aMethodString,
1281 const Sequence<JS::Value>& aData) {
1282 if (!ShouldProceed(aMethodName)) {
1283 return;
1286 AssertIsOnOwningThread();
1288 ConsoleCommon::ClearException ce(aCx);
1290 RefPtr<ConsoleCallData> callData =
1291 new ConsoleCallData(aMethodName, aMethodString, this);
1292 if (!StoreCallData(aCx, callData, aData)) {
1293 return;
1296 OriginAttributes oa;
1298 if (NS_IsMainThread()) {
1299 if (mGlobal) {
1300 // Save the principal's OriginAttributes in the console event data
1301 // so that we will be able to filter messages by origin attributes.
1302 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
1303 if (NS_WARN_IF(!sop)) {
1304 return;
1307 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1308 if (NS_WARN_IF(!principal)) {
1309 return;
1312 oa = principal->OriginAttributesRef();
1313 callData->SetAddonId(principal);
1315 #ifdef DEBUG
1316 if (!principal->IsSystemPrincipal()) {
1317 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
1318 if (webNav) {
1319 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1320 MOZ_ASSERT(loadContext);
1322 bool pb;
1323 if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
1324 MOZ_ASSERT(pb == !!oa.mPrivateBrowsingId);
1328 #endif
1330 } else if (WorkletThread::IsOnWorkletThread()) {
1331 nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
1332 MOZ_ASSERT(global);
1333 oa = global->Impl()->OriginAttributesRef();
1334 } else {
1335 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1336 MOZ_ASSERT(workerPrivate);
1337 oa = workerPrivate->GetOriginAttributes();
1340 callData->SetOriginAttributes(oa);
1342 JS::StackCapture captureMode =
1343 ShouldIncludeStackTrace(aMethodName)
1344 ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH))
1345 : JS::StackCapture(JS::FirstSubsumedFrame(aCx));
1346 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode));
1348 if (stack) {
1349 callData->mTopStackFrame.emplace();
1350 StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
1353 if (NS_IsMainThread()) {
1354 callData->mStack = stack;
1355 } else {
1356 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1357 // before we post our runnable to the main thread.
1358 callData->mReifiedStack.emplace();
1359 ReifyStack(aCx, stack, *callData->mReifiedStack);
1362 DOMHighResTimeStamp monotonicTimer;
1364 // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1365 if ((aMethodName == MethodTime || aMethodName == MethodTimeLog ||
1366 aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) &&
1367 !MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
1368 return;
1371 if (aMethodName == MethodTime && !aData.IsEmpty()) {
1372 callData->mStartTimerStatus =
1373 StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel,
1374 &callData->mStartTimerValue);
1377 else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
1378 callData->mLogTimerStatus =
1379 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1380 &callData->mLogTimerDuration, true /* Cancel timer */);
1383 else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
1384 callData->mLogTimerStatus =
1385 LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1386 &callData->mLogTimerDuration, false /* Cancel timer */);
1389 else if (aMethodName == MethodCount) {
1390 callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
1391 if (!callData->mCountValue) {
1392 return;
1396 else if (aMethodName == MethodCountReset) {
1397 callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
1398 if (callData->mCountLabel.IsEmpty()) {
1399 return;
1403 // Before processing this CallData differently, it's time to call the dump
1404 // function.
1405 if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
1406 MaybeExecuteDumpFunction(aCx, aMethodString, aData, stack);
1407 } else if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) &&
1408 !aData.IsEmpty()) {
1409 MaybeExecuteDumpFunctionForTime(aCx, aMethodName, aMethodString,
1410 monotonicTimer, aData[0]);
1411 } else {
1412 MaybeExecuteDumpFunction(aCx, aMethodString, aData, nullptr);
1415 if (NS_IsMainThread()) {
1416 if (mInnerID) {
1417 callData->SetIDs(mOuterID, mInnerID);
1418 } else if (!mPassedInnerID.IsEmpty()) {
1419 callData->SetIDs(u"jsm"_ns, mPassedInnerID);
1420 } else {
1421 nsAutoString filename;
1422 if (callData->mTopStackFrame.isSome()) {
1423 filename = callData->mTopStackFrame->mFilename;
1426 callData->SetIDs(u"jsm"_ns, filename);
1429 GetOrCreateMainThreadData()->ProcessCallData(aCx, callData, aData);
1431 // Just because we don't want to expose
1432 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1433 // cleanup the mCallDataStorage:
1434 UnstoreCallData(callData);
1435 return;
1438 if (WorkletThread::IsOnWorkletThread()) {
1439 RefPtr<ConsoleCallDataWorkletRunnable> runnable =
1440 ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData);
1441 if (!runnable) {
1442 return;
1445 NS_DispatchToMainThread(runnable);
1446 return;
1449 // We do this only in workers for now.
1450 NotifyHandler(aCx, aData, callData);
1452 if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
1453 RefPtr<ConsoleCallDataWorkerRunnable> runnable =
1454 new ConsoleCallDataWorkerRunnable(this, callData);
1455 Unused << NS_WARN_IF(!runnable->Dispatch(aCx, aData));
1459 MainThreadConsoleData* Console::GetOrCreateMainThreadData() {
1460 AssertIsOnOwningThread();
1462 if (!mMainThreadData) {
1463 mMainThreadData = new MainThreadConsoleData();
1466 return mMainThreadData;
1469 // We store information to lazily compute the stack in the reserved slots of
1470 // LazyStackGetter. The first slot always stores a JS object: it's either the
1471 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1472 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1473 // reified the stack yet, or an UndefinedValue() otherwise.
1474 enum { SLOT_STACKOBJ, SLOT_RAW_STACK };
1476 bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
1477 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1478 JS::Rooted<JSObject*> callee(aCx, &args.callee());
1480 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1481 if (v.isUndefined()) {
1482 // Already reified.
1483 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1484 return true;
1487 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1488 nsTArray<ConsoleStackEntry> reifiedStack;
1489 ReifyStack(aCx, stack, reifiedStack);
1491 JS::Rooted<JS::Value> stackVal(aCx);
1492 if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
1493 return false;
1496 MOZ_ASSERT(stackVal.isObject());
1498 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1499 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1501 args.rval().set(stackVal);
1502 return true;
1505 void MainThreadConsoleData::ProcessCallData(
1506 JSContext* aCx, ConsoleCallData* aData,
1507 const Sequence<JS::Value>& aArguments) {
1508 AssertIsOnMainThread();
1509 MOZ_ASSERT(aData);
1511 JS::Rooted<JS::Value> eventValue(aCx);
1513 // We want to create a console event object and pass it to our
1514 // nsIConsoleAPIStorage implementation. We want to define some accessor
1515 // properties on this object, and those will need to keep an nsIStackFrame
1516 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1517 // further, passing untrusted objects to system code is likely to run afoul of
1518 // Object Xrays. So we want to wrap in a system-principal scope here. But
1519 // which one? We could cheat and try to get the underlying JSObject* of
1520 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1521 // with explicit permission from the XPConnect module owner. If you're
1522 // tempted to do that anywhere else, talk to said module owner first.
1524 // aCx and aArguments are in the same compartment.
1525 JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
1526 if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
1527 aCx, aArguments, targetScope, &eventValue, aData, &mGroupStack))) {
1528 return;
1531 if (!mStorage) {
1532 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1535 if (!mStorage) {
1536 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1537 return;
1540 nsAutoString innerID;
1542 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1543 if (aData->mIDType == ConsoleCallData::eString) {
1544 innerID = aData->mInnerIDString;
1545 } else {
1546 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1547 innerID.AppendInt(aData->mInnerIDNumber);
1550 if (aData->mMethodName == Console::MethodClear) {
1551 DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
1552 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
1555 if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
1556 NS_WARNING("Failed to record a console event.");
1560 /* static */
1561 bool Console::PopulateConsoleNotificationInTheTargetScope(
1562 JSContext* aCx, const Sequence<JS::Value>& aArguments,
1563 JS::Handle<JSObject*> aTargetScope,
1564 JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData,
1565 nsTArray<nsString>* aGroupStack) {
1566 MOZ_ASSERT(aCx);
1567 MOZ_ASSERT(aData);
1568 MOZ_ASSERT(aTargetScope);
1569 MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
1571 ConsoleStackEntry frame;
1572 if (aData->mTopStackFrame) {
1573 frame = *aData->mTopStackFrame;
1576 ConsoleCommon::ClearException ce(aCx);
1577 RootedDictionary<ConsoleEvent> event(aCx);
1579 event.mAddonId = aData->mAddonId;
1581 event.mID.Construct();
1582 event.mInnerID.Construct();
1584 event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1586 if (aData->mIDType == ConsoleCallData::eString) {
1587 event.mID.Value().SetAsString() = aData->mOuterIDString;
1588 event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1589 } else if (aData->mIDType == ConsoleCallData::eNumber) {
1590 event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
1591 event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
1592 } else {
1593 // aData->mIDType can be eUnknown when we dispatch notifications via
1594 // mConsoleEventNotifier.
1595 event.mID.Value().SetAsUnsignedLongLong() = 0;
1596 event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
1599 event.mConsoleID = aData->mConsoleID;
1600 event.mLevel = aData->mMethodString;
1601 event.mFilename = frame.mFilename;
1602 event.mPrefix = aData->mPrefix;
1604 nsCOMPtr<nsIURI> filenameURI;
1605 nsAutoCString pass;
1606 if (NS_IsMainThread() &&
1607 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
1608 NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
1609 nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI =
1610 do_QueryInterface(filenameURI);
1611 nsAutoCString spec;
1612 if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
1613 CopyUTF8toUTF16(spec, event.mFilename);
1617 event.mSourceId = frame.mSourceId;
1618 event.mLineNumber = frame.mLineNumber;
1619 event.mColumnNumber = frame.mColumnNumber;
1620 event.mFunctionName = frame.mFunctionName;
1621 event.mTimeStamp = aData->mMicroSecondTimeStamp / PR_USEC_PER_MSEC;
1622 event.mMicroSecondTimeStamp = aData->mMicroSecondTimeStamp;
1623 event.mPrivate = !!aData->mOriginAttributes.mPrivateBrowsingId;
1625 switch (aData->mMethodName) {
1626 case MethodLog:
1627 case MethodInfo:
1628 case MethodWarn:
1629 case MethodError:
1630 case MethodException:
1631 case MethodDebug:
1632 case MethodAssert:
1633 case MethodGroup:
1634 case MethodGroupCollapsed:
1635 case MethodTrace:
1636 event.mArguments.Construct();
1637 event.mStyles.Construct();
1638 if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1639 event.mArguments.Value(),
1640 event.mStyles.Value()))) {
1641 return false;
1644 break;
1646 default:
1647 event.mArguments.Construct();
1648 if (NS_WARN_IF(
1649 !event.mArguments.Value().AppendElements(aArguments, fallible))) {
1650 return false;
1654 if (aData->mMethodName == MethodGroup ||
1655 aData->mMethodName == MethodGroupCollapsed) {
1656 ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName,
1657 aGroupStack);
1660 else if (aData->mMethodName == MethodGroupEnd) {
1661 if (!UnstoreGroupName(event.mGroupName, aGroupStack)) {
1662 return false;
1666 else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
1667 event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
1668 aData->mStartTimerStatus);
1671 else if ((aData->mMethodName == MethodTimeEnd ||
1672 aData->mMethodName == MethodTimeLog) &&
1673 !aArguments.IsEmpty()) {
1674 event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
1675 aData->mLogTimerDuration,
1676 aData->mLogTimerStatus);
1679 else if (aData->mMethodName == MethodCount ||
1680 aData->mMethodName == MethodCountReset) {
1681 event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
1682 aData->mCountValue);
1685 JSAutoRealm ar2(aCx, aTargetScope);
1687 if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
1688 return false;
1691 JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
1692 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
1693 JSPROP_ENUMERATE))) {
1694 return false;
1697 if (ShouldIncludeStackTrace(aData->mMethodName)) {
1698 // Now define the "stacktrace" property on eventObj. There are two cases
1699 // here. Either we came from a worker and have a reified stack, or we want
1700 // to define a getter that will lazily reify the stack.
1701 if (aData->mReifiedStack) {
1702 JS::Rooted<JS::Value> stacktrace(aCx);
1703 if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
1704 NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
1705 JSPROP_ENUMERATE))) {
1706 return false;
1708 } else {
1709 JSFunction* fun =
1710 js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace");
1711 if (NS_WARN_IF(!fun)) {
1712 return false;
1715 JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
1717 // We want to store our stack in the function and have it stay alive. But
1718 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1719 // wrapper and the raw pointer: the former will keep the latter alive.
1720 JS::Rooted<JS::Value> stackVal(aCx);
1721 nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack, &stackVal);
1722 if (NS_WARN_IF(NS_FAILED(rv))) {
1723 return false;
1726 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1727 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1728 JS::PrivateValue(aData->mStack.get()));
1730 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", funObj,
1731 nullptr, JSPROP_ENUMERATE))) {
1732 return false;
1737 return true;
1740 namespace {
1742 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1743 // aSequence.
1744 bool FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence,
1745 nsString& aOutput) {
1746 if (!aOutput.IsEmpty()) {
1747 JS::Rooted<JSString*> str(
1748 aCx, JS_NewUCStringCopyN(aCx, aOutput.get(), aOutput.Length()));
1749 if (NS_WARN_IF(!str)) {
1750 return false;
1753 if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
1754 return false;
1757 aOutput.Truncate();
1760 return true;
1763 } // namespace
1765 static void MakeFormatString(nsCString& aFormat, int32_t aInteger,
1766 int32_t aMantissa, char aCh) {
1767 aFormat.Append('%');
1768 if (aInteger >= 0) {
1769 aFormat.AppendInt(aInteger);
1772 if (aMantissa >= 0) {
1773 aFormat.Append('.');
1774 aFormat.AppendInt(aMantissa);
1777 aFormat.Append(aCh);
1780 // If the first JS::Value of the array is a string, this method uses it to
1781 // format a string. The supported sequences are:
1782 // %s - string
1783 // %d,%i - integer
1784 // %f - double
1785 // %o,%O - a JS object.
1786 // %c - style string.
1787 // The output is an array where any object is a separated item, the rest is
1788 // unified in a format string.
1789 // Example if the input is:
1790 // "string: %s, integer: %d, object: %o, double: %f", 's', 1, window, 0.9
1791 // The output will be:
1792 // [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
1794 // The aStyles array is populated with the style strings that the function
1795 // finds based the format string. The index of the styles matches the indexes
1796 // of elements that need the custom styling from aSequence. For elements with
1797 // no custom styling the array is padded with null elements.
1798 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
1799 Sequence<JS::Value>& aSequence,
1800 Sequence<nsString>& aStyles) {
1801 // This method processes the arguments as format strings (%d, %i, %s...)
1802 // only if the first element of them is a valid and not-empty string.
1804 if (aData.IsEmpty()) {
1805 return true;
1808 if (aData.Length() == 1 || !aData[0].isString()) {
1809 return aSequence.AppendElements(aData, fallible);
1812 JS::Rooted<JS::Value> format(aCx, aData[0]);
1813 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1814 if (NS_WARN_IF(!jsString)) {
1815 return false;
1818 nsAutoJSString string;
1819 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1820 return false;
1823 if (string.IsEmpty()) {
1824 return aSequence.AppendElements(aData, fallible);
1827 nsString::const_iterator start, end;
1828 string.BeginReading(start);
1829 string.EndReading(end);
1831 nsString output;
1832 uint32_t index = 1;
1834 while (start != end) {
1835 if (*start != '%') {
1836 output.Append(*start);
1837 ++start;
1838 continue;
1841 ++start;
1842 if (start == end) {
1843 output.Append('%');
1844 break;
1847 if (*start == '%') {
1848 output.Append(*start);
1849 ++start;
1850 continue;
1853 nsAutoString tmp;
1854 tmp.Append('%');
1856 int32_t integer = -1;
1857 int32_t mantissa = -1;
1859 // Let's parse %<number>.<number> for %d and %f
1860 if (*start >= '0' && *start <= '9') {
1861 integer = 0;
1863 do {
1864 integer = integer * 10 + *start - '0';
1865 tmp.Append(*start);
1866 ++start;
1867 } while (*start >= '0' && *start <= '9' && start != end);
1870 if (start == end) {
1871 output.Append(tmp);
1872 break;
1875 if (*start == '.') {
1876 tmp.Append(*start);
1877 ++start;
1879 if (start == end) {
1880 output.Append(tmp);
1881 break;
1884 // '.' must be followed by a number.
1885 if (*start < '0' || *start > '9') {
1886 output.Append(tmp);
1887 continue;
1890 mantissa = 0;
1892 do {
1893 mantissa = mantissa * 10 + *start - '0';
1894 tmp.Append(*start);
1895 ++start;
1896 } while (*start >= '0' && *start <= '9' && start != end);
1898 if (start == end) {
1899 output.Append(tmp);
1900 break;
1904 char ch = *start;
1905 tmp.Append(ch);
1906 ++start;
1908 switch (ch) {
1909 case 'o':
1910 case 'O': {
1911 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1912 return false;
1915 JS::Rooted<JS::Value> v(aCx);
1916 if (index < aData.Length()) {
1917 v = aData[index++];
1920 if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
1921 return false;
1924 break;
1927 case 'c': {
1928 // If there isn't any output but there's already a style, then
1929 // discard the previous style and use the next one instead.
1930 if (output.IsEmpty() && !aStyles.IsEmpty()) {
1931 aStyles.RemoveLastElement();
1934 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1935 return false;
1938 if (index < aData.Length()) {
1939 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1940 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1941 if (NS_WARN_IF(!jsString)) {
1942 return false;
1945 int32_t diff = aSequence.Length() - aStyles.Length();
1946 if (diff > 0) {
1947 for (int32_t i = 0; i < diff; i++) {
1948 if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
1949 return false;
1954 nsAutoJSString string;
1955 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1956 return false;
1959 if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
1960 return false;
1963 break;
1966 case 's':
1967 if (index < aData.Length()) {
1968 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1969 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1970 if (NS_WARN_IF(!jsString)) {
1971 return false;
1974 nsAutoJSString v;
1975 if (NS_WARN_IF(!v.init(aCx, jsString))) {
1976 return false;
1979 output.Append(v);
1981 break;
1983 case 'd':
1984 case 'i':
1985 if (index < aData.Length()) {
1986 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1988 if (value.isBigInt()) {
1989 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1990 if (NS_WARN_IF(!jsString)) {
1991 return false;
1994 nsAutoJSString v;
1995 if (NS_WARN_IF(!v.init(aCx, jsString))) {
1996 return false;
1998 output.Append(v);
1999 break;
2002 int32_t v;
2003 if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
2004 return false;
2007 nsCString format;
2008 MakeFormatString(format, integer, mantissa, 'd');
2009 output.AppendPrintf(format.get(), v);
2011 break;
2013 case 'f':
2014 if (index < aData.Length()) {
2015 JS::Rooted<JS::Value> value(aCx, aData[index++]);
2017 double v;
2018 if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
2019 return false;
2022 // nspr returns "nan", but we want to expose it as "NaN"
2023 if (std::isnan(v)) {
2024 output.AppendFloat(v);
2025 } else {
2026 nsCString format;
2027 MakeFormatString(format, integer, mantissa, 'f');
2028 output.AppendPrintf(format.get(), v);
2031 break;
2033 default:
2034 output.Append(tmp);
2035 break;
2039 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
2040 return false;
2043 // Discard trailing style element if there is no output to apply it to.
2044 if (aStyles.Length() > aSequence.Length()) {
2045 aStyles.TruncateLength(aSequence.Length());
2048 // The rest of the array, if unused by the format string.
2049 for (; index < aData.Length(); ++index) {
2050 if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
2051 return false;
2055 return true;
2058 // Stringify and Concat all the JS::Value in a single string using ' ' as
2059 // separator. The new group name will be stored in aGroupStack array.
2060 static void ComposeAndStoreGroupName(JSContext* aCx,
2061 const Sequence<JS::Value>& aData,
2062 nsAString& aName,
2063 nsTArray<nsString>* aGroupStack) {
2064 StringJoinAppend(
2065 aName, u" "_ns, aData, [aCx](nsAString& dest, const JS::Value& valueRef) {
2066 JS::Rooted<JS::Value> value(aCx, valueRef);
2067 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2068 if (!jsString) {
2069 return;
2072 nsAutoJSString string;
2073 if (!string.init(aCx, jsString)) {
2074 return;
2077 dest.Append(string);
2080 aGroupStack->AppendElement(aName);
2083 // Remove the last group name and return that name. It returns false if
2084 // aGroupStack is empty.
2085 static bool UnstoreGroupName(nsAString& aName,
2086 nsTArray<nsString>* aGroupStack) {
2087 if (aGroupStack->IsEmpty()) {
2088 return false;
2091 aName = aGroupStack->PopLastElement();
2092 return true;
2095 Console::TimerStatus Console::StartTimer(JSContext* aCx, const JS::Value& aName,
2096 DOMHighResTimeStamp aTimestamp,
2097 nsAString& aTimerLabel,
2098 DOMHighResTimeStamp* aTimerValue) {
2099 AssertIsOnOwningThread();
2100 MOZ_ASSERT(aTimerValue);
2102 *aTimerValue = 0;
2104 if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
2105 return eTimerMaxReached;
2108 JS::Rooted<JS::Value> name(aCx, aName);
2109 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2110 if (NS_WARN_IF(!jsString)) {
2111 return eTimerJSException;
2114 nsAutoJSString label;
2115 if (NS_WARN_IF(!label.init(aCx, jsString))) {
2116 return eTimerJSException;
2119 aTimerLabel = label;
2121 if (mTimerRegistry.WithEntryHandle(label, [&](auto&& entry) {
2122 if (entry) {
2123 return true;
2125 entry.Insert(aTimestamp);
2126 return false;
2127 })) {
2128 return eTimerAlreadyExists;
2131 *aTimerValue = aTimestamp;
2132 return eTimerDone;
2135 /* static */
2136 JS::Value Console::CreateStartTimerValue(JSContext* aCx,
2137 const nsAString& aTimerLabel,
2138 TimerStatus aTimerStatus) {
2139 MOZ_ASSERT(aTimerStatus != eTimerUnknown);
2141 if (aTimerStatus != eTimerDone) {
2142 return CreateTimerError(aCx, aTimerLabel, aTimerStatus);
2145 RootedDictionary<ConsoleTimerStart> timer(aCx);
2147 timer.mName = aTimerLabel;
2149 JS::Rooted<JS::Value> value(aCx);
2150 if (!ToJSValue(aCx, timer, &value)) {
2151 return JS::UndefinedValue();
2154 return value;
2157 Console::TimerStatus Console::LogTimer(JSContext* aCx, const JS::Value& aName,
2158 DOMHighResTimeStamp aTimestamp,
2159 nsAString& aTimerLabel,
2160 double* aTimerDuration,
2161 bool aCancelTimer) {
2162 AssertIsOnOwningThread();
2163 MOZ_ASSERT(aTimerDuration);
2165 *aTimerDuration = 0;
2167 JS::Rooted<JS::Value> name(aCx, aName);
2168 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2169 if (NS_WARN_IF(!jsString)) {
2170 return eTimerJSException;
2173 nsAutoJSString key;
2174 if (NS_WARN_IF(!key.init(aCx, jsString))) {
2175 return eTimerJSException;
2178 aTimerLabel = key;
2180 DOMHighResTimeStamp value = 0;
2182 if (aCancelTimer) {
2183 if (!mTimerRegistry.Remove(key, &value)) {
2184 NS_WARNING("mTimerRegistry entry not found");
2185 return eTimerDoesntExist;
2187 } else {
2188 if (!mTimerRegistry.Get(key, &value)) {
2189 NS_WARNING("mTimerRegistry entry not found");
2190 return eTimerDoesntExist;
2194 *aTimerDuration = aTimestamp - value;
2195 return eTimerDone;
2198 /* static */
2199 JS::Value Console::CreateLogOrEndTimerValue(JSContext* aCx,
2200 const nsAString& aLabel,
2201 double aDuration,
2202 TimerStatus aStatus) {
2203 if (aStatus != eTimerDone) {
2204 return CreateTimerError(aCx, aLabel, aStatus);
2207 RootedDictionary<ConsoleTimerLogOrEnd> timer(aCx);
2208 timer.mName = aLabel;
2209 timer.mDuration = aDuration;
2211 JS::Rooted<JS::Value> value(aCx);
2212 if (!ToJSValue(aCx, timer, &value)) {
2213 return JS::UndefinedValue();
2216 return value;
2219 /* static */
2220 JS::Value Console::CreateTimerError(JSContext* aCx, const nsAString& aLabel,
2221 TimerStatus aStatus) {
2222 MOZ_ASSERT(aStatus != eTimerUnknown && aStatus != eTimerDone);
2224 RootedDictionary<ConsoleTimerError> error(aCx);
2226 error.mName = aLabel;
2228 switch (aStatus) {
2229 case eTimerAlreadyExists:
2230 error.mError.AssignLiteral("timerAlreadyExists");
2231 break;
2233 case eTimerDoesntExist:
2234 error.mError.AssignLiteral("timerDoesntExist");
2235 break;
2237 case eTimerJSException:
2238 error.mError.AssignLiteral("timerJSError");
2239 break;
2241 case eTimerMaxReached:
2242 error.mError.AssignLiteral("maxTimersExceeded");
2243 break;
2245 default:
2246 MOZ_CRASH("Unsupported status");
2247 break;
2250 JS::Rooted<JS::Value> value(aCx);
2251 if (!ToJSValue(aCx, error, &value)) {
2252 return JS::UndefinedValue();
2255 return value;
2258 uint32_t Console::IncreaseCounter(JSContext* aCx,
2259 const Sequence<JS::Value>& aArguments,
2260 nsAString& aCountLabel) {
2261 AssertIsOnOwningThread();
2263 ConsoleCommon::ClearException ce(aCx);
2265 MOZ_ASSERT(!aArguments.IsEmpty());
2267 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2268 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2269 if (!jsString) {
2270 return 0; // We cannot continue.
2273 nsAutoJSString string;
2274 if (!string.init(aCx, jsString)) {
2275 return 0; // We cannot continue.
2278 aCountLabel = string;
2280 const bool maxCountersReached = mCounterRegistry.Count() >= MAX_PAGE_COUNTERS;
2281 return mCounterRegistry.WithEntryHandle(
2282 aCountLabel, [maxCountersReached](auto&& entry) -> uint32_t {
2283 if (entry) {
2284 ++entry.Data();
2285 } else {
2286 if (maxCountersReached) {
2287 return MAX_PAGE_COUNTERS;
2289 entry.Insert(1);
2291 return entry.Data();
2295 uint32_t Console::ResetCounter(JSContext* aCx,
2296 const Sequence<JS::Value>& aArguments,
2297 nsAString& aCountLabel) {
2298 AssertIsOnOwningThread();
2300 ConsoleCommon::ClearException ce(aCx);
2302 MOZ_ASSERT(!aArguments.IsEmpty());
2304 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2305 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2306 if (!jsString) {
2307 return 0; // We cannot continue.
2310 nsAutoJSString string;
2311 if (!string.init(aCx, jsString)) {
2312 return 0; // We cannot continue.
2315 aCountLabel = string;
2317 if (mCounterRegistry.Remove(aCountLabel)) {
2318 return 0;
2321 // Let's return something different than 0 if the key doesn't exist.
2322 return MAX_PAGE_COUNTERS;
2325 // This method generates a ConsoleCounter dictionary as JS::Value. If
2326 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
2327 // instead. See IncreaseCounter.
2328 // * aCx - this is the context that will root the returned value.
2329 // * aCountLabel - this label must be what IncreaseCounter received as
2330 // aTimerLabel.
2331 // * aCountValue - the return value of IncreaseCounter.
2332 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
2333 const nsAString& aCountLabel,
2334 uint32_t aCountValue) {
2335 ConsoleCommon::ClearException ce(aCx);
2337 if (aCountValue == MAX_PAGE_COUNTERS) {
2338 RootedDictionary<ConsoleCounterError> error(aCx);
2339 error.mLabel = aCountLabel;
2340 error.mError.AssignLiteral("counterDoesntExist");
2342 JS::Rooted<JS::Value> value(aCx);
2343 if (!ToJSValue(aCx, error, &value)) {
2344 return JS::UndefinedValue();
2347 return value;
2350 RootedDictionary<ConsoleCounter> data(aCx);
2351 data.mLabel = aCountLabel;
2352 data.mCount = aCountValue;
2354 JS::Rooted<JS::Value> value(aCx);
2355 if (!ToJSValue(aCx, data, &value)) {
2356 return JS::UndefinedValue();
2359 return value;
2362 /* static */
2363 bool Console::ShouldIncludeStackTrace(MethodName aMethodName) {
2364 switch (aMethodName) {
2365 case MethodError:
2366 case MethodException:
2367 case MethodAssert:
2368 case MethodTrace:
2369 return true;
2370 default:
2371 return false;
2375 JSObject* MainThreadConsoleData::GetOrCreateSandbox(JSContext* aCx,
2376 nsIPrincipal* aPrincipal) {
2377 AssertIsOnMainThread();
2379 if (!mSandbox) {
2380 nsIXPConnect* xpc = nsContentUtils::XPConnect();
2381 MOZ_ASSERT(xpc, "This should never be null!");
2383 JS::Rooted<JSObject*> sandbox(aCx);
2384 nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
2385 if (NS_WARN_IF(NS_FAILED(rv))) {
2386 return nullptr;
2389 mSandbox = new JSObjectHolder(aCx, sandbox);
2392 return mSandbox->GetJSObject();
2395 bool Console::StoreCallData(JSContext* aCx, ConsoleCallData* aCallData,
2396 const Sequence<JS::Value>& aArguments) {
2397 AssertIsOnOwningThread();
2399 if (NS_WARN_IF(!mArgumentStorage.growBy(1))) {
2400 return false;
2402 if (!mArgumentStorage.end()[-1].Initialize(aCx, aArguments)) {
2403 mArgumentStorage.shrinkBy(1);
2404 return false;
2407 MOZ_ASSERT(aCallData);
2408 MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
2410 mCallDataStorage.AppendElement(aCallData);
2412 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2414 if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
2415 mCallDataStorage.RemoveElementAt(0);
2416 mArgumentStorage.erase(&mArgumentStorage[0]);
2418 return true;
2421 void Console::UnstoreCallData(ConsoleCallData* aCallData) {
2422 AssertIsOnOwningThread();
2424 MOZ_ASSERT(aCallData);
2425 MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2427 size_t index = mCallDataStorage.IndexOf(aCallData);
2428 // It can be that mCallDataStorage has been already cleaned in case the
2429 // processing of the argument of some Console methods triggers the
2430 // window.close().
2431 if (index == mCallDataStorage.NoIndex) {
2432 return;
2435 mCallDataStorage.RemoveElementAt(index);
2436 mArgumentStorage.erase(&mArgumentStorage[index]);
2439 void Console::NotifyHandler(JSContext* aCx,
2440 const Sequence<JS::Value>& aArguments,
2441 ConsoleCallData* aCallData) {
2442 AssertIsOnOwningThread();
2443 MOZ_ASSERT(!NS_IsMainThread());
2444 MOZ_ASSERT(aCallData);
2446 if (!mConsoleEventNotifier) {
2447 return;
2450 JS::Rooted<JS::Value> value(aCx);
2452 JS::Rooted<JSObject*> callableGlobal(
2453 aCx, mConsoleEventNotifier->CallbackGlobalOrNull());
2454 if (NS_WARN_IF(!callableGlobal)) {
2455 return;
2458 // aCx and aArguments are in the same compartment because this method is
2459 // called directly when a Console.something() runs.
2460 // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
2461 // sent to.
2462 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2463 aCx, aArguments, callableGlobal, &value, aCallData, &mGroupStack))) {
2464 return;
2467 JS::Rooted<JS::Value> ignored(aCx);
2468 RefPtr<AnyCallback> notifier(mConsoleEventNotifier);
2469 notifier->Call(value, &ignored);
2472 void Console::RetrieveConsoleEvents(JSContext* aCx,
2473 nsTArray<JS::Value>& aEvents,
2474 ErrorResult& aRv) {
2475 AssertIsOnOwningThread();
2477 // We don't want to expose this functionality to main-thread yet.
2478 MOZ_ASSERT(!NS_IsMainThread());
2480 JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
2482 for (uint32_t i = 0; i < mArgumentStorage.length(); ++i) {
2483 JS::Rooted<JS::Value> value(aCx);
2485 JS::Rooted<JSObject*> sequenceScope(aCx, mArgumentStorage[i].Global());
2486 JSAutoRealm ar(aCx, sequenceScope);
2488 Sequence<JS::Value> sequence;
2489 SequenceRooter<JS::Value> arguments(aCx, &sequence);
2491 if (!mArgumentStorage[i].PopulateArgumentsSequence(sequence)) {
2492 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2493 return;
2496 // Here we have aCx and sequence in the same compartment.
2497 // targetScope is the destination scope and value will be populated in its
2498 // compartment.
2499 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2500 aCx, sequence, targetScope, &value, mCallDataStorage[i],
2501 &mGroupStack))) {
2502 aRv.Throw(NS_ERROR_FAILURE);
2503 return;
2506 aEvents.AppendElement(value);
2510 void Console::SetConsoleEventHandler(AnyCallback* aHandler) {
2511 AssertIsOnOwningThread();
2513 // We don't want to expose this functionality to main-thread yet.
2514 MOZ_ASSERT(!NS_IsMainThread());
2516 mConsoleEventNotifier = aHandler;
2519 void Console::AssertIsOnOwningThread() const {
2520 NS_ASSERT_OWNINGTHREAD(Console);
2523 bool Console::IsShuttingDown() const {
2524 MOZ_ASSERT(mStatus != eUnknown);
2525 return mStatus == eShuttingDown;
2528 /* static */
2529 already_AddRefed<Console> Console::GetConsole(const GlobalObject& aGlobal) {
2530 ErrorResult rv;
2531 RefPtr<Console> console = GetConsoleInternal(aGlobal, rv);
2532 if (NS_WARN_IF(rv.Failed()) || !console) {
2533 rv.SuppressException();
2534 return nullptr;
2537 console->AssertIsOnOwningThread();
2539 if (console->IsShuttingDown()) {
2540 return nullptr;
2543 return console.forget();
2546 /* static */
2547 already_AddRefed<Console> Console::GetConsoleInternal(
2548 const GlobalObject& aGlobal, ErrorResult& aRv) {
2549 // Window
2550 if (NS_IsMainThread()) {
2551 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2552 do_QueryInterface(aGlobal.GetAsSupports());
2554 // we are probably running a chrome script.
2555 if (!innerWindow) {
2556 RefPtr<Console> console = new Console(aGlobal.Context(), nullptr, 0, 0);
2557 console->Initialize(aRv);
2558 if (NS_WARN_IF(aRv.Failed())) {
2559 return nullptr;
2562 return console.forget();
2565 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow);
2566 return window->GetConsole(aGlobal.Context(), aRv);
2569 // Worklet
2570 nsCOMPtr<WorkletGlobalScope> workletScope =
2571 do_QueryInterface(aGlobal.GetAsSupports());
2572 if (workletScope) {
2573 WorkletThread::AssertIsOnWorkletThread();
2574 return workletScope->GetConsole(aGlobal.Context(), aRv);
2577 // Workers
2578 MOZ_ASSERT(!NS_IsMainThread());
2580 JSContext* cx = aGlobal.Context();
2581 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
2582 MOZ_ASSERT(workerPrivate);
2584 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
2585 if (NS_WARN_IF(!global)) {
2586 return nullptr;
2589 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
2590 MOZ_ASSERT(scope);
2592 // Normal worker scope.
2593 if (scope == global) {
2594 return scope->GetConsole(aRv);
2597 // Debugger worker scope
2599 WorkerDebuggerGlobalScope* debuggerScope =
2600 workerPrivate->DebuggerGlobalScope();
2601 MOZ_ASSERT(debuggerScope);
2602 MOZ_ASSERT(debuggerScope == global, "Which kind of global do we have?");
2604 return debuggerScope->GetConsole(aRv);
2607 bool Console::MonotonicTimer(JSContext* aCx, MethodName aMethodName,
2608 const Sequence<JS::Value>& aData,
2609 DOMHighResTimeStamp* aTimeStamp) {
2610 if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(mGlobal)) {
2611 nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(innerWindow);
2612 MOZ_ASSERT(win);
2614 RefPtr<Performance> performance = win->GetPerformance();
2615 if (!performance) {
2616 return false;
2619 *aTimeStamp = performance->Now();
2620 return true;
2623 if (NS_IsMainThread()) {
2624 *aTimeStamp = (TimeStamp::Now() - mCreationTimeStamp).ToMilliseconds();
2625 return true;
2628 if (nsCOMPtr<WorkletGlobalScope> workletGlobal = do_QueryInterface(mGlobal)) {
2629 *aTimeStamp = workletGlobal->TimeStampToDOMHighRes(TimeStamp::Now());
2630 return true;
2633 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2634 MOZ_ASSERT(workerPrivate);
2636 *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
2637 return true;
2640 /* static */
2641 already_AddRefed<ConsoleInstance> Console::CreateInstance(
2642 const GlobalObject& aGlobal, const ConsoleInstanceOptions& aOptions) {
2643 RefPtr<ConsoleInstance> console =
2644 new ConsoleInstance(aGlobal.Context(), aOptions);
2645 return console.forget();
2648 void Console::StringifyElement(Element* aElement, nsAString& aOut) {
2649 aOut.AppendLiteral("<");
2650 aOut.Append(aElement->LocalName());
2651 uint32_t attrCount = aElement->GetAttrCount();
2652 nsAutoString idAttr;
2653 nsAutoString classAttr;
2654 nsAutoString nameAttr;
2655 nsAutoString otherAttrs;
2656 for (uint32_t i = 0; i < attrCount; i++) {
2657 BorrowedAttrInfo attrInfo = aElement->GetAttrInfoAt(i);
2658 nsAutoString attrValue;
2659 attrInfo.mValue->ToString(attrValue);
2661 const nsAttrName* attrName = attrInfo.mName;
2662 if (attrName->Equals(nsGkAtoms::id)) {
2663 idAttr.AppendLiteral(" id=\"");
2664 idAttr.Append(attrValue);
2665 idAttr.AppendLiteral("\"");
2666 } else if (attrName->Equals(nsGkAtoms::_class)) {
2667 classAttr.AppendLiteral(" class=\"");
2668 classAttr.Append(attrValue);
2669 classAttr.AppendLiteral("\"");
2670 } else if (attrName->Equals(nsGkAtoms::name)) {
2671 nameAttr.AppendLiteral(" name=\"");
2672 nameAttr.Append(attrValue);
2673 nameAttr.AppendLiteral("\"");
2674 } else {
2675 nsAutoString attrNameStr;
2676 attrName->GetQualifiedName(attrNameStr);
2677 otherAttrs.AppendLiteral(" ");
2678 otherAttrs.Append(attrNameStr);
2679 otherAttrs.AppendLiteral("=\"");
2680 otherAttrs.Append(attrValue);
2681 otherAttrs.AppendLiteral("\"");
2684 if (!idAttr.IsEmpty()) {
2685 aOut.Append(idAttr);
2687 if (!classAttr.IsEmpty()) {
2688 aOut.Append(classAttr);
2690 if (!nameAttr.IsEmpty()) {
2691 aOut.Append(nameAttr);
2693 if (!otherAttrs.IsEmpty()) {
2694 aOut.Append(otherAttrs);
2696 aOut.AppendLiteral(">");
2699 void Console::MaybeExecuteDumpFunction(JSContext* aCx,
2700 const nsAString& aMethodName,
2701 const Sequence<JS::Value>& aData,
2702 nsIStackFrame* aStack) {
2703 if (!mDumpFunction && !mDumpToStdout) {
2704 return;
2707 nsAutoString message;
2708 message.AssignLiteral("console.");
2709 message.Append(aMethodName);
2710 message.AppendLiteral(": ");
2712 if (!mPrefix.IsEmpty()) {
2713 message.Append(mPrefix);
2714 message.AppendLiteral(": ");
2717 for (uint32_t i = 0; i < aData.Length(); ++i) {
2718 JS::Rooted<JS::Value> v(aCx, aData[i]);
2719 if (v.isObject()) {
2720 Element* element = nullptr;
2721 if (NS_SUCCEEDED(UNWRAP_OBJECT(Element, &v, element))) {
2722 if (i != 0) {
2723 message.AppendLiteral(" ");
2725 StringifyElement(element, message);
2726 continue;
2730 JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
2731 if (!jsString) {
2732 continue;
2735 nsAutoJSString string;
2736 if (NS_WARN_IF(!string.init(aCx, jsString))) {
2737 return;
2740 if (i != 0) {
2741 message.AppendLiteral(" ");
2744 message.Append(string);
2747 message.AppendLiteral("\n");
2749 // aStack can be null.
2751 nsCOMPtr<nsIStackFrame> stack(aStack);
2753 while (stack) {
2754 nsAutoString filename;
2755 stack->GetFilename(aCx, filename);
2757 message.Append(filename);
2758 message.AppendLiteral(" ");
2760 message.AppendInt(stack->GetLineNumber(aCx));
2761 message.AppendLiteral(" ");
2763 nsAutoString functionName;
2764 stack->GetName(aCx, functionName);
2766 message.Append(functionName);
2767 message.AppendLiteral("\n");
2769 nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
2771 if (!caller) {
2772 caller = stack->GetAsyncCaller(aCx);
2775 stack.swap(caller);
2778 ExecuteDumpFunction(message);
2781 void Console::MaybeExecuteDumpFunctionForTime(JSContext* aCx,
2782 MethodName aMethodName,
2783 const nsAString& aMethodString,
2784 uint64_t aMonotonicTimer,
2785 const JS::Value& aData) {
2786 if (!mDumpFunction && !mDumpToStdout) {
2787 return;
2790 nsAutoString message;
2791 message.AssignLiteral("console.");
2792 message.Append(aMethodString);
2793 message.AppendLiteral(": ");
2795 if (!mPrefix.IsEmpty()) {
2796 message.Append(mPrefix);
2797 message.AppendLiteral(": ");
2800 JS::Rooted<JS::Value> v(aCx, aData);
2801 JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
2802 if (!jsString) {
2803 return;
2806 nsAutoJSString string;
2807 if (NS_WARN_IF(!string.init(aCx, jsString))) {
2808 return;
2811 message.Append(string);
2812 message.AppendLiteral(" @ ");
2813 message.AppendInt(aMonotonicTimer);
2815 message.AppendLiteral("\n");
2816 ExecuteDumpFunction(message);
2819 void Console::ExecuteDumpFunction(const nsAString& aMessage) {
2820 if (mDumpFunction) {
2821 RefPtr<ConsoleInstanceDumpCallback> dumpFunction(mDumpFunction);
2822 dumpFunction->Call(aMessage);
2823 return;
2826 NS_ConvertUTF16toUTF8 str(aMessage);
2827 MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
2828 #ifdef ANDROID
2829 __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
2830 #endif
2831 fputs(str.get(), stdout);
2832 fflush(stdout);
2835 bool Console::ShouldProceed(MethodName aName) const {
2836 return mCurrentLogLevel <= InternalLogLevelToInteger(aName);
2839 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const {
2840 switch (aLevel) {
2841 case ConsoleLogLevel::All:
2842 return 0;
2843 case ConsoleLogLevel::Debug:
2844 return 2;
2845 case ConsoleLogLevel::Log:
2846 return 3;
2847 case ConsoleLogLevel::Info:
2848 return 3;
2849 case ConsoleLogLevel::Clear:
2850 return 3;
2851 case ConsoleLogLevel::Trace:
2852 return 3;
2853 case ConsoleLogLevel::TimeLog:
2854 return 3;
2855 case ConsoleLogLevel::TimeEnd:
2856 return 3;
2857 case ConsoleLogLevel::Time:
2858 return 3;
2859 case ConsoleLogLevel::Group:
2860 return 3;
2861 case ConsoleLogLevel::GroupEnd:
2862 return 3;
2863 case ConsoleLogLevel::Profile:
2864 return 3;
2865 case ConsoleLogLevel::ProfileEnd:
2866 return 3;
2867 case ConsoleLogLevel::Dir:
2868 return 3;
2869 case ConsoleLogLevel::Dirxml:
2870 return 3;
2871 case ConsoleLogLevel::Warn:
2872 return 4;
2873 case ConsoleLogLevel::Error:
2874 return 5;
2875 case ConsoleLogLevel::Off:
2876 return UINT32_MAX;
2877 default:
2878 MOZ_CRASH(
2879 "ConsoleLogLevel is out of sync with the Console implementation!");
2880 return 0;
2884 uint32_t Console::InternalLogLevelToInteger(MethodName aName) const {
2885 switch (aName) {
2886 case MethodLog:
2887 return 3;
2888 case MethodInfo:
2889 return 3;
2890 case MethodWarn:
2891 return 4;
2892 case MethodError:
2893 return 5;
2894 case MethodException:
2895 return 5;
2896 case MethodDebug:
2897 return 2;
2898 case MethodTable:
2899 return 3;
2900 case MethodTrace:
2901 return 3;
2902 case MethodDir:
2903 return 3;
2904 case MethodDirxml:
2905 return 3;
2906 case MethodGroup:
2907 return 3;
2908 case MethodGroupCollapsed:
2909 return 3;
2910 case MethodGroupEnd:
2911 return 3;
2912 case MethodTime:
2913 return 3;
2914 case MethodTimeLog:
2915 return 3;
2916 case MethodTimeEnd:
2917 return 3;
2918 case MethodTimeStamp:
2919 return 3;
2920 case MethodAssert:
2921 return 3;
2922 case MethodCount:
2923 return 3;
2924 case MethodCountReset:
2925 return 3;
2926 case MethodClear:
2927 return 3;
2928 case MethodProfile:
2929 return 3;
2930 case MethodProfileEnd:
2931 return 3;
2932 default:
2933 MOZ_CRASH("MethodName is out of sync with the Console implementation!");
2934 return 0;
2938 bool Console::ArgumentData::Initialize(JSContext* aCx,
2939 const Sequence<JS::Value>& aArguments) {
2940 mGlobal = JS::CurrentGlobalOrNull(aCx);
2942 if (NS_WARN_IF(!mArguments.AppendElements(aArguments, fallible))) {
2943 return false;
2946 return true;
2949 void Console::ArgumentData::Trace(const TraceCallbacks& aCallbacks,
2950 void* aClosure) {
2951 ArgumentData* tmp = this;
2952 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
2953 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments[i])
2956 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
2959 bool Console::ArgumentData::PopulateArgumentsSequence(
2960 Sequence<JS::Value>& aSequence) const {
2961 AssertIsOnOwningThread();
2963 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
2964 if (NS_WARN_IF(!aSequence.AppendElement(mArguments[i], fallible))) {
2965 return false;
2969 return true;
2972 } // namespace mozilla::dom