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/ConsoleBinding.h"
9
10 #include "mozilla/dom/BlobBinding.h"
11 #include "mozilla/dom/Exceptions.h"
12 #include "mozilla/dom/File.h"
13 #include "mozilla/dom/FunctionBinding.h"
14 #include "mozilla/dom/Performance.h"
15 #include "mozilla/dom/StructuredCloneHolder.h"
16 #include "mozilla/dom/ToJSValue.h"
17 #include "mozilla/dom/WorkletGlobalScope.h"
18 #include "mozilla/Maybe.h"
19 #include "nsCycleCollectionParticipant.h"
20 #include "nsDocument.h"
21 #include "nsDOMNavigationTiming.h"
22 #include "nsGlobalWindow.h"
23 #include "nsJSUtils.h"
24 #include "nsNetUtil.h"
25 #include "ScriptSettings.h"
26 #include "WorkerPrivate.h"
27 #include "WorkerRunnable.h"
28 #include "WorkerScope.h"
29 #include "xpcpublic.h"
30 #include "nsContentUtils.h"
31 #include "nsDocShell.h"
32 #include "nsProxyRelease.h"
33 #include "mozilla/TimerClamping.h"
34 #include "mozilla/ConsoleTimelineMarker.h"
35 #include "mozilla/TimestampTimelineMarker.h"
36
37 #include "nsIConsoleAPIStorage.h"
38 #include "nsIDOMWindowUtils.h"
39 #include "nsIInterfaceRequestorUtils.h"
40 #include "nsILoadContext.h"
41 #include "nsIProgrammingLanguage.h"
42 #include "nsISensitiveInfoHiddenURI.h"
43 #include "nsIServiceManager.h"
44 #include "nsISupportsPrimitives.h"
45 #include "nsIWebNavigation.h"
46 #include "nsIXPConnect.h"
47
48 // The maximum allowed number of concurrent timers per page.
49 #define MAX_PAGE_TIMERS 10000
50
51 // The maximum allowed number of concurrent counters per page.
52 #define MAX_PAGE_COUNTERS 10000
53
54 // The maximum stacktrace depth when populating the stacktrace array used for
55 // console.trace().
56 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
57
58 // This tags are used in the Structured Clone Algorithm to move js values from
59 // worker thread to main thread
60 #define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN
61
62 // This value is taken from ConsoleAPIStorage.js
63 #define STORAGE_MAX_EVENTS 1000
64
65 using namespace mozilla::dom::exceptions;
66 using namespace mozilla::dom::workers;
67
68 namespace mozilla {
69 namespace dom {
70
71 struct
72 ConsoleStructuredCloneData
73 {
74 nsCOMPtr<nsISupports> mParent;
75 nsTArray<RefPtr<BlobImpl>> mBlobs;
76 };
77
78 /**
79 * Console API in workers uses the Structured Clone Algorithm to move any value
80 * from the worker thread to the main-thread. Some object cannot be moved and,
81 * in these cases, we convert them to strings.
82 * It's not the best, but at least we are able to show something.
83 */
84
85 class ConsoleCallData final
86 {
87 public:
88 NS_INLINE_DECL_REFCOUNTING(ConsoleCallData)
89
ConsoleCallData()90 ConsoleCallData()
91 : mMethodName(Console::MethodLog)
92 , mPrivate(false)
93 , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
94 , mStartTimerValue(0)
95 , mStartTimerStatus(false)
96 , mStopTimerDuration(0)
97 , mStopTimerStatus(false)
98 , mCountValue(MAX_PAGE_COUNTERS)
99 , mIDType(eUnknown)
100 , mOuterIDNumber(0)
101 , mInnerIDNumber(0)
102 , mStatus(eUnused)
103 #ifdef DEBUG
104 , mOwningThread(PR_GetCurrentThread())
105 #endif
106 {}
107
108 bool
Initialize(JSContext * aCx,Console::MethodName aName,const nsAString & aString,const Sequence<JS::Value> & aArguments,Console * aConsole)109 Initialize(JSContext* aCx, Console::MethodName aName,
110 const nsAString& aString,
111 const Sequence<JS::Value>& aArguments,
112 Console* aConsole)
113 {
114 AssertIsOnOwningThread();
115 MOZ_ASSERT(aConsole);
116
117 // We must be registered before doing any JS operation otherwise it can
118 // happen that mCopiedArguments are not correctly traced.
119 aConsole->StoreCallData(this);
120
121 mMethodName = aName;
122 mMethodString = aString;
123
124 mGlobal = JS::CurrentGlobalOrNull(aCx);
125
126 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
127 if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
128 aConsole->UnstoreCallData(this);
129 return false;
130 }
131 }
132
133 return true;
134 }
135
136 void
SetIDs(uint64_t aOuterID,uint64_t aInnerID)137 SetIDs(uint64_t aOuterID, uint64_t aInnerID)
138 {
139 MOZ_ASSERT(mIDType == eUnknown);
140
141 mOuterIDNumber = aOuterID;
142 mInnerIDNumber = aInnerID;
143 mIDType = eNumber;
144 }
145
146 void
SetIDs(const nsAString & aOuterID,const nsAString & aInnerID)147 SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
148 {
149 MOZ_ASSERT(mIDType == eUnknown);
150
151 mOuterIDString = aOuterID;
152 mInnerIDString = aInnerID;
153 mIDType = eString;
154 }
155
156 void
SetOriginAttributes(const PrincipalOriginAttributes & aOriginAttributes)157 SetOriginAttributes(const PrincipalOriginAttributes& aOriginAttributes)
158 {
159 mOriginAttributes = aOriginAttributes;
160 }
161
162 bool
PopulateArgumentsSequence(Sequence<JS::Value> & aSequence) const163 PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const
164 {
165 AssertIsOnOwningThread();
166
167 for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
168 if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
169 fallible))) {
170 return false;
171 }
172 }
173
174 return true;
175 }
176
177 void
Trace(const TraceCallbacks & aCallbacks,void * aClosure)178 Trace(const TraceCallbacks& aCallbacks, void* aClosure)
179 {
180 AssertIsOnOwningThread();
181
182 ConsoleCallData* tmp = this;
183 for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
184 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
185 }
186
187 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal);
188 }
189
190 void
AssertIsOnOwningThread() const191 AssertIsOnOwningThread() const
192 {
193 MOZ_ASSERT(mOwningThread);
194 MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
195 }
196
197 JS::Heap<JSObject*> mGlobal;
198
199 // This is a copy of the arguments we received from the DOM bindings. Console
200 // object traces them because this ConsoleCallData calls
201 // RegisterConsoleCallData() in the Initialize().
202 nsTArray<JS::Heap<JS::Value>> mCopiedArguments;
203
204 Console::MethodName mMethodName;
205 bool mPrivate;
206 int64_t mTimeStamp;
207
208 // These values are set in the owning thread and they contain the timestamp of
209 // when the new timer has started, the name of it and the status of the
210 // creation of it. If status is false, something went wrong. User
211 // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
212 // monotonicTimer from Performance.now();
213 // They will be set on the owning thread and never touched again on that
214 // thread. They will be used in order to create a ConsoleTimerStart dictionary
215 // when console.time() is used.
216 DOMHighResTimeStamp mStartTimerValue;
217 nsString mStartTimerLabel;
218 bool mStartTimerStatus;
219
220 // These values are set in the owning thread and they contain the duration,
221 // the name and the status of the StopTimer method. If status is false,
222 // something went wrong. They will be set on the owning thread and never
223 // touched again on that thread. They will be used in order to create a
224 // ConsoleTimerEnd dictionary. This members are set when
225 // console.timeEnd() is called.
226 double mStopTimerDuration;
227 nsString mStopTimerLabel;
228 bool mStopTimerStatus;
229
230 // These 2 values are set by IncreaseCounter on the owning thread and they are
231 // used CreateCounterValue. These members are set when console.count() is
232 // called.
233 nsString mCountLabel;
234 uint32_t mCountValue;
235
236 // The concept of outerID and innerID is misleading because when a
237 // ConsoleCallData is created from a window, these are the window IDs, but
238 // when the object is created from a SharedWorker, a ServiceWorker or a
239 // subworker of a ChromeWorker these IDs are the type of worker and the
240 // filename of the callee.
241 // In Console.jsm the ID is 'jsm'.
242 enum {
243 eString,
244 eNumber,
245 eUnknown
246 } mIDType;
247
248 uint64_t mOuterIDNumber;
249 nsString mOuterIDString;
250
251 uint64_t mInnerIDNumber;
252 nsString mInnerIDString;
253
254 PrincipalOriginAttributes mOriginAttributes;
255
256 nsString mMethodString;
257
258 // Stack management is complicated, because we want to do it as
259 // lazily as possible. Therefore, we have the following behavior:
260 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
261 // 2) mReifiedStack is initialized if we're created in a worker.
262 // 3) mStack is set (possibly to null if there is no JS on the stack) if
263 // we're created on main thread.
264 Maybe<ConsoleStackEntry> mTopStackFrame;
265 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
266 nsCOMPtr<nsIStackFrame> mStack;
267
268 // mStatus is about the lifetime of this object. Console must take care of
269 // keep it alive or not following this enumeration.
270 enum {
271 // If the object is created but it is owned by some runnable, this is its
272 // status. It can be deleted at any time.
273 eUnused,
274
275 // When a runnable takes ownership of a ConsoleCallData and send it to
276 // different thread, this is its status. Console cannot delete it at this
277 // time.
278 eInUse,
279
280 // When a runnable owns this ConsoleCallData, we can't delete it directly.
281 // instead, we mark it with this new status and we move it in
282 // mCallDataStoragePending list in order to keep it alive an trace it
283 // correctly. Once the runnable finishs its task, it will delete this object
284 // calling ReleaseCallData().
285 eToBeDeleted
286 } mStatus;
287
288 #ifdef DEBUG
289 PRThread* mOwningThread;
290 #endif
291
292 private:
~ConsoleCallData()293 ~ConsoleCallData()
294 {
295 AssertIsOnOwningThread();
296 MOZ_ASSERT(mStatus != eInUse);
297 }
298 };
299
300 // This class is used to clear any exception at the end of this method.
301 class ClearException
302 {
303 public:
ClearException(JSContext * aCx)304 explicit ClearException(JSContext* aCx)
305 : mCx(aCx)
306 {
307 }
308
~ClearException()309 ~ClearException()
310 {
311 JS_ClearPendingException(mCx);
312 }
313
314 private:
315 JSContext* mCx;
316 };
317
318 class ConsoleRunnable : public WorkerProxyToMainThreadRunnable
319 , public StructuredCloneHolderBase
320 {
321 public:
ConsoleRunnable(Console * aConsole)322 explicit ConsoleRunnable(Console* aConsole)
323 : WorkerProxyToMainThreadRunnable(GetCurrentThreadWorkerPrivate())
324 , mConsole(aConsole)
325 {}
326
327 virtual
~ConsoleRunnable()328 ~ConsoleRunnable()
329 {
330 // Clear the StructuredCloneHolderBase class.
331 Clear();
332 }
333
334 bool
Dispatch(JSContext * aCx)335 Dispatch(JSContext* aCx)
336 {
337 mWorkerPrivate->AssertIsOnWorkerThread();
338
339 if (NS_WARN_IF(!PreDispatch(aCx))) {
340 RunBackOnWorkerThread();
341 return false;
342 }
343
344 if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch())) {
345 // RunBackOnWorkerThread() will be called by
346 // WorkerProxyToMainThreadRunnable::Dispatch().
347 return false;
348 }
349
350 return true;
351 }
352
353 protected:
354 void
RunOnMainThread()355 RunOnMainThread() override
356 {
357 AssertIsOnMainThread();
358
359 // Walk up to our containing page
360 WorkerPrivate* wp = mWorkerPrivate;
361 while (wp->GetParent()) {
362 wp = wp->GetParent();
363 }
364
365 nsPIDOMWindowInner* window = wp->GetWindow();
366 if (!window) {
367 RunWindowless();
368 } else {
369 RunWithWindow(window);
370 }
371 }
372
373 void
RunWithWindow(nsPIDOMWindowInner * aWindow)374 RunWithWindow(nsPIDOMWindowInner* aWindow)
375 {
376 AssertIsOnMainThread();
377
378 AutoJSAPI jsapi;
379 MOZ_ASSERT(aWindow);
380
381 RefPtr<nsGlobalWindow> win = nsGlobalWindow::Cast(aWindow);
382 if (NS_WARN_IF(!jsapi.Init(win))) {
383 return;
384 }
385
386 MOZ_ASSERT(aWindow->IsInnerWindow());
387 nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
388 if (NS_WARN_IF(!outerWindow)) {
389 return;
390 }
391
392 RunConsole(jsapi.cx(), outerWindow, aWindow);
393 }
394
395 void
RunWindowless()396 RunWindowless()
397 {
398 AssertIsOnMainThread();
399
400 WorkerPrivate* wp = mWorkerPrivate;
401 while (wp->GetParent()) {
402 wp = wp->GetParent();
403 }
404
405 MOZ_ASSERT(!wp->GetWindow());
406
407 AutoSafeJSContext cx;
408
409 JS::Rooted<JSObject*> global(cx, mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal()));
410 if (NS_WARN_IF(!global)) {
411 return;
412 }
413
414 // The CreateSandbox call returns a proxy to the actual sandbox object. We
415 // don't need a proxy here.
416 global = js::UncheckedUnwrap(global);
417
418 JSAutoCompartment ac(cx, global);
419
420 RunConsole(cx, nullptr, nullptr);
421 }
422
423 void
RunBackOnWorkerThread()424 RunBackOnWorkerThread() override
425 {
426 mWorkerPrivate->AssertIsOnWorkerThread();
427 ReleaseData();
428 mConsole = nullptr;
429 }
430
431 // This method is called in the owning thread of the Console object.
432 virtual bool
433 PreDispatch(JSContext* aCx) = 0;
434
435 // This method is called in the main-thread.
436 virtual void
437 RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
438 nsPIDOMWindowInner* aInnerWindow) = 0;
439
440 // This method is called in the owning thread of the Console object.
441 virtual void
442 ReleaseData() = 0;
443
CustomReadHandler(JSContext * aCx,JSStructuredCloneReader * aReader,uint32_t aTag,uint32_t aIndex)444 virtual JSObject* CustomReadHandler(JSContext* aCx,
445 JSStructuredCloneReader* aReader,
446 uint32_t aTag,
447 uint32_t aIndex) override
448 {
449 AssertIsOnMainThread();
450
451 if (aTag == CONSOLE_TAG_BLOB) {
452 MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);
453
454 JS::Rooted<JS::Value> val(aCx);
455 {
456 RefPtr<Blob> blob =
457 Blob::Create(mClonedData.mParent, mClonedData.mBlobs.ElementAt(aIndex));
458 if (!ToJSValue(aCx, blob, &val)) {
459 return nullptr;
460 }
461 }
462
463 return &val.toObject();
464 }
465
466 MOZ_CRASH("No other tags are supported.");
467 return nullptr;
468 }
469
CustomWriteHandler(JSContext * aCx,JSStructuredCloneWriter * aWriter,JS::Handle<JSObject * > aObj)470 virtual bool CustomWriteHandler(JSContext* aCx,
471 JSStructuredCloneWriter* aWriter,
472 JS::Handle<JSObject*> aObj) override
473 {
474 RefPtr<Blob> blob;
475 if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
476 blob->Impl()->MayBeClonedToOtherThreads()) {
477 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
478 mClonedData.mBlobs.Length()))) {
479 return false;
480 }
481
482 mClonedData.mBlobs.AppendElement(blob->Impl());
483 return true;
484 }
485
486 if (!JS_ObjectNotWritten(aWriter, aObj)) {
487 return false;
488 }
489
490 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
491 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
492 if (NS_WARN_IF(!jsString)) {
493 return false;
494 }
495
496 if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
497 return false;
498 }
499
500 return true;
501 }
502
503 // This must be released on the worker thread.
504 RefPtr<Console> mConsole;
505
506 ConsoleStructuredCloneData mClonedData;
507 };
508
509 // This runnable appends a CallData object into the Console queue running on
510 // the main-thread.
511 class ConsoleCallDataRunnable final : public ConsoleRunnable
512 {
513 public:
ConsoleCallDataRunnable(Console * aConsole,ConsoleCallData * aCallData)514 ConsoleCallDataRunnable(Console* aConsole,
515 ConsoleCallData* aCallData)
516 : ConsoleRunnable(aConsole)
517 , mCallData(aCallData)
518 {
519 MOZ_ASSERT(aCallData);
520 mWorkerPrivate->AssertIsOnWorkerThread();
521 mCallData->AssertIsOnOwningThread();
522 }
523
524 private:
~ConsoleCallDataRunnable()525 ~ConsoleCallDataRunnable()
526 {
527 MOZ_ASSERT(!mCallData);
528 }
529
530 bool
PreDispatch(JSContext * aCx)531 PreDispatch(JSContext* aCx) override
532 {
533 mWorkerPrivate->AssertIsOnWorkerThread();
534 mCallData->AssertIsOnOwningThread();
535
536 ClearException ce(aCx);
537
538 JS::Rooted<JSObject*> arguments(aCx,
539 JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
540 if (NS_WARN_IF(!arguments)) {
541 return false;
542 }
543
544 JS::Rooted<JS::Value> arg(aCx);
545 for (uint32_t i = 0; i < mCallData->mCopiedArguments.Length(); ++i) {
546 arg = mCallData->mCopiedArguments[i];
547 if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
548 JSPROP_ENUMERATE))) {
549 return false;
550 }
551 }
552
553 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
554
555 if (NS_WARN_IF(!Write(aCx, value))) {
556 return false;
557 }
558
559 mCallData->mStatus = ConsoleCallData::eInUse;
560 return true;
561 }
562
563 void
RunConsole(JSContext * aCx,nsPIDOMWindowOuter * aOuterWindow,nsPIDOMWindowInner * aInnerWindow)564 RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
565 nsPIDOMWindowInner* aInnerWindow) override
566 {
567 AssertIsOnMainThread();
568
569 // The windows have to run in parallel.
570 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
571
572 if (aOuterWindow) {
573 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
574
575 // Save the principal's OriginAttributes in the console event data
576 // so that we will be able to filter messages by origin attributes.
577 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aInnerWindow);
578 if (NS_WARN_IF(!sop)) {
579 return;
580 }
581
582 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
583 if (NS_WARN_IF(!principal)) {
584 return;
585 }
586
587 mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
588 } else {
589 ConsoleStackEntry frame;
590 if (mCallData->mTopStackFrame) {
591 frame = *mCallData->mTopStackFrame;
592 }
593
594 nsString id = frame.mFilename;
595 nsString innerID;
596 if (mWorkerPrivate->IsSharedWorker()) {
597 innerID = NS_LITERAL_STRING("SharedWorker");
598 } else if (mWorkerPrivate->IsServiceWorker()) {
599 innerID = NS_LITERAL_STRING("ServiceWorker");
600 // Use scope as ID so the webconsole can decide if the message should
601 // show up per tab
602 id.AssignWithConversion(mWorkerPrivate->WorkerName());
603 } else {
604 innerID = NS_LITERAL_STRING("Worker");
605 }
606
607 mCallData->SetIDs(id, innerID);
608
609 // Save the principal's OriginAttributes in the console event data
610 // so that we will be able to filter messages by origin attributes.
611 nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal();
612 if (NS_WARN_IF(!principal)) {
613 return;
614 }
615
616 mCallData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
617 }
618
619 // Now we could have the correct window (if we are not window-less).
620 mClonedData.mParent = aInnerWindow;
621
622 ProcessCallData(aCx);
623
624 mClonedData.mParent = nullptr;
625 }
626
627 virtual void
ReleaseData()628 ReleaseData() override
629 {
630 mConsole->AssertIsOnOwningThread();
631
632 if (mCallData->mStatus == ConsoleCallData::eToBeDeleted) {
633 mConsole->ReleaseCallData(mCallData);
634 } else {
635 MOZ_ASSERT(mCallData->mStatus == ConsoleCallData::eInUse);
636 mCallData->mStatus = ConsoleCallData::eUnused;
637 }
638
639 mCallData = nullptr;
640 }
641
642 void
ProcessCallData(JSContext * aCx)643 ProcessCallData(JSContext* aCx)
644 {
645 AssertIsOnMainThread();
646
647 ClearException ce(aCx);
648
649 JS::Rooted<JS::Value> argumentsValue(aCx);
650 if (!Read(aCx, &argumentsValue)) {
651 return;
652 }
653
654 MOZ_ASSERT(argumentsValue.isObject());
655
656 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
657
658 uint32_t length;
659 if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
660 return;
661 }
662
663 Sequence<JS::Value> values;
664 SequenceRooter<JS::Value> arguments(aCx, &values);
665
666 for (uint32_t i = 0; i < length; ++i) {
667 JS::Rooted<JS::Value> value(aCx);
668
669 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
670 return;
671 }
672
673 if (!values.AppendElement(value, fallible)) {
674 return;
675 }
676 }
677
678 MOZ_ASSERT(values.Length() == length);
679
680 mConsole->ProcessCallData(aCx, mCallData, values);
681 }
682
683 RefPtr<ConsoleCallData> mCallData;
684 };
685
686 // This runnable calls ProfileMethod() on the console on the main-thread.
687 class ConsoleProfileRunnable final : public ConsoleRunnable
688 {
689 public:
ConsoleProfileRunnable(Console * aConsole,const nsAString & aAction,const Sequence<JS::Value> & aArguments)690 ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
691 const Sequence<JS::Value>& aArguments)
692 : ConsoleRunnable(aConsole)
693 , mAction(aAction)
694 , mArguments(aArguments)
695 {
696 MOZ_ASSERT(aConsole);
697 }
698
699 private:
700 bool
PreDispatch(JSContext * aCx)701 PreDispatch(JSContext* aCx) override
702 {
703 ClearException ce(aCx);
704
705 JS::Rooted<JSObject*> arguments(aCx,
706 JS_NewArrayObject(aCx, mArguments.Length()));
707 if (NS_WARN_IF(!arguments)) {
708 return false;
709 }
710
711 JS::Rooted<JS::Value> arg(aCx);
712 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
713 arg = mArguments[i];
714 if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
715 JSPROP_ENUMERATE))) {
716 return false;
717 }
718 }
719
720 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
721
722 if (NS_WARN_IF(!Write(aCx, value))) {
723 return false;
724 }
725
726 return true;
727 }
728
729 void
RunConsole(JSContext * aCx,nsPIDOMWindowOuter * aOuterWindow,nsPIDOMWindowInner * aInnerWindow)730 RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
731 nsPIDOMWindowInner* aInnerWindow) override
732 {
733 AssertIsOnMainThread();
734
735 ClearException ce(aCx);
736
737 // Now we could have the correct window (if we are not window-less).
738 mClonedData.mParent = aInnerWindow;
739
740 JS::Rooted<JS::Value> argumentsValue(aCx);
741 bool ok = Read(aCx, &argumentsValue);
742 mClonedData.mParent = nullptr;
743
744 if (!ok) {
745 return;
746 }
747
748 MOZ_ASSERT(argumentsValue.isObject());
749 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
750
751 uint32_t length;
752 if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
753 return;
754 }
755
756 Sequence<JS::Value> arguments;
757
758 for (uint32_t i = 0; i < length; ++i) {
759 JS::Rooted<JS::Value> value(aCx);
760
761 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
762 return;
763 }
764
765 if (!arguments.AppendElement(value, fallible)) {
766 return;
767 }
768 }
769
770 mConsole->ProfileMethodInternal(aCx, mAction, arguments);
771 }
772
773 virtual void
ReleaseData()774 ReleaseData() override
775 {}
776
777 nsString mAction;
778
779 // This is a reference of the sequence of arguments we receive from the DOM
780 // bindings and it's rooted by them. It's only used on the owning thread in
781 // PreDispatch().
782 const Sequence<JS::Value>& mArguments;
783 };
784
785 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
786
787 // We don't need to traverse/unlink mStorage and mSandbox because they are not
788 // CCed objects and they are only used on the main thread, even when this
789 // Console object is used on workers.
790
791 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
792 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
793 NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
794 tmp->Shutdown();
795 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
796
797 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
798 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
799 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
800 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
801 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
802
803 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
804 for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
805 tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
806 }
807
808 for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
809 tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
810 }
811
812 NS_IMPL_CYCLE_COLLECTION_TRACE_END
813
NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)814 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
815 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
816
817 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
818 NS_INTERFACE_MAP_ENTRY(nsIObserver)
819 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
820 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
821 NS_INTERFACE_MAP_END
822
823 /* static */ already_AddRefed<Console>
824 Console::Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv)
825 {
826 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
827
828 RefPtr<Console> console = new Console(aWindow);
829 console->Initialize(aRv);
830 if (NS_WARN_IF(aRv.Failed())) {
831 return nullptr;
832 }
833
834 return console.forget();
835 }
836
Console(nsPIDOMWindowInner * aWindow)837 Console::Console(nsPIDOMWindowInner* aWindow)
838 : mWindow(aWindow)
839 #ifdef DEBUG
840 , mOwningThread(PR_GetCurrentThread())
841 #endif
842 , mOuterID(0)
843 , mInnerID(0)
844 , mStatus(eUnknown)
845 {
846 MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);
847
848 if (mWindow) {
849 MOZ_ASSERT(mWindow->IsInnerWindow());
850 mInnerID = mWindow->WindowID();
851
852 // Without outerwindow any console message coming from this object will not
853 // shown in the devtools webconsole. But this should be fine because
854 // probably we are shutting down, or the window is CCed/GCed.
855 nsPIDOMWindowOuter* outerWindow = mWindow->GetOuterWindow();
856 if (outerWindow) {
857 mOuterID = outerWindow->WindowID();
858 }
859 }
860
861 mozilla::HoldJSObjects(this);
862 }
863
~Console()864 Console::~Console()
865 {
866 AssertIsOnOwningThread();
867 Shutdown();
868 mozilla::DropJSObjects(this);
869 }
870
871 void
Initialize(ErrorResult & aRv)872 Console::Initialize(ErrorResult& aRv)
873 {
874 AssertIsOnOwningThread();
875 MOZ_ASSERT(mStatus == eUnknown);
876
877 if (NS_IsMainThread()) {
878 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
879 if (NS_WARN_IF(!obs)) {
880 aRv.Throw(NS_ERROR_FAILURE);
881 return;
882 }
883
884 aRv = obs->AddObserver(this, "inner-window-destroyed", true);
885 if (NS_WARN_IF(aRv.Failed())) {
886 return;
887 }
888
889 aRv = obs->AddObserver(this, "memory-pressure", true);
890 if (NS_WARN_IF(aRv.Failed())) {
891 return;
892 }
893 }
894
895 mStatus = eInitialized;
896 }
897
898 void
Shutdown()899 Console::Shutdown()
900 {
901 AssertIsOnOwningThread();
902
903 if (mStatus == eUnknown || mStatus == eShuttingDown) {
904 return;
905 }
906
907 if (NS_IsMainThread()) {
908 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
909 if (obs) {
910 obs->RemoveObserver(this, "inner-window-destroyed");
911 obs->RemoveObserver(this, "memory-pressure");
912 }
913 }
914
915 NS_ReleaseOnMainThread(mStorage.forget());
916 NS_ReleaseOnMainThread(mSandbox.forget());
917
918 mTimerRegistry.Clear();
919 mCounterRegistry.Clear();
920
921 mCallDataStorage.Clear();
922 mCallDataStoragePending.Clear();
923
924 mStatus = eShuttingDown;
925 }
926
927 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)928 Console::Observe(nsISupports* aSubject, const char* aTopic,
929 const char16_t* aData)
930 {
931 AssertIsOnMainThread();
932
933 if (!strcmp(aTopic, "inner-window-destroyed")) {
934 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
935 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
936
937 uint64_t innerID;
938 nsresult rv = wrapper->GetData(&innerID);
939 NS_ENSURE_SUCCESS(rv, rv);
940
941 if (innerID == mInnerID) {
942 Shutdown();
943 }
944
945 return NS_OK;
946 }
947
948 if (!strcmp(aTopic, "memory-pressure")) {
949 ClearStorage();
950 return NS_OK;
951 }
952
953 return NS_OK;
954 }
955
956 void
ClearStorage()957 Console::ClearStorage()
958 {
959 mCallDataStorage.Clear();
960 }
961
962 #define METHOD(name, string) \
963 /* static */ void \
964 Console::name(const GlobalObject& aGlobal, const Sequence<JS::Value>& aData) \
965 { \
966 Method(aGlobal, Method##name, NS_LITERAL_STRING(string), aData); \
967 }
968
969 METHOD(Log, "log")
970 METHOD(Info, "info")
971 METHOD(Warn, "warn")
972 METHOD(Error, "error")
973 METHOD(Exception, "exception")
974 METHOD(Debug, "debug")
975 METHOD(Table, "table")
976 METHOD(Clear, "clear")
977
978 /* static */ void
Trace(const GlobalObject & aGlobal)979 Console::Trace(const GlobalObject& aGlobal)
980 {
981 const Sequence<JS::Value> data;
982 Method(aGlobal, MethodTrace, NS_LITERAL_STRING("trace"), data);
983 }
984
985 // Displays an interactive listing of all the properties of an object.
986 METHOD(Dir, "dir");
987 METHOD(Dirxml, "dirxml");
988
989 METHOD(Group, "group")
990 METHOD(GroupCollapsed, "groupCollapsed")
991 METHOD(GroupEnd, "groupEnd")
992
993 /* static */ void
Time(const GlobalObject & aGlobal,const JS::Handle<JS::Value> aTime)994 Console::Time(const GlobalObject& aGlobal, const JS::Handle<JS::Value> aTime)
995 {
996 JSContext* cx = aGlobal.Context();
997
998 Sequence<JS::Value> data;
999 SequenceRooter<JS::Value> rooter(cx, &data);
1000
1001 if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
1002 return;
1003 }
1004
1005 Method(aGlobal, MethodTime, NS_LITERAL_STRING("time"), data);
1006 }
1007
1008 /* static */ void
TimeEnd(const GlobalObject & aGlobal,const JS::Handle<JS::Value> aTime)1009 Console::TimeEnd(const GlobalObject& aGlobal, const JS::Handle<JS::Value> aTime)
1010 {
1011 JSContext* cx = aGlobal.Context();
1012
1013 Sequence<JS::Value> data;
1014 SequenceRooter<JS::Value> rooter(cx, &data);
1015
1016 if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
1017 return;
1018 }
1019
1020 Method(aGlobal, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
1021 }
1022
1023 /* static */ void
TimeStamp(const GlobalObject & aGlobal,const JS::Handle<JS::Value> aData)1024 Console::TimeStamp(const GlobalObject& aGlobal,
1025 const JS::Handle<JS::Value> aData)
1026 {
1027 JSContext* cx = aGlobal.Context();
1028
1029 Sequence<JS::Value> data;
1030 SequenceRooter<JS::Value> rooter(cx, &data);
1031
1032 if (aData.isString() && !data.AppendElement(aData, fallible)) {
1033 return;
1034 }
1035
1036 Method(aGlobal, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
1037 }
1038
1039 /* static */ void
Profile(const GlobalObject & aGlobal,const Sequence<JS::Value> & aData)1040 Console::Profile(const GlobalObject& aGlobal, const Sequence<JS::Value>& aData)
1041 {
1042 ProfileMethod(aGlobal, NS_LITERAL_STRING("profile"), aData);
1043 }
1044
1045 /* static */ void
ProfileEnd(const GlobalObject & aGlobal,const Sequence<JS::Value> & aData)1046 Console::ProfileEnd(const GlobalObject& aGlobal,
1047 const Sequence<JS::Value>& aData)
1048 {
1049 ProfileMethod(aGlobal, NS_LITERAL_STRING("profileEnd"), aData);
1050 }
1051
1052 /* static */ void
ProfileMethod(const GlobalObject & aGlobal,const nsAString & aAction,const Sequence<JS::Value> & aData)1053 Console::ProfileMethod(const GlobalObject& aGlobal, const nsAString& aAction,
1054 const Sequence<JS::Value>& aData)
1055 {
1056 RefPtr<Console> console = GetConsole(aGlobal);
1057 if (!console) {
1058 return;
1059 }
1060
1061 JSContext* cx = aGlobal.Context();
1062 console->ProfileMethodInternal(cx, aAction, aData);
1063 }
1064
1065 void
ProfileMethodInternal(JSContext * aCx,const nsAString & aAction,const Sequence<JS::Value> & aData)1066 Console::ProfileMethodInternal(JSContext* aCx, const nsAString& aAction,
1067 const Sequence<JS::Value>& aData)
1068 {
1069 if (!NS_IsMainThread()) {
1070 // Here we are in a worker thread.
1071 RefPtr<ConsoleProfileRunnable> runnable =
1072 new ConsoleProfileRunnable(this, aAction, aData);
1073
1074 runnable->Dispatch(aCx);
1075 return;
1076 }
1077
1078 ClearException ce(aCx);
1079
1080 RootedDictionary<ConsoleProfileEvent> event(aCx);
1081 event.mAction = aAction;
1082
1083 event.mArguments.Construct();
1084 Sequence<JS::Value>& sequence = event.mArguments.Value();
1085
1086 for (uint32_t i = 0; i < aData.Length(); ++i) {
1087 if (!sequence.AppendElement(aData[i], fallible)) {
1088 return;
1089 }
1090 }
1091
1092 JS::Rooted<JS::Value> eventValue(aCx);
1093 if (!ToJSValue(aCx, event, &eventValue)) {
1094 return;
1095 }
1096
1097 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
1098 MOZ_ASSERT(eventObj);
1099
1100 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
1101 JSPROP_ENUMERATE)) {
1102 return;
1103 }
1104
1105 nsIXPConnect* xpc = nsContentUtils::XPConnect();
1106 nsCOMPtr<nsISupports> wrapper;
1107 const nsIID& iid = NS_GET_IID(nsISupports);
1108
1109 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
1110 return;
1111 }
1112
1113 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1114 if (obs) {
1115 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
1116 }
1117 }
1118
1119 /* static */ void
Assert(const GlobalObject & aGlobal,bool aCondition,const Sequence<JS::Value> & aData)1120 Console::Assert(const GlobalObject& aGlobal, bool aCondition,
1121 const Sequence<JS::Value>& aData)
1122 {
1123 if (!aCondition) {
1124 Method(aGlobal, MethodAssert, NS_LITERAL_STRING("assert"), aData);
1125 }
1126 }
1127
1128 METHOD(Count, "count")
1129
1130 /* static */ void
NoopMethod(const GlobalObject & aGlobal)1131 Console::NoopMethod(const GlobalObject& aGlobal)
1132 {
1133 // Nothing to do.
1134 }
1135
1136 namespace {
1137
1138 nsresult
StackFrameToStackEntry(JSContext * aCx,nsIStackFrame * aStackFrame,ConsoleStackEntry & aStackEntry)1139 StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
1140 ConsoleStackEntry& aStackEntry)
1141 {
1142 MOZ_ASSERT(aStackFrame);
1143
1144 nsresult rv = aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
1145 NS_ENSURE_SUCCESS(rv, rv);
1146
1147 int32_t lineNumber;
1148 rv = aStackFrame->GetLineNumber(aCx, &lineNumber);
1149 NS_ENSURE_SUCCESS(rv, rv);
1150
1151 aStackEntry.mLineNumber = lineNumber;
1152
1153 int32_t columnNumber;
1154 rv = aStackFrame->GetColumnNumber(aCx, &columnNumber);
1155 NS_ENSURE_SUCCESS(rv, rv);
1156
1157 aStackEntry.mColumnNumber = columnNumber;
1158
1159 rv = aStackFrame->GetName(aCx, aStackEntry.mFunctionName);
1160 NS_ENSURE_SUCCESS(rv, rv);
1161
1162 nsString cause;
1163 rv = aStackFrame->GetAsyncCause(aCx, cause);
1164 NS_ENSURE_SUCCESS(rv, rv);
1165 if (!cause.IsEmpty()) {
1166 aStackEntry.mAsyncCause.Construct(cause);
1167 }
1168
1169 aStackEntry.mLanguage = nsIProgrammingLanguage::JAVASCRIPT;
1170 return NS_OK;
1171 }
1172
1173 nsresult
ReifyStack(JSContext * aCx,nsIStackFrame * aStack,nsTArray<ConsoleStackEntry> & aRefiedStack)1174 ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
1175 nsTArray<ConsoleStackEntry>& aRefiedStack)
1176 {
1177 nsCOMPtr<nsIStackFrame> stack(aStack);
1178
1179 while (stack) {
1180 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
1181 nsresult rv = StackFrameToStackEntry(aCx, stack, data);
1182 NS_ENSURE_SUCCESS(rv, rv);
1183
1184 nsCOMPtr<nsIStackFrame> caller;
1185 rv = stack->GetCaller(aCx, getter_AddRefs(caller));
1186 NS_ENSURE_SUCCESS(rv, rv);
1187
1188 if (!caller) {
1189 rv = stack->GetAsyncCaller(aCx, getter_AddRefs(caller));
1190 NS_ENSURE_SUCCESS(rv, rv);
1191 }
1192 stack.swap(caller);
1193 }
1194
1195 return NS_OK;
1196 }
1197
1198 } // anonymous namespace
1199
1200 // Queue a call to a console method. See the CALL_DELAY constant.
1201 /* static */ void
Method(const GlobalObject & aGlobal,MethodName aMethodName,const nsAString & aMethodString,const Sequence<JS::Value> & aData)1202 Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
1203 const nsAString& aMethodString,
1204 const Sequence<JS::Value>& aData)
1205 {
1206 RefPtr<Console> console = GetConsole(aGlobal);
1207 if (!console) {
1208 return;
1209 }
1210
1211 console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString,
1212 aData);
1213 }
1214
1215 void
MethodInternal(JSContext * aCx,MethodName aMethodName,const nsAString & aMethodString,const Sequence<JS::Value> & aData)1216 Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
1217 const nsAString& aMethodString,
1218 const Sequence<JS::Value>& aData)
1219 {
1220 AssertIsOnOwningThread();
1221
1222 RefPtr<ConsoleCallData> callData(new ConsoleCallData());
1223
1224 ClearException ce(aCx);
1225
1226 if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
1227 aData, this))) {
1228 return;
1229 }
1230
1231 if (mWindow) {
1232 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
1233 if (!webNav) {
1234 return;
1235 }
1236
1237 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
1238 MOZ_ASSERT(loadContext);
1239
1240 loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
1241
1242 // Save the principal's OriginAttributes in the console event data
1243 // so that we will be able to filter messages by origin attributes.
1244 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow);
1245 if (NS_WARN_IF(!sop)) {
1246 return;
1247 }
1248
1249 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1250 if (NS_WARN_IF(!principal)) {
1251 return;
1252 }
1253
1254 callData->SetOriginAttributes(BasePrincipal::Cast(principal)->OriginAttributesRef());
1255 }
1256
1257 JS::StackCapture captureMode = ShouldIncludeStackTrace(aMethodName) ?
1258 JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH)) :
1259 JS::StackCapture(JS::FirstSubsumedFrame(aCx));
1260 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, mozilla::Move(captureMode));
1261
1262 if (stack) {
1263 callData->mTopStackFrame.emplace();
1264 nsresult rv = StackFrameToStackEntry(aCx, stack,
1265 *callData->mTopStackFrame);
1266 if (NS_FAILED(rv)) {
1267 return;
1268 }
1269 }
1270
1271 if (NS_IsMainThread()) {
1272 callData->mStack = stack;
1273 } else {
1274 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
1275 // before we post our runnable to the main thread.
1276 callData->mReifiedStack.emplace();
1277 nsresult rv = ReifyStack(aCx, stack, *callData->mReifiedStack);
1278 if (NS_WARN_IF(NS_FAILED(rv))) {
1279 return;
1280 }
1281 }
1282
1283 DOMHighResTimeStamp monotonicTimer;
1284
1285 // Monotonic timer for 'time' and 'timeEnd'
1286 if (aMethodName == MethodTime ||
1287 aMethodName == MethodTimeEnd ||
1288 aMethodName == MethodTimeStamp) {
1289 if (mWindow) {
1290 nsGlobalWindow *win = nsGlobalWindow::Cast(mWindow);
1291 MOZ_ASSERT(win);
1292
1293 RefPtr<Performance> performance = win->GetPerformance();
1294 if (!performance) {
1295 return;
1296 }
1297
1298 monotonicTimer = performance->Now();
1299
1300 nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
1301 RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
1302 bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
1303
1304 // The 'timeStamp' recordings do not need an argument; use empty string
1305 // if no arguments passed in.
1306 if (isTimelineRecording && aMethodName == MethodTimeStamp) {
1307 JS::Rooted<JS::Value> value(aCx, aData.Length() == 0
1308 ? JS_GetEmptyStringValue(aCx)
1309 : aData[0]);
1310 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1311
1312 nsAutoJSString key;
1313 if (jsString) {
1314 key.init(aCx, jsString);
1315 }
1316
1317 timelines->AddMarkerForDocShell(docShell, Move(
1318 MakeUnique<TimestampTimelineMarker>(key)));
1319 }
1320 // For `console.time(foo)` and `console.timeEnd(foo)`.
1321 else if (isTimelineRecording && aData.Length() == 1) {
1322 JS::Rooted<JS::Value> value(aCx, aData[0]);
1323 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1324
1325 if (jsString) {
1326 nsAutoJSString key;
1327 if (key.init(aCx, jsString)) {
1328 timelines->AddMarkerForDocShell(docShell, Move(
1329 MakeUnique<ConsoleTimelineMarker>(
1330 key, aMethodName == MethodTime ? MarkerTracingType::START
1331 : MarkerTracingType::END)));
1332 }
1333 }
1334 }
1335 } else {
1336 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1337 MOZ_ASSERT(workerPrivate);
1338
1339 TimeDuration duration =
1340 mozilla::TimeStamp::Now() - workerPrivate->NowBaseTimeStamp();
1341
1342 monotonicTimer = TimerClamping::ReduceMsTimeValue(duration.ToMilliseconds());
1343 }
1344 }
1345
1346 if (aMethodName == MethodTime && !aData.IsEmpty()) {
1347 callData->mStartTimerStatus = StartTimer(aCx, aData[0],
1348 monotonicTimer,
1349 callData->mStartTimerLabel,
1350 &callData->mStartTimerValue);
1351 }
1352
1353 else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
1354 callData->mStopTimerStatus = StopTimer(aCx, aData[0],
1355 monotonicTimer,
1356 callData->mStopTimerLabel,
1357 &callData->mStopTimerDuration);
1358 }
1359
1360 else if (aMethodName == MethodCount) {
1361 ConsoleStackEntry frame;
1362 if (callData->mTopStackFrame) {
1363 frame = *callData->mTopStackFrame;
1364 }
1365
1366 callData->mCountValue = IncreaseCounter(aCx, frame, aData,
1367 callData->mCountLabel);
1368 }
1369
1370 if (NS_IsMainThread()) {
1371 callData->SetIDs(mOuterID, mInnerID);
1372 ProcessCallData(aCx, callData, aData);
1373
1374 // Just because we don't want to expose
1375 // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
1376 // cleanup the mCallDataStorage:
1377 UnstoreCallData(callData);
1378 return;
1379 }
1380
1381 // We do this only in workers for now.
1382 NotifyHandler(aCx, aData, callData);
1383
1384 RefPtr<ConsoleCallDataRunnable> runnable =
1385 new ConsoleCallDataRunnable(this, callData);
1386 Unused << NS_WARN_IF(!runnable->Dispatch(aCx));
1387 }
1388
1389 // We store information to lazily compute the stack in the reserved slots of
1390 // LazyStackGetter. The first slot always stores a JS object: it's either the
1391 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1392 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1393 // reified the stack yet, or an UndefinedValue() otherwise.
1394 enum {
1395 SLOT_STACKOBJ,
1396 SLOT_RAW_STACK
1397 };
1398
1399 bool
LazyStackGetter(JSContext * aCx,unsigned aArgc,JS::Value * aVp)1400 LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
1401 {
1402 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1403 JS::Rooted<JSObject*> callee(aCx, &args.callee());
1404
1405 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1406 if (v.isUndefined()) {
1407 // Already reified.
1408 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1409 return true;
1410 }
1411
1412 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1413 nsTArray<ConsoleStackEntry> reifiedStack;
1414 nsresult rv = ReifyStack(aCx, stack, reifiedStack);
1415 if (NS_WARN_IF(NS_FAILED(rv))) {
1416 Throw(aCx, rv);
1417 return false;
1418 }
1419
1420 JS::Rooted<JS::Value> stackVal(aCx);
1421 if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
1422 return false;
1423 }
1424
1425 MOZ_ASSERT(stackVal.isObject());
1426
1427 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1428 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1429
1430 args.rval().set(stackVal);
1431 return true;
1432 }
1433
1434 void
ProcessCallData(JSContext * aCx,ConsoleCallData * aData,const Sequence<JS::Value> & aArguments)1435 Console::ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
1436 const Sequence<JS::Value>& aArguments)
1437 {
1438 AssertIsOnMainThread();
1439 MOZ_ASSERT(aData);
1440
1441 JS::Rooted<JS::Value> eventValue(aCx);
1442
1443 // We want to create a console event object and pass it to our
1444 // nsIConsoleAPIStorage implementation. We want to define some accessor
1445 // properties on this object, and those will need to keep an nsIStackFrame
1446 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1447 // further, passing untrusted objects to system code is likely to run afoul of
1448 // Object Xrays. So we want to wrap in a system-principal scope here. But
1449 // which one? We could cheat and try to get the underlying JSObject* of
1450 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1451 // with explicit permission from the XPConnect module owner. If you're
1452 // tempted to do that anywhere else, talk to said module owner first.
1453
1454 // aCx and aArguments are in the same compartment.
1455 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
1456 xpc::PrivilegedJunkScope(),
1457 &eventValue, aData))) {
1458 return;
1459 }
1460
1461 if (!mStorage) {
1462 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1463 }
1464
1465 if (!mStorage) {
1466 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1467 return;
1468 }
1469
1470 nsAutoString innerID, outerID;
1471
1472 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1473 if (aData->mIDType == ConsoleCallData::eString) {
1474 outerID = aData->mOuterIDString;
1475 innerID = aData->mInnerIDString;
1476 } else {
1477 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1478 outerID.AppendInt(aData->mOuterIDNumber);
1479 innerID.AppendInt(aData->mInnerIDNumber);
1480 }
1481
1482 if (aData->mMethodName == MethodClear) {
1483 DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
1484 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
1485 }
1486
1487 if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
1488 NS_WARNING("Failed to record a console event.");
1489 }
1490 }
1491
1492 bool
PopulateConsoleNotificationInTheTargetScope(JSContext * aCx,const Sequence<JS::Value> & aArguments,JSObject * aTargetScope,JS::MutableHandle<JS::Value> aEventValue,ConsoleCallData * aData) const1493 Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
1494 const Sequence<JS::Value>& aArguments,
1495 JSObject* aTargetScope,
1496 JS::MutableHandle<JS::Value> aEventValue,
1497 ConsoleCallData* aData) const
1498 {
1499 MOZ_ASSERT(aCx);
1500 MOZ_ASSERT(aData);
1501 MOZ_ASSERT(aTargetScope);
1502
1503 JS::Rooted<JSObject*> targetScope(aCx, aTargetScope);
1504
1505 ConsoleStackEntry frame;
1506 if (aData->mTopStackFrame) {
1507 frame = *aData->mTopStackFrame;
1508 }
1509
1510 ClearException ce(aCx);
1511 RootedDictionary<ConsoleEvent> event(aCx);
1512
1513 // Save the principal's OriginAttributes in the console event data
1514 // so that we will be able to filter messages by origin attributes.
1515 JS::Rooted<JS::Value> originAttributesValue(aCx);
1516 if (ToJSValue(aCx, aData->mOriginAttributes, &originAttributesValue)) {
1517 event.mOriginAttributes = originAttributesValue;
1518 }
1519
1520 event.mID.Construct();
1521 event.mInnerID.Construct();
1522
1523 if (aData->mIDType == ConsoleCallData::eString) {
1524 event.mID.Value().SetAsString() = aData->mOuterIDString;
1525 event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1526 } else if (aData->mIDType == ConsoleCallData::eNumber) {
1527 event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
1528 event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
1529 } else {
1530 // aData->mIDType can be eUnknown when we dispatch notifications via
1531 // mConsoleEventNotifier.
1532 event.mID.Value().SetAsUnsignedLongLong() = 0;
1533 event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
1534 }
1535
1536 event.mLevel = aData->mMethodString;
1537 event.mFilename = frame.mFilename;
1538
1539 nsCOMPtr<nsIURI> filenameURI;
1540 nsAutoCString pass;
1541 if (NS_IsMainThread() &&
1542 NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
1543 NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
1544 nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
1545 nsAutoCString spec;
1546 if (safeURI &&
1547 NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
1548 CopyUTF8toUTF16(spec, event.mFilename);
1549 }
1550 }
1551
1552 event.mLineNumber = frame.mLineNumber;
1553 event.mColumnNumber = frame.mColumnNumber;
1554 event.mFunctionName = frame.mFunctionName;
1555 event.mTimeStamp = aData->mTimeStamp;
1556 event.mPrivate = aData->mPrivate;
1557
1558 switch (aData->mMethodName) {
1559 case MethodLog:
1560 case MethodInfo:
1561 case MethodWarn:
1562 case MethodError:
1563 case MethodException:
1564 case MethodDebug:
1565 case MethodAssert:
1566 event.mArguments.Construct();
1567 event.mStyles.Construct();
1568 if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
1569 event.mArguments.Value(),
1570 event.mStyles.Value()))) {
1571 return false;
1572 }
1573
1574 break;
1575
1576 default:
1577 event.mArguments.Construct();
1578 if (NS_WARN_IF(!ArgumentsToValueList(aArguments,
1579 event.mArguments.Value()))) {
1580 return false;
1581 }
1582 }
1583
1584 if (aData->mMethodName == MethodGroup ||
1585 aData->mMethodName == MethodGroupCollapsed ||
1586 aData->mMethodName == MethodGroupEnd) {
1587 ComposeGroupName(aCx, aArguments, event.mGroupName);
1588 }
1589
1590 else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
1591 event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
1592 aData->mStartTimerValue,
1593 aData->mStartTimerStatus);
1594 }
1595
1596 else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
1597 event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
1598 aData->mStopTimerDuration,
1599 aData->mStopTimerStatus);
1600 }
1601
1602 else if (aData->mMethodName == MethodCount) {
1603 event.mCounter = CreateCounterValue(aCx, aData->mCountLabel,
1604 aData->mCountValue);
1605 }
1606
1607 JSAutoCompartment ac2(aCx, targetScope);
1608
1609 if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
1610 return false;
1611 }
1612
1613 JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
1614 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
1615 JSPROP_ENUMERATE))) {
1616 return false;
1617 }
1618
1619 if (ShouldIncludeStackTrace(aData->mMethodName)) {
1620 // Now define the "stacktrace" property on eventObj. There are two cases
1621 // here. Either we came from a worker and have a reified stack, or we want
1622 // to define a getter that will lazily reify the stack.
1623 if (aData->mReifiedStack) {
1624 JS::Rooted<JS::Value> stacktrace(aCx);
1625 if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
1626 NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
1627 JSPROP_ENUMERATE))) {
1628 return false;
1629 }
1630 } else {
1631 JSFunction* fun = js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0,
1632 "stacktrace");
1633 if (NS_WARN_IF(!fun)) {
1634 return false;
1635 }
1636
1637 JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
1638
1639 // We want to store our stack in the function and have it stay alive. But
1640 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1641 // wrapper and the raw pointer: the former will keep the latter alive.
1642 JS::Rooted<JS::Value> stackVal(aCx);
1643 nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack,
1644 &stackVal);
1645 if (NS_WARN_IF(NS_FAILED(rv))) {
1646 return false;
1647 }
1648
1649 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1650 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1651 JS::PrivateValue(aData->mStack.get()));
1652
1653 if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace",
1654 JS::UndefinedHandleValue,
1655 JSPROP_ENUMERATE | JSPROP_SHARED |
1656 JSPROP_GETTER | JSPROP_SETTER,
1657 JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
1658 nullptr))) {
1659 return false;
1660 }
1661 }
1662 }
1663
1664 return true;
1665 }
1666
1667 namespace {
1668
1669 // Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
1670 bool
FlushOutput(JSContext * aCx,Sequence<JS::Value> & aSequence,nsString & aOutput)1671 FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
1672 {
1673 if (!aOutput.IsEmpty()) {
1674 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1675 aOutput.get(),
1676 aOutput.Length()));
1677 if (NS_WARN_IF(!str)) {
1678 return false;
1679 }
1680
1681 if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
1682 return false;
1683 }
1684
1685 aOutput.Truncate();
1686 }
1687
1688 return true;
1689 }
1690
1691 } // namespace
1692
1693 bool
ProcessArguments(JSContext * aCx,const Sequence<JS::Value> & aData,Sequence<JS::Value> & aSequence,Sequence<nsString> & aStyles) const1694 Console::ProcessArguments(JSContext* aCx,
1695 const Sequence<JS::Value>& aData,
1696 Sequence<JS::Value>& aSequence,
1697 Sequence<nsString>& aStyles) const
1698 {
1699 if (aData.IsEmpty()) {
1700 return true;
1701 }
1702
1703 if (aData.Length() == 1 || !aData[0].isString()) {
1704 return ArgumentsToValueList(aData, aSequence);
1705 }
1706
1707 JS::Rooted<JS::Value> format(aCx, aData[0]);
1708 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1709 if (NS_WARN_IF(!jsString)) {
1710 return false;
1711 }
1712
1713 nsAutoJSString string;
1714 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1715 return false;
1716 }
1717
1718 nsString::const_iterator start, end;
1719 string.BeginReading(start);
1720 string.EndReading(end);
1721
1722 nsString output;
1723 uint32_t index = 1;
1724
1725 while (start != end) {
1726 if (*start != '%') {
1727 output.Append(*start);
1728 ++start;
1729 continue;
1730 }
1731
1732 ++start;
1733 if (start == end) {
1734 output.Append('%');
1735 break;
1736 }
1737
1738 if (*start == '%') {
1739 output.Append(*start);
1740 ++start;
1741 continue;
1742 }
1743
1744 nsAutoString tmp;
1745 tmp.Append('%');
1746
1747 int32_t integer = -1;
1748 int32_t mantissa = -1;
1749
1750 // Let's parse %<number>.<number> for %d and %f
1751 if (*start >= '0' && *start <= '9') {
1752 integer = 0;
1753
1754 do {
1755 integer = integer * 10 + *start - '0';
1756 tmp.Append(*start);
1757 ++start;
1758 } while (*start >= '0' && *start <= '9' && start != end);
1759 }
1760
1761 if (start == end) {
1762 output.Append(tmp);
1763 break;
1764 }
1765
1766 if (*start == '.') {
1767 tmp.Append(*start);
1768 ++start;
1769
1770 if (start == end) {
1771 output.Append(tmp);
1772 break;
1773 }
1774
1775 // '.' must be followed by a number.
1776 if (*start < '0' || *start > '9') {
1777 output.Append(tmp);
1778 continue;
1779 }
1780
1781 mantissa = 0;
1782
1783 do {
1784 mantissa = mantissa * 10 + *start - '0';
1785 tmp.Append(*start);
1786 ++start;
1787 } while (*start >= '0' && *start <= '9' && start != end);
1788
1789 if (start == end) {
1790 output.Append(tmp);
1791 break;
1792 }
1793 }
1794
1795 char ch = *start;
1796 tmp.Append(ch);
1797 ++start;
1798
1799 switch (ch) {
1800 case 'o':
1801 case 'O':
1802 {
1803 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1804 return false;
1805 }
1806
1807 JS::Rooted<JS::Value> v(aCx);
1808 if (index < aData.Length()) {
1809 v = aData[index++];
1810 }
1811
1812 if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
1813 return false;
1814 }
1815
1816 break;
1817 }
1818
1819 case 'c':
1820 {
1821 // If there isn't any output but there's already a style, then
1822 // discard the previous style and use the next one instead.
1823 if (output.IsEmpty() && !aStyles.IsEmpty()) {
1824 aStyles.TruncateLength(aStyles.Length() - 1);
1825 }
1826
1827 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1828 return false;
1829 }
1830
1831 if (index < aData.Length()) {
1832 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1833 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1834 if (NS_WARN_IF(!jsString)) {
1835 return false;
1836 }
1837
1838 int32_t diff = aSequence.Length() - aStyles.Length();
1839 if (diff > 0) {
1840 for (int32_t i = 0; i < diff; i++) {
1841 if (NS_WARN_IF(!aStyles.AppendElement(NullString(), fallible))) {
1842 return false;
1843 }
1844 }
1845 }
1846
1847 nsAutoJSString string;
1848 if (NS_WARN_IF(!string.init(aCx, jsString))) {
1849 return false;
1850 }
1851
1852 if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
1853 return false;
1854 }
1855 }
1856 break;
1857 }
1858
1859 case 's':
1860 if (index < aData.Length()) {
1861 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1862 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1863 if (NS_WARN_IF(!jsString)) {
1864 return false;
1865 }
1866
1867 nsAutoJSString v;
1868 if (NS_WARN_IF(!v.init(aCx, jsString))) {
1869 return false;
1870 }
1871
1872 output.Append(v);
1873 }
1874 break;
1875
1876 case 'd':
1877 case 'i':
1878 if (index < aData.Length()) {
1879 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1880
1881 int32_t v;
1882 if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
1883 return false;
1884 }
1885
1886 nsCString format;
1887 MakeFormatString(format, integer, mantissa, 'd');
1888 output.AppendPrintf(format.get(), v);
1889 }
1890 break;
1891
1892 case 'f':
1893 if (index < aData.Length()) {
1894 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1895
1896 double v;
1897 if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
1898 return false;
1899 }
1900
1901 // nspr returns "nan", but we want to expose it as "NaN"
1902 if (std::isnan(v)) {
1903 output.AppendFloat(v);
1904 } else {
1905 nsCString format;
1906 MakeFormatString(format, integer, mantissa, 'f');
1907 output.AppendPrintf(format.get(), v);
1908 }
1909 }
1910 break;
1911
1912 default:
1913 output.Append(tmp);
1914 break;
1915 }
1916 }
1917
1918 if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
1919 return false;
1920 }
1921
1922 // Discard trailing style element if there is no output to apply it to.
1923 if (aStyles.Length() > aSequence.Length()) {
1924 aStyles.TruncateLength(aSequence.Length());
1925 }
1926
1927 // The rest of the array, if unused by the format string.
1928 for (; index < aData.Length(); ++index) {
1929 if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
1930 return false;
1931 }
1932 }
1933
1934 return true;
1935 }
1936
1937 void
MakeFormatString(nsCString & aFormat,int32_t aInteger,int32_t aMantissa,char aCh) const1938 Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
1939 int32_t aMantissa, char aCh) const
1940 {
1941 aFormat.Append('%');
1942 if (aInteger >= 0) {
1943 aFormat.AppendInt(aInteger);
1944 }
1945
1946 if (aMantissa >= 0) {
1947 aFormat.Append('.');
1948 aFormat.AppendInt(aMantissa);
1949 }
1950
1951 aFormat.Append(aCh);
1952 }
1953
1954 void
ComposeGroupName(JSContext * aCx,const Sequence<JS::Value> & aData,nsAString & aName) const1955 Console::ComposeGroupName(JSContext* aCx,
1956 const Sequence<JS::Value>& aData,
1957 nsAString& aName) const
1958 {
1959 for (uint32_t i = 0; i < aData.Length(); ++i) {
1960 if (i != 0) {
1961 aName.AppendASCII(" ");
1962 }
1963
1964 JS::Rooted<JS::Value> value(aCx, aData[i]);
1965 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1966 if (!jsString) {
1967 return;
1968 }
1969
1970 nsAutoJSString string;
1971 if (!string.init(aCx, jsString)) {
1972 return;
1973 }
1974
1975 aName.Append(string);
1976 }
1977 }
1978
1979 bool
StartTimer(JSContext * aCx,const JS::Value & aName,DOMHighResTimeStamp aTimestamp,nsAString & aTimerLabel,DOMHighResTimeStamp * aTimerValue)1980 Console::StartTimer(JSContext* aCx, const JS::Value& aName,
1981 DOMHighResTimeStamp aTimestamp,
1982 nsAString& aTimerLabel,
1983 DOMHighResTimeStamp* aTimerValue)
1984 {
1985 AssertIsOnOwningThread();
1986 MOZ_ASSERT(aTimerValue);
1987
1988 *aTimerValue = 0;
1989
1990 if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
1991 return false;
1992 }
1993
1994 JS::Rooted<JS::Value> name(aCx, aName);
1995 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1996 if (NS_WARN_IF(!jsString)) {
1997 return false;
1998 }
1999
2000 nsAutoJSString label;
2001 if (NS_WARN_IF(!label.init(aCx, jsString))) {
2002 return false;
2003 }
2004
2005 DOMHighResTimeStamp entry = 0;
2006 if (!mTimerRegistry.Get(label, &entry)) {
2007 mTimerRegistry.Put(label, aTimestamp);
2008 } else {
2009 aTimestamp = entry;
2010 }
2011
2012 aTimerLabel = label;
2013 *aTimerValue = aTimestamp;
2014 return true;
2015 }
2016
2017 JS::Value
CreateStartTimerValue(JSContext * aCx,const nsAString & aTimerLabel,DOMHighResTimeStamp aTimerValue,bool aTimerStatus) const2018 Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
2019 DOMHighResTimeStamp aTimerValue,
2020 bool aTimerStatus) const
2021 {
2022 if (!aTimerStatus) {
2023 RootedDictionary<ConsoleTimerError> error(aCx);
2024
2025 JS::Rooted<JS::Value> value(aCx);
2026 if (!ToJSValue(aCx, error, &value)) {
2027 return JS::UndefinedValue();
2028 }
2029
2030 return value;
2031 }
2032
2033 RootedDictionary<ConsoleTimerStart> timer(aCx);
2034
2035 timer.mName = aTimerLabel;
2036 timer.mStarted = aTimerValue;
2037
2038 JS::Rooted<JS::Value> value(aCx);
2039 if (!ToJSValue(aCx, timer, &value)) {
2040 return JS::UndefinedValue();
2041 }
2042
2043 return value;
2044 }
2045
2046 bool
StopTimer(JSContext * aCx,const JS::Value & aName,DOMHighResTimeStamp aTimestamp,nsAString & aTimerLabel,double * aTimerDuration)2047 Console::StopTimer(JSContext* aCx, const JS::Value& aName,
2048 DOMHighResTimeStamp aTimestamp,
2049 nsAString& aTimerLabel,
2050 double* aTimerDuration)
2051 {
2052 AssertIsOnOwningThread();
2053 MOZ_ASSERT(aTimerDuration);
2054
2055 *aTimerDuration = 0;
2056
2057 JS::Rooted<JS::Value> name(aCx, aName);
2058 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
2059 if (NS_WARN_IF(!jsString)) {
2060 return false;
2061 }
2062
2063 nsAutoJSString key;
2064 if (NS_WARN_IF(!key.init(aCx, jsString))) {
2065 return false;
2066 }
2067
2068 DOMHighResTimeStamp entry = 0;
2069 if (NS_WARN_IF(!mTimerRegistry.Get(key, &entry))) {
2070 return false;
2071 }
2072
2073 mTimerRegistry.Remove(key);
2074
2075 aTimerLabel = key;
2076 *aTimerDuration = aTimestamp - entry;
2077 return true;
2078 }
2079
2080 JS::Value
CreateStopTimerValue(JSContext * aCx,const nsAString & aLabel,double aDuration,bool aStatus) const2081 Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
2082 double aDuration, bool aStatus) const
2083 {
2084 if (!aStatus) {
2085 return JS::UndefinedValue();
2086 }
2087
2088 RootedDictionary<ConsoleTimerEnd> timer(aCx);
2089 timer.mName = aLabel;
2090 timer.mDuration = aDuration;
2091
2092 JS::Rooted<JS::Value> value(aCx);
2093 if (!ToJSValue(aCx, timer, &value)) {
2094 return JS::UndefinedValue();
2095 }
2096
2097 return value;
2098 }
2099
2100 bool
ArgumentsToValueList(const Sequence<JS::Value> & aData,Sequence<JS::Value> & aSequence) const2101 Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
2102 Sequence<JS::Value>& aSequence) const
2103 {
2104 for (uint32_t i = 0; i < aData.Length(); ++i) {
2105 if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
2106 return false;
2107 }
2108 }
2109
2110 return true;
2111 }
2112
2113 uint32_t
IncreaseCounter(JSContext * aCx,const ConsoleStackEntry & aFrame,const Sequence<JS::Value> & aArguments,nsAString & aCountLabel)2114 Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
2115 const Sequence<JS::Value>& aArguments,
2116 nsAString& aCountLabel)
2117 {
2118 AssertIsOnOwningThread();
2119
2120 ClearException ce(aCx);
2121
2122 nsAutoString key;
2123 nsAutoString label;
2124
2125 if (!aArguments.IsEmpty()) {
2126 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
2127 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
2128
2129 nsAutoJSString string;
2130 if (jsString && string.init(aCx, jsString)) {
2131 label = string;
2132 key = string;
2133 }
2134 }
2135
2136 if (key.IsEmpty()) {
2137 key.Append(aFrame.mFilename);
2138 key.Append(':');
2139 key.AppendInt(aFrame.mLineNumber);
2140 }
2141
2142 uint32_t count = 0;
2143 if (!mCounterRegistry.Get(key, &count) &&
2144 mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
2145 return MAX_PAGE_COUNTERS;
2146 }
2147
2148 ++count;
2149 mCounterRegistry.Put(key, count);
2150
2151 aCountLabel = label;
2152 return count;
2153 }
2154
2155 JS::Value
CreateCounterValue(JSContext * aCx,const nsAString & aCountLabel,uint32_t aCountValue) const2156 Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
2157 uint32_t aCountValue) const
2158 {
2159 ClearException ce(aCx);
2160
2161 if (aCountValue == MAX_PAGE_COUNTERS) {
2162 RootedDictionary<ConsoleCounterError> error(aCx);
2163
2164 JS::Rooted<JS::Value> value(aCx);
2165 if (!ToJSValue(aCx, error, &value)) {
2166 return JS::UndefinedValue();
2167 }
2168
2169 return value;
2170 }
2171
2172 RootedDictionary<ConsoleCounter> data(aCx);
2173 data.mLabel = aCountLabel;
2174 data.mCount = aCountValue;
2175
2176 JS::Rooted<JS::Value> value(aCx);
2177 if (!ToJSValue(aCx, data, &value)) {
2178 return JS::UndefinedValue();
2179 }
2180
2181 return value;
2182 }
2183
2184 bool
ShouldIncludeStackTrace(MethodName aMethodName) const2185 Console::ShouldIncludeStackTrace(MethodName aMethodName) const
2186 {
2187 switch (aMethodName) {
2188 case MethodError:
2189 case MethodException:
2190 case MethodAssert:
2191 case MethodTrace:
2192 return true;
2193 default:
2194 return false;
2195 }
2196 }
2197
2198 JSObject*
GetOrCreateSandbox(JSContext * aCx,nsIPrincipal * aPrincipal)2199 Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
2200 {
2201 AssertIsOnMainThread();
2202
2203 if (!mSandbox) {
2204 nsIXPConnect* xpc = nsContentUtils::XPConnect();
2205 MOZ_ASSERT(xpc, "This should never be null!");
2206
2207 JS::Rooted<JSObject*> sandbox(aCx);
2208 nsresult rv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
2209 if (NS_WARN_IF(NS_FAILED(rv))) {
2210 return nullptr;
2211 }
2212
2213 mSandbox = new JSObjectHolder(aCx, sandbox);
2214 }
2215
2216 return mSandbox->GetJSObject();
2217 }
2218
2219 void
StoreCallData(ConsoleCallData * aCallData)2220 Console::StoreCallData(ConsoleCallData* aCallData)
2221 {
2222 AssertIsOnOwningThread();
2223
2224 MOZ_ASSERT(aCallData);
2225 MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
2226 MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
2227
2228 mCallDataStorage.AppendElement(aCallData);
2229
2230 if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
2231 RefPtr<ConsoleCallData> callData = mCallDataStorage[0];
2232 mCallDataStorage.RemoveElementAt(0);
2233
2234 MOZ_ASSERT(callData->mStatus != ConsoleCallData::eToBeDeleted);
2235
2236 // We cannot delete this object now because we have to trace its JSValues
2237 // until the pending operation (ConsoleCallDataRunnable) is completed.
2238 if (callData->mStatus == ConsoleCallData::eInUse) {
2239 callData->mStatus = ConsoleCallData::eToBeDeleted;
2240 mCallDataStoragePending.AppendElement(callData);
2241 }
2242 }
2243 }
2244
2245 void
UnstoreCallData(ConsoleCallData * aCallData)2246 Console::UnstoreCallData(ConsoleCallData* aCallData)
2247 {
2248 AssertIsOnOwningThread();
2249
2250 MOZ_ASSERT(aCallData);
2251
2252 MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
2253
2254 // It can be that mCallDataStorage has been already cleaned in case the
2255 // processing of the argument of some Console methods triggers the
2256 // window.close().
2257
2258 mCallDataStorage.RemoveElement(aCallData);
2259 }
2260
2261 void
ReleaseCallData(ConsoleCallData * aCallData)2262 Console::ReleaseCallData(ConsoleCallData* aCallData)
2263 {
2264 AssertIsOnOwningThread();
2265 MOZ_ASSERT(aCallData);
2266 MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eToBeDeleted);
2267 MOZ_ASSERT(mCallDataStoragePending.Contains(aCallData));
2268
2269 mCallDataStoragePending.RemoveElement(aCallData);
2270 }
2271
2272 void
NotifyHandler(JSContext * aCx,const Sequence<JS::Value> & aArguments,ConsoleCallData * aCallData) const2273 Console::NotifyHandler(JSContext* aCx, const Sequence<JS::Value>& aArguments,
2274 ConsoleCallData* aCallData) const
2275 {
2276 AssertIsOnOwningThread();
2277 MOZ_ASSERT(!NS_IsMainThread());
2278 MOZ_ASSERT(aCallData);
2279
2280 if (!mConsoleEventNotifier) {
2281 return;
2282 }
2283
2284 JS::Rooted<JS::Value> value(aCx);
2285
2286 // aCx and aArguments are in the same compartment because this method is
2287 // called directly when a Console.something() runs.
2288 // mConsoleEventNotifier->Callable() is the scope where value will be sent to.
2289 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
2290 mConsoleEventNotifier->Callable(),
2291 &value,
2292 aCallData))) {
2293 return;
2294 }
2295
2296 JS::Rooted<JS::Value> ignored(aCx);
2297 mConsoleEventNotifier->Call(value, &ignored);
2298 }
2299
2300 void
RetrieveConsoleEvents(JSContext * aCx,nsTArray<JS::Value> & aEvents,ErrorResult & aRv)2301 Console::RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
2302 ErrorResult& aRv)
2303 {
2304 AssertIsOnOwningThread();
2305
2306 // We don't want to expose this functionality to main-thread yet.
2307 MOZ_ASSERT(!NS_IsMainThread());
2308
2309 JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
2310
2311 for (uint32_t i = 0; i < mCallDataStorage.Length(); ++i) {
2312 JS::Rooted<JS::Value> value(aCx);
2313
2314 JS::Rooted<JSObject*> sequenceScope(aCx, mCallDataStorage[i]->mGlobal);
2315 JSAutoCompartment ac(aCx, sequenceScope);
2316
2317 Sequence<JS::Value> sequence;
2318 SequenceRooter<JS::Value> arguments(aCx, &sequence);
2319
2320 if (!mCallDataStorage[i]->PopulateArgumentsSequence(sequence)) {
2321 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2322 return;
2323 }
2324
2325 // Here we have aCx and sequence in the same compartment.
2326 // targetScope is the destination scope and value will be populated in its
2327 // compartment.
2328 if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, sequence,
2329 targetScope,
2330 &value,
2331 mCallDataStorage[i]))) {
2332 aRv.Throw(NS_ERROR_FAILURE);
2333 return;
2334 }
2335
2336 aEvents.AppendElement(value);
2337 }
2338 }
2339
2340 void
SetConsoleEventHandler(AnyCallback * aHandler)2341 Console::SetConsoleEventHandler(AnyCallback* aHandler)
2342 {
2343 AssertIsOnOwningThread();
2344
2345 // We don't want to expose this functionality to main-thread yet.
2346 MOZ_ASSERT(!NS_IsMainThread());
2347
2348 mConsoleEventNotifier = aHandler;
2349 }
2350
2351 void
AssertIsOnOwningThread() const2352 Console::AssertIsOnOwningThread() const
2353 {
2354 MOZ_ASSERT(mOwningThread);
2355 MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
2356 }
2357
2358 bool
IsShuttingDown() const2359 Console::IsShuttingDown() const
2360 {
2361 MOZ_ASSERT(mStatus != eUnknown);
2362 return mStatus == eShuttingDown;
2363 }
2364
2365 /* static */ already_AddRefed<Console>
GetConsole(const GlobalObject & aGlobal)2366 Console::GetConsole(const GlobalObject& aGlobal)
2367 {
2368 ErrorResult rv;
2369 RefPtr<Console> console = GetConsoleInternal(aGlobal, rv);
2370 if (NS_WARN_IF(rv.Failed()) || !console) {
2371 rv.SuppressException();
2372 return nullptr;
2373 }
2374
2375 console->AssertIsOnOwningThread();
2376
2377 if (console->IsShuttingDown()) {
2378 return nullptr;
2379 }
2380
2381 return console.forget();
2382 }
2383
2384 /* static */ Console*
GetConsoleInternal(const GlobalObject & aGlobal,ErrorResult & aRv)2385 Console::GetConsoleInternal(const GlobalObject& aGlobal, ErrorResult& aRv)
2386 {
2387 // Worklet
2388 if (NS_IsMainThread()) {
2389 nsCOMPtr<WorkletGlobalScope> workletScope =
2390 do_QueryInterface(aGlobal.GetAsSupports());
2391 if (workletScope) {
2392 return workletScope->GetConsole(aRv);
2393 }
2394 }
2395
2396 // Window
2397 if (NS_IsMainThread()) {
2398 nsCOMPtr<nsPIDOMWindowInner> innerWindow =
2399 do_QueryInterface(aGlobal.GetAsSupports());
2400 if (NS_WARN_IF(!innerWindow)) {
2401 return nullptr;
2402 }
2403
2404 nsGlobalWindow* window = nsGlobalWindow::Cast(innerWindow);
2405 return window->GetConsole(aRv);
2406 }
2407
2408 // Workers
2409 MOZ_ASSERT(!NS_IsMainThread());
2410
2411 JSContext* cx = aGlobal.Context();
2412 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
2413 MOZ_ASSERT(workerPrivate);
2414
2415 nsCOMPtr<nsIGlobalObject> global =
2416 do_QueryInterface(aGlobal.GetAsSupports());
2417 if (NS_WARN_IF(!global)) {
2418 return nullptr;
2419 }
2420
2421 WorkerGlobalScope* scope = workerPrivate->GlobalScope();
2422 MOZ_ASSERT(scope);
2423
2424 // Normal worker scope.
2425 if (scope == global) {
2426 return scope->GetConsole(aRv);
2427 }
2428
2429 // Debugger worker scope
2430 else {
2431 WorkerDebuggerGlobalScope* debuggerScope =
2432 workerPrivate->DebuggerGlobalScope();
2433 MOZ_ASSERT(debuggerScope);
2434 MOZ_ASSERT(debuggerScope == global, "Which kind of global do we have?");
2435
2436 return debuggerScope->GetConsole(aRv);
2437 }
2438 }
2439
2440 } // namespace dom
2441 } // namespace mozilla
2442