Coverage Report

Created: 2018-09-25 14:53

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