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