Source code

Revision control

Other Tools

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/Document.h"
15
#include "mozilla/dom/Exceptions.h"
16
#include "mozilla/dom/File.h"
17
#include "mozilla/dom/FunctionBinding.h"
18
#include "mozilla/dom/Performance.h"
19
#include "mozilla/dom/PromiseBinding.h"
20
#include "mozilla/dom/ScriptSettings.h"
21
#include "mozilla/dom/StructuredCloneHolder.h"
22
#include "mozilla/dom/ToJSValue.h"
23
#include "mozilla/dom/WorkerPrivate.h"
24
#include "mozilla/dom/WorkerRunnable.h"
25
#include "mozilla/dom/WorkerScope.h"
26
#include "mozilla/dom/WorkletGlobalScope.h"
27
#include "mozilla/dom/WorkletImpl.h"
28
#include "mozilla/dom/WorkletThread.h"
29
#include "mozilla/BasePrincipal.h"
30
#include "mozilla/Maybe.h"
31
#include "mozilla/StaticPrefs_devtools.h"
32
#include "nsCycleCollectionParticipant.h"
33
#include "nsDOMNavigationTiming.h"
34
#include "nsGlobalWindow.h"
35
#include "nsJSUtils.h"
36
#include "nsNetUtil.h"
37
#include "xpcpublic.h"
38
#include "nsContentUtils.h"
39
#include "nsDocShell.h"
40
#include "nsProxyRelease.h"
41
#include "mozilla/ConsoleTimelineMarker.h"
42
#include "mozilla/TimestampTimelineMarker.h"
43
44
#include "nsIConsoleAPIStorage.h"
45
#include "nsIException.h" // for nsIStackFrame
46
#include "nsIInterfaceRequestorUtils.h"
47
#include "nsILoadContext.h"
48
#include "nsISensitiveInfoHiddenURI.h"
49
#include "nsISupportsPrimitives.h"
50
#include "nsIWebNavigation.h"
51
#include "nsIXPConnect.h"
52
53
// The maximum allowed number of concurrent timers per page.
54
#define MAX_PAGE_TIMERS 10000
55
56
// The maximum allowed number of concurrent counters per page.
57
#define MAX_PAGE_COUNTERS 10000
58
59
// The maximum stacktrace depth when populating the stacktrace array used for
60
// console.trace().
61
#define DEFAULT_MAX_STACKTRACE_DEPTH 200
62
63
// This tags are used in the Structured Clone Algorithm to move js values from
64
// worker thread to main thread
65
#define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
66
67
// This value is taken from ConsoleAPIStorage.js
68
#define STORAGE_MAX_EVENTS 1000
69
70
using namespace mozilla::dom::exceptions;
71
72
namespace mozilla {
73
namespace dom {
74
75
struct ConsoleStructuredCloneData {
76
nsCOMPtr<nsIGlobalObject> mGlobal;
77
nsTArray<RefPtr<BlobImpl>> mBlobs;
78
};
79
80
/**
81
* Console API in workers uses the Structured Clone Algorithm to move any value
82
* from the worker thread to the main-thread. Some object cannot be moved and,
83
* in these cases, we convert them to strings.
84
* It's not the best, but at least we are able to show something.
85
*/
86
87
class ConsoleCallData final {
88
public:
89
NS_INLINE_DECL_REFCOUNTING(ConsoleCallData)
90
91
ConsoleCallData()
92
: mMethodName(Console::MethodLog),
93
mTimeStamp(JS_Now() / PR_USEC_PER_MSEC),
94
mStartTimerValue(0),
95
mStartTimerStatus(Console::eTimerUnknown),
96
mLogTimerDuration(0),
97
mLogTimerStatus(Console::eTimerUnknown),
98
mCountValue(MAX_PAGE_COUNTERS),
99
mIDType(eUnknown),
100
mOuterIDNumber(0),
101
mInnerIDNumber(0),
102
mStatus(eUnused) {}
103
104
bool Initialize(JSContext* aCx, Console::MethodName aName,
105
const nsAString& aString,
106
const Sequence<JS::Value>& aArguments, Console* aConsole) {
107
AssertIsOnOwningThread();
108
MOZ_ASSERT(aConsole);
109
110
// We must be registered before doing any JS operation otherwise it can
111
// happen that mCopiedArguments are not correctly traced.
112
aConsole->StoreCallData(this);
113
114
mMethodName = aName;
115
mMethodString = aString;
116
117
mGlobal = JS::CurrentGlobalOrNull(aCx);
118
119
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
120
if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
121
aConsole->UnstoreCallData(this);
122
return false;
123
}
124
}
125
126
return true;
127
}
128
129
void SetIDs(uint64_t aOuterID, uint64_t aInnerID) {
130
MOZ_ASSERT(mIDType == eUnknown);
131
132
mOuterIDNumber = aOuterID;
133
mInnerIDNumber = aInnerID;
134
mIDType = eNumber;
135
}
136
137
void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID) {
138
MOZ_ASSERT(mIDType == eUnknown);
139
140
mOuterIDString = aOuterID;
141
mInnerIDString = aInnerID;
142
mIDType = eString;
143
}
144
145
void SetOriginAttributes(const OriginAttributes& aOriginAttributes) {
146
mOriginAttributes = aOriginAttributes;
147
}
148
149
void SetAddonId(nsIPrincipal* aPrincipal) {
150
nsAutoString addonId;
151
aPrincipal->GetAddonId(addonId);
152
153
mAddonId = addonId;
154
}
155
156
bool PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const {
157
AssertIsOnOwningThread();
158
159
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
160
if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i], fallible))) {
161
return false;
162
}
163
}
164
165
return true;
166
}
167
168
void Trace(const TraceCallbacks& aCallbacks, void* aClosure) {
169
ConsoleCallData* tmp = this;
170
for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
171
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
172
}
173
174
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal);
175
}
176
177
void AssertIsOnOwningThread() const {
178
NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
179
}
180
181
JS::Heap<JSObject*> mGlobal;
182
183
// This is a copy of the arguments we received from the DOM bindings. Console
184
// object traces them because this ConsoleCallData calls
185
// RegisterConsoleCallData() in the Initialize().
186
nsTArray<JS::Heap<JS::Value>> mCopiedArguments;
187
188
Console::MethodName mMethodName;
189
int64_t mTimeStamp;
190
191
// These values are set in the owning thread and they contain the timestamp of
192
// when the new timer has started, the name of it and the status of the
193
// creation of it. If status is false, something went wrong. User
194
// DOMHighResTimeStamp instead mozilla::TimeStamp because we use
195
// monotonicTimer from Performance.now();
196
// They will be set on the owning thread and never touched again on that
197
// thread. They will be used in order to create a ConsoleTimerStart dictionary
198
// when console.time() is used.
199
DOMHighResTimeStamp mStartTimerValue;
200
nsString mStartTimerLabel;
201
Console::TimerStatus mStartTimerStatus;
202
203
// These values are set in the owning thread and they contain the duration,
204
// the name and the status of the LogTimer method. If status is false,
205
// something went wrong. They will be set on the owning thread and never
206
// touched again on that thread. They will be used in order to create a
207
// ConsoleTimerLogOrEnd dictionary. This members are set when
208
// console.timeEnd() or console.timeLog() are called.
209
double mLogTimerDuration;
210
nsString mLogTimerLabel;
211
Console::TimerStatus mLogTimerStatus;
212
213
// These 2 values are set by IncreaseCounter or ResetCounter on the owning
214
// thread and they are used by CreateCounterOrResetCounterValue.
215
// These members are set when console.count() or console.countReset() are
216
// called.
217
nsString mCountLabel;
218
uint32_t mCountValue;
219
220
// The concept of outerID and innerID is misleading because when a
221
// ConsoleCallData is created from a window, these are the window IDs, but
222
// when the object is created from a SharedWorker, a ServiceWorker or a
223
// subworker of a ChromeWorker these IDs are the type of worker and the
224
// filename of the callee.
225
// In Console.jsm the ID is 'jsm'.
226
enum { eString, eNumber, eUnknown } mIDType;
227
228
uint64_t mOuterIDNumber;
229
nsString mOuterIDString;
230
231
uint64_t mInnerIDNumber;
232
nsString mInnerIDString;
233
234
OriginAttributes mOriginAttributes;
235
236
nsString mAddonId;
237
238
nsString mMethodString;
239
240
// Stack management is complicated, because we want to do it as
241
// lazily as possible. Therefore, we have the following behavior:
242
// 1) mTopStackFrame is initialized whenever we have any JS on the stack
243
// 2) mReifiedStack is initialized if we're created in a worker.
244
// 3) mStack is set (possibly to null if there is no JS on the stack) if
245
// we're created on main thread.
246
Maybe<ConsoleStackEntry> mTopStackFrame;
247
Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
248
nsCOMPtr<nsIStackFrame> mStack;
249
250
// mStatus is about the lifetime of this object. Console must take care of
251
// keep it alive or not following this enumeration.
252
enum {
253
// If the object is created but it is owned by some runnable, this is its
254
// status. It can be deleted at any time.
255
eUnused,
256
257
// When a runnable takes ownership of a ConsoleCallData and send it to
258
// different thread, this is its status. Console cannot delete it at this
259
// time.
260
eInUse,
261
262
// When a runnable owns this ConsoleCallData, we can't delete it directly.
263
// instead, we mark it with this new status and we move it in
264
// mCallDataStoragePending list in order to keep it alive an trace it
265
// correctly. Once the runnable finishs its task, it will delete this object
266
// calling ReleaseCallData().
267
eToBeDeleted
268
} mStatus;
269
270
private:
271
~ConsoleCallData() {
272
AssertIsOnOwningThread();
273
MOZ_ASSERT(mStatus != eInUse);
274
}
275
};
276
277
// This base class must be extended for Worker and for Worklet.
278
class ConsoleRunnable : public StructuredCloneHolderBase {
279
public:
280
~ConsoleRunnable() override {
281
// Clear the StructuredCloneHolderBase class.
282
Clear();
283
}
284
285
protected:
286
JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
287
const JS::CloneDataPolicy& aCloneDataPolicy,
288
uint32_t aTag, uint32_t aIndex) override {
289
AssertIsOnMainThread();
290
291
if (aTag == CONSOLE_TAG_BLOB) {
292
MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
293
294
JS::Rooted<JS::Value> val(aCx);
295
{
296
nsCOMPtr<nsIGlobalObject> global = mClonedData.mGlobal;
297
RefPtr<Blob> blob =
298
Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex));
299
if (!ToJSValue(aCx, blob, &val)) {
300
return nullptr;
301
}
302
}
303
304
return &val.toObject();
305
}
306
307
MOZ_CRASH("No other tags are supported.");
308
return nullptr;
309
}
310
311
bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
312
JS::Handle<JSObject*> aObj,
313
bool* aSameProcessScopeRequired) override {
314
RefPtr<Blob> blob;
315
if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
316
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
317
mClonedData.mBlobs.Length()))) {
318
return false;
319
}
320
321
mClonedData.mBlobs.AppendElement(blob->Impl());
322
return true;
323
}
324
325
if (!JS_ObjectNotWritten(aWriter, aObj)) {
326
return false;
327
}
328
329
JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
330
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
331
if (NS_WARN_IF(!jsString)) {
332
return false;
333
}
334
335
if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
336
return false;
337
}
338
339
return true;
340
}
341
342
// Helper methods for CallData
343
bool StoreConsoleData(JSContext* aCx, ConsoleCallData* aCallData) {
344
ConsoleCommon::ClearException ce(aCx);
345
346
JS::Rooted<JSObject*> arguments(
347
aCx, JS::NewArrayObject(aCx, aCallData->mCopiedArguments.Length()));
348
if (NS_WARN_IF(!arguments)) {
349
return false;
350
}
351
352
JS::Rooted<JS::Value> arg(aCx);
353
for (uint32_t i = 0; i < aCallData->mCopiedArguments.Length(); ++i) {
354
arg = aCallData->mCopiedArguments[i];
355
if (NS_WARN_IF(
356
!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
357
return false;
358
}
359
}
360
361
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
362
363
if (NS_WARN_IF(!Write(aCx, value))) {
364
return false;
365
}
366
367
return true;
368
}
369
370
void ProcessCallData(JSContext* aCx, Console* aConsole,
371
ConsoleCallData* aCallData) {
372
AssertIsOnMainThread();
373
374
ConsoleCommon::ClearException ce(aCx);
375
376
JS::Rooted<JS::Value> argumentsValue(aCx);
377
if (!Read(aCx, &argumentsValue)) {
378
return;
379
}
380
381
MOZ_ASSERT(argumentsValue.isObject());
382
383
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
384
385
uint32_t length;
386
if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
387
return;
388
}
389
390
Sequence<JS::Value> values;
391
SequenceRooter<JS::Value> arguments(aCx, &values);
392
393
for (uint32_t i = 0; i < length; ++i) {
394
JS::Rooted<JS::Value> value(aCx);
395
396
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
397
return;
398
}
399
400
if (!values.AppendElement(value, fallible)) {
401
return;
402
}
403
}
404
405
MOZ_ASSERT(values.Length() == length);
406
407
aConsole->ProcessCallData(aCx, aCallData, values);
408
}
409
410
void ReleaseCallData(Console* aConsole, ConsoleCallData* aCallData) {
411
aConsole->AssertIsOnOwningThread();
412
413
if (aCallData->mStatus == ConsoleCallData::eToBeDeleted) {
414
aConsole->ReleaseCallData(aCallData);
415
} else {
416
MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eInUse);
417
aCallData->mStatus = ConsoleCallData::eUnused;
418
}
419
}
420
421
// Helper methods for Profile calls
422
bool StoreProfileData(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
423
ConsoleCommon::ClearException ce(aCx);
424
425
JS::Rooted<JSObject*> arguments(
426
aCx, JS::NewArrayObject(aCx, aArguments.Length()));
427
if (NS_WARN_IF(!arguments)) {
428
return false;
429
}
430
431
JS::Rooted<JS::Value> arg(aCx);
432
for (uint32_t i = 0; i < aArguments.Length(); ++i) {
433
arg = aArguments[i];
434
if (NS_WARN_IF(
435
!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
436
return false;
437
}
438
}
439
440
JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
441
442
if (NS_WARN_IF(!Write(aCx, value))) {
443
return false;
444
}
445
446
return true;
447
}
448
449
void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName,
450
const nsAString& aAction) {
451
AssertIsOnMainThread();
452
453
ConsoleCommon::ClearException ce(aCx);
454
455
JS::Rooted<JS::Value> argumentsValue(aCx);
456
bool ok = Read(aCx, &argumentsValue);
457
mClonedData.mGlobal = nullptr;
458
459
if (!ok) {
460
return;
461
}
462
463
MOZ_ASSERT(argumentsValue.isObject());
464
JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
465
if (NS_WARN_IF(!argumentsObj)) {
466
return;
467
}
468
469
uint32_t length;
470
if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
471
return;
472
}
473
474
Sequence<JS::Value> arguments;
475
476
for (uint32_t i = 0; i < length; ++i) {
477
JS::Rooted<JS::Value> value(aCx);
478
479
if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
480
return;
481
}
482
483
if (!arguments.AppendElement(value, fallible)) {
484
return;
485
}
486
}
487
488
Console::ProfileMethodMainthread(aCx, aAction, arguments);
489
}
490
491
ConsoleStructuredCloneData mClonedData;
492
};
493
494
class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
495
protected:
496
explicit ConsoleWorkletRunnable(Console* aConsole)
497
: Runnable("dom::console::ConsoleWorkletRunnable"), mConsole(aConsole) {
498
WorkletThread::AssertIsOnWorkletThread();
499
nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mConsole->mGlobal);
500
MOZ_ASSERT(global);
501
mWorkletImpl = global->Impl();
502
MOZ_ASSERT(mWorkletImpl);
503
}
504
505
~ConsoleWorkletRunnable() override = default;
506
507
NS_IMETHOD
508
Run() override {
509
// This runnable is dispatched to main-thread first, then it goes back to
510
// worklet thread.
511
if (NS_IsMainThread()) {
512
RunOnMainThread();
513
RefPtr<ConsoleWorkletRunnable> runnable(this);
514
return mWorkletImpl->SendControlMessage(runnable.forget());
515
}
516
517
WorkletThread::AssertIsOnWorkletThread();
518
519
ReleaseData();
520
mConsole = nullptr;
521
return NS_OK;
522
}
523
524
protected:
525
// This method is called in the main-thread.
526
virtual void RunOnMainThread() = 0;
527
528
// This method is called in the owning thread of the Console object.
529
virtual void ReleaseData() = 0;
530
531
// This must be released on the worker thread.
532
RefPtr<Console> mConsole;
533
534
RefPtr<WorkletImpl> mWorkletImpl;
535
};
536
537
// This runnable appends a CallData object into the Console queue running on
538
// the main-thread.
539
class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
540
public:
541
static already_AddRefed<ConsoleCallDataWorkletRunnable> Create(
542
JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData) {
543
WorkletThread::AssertIsOnWorkletThread();
544
545
RefPtr<ConsoleCallDataWorkletRunnable> runnable =
546
new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData);
547
548
if (!runnable->StoreConsoleData(aCx, aConsoleData)) {
549
return nullptr;
550
}
551
552
return runnable.forget();
553
}
554
555
private:
556
ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData)
557
: ConsoleWorkletRunnable(aConsole), mCallData(aCallData) {
558
WorkletThread::AssertIsOnWorkletThread();
559
MOZ_ASSERT(aCallData);
560
aCallData->AssertIsOnOwningThread();
561
562
const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo();
563
mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID());
564
565
// Marking this CallData as in use.
566
mCallData->mStatus = ConsoleCallData::eInUse;
567
}
568
569
~ConsoleCallDataWorkletRunnable() override { MOZ_ASSERT(!mCallData); }
570
571
void RunOnMainThread() override {
572
AutoSafeJSContext cx;
573
574
JSObject* sandbox =
575
mConsole->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
576
JS::Rooted<JSObject*> global(cx, sandbox);
577
if (NS_WARN_IF(!global)) {
578
return;
579
}
580
581
// The CreateSandbox call returns a proxy to the actual sandbox object. We
582
// don't need a proxy here.
583
global = js::UncheckedUnwrap(global);
584
585
JSAutoRealm ar(cx, global);
586
587
// We don't need to set a parent object in mCallData bacause there are not
588
// DOM objects exposed to worklet.
589
590
ProcessCallData(cx, mConsole, mCallData);
591
}
592
593
virtual void ReleaseData() override {
594
ReleaseCallData(mConsole, mCallData);
595
mCallData = nullptr;
596
}
597
598
RefPtr<ConsoleCallData> mCallData;
599
};
600
601
class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
602
public ConsoleRunnable {
603
public:
604
explicit ConsoleWorkerRunnable(Console* aConsole) : mConsole(aConsole) {}
605
606
~ConsoleWorkerRunnable() override = default;
607
608
bool Dispatch(JSContext* aCx) {
609
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
610
MOZ_ASSERT(workerPrivate);
611
612
if (NS_WARN_IF(!PreDispatch(aCx))) {
613
RunBackOnWorkerThreadForCleanup(workerPrivate);
614
return false;
615
}
616
617
if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
618
// RunBackOnWorkerThreadForCleanup() will be called by
619
// WorkerProxyToMainThreadRunnable::Dispatch().
620
return false;
621
}
622
623
return true;
624
}
625
626
protected:
627
void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
628
MOZ_ASSERT(aWorkerPrivate);
629
AssertIsOnMainThread();
630
631
// Walk up to our containing page
632
WorkerPrivate* wp = aWorkerPrivate;
633
while (wp->GetParent()) {
634
wp = wp->GetParent();
635
}
636
637
nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow();
638
if (!window) {
639
RunWindowless(aWorkerPrivate);
640
} else {
641
RunWithWindow(aWorkerPrivate, window);
642
}
643
}
644
645
void RunWithWindow(WorkerPrivate* aWorkerPrivate,
646
nsPIDOMWindowInner* aWindow) {
647
MOZ_ASSERT(aWorkerPrivate);
648
AssertIsOnMainThread();
649
650
AutoJSAPI jsapi;
651
MOZ_ASSERT(aWindow);
652
653
RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
654
if (NS_WARN_IF(!jsapi.Init(win))) {
655
return;
656
}
657
658
nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
659
if (NS_WARN_IF(!outerWindow)) {
660
return;
661
}
662
663
RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
664
aWindow);
665
}
666
667
void RunWindowless(WorkerPrivate* aWorkerPrivate) {
668
MOZ_ASSERT(aWorkerPrivate);
669
AssertIsOnMainThread();
670
671
WorkerPrivate* wp = aWorkerPrivate;
672
while (wp->GetParent()) {
673
wp = wp->GetParent();
674
}
675
676
MOZ_ASSERT(!wp->GetWindow());
677
678
AutoJSAPI jsapi;
679
jsapi.Init();
680
681
JSContext* cx = jsapi.cx();
682
683
JS::Rooted<JSObject*> global(
684
cx, mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal()));
685
if (NS_WARN_IF(!global)) {
686
return;
687
}
688
689
// The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
690
// We don't need a proxy here.
691
global = js::UncheckedUnwrap(global);
692
693
JSAutoRealm ar(cx, global);
694
695
nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global);
696
if (NS_WARN_IF(!globalObject)) {
697
return;
698
}
699
700
RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr);
701
}
702
703
void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
704
MOZ_ASSERT(aWorkerPrivate);
705
aWorkerPrivate->AssertIsOnWorkerThread();
706
ReleaseData();
707
mConsole = nullptr;
708
}
709
710
// This method is called in the owning thread of the Console object.
711
virtual bool PreDispatch(JSContext* aCx) = 0;
712
713
// This method is called in the main-thread.
714
virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
715
WorkerPrivate* aWorkerPrivate,
716
nsPIDOMWindowOuter* aOuterWindow,
717
nsPIDOMWindowInner* aInnerWindow) = 0;
718
719
// This method is called in the owning thread of the Console object.
720
virtual void ReleaseData() = 0;
721
722
bool ForMessaging() const override { return true; }
723
724
// This must be released on the worker thread.
725
RefPtr<Console> mConsole;
726
};
727
728
// This runnable appends a CallData object into the Console queue running on
729
// the main-thread.
730
class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
731
public:
732
ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
733
: ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
734
MOZ_ASSERT(aCallData);
735
mCallData->AssertIsOnOwningThread();
736
737
// Marking this CallData as in use.
738
mCallData->mStatus = ConsoleCallData::eInUse;
739
}
740
741
private:
742
~ConsoleCallDataWorkerRunnable() override { MOZ_ASSERT(!mCallData); }
743
744
bool PreDispatch(JSContext* aCx) override {
745
return StoreConsoleData(aCx, mCallData);
746
}
747
748
void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
749
WorkerPrivate* aWorkerPrivate,
750
nsPIDOMWindowOuter* aOuterWindow,
751
nsPIDOMWindowInner* aInnerWindow) override {
752
MOZ_ASSERT(aGlobal);
753
MOZ_ASSERT(aWorkerPrivate);
754
AssertIsOnMainThread();
755
756
// The windows have to run in parallel.
757
MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
758
759
if (aOuterWindow) {
760
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
761
} else {
762
ConsoleStackEntry frame;
763
if (mCallData->mTopStackFrame) {
764
frame = *mCallData->mTopStackFrame;
765
}
766
767
nsString id = frame.mFilename;
768
nsString innerID;
769
if (aWorkerPrivate->IsSharedWorker()) {
770
innerID = NS_LITERAL_STRING("SharedWorker");
771
} else if (aWorkerPrivate->IsServiceWorker()) {
772
innerID = NS_LITERAL_STRING("ServiceWorker");
773
// Use scope as ID so the webconsole can decide if the message should
774
// show up per tab
775
CopyASCIItoUTF16(aWorkerPrivate->ServiceWorkerScope(), id);
776
} else {
777
innerID = NS_LITERAL_STRING("Worker");
778
}
779
780
mCallData->SetIDs(id, innerID);
781
}
782
783
mClonedData.mGlobal = aGlobal;
784
785
ProcessCallData(aCx, mConsole, mCallData);
786
787
mClonedData.mGlobal = nullptr;
788
}
789
790
virtual void ReleaseData() override {
791
ReleaseCallData(mConsole, mCallData);
792
mCallData = nullptr;
793
}
794
795
RefPtr<ConsoleCallData> mCallData;
796
};
797
798
// This runnable calls ProfileMethod() on the console on the main-thread.
799
class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable {
800
public:
801
static already_AddRefed<ConsoleProfileWorkletRunnable> Create(
802
JSContext* aCx, Console* aConsole, Console::MethodName aName,
803
const nsAString& aAction, const Sequence<JS::Value>& aArguments) {
804
WorkletThread::AssertIsOnWorkletThread();
805
806
RefPtr<ConsoleProfileWorkletRunnable> runnable =
807
new ConsoleProfileWorkletRunnable(aConsole, aName, aAction);
808
809
if (!runnable->StoreProfileData(aCx, aArguments)) {
810
return nullptr;
811
}
812
813
return runnable.forget();
814
}
815
816
private:
817
ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName,
818
const nsAString& aAction)
819
: ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) {
820
MOZ_ASSERT(aConsole);
821
}
822
823
void RunOnMainThread() override {
824
AssertIsOnMainThread();
825
826
AutoSafeJSContext cx;
827
828
JSObject* sandbox =
829
mConsole->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
830
JS::Rooted<JSObject*> global(cx, sandbox);
831
if (NS_WARN_IF(!global)) {
832
return;
833
}
834
835
// The CreateSandbox call returns a proxy to the actual sandbox object. We
836
// don't need a proxy here.
837
global = js::UncheckedUnwrap(global);
838
839
JSAutoRealm ar(cx, global);
840
841
// We don't need to set a parent object in mCallData bacause there are not
842
// DOM objects exposed to worklet.
843
ProcessProfileData(cx, mName, mAction);
844
}
845
846
virtual void ReleaseData() override {}
847
848
Console::MethodName mName;
849
nsString mAction;
850
};
851
852
// This runnable calls ProfileMethod() on the console on the main-thread.
853
class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
854
public:
855
ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
856
const nsAString& aAction,
857
const Sequence<JS::Value>& aArguments)
858
: ConsoleWorkerRunnable(aConsole),
859
mName(aName),
860
mAction(aAction),
861
mArguments(aArguments) {
862
MOZ_ASSERT(aConsole);
863
}
864
865
private:
866
bool PreDispatch(JSContext* aCx) override {
867
return StoreProfileData(aCx, mArguments);
868
}
869
870
void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
871
WorkerPrivate* aWorkerPrivate,
872
nsPIDOMWindowOuter* aOuterWindow,
873
nsPIDOMWindowInner* aInnerWindow) override {
874
AssertIsOnMainThread();
875
MOZ_ASSERT(aGlobal);
876
877
mClonedData.mGlobal = aGlobal;
878
879
ProcessProfileData(aCx, mName, mAction);
880
881
mClonedData.mGlobal = nullptr;
882
}
883
884
virtual void ReleaseData() override {}
885
886
Console::MethodName mName;
887
nsString mAction;
888
889
// This is a reference of the sequence of arguments we receive from the DOM
890
// bindings and it's rooted by them. It's only used on the owning thread in
891
// PreDispatch().
892
const Sequence<JS::Value>& mArguments;
893
};
894
895
NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
896
897
// We don't need to traverse/unlink mStorage and mSandbox because they are not
898
// CCed objects and they are only used on the main thread, even when this
899
// Console object is used on workers.
900
901
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
902
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
903
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
904
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
905
tmp->Shutdown();
906
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
907
908
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
909
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
910
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
911
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
912
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
913
914
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
915
for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
916
tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
917
}
918
919
for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
920
tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
921
}
922
923
NS_IMPL_CYCLE_COLLECTION_TRACE_END
924
925
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
926
NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
927
928
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
929
NS_INTERFACE_MAP_ENTRY(nsIObserver)
930
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
931
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
932
NS_INTERFACE_MAP_END
933
934
/* static */
935
already_AddRefed<Console> Console::Create(JSContext* aCx,
936
nsPIDOMWindowInner* aWindow,
937
ErrorResult& aRv) {
938
MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
939
940
uint64_t outerWindowID = 0;
941
uint64_t innerWindowID = 0;
942
943
if (aWindow) {
944
innerWindowID = aWindow->WindowID();
945
946
// Without outerwindow any console message coming from this object will not
947
// shown in the devtools webconsole. But this should be fine because
948
// probably we are shutting down, or the window is CCed/GCed.
949
nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
950
if (outerWindow) {
951
outerWindowID = outerWindow->WindowID();
952
}
953
}
954
955
RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow),
956
outerWindowID, innerWindowID);
957
console->Initialize(aRv);
958
if (NS_WARN_IF(aRv.Failed())) {
959
return nullptr;
960
}
961
962
return console.forget();
963
}
964
965
/* static */
966
already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
967
nsIGlobalObject* aGlobal,
968
uint64_t aOuterWindowID,
969
uint64_t aInnerWindowID,
970
ErrorResult& aRv) {
971
WorkletThread::AssertIsOnWorkletThread();
972
973
RefPtr<Console> console =
974
new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID);
975
console->Initialize(aRv);
976
if (NS_WARN_IF(aRv.Failed())) {
977
return nullptr;
978
}
979
980
return console.forget();
981
}
982
983
Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
984
uint64_t aOuterWindowID, uint64_t aInnerWindowID)
985
: mGlobal(aGlobal),
986
mOuterID(aOuterWindowID),
987
mInnerID(aInnerWindowID),
988
mDumpToStdout(false),
989
mChromeInstance(false),
990
mMaxLogLevel(ConsoleLogLevel::All),
991
mStatus(eUnknown),
992
mCreationTimeStamp(TimeStamp::Now()) {
993
// Let's enable the dumping to stdout by default for chrome.
994
if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
995
mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
996
} else {
997
mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
998
}
999
1000
mozilla::HoldJSObjects(this);
1001
}
1002
1003
Console::~Console() {
1004
AssertIsOnOwningThread();
1005
Shutdown();
1006
mozilla::DropJSObjects(this);
1007
}
1008
1009
void Console::Initialize(ErrorResult& aRv) {
1010
AssertIsOnOwningThread();
1011
MOZ_ASSERT(mStatus == eUnknown);
1012
1013
if (NS_IsMainThread()) {
1014
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1015
if (NS_WARN_IF(!obs)) {
1016
aRv.Throw(NS_ERROR_FAILURE);
1017
return;
1018
}
1019
1020
if (mInnerID) {
1021
aRv = obs->AddObserver(this, "inner-window-destroyed", true);
1022
if (NS_WARN_IF(aRv.Failed())) {
1023
return;
1024
}
1025
}
1026
1027
aRv = obs->AddObserver(this, "memory-pressure", true);
1028
if (NS_WARN_IF(aRv.Failed())) {
1029
return;
1030
}
1031
}
1032
1033
mStatus = eInitialized;
1034
}
1035
1036
void Console::Shutdown() {
1037
AssertIsOnOwningThread();
1038
1039
if (mStatus == eUnknown || mStatus == eShuttingDown) {
1040
return;
1041
}
1042
1043
if (NS_IsMainThread()) {
1044
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1045
if (obs) {
1046
obs->RemoveObserver(this, "inner-window-destroyed");
1047
obs->RemoveObserver(this, "memory-pressure");
1048
}
1049
}
1050
1051
NS_ReleaseOnMainThreadSystemGroup("Console::mStorage", mStorage.forget());
1052
NS_ReleaseOnMainThreadSystemGroup("Console::mSandbox", mSandbox.forget());
1053
1054
mTimerRegistry.Clear();
1055
mCounterRegistry.Clear();
1056
1057
mCallDataStorage.Clear();
1058
mCallDataStoragePending.Clear();
1059
1060
mStatus = eShuttingDown;
1061
}
1062
1063
NS_IMETHODIMP
1064
Console::Observe(nsISupports* aSubject, const char* aTopic,
1065
const char16_t* aData) {
1066
AssertIsOnMainThread();
1067
1068
if (!strcmp(aTopic, "inner-window-destroyed")) {
1069
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
1070
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
1071
1072
uint64_t innerID;
1073
nsresult rv = wrapper->GetData(&innerID);
1074
NS_ENSURE_SUCCESS(rv, rv);
1075
1076
if (innerID == mInnerID) {
1077
Shutdown();
1078
}
1079
1080
return NS_OK;
1081
}
1082
1083
if (!strcmp(aTopic, "memory-pressure")) {
1084
ClearStorage();
1085
return NS_OK;
1086
}
1087
1088
return NS_OK;
1089
}
1090
1091
void Console::ClearStorage() { mCallDataStorage.Clear(); }
1092
1093
#define METHOD(name, string) \
1094
/* static */ void Console::name(const GlobalObject& aGlobal, \
1095
const Sequence<JS::Value>& aData) { \
1096
Method(aGlobal, Method##name, NS_LITERAL_STRING(string), aData); \
1097
}
1098
1099
METHOD(Log, "log")
1100
METHOD(Info, "info")
1101
METHOD(Warn, "warn")
1102
METHOD(Error, "error")
1103
METHOD(Exception, "exception")
1104
METHOD(Debug, "debug")
1105
METHOD(Table, "table")
1106
METHOD(Trace, "trace")
1107
1108
// Displays an interactive listing of all the properties of an object.
1109
METHOD(Dir, "dir");
1110
METHOD(Dirxml, "dirxml");
1111
1112
METHOD(Group, "group")
1113
METHOD(GroupCollapsed, "groupCollapsed")
1114
1115
#undef METHOD
1116
1117
/* static */
1118
void Console::Clear(const GlobalObject& aGlobal) {
1119
const Sequence<JS::Value> data;
1120
Method(aGlobal, MethodClear, NS_LITERAL_STRING("clear"), data);
1121
}
1122
1123
/* static */
1124
void Console::GroupEnd(const GlobalObject& aGlobal) {
1125
const Sequence<JS::Value> data;
1126
Method(aGlobal, MethodGroupEnd, NS_LITERAL_STRING("groupEnd"), data);
1127
}
1128
1129
/* static */
1130
void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
1131
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime,
1132
NS_LITERAL_STRING("time"));
1133
}
1134
1135
/* static */
1136
void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
1137
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
1138
NS_LITERAL_STRING("timeEnd"));
1139
}
1140
1141
/* static */
1142
void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
1143
const Sequence<JS::Value>& aData) {
1144
StringMethod(aGlobal, aLabel, aData, MethodTimeLog,
1145
NS_LITERAL_STRING("timeLog"));
1146
}
1147
1148
/* static */
1149
void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
1150
const Sequence<JS::Value>& aData,
1151
MethodName aMethodName,
1152
const nsAString& aMethodString) {
1153
RefPtr<Console> console = GetConsole(aGlobal);
1154
if (!console) {
1155
return;
1156
}
1157
1158
console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
1159
aMethodString);
1160
}
1161
1162
void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
1163
const Sequence<JS::Value>& aData,
1164
MethodName aMethodName,
1165
const nsAString& aMethodString) {
1166
ConsoleCommon::ClearException ce(aCx);
1167
1168
Sequence<JS::Value> data;
1169
SequenceRooter<JS::Value> rooter(aCx, &data);
1170
1171
JS::Rooted<JS::Value> value(aCx);
1172
if (!dom::ToJSValue(aCx, aLabel, &value)) {
1173
return;
1174
}
1175
1176
if (!data.AppendElement(value, fallible)) {
1177
return;
1178
}
1179
1180
for (uint32_t i = 0; i < aData.Length(); ++i) {
1181
if (!data.AppendElement(aData[i], fallible)) {
1182
return;
1183
}
1184
}
1185
1186
MethodInternal(aCx, aMethodName, aMethodString, data);
1187
}
1188
1189
/* static */
1190
void Console::TimeStamp(const GlobalObject& aGlobal,
1191
const JS::Handle<JS::Value> aData) {
1192
JSContext* cx = aGlobal.Context();
1193
1194
ConsoleCommon::ClearException ce(cx);
1195
1196
Sequence<JS::Value> data;
1197
SequenceRooter<JS::Value> rooter(cx, &data);
1198
1199
if (aData.isString() && !data.AppendElement(aData, fallible)) {
1200
return;
1201
}
1202
1203
Method(aGlobal, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
1204
}
1205
1206
/* static */
1207
void Console::Profile(const GlobalObject& aGlobal,
1208
const Sequence<JS::Value>& aData) {
1209
ProfileMethod(aGlobal, MethodProfile, NS_LITERAL_STRING("profile"), aData);
1210
}
1211
1212
/* static */
1213
void Console::ProfileEnd(const GlobalObject& aGlobal,
1214
const Sequence<JS::Value>& aData) {
1215
ProfileMethod(aGlobal, MethodProfileEnd, NS_LITERAL_STRING("profileEnd"),
1216
aData);
1217
}
1218
1219
/* static */
1220
void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
1221
const nsAString& aAction,
1222
const Sequence<JS::Value>& aData) {
1223
RefPtr<Console> console = GetConsole(aGlobal);
1224
if (!console) {
1225
return;
1226
}
1227
1228
JSContext* cx = aGlobal.Context();
1229
console->ProfileMethodInternal(cx, aName, aAction, aData);
1230
}
1231
1232
bool Console::IsEnabled(JSContext* aCx) const {
1233
// Console is always enabled if it is a custom Chrome-Only instance.
1234
if (mChromeInstance) {
1235
return true;
1236
}
1237
1238
// Make all Console API no-op if DevTools aren't enabled.
1239
return StaticPrefs::devtools_enabled();
1240
}
1241
1242
void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
1243
const nsAString& aAction,
1244
const Sequence<JS::Value>& aData) {
1245
if (!IsEnabled(aCx)) {
1246
return;
1247
}
1248
1249
if (!ShouldProceed(aMethodName)) {
1250
return;
1251
}
1252
1253
MaybeExecuteDumpFunction(aCx, aAction, aData, nullptr);
1254
1255
if (WorkletThread::IsOnWorkletThread()) {
1256
RefPtr<ConsoleProfileWorkletRunnable> runnable =
1257
ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
1258
aData);
1259
if (!runnable) {
1260
return;
1261
}
1262
1263
NS_DispatchToMainThread(runnable.forget());
1264
return;
1265
}
1266
1267
if (!NS_IsMainThread()) {
1268
// Here we are in a worker thread.
1269
RefPtr<ConsoleProfileWorkerRunnable> runnable =
1270
new ConsoleProfileWorkerRunnable(this, aMethodName, aAction, aData);
1271
1272
runnable->Dispatch(aCx);
1273
return;
1274
}
1275
1276
ProfileMethodMainthread(aCx, aAction, aData);
1277
}
1278
1279
// static
1280
void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction,
1281
const Sequence<JS::Value>& aData) {
1282
MOZ_ASSERT(NS_IsMainThread());
1283
ConsoleCommon::ClearException ce(aCx);
1284
1285
RootedDictionary<ConsoleProfileEvent> event(aCx);
1286
event.mAction = aAction;
1287
event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1288
1289
event.mArguments.Construct();
1290
Sequence<JS::Value>& sequence = event.mArguments.Value();
1291
1292
for (uint32_t i = 0; i < aData.Length(); ++i) {
1293
if (!sequence.AppendElement(aData[i], fallible)) {
1294
return;
1295
}
1296
}
1297
1298
JS::Rooted<JS::Value> eventValue(aCx);
1299
if (!ToJSValue(aCx, event, &eventValue)) {
1300
return;
1301
}
1302
1303
JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1304
MOZ_ASSERT(eventObj);
1305
1306
if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1307
JSPROP_ENUMERATE)) {
1308
return;
1309
}
1310
1311
nsIXPConnect* xpc = nsContentUtils::XPConnect();
1312
nsCOMPtr<nsISupports> wrapper;
1313
const nsIID& iid = NS_GET_IID(nsISupports);
1314
1315
if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
1316
return;
1317
}
1318
1319
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1320
if (obs) {
1321
obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1322
}
1323
}
1324
1325
/* static */
1326
void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1327
const Sequence<JS::Value>& aData) {
1328
if (!aCondition) {
1329
Method(aGlobal, MethodAssert, NS_LITERAL_STRING("assert"), aData);
1330
}
1331
}
1332
1333
/* static */
1334
void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
1335
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
1336
NS_LITERAL_STRING("count"));
1337
}
1338
1339
/* static */
1340
void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
1341
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
1342
NS_LITERAL_STRING("countReset"));
1343
}
1344
1345
namespace {
1346
1347
void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
1348
ConsoleStackEntry& aStackEntry) {
1349
MOZ_ASSERT(aStackFrame);
1350
1351
aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
1352
1353
aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx);
1354
aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx);
1355
aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx);
1356
1357
aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
1358
1359
nsString cause;
1360
aStackFrame->GetAsyncCause(aCx, cause);
1361
if (!cause.IsEmpty()) {
1362
aStackEntry.mAsyncCause.Construct(cause);
1363
}
1364
}
1365
1366
void ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
1367
nsTArray<ConsoleStackEntry>& aRefiedStack) {
1368
nsCOMPtr<nsIStackFrame> stack(aStack);
1369
1370
while (stack) {
1371
ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1372
StackFrameToStackEntry(aCx, stack, data);
1373
1374
nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);
1375
1376
if (!caller) {
1377
caller = stack->GetAsyncCaller(aCx);
1378
}
1379
stack.swap(caller);
1380
}
1381
}
1382
1383
} // anonymous namespace
1384
1385
// Queue a call to a console method. See the CALL_DELAY constant.
1386
/* static */
1387
void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
1388
const nsAString& aMethodString,
1389
const Sequence<JS::Value>& aData) {
1390
RefPtr<Console> console = GetConsole(aGlobal);
1391
if (!console) {
1392
return;
1393
}
1394
1395
console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData);
1396
}
1397
1398
void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
1399
const nsAString& aMethodString,
1400
const Sequence<JS::Value>& aData) {
1401
if (!IsEnabled(aCx)) {
1402
return;
1403
}
1404
1405
if (!ShouldProceed(aMethodName)) {
1406
return;
1407
}
1408
1409
AssertIsOnOwningThread();
1410
1411
RefPtr<ConsoleCallData> callData(new ConsoleCallData());
1412
1413
ConsoleCommon::ClearException ce(aCx);
1414
1415
if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString, aData,
1416
this))) {
1417
return;
1418
}
1419
1420
OriginAttributes oa;
1421
1422
if (NS_IsMainThread()) {
1423
if (mGlobal) {
1424
// Save the principal's OriginAttributes in the console event data
1425
// so that we will be able to filter messages by origin attributes.
1426
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
1427
if (NS_WARN_IF(!sop)) {
1428
return;
1429
}
1430
1431
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1432
if (NS_WARN_IF(!principal)) {
1433
return;
1434
}
1435
1436
oa = principal->OriginAttributesRef();
1437
callData->SetAddonId(principal);
1438
1439
#ifdef DEBUG
1440
if (!principal->IsSystemPrincipal()) {
1441
nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
1442
if (webNav) {
1443
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1444
MOZ_ASSERT(loadContext);
1445
1446
bool pb;
1447
if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
1448
MOZ_ASSERT(pb == !!oa.mPrivateBrowsingId);
1449
}
1450
}
1451
}
1452
#endif
1453
}
1454
} else if (WorkletThread::IsOnWorkletThread()) {
1455
nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
1456
MOZ_ASSERT(global);
1457
oa = global->Impl()->OriginAttributesRef();
1458
} else {
1459
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1460
MOZ_ASSERT(workerPrivate);
1461
oa = workerPrivate->GetOriginAttributes();
1462
}
1463
1464
callData->SetOriginAttributes(oa);
1465
1466
JS::StackCapture captureMode =
1467
ShouldIncludeStackTrace(aMethodName)
1468
? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH))
1469
: JS::StackCapture(JS::FirstSubsumedFrame(aCx));
1470
nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode));
1471
1472
if (stack) {
1473
callData->mTopStackFrame.emplace();
1474
StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
1475
}
1476
1477
if (NS_IsMainThread()) {
1478
callData->mStack = stack;
1479
} else {
1480
// nsIStackFrame is not threadsafe, so we need to snapshot it now,
1481
// before we post our runnable to the main thread.
1482
callData->mReifiedStack.emplace();
1483
ReifyStack(aCx, stack, *callData->mReifiedStack);
1484
}
1485
1486
DOMHighResTimeStamp monotonicTimer;
1487
1488
// Monotonic timer for 'time', 'timeLog' and 'timeEnd'
1489
if ((aMethodName == MethodTime || aMethodName == MethodTimeLog ||
1490
aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) &&
1491
!MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
1492
return;
1493
}
1494
1495
if (aMethodName == MethodTime && !aData.IsEmpty()) {
1496
callData->mStartTimerStatus =
1497
StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel,
1498
&callData->mStartTimerValue);
1499
}
1500
1501
else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
1502
callData->mLogTimerStatus =
1503
LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1504
&callData->mLogTimerDuration, true /* Cancel timer */);
1505
}
1506
1507
else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
1508
callData->mLogTimerStatus =
1509
LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
1510
&callData->mLogTimerDuration, false /* Cancel timer */);
1511
}
1512
1513
else if (aMethodName == MethodCount) {
1514
callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
1515
if (!callData->mCountValue) {
1516
return;
1517
}
1518
}
1519
1520
else if (aMethodName == MethodCountReset) {
1521
callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
1522
if (callData->mCountLabel.IsEmpty()) {
1523
return;
1524
}
1525
}
1526
1527
// Before processing this CallData differently, it's time to call the dump
1528
// function.
1529
if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
1530
MaybeExecuteDumpFunction(aCx, aMethodString, aData, stack);
1531
} else if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd) &&
1532
!aData.IsEmpty()) {
1533
MaybeExecuteDumpFunctionForTime(aCx, aMethodName, aMethodString,
1534
monotonicTimer, aData[0]);
1535
} else {
1536
MaybeExecuteDumpFunction(aCx, aMethodString, aData, nullptr);
1537
}
1538
1539
if (NS_IsMainThread()) {
1540
if (mInnerID) {
1541
callData->SetIDs(mOuterID, mInnerID);
1542
} else if (!mPassedInnerID.IsEmpty()) {
1543
callData->SetIDs(NS_LITERAL_STRING("jsm"), mPassedInnerID);
1544
} else {
1545
nsAutoString filename;
1546
if (callData->mTopStackFrame.isSome()) {
1547
filename = callData->mTopStackFrame->mFilename;
1548
}
1549
1550
callData->SetIDs(NS_LITERAL_STRING("jsm"), filename);
1551
}
1552
1553
ProcessCallData(aCx, callData, aData);
1554
1555
// Just because we don't want to expose
1556
// retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1557
// cleanup the mCallDataStorage:
1558
UnstoreCallData(callData);
1559
return;
1560
}
1561
1562
if (WorkletThread::IsOnWorkletThread()) {
1563
RefPtr<ConsoleCallDataWorkletRunnable> runnable =
1564
ConsoleCallDataWorkletRunnable::Create(aCx, this, callData);
1565
if (!runnable) {
1566
return;
1567
}
1568
1569
NS_DispatchToMainThread(runnable);
1570
return;
1571
}
1572
1573
// We do this only in workers for now.
1574
NotifyHandler(aCx, aData, callData);
1575
1576
RefPtr<ConsoleCallDataWorkerRunnable> runnable =
1577
new ConsoleCallDataWorkerRunnable(this, callData);
1578
Unused << NS_WARN_IF(!runnable->Dispatch(aCx));
1579
}
1580
1581
// We store information to lazily compute the stack in the reserved slots of
1582
// LazyStackGetter. The first slot always stores a JS object: it's either the
1583
// JS wrapper of the nsIStackFrame or the actual reified stack representation.
1584
// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1585
// reified the stack yet, or an UndefinedValue() otherwise.
1586
enum { SLOT_STACKOBJ, SLOT_RAW_STACK };
1587
1588
bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
1589
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1590
JS::Rooted<JSObject*> callee(aCx, &args.callee());
1591
1592
JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1593
if (v.isUndefined()) {
1594
// Already reified.
1595
args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1596
return true;
1597
}
1598
1599
nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1600
nsTArray<ConsoleStackEntry> reifiedStack;
1601
ReifyStack(aCx, stack, reifiedStack);
1602
1603
JS::Rooted<JS::Value> stackVal(aCx);
1604
if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
1605
return false;
1606
}
1607
1608
MOZ_ASSERT(stackVal.isObject());
1609
1610
js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1611
js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1612
1613
args.rval().set(stackVal);
1614
return true;
1615
}
1616
1617
void Console::ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
1618
const Sequence<JS::Value>& aArguments) {
1619
AssertIsOnMainThread();
1620
MOZ_ASSERT(aData);
1621
1622
JS::Rooted<JS::Value> eventValue(aCx);
1623
1624
// We want to create a console event object and pass it to our
1625
// nsIConsoleAPIStorage implementation. We want to define some accessor
1626
// properties on this object, and those will need to keep an nsIStackFrame
1627
// alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1628
// further, passing untrusted objects to system code is likely to run afoul of
1629
// Object Xrays. So we want to wrap in a system-principal scope here. But
1630
// which one? We could cheat and try to get the underlying JSObject* of
1631
// mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1632
// with explicit permission from the XPConnect module owner. If you're
1633
// tempted to do that anywhere else, talk to said module owner first.
1634
1635
// aCx and aArguments are in the same compartment.
1636
JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
1637
if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(
1638
aCx, aArguments, targetScope, &eventValue, aData))) {
1639
return;
1640
}
1641
1642
if (!mStorage) {
1643
mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1644
}
1645
1646
if (!mStorage) {
1647
NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1648
return;
1649
}
1650
1651
nsAutoString innerID, outerID;
1652
1653
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1654
if (aData->mIDType == ConsoleCallData::eString) {
1655
outerID = aData->mOuterIDString;
1656
innerID = aData->mInnerIDString;
1657
} else {
1658
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1659
outerID.AppendInt(aData->mOuterIDNumber);
1660
innerID.AppendInt(aData->mInnerIDNumber);
1661
}
1662
1663
if (aData->mMethodName == MethodClear) {
1664
DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
1665
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
1666
}
1667
1668
if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
1669
NS_WARNING("Failed to record a console event.");
1670
}
1671
}
1672
1673
bool Console::PopulateConsoleNotificationInTheTargetScope(
1674
JSContext* aCx, const Sequence<JS::Value>& aArguments,
1675
JS::Handle<JSObject*> aTargetScope,
1676
JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData) {
1677
MOZ_ASSERT(aCx);
1678
MOZ_ASSERT(aData);
1679
MOZ_ASSERT(aTargetScope);
1680
MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));
1681
1682
ConsoleStackEntry frame;
1683
if (aData->mTopStackFrame) {
1684
frame = *aData->mTopStackFrame;
1685
}
1686
1687
ConsoleCommon::ClearException ce(aCx);
1688
RootedDictionary<ConsoleEvent> event(aCx);
1689
1690
event.mAddonId = aData->mAddonId;
1691
1692
event.mID.Construct();
1693
event.mInnerID.Construct();
1694
1695
event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);
1696
1697
if (aData->mIDType == ConsoleCallData::eString) {
1698
event.mID.Value().SetAsString() = aData->mOuterIDString;
1699
event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1700
} else if (aData->mIDType == ConsoleCallData::eNumber) {
1701
event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
1702
event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
1703
} else {
1704
// aData->mIDType can be eUnknown when we dispatch notifications via
1705
// mConsoleEventNotifier.
1706
event.mID.Value().SetAsUnsignedLongLong() = 0;
1707
event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
1708
}
1709
1710
event.mConsoleID = mConsoleID;
1711
event.mLevel = aData->mMethodString;
1712
event.mFilename = frame.mFilename;
1713
event.mPrefix = mPrefix;
1714
1715
nsCOMPtr<nsIURI> filenameURI;
1716
nsAutoCString pass;
1717
if (NS_IsMainThread() &&
1718
NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
1719
NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
1720
nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI =
1721
do_QueryInterface(filenameURI);
1722
nsAutoCString spec;
1723
if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
1724
CopyUTF8toUTF16(spec, event.mFilename);
1725
}
1726
}
1727
1728
event.mSourceId = frame.mSourceId;
1729
event.mLineNumber = frame.mLineNumber;
1730
event.mColumnNumber = frame.mColumnNumber;
1731
event.mFunctionName = frame.mFunctionName;
1732
event.mTimeStamp = aData->mTimeStamp;
1733
event.mPrivate = !!aData->mOriginAttributes.mPrivateBrowsingId;
1734
1735
switch (aData->mMethodName) {
1736
case MethodLog:
1737
case MethodInfo:
1738
case MethodWarn:
1739
case MethodError:
1740
case MethodException:
1741
case MethodDebug:
1742
case MethodAssert:
1743
case MethodGroup:
1744
case MethodGroupCollapsed:
1745
event.mArguments.Construct();
1746
event.mStyles.Construct();
1747
if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1748
event.mArguments.Value(),
1749
event.mStyles.Value()))) {
1750
return false;
1751
}
1752
1753
break;
1754
1755
default:
1756
event.mArguments.Construct();
1757
if (NS_WARN_IF(
1758
!ArgumentsToValueList(aArguments, event.mArguments.Value()))) {
1759
return false;
1760
}
1761
}
1762
1763
if (aData->mMethodName == MethodGroup ||
1764
aData->mMethodName == MethodGroupCollapsed) {
1765
ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName);
1766
}
1767
1768
else if (aData->mMethodName == MethodGroupEnd) {
1769
if (!UnstoreGroupName(event.mGroupName)) {
1770
return false;
1771
}
1772
}
1773
1774
else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
1775
event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
1776
aData->mStartTimerStatus);
1777
}
1778
1779
else if ((aData->mMethodName == MethodTimeEnd ||
1780
aData->mMethodName == MethodTimeLog) &&
1781
!aArguments.IsEmpty()) {
1782
event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
1783
aData->mLogTimerDuration,
1784
aData->mLogTimerStatus);
1785
}
1786
1787
else if (aData->mMethodName == MethodCount ||
1788
aData->mMethodName == MethodCountReset) {
1789
event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
1790
aData->mCountValue);
1791
}
1792
1793
JSAutoRealm ar2(aCx, aTargetScope);
1794
1795
if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {