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/. */
6 
7 #include "mozilla/dom/Console.h"
8 #include "mozilla/dom/ConsoleInstance.h"
9 #include "mozilla/dom/ConsoleBinding.h"
10 #include "ConsoleCommon.h"
11 
12 #include "js/Array.h"  // JS::GetArrayLength, JS::NewArrayObject
13 #include "mozilla/dom/BlobBinding.h"
14 #include "mozilla/dom/BlobImpl.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/Exceptions.h"
17 #include "mozilla/dom/File.h"
18 #include "mozilla/dom/FunctionBinding.h"
19 #include "mozilla/dom/Performance.h"
20 #include "mozilla/dom/PromiseBinding.h"
21 #include "mozilla/dom/ScriptSettings.h"
22 #include "mozilla/dom/StructuredCloneHolder.h"
23 #include "mozilla/dom/ToJSValue.h"
24 #include "mozilla/dom/WorkerPrivate.h"
25 #include "mozilla/dom/WorkerRunnable.h"
26 #include "mozilla/dom/WorkerScope.h"
27 #include "mozilla/dom/WorkletGlobalScope.h"
28 #include "mozilla/dom/WorkletImpl.h"
29 #include "mozilla/dom/WorkletThread.h"
30 #include "mozilla/BasePrincipal.h"
31 #include "mozilla/HoldDropJSObjects.h"
32 #include "mozilla/JSObjectHolder.h"
33 #include "mozilla/Maybe.h"
34 #include "mozilla/Preferences.h"
35 #include "mozilla/StaticPrefs_devtools.h"
36 #include "mozilla/StaticPrefs_dom.h"
37 #include "nsCycleCollectionParticipant.h"
38 #include "nsDOMNavigationTiming.h"
39 #include "nsGlobalWindow.h"
40 #include "nsJSUtils.h"
41 #include "nsNetUtil.h"
42 #include "xpcpublic.h"
43 #include "nsContentUtils.h"
44 #include "nsDocShell.h"
45 #include "nsProxyRelease.h"
46 #include "nsReadableUtils.h"
47 #include "mozilla/ConsoleTimelineMarker.h"
48 #include "mozilla/TimestampTimelineMarker.h"
49 
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"
58 
59 // The maximum allowed number of concurrent timers per page.
60 #define MAX_PAGE_TIMERS 10000
61 
62 // The maximum allowed number of concurrent counters per page.
63 #define MAX_PAGE_COUNTERS 10000
64 
65 // The maximum stacktrace depth when populating the stacktrace array used for
66 // console.trace().
67 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
68 
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
72 
73 // This value is taken from ConsoleAPIStorage.js
74 #define STORAGE_MAX_EVENTS 1000
75 
76 using namespace mozilla::dom::exceptions;
77 
78 namespace mozilla::dom {
79 
80 struct ConsoleStructuredCloneData {
81   nsCOMPtr<nsIGlobalObject> mGlobal;
82   nsTArray<RefPtr<BlobImpl>> mBlobs;
83 };
84 
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);
90 
91 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
92                              Sequence<JS::Value>& aSequence,
93                              Sequence<nsString>& aStyles);
94 
95 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
96                                                   const nsAString& aCountLabel,
97                                                   uint32_t aCountValue);
98 
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.
104  */
105 
106 class ConsoleCallData final {
107  public:
108   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData)
109 
ConsoleCallData(Console::MethodName aName,const nsAString & aString,Console * aConsole)110   ConsoleCallData(Console::MethodName aName, const nsAString& aString,
111                   Console* aConsole)
112       : mConsoleID(aConsole->mConsoleID),
113         mPrefix(aConsole->mPrefix),
114         mMethodName(aName),
115         mTimeStamp(JS_Now() / PR_USEC_PER_MSEC),
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) {}
125 
SetIDs(uint64_t aOuterID,uint64_t aInnerID)126   void SetIDs(uint64_t aOuterID, uint64_t aInnerID) {
127     MOZ_ASSERT(mIDType == eUnknown);
128 
129     mOuterIDNumber = aOuterID;
130     mInnerIDNumber = aInnerID;
131     mIDType = eNumber;
132   }
133 
SetIDs(const nsAString & aOuterID,const nsAString & aInnerID)134   void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) {
135     MOZ_ASSERT(mIDType == eUnknown);
136 
137     mOuterIDString = aOuterID;
138     mInnerIDString = aInnerID;
139     mIDType = eString;
140   }
141 
SetOriginAttributes(const OriginAttributes & aOriginAttributes)142   void SetOriginAttributes(const OriginAttributes& aOriginAttributes) {
143     mOriginAttributes = aOriginAttributes;
144   }
145 
SetAddonId(nsIPrincipal * aPrincipal)146   void SetAddonId(nsIPrincipal* aPrincipal) {
147     nsAutoString addonId;
148     aPrincipal->GetAddonId(addonId);
149 
150     mAddonId = addonId;
151   }
152 
AssertIsOnOwningThread() const153   void AssertIsOnOwningThread() const {
154     NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
155   }
156 
157   const nsString mConsoleID;
158   const nsString mPrefix;
159 
160   const Console::MethodName mMethodName;
161   int64_t mTimeStamp;
162 
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;
174 
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;
184 
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;
191 
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.jsm the ID is 'jsm'.
198   enum { eString, eNumber, eUnknown } mIDType;
199 
200   uint64_t mOuterIDNumber;
201   nsString mOuterIDString;
202 
203   uint64_t mInnerIDNumber;
204   nsString mInnerIDString;
205 
206   OriginAttributes mOriginAttributes;
207 
208   nsString mAddonId;
209 
210   const nsString mMethodString;
211 
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;
221 
222  private:
223   ~ConsoleCallData() = default;
224 
225   NS_DECL_OWNINGTHREAD;
226 };
227 
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);
235 
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);
240 
241  private:
~MainThreadConsoleData()242   ~MainThreadConsoleData() {
243     NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
244                            mStorage.forget());
245     NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
246                            mSandbox.forget());
247   }
248 
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;
255 };
256 
257 // This base class must be extended for Worker and for Worklet.
258 class ConsoleRunnable : public StructuredCloneHolderBase {
259  public:
~ConsoleRunnable()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();
265   }
266 
267  protected:
CustomReadHandler(JSContext * aCx,JSStructuredCloneReader * aReader,const JS::CloneDataPolicy & aCloneDataPolicy,uint32_t aTag,uint32_t aIndex)268   JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
269                               const JS::CloneDataPolicy& aCloneDataPolicy,
270                               uint32_t aTag, uint32_t aIndex) override {
271     AssertIsOnMainThread();
272 
273     if (aTag == CONSOLE_TAG_BLOB) {
274       MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
275 
276       JS::Rooted<JS::Value> val(aCx);
277       {
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;
283         }
284       }
285 
286       return &val.toObject();
287     }
288 
289     MOZ_CRASH("No other tags are supported.");
290     return nullptr;
291   }
292 
CustomWriteHandler(JSContext * aCx,JSStructuredCloneWriter * aWriter,JS::Handle<JSObject * > aObj,bool * aSameProcessScopeRequired)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;
301       }
302 
303       mClonedData.mBlobs.AppendElement(blob->Impl());
304       return true;
305     }
306 
307     if (!JS_ObjectNotWritten(aWriter, aObj)) {
308       return false;
309     }
310 
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;
315     }
316 
317     if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
318       return false;
319     }
320 
321     return true;
322   }
323 
324   // Helper method for CallData
ProcessCallData(JSContext * aCx,MainThreadConsoleData * aConsoleData,ConsoleCallData * aCallData)325   void ProcessCallData(JSContext* aCx, MainThreadConsoleData* aConsoleData,
326                        ConsoleCallData* aCallData) {
327     AssertIsOnMainThread();
328 
329     ConsoleCommon::ClearException ce(aCx);
330 
331     JS::Rooted<JS::Value> argumentsValue(aCx);
332     if (!Read(aCx, &argumentsValue)) {
333       return;
334     }
335 
336     MOZ_ASSERT(argumentsValue.isObject());
337 
338     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
339 
340     uint32_t length;
341     if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
342       return;
343     }
344 
345     Sequence<JS::Value> values;
346     SequenceRooter<JS::Value> arguments(aCx, &values);
347 
348     for (uint32_t i = 0; i < length; ++i) {
349       JS::Rooted<JS::Value> value(aCx);
350 
351       if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
352         return;
353       }
354 
355       if (!values.AppendElement(value, fallible)) {
356         return;
357       }
358     }
359 
360     MOZ_ASSERT(values.Length() == length);
361 
362     aConsoleData->ProcessCallData(aCx, aCallData, values);
363   }
364 
365   // Generic
WriteArguments(JSContext * aCx,const Sequence<JS::Value> & aArguments)366   bool WriteArguments(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
367     ConsoleCommon::ClearException ce(aCx);
368 
369     JS::Rooted<JSObject*> arguments(
370         aCx, JS::NewArrayObject(aCx, aArguments.Length()));
371     if (NS_WARN_IF(!arguments)) {
372       return false;
373     }
374 
375     JS::Rooted<JS::Value> arg(aCx);
376     for (uint32_t i = 0; i < aArguments.Length(); ++i) {
377       arg = aArguments[i];
378       if (NS_WARN_IF(
379               !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
380         return false;
381       }
382     }
383 
384     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
385     return WriteData(aCx, value);
386   }
387 
388   // Helper method for Profile calls
ProcessProfileData(JSContext * aCx,Console::MethodName aMethodName,const nsAString & aAction)389   void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName,
390                           const nsAString& aAction) {
391     AssertIsOnMainThread();
392 
393     ConsoleCommon::ClearException ce(aCx);
394 
395     JS::Rooted<JS::Value> argumentsValue(aCx);
396     bool ok = Read(aCx, &argumentsValue);
397     mClonedData.mGlobal = nullptr;
398 
399     if (!ok) {
400       return;
401     }
402 
403     MOZ_ASSERT(argumentsValue.isObject());
404     JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
405     if (NS_WARN_IF(!argumentsObj)) {
406       return;
407     }
408 
409     uint32_t length;
410     if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
411       return;
412     }
413 
414     Sequence<JS::Value> arguments;
415 
416     for (uint32_t i = 0; i < length; ++i) {
417       JS::Rooted<JS::Value> value(aCx);
418 
419       if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
420         return;
421       }
422 
423       if (!arguments.AppendElement(value, fallible)) {
424         return;
425       }
426     }
427 
428     Console::ProfileMethodMainthread(aCx, aAction, arguments);
429   }
430 
WriteData(JSContext * aCx,JS::Handle<JS::Value> aValue)431   bool WriteData(JSContext* aCx, JS::Handle<JS::Value> aValue) {
432     // We use structuredClone to send the JSValue to the main-thread, in order
433     // to store it into the Console API Service. The consumer will be the
434     // console panel in the devtools and, because of this, we want to allow the
435     // cloning of sharedArrayBuffers and WASM modules.
436     JS::CloneDataPolicy cloneDataPolicy;
437     cloneDataPolicy.allowIntraClusterClonableSharedObjects();
438     cloneDataPolicy.allowSharedMemoryObjects();
439 
440     if (NS_WARN_IF(
441             !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) {
442       // Ignore the message.
443       return false;
444     }
445 
446     return true;
447   }
448 
449   ConsoleStructuredCloneData mClonedData;
450 };
451 
452 class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
453  protected:
ConsoleWorkletRunnable(Console * aConsole)454   explicit ConsoleWorkletRunnable(Console* aConsole)
455       : Runnable("dom::console::ConsoleWorkletRunnable"),
456         mConsoleData(aConsole->GetOrCreateMainThreadData()) {
457     WorkletThread::AssertIsOnWorkletThread();
458     nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(aConsole->mGlobal);
459     MOZ_ASSERT(global);
460     mWorkletImpl = global->Impl();
461     MOZ_ASSERT(mWorkletImpl);
462   }
463 
464   ~ConsoleWorkletRunnable() override = default;
465 
466  protected:
467   RefPtr<MainThreadConsoleData> mConsoleData;
468 
469   RefPtr<WorkletImpl> mWorkletImpl;
470 };
471 
472 // This runnable appends a CallData object into the Console queue running on
473 // the main-thread.
474 class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
475  public:
Create(JSContext * aCx,Console * aConsole,ConsoleCallData * aConsoleData,const Sequence<JS::Value> & aArguments)476   static already_AddRefed<ConsoleCallDataWorkletRunnable> Create(
477       JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData,
478       const Sequence<JS::Value>& aArguments) {
479     WorkletThread::AssertIsOnWorkletThread();
480 
481     RefPtr<ConsoleCallDataWorkletRunnable> runnable =
482         new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData);
483 
484     if (!runnable->WriteArguments(aCx, aArguments)) {
485       return nullptr;
486     }
487 
488     return runnable.forget();
489   }
490 
491  private:
ConsoleCallDataWorkletRunnable(Console * aConsole,ConsoleCallData * aCallData)492   ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData)
493       : ConsoleWorkletRunnable(aConsole), mCallData(aCallData) {
494     WorkletThread::AssertIsOnWorkletThread();
495     MOZ_ASSERT(aCallData);
496     aCallData->AssertIsOnOwningThread();
497 
498     const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo();
499     mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID());
500   }
501 
502   ~ConsoleCallDataWorkletRunnable() override = default;
503 
Run()504   NS_IMETHOD Run() override {
505     AssertIsOnMainThread();
506     AutoJSAPI jsapi;
507     jsapi.Init();
508     JSContext* cx = jsapi.cx();
509 
510     JSObject* sandbox =
511         mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
512     JS::Rooted<JSObject*> global(cx, sandbox);
513     if (NS_WARN_IF(!global)) {
514       return NS_ERROR_FAILURE;
515     }
516 
517     // The CreateSandbox call returns a proxy to the actual sandbox object. We
518     // don't need a proxy here.
519     global = js::UncheckedUnwrap(global);
520 
521     JSAutoRealm ar(cx, global);
522 
523     // We don't need to set a parent object in mCallData bacause there are not
524     // DOM objects exposed to worklet.
525 
526     ProcessCallData(cx, mConsoleData, mCallData);
527 
528     return NS_OK;
529   }
530 
531   RefPtr<ConsoleCallData> mCallData;
532 };
533 
534 class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
535                               public ConsoleRunnable {
536  public:
ConsoleWorkerRunnable(Console * aConsole)537   explicit ConsoleWorkerRunnable(Console* aConsole)
538       : mConsoleData(aConsole->GetOrCreateMainThreadData()) {}
539 
540   ~ConsoleWorkerRunnable() override = default;
541 
Dispatch(JSContext * aCx,const Sequence<JS::Value> & aArguments)542   bool Dispatch(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
543     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
544     MOZ_ASSERT(workerPrivate);
545 
546     if (NS_WARN_IF(!WriteArguments(aCx, aArguments))) {
547       RunBackOnWorkerThreadForCleanup(workerPrivate);
548       return false;
549     }
550 
551     if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
552       // RunBackOnWorkerThreadForCleanup() will be called by
553       // WorkerProxyToMainThreadRunnable::Dispatch().
554       return false;
555     }
556 
557     return true;
558   }
559 
560  protected:
RunOnMainThread(WorkerPrivate * aWorkerPrivate)561   void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
562     MOZ_ASSERT(aWorkerPrivate);
563     AssertIsOnMainThread();
564 
565     // Walk up to our containing page
566     WorkerPrivate* wp = aWorkerPrivate;
567     while (wp->GetParent()) {
568       wp = wp->GetParent();
569     }
570 
571     nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow();
572     if (!window) {
573       RunWindowless(aWorkerPrivate);
574     } else {
575       RunWithWindow(aWorkerPrivate, window);
576     }
577   }
578 
RunWithWindow(WorkerPrivate * aWorkerPrivate,nsPIDOMWindowInner * aWindow)579   void RunWithWindow(WorkerPrivate* aWorkerPrivate,
580                      nsPIDOMWindowInner* aWindow) {
581     MOZ_ASSERT(aWorkerPrivate);
582     AssertIsOnMainThread();
583 
584     AutoJSAPI jsapi;
585     MOZ_ASSERT(aWindow);
586 
587     RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
588     if (NS_WARN_IF(!jsapi.Init(win))) {
589       return;
590     }
591 
592     nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
593     if (NS_WARN_IF(!outerWindow)) {
594       return;
595     }
596 
597     RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
598                aWindow);
599   }
600 
RunWindowless(WorkerPrivate * aWorkerPrivate)601   void RunWindowless(WorkerPrivate* aWorkerPrivate) {
602     MOZ_ASSERT(aWorkerPrivate);
603     AssertIsOnMainThread();
604 
605     WorkerPrivate* wp = aWorkerPrivate;
606     while (wp->GetParent()) {
607       wp = wp->GetParent();
608     }
609 
610     MOZ_ASSERT(!wp->GetWindow());
611 
612     AutoJSAPI jsapi;
613     jsapi.Init();
614 
615     JSContext* cx = jsapi.cx();
616 
617     JS::Rooted<JSObject*> global(
618         cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal()));
619     if (NS_WARN_IF(!global)) {
620       return;
621     }
622 
623     // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
624     // We don't need a proxy here.
625     global = js::UncheckedUnwrap(global);
626 
627     JSAutoRealm ar(cx, global);
628 
629     nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global);
630     if (NS_WARN_IF(!globalObject)) {
631       return;
632     }
633 
634     RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr);
635   }
636 
RunBackOnWorkerThreadForCleanup(WorkerPrivate * aWorkerPrivate)637   void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
638     MOZ_ASSERT(aWorkerPrivate);
639     aWorkerPrivate->AssertIsOnWorkerThread();
640   }
641 
642   // This method is called in the main-thread.
643   virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
644                           WorkerPrivate* aWorkerPrivate,
645                           nsPIDOMWindowOuter* aOuterWindow,
646                           nsPIDOMWindowInner* aInnerWindow) = 0;
647 
ForMessaging() const648   bool ForMessaging() const override { return true; }
649 
650   RefPtr<MainThreadConsoleData> mConsoleData;
651 };
652 
653 // This runnable appends a CallData object into the Console queue running on
654 // the main-thread.
655 class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
656  public:
ConsoleCallDataWorkerRunnable(Console * aConsole,ConsoleCallData * aCallData)657   ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
658       : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
659     MOZ_ASSERT(aCallData);
660     mCallData->AssertIsOnOwningThread();
661   }
662 
663  private:
664   ~ConsoleCallDataWorkerRunnable() override = default;
665 
RunConsole(JSContext * aCx,nsIGlobalObject * aGlobal,WorkerPrivate * aWorkerPrivate,nsPIDOMWindowOuter * aOuterWindow,nsPIDOMWindowInner * aInnerWindow)666   void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
667                   WorkerPrivate* aWorkerPrivate,
668                   nsPIDOMWindowOuter* aOuterWindow,
669                   nsPIDOMWindowInner* aInnerWindow) override {
670     MOZ_ASSERT(aGlobal);
671     MOZ_ASSERT(aWorkerPrivate);
672     AssertIsOnMainThread();
673 
674     // The windows have to run in parallel.
675     MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
676 
677     if (aOuterWindow) {
678       mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
679     } else {
680       ConsoleStackEntry frame;
681       if (mCallData->mTopStackFrame) {
682         frame = *mCallData->mTopStackFrame;
683       }
684 
685       nsString id = frame.mFilename;
686       nsString innerID;
687       if (aWorkerPrivate->IsSharedWorker()) {
688         innerID = u"SharedWorker"_ns;
689       } else if (aWorkerPrivate->IsServiceWorker()) {
690         innerID = u"ServiceWorker"_ns;
691         // Use scope as ID so the webconsole can decide if the message should
692         // show up per tab
693         CopyASCIItoUTF16(aWorkerPrivate->ServiceWorkerScope(), id);
694       } else {
695         innerID = u"Worker"_ns;
696       }
697 
698       mCallData->SetIDs(id, innerID);
699     }
700 
701     mClonedData.mGlobal = aGlobal;
702 
703     ProcessCallData(aCx, mConsoleData, mCallData);
704 
705     mClonedData.mGlobal = nullptr;
706   }
707 
708   RefPtr<ConsoleCallData> mCallData;
709 };
710 
711 // This runnable calls ProfileMethod() on the console on the main-thread.
712 class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable {
713  public:
Create(JSContext * aCx,Console * aConsole,Console::MethodName aName,const nsAString & aAction,const Sequence<JS::Value> & aArguments)714   static already_AddRefed<ConsoleProfileWorkletRunnable> Create(
715       JSContext* aCx, Console* aConsole, Console::MethodName aName,
716       const nsAString& aAction, const Sequence<JS::Value>& aArguments) {
717     WorkletThread::AssertIsOnWorkletThread();
718 
719     RefPtr<ConsoleProfileWorkletRunnable> runnable =
720         new ConsoleProfileWorkletRunnable(aConsole, aName, aAction);
721 
722     if (!runnable->WriteArguments(aCx, aArguments)) {
723       return nullptr;
724     }
725 
726     return runnable.forget();
727   }
728 
729  private:
ConsoleProfileWorkletRunnable(Console * aConsole,Console::MethodName aName,const nsAString & aAction)730   ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName,
731                                 const nsAString& aAction)
732       : ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) {
733     MOZ_ASSERT(aConsole);
734   }
735 
Run()736   NS_IMETHOD Run() override {
737     AssertIsOnMainThread();
738 
739     AutoJSAPI jsapi;
740     jsapi.Init();
741     JSContext* cx = jsapi.cx();
742 
743     JSObject* sandbox =
744         mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
745     JS::Rooted<JSObject*> global(cx, sandbox);
746     if (NS_WARN_IF(!global)) {
747       return NS_ERROR_FAILURE;
748     }
749 
750     // The CreateSandbox call returns a proxy to the actual sandbox object. We
751     // don't need a proxy here.
752     global = js::UncheckedUnwrap(global);
753 
754     JSAutoRealm ar(cx, global);
755 
756     // We don't need to set a parent object in mCallData bacause there are not
757     // DOM objects exposed to worklet.
758     ProcessProfileData(cx, mName, mAction);
759 
760     return NS_OK;
761   }
762 
763   Console::MethodName mName;
764   nsString mAction;
765 };
766 
767 // This runnable calls ProfileMethod() on the console on the main-thread.
768 class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
769  public:
ConsoleProfileWorkerRunnable(Console * aConsole,Console::MethodName aName,const nsAString & aAction)770   ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
771                                const nsAString& aAction)
772       : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) {
773     MOZ_ASSERT(aConsole);
774   }
775 
776  private:
RunConsole(JSContext * aCx,nsIGlobalObject * aGlobal,WorkerPrivate * aWorkerPrivate,nsPIDOMWindowOuter * aOuterWindow,nsPIDOMWindowInner * aInnerWindow)777   void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
778                   WorkerPrivate* aWorkerPrivate,
779                   nsPIDOMWindowOuter* aOuterWindow,
780                   nsPIDOMWindowInner* aInnerWindow) override {
781     AssertIsOnMainThread();
782     MOZ_ASSERT(aGlobal);
783 
784     mClonedData.mGlobal = aGlobal;
785 
786     ProcessProfileData(aCx, mName, mAction);
787 
788     mClonedData.mGlobal = nullptr;
789   }
790 
791   Console::MethodName mName;
792   nsString mAction;
793 };
794 
795 NS_IMPL_CYCLE_COLLECTION_MULTI_ZONE_JSHOLDER_CLASS(Console)
796 
797 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
798   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
799   NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
800   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
801   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
802   tmp->Shutdown();
803 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
804 
805 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
806   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
807   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
808   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
809 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
810 
811 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
812   for (uint32_t i = 0; i < tmp->mArgumentStorage.length(); ++i) {
813     tmp->mArgumentStorage[i].Trace(aCallbacks, aClosure);
814   }
815 NS_IMPL_CYCLE_COLLECTION_TRACE_END
816 
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)817 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
818 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
819 
820 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
821   NS_INTERFACE_MAP_ENTRY(nsIObserver)
822   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
823   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
824 NS_INTERFACE_MAP_END
825 
826 /* static */
827 already_AddRefed<Console> Console::Create(JSContext* aCx,
828                                           nsPIDOMWindowInner* aWindow,
829                                           ErrorResult& aRv) {
830   MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
831 
832   uint64_t outerWindowID = 0;
833   uint64_t innerWindowID = 0;
834 
835   if (aWindow) {
836     innerWindowID = aWindow->WindowID();
837 
838     // Without outerwindow any console message coming from this object will not
839     // shown in the devtools webconsole. But this should be fine because
840     // probably we are shutting down, or the window is CCed/GCed.
841     nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
842     if (outerWindow) {
843       outerWindowID = outerWindow->WindowID();
844     }
845   }
846 
847   RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow),
848                                         outerWindowID, innerWindowID);
849   console->Initialize(aRv);
850   if (NS_WARN_IF(aRv.Failed())) {
851     return nullptr;
852   }
853 
854   return console.forget();
855 }
856 
857 /* static */
CreateForWorklet(JSContext * aCx,nsIGlobalObject * aGlobal,uint64_t aOuterWindowID,uint64_t aInnerWindowID,ErrorResult & aRv)858 already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
859                                                     nsIGlobalObject* aGlobal,
860                                                     uint64_t aOuterWindowID,
861                                                     uint64_t aInnerWindowID,
862                                                     ErrorResult& aRv) {
863   WorkletThread::AssertIsOnWorkletThread();
864 
865   RefPtr<Console> console =
866       new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID);
867   console->Initialize(aRv);
868   if (NS_WARN_IF(aRv.Failed())) {
869     return nullptr;
870   }
871 
872   return console.forget();
873 }
874 
Console(JSContext * aCx,nsIGlobalObject * aGlobal,uint64_t aOuterWindowID,uint64_t aInnerWindowID)875 Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
876                  uint64_t aOuterWindowID, uint64_t aInnerWindowID)
877     : mGlobal(aGlobal),
878       mOuterID(aOuterWindowID),
879       mInnerID(aInnerWindowID),
880       mDumpToStdout(false),
881       mChromeInstance(false),
882       mMaxLogLevel(ConsoleLogLevel::All),
883       mStatus(eUnknown),
884       mCreationTimeStamp(TimeStamp::Now()) {
885   // Let's enable the dumping to stdout by default for chrome.
886   if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
887     mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
888   } else {
889     mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
890   }
891 
892   mozilla::HoldJSObjects(this);
893 }
894 
~Console()895 Console::~Console() {
896   AssertIsOnOwningThread();
897   Shutdown();
898   mozilla::DropJSObjects(this);
899 }
900 
Initialize(ErrorResult & aRv)901 void Console::Initialize(ErrorResult& aRv) {
902   AssertIsOnOwningThread();
903   MOZ_ASSERT(mStatus == eUnknown);
904 
905   if (NS_IsMainThread()) {
906     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
907     if (NS_WARN_IF(!obs)) {
908       aRv.Throw(NS_ERROR_FAILURE);
909       return;
910     }
911 
912     if (mInnerID) {
913       aRv = obs->AddObserver(this, "inner-window-destroyed", true);
914       if (NS_WARN_IF(aRv.Failed())) {
915         return;
916       }
917     }
918 
919     aRv = obs->AddObserver(this, "memory-pressure", true);
920     if (NS_WARN_IF(aRv.Failed())) {
921       return;
922     }
923   }
924 
925   mStatus = eInitialized;
926 }
927 
Shutdown()928 void Console::Shutdown() {
929   AssertIsOnOwningThread();
930 
931   if (mStatus == eUnknown || mStatus == eShuttingDown) {
932     return;
933   }
934 
935   if (NS_IsMainThread()) {
936     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
937     if (obs) {
938       obs->RemoveObserver(this, "inner-window-destroyed");
939       obs->RemoveObserver(this, "memory-pressure");
940     }
941   }
942 
943   mTimerRegistry.Clear();
944   mCounterRegistry.Clear();
945 
946   ClearStorage();
947   mCallDataStorage.Clear();
948 
949   mStatus = eShuttingDown;
950 }
951 
952 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)953 Console::Observe(nsISupports* aSubject, const char* aTopic,
954                  const char16_t* aData) {
955   AssertIsOnMainThread();
956 
957   if (!strcmp(aTopic, "inner-window-destroyed")) {
958     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
959     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
960 
961     uint64_t innerID;
962     nsresult rv = wrapper->GetData(&innerID);
963     NS_ENSURE_SUCCESS(rv, rv);
964 
965     if (innerID == mInnerID) {
966       Shutdown();
967     }
968 
969     return NS_OK;
970   }
971 
972   if (!strcmp(aTopic, "memory-pressure")) {
973     ClearStorage();
974     return NS_OK;
975   }
976 
977   return NS_OK;
978 }
979 
ClearStorage()980 void Console::ClearStorage() {
981   mCallDataStorage.Clear();
982   mArgumentStorage.clearAndFree();
983 }
984 
985 #define METHOD(name, string)                                          \
986   /* static */ void Console::name(const GlobalObject& aGlobal,        \
987                                   const Sequence<JS::Value>& aData) { \
988     Method(aGlobal, Method##name, nsLiteralString(string), aData);    \
989   }
990 
991 METHOD(Log, u"log")
992 METHOD(Info, u"info")
993 METHOD(Warn, u"warn")
994 METHOD(Error, u"error")
995 METHOD(Exception, u"exception")
996 METHOD(Debug, u"debug")
997 METHOD(Table, u"table")
998 METHOD(Trace, u"trace")
999 
1000 // Displays an interactive listing of all the properties of an object.
1001 METHOD(Dir, u"dir");
1002 METHOD(Dirxml, u"dirxml");
1003 
1004 METHOD(Group, u"group")
1005 METHOD(GroupCollapsed, u"groupCollapsed")
1006 
1007 #undef METHOD
1008 
1009 /* static */
Clear(const GlobalObject & aGlobal)1010 void Console::Clear(const GlobalObject& aGlobal) {
1011   const Sequence<JS::Value> data;
1012   Method(aGlobal, MethodClear, u"clear"_ns, data);
1013 }
1014 
1015 /* static */
GroupEnd(const GlobalObject & aGlobal)1016 void Console::GroupEnd(const GlobalObject& aGlobal) {
1017   const Sequence<JS::Value> data;
1018   Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data);
1019 }
1020 
1021 /* static */
Time(const GlobalObject & aGlobal,const nsAString & aLabel)1022 void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
1023   StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns);
1024 }
1025 
1026 /* static */
TimeEnd(const GlobalObject & aGlobal,const nsAString & aLabel)1027 void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
1028   StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
1029                u"timeEnd"_ns);
1030 }
1031 
1032 /* static */
TimeLog(const GlobalObject & aGlobal,const nsAString & aLabel,const Sequence<JS::Value> & aData)1033 void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
1034                       const Sequence<JS::Value>& aData) {
1035   StringMethod(aGlobal, aLabel, aData, MethodTimeLog, u"timeLog"_ns);
1036 }
1037 
1038 /* static */
StringMethod(const GlobalObject & aGlobal,const nsAString & aLabel,const Sequence<JS::Value> & aData,MethodName aMethodName,const nsAString & aMethodString)1039 void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
1040                            const Sequence<JS::Value>& aData,
1041                            MethodName aMethodName,
1042                            const nsAString& aMethodString) {
1043   RefPtr<Console> console = GetConsole(aGlobal);
1044   if (!console) {
1045     return;
1046   }
1047 
1048   console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
1049                                 aMethodString);
1050 }
1051 
StringMethodInternal(JSContext * aCx,const nsAString & aLabel,const Sequence<JS::Value> & aData,MethodName aMethodName,const nsAString & aMethodString)1052 void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
1053                                    const Sequence<JS::Value>& aData,
1054                                    MethodName aMethodName,
1055                                    const nsAString& aMethodString) {
1056   ConsoleCommon::ClearException ce(aCx);
1057 
1058   Sequence<JS::Value> data;
1059   SequenceRooter<JS::Value> rooter(aCx, &data);
1060 
1061   JS::Rooted<JS::Value> value(aCx);
1062   if (!dom::ToJSValue(aCx, aLabel, &value)) {
1063     return;
1064   }
1065 
1066   if (!data.AppendElement(value, fallible)) {
1067     return;
1068   }
1069 
1070   for (uint32_t i = 0; i < aData.Length(); ++i) {
1071     if (!data.AppendElement(aData[i], fallible)) {
1072       return;
1073     }
1074   }
1075 
1076   MethodInternal(aCx, aMethodName, aMethodString, data);
1077 }
1078 
1079 /* static */
TimeStamp(const GlobalObject & aGlobal,const JS::Handle<JS::Value> aData)1080 void Console::TimeStamp(const GlobalObject& aGlobal,
1081                         const JS::Handle<JS::Value> aData) {
1082   JSContext* cx = aGlobal.Context();
1083 
1084   ConsoleCommon::ClearException ce(cx);
1085 
1086   Sequence<JS::Value> data;
1087   SequenceRooter<JS::Value> rooter(cx, &data);
1088 
1089   if (aData.isString() && !data.AppendElement(aData, fallible)) {
1090     return;
1091   }
1092 
1093   Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data);
1094 }
1095 
1096 /* static */
Profile(const GlobalObject & aGlobal,const Sequence<JS::Value> & aData)1097 void Console::Profile(const GlobalObject& aGlobal,
1098                       const Sequence<JS::Value>& aData) {
1099   ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData);
1100 }
1101 
1102 /* static */
ProfileEnd(const GlobalObject & aGlobal,const Sequence<JS::Value> & aData)1103 void Console::ProfileEnd(const GlobalObject& aGlobal,
1104                          const Sequence<JS::Value>& aData) {
1105   ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData);
1106 }
1107 
1108 /* static */
ProfileMethod(const GlobalObject & aGlobal,MethodName aName,const nsAString & aAction,const Sequence<JS::Value> & aData)1109 void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
1110                             const nsAString& aAction,
1111                             const Sequence<JS::Value>& aData) {
1112   RefPtr<Console> console = GetConsole(aGlobal);
1113   if (!console) {
1114     return;
1115   }
1116 
1117   JSContext* cx = aGlobal.Context();
1118   console->ProfileMethodInternal(cx, aName, aAction, aData);
1119 }
1120 
IsEnabled(JSContext * aCx) const1121 bool Console::IsEnabled(JSContext* aCx) const {
1122   // Console is always enabled if it is a custom Chrome-Only instance.
1123   if (mChromeInstance) {
1124     return true;
1125   }
1126 
1127   // Make all Console API no-op if DevTools aren't enabled.
1128   return StaticPrefs::devtools_enabled();
1129 }
1130 
ProfileMethodInternal(JSContext * aCx,MethodName aMethodName,const nsAString & aAction,const Sequence<JS::Value> & aData)1131 void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
1132                                     const nsAString& aAction,
1133                                     const Sequence<JS::Value>& aData) {
1134   if (!IsEnabled(aCx)) {
1135     return;
1136   }
1137 
1138   if (!ShouldProceed(aMethodName)) {
1139     return;
1140   }
1141 
1142   MaybeExecuteDumpFunction(aCx, aAction, aData, nullptr);
1143 
1144   if (WorkletThread::IsOnWorkletThread()) {
1145     RefPtr<ConsoleProfileWorkletRunnable> runnable =
1146         ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
1147                                               aData);
1148     if (!runnable) {
1149       return;
1150     }
1151 
1152     NS_DispatchToMainThread(runnable.forget());
1153     return;
1154   }
1155 
1156   if (!NS_IsMainThread()) {
1157     // Here we are in a worker thread.
1158     RefPtr<ConsoleProfileWorkerRunnable> runnable =
1159         new ConsoleProfileWorkerRunnable(this, aMethodName, aAction);
1160 
1161     runnable->Dispatch(aCx, aData);
1162     return;
1163   }
1164 
1165   ProfileMethodMainthread(aCx, aAction, aData);
1166 }
1167 
1168 // static
ProfileMethodMainthread(JSContext * aCx,const nsAString & aAction,const Sequence<JS::Value> & aData)1169 void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction,
1170                                       const Sequence<JS::Value>& aData) {
1171   MOZ_ASSERT(NS_IsMainThread());
1172   ConsoleCommon::ClearException ce(aCx);
1173 
1174   RootedDictionary<ConsoleProfileEvent> event(aCx);
1175   event.mAction = aAction;
1176   event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1177 
1178   event.mArguments.Construct();
1179   Sequence<JS::Value>& sequence = event.mArguments.Value();
1180 
1181   for (uint32_t i = 0; i < aData.Length(); ++i) {
1182     if (!sequence.AppendElement(aData[i], fallible)) {
1183       return;
1184     }
1185   }
1186 
1187   JS::Rooted<JS::Value> eventValue(aCx);
1188   if (!ToJSValue(aCx, event, &eventValue)) {
1189     return;
1190   }
1191 
1192   JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1193   MOZ_ASSERT(eventObj);
1194 
1195   if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1196                          JSPROP_ENUMERATE)) {
1197     return;
1198   }
1199 
1200   nsIXPConnect* xpc = nsContentUtils::XPConnect();
1201   nsCOMPtr<nsISupports> wrapper;
1202   const nsIID& iid = NS_GET_IID(nsISupports);
1203 
1204   if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
1205     return;
1206   }
1207 
1208   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1209   if (obs) {
1210     obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1211   }
1212 }
1213 
1214 /* static */
Assert(const GlobalObject & aGlobal,bool aCondition,const Sequence<JS::Value> & aData)1215 void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1216                      const Sequence<JS::Value>& aData) {
1217   if (!aCondition) {
1218     Method(aGlobal, MethodAssert, u"assert"_ns, aData);
1219   }
1220 }
1221 
1222 /* static */
Count(const GlobalObject & aGlobal,const nsAString & aLabel)1223 void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
1224   StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
1225                u"count"_ns);
1226 }
1227 
1228 /* static */
CountReset(const GlobalObject & aGlobal,const nsAString & aLabel)1229 void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
1230   StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
1231                u"countReset"_ns);
1232 }
1233 
1234 namespace {
1235 
StackFrameToStackEntry(JSContext * aCx,nsIStackFrame * aStackFrame,ConsoleStackEntry & aStackEntry)1236 void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
1237                             ConsoleStackEntry& aStackEntry) {
1238   MOZ_ASSERT(aStackFrame);
1239 
1240   aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
1241 
1242   aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx);
1243   aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx);
1244   aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx);
1245 
1246   aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
1247 
1248   nsString cause;
1249   aStackFrame->GetAsyncCause(aCx, cause);
1250   if (!cause.IsEmpty()) {
1251     aStackEntry.mAsyncCause.Construct(cause);
1252   }
1253 }
1254 
ReifyStack(JSContext * aCx,nsIStackFrame * aStack,nsTArray<ConsoleStackEntry> & aRefiedStack)1255 void ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
1256                 nsTArray<ConsoleStackEntry>& aRefiedStack) {
1257   nsCOMPtr<nsIStackFrame> stack(aStack);
1258 
1259   while (stack) {
1260     ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1261     StackFrameToStackEntry(aCx, stack, data);
1262 
1263     nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
1264 
1265     if (!caller) {
1266       caller = stack->GetAsyncCaller(aCx);
1267     }
1268     stack.swap(caller);
1269   }
1270 }
1271 
1272 }  // anonymous namespace
1273 
1274 // Queue a call to a console method. See the CALL_DELAY constant.
1275 /* static */
Method(const GlobalObject & aGlobal,MethodName aMethodName,const nsAString & aMethodString,const Sequence<JS::Value> & aData)1276 void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
1277                      const nsAString& aMethodString,
1278                      const Sequence<JS::Value>& aData) {
1279   RefPtr<Console> console = GetConsole(aGlobal);
1280   if (!console) {
1281     return;
1282   }
1283 
1284   console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData);
1285 }
1286 
MethodInternal(JSContext * aCx,MethodName aMethodName,const nsAString & aMethodString,const Sequence<JS::Value> & aData)1287 void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
1288                              const nsAString& aMethodString,
1289                              const Sequence<JS::Value>& aData) {
1290   if (!IsEnabled(aCx)) {
1291     return;
1292   }
1293 
1294   if (!ShouldProceed(aMethodName)) {
1295     return;
1296   }
1297 
1298   AssertIsOnOwningThread();
1299 
1300   ConsoleCommon::ClearException ce(aCx);
1301 
1302   RefPtr<ConsoleCallData> callData =
1303       new ConsoleCallData(aMethodName, aMethodString, this);
1304   if (!StoreCallData(aCx, callData, aData)) {
1305     return;
1306   }
1307 
1308   OriginAttributes oa;
1309 
1310   if (NS_IsMainThread()) {
1311     if (mGlobal) {
1312       // Save the principal's OriginAttributes in the console event data
1313       // so that we will be able to filter messages by origin attributes.
1314       nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
1315       if (NS_WARN_IF(!sop)) {
1316         return;
1317       }
1318 
1319       nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1320       if (NS_WARN_IF(!principal)) {
1321         return;
1322       }
1323 
1324       oa = principal->OriginAttributesRef();
1325       callData->SetAddonId(principal);
1326 
1327 #ifdef DEBUG
1328       if (!principal->IsSystemPrincipal()) {
1329         nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
1330         if (webNav) {
1331           nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1332           MOZ_ASSERT(loadContext);
1333 
1334           bool pb;
1335           if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
1336             MOZ_ASSERT(pb == !!oa.mPrivateBrowsingId);
1337           }
1338         }
1339       }
1340 #endif
1341     }
1342   } else if (WorkletThread::IsOnWorkletThread()) {
1343     nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
1344     MOZ_ASSERT(global);
1345     oa = global->Impl()->OriginAttributesRef();
1346   } else {
1347     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1348     MOZ_ASSERT(workerPrivate);
1349     oa = workerPrivate->GetOriginAttributes();
1350   }
1351 
1352   callData->SetOriginAttributes(oa);
1353 
1354   JS::StackCapture captureMode =
1355       ShouldIncludeStackTrace(aMethodName)
1356           ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH))
1357           : JS::StackCapture(JS::FirstSubsumedFrame(aCx));
1358   nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode));
1359 
1360   if (stack) {
1361     callData->mTopStackFrame.emplace();
1362     StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
1363   }
1364 
1365   if (NS_IsMainThread()) {
1366     callData->mStack = stack;
1367   } else {
1368     // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1369     // before we post our runnable to the main thread.
1370     callData->mReifiedStack.emplace();
1371     ReifyStack(aCx, stack, *callData->mReifiedStack);
1372   }
1373 
1374   DOMHighResTimeStamp monotonicTimer;
1375 
1376   // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1377   if ((aMethodName == MethodTime || aMethodName == MethodTimeLog ||
1378        aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) &&
1379       !MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
1380     return;
1381   }
1382 
1383   if (aMethodName == MethodTime && !aData.IsEmpty()) {
1384     callData->mStartTimerStatus =
1385         StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel,
1386                    &callData->mStartTimerValue);
1387   }
1388 
1389   else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
1390     callData->mLogTimerStatus =
1391         LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1392                  &callData->mLogTimerDuration, true /* Cancel timer */);
1393   }
1394 
1395   else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
1396     callData->mLogTimerStatus =
1397         LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1398                  &callData->mLogTimerDuration, false /* Cancel timer */);
1399   }
1400 
1401   else if (aMethodName == MethodCount) {
1402     callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
1403     if (!callData->mCountValue) {
1404       return;
1405     }
1406   }
1407 
1408   else if (aMethodName == MethodCountReset) {
1409     callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
1410     if (callData->mCountLabel.IsEmpty()) {
1411       return;
1412     }
1413   }
1414 
1415   // Before processing this CallData differently, it's time to call the dump
1416   // function.
1417   if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
1418     MaybeExecuteDumpFunction(aCx, aMethodString, aData, stack);
1419   } else if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) &&
1420              !aData.IsEmpty()) {
1421     MaybeExecuteDumpFunctionForTime(aCx, aMethodName, aMethodString,
1422                                     monotonicTimer, aData[0]);
1423   } else {
1424     MaybeExecuteDumpFunction(aCx, aMethodString, aData, nullptr);
1425   }
1426 
1427   if (NS_IsMainThread()) {
1428     if (mInnerID) {
1429       callData->SetIDs(mOuterID, mInnerID);
1430     } else if (!mPassedInnerID.IsEmpty()) {
1431       callData->SetIDs(u"jsm"_ns, mPassedInnerID);
1432     } else {
1433       nsAutoString filename;
1434       if (callData->mTopStackFrame.isSome()) {
1435         filename = callData->mTopStackFrame->mFilename;
1436       }
1437 
1438       callData->SetIDs(u"jsm"_ns, filename);
1439     }
1440 
1441     GetOrCreateMainThreadData()->ProcessCallData(aCx, callData, aData);
1442 
1443     // Just because we don't want to expose
1444     // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1445     // cleanup the mCallDataStorage:
1446     UnstoreCallData(callData);
1447     return;
1448   }
1449 
1450   if (WorkletThread::IsOnWorkletThread()) {
1451     RefPtr<ConsoleCallDataWorkletRunnable> runnable =
1452         ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData);
1453     if (!runnable) {
1454       return;
1455     }
1456 
1457     NS_DispatchToMainThread(runnable);
1458     return;
1459   }
1460 
1461   // We do this only in workers for now.
1462   NotifyHandler(aCx, aData, callData);
1463 
1464   if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
1465     RefPtr<ConsoleCallDataWorkerRunnable> runnable =
1466         new ConsoleCallDataWorkerRunnable(this, callData);
1467     Unused << NS_WARN_IF(!runnable->Dispatch(aCx, aData));
1468   }
1469 }
1470 
GetOrCreateMainThreadData()1471 MainThreadConsoleData* Console::GetOrCreateMainThreadData() {
1472   AssertIsOnOwningThread();
1473 
1474   if (!mMainThreadData) {
1475     mMainThreadData = new MainThreadConsoleData();
1476   }
1477 
1478   return mMainThreadData;
1479 }
1480 
1481 // We store information to lazily compute the stack in the reserved slots of
1482 // LazyStackGetter.  The first slot always stores a JS object: it's either the
1483 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1484 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1485 // reified the stack yet, or an UndefinedValue() otherwise.
1486 enum { SLOT_STACKOBJ, SLOT_RAW_STACK };
1487 
LazyStackGetter(JSContext * aCx,unsigned aArgc,JS::Value * aVp)1488 bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
1489   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1490   JS::Rooted<JSObject*> callee(aCx, &args.callee());
1491 
1492   JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1493   if (v.isUndefined()) {
1494     // Already reified.
1495     args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1496     return true;
1497   }
1498 
1499   nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1500   nsTArray<ConsoleStackEntry> reifiedStack;
1501   ReifyStack(aCx, stack, reifiedStack);
1502 
1503   JS::Rooted<JS::Value> stackVal(aCx);
1504   if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
1505     return false;
1506   }
1507 
1508   MOZ_ASSERT(stackVal.isObject());
1509 
1510   js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1511   js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1512 
1513   args.rval().set(stackVal);
1514   return true;
1515 }
1516 
ProcessCallData(JSContext * aCx,ConsoleCallData * aData,const Sequence<JS::Value> & aArguments)1517 void MainThreadConsoleData::ProcessCallData(
1518     JSContext* aCx, ConsoleCallData* aData,
1519     const Sequence<JS::Value>& aArguments) {
1520   AssertIsOnMainThread();
1521   MOZ_ASSERT(aData);
1522 
1523   JS::Rooted<JS::Value> eventValue(aCx);
1524 
1525   // We want to create a console event object and pass it to our
1526   // nsIConsoleAPIStorage implementation.  We want to define some accessor
1527   // properties on this object, and those will need to keep an nsIStackFrame
1528   // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
1529   // further, passing untrusted objects to system code is likely to run afoul of
1530   // Object Xrays.  So we want to wrap in a system-principal scope here.  But
1531   // which one?  We could cheat and try to get the underlying JSObject* of
1532   // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
1533   // with explicit permission from the XPConnect module owner.  If you're
1534   // tempted to do that anywhere else, talk to said module owner first.
1535 
1536   // aCx and aArguments are in the same compartment.
1537   JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
1538   if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
1539           aCx, aArguments, targetScope, &eventValue, aData, &mGroupStack))) {
1540     return;
1541   }
1542 
1543   if (!mStorage) {
1544     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1545   }
1546 
1547   if (!mStorage) {
1548     NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1549     return;
1550   }
1551 
1552   nsAutoString innerID, outerID;
1553 
1554   MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1555   if (aData->mIDType == ConsoleCallData::eString) {
1556     outerID = aData->mOuterIDString;
1557     innerID = aData->mInnerIDString;
1558   } else {
1559     MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1560     outerID.AppendInt(aData->mOuterIDNumber);
1561     innerID.AppendInt(aData->mInnerIDNumber);
1562   }
1563 
1564   if (aData->mMethodName == Console::MethodClear) {
1565     DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
1566     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
1567   }
1568 
1569   if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
1570     NS_WARNING("Failed to record a console event.");
1571   }
1572 }
1573 
1574 /* static */
PopulateConsoleNotificationInTheTargetScope(JSContext * aCx,const Sequence<JS::Value> & aArguments,JS::Handle<JSObject * > aTargetScope,JS::MutableHandle<JS::Value> aEventValue,ConsoleCallData * aData,nsTArray<nsString> * aGroupStack)1575 bool Console::PopulateConsoleNotificationInTheTargetScope(
1576     JSContext* aCx, const Sequence<JS::Value>& aArguments,
1577     JS::Handle<JSObject*> aTargetScope,
1578     JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData,
1579     nsTArray<nsString>* aGroupStack) {
1580   MOZ_ASSERT(aCx);
1581   MOZ_ASSERT(aData);
1582   MOZ_ASSERT(aTargetScope);
1583   MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
1584 
1585   ConsoleStackEntry frame;
1586   if (aData->mTopStackFrame) {
1587     frame = *aData->mTopStackFrame;
1588   }
1589 
1590   ConsoleCommon::ClearException ce(aCx);
1591   RootedDictionary<ConsoleEvent> event(aCx);
1592 
1593   event.mAddonId = aData->mAddonId;
1594 
1595   event.mID.Construct();
1596   event.mInnerID.Construct();
1597 
1598   event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1599 
1600   if (aData->mIDType == ConsoleCallData::eString) {
1601     event.mID.Value().SetAsString() = aData->mOuterIDString;
1602     event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1603   } else if (aData->mIDType == ConsoleCallData::eNumber) {
1604     event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
1605     event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
1606   } else {
1607     // aData->mIDType can be eUnknown when we dispatch notifications via
1608     // mConsoleEventNotifier.
1609     event.mID.Value().SetAsUnsignedLongLong() = 0;
1610     event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
1611   }
1612 
1613   event.mConsoleID = aData->mConsoleID;
1614   event.mLevel = aData->mMethodString;
1615   event.mFilename = frame.mFilename;
1616   event.mPrefix = aData->mPrefix;
1617 
1618   nsCOMPtr<nsIURI> filenameURI;
1619   nsAutoCString pass;
1620   if (NS_IsMainThread() &&
1621       NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
1622       NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
1623     nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI =
1624         do_QueryInterface(filenameURI);
1625     nsAutoCString spec;
1626     if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
1627       CopyUTF8toUTF16(spec, event.mFilename);
1628     }
1629   }
1630 
1631   event.mSourceId = frame.mSourceId;
1632   event.mLineNumber = frame.mLineNumber;
1633   event.mColumnNumber = frame.mColumnNumber;
1634   event.mFunctionName = frame.mFunctionName;
1635   event.mTimeStamp = aData->mTimeStamp;
1636   event.mPrivate = !!aData->mOriginAttributes.mPrivateBrowsingId;
1637 
1638   switch (aData->mMethodName) {
1639     case MethodLog:
1640     case MethodInfo:
1641     case MethodWarn:
1642     case MethodError:
1643     case MethodException:
1644     case MethodDebug:
1645     case MethodAssert:
1646     case MethodGroup:
1647     case MethodGroupCollapsed:
1648     case MethodTrace:
1649       event.mArguments.Construct();
1650       event.mStyles.Construct();
1651       if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1652                                        event.mArguments.Value(),
1653                                        event.mStyles.Value()))) {
1654         return false;
1655       }
1656 
1657       break;
1658 
1659     default:
1660       event.mArguments.Construct();
1661       if (NS_WARN_IF(
1662               !event.mArguments.Value().AppendElements(aArguments, fallible))) {
1663         return false;
1664       }
1665   }
1666 
1667   if (aData->mMethodName == MethodGroup ||
1668       aData->mMethodName == MethodGroupCollapsed) {
1669     ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName,
1670                              aGroupStack);
1671   }
1672 
1673   else if (aData->mMethodName == MethodGroupEnd) {
1674     if (!UnstoreGroupName(event.mGroupName, aGroupStack)) {
1675       return false;
1676     }
1677   }
1678 
1679   else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
1680     event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
1681                                          aData->mStartTimerStatus);
1682   }
1683 
1684   else if ((aData->mMethodName == MethodTimeEnd ||
1685             aData->mMethodName == MethodTimeLog) &&
1686            !aArguments.IsEmpty()) {
1687     event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
1688                                             aData->mLogTimerDuration,
1689                                             aData->mLogTimerStatus);
1690   }
1691 
1692   else if (aData->mMethodName == MethodCount ||
1693            aData->mMethodName == MethodCountReset) {
1694     event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
1695                                                       aData->mCountValue);
1696   }
1697 
1698   JSAutoRealm ar2(aCx, aTargetScope);
1699 
1700   if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
1701     return false;
1702   }
1703 
1704   JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
1705   if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
1706                                     JSPROP_ENUMERATE))) {
1707     return false;
1708   }
1709 
1710   if (ShouldIncludeStackTrace(aData->mMethodName)) {
1711     // Now define the "stacktrace" property on eventObj.  There are two cases
1712     // here.  Either we came from a worker and have a reified stack, or we want
1713     // to define a getter that will lazily reify the stack.
1714     if (aData->mReifiedStack) {
1715       JS::Rooted<JS::Value> stacktrace(aCx);
1716       if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
1717           NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
1718                                         JSPROP_ENUMERATE))) {
1719         return false;
1720       }
1721     } else {
1722       JSFunction* fun =
1723           js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace");
1724       if (NS_WARN_IF(!fun)) {
1725         return false;
1726       }
1727 
1728       JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
1729 
1730       // We want to store our stack in the function and have it stay alive.  But
1731       // we also need sane access to the C++ nsIStackFrame.  So store both a JS
1732       // wrapper and the raw pointer: the former will keep the latter alive.
1733       JS::Rooted<JS::Value> stackVal(aCx);
1734       nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack, &stackVal);
1735       if (NS_WARN_IF(NS_FAILED(rv))) {
1736         return false;
1737       }
1738 
1739       js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1740       js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1741                                     JS::PrivateValue(aData->mStack.get()));
1742 
1743       if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", funObj,
1744                                         nullptr, JSPROP_ENUMERATE))) {
1745         return false;
1746       }
1747     }
1748   }
1749 
1750   return true;
1751 }
1752 
1753 namespace {
1754 
1755 // Helper method for ProcessArguments. Flushes output, if non-empty, to
1756 // aSequence.
FlushOutput(JSContext * aCx,Sequence<JS::Value> & aSequence,nsString & aOutput)1757 bool FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence,
1758                  nsString& aOutput) {
1759   if (!aOutput.IsEmpty()) {
1760     JS::Rooted<JSString*> str(
1761         aCx, JS_NewUCStringCopyN(aCx, aOutput.get(), aOutput.Length()));
1762     if (NS_WARN_IF(!str)) {
1763       return false;
1764     }
1765 
1766     if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
1767       return false;
1768     }
1769 
1770     aOutput.Truncate();
1771   }
1772 
1773   return true;
1774 }
1775 
1776 }  // namespace
1777 
MakeFormatString(nsCString & aFormat,int32_t aInteger,int32_t aMantissa,char aCh)1778 static void MakeFormatString(nsCString& aFormat, int32_t aInteger,
1779                              int32_t aMantissa, char aCh) {
1780   aFormat.Append('%');
1781   if (aInteger >= 0) {
1782     aFormat.AppendInt(aInteger);
1783   }
1784 
1785   if (aMantissa >= 0) {
1786     aFormat.Append('.');
1787     aFormat.AppendInt(aMantissa);
1788   }
1789 
1790   aFormat.Append(aCh);
1791 }
1792 
1793 // If the first JS::Value of the array is a string, this method uses it to
1794 // format a string. The supported sequences are:
1795 //   %s    - string
1796 //   %d,%i - integer
1797 //   %f    - double
1798 //   %o,%O - a JS object.
1799 //   %c    - style string.
1800 // The output is an array where any object is a separated item, the rest is
1801 // unified in a format string.
1802 // Example if the input is:
1803 //   "string: %s, integer: %d, object: %o, double: %d", 's', 1, window, 0.9
1804 // The output will be:
1805 //   [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
1806 //
1807 // The aStyles array is populated with the style strings that the function
1808 // finds based the format string. The index of the styles matches the indexes
1809 // of elements that need the custom styling from aSequence. For elements with
1810 // no custom styling the array is padded with null elements.
ProcessArguments(JSContext * aCx,const Sequence<JS::Value> & aData,Sequence<JS::Value> & aSequence,Sequence<nsString> & aStyles)1811 static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
1812                              Sequence<JS::Value>& aSequence,
1813                              Sequence<nsString>& aStyles) {
1814   // This method processes the arguments as format strings (%d, %i, %s...)
1815   // only if the first element of them is a valid and not-empty string.
1816 
1817   if (aData.IsEmpty()) {
1818     return true;
1819   }
1820 
1821   if (aData.Length() == 1 || !aData[0].isString()) {
1822     return aSequence.AppendElements(aData, fallible);
1823   }
1824 
1825   JS::Rooted<JS::Value> format(aCx, aData[0]);
1826   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1827   if (NS_WARN_IF(!jsString)) {
1828     return false;
1829   }
1830 
1831   nsAutoJSString string;
1832   if (NS_WARN_IF(!string.init(aCx, jsString))) {
1833     return false;
1834   }
1835 
1836   if (string.IsEmpty()) {
1837     return aSequence.AppendElements(aData, fallible);
1838   }
1839 
1840   nsString::const_iterator start, end;
1841   string.BeginReading(start);
1842   string.EndReading(end);
1843 
1844   nsString output;
1845   uint32_t index = 1;
1846 
1847   while (start != end) {
1848     if (*start != '%') {
1849       output.Append(*start);
1850       ++start;
1851       continue;
1852     }
1853 
1854     ++start;
1855     if (start == end) {
1856       output.Append('%');
1857       break;
1858     }
1859 
1860     if (*start == '%') {
1861       output.Append(*start);
1862       ++start;
1863       continue;
1864     }
1865 
1866     nsAutoString tmp;
1867     tmp.Append('%');
1868 
1869     int32_t integer = -1;
1870     int32_t mantissa = -1;
1871 
1872     // Let's parse %<number>.<number> for %d and %f
1873     if (*start >= '0' && *start <= '9') {
1874       integer = 0;
1875 
1876       do {
1877         integer = integer * 10 + *start - '0';
1878         tmp.Append(*start);
1879         ++start;
1880       } while (*start >= '0' && *start <= '9' && start != end);
1881     }
1882 
1883     if (start == end) {
1884       output.Append(tmp);
1885       break;
1886     }
1887 
1888     if (*start == '.') {
1889       tmp.Append(*start);
1890       ++start;
1891 
1892       if (start == end) {
1893         output.Append(tmp);
1894         break;
1895       }
1896 
1897       // '.' must be followed by a number.
1898       if (*start < '0' || *start > '9') {
1899         output.Append(tmp);
1900         continue;
1901       }
1902 
1903       mantissa = 0;
1904 
1905       do {
1906         mantissa = mantissa * 10 + *start - '0';
1907         tmp.Append(*start);
1908         ++start;
1909       } while (*start >= '0' && *start <= '9' && start != end);
1910 
1911       if (start == end) {
1912         output.Append(tmp);
1913         break;
1914       }
1915     }
1916 
1917     char ch = *start;
1918     tmp.Append(ch);
1919     ++start;
1920 
1921     switch (ch) {
1922       case 'o':
1923       case 'O': {
1924         if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1925           return false;
1926         }
1927 
1928         JS::Rooted<JS::Value> v(aCx);
1929         if (index < aData.Length()) {
1930           v = aData[index++];
1931         }
1932 
1933         if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
1934           return false;
1935         }
1936 
1937         break;
1938       }
1939 
1940       case 'c': {
1941         // If there isn't any output but there's already a style, then
1942         // discard the previous style and use the next one instead.
1943         if (output.IsEmpty() && !aStyles.IsEmpty()) {
1944           aStyles.RemoveLastElement();
1945         }
1946 
1947         if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1948           return false;
1949         }
1950 
1951         if (index < aData.Length()) {
1952           JS::Rooted<JS::Value> v(aCx, aData[index++]);
1953           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1954           if (NS_WARN_IF(!jsString)) {
1955             return false;
1956           }
1957 
1958           int32_t diff = aSequence.Length() - aStyles.Length();
1959           if (diff > 0) {
1960             for (int32_t i = 0; i < diff; i++) {
1961               if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
1962                 return false;
1963               }
1964             }
1965           }
1966 
1967           nsAutoJSString string;
1968           if (NS_WARN_IF(!string.init(aCx, jsString))) {
1969             return false;
1970           }
1971 
1972           if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
1973             return false;
1974           }
1975         }
1976         break;
1977       }
1978 
1979       case 's':
1980         if (index < aData.Length()) {
1981           JS::Rooted<JS::Value> value(aCx, aData[index++]);
1982           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1983           if (NS_WARN_IF(!jsString)) {
1984             return false;
1985           }
1986 
1987           nsAutoJSString v;
1988           if (NS_WARN_IF(!v.init(aCx, jsString))) {
1989             return false;
1990           }
1991 
1992           output.Append(v);
1993         }
1994         break;
1995 
1996       case 'd':
1997       case 'i':
1998         if (index < aData.Length()) {
1999           JS::Rooted<JS::Value> value(aCx, aData[index++]);
2000 
2001           int32_t v;
2002           if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
2003             return false;
2004           }
2005 
2006           nsCString format;
2007           MakeFormatString(format, integer, mantissa, 'd');
2008           output.AppendPrintf(format.get(), v);
2009         }
2010         break;
2011 
2012       case 'f':
2013         if (index < aData.Length()) {
2014           JS::Rooted<JS::Value> value(aCx, aData[index++]);
2015 
2016           double v;
2017           if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
2018             return false;
2019           }
2020 
2021           // nspr returns "nan", but we want to expose it as "NaN"
2022           if (std::isnan(v)) {
2023             output.AppendFloat(v);
2024           } else {
2025             nsCString format;
2026             MakeFormatString(format, integer, mantissa, 'f');
2027             output.AppendPrintf(format.get(), v);
2028           }
2029         }
2030         break;
2031 
2032       default:
2033         output.Append(tmp);
2034         break;
2035     }
2036   }
2037 
2038   if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
2039     return false;
2040   }
2041 
2042   // Discard trailing style element if there is no output to apply it to.
2043   if (aStyles.Length() > aSequence.Length()) {
2044     aStyles.TruncateLength(aSequence.Length());
2045   }
2046 
2047   // The rest of the array, if unused by the format string.
2048   for (; index < aData.Length(); ++index) {
2049     if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
2050       return false;
2051     }
2052   }
2053 
2054   return true;
2055 }
2056 
2057 // Stringify and Concat all the JS::Value in a single string using ' ' as
2058 // separator. The new group name will be stored in aGroupStack array.
ComposeAndStoreGroupName(JSContext * aCx,const Sequence<JS::Value> & aData,nsAString & aName,nsTArray<nsString> * aGroupStack)2059 static void ComposeAndStoreGroupName(JSContext* aCx,
2060                                      const Sequence<JS::Value>& aData,
2061                                      nsAString& aName,
2062                                      nsTArray<nsString>* aGroupStack) {
2063   StringJoinAppend(
2064       aName, u" "_ns, aData, [aCx](nsAString& dest, const JS::Value& valueRef) {
2065         JS::Rooted<JS::Value> value(aCx, valueRef);
2066         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2067         if (!jsString) {
2068           return;
2069         }
2070 
2071         nsAutoJSString string;
2072         if (!string.init(aCx, jsString)) {
2073           return;
2074         }
2075 
2076         dest.Append(string);
2077       });
2078 
2079   aGroupStack->AppendElement(aName);
2080 }
2081 
2082 // Remove the last group name and return that name. It returns false if
2083 // aGroupStack is empty.
UnstoreGroupName(nsAString & aName,nsTArray<nsString> * aGroupStack)2084 static bool UnstoreGroupName(nsAString& aName,
2085                              nsTArray<nsString>* aGroupStack) {
2086   if (aGroupStack->IsEmpty()) {
2087     return false;
2088   }
2089 
2090   aName = aGroupStack->PopLastElement();
2091   return true;
2092 }
2093 
StartTimer(JSContext * aCx,const JS::Value & aName,DOMHighResTimeStamp aTimestamp,nsAString & aTimerLabel,DOMHighResTimeStamp * aTimerValue)2094 Console::TimerStatus Console::StartTimer(JSContext* aCx, const JS::Value& aName,
2095                                          DOMHighResTimeStamp aTimestamp,
2096                                          nsAString& aTimerLabel,
2097                                          DOMHighResTimeStamp* aTimerValue) {
2098   AssertIsOnOwningThread();
2099   MOZ_ASSERT(aTimerValue);
2100 
2101   *aTimerValue = 0;
2102 
2103   if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
2104     return eTimerMaxReached;
2105   }
2106 
2107   JS::Rooted<JS::Value> name(aCx, aName);
2108   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2109   if (NS_WARN_IF(!jsString)) {
2110     return eTimerJSException;
2111   }
2112 
2113   nsAutoJSString label;
2114   if (NS_WARN_IF(!label.init(aCx, jsString))) {
2115     return eTimerJSException;
2116   }
2117 
2118   aTimerLabel = label;
2119 
2120   if (mTimerRegistry.WithEntryHandle(label, [&](auto&& entry) {
2121         if (entry) {
2122           return true;
2123         }
2124         entry.Insert(aTimestamp);
2125         return false;
2126       })) {
2127     return eTimerAlreadyExists;
2128   }
2129 
2130   *aTimerValue = aTimestamp;
2131   return eTimerDone;
2132 }
2133 
2134 /* static */
CreateStartTimerValue(JSContext * aCx,const nsAString & aTimerLabel,TimerStatus aTimerStatus)2135 JS::Value Console::CreateStartTimerValue(JSContext* aCx,
2136                                          const nsAString& aTimerLabel,
2137                                          TimerStatus aTimerStatus) {
2138   MOZ_ASSERT(aTimerStatus != eTimerUnknown);
2139 
2140   if (aTimerStatus != eTimerDone) {
2141     return CreateTimerError(aCx, aTimerLabel, aTimerStatus);
2142   }
2143 
2144   RootedDictionary<ConsoleTimerStart> timer(aCx);
2145 
2146   timer.mName = aTimerLabel;
2147 
2148   JS::Rooted<JS::Value> value(aCx);
2149   if (!ToJSValue(aCx, timer, &value)) {
2150     return JS::UndefinedValue();
2151   }
2152 
2153   return value;
2154 }
2155 
LogTimer(JSContext * aCx,const JS::Value & aName,DOMHighResTimeStamp aTimestamp,nsAString & aTimerLabel,double * aTimerDuration,bool aCancelTimer)2156 Console::TimerStatus Console::LogTimer(JSContext* aCx, const JS::Value& aName,
2157                                        DOMHighResTimeStamp aTimestamp,
2158                                        nsAString& aTimerLabel,
2159                                        double* aTimerDuration,
2160                                        bool aCancelTimer) {
2161   AssertIsOnOwningThread();
2162   MOZ_ASSERT(aTimerDuration);
2163 
2164   *aTimerDuration = 0;
2165 
2166   JS::Rooted<JS::Value> name(aCx, aName);
2167   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2168   if (NS_WARN_IF(!jsString)) {
2169     return eTimerJSException;
2170   }
2171 
2172   nsAutoJSString key;
2173   if (NS_WARN_IF(!key.init(aCx, jsString))) {
2174     return eTimerJSException;
2175   }
2176 
2177   aTimerLabel = key;
2178 
2179   DOMHighResTimeStamp value = 0;
2180 
2181   if (aCancelTimer) {
2182     if (!mTimerRegistry.Remove(key, &value)) {
2183       NS_WARNING("mTimerRegistry entry not found");
2184       return eTimerDoesntExist;
2185     }
2186   } else {
2187     if (!mTimerRegistry.Get(key, &value)) {
2188       NS_WARNING("mTimerRegistry entry not found");
2189       return eTimerDoesntExist;
2190     }
2191   }
2192 
2193   *aTimerDuration = aTimestamp - value;
2194   return eTimerDone;
2195 }
2196 
2197 /* static */
CreateLogOrEndTimerValue(JSContext * aCx,const nsAString & aLabel,double aDuration,TimerStatus aStatus)2198 JS::Value Console::CreateLogOrEndTimerValue(JSContext* aCx,
2199                                             const nsAString& aLabel,
2200                                             double aDuration,
2201                                             TimerStatus aStatus) {
2202   if (aStatus != eTimerDone) {
2203     return CreateTimerError(aCx, aLabel, aStatus);
2204   }
2205 
2206   RootedDictionary<ConsoleTimerLogOrEnd> timer(aCx);
2207   timer.mName = aLabel;
2208   timer.mDuration = aDuration;
2209 
2210   JS::Rooted<JS::Value> value(aCx);
2211   if (!ToJSValue(aCx, timer, &value)) {
2212     return JS::UndefinedValue();
2213   }
2214 
2215   return value;
2216 }
2217 
2218 /* static */
CreateTimerError(JSContext * aCx,const nsAString & aLabel,TimerStatus aStatus)2219 JS::Value Console::CreateTimerError(JSContext* aCx, const nsAString& aLabel,
2220                                     TimerStatus aStatus) {
2221   MOZ_ASSERT(aStatus != eTimerUnknown && aStatus != eTimerDone);
2222 
2223   RootedDictionary<ConsoleTimerError> error(aCx);
2224 
2225   error.mName = aLabel;
2226 
2227   switch (aStatus) {
2228     case eTimerAlreadyExists:
2229       error.mError.AssignLiteral("timerAlreadyExists");
2230       break;
2231 
2232     case eTimerDoesntExist:
2233       error.mError.AssignLiteral("timerDoesntExist");
2234       break;
2235 
2236     case eTimerJSException:
2237       error.mError.AssignLiteral("timerJSError");
2238       break;
2239 
2240     case eTimerMaxReached:
2241       error.mError.AssignLiteral("maxTimersExceeded");
2242       break;
2243 
2244     default:
2245       MOZ_CRASH("Unsupported status");
2246       break;
2247   }
2248 
2249   JS::Rooted<JS::Value> value(aCx);
2250   if (!ToJSValue(aCx, error, &value)) {
2251     return JS::UndefinedValue();
2252   }
2253 
2254   return value;
2255 }
2256 
IncreaseCounter(JSContext * aCx,const Sequence<JS::Value> & aArguments,nsAString & aCountLabel)2257 uint32_t Console::IncreaseCounter(JSContext* aCx,
2258                                   const Sequence<JS::Value>& aArguments,
2259                                   nsAString& aCountLabel) {
2260   AssertIsOnOwningThread();
2261 
2262   ConsoleCommon::ClearException ce(aCx);
2263 
2264   MOZ_ASSERT(!aArguments.IsEmpty());
2265 
2266   JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2267   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2268   if (!jsString) {
2269     return 0;  // We cannot continue.
2270   }
2271 
2272   nsAutoJSString string;
2273   if (!string.init(aCx, jsString)) {
2274     return 0;  // We cannot continue.
2275   }
2276 
2277   aCountLabel = string;
2278 
2279   const bool maxCountersReached = mCounterRegistry.Count() >= MAX_PAGE_COUNTERS;
2280   return mCounterRegistry.WithEntryHandle(
2281       aCountLabel, [maxCountersReached](auto&& entry) -> uint32_t {
2282         if (entry) {
2283           ++entry.Data();
2284         } else {
2285           if (maxCountersReached) {
2286             return MAX_PAGE_COUNTERS;
2287           }
2288           entry.Insert(1);
2289         }
2290         return entry.Data();
2291       });
2292 }
2293 
ResetCounter(JSContext * aCx,const Sequence<JS::Value> & aArguments,nsAString & aCountLabel)2294 uint32_t Console::ResetCounter(JSContext* aCx,
2295                                const Sequence<JS::Value>& aArguments,
2296                                nsAString& aCountLabel) {
2297   AssertIsOnOwningThread();
2298 
2299   ConsoleCommon::ClearException ce(aCx);
2300 
2301   MOZ_ASSERT(!aArguments.IsEmpty());
2302 
2303   JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2304   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2305   if (!jsString) {
2306     return 0;  // We cannot continue.
2307   }
2308 
2309   nsAutoJSString string;
2310   if (!string.init(aCx, jsString)) {
2311     return 0;  // We cannot continue.
2312   }
2313 
2314   aCountLabel = string;
2315 
2316   if (mCounterRegistry.Remove(aCountLabel)) {
2317     return 0;
2318   }
2319 
2320   // Let's return something different than 0 if the key doesn't exist.
2321   return MAX_PAGE_COUNTERS;
2322 }
2323 
2324 // This method generates a ConsoleCounter dictionary as JS::Value. If
2325 // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
2326 // instead. See IncreaseCounter.
2327 // * aCx - this is the context that will root the returned value.
2328 // * aCountLabel - this label must be what IncreaseCounter received as
2329 //                 aTimerLabel.
2330 // * aCountValue - the return value of IncreaseCounter.
CreateCounterOrResetCounterValue(JSContext * aCx,const nsAString & aCountLabel,uint32_t aCountValue)2331 static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
2332                                                   const nsAString& aCountLabel,
2333                                                   uint32_t aCountValue) {
2334   ConsoleCommon::ClearException ce(aCx);
2335 
2336   if (aCountValue == MAX_PAGE_COUNTERS) {
2337     RootedDictionary<ConsoleCounterError> error(aCx);
2338     error.mLabel = aCountLabel;
2339     error.mError.AssignLiteral("counterDoesntExist");
2340 
2341     JS::Rooted<JS::Value> value(aCx);
2342     if (!ToJSValue(aCx, error, &value)) {
2343       return JS::UndefinedValue();
2344     }
2345 
2346     return value;
2347   }
2348 
2349   RootedDictionary<ConsoleCounter> data(aCx);
2350   data.mLabel = aCountLabel;
2351   data.mCount = aCountValue;
2352 
2353   JS::Rooted<JS::Value> value(aCx);
2354   if (!ToJSValue(aCx, data, &value)) {
2355     return JS::UndefinedValue();
2356   }
2357 
2358   return value;
2359 }
2360 
2361 /* static */
ShouldIncludeStackTrace(MethodName aMethodName)2362 bool Console::ShouldIncludeStackTrace(MethodName aMethodName) {
2363   switch (aMethodName) {
2364     case MethodError:
2365     case MethodException:
2366     case MethodAssert:
2367     case MethodTrace:
2368       return true;
2369     default:
2370       return false;
2371   }
2372 }
2373 
GetOrCreateSandbox(JSContext * aCx,nsIPrincipal * aPrincipal)2374 JSObject* MainThreadConsoleData::GetOrCreateSandbox(JSContext* aCx,
2375                                                     nsIPrincipal* aPrincipal) {
2376   AssertIsOnMainThread();
2377 
2378   if (!mSandbox) {
2379     nsIXPConnect* xpc = nsContentUtils::XPConnect();
2380     MOZ_ASSERT(xpc, "This should never be null!");
2381 
2382     JS::Rooted<JSObject*> sandbox(aCx);
2383     nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
2384     if (NS_WARN_IF(NS_FAILED(rv))) {
2385       return nullptr;
2386     }
2387 
2388     mSandbox = new JSObjectHolder(aCx, sandbox);
2389   }
2390 
2391   return mSandbox->GetJSObject();
2392 }
2393 
StoreCallData(JSContext * aCx,ConsoleCallData * aCallData,const Sequence<JS::Value> & aArguments)2394 bool Console::StoreCallData(JSContext* aCx, ConsoleCallData* aCallData,
2395                             const Sequence<JS::Value>& aArguments) {
2396   AssertIsOnOwningThread();
2397 
2398   if (NS_WARN_IF(!mArgumentStorage.growBy(1))) {
2399     return false;
2400   }
2401   if (!mArgumentStorage.end()[-1].Initialize(aCx, aArguments)) {
2402     mArgumentStorage.shrinkBy(1);
2403     return false;
2404   }
2405 
2406   MOZ_ASSERT(aCallData);
2407   MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
2408 
2409   mCallDataStorage.AppendElement(aCallData);
2410 
2411   MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2412 
2413   if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
2414     mCallDataStorage.RemoveElementAt(0);
2415     mArgumentStorage.erase(&mArgumentStorage[0]);
2416   }
2417   return true;
2418 }
2419 
UnstoreCallData(ConsoleCallData * aCallData)2420 void Console::UnstoreCallData(ConsoleCallData* aCallData) {
2421   AssertIsOnOwningThread();
2422 
2423   MOZ_ASSERT(aCallData);
2424   MOZ_ASSERT(mCallDataStorage.Length() == mArgumentStorage.length());
2425 
2426   size_t index = mCallDataStorage.IndexOf(aCallData);
2427   // It can be that mCallDataStorage has been already cleaned in case the
2428   // processing of the argument of some Console methods triggers the
2429   // window.close().
2430   if (index == mCallDataStorage.NoIndex) {
2431     return;
2432   }
2433 
2434   mCallDataStorage.RemoveElementAt(index);
2435   mArgumentStorage.erase(&mArgumentStorage[index]);
2436 }
2437 
NotifyHandler(JSContext * aCx,const Sequence<JS::Value> & aArguments,ConsoleCallData * aCallData)2438 void Console::NotifyHandler(JSContext* aCx,
2439                             const Sequence<JS::Value>& aArguments,
2440                             ConsoleCallData* aCallData) {
2441   AssertIsOnOwningThread();
2442   MOZ_ASSERT(!NS_IsMainThread());
2443   MOZ_ASSERT(aCallData);
2444 
2445   if (!mConsoleEventNotifier) {
2446     return;
2447   }
2448 
2449   JS::Rooted<JS::Value> value(aCx);
2450 
2451   JS::Rooted<JSObject*> callableGlobal(
2452       aCx, mConsoleEventNotifier->CallbackGlobalOrNull());
2453   if (NS_WARN_IF(!callableGlobal)) {
2454     return;
2455   }
2456 
2457   // aCx and aArguments are in the same compartment because this method is
2458   // called directly when a Console.something() runs.
2459   // mConsoleEventNotifier->CallbackGlobal() is the scope where value will be
2460   // sent to.
2461   if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2462           aCx, aArguments, callableGlobal, &value, aCallData, &mGroupStack))) {
2463     return;
2464   }
2465 
2466   JS::Rooted<JS::Value> ignored(aCx);
2467   RefPtr<AnyCallback> notifier(mConsoleEventNotifier);
2468   notifier->Call(value, &ignored);
2469 }
2470 
RetrieveConsoleEvents(JSContext * aCx,nsTArray<JS::Value> & aEvents,ErrorResult & aRv)2471 void Console::RetrieveConsoleEvents(JSContext* aCx,
2472                                     nsTArray<JS::Value>& aEvents,
2473                                     ErrorResult& aRv) {
2474   AssertIsOnOwningThread();
2475 
2476   // We don't want to expose this functionality to main-thread yet.
2477   MOZ_ASSERT(!NS_IsMainThread());
2478 
2479   JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
2480 
2481   for (uint32_t i = 0; i < mArgumentStorage.length(); ++i) {
2482     JS::Rooted<JS::Value> value(aCx);
2483 
2484     JS::Rooted<JSObject*> sequenceScope(aCx, mArgumentStorage[i].Global());
2485     JSAutoRealm ar(aCx, sequenceScope);
2486 
2487     Sequence<JS::Value> sequence;
2488     SequenceRooter<JS::Value> arguments(aCx, &sequence);
2489 
2490     if (!mArgumentStorage[i].PopulateArgumentsSequence(sequence)) {
2491       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2492       return;
2493     }
2494 
2495     // Here we have aCx and sequence in the same compartment.
2496     // targetScope is the destination scope and value will be populated in its
2497     // compartment.
2498     if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
2499             aCx, sequence, targetScope, &value, mCallDataStorage[i],
2500             &mGroupStack))) {
2501       aRv.Throw(NS_ERROR_FAILURE);
2502       return;
2503     }
2504 
2505     aEvents.AppendElement(value);
2506   }
2507 }
2508 
SetConsoleEventHandler(AnyCallback * aHandler)2509 void Console::SetConsoleEventHandler(AnyCallback* aHandler) {
2510   AssertIsOnOwningThread();
2511 
2512   // We don't want to expose this functionality to main-thread yet.
2513   MOZ_ASSERT(!NS_IsMainThread());
2514 
2515   mConsoleEventNotifier = aHandler;
2516 }
2517 
AssertIsOnOwningThread() const2518 void Console::AssertIsOnOwningThread() const {
2519   NS_ASSERT_OWNINGTHREAD(Console);
2520 }
2521 
IsShuttingDown() const2522 bool Console::IsShuttingDown() const {
2523   MOZ_ASSERT(mStatus != eUnknown);
2524   return mStatus == eShuttingDown;
2525 }
2526 
2527 /* static */
GetConsole(const GlobalObject & aGlobal)2528 already_AddRefed<Console> Console::GetConsole(const GlobalObject& aGlobal) {
2529   ErrorResult rv;
2530   RefPtr<Console> console = GetConsoleInternal(aGlobal, rv);
2531   if (NS_WARN_IF(rv.Failed()) || !console) {
2532     rv.SuppressException();
2533     return nullptr;
2534   }
2535 
2536   console->AssertIsOnOwningThread();
2537 
2538   if (console->IsShuttingDown()) {
2539     return nullptr;
2540   }
2541 
2542   return console.forget();
2543 }
2544 
2545 /* static */
GetConsoleInternal(const GlobalObject & aGlobal,ErrorResult & aRv)2546 already_AddRefed<Console> Console::GetConsoleInternal(
2547     const GlobalObject& aGlobal, ErrorResult& aRv) {
2548   // Window
2549   if (NS_IsMainThread()) {
2550     nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2551         do_QueryInterface(aGlobal.GetAsSupports());
2552 
2553     // we are probably running a chrome script.
2554     if (!innerWindow) {
2555       RefPtr<Console> console = new Console(aGlobal.Context(), nullptr, 0, 0);
2556       console->Initialize(aRv);
2557       if (NS_WARN_IF(aRv.Failed())) {
2558         return nullptr;
2559       }
2560 
2561       return console.forget();
2562     }
2563 
2564     nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(innerWindow);
2565     return window->GetConsole(aGlobal.Context(), aRv);
2566   }
2567 
2568   // Worklet
2569   nsCOMPtr<WorkletGlobalScope> workletScope =
2570       do_QueryInterface(aGlobal.GetAsSupports());
2571   if (workletScope) {
2572     WorkletThread::AssertIsOnWorkletThread();
2573     return workletScope->GetConsole(aGlobal.Context(), aRv);
2574   }
2575 
2576   // Workers
2577   MOZ_ASSERT(!NS_IsMainThread());
2578 
2579   JSContext* cx = aGlobal.Context();
2580   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
2581   MOZ_ASSERT(workerPrivate);
2582 
2583   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
2584   if (NS_WARN_IF(!global)) {
2585     return nullptr;
2586   }
2587 
2588   WorkerGlobalScope* scope = workerPrivate->GlobalScope();
2589   MOZ_ASSERT(scope);
2590 
2591   // Normal worker scope.
2592   if (scope == global) {
2593     return scope->GetConsole(aRv);
2594   }
2595 
2596   // Debugger worker scope
2597 
2598   WorkerDebuggerGlobalScope* debuggerScope =
2599       workerPrivate->DebuggerGlobalScope();
2600   MOZ_ASSERT(debuggerScope);
2601   MOZ_ASSERT(debuggerScope == global, "Which kind of global do we have?");
2602 
2603   return debuggerScope->GetConsole(aRv);
2604 }
2605 
MonotonicTimer(JSContext * aCx,MethodName aMethodName,const Sequence<JS::Value> & aData,DOMHighResTimeStamp * aTimeStamp)2606 bool Console::MonotonicTimer(JSContext* aCx, MethodName aMethodName,
2607                              const Sequence<JS::Value>& aData,
2608                              DOMHighResTimeStamp* aTimeStamp) {
2609   if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(mGlobal)) {
2610     nsGlobalWindowInner* win = nsGlobalWindowInner::Cast(innerWindow);
2611     MOZ_ASSERT(win);
2612 
2613     RefPtr<Performance> performance = win->GetPerformance();
2614     if (!performance) {
2615       return false;
2616     }
2617 
2618     *aTimeStamp = performance->Now();
2619 
2620     nsDocShell* docShell = static_cast<nsDocShell*>(win->GetDocShell());
2621     RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
2622     bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
2623 
2624     // The 'timeStamp' recordings do not need an argument; use empty string
2625     // if no arguments passed in.
2626     if (isTimelineRecording && aMethodName == MethodTimeStamp) {
2627       JS::Rooted<JS::Value> value(
2628           aCx, aData.Length() == 0 ? JS_GetEmptyStringValue(aCx) : aData[0]);
2629       JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2630       if (!jsString) {
2631         return false;
2632       }
2633 
2634       nsAutoJSString key;
2635       if (!key.init(aCx, jsString)) {
2636         return false;
2637       }
2638 
2639       timelines->AddMarkerForDocShell(docShell,
2640                                       MakeUnique<TimestampTimelineMarker>(key));
2641     }
2642     // For `console.time(foo)` and `console.timeEnd(foo)`.
2643     else if (isTimelineRecording && aData.Length() == 1) {
2644       JS::Rooted<JS::Value> value(aCx, aData[0]);
2645       JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
2646       if (!jsString) {
2647         return false;
2648       }
2649 
2650       nsAutoJSString key;
2651       if (!key.init(aCx, jsString)) {
2652         return false;
2653       }
2654 
2655       timelines->AddMarkerForDocShell(
2656           docShell,
2657           MakeUnique<ConsoleTimelineMarker>(key, aMethodName == MethodTime
2658                                                      ? MarkerTracingType::START
2659                                                      : MarkerTracingType::END));
2660     }
2661 
2662     return true;
2663   }
2664 
2665   if (NS_IsMainThread()) {
2666     *aTimeStamp = (TimeStamp::Now() - mCreationTimeStamp).ToMilliseconds();
2667     return true;
2668   }
2669 
2670   if (nsCOMPtr<WorkletGlobalScope> workletGlobal = do_QueryInterface(mGlobal)) {
2671     *aTimeStamp = workletGlobal->TimeStampToDOMHighRes(TimeStamp::Now());
2672     return true;
2673   }
2674 
2675   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2676   MOZ_ASSERT(workerPrivate);
2677 
2678   *aTimeStamp = workerPrivate->TimeStampToDOMHighRes(TimeStamp::Now());
2679   return true;
2680 }
2681 
2682 /* static */
CreateInstance(const GlobalObject & aGlobal,const ConsoleInstanceOptions & aOptions)2683 already_AddRefed<ConsoleInstance> Console::CreateInstance(
2684     const GlobalObject& aGlobal, const ConsoleInstanceOptions& aOptions) {
2685   RefPtr<ConsoleInstance> console =
2686       new ConsoleInstance(aGlobal.Context(), aOptions);
2687   return console.forget();
2688 }
2689 
MaybeExecuteDumpFunction(JSContext * aCx,const nsAString & aMethodName,const Sequence<JS::Value> & aData,nsIStackFrame * aStack)2690 void Console::MaybeExecuteDumpFunction(JSContext* aCx,
2691                                        const nsAString& aMethodName,
2692                                        const Sequence<JS::Value>& aData,
2693                                        nsIStackFrame* aStack) {
2694   if (!mDumpFunction && !mDumpToStdout) {
2695     return;
2696   }
2697 
2698   nsAutoString message;
2699   message.AssignLiteral("console.");
2700   message.Append(aMethodName);
2701   message.AppendLiteral(": ");
2702 
2703   if (!mPrefix.IsEmpty()) {
2704     message.Append(mPrefix);
2705     message.AppendLiteral(": ");
2706   }
2707 
2708   for (uint32_t i = 0; i < aData.Length(); ++i) {
2709     JS::Rooted<JS::Value> v(aCx, aData[i]);
2710     JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
2711     if (!jsString) {
2712       continue;
2713     }
2714 
2715     nsAutoJSString string;
2716     if (NS_WARN_IF(!string.init(aCx, jsString))) {
2717       return;
2718     }
2719 
2720     if (i != 0) {
2721       message.AppendLiteral(" ");
2722     }
2723 
2724     message.Append(string);
2725   }
2726 
2727   message.AppendLiteral("\n");
2728 
2729   // aStack can be null.
2730 
2731   nsCOMPtr<nsIStackFrame> stack(aStack);
2732 
2733   while (stack) {
2734     nsAutoString filename;
2735     stack->GetFilename(aCx, filename);
2736 
2737     message.Append(filename);
2738     message.AppendLiteral(" ");
2739 
2740     message.AppendInt(stack->GetLineNumber(aCx));
2741     message.AppendLiteral(" ");
2742 
2743     nsAutoString functionName;
2744     stack->GetName(aCx, functionName);
2745 
2746     message.Append(functionName);
2747     message.AppendLiteral("\n");
2748 
2749     nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
2750 
2751     if (!caller) {
2752       caller = stack->GetAsyncCaller(aCx);
2753     }
2754 
2755     stack.swap(caller);
2756   }
2757 
2758   ExecuteDumpFunction(message);
2759 }
2760 
MaybeExecuteDumpFunctionForTime(JSContext * aCx,MethodName aMethodName,const nsAString & aMethodString,uint64_t aMonotonicTimer,const JS::Value & aData)2761 void Console::MaybeExecuteDumpFunctionForTime(JSContext* aCx,
2762                                               MethodName aMethodName,
2763                                               const nsAString& aMethodString,
2764                                               uint64_t aMonotonicTimer,
2765                                               const JS::Value& aData) {
2766   if (!mDumpFunction && !mDumpToStdout) {
2767     return;
2768   }
2769 
2770   nsAutoString message;
2771   message.AssignLiteral("console.");
2772   message.Append(aMethodString);
2773   message.AppendLiteral(": ");
2774 
2775   if (!mPrefix.IsEmpty()) {
2776     message.Append(mPrefix);
2777     message.AppendLiteral(": ");
2778   }
2779 
2780   JS::Rooted<JS::Value> v(aCx, aData);
2781   JS::Rooted<JSString*> jsString(aCx, JS_ValueToSource(aCx, v));
2782   if (!jsString) {
2783     return;
2784   }
2785 
2786   nsAutoJSString string;
2787   if (NS_WARN_IF(!string.init(aCx, jsString))) {
2788     return;
2789   }
2790 
2791   message.Append(string);
2792   message.AppendLiteral(" @ ");
2793   message.AppendInt(aMonotonicTimer);
2794 
2795   message.AppendLiteral("\n");
2796   ExecuteDumpFunction(message);
2797 }
2798 
ExecuteDumpFunction(const nsAString & aMessage)2799 void Console::ExecuteDumpFunction(const nsAString& aMessage) {
2800   if (mDumpFunction) {
2801     RefPtr<ConsoleInstanceDumpCallback> dumpFunction(mDumpFunction);
2802     dumpFunction->Call(aMessage);
2803     return;
2804   }
2805 
2806   NS_ConvertUTF16toUTF8 str(aMessage);
2807   MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("%s", str.get()));
2808 #ifdef ANDROID
2809   __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", str.get());
2810 #endif
2811   fputs(str.get(), stdout);
2812   fflush(stdout);
2813 }
2814 
PrefToValue(const nsAString & aPref,const ConsoleLogLevel aLevel)2815 ConsoleLogLevel PrefToValue(const nsAString& aPref,
2816                             const ConsoleLogLevel aLevel) {
2817   if (!NS_IsMainThread()) {
2818     NS_WARNING("Console.maxLogLevelPref is not supported on workers!");
2819     return ConsoleLogLevel::All;
2820   }
2821   if (aPref.IsEmpty()) {
2822     return aLevel;
2823   }
2824 
2825   NS_ConvertUTF16toUTF8 pref(aPref);
2826   nsAutoCString value;
2827   nsresult rv = Preferences::GetCString(pref.get(), value);
2828   if (NS_WARN_IF(NS_FAILED(rv))) {
2829     return aLevel;
2830   }
2831 
2832   int index = FindEnumStringIndexImpl(value.get(), value.Length(),
2833                                       ConsoleLogLevelValues::strings);
2834   if (NS_WARN_IF(index < 0)) {
2835     nsString message;
2836     message.AssignLiteral("Invalid Console.maxLogLevelPref value: ");
2837     message.Append(NS_ConvertUTF8toUTF16(value));
2838 
2839     nsContentUtils::LogSimpleConsoleError(message, "chrome", false,
2840                                           true /* from chrome context*/);
2841     return aLevel;
2842   }
2843 
2844   MOZ_ASSERT(index < (int)ConsoleLogLevelValues::Count);
2845   return static_cast<ConsoleLogLevel>(index);
2846 }
2847 
ShouldProceed(MethodName aName) const2848 bool Console::ShouldProceed(MethodName aName) const {
2849   ConsoleLogLevel maxLogLevel = PrefToValue(mMaxLogLevelPref, mMaxLogLevel);
2850   return WebIDLLogLevelToInteger(maxLogLevel) <=
2851          InternalLogLevelToInteger(aName);
2852 }
2853 
WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const2854 uint32_t Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const {
2855   switch (aLevel) {
2856     case ConsoleLogLevel::All:
2857       return 0;
2858     case ConsoleLogLevel::Debug:
2859       return 2;
2860     case ConsoleLogLevel::Log:
2861       return 3;
2862     case ConsoleLogLevel::Info:
2863       return 3;
2864     case ConsoleLogLevel::Clear:
2865       return 3;
2866     case ConsoleLogLevel::Trace:
2867       return 3;
2868     case ConsoleLogLevel::TimeLog:
2869       return 3;
2870     case ConsoleLogLevel::TimeEnd:
2871       return 3;
2872     case ConsoleLogLevel::Time:
2873       return 3;
2874     case ConsoleLogLevel::Group:
2875       return 3;
2876     case ConsoleLogLevel::GroupEnd:
2877       return 3;
2878     case ConsoleLogLevel::Profile:
2879       return 3;
2880     case ConsoleLogLevel::ProfileEnd:
2881       return 3;
2882     case ConsoleLogLevel::Dir:
2883       return 3;
2884     case ConsoleLogLevel::Dirxml:
2885       return 3;
2886     case ConsoleLogLevel::Warn:
2887       return 4;
2888     case ConsoleLogLevel::Error:
2889       return 5;
2890     case ConsoleLogLevel::Off:
2891       return UINT32_MAX;
2892     default:
2893       MOZ_CRASH(
2894           "ConsoleLogLevel is out of sync with the Console implementation!");
2895       return 0;
2896   }
2897 
2898   return 0;
2899 }
2900 
InternalLogLevelToInteger(MethodName aName) const2901 uint32_t Console::InternalLogLevelToInteger(MethodName aName) const {
2902   switch (aName) {
2903     case MethodLog:
2904       return 3;
2905     case MethodInfo:
2906       return 3;
2907     case MethodWarn:
2908       return 4;
2909     case MethodError:
2910       return 5;
2911     case MethodException:
2912       return 5;
2913     case MethodDebug:
2914       return 2;
2915     case MethodTable:
2916       return 3;
2917     case MethodTrace:
2918       return 3;
2919     case MethodDir:
2920       return 3;
2921     case MethodDirxml:
2922       return 3;
2923     case MethodGroup:
2924       return 3;
2925     case MethodGroupCollapsed:
2926       return 3;
2927     case MethodGroupEnd:
2928       return 3;
2929     case MethodTime:
2930       return 3;
2931     case MethodTimeLog:
2932       return 3;
2933     case MethodTimeEnd:
2934       return 3;
2935     case MethodTimeStamp:
2936       return 3;
2937     case MethodAssert:
2938       return 3;
2939     case MethodCount:
2940       return 3;
2941     case MethodCountReset:
2942       return 3;
2943     case MethodClear:
2944       return 3;
2945     case MethodProfile:
2946       return 3;
2947     case MethodProfileEnd:
2948       return 3;
2949     default:
2950       MOZ_CRASH("MethodName is out of sync with the Console implementation!");
2951       return 0;
2952   }
2953 
2954   return 0;
2955 }
2956 
Initialize(JSContext * aCx,const Sequence<JS::Value> & aArguments)2957 bool Console::ArgumentData::Initialize(JSContext* aCx,
2958                                        const Sequence<JS::Value>& aArguments) {
2959   mGlobal = JS::CurrentGlobalOrNull(aCx);
2960 
2961   if (NS_WARN_IF(!mArguments.AppendElements(aArguments, fallible))) {
2962     return false;
2963   }
2964 
2965   return true;
2966 }
2967 
Trace(const TraceCallbacks & aCallbacks,void * aClosure)2968 void Console::ArgumentData::Trace(const TraceCallbacks& aCallbacks,
2969                                   void* aClosure) {
2970   ArgumentData* tmp = this;
2971   for (uint32_t i = 0; i < mArguments.Length(); ++i) {
2972     NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mArguments[i])
2973   }
2974 
2975   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
2976 }
2977 
PopulateArgumentsSequence(Sequence<JS::Value> & aSequence) const2978 bool Console::ArgumentData::PopulateArgumentsSequence(
2979     Sequence<JS::Value>& aSequence) const {
2980   AssertIsOnOwningThread();
2981 
2982   for (uint32_t i = 0; i < mArguments.Length(); ++i) {
2983     if (NS_WARN_IF(!aSequence.AppendElement(mArguments[i], fallible))) {
2984       return false;
2985     }
2986   }
2987 
2988   return true;
2989 }
2990 
2991 }  // namespace mozilla::dom
2992