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