Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / base / Console.cpp
blobc0e13683e8dacc7c9736bc92ce5bff3f235f7d9b
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/Console.h"
7 #include "mozilla/dom/ConsoleBinding.h"
9 #include "mozilla/dom/Exceptions.h"
10 #include "mozilla/dom/ToJSValue.h"
11 #include "mozilla/Maybe.h"
12 #include "nsCycleCollectionParticipant.h"
13 #include "nsDocument.h"
14 #include "nsDOMNavigationTiming.h"
15 #include "nsGlobalWindow.h"
16 #include "nsJSUtils.h"
17 #include "nsPerformance.h"
18 #include "ScriptSettings.h"
19 #include "WorkerPrivate.h"
20 #include "WorkerRunnable.h"
21 #include "xpcprivate.h"
22 #include "nsContentUtils.h"
23 #include "nsDocShell.h"
25 #include "nsIConsoleAPIStorage.h"
26 #include "nsIDOMWindowUtils.h"
27 #include "nsIInterfaceRequestorUtils.h"
28 #include "nsILoadContext.h"
29 #include "nsIServiceManager.h"
30 #include "nsISupportsPrimitives.h"
31 #include "nsIWebNavigation.h"
32 #include "nsIXPConnect.h"
34 // The maximum allowed number of concurrent timers per page.
35 #define MAX_PAGE_TIMERS 10000
37 // The maximum allowed number of concurrent counters per page.
38 #define MAX_PAGE_COUNTERS 10000
40 // The maximum stacktrace depth when populating the stacktrace array used for
41 // console.trace().
42 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
44 // This tag is used in the Structured Clone Algorithm to move js values from
45 // worker thread to main thread
46 #define CONSOLE_TAG JS_SCTAG_USER_MIN
48 using namespace mozilla::dom::exceptions;
49 using namespace mozilla::dom::workers;
51 namespace mozilla {
52 namespace dom {
54 /**
55 * Console API in workers uses the Structured Clone Algorithm to move any value
56 * from the worker thread to the main-thread. Some object cannot be moved and,
57 * in these cases, we convert them to strings.
58 * It's not the best, but at least we are able to show something.
61 // This method is called by the Structured Clone Algorithm when some data has
62 // to be read.
63 static JSObject*
64 ConsoleStructuredCloneCallbacksRead(JSContext* aCx,
65 JSStructuredCloneReader* /* unused */,
66 uint32_t aTag, uint32_t aData,
67 void* aClosure)
69 AssertIsOnMainThread();
71 if (aTag != CONSOLE_TAG) {
72 return nullptr;
75 nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
76 MOZ_ASSERT(strings->Length() > aData);
78 JS::Rooted<JS::Value> value(aCx);
79 if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) {
80 return nullptr;
83 JS::Rooted<JSObject*> obj(aCx);
84 if (!JS_ValueToObject(aCx, value, &obj)) {
85 return nullptr;
88 return obj;
91 // This method is called by the Structured Clone Algorithm when some data has
92 // to be written.
93 static bool
94 ConsoleStructuredCloneCallbacksWrite(JSContext* aCx,
95 JSStructuredCloneWriter* aWriter,
96 JS::Handle<JSObject*> aObj,
97 void* aClosure)
99 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
100 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
101 if (!jsString) {
102 return false;
105 nsAutoJSString string;
106 if (!string.init(aCx, jsString)) {
107 return false;
110 nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
112 if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) {
113 return false;
116 strings->AppendElement(string);
118 return true;
121 static void
122 ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */,
123 uint32_t /* aErrorId */)
125 NS_WARNING("Failed to clone data for the Console API in workers.");
128 static const JSStructuredCloneCallbacks gConsoleCallbacks = {
129 ConsoleStructuredCloneCallbacksRead,
130 ConsoleStructuredCloneCallbacksWrite,
131 ConsoleStructuredCloneCallbacksError
134 class ConsoleCallData MOZ_FINAL
136 public:
137 ConsoleCallData()
138 : mMethodName(Console::MethodLog)
139 , mPrivate(false)
140 , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
141 , mIDType(eUnknown)
142 , mOuterIDNumber(0)
143 , mInnerIDNumber(0)
146 void
147 Initialize(JSContext* aCx, Console::MethodName aName,
148 const nsAString& aString, const Sequence<JS::Value>& aArguments)
150 mGlobal = JS::CurrentGlobalOrNull(aCx);
151 mMethodName = aName;
152 mMethodString = aString;
154 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
155 mArguments.AppendElement(aArguments[i]);
159 void
160 SetIDs(uint64_t aOuterID, uint64_t aInnerID)
162 MOZ_ASSERT(mIDType == eUnknown);
164 mOuterIDNumber = aOuterID;
165 mInnerIDNumber = aInnerID;
166 mIDType = eNumber;
169 void
170 SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
172 MOZ_ASSERT(mIDType == eUnknown);
174 mOuterIDString = aOuterID;
175 mInnerIDString = aInnerID;
176 mIDType = eString;
179 void
180 CleanupJSObjects()
182 mArguments.Clear();
183 mGlobal = nullptr;
186 JS::Heap<JSObject*> mGlobal;
188 Console::MethodName mMethodName;
189 bool mPrivate;
190 int64_t mTimeStamp;
191 DOMHighResTimeStamp mMonotonicTimer;
193 // The concept of outerID and innerID is misleading because when a
194 // ConsoleCallData is created from a window, these are the window IDs, but
195 // when the object is created from a SharedWorker, a ServiceWorker or a
196 // subworker of a ChromeWorker these IDs are the type of worker and the
197 // filename of the callee.
198 // In Console.jsm the ID is 'jsm'.
199 enum {
200 eString,
201 eNumber,
202 eUnknown
203 } mIDType;
205 uint64_t mOuterIDNumber;
206 nsString mOuterIDString;
208 uint64_t mInnerIDNumber;
209 nsString mInnerIDString;
211 nsString mMethodString;
212 nsTArray<JS::Heap<JS::Value>> mArguments;
214 // Stack management is complicated, because we want to do it as
215 // lazily as possible. Therefore, we have the following behavior:
216 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
217 // 2) mReifiedStack is initialized if we're created in a worker.
218 // 3) mStack is set (possibly to null if there is no JS on the stack) if
219 // we're created on main thread.
220 Maybe<ConsoleStackEntry> mTopStackFrame;
221 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
222 nsCOMPtr<nsIStackFrame> mStack;
225 // This class is used to clear any exception at the end of this method.
226 class ClearException
228 public:
229 explicit ClearException(JSContext* aCx)
230 : mCx(aCx)
234 ~ClearException()
236 JS_ClearPendingException(mCx);
239 private:
240 JSContext* mCx;
243 class ConsoleRunnable : public nsRunnable
245 public:
246 explicit ConsoleRunnable(Console* aConsole)
247 : mWorkerPrivate(GetCurrentThreadWorkerPrivate())
248 , mConsole(aConsole)
250 MOZ_ASSERT(mWorkerPrivate);
253 virtual
254 ~ConsoleRunnable()
258 bool
259 Dispatch()
261 mWorkerPrivate->AssertIsOnWorkerThread();
263 JSContext* cx = mWorkerPrivate->GetJSContext();
265 if (!PreDispatch(cx)) {
266 return false;
269 AutoSyncLoopHolder syncLoop(mWorkerPrivate);
270 mSyncLoopTarget = syncLoop.EventTarget();
272 if (NS_FAILED(NS_DispatchToMainThread(this))) {
273 JS_ReportError(cx,
274 "Failed to dispatch to main thread for the Console API!");
275 return false;
278 return syncLoop.Run();
281 private:
282 NS_IMETHOD Run()
284 AssertIsOnMainThread();
286 // Walk up to our containing page
287 WorkerPrivate* wp = mWorkerPrivate;
288 while (wp->GetParent()) {
289 wp = wp->GetParent();
292 nsPIDOMWindow* window = wp->GetWindow();
293 if (!window) {
294 RunWindowless();
295 } else {
296 RunWithWindow(window);
299 nsRefPtr<MainThreadStopSyncLoopRunnable> response =
300 new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
301 mSyncLoopTarget.forget(),
302 true);
303 if (!response->Dispatch(nullptr)) {
304 NS_WARNING("Failed to dispatch response!");
307 return NS_OK;
310 void
311 RunWithWindow(nsPIDOMWindow* aWindow)
313 AutoJSAPI jsapi;
314 MOZ_ASSERT(aWindow);
316 nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(aWindow);
317 if (NS_WARN_IF(!jsapi.Init(win))) {
318 return;
321 MOZ_ASSERT(aWindow->IsInnerWindow());
322 nsPIDOMWindow* outerWindow = aWindow->GetOuterWindow();
323 MOZ_ASSERT(outerWindow);
325 RunConsole(jsapi.cx(), outerWindow, aWindow);
328 void
329 RunWindowless()
331 WorkerPrivate* wp = mWorkerPrivate;
332 while (wp->GetParent()) {
333 wp = wp->GetParent();
336 MOZ_ASSERT(!wp->GetWindow());
338 AutoSafeJSContext cx;
340 nsCOMPtr<nsIXPConnectJSObjectHolder> sandbox =
341 mConsole->GetOrCreateSandbox(cx, wp->GetPrincipal());
342 if (NS_WARN_IF(!sandbox)) {
343 return;
346 JS::Rooted<JSObject*> global(cx, sandbox->GetJSObject());
347 if (NS_WARN_IF(!global)) {
348 return;
351 // The CreateSandbox call returns a proxy to the actual sandbox object. We
352 // don't need a proxy here.
353 global = js::UncheckedUnwrap(global);
355 JSAutoCompartment ac(cx, global);
357 RunConsole(cx, nullptr, nullptr);
360 protected:
361 virtual bool
362 PreDispatch(JSContext* aCx) = 0;
364 virtual void
365 RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
366 nsPIDOMWindow* aInnerWindow) = 0;
368 WorkerPrivate* mWorkerPrivate;
370 // Raw pointer because this method is async and this object is kept alive by
371 // the caller.
372 Console* mConsole;
374 private:
375 nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
378 // This runnable appends a CallData object into the Console queue running on
379 // the main-thread.
380 class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
382 public:
383 ConsoleCallDataRunnable(Console* aConsole,
384 ConsoleCallData* aCallData)
385 : ConsoleRunnable(aConsole)
386 , mCallData(aCallData)
389 private:
390 ~ConsoleCallDataRunnable()
393 bool
394 PreDispatch(JSContext* aCx) MOZ_OVERRIDE
396 mWorkerPrivate->AssertIsOnWorkerThread();
398 ClearException ce(aCx);
399 JSAutoCompartment ac(aCx, mCallData->mGlobal);
401 JS::Rooted<JSObject*> arguments(aCx,
402 JS_NewArrayObject(aCx, mCallData->mArguments.Length()));
403 if (!arguments) {
404 return false;
407 JS::Rooted<JS::Value> arg(aCx);
408 for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) {
409 arg = mCallData->mArguments[i];
410 if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
411 return false;
415 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
417 if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
418 return false;
421 mCallData->CleanupJSObjects();
422 return true;
425 void
426 RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
427 nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
429 MOZ_ASSERT(NS_IsMainThread());
431 // The windows have to run in parallel.
432 MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
434 if (aOuterWindow) {
435 mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
436 } else {
437 ConsoleStackEntry frame;
438 if (mCallData->mTopStackFrame) {
439 frame = *mCallData->mTopStackFrame;
442 nsString id;
443 if (mWorkerPrivate->IsSharedWorker()) {
444 id = NS_LITERAL_STRING("SharedWorker");
445 } else if (mWorkerPrivate->IsServiceWorker()) {
446 id = NS_LITERAL_STRING("ServiceWorker");
447 } else {
448 id = NS_LITERAL_STRING("Worker");
451 mCallData->SetIDs(id, frame.mFilename);
454 ProcessCallData(aCx);
455 mCallData->CleanupJSObjects();
458 private:
459 void
460 ProcessCallData(JSContext* aCx)
462 ClearException ce(aCx);
464 JS::Rooted<JS::Value> argumentsValue(aCx);
465 if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
466 return;
469 MOZ_ASSERT(argumentsValue.isObject());
470 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
471 MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
473 uint32_t length;
474 if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
475 return;
478 for (uint32_t i = 0; i < length; ++i) {
479 JS::Rooted<JS::Value> value(aCx);
481 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
482 return;
485 mCallData->mArguments.AppendElement(value);
488 MOZ_ASSERT(mCallData->mArguments.Length() == length);
490 mCallData->mGlobal = JS::CurrentGlobalOrNull(aCx);
491 mConsole->ProcessCallData(mCallData);
494 ConsoleCallData* mCallData;
496 JSAutoStructuredCloneBuffer mArguments;
497 nsTArray<nsString> mStrings;
500 // This runnable calls ProfileMethod() on the console on the main-thread.
501 class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
503 public:
504 ConsoleProfileRunnable(Console* aConsole, const nsAString& aAction,
505 const Sequence<JS::Value>& aArguments)
506 : ConsoleRunnable(aConsole)
507 , mAction(aAction)
508 , mArguments(aArguments)
510 MOZ_ASSERT(aConsole);
513 private:
514 bool
515 PreDispatch(JSContext* aCx) MOZ_OVERRIDE
517 ClearException ce(aCx);
519 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
520 if (!global) {
521 return false;
524 JSAutoCompartment ac(aCx, global);
526 JS::Rooted<JSObject*> arguments(aCx,
527 JS_NewArrayObject(aCx, mArguments.Length()));
528 if (!arguments) {
529 return false;
532 JS::Rooted<JS::Value> arg(aCx);
533 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
534 arg = mArguments[i];
535 if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
536 return false;
540 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
542 if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
543 return false;
546 return true;
549 void
550 RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow,
551 nsPIDOMWindow* aInnerWindow) MOZ_OVERRIDE
553 ClearException ce(aCx);
555 JS::Rooted<JS::Value> argumentsValue(aCx);
556 if (!mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
557 return;
560 MOZ_ASSERT(argumentsValue.isObject());
561 JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
562 MOZ_ASSERT(JS_IsArrayObject(aCx, argumentsObj));
564 uint32_t length;
565 if (!JS_GetArrayLength(aCx, argumentsObj, &length)) {
566 return;
569 Sequence<JS::Value> arguments;
571 for (uint32_t i = 0; i < length; ++i) {
572 JS::Rooted<JS::Value> value(aCx);
574 if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
575 return;
578 arguments.AppendElement(value);
581 mConsole->ProfileMethod(aCx, mAction, arguments);
584 nsString mAction;
585 Sequence<JS::Value> mArguments;
587 JSAutoStructuredCloneBuffer mBuffer;
588 nsTArray<nsString> mStrings;
591 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
593 // We don't need to traverse/unlink mStorage and mSanbox because they are not
594 // CCed objects and they are only used on the main thread, even when this
595 // Console object is used on workers.
597 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
598 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
599 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
600 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
602 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
603 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
604 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
605 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
607 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
608 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
609 NS_IMPL_CYCLE_COLLECTION_TRACE_END
611 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
612 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
614 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
615 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
616 NS_INTERFACE_MAP_ENTRY(nsIObserver)
617 NS_INTERFACE_MAP_ENTRY(nsISupports)
618 NS_INTERFACE_MAP_END
620 Console::Console(nsPIDOMWindow* aWindow)
621 : mWindow(aWindow)
622 , mOuterID(0)
623 , mInnerID(0)
625 if (mWindow) {
626 MOZ_ASSERT(mWindow->IsInnerWindow());
627 mInnerID = mWindow->WindowID();
629 nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
630 MOZ_ASSERT(outerWindow);
631 mOuterID = outerWindow->WindowID();
634 if (NS_IsMainThread()) {
635 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
636 if (obs) {
637 obs->AddObserver(this, "inner-window-destroyed", false);
641 mozilla::HoldJSObjects(this);
644 Console::~Console()
646 if (!NS_IsMainThread()) {
647 nsCOMPtr<nsIThread> mainThread;
648 NS_GetMainThread(getter_AddRefs(mainThread));
650 if (mStorage) {
651 nsIConsoleAPIStorage* storage;
652 mStorage.forget(&storage);
653 NS_ProxyRelease(mainThread, storage, false);
656 if (mSandbox) {
657 nsIXPConnectJSObjectHolder* sandbox;
658 mSandbox.forget(&sandbox);
659 NS_ProxyRelease(mainThread, sandbox, false);
663 mozilla::DropJSObjects(this);
666 NS_IMETHODIMP
667 Console::Observe(nsISupports* aSubject, const char* aTopic,
668 const char16_t* aData)
670 MOZ_ASSERT(NS_IsMainThread());
672 if (strcmp(aTopic, "inner-window-destroyed")) {
673 return NS_OK;
676 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
677 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
679 uint64_t innerID;
680 nsresult rv = wrapper->GetData(&innerID);
681 NS_ENSURE_SUCCESS(rv, rv);
683 if (innerID == mInnerID) {
684 nsCOMPtr<nsIObserverService> obs =
685 do_GetService("@mozilla.org/observer-service;1");
686 if (obs) {
687 obs->RemoveObserver(this, "inner-window-destroyed");
690 mTimerRegistry.Clear();
693 return NS_OK;
696 JSObject*
697 Console::WrapObject(JSContext* aCx)
699 return ConsoleBinding::Wrap(aCx, this);
702 #define METHOD(name, string) \
703 void \
704 Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
706 Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
709 METHOD(Log, "log")
710 METHOD(Info, "info")
711 METHOD(Warn, "warn")
712 METHOD(Error, "error")
713 METHOD(Exception, "exception")
714 METHOD(Debug, "debug")
715 METHOD(Table, "table")
717 void
718 Console::Trace(JSContext* aCx)
720 const Sequence<JS::Value> data;
721 Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
724 // Displays an interactive listing of all the properties of an object.
725 METHOD(Dir, "dir");
727 METHOD(Group, "group")
728 METHOD(GroupCollapsed, "groupCollapsed")
729 METHOD(GroupEnd, "groupEnd")
731 void
732 Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
734 Sequence<JS::Value> data;
735 SequenceRooter<JS::Value> rooter(aCx, &data);
737 if (!aTime.isUndefined()) {
738 data.AppendElement(aTime);
741 Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
744 void
745 Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
747 Sequence<JS::Value> data;
748 SequenceRooter<JS::Value> rooter(aCx, &data);
750 if (!aTime.isUndefined()) {
751 data.AppendElement(aTime);
754 Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
757 void
758 Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
760 ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
763 void
764 Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
766 ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
769 void
770 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
771 const Sequence<JS::Value>& aData)
773 if (!NS_IsMainThread()) {
774 // Here we are in a worker thread.
775 nsRefPtr<ConsoleProfileRunnable> runnable =
776 new ConsoleProfileRunnable(this, aAction, aData);
777 runnable->Dispatch();
778 return;
781 ClearException ce(aCx);
783 RootedDictionary<ConsoleProfileEvent> event(aCx);
784 event.mAction = aAction;
786 event.mArguments.Construct();
787 Sequence<JS::Value>& sequence = event.mArguments.Value();
789 for (uint32_t i = 0; i < aData.Length(); ++i) {
790 sequence.AppendElement(aData[i]);
793 JS::Rooted<JS::Value> eventValue(aCx);
794 if (!ToJSValue(aCx, event, &eventValue)) {
795 return;
798 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
799 MOZ_ASSERT(eventObj);
801 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
802 JSPROP_ENUMERATE)) {
803 return;
806 nsXPConnect* xpc = nsXPConnect::XPConnect();
807 nsCOMPtr<nsISupports> wrapper;
808 const nsIID& iid = NS_GET_IID(nsISupports);
810 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
811 return;
814 nsCOMPtr<nsIObserverService> obs =
815 do_GetService("@mozilla.org/observer-service;1");
816 if (obs) {
817 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
821 void
822 Console::Assert(JSContext* aCx, bool aCondition,
823 const Sequence<JS::Value>& aData)
825 if (!aCondition) {
826 Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
830 METHOD(Count, "count")
832 void
833 Console::__noSuchMethod__()
835 // Nothing to do.
838 static
839 nsresult
840 StackFrameToStackEntry(nsIStackFrame* aStackFrame,
841 ConsoleStackEntry& aStackEntry,
842 uint32_t aLanguage)
844 MOZ_ASSERT(aStackFrame);
846 nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
847 NS_ENSURE_SUCCESS(rv, rv);
849 int32_t lineNumber;
850 rv = aStackFrame->GetLineNumber(&lineNumber);
851 NS_ENSURE_SUCCESS(rv, rv);
853 aStackEntry.mLineNumber = lineNumber;
855 int32_t columnNumber;
856 rv = aStackFrame->GetColumnNumber(&columnNumber);
857 NS_ENSURE_SUCCESS(rv, rv);
859 aStackEntry.mColumnNumber = columnNumber;
861 rv = aStackFrame->GetName(aStackEntry.mFunctionName);
862 NS_ENSURE_SUCCESS(rv, rv);
864 aStackEntry.mLanguage = aLanguage;
865 return NS_OK;
868 static
869 nsresult
870 ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
872 nsCOMPtr<nsIStackFrame> stack(aStack);
874 while (stack) {
875 uint32_t language;
876 nsresult rv = stack->GetLanguage(&language);
877 NS_ENSURE_SUCCESS(rv, rv);
879 if (language == nsIProgrammingLanguage::JAVASCRIPT ||
880 language == nsIProgrammingLanguage::JAVASCRIPT2) {
881 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
882 rv = StackFrameToStackEntry(stack, data, language);
883 NS_ENSURE_SUCCESS(rv, rv);
886 nsCOMPtr<nsIStackFrame> caller;
887 rv = stack->GetCaller(getter_AddRefs(caller));
888 NS_ENSURE_SUCCESS(rv, rv);
890 stack.swap(caller);
893 return NS_OK;
896 class ConsoleTimelineMarker : public TimelineMarker
898 public:
899 ConsoleTimelineMarker(nsDocShell* aDocShell,
900 TracingMetadata aMetaData,
901 const nsAString& aCause)
902 : TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
904 if (aMetaData == TRACING_INTERVAL_END) {
905 CaptureStack();
909 virtual bool Equals(const TimelineMarker* aOther)
911 if (!TimelineMarker::Equals(aOther)) {
912 return false;
914 // Console markers must have matching causes as well.
915 return GetCause() == aOther->GetCause();
918 virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
920 if (GetMetaData() == TRACING_INTERVAL_START) {
921 aMarker.mCauseName.Construct(GetCause());
922 } else {
923 aMarker.mEndStack = GetStack();
928 // Queue a call to a console method. See the CALL_DELAY constant.
929 void
930 Console::Method(JSContext* aCx, MethodName aMethodName,
931 const nsAString& aMethodString,
932 const Sequence<JS::Value>& aData)
934 nsAutoPtr<ConsoleCallData> callData(new ConsoleCallData());
936 ClearException ce(aCx);
938 callData->Initialize(aCx, aMethodName, aMethodString, aData);
940 if (mWindow) {
941 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
942 if (!webNav) {
943 return;
946 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
947 MOZ_ASSERT(loadContext);
949 loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
952 uint32_t maxDepth = ShouldIncludeStackTrace(aMethodName) ?
953 DEFAULT_MAX_STACKTRACE_DEPTH : 1;
954 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
956 if (!stack) {
957 return;
960 // Walk up to the first JS stack frame and save it if we find it.
961 do {
962 uint32_t language;
963 nsresult rv = stack->GetLanguage(&language);
964 if (NS_FAILED(rv)) {
965 return;
968 if (language == nsIProgrammingLanguage::JAVASCRIPT ||
969 language == nsIProgrammingLanguage::JAVASCRIPT2) {
970 callData->mTopStackFrame.emplace();
971 nsresult rv = StackFrameToStackEntry(stack,
972 *callData->mTopStackFrame,
973 language);
974 if (NS_FAILED(rv)) {
975 return;
978 break;
981 nsCOMPtr<nsIStackFrame> caller;
982 rv = stack->GetCaller(getter_AddRefs(caller));
983 if (NS_FAILED(rv)) {
984 return;
987 stack.swap(caller);
988 } while (stack);
990 if (NS_IsMainThread()) {
991 callData->mStack = stack;
992 } else {
993 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
994 // before we post our runnable to the main thread.
995 callData->mReifiedStack.emplace();
996 nsresult rv = ReifyStack(stack, *callData->mReifiedStack);
997 if (NS_WARN_IF(NS_FAILED(rv))) {
998 return;
1002 // Monotonic timer for 'time' and 'timeEnd'
1003 if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd)) {
1004 if (mWindow) {
1005 nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
1006 MOZ_ASSERT(win);
1008 nsRefPtr<nsPerformance> performance = win->GetPerformance();
1009 if (!performance) {
1010 return;
1013 callData->mMonotonicTimer = performance->Now();
1015 // 'time' and 'timeEnd' are displayed in the devtools timeline if active.
1016 bool isTimelineRecording = false;
1017 nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
1018 if (docShell) {
1019 docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
1022 if (isTimelineRecording && aData.Length() == 1) {
1023 JS::Rooted<JS::Value> value(aCx, aData[0]);
1024 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1025 if (jsString) {
1026 nsAutoJSString key;
1027 if (key.init(aCx, jsString)) {
1028 mozilla::UniquePtr<TimelineMarker> marker =
1029 MakeUnique<ConsoleTimelineMarker>(docShell,
1030 aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
1031 key);
1032 docShell->AddProfileTimelineMarker(marker);
1037 } else {
1038 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1039 MOZ_ASSERT(workerPrivate);
1041 TimeDuration duration =
1042 mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
1044 callData->mMonotonicTimer = duration.ToMilliseconds();
1048 if (NS_IsMainThread()) {
1049 callData->SetIDs(mOuterID, mInnerID);
1050 ProcessCallData(callData);
1051 return;
1054 // Note: we can pass the reference of callData because this runnable calls
1055 // ProcessCallData() synchronously.
1056 nsRefPtr<ConsoleCallDataRunnable> runnable =
1057 new ConsoleCallDataRunnable(this, callData);
1058 runnable->Dispatch();
1061 // We store information to lazily compute the stack in the reserved slots of
1062 // LazyStackGetter. The first slot always stores a JS object: it's either the
1063 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
1064 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
1065 // reified the stack yet, or an UndefinedValue() otherwise.
1066 enum {
1067 SLOT_STACKOBJ,
1068 SLOT_RAW_STACK
1071 bool
1072 LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
1074 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
1075 JS::Rooted<JSObject*> callee(aCx, &args.callee());
1077 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
1078 if (v.isUndefined()) {
1079 // Already reified.
1080 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1081 return true;
1084 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1085 nsTArray<ConsoleStackEntry> reifiedStack;
1086 nsresult rv = ReifyStack(stack, reifiedStack);
1087 if (NS_FAILED(rv)) {
1088 Throw(aCx, rv);
1089 return false;
1092 JS::Rooted<JS::Value> stackVal(aCx);
1093 if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
1094 return false;
1097 MOZ_ASSERT(stackVal.isObject());
1099 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1100 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1102 args.rval().set(stackVal);
1103 return true;
1106 void
1107 Console::ProcessCallData(ConsoleCallData* aData)
1109 MOZ_ASSERT(aData);
1110 MOZ_ASSERT(NS_IsMainThread());
1112 ConsoleStackEntry frame;
1113 if (aData->mTopStackFrame) {
1114 frame = *aData->mTopStackFrame;
1117 AutoSafeJSContext cx;
1118 ClearException ce(cx);
1119 RootedDictionary<ConsoleEvent> event(cx);
1121 JSAutoCompartment ac(cx, aData->mGlobal);
1123 event.mID.Construct();
1124 event.mInnerID.Construct();
1126 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1127 if (aData->mIDType == ConsoleCallData::eString) {
1128 event.mID.Value().SetAsString() = aData->mOuterIDString;
1129 event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
1130 } else {
1131 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1132 event.mID.Value().SetAsUnsignedLong() = aData->mOuterIDNumber;
1133 event.mInnerID.Value().SetAsUnsignedLong() = aData->mInnerIDNumber;
1136 event.mLevel = aData->mMethodString;
1137 event.mFilename = frame.mFilename;
1138 event.mLineNumber = frame.mLineNumber;
1139 event.mColumnNumber = frame.mColumnNumber;
1140 event.mFunctionName = frame.mFunctionName;
1141 event.mTimeStamp = aData->mTimeStamp;
1142 event.mPrivate = aData->mPrivate;
1144 switch (aData->mMethodName) {
1145 case MethodLog:
1146 case MethodInfo:
1147 case MethodWarn:
1148 case MethodError:
1149 case MethodException:
1150 case MethodDebug:
1151 case MethodAssert:
1152 event.mArguments.Construct();
1153 event.mStyles.Construct();
1154 ProcessArguments(cx, aData->mArguments, event.mArguments.Value(),
1155 event.mStyles.Value());
1156 break;
1158 default:
1159 event.mArguments.Construct();
1160 ArgumentsToValueList(aData->mArguments, event.mArguments.Value());
1163 if (aData->mMethodName == MethodGroup ||
1164 aData->mMethodName == MethodGroupCollapsed ||
1165 aData->mMethodName == MethodGroupEnd) {
1166 ComposeGroupName(cx, aData->mArguments, event.mGroupName);
1169 else if (aData->mMethodName == MethodTime && !aData->mArguments.IsEmpty()) {
1170 event.mTimer = StartTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
1173 else if (aData->mMethodName == MethodTimeEnd && !aData->mArguments.IsEmpty()) {
1174 event.mTimer = StopTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
1177 else if (aData->mMethodName == MethodCount) {
1178 event.mCounter = IncreaseCounter(cx, frame, aData->mArguments);
1181 // We want to create a console event object and pass it to our
1182 // nsIConsoleAPIStorage implementation. We want to define some accessor
1183 // properties on this object, and those will need to keep an nsIStackFrame
1184 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1185 // further, passing untrusted objects to system code is likely to run afoul of
1186 // Object Xrays. So we want to wrap in a system-principal scope here. But
1187 // which one? We could cheat and try to get the underlying JSObject* of
1188 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1189 // with explicit permission from the XPConnect module owner. If you're
1190 // tempted to do that anywhere else, talk to said module owner first.
1191 JSAutoCompartment ac2(cx, xpc::PrivilegedJunkScope());
1193 JS::Rooted<JS::Value> eventValue(cx);
1194 if (!ToJSValue(cx, event, &eventValue)) {
1195 return;
1198 JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
1199 MOZ_ASSERT(eventObj);
1201 if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
1202 return;
1205 if (ShouldIncludeStackTrace(aData->mMethodName)) {
1206 // Now define the "stacktrace" property on eventObj. There are two cases
1207 // here. Either we came from a worker and have a reified stack, or we want
1208 // to define a getter that will lazily reify the stack.
1209 if (aData->mReifiedStack) {
1210 JS::Rooted<JS::Value> stacktrace(cx);
1211 if (!ToJSValue(cx, *aData->mReifiedStack, &stacktrace) ||
1212 !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
1213 JSPROP_ENUMERATE)) {
1214 return;
1216 } else {
1217 JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
1218 eventObj, "stacktrace");
1219 if (!fun) {
1220 return;
1223 JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
1225 // We want to store our stack in the function and have it stay alive. But
1226 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1227 // wrapper and the raw pointer: the former will keep the latter alive.
1228 JS::Rooted<JS::Value> stackVal(cx);
1229 nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
1230 &stackVal);
1231 if (NS_FAILED(rv)) {
1232 return;
1235 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1236 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1237 JS::PrivateValue(aData->mStack.get()));
1239 if (!JS_DefineProperty(cx, eventObj, "stacktrace",
1240 JS::UndefinedHandleValue,
1241 JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
1242 JSPROP_SETTER,
1243 JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
1244 nullptr)) {
1245 return;
1250 if (!mStorage) {
1251 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1254 if (!mStorage) {
1255 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1256 return;
1259 nsAutoString innerID, outerID;
1261 MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
1262 if (aData->mIDType == ConsoleCallData::eString) {
1263 outerID = aData->mOuterIDString;
1264 innerID = aData->mInnerIDString;
1265 } else {
1266 MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
1267 outerID.AppendInt(aData->mOuterIDNumber);
1268 innerID.AppendInt(aData->mInnerIDNumber);
1271 if (NS_FAILED(mStorage->RecordPendingEvent(innerID, outerID, eventValue))) {
1272 NS_WARNING("Failed to record a console event.");
1276 void
1277 Console::ProcessArguments(JSContext* aCx,
1278 const nsTArray<JS::Heap<JS::Value>>& aData,
1279 Sequence<JS::Value>& aSequence,
1280 Sequence<JS::Value>& aStyles)
1282 if (aData.IsEmpty()) {
1283 return;
1286 if (aData.Length() == 1 || !aData[0].isString()) {
1287 ArgumentsToValueList(aData, aSequence);
1288 return;
1291 JS::Rooted<JS::Value> format(aCx, aData[0]);
1292 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1293 if (!jsString) {
1294 return;
1297 nsAutoJSString string;
1298 if (!string.init(aCx, jsString)) {
1299 return;
1302 nsString::const_iterator start, end;
1303 string.BeginReading(start);
1304 string.EndReading(end);
1306 nsString output;
1307 uint32_t index = 1;
1309 while (start != end) {
1310 if (*start != '%') {
1311 output.Append(*start);
1312 ++start;
1313 continue;
1316 ++start;
1317 if (start == end) {
1318 output.Append('%');
1319 break;
1322 if (*start == '%') {
1323 output.Append(*start);
1324 ++start;
1325 continue;
1328 nsAutoString tmp;
1329 tmp.Append('%');
1331 int32_t integer = -1;
1332 int32_t mantissa = -1;
1334 // Let's parse %<number>.<number> for %d and %f
1335 if (*start >= '0' && *start <= '9') {
1336 integer = 0;
1338 do {
1339 integer = integer * 10 + *start - '0';
1340 tmp.Append(*start);
1341 ++start;
1342 } while (*start >= '0' && *start <= '9' && start != end);
1345 if (start == end) {
1346 output.Append(tmp);
1347 break;
1350 if (*start == '.') {
1351 tmp.Append(*start);
1352 ++start;
1354 if (start == end) {
1355 output.Append(tmp);
1356 break;
1359 // '.' must be followed by a number.
1360 if (*start < '0' || *start > '9') {
1361 output.Append(tmp);
1362 continue;
1365 mantissa = 0;
1367 do {
1368 mantissa = mantissa * 10 + *start - '0';
1369 tmp.Append(*start);
1370 ++start;
1371 } while (*start >= '0' && *start <= '9' && start != end);
1373 if (start == end) {
1374 output.Append(tmp);
1375 break;
1379 char ch = *start;
1380 tmp.Append(ch);
1381 ++start;
1383 switch (ch) {
1384 case 'o':
1385 case 'O':
1387 if (!output.IsEmpty()) {
1388 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1389 output.get(),
1390 output.Length()));
1391 if (!str) {
1392 return;
1395 aSequence.AppendElement(JS::StringValue(str));
1396 output.Truncate();
1399 JS::Rooted<JS::Value> v(aCx);
1400 if (index < aData.Length()) {
1401 v = aData[index++];
1404 aSequence.AppendElement(v);
1405 break;
1408 case 'c':
1410 if (!output.IsEmpty()) {
1411 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1412 output.get(),
1413 output.Length()));
1414 if (!str) {
1415 return;
1418 aSequence.AppendElement(JS::StringValue(str));
1419 output.Truncate();
1422 if (index < aData.Length()) {
1423 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1424 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1425 if (!jsString) {
1426 return;
1429 int32_t diff = aSequence.Length() - aStyles.Length();
1430 if (diff > 0) {
1431 for (int32_t i = 0; i < diff; i++) {
1432 aStyles.AppendElement(JS::NullValue());
1435 aStyles.AppendElement(JS::StringValue(jsString));
1437 break;
1440 case 's':
1441 if (index < aData.Length()) {
1442 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1443 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1444 if (!jsString) {
1445 return;
1448 nsAutoJSString v;
1449 if (!v.init(aCx, jsString)) {
1450 return;
1453 output.Append(v);
1455 break;
1457 case 'd':
1458 case 'i':
1459 if (index < aData.Length()) {
1460 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1462 int32_t v;
1463 if (!JS::ToInt32(aCx, value, &v)) {
1464 return;
1467 nsCString format;
1468 MakeFormatString(format, integer, mantissa, 'd');
1469 output.AppendPrintf(format.get(), v);
1471 break;
1473 case 'f':
1474 if (index < aData.Length()) {
1475 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1477 double v;
1478 if (!JS::ToNumber(aCx, value, &v)) {
1479 return;
1482 nsCString format;
1483 MakeFormatString(format, integer, mantissa, 'f');
1484 output.AppendPrintf(format.get(), v);
1486 break;
1488 default:
1489 output.Append(tmp);
1490 break;
1494 if (!output.IsEmpty()) {
1495 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
1496 output.Length()));
1497 if (!str) {
1498 return;
1501 aSequence.AppendElement(JS::StringValue(str));
1504 // The rest of the array, if unused by the format string.
1505 for (; index < aData.Length(); ++index) {
1506 aSequence.AppendElement(aData[index]);
1510 void
1511 Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
1512 int32_t aMantissa, char aCh)
1514 aFormat.Append('%');
1515 if (aInteger >= 0) {
1516 aFormat.AppendInt(aInteger);
1519 if (aMantissa >= 0) {
1520 aFormat.Append('.');
1521 aFormat.AppendInt(aMantissa);
1524 aFormat.Append(aCh);
1527 void
1528 Console::ComposeGroupName(JSContext* aCx,
1529 const nsTArray<JS::Heap<JS::Value>>& aData,
1530 nsAString& aName)
1532 for (uint32_t i = 0; i < aData.Length(); ++i) {
1533 if (i != 0) {
1534 aName.AppendASCII(" ");
1537 JS::Rooted<JS::Value> value(aCx, aData[i]);
1538 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1539 if (!jsString) {
1540 return;
1543 nsAutoJSString string;
1544 if (!string.init(aCx, jsString)) {
1545 return;
1548 aName.Append(string);
1552 JS::Value
1553 Console::StartTimer(JSContext* aCx, const JS::Value& aName,
1554 DOMHighResTimeStamp aTimestamp)
1556 if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
1557 RootedDictionary<ConsoleTimerError> error(aCx);
1559 JS::Rooted<JS::Value> value(aCx);
1560 if (!ToJSValue(aCx, error, &value)) {
1561 return JS::UndefinedValue();
1564 return value;
1567 RootedDictionary<ConsoleTimerStart> timer(aCx);
1569 JS::Rooted<JS::Value> name(aCx, aName);
1570 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1571 if (!jsString) {
1572 return JS::UndefinedValue();
1575 nsAutoJSString key;
1576 if (!key.init(aCx, jsString)) {
1577 return JS::UndefinedValue();
1580 timer.mName = key;
1582 DOMHighResTimeStamp entry;
1583 if (!mTimerRegistry.Get(key, &entry)) {
1584 mTimerRegistry.Put(key, aTimestamp);
1585 } else {
1586 aTimestamp = entry;
1589 timer.mStarted = aTimestamp;
1591 JS::Rooted<JS::Value> value(aCx);
1592 if (!ToJSValue(aCx, timer, &value)) {
1593 return JS::UndefinedValue();
1596 return value;
1599 JS::Value
1600 Console::StopTimer(JSContext* aCx, const JS::Value& aName,
1601 DOMHighResTimeStamp aTimestamp)
1603 JS::Rooted<JS::Value> name(aCx, aName);
1604 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1605 if (!jsString) {
1606 return JS::UndefinedValue();
1609 nsAutoJSString key;
1610 if (!key.init(aCx, jsString)) {
1611 return JS::UndefinedValue();
1614 DOMHighResTimeStamp entry;
1615 if (!mTimerRegistry.Get(key, &entry)) {
1616 return JS::UndefinedValue();
1619 mTimerRegistry.Remove(key);
1621 RootedDictionary<ConsoleTimerEnd> timer(aCx);
1622 timer.mName = key;
1623 timer.mDuration = aTimestamp - entry;
1625 JS::Rooted<JS::Value> value(aCx);
1626 if (!ToJSValue(aCx, timer, &value)) {
1627 return JS::UndefinedValue();
1630 return value;
1633 void
1634 Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
1635 Sequence<JS::Value>& aSequence)
1637 for (uint32_t i = 0; i < aData.Length(); ++i) {
1638 aSequence.AppendElement(aData[i]);
1642 JS::Value
1643 Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
1644 const nsTArray<JS::Heap<JS::Value>>& aArguments)
1646 ClearException ce(aCx);
1648 nsAutoString key;
1649 nsAutoString label;
1651 if (!aArguments.IsEmpty()) {
1652 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
1653 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
1655 nsAutoJSString string;
1656 if (jsString && string.init(aCx, jsString)) {
1657 label = string;
1658 key = string;
1662 if (key.IsEmpty()) {
1663 key.Append(aFrame.mFilename);
1664 key.Append(':');
1665 key.AppendInt(aFrame.mLineNumber);
1668 uint32_t count = 0;
1669 if (!mCounterRegistry.Get(key, &count)) {
1670 if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
1671 RootedDictionary<ConsoleCounterError> error(aCx);
1673 JS::Rooted<JS::Value> value(aCx);
1674 if (!ToJSValue(aCx, error, &value)) {
1675 return JS::UndefinedValue();
1678 return value;
1682 ++count;
1683 mCounterRegistry.Put(key, count);
1685 RootedDictionary<ConsoleCounter> data(aCx);
1686 data.mLabel = label;
1687 data.mCount = count;
1689 JS::Rooted<JS::Value> value(aCx);
1690 if (!ToJSValue(aCx, data, &value)) {
1691 return JS::UndefinedValue();
1694 return value;
1697 bool
1698 Console::ShouldIncludeStackTrace(MethodName aMethodName)
1700 switch (aMethodName) {
1701 case MethodError:
1702 case MethodException:
1703 case MethodAssert:
1704 case MethodTrace:
1705 return true;
1706 default:
1707 return false;
1711 nsIXPConnectJSObjectHolder*
1712 Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
1714 MOZ_ASSERT(NS_IsMainThread());
1716 if (!mSandbox) {
1717 nsIXPConnect* xpc = nsContentUtils::XPConnect();
1718 MOZ_ASSERT(xpc, "This should never be null!");
1720 nsresult rv = xpc->CreateSandbox(aCx, aPrincipal,
1721 getter_AddRefs(mSandbox));
1722 if (NS_WARN_IF(NS_FAILED(rv))) {
1723 return nullptr;
1727 return mSandbox;
1730 } // namespace dom
1731 } // namespace mozilla