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"
24 #include "nsIConsoleAPIStorage.h"
25 #include "nsIDOMWindowUtils.h"
26 #include "nsIInterfaceRequestorUtils.h"
27 #include "nsILoadContext.h"
28 #include "nsIServiceManager.h"
29 #include "nsISupportsPrimitives.h"
30 #include "nsIWebNavigation.h"
32 // The maximum allowed number of concurrent timers per page.
33 #define MAX_PAGE_TIMERS 10000
35 // The maximum allowed number of concurrent counters per page.
36 #define MAX_PAGE_COUNTERS 10000
38 // The maximum stacktrace depth when populating the stacktrace array used for
40 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
42 // The console API methods are async and their action is executed later. This
43 // delay tells how much later.
44 #define CALL_DELAY 15 // milliseconds
46 // This constant tells how many messages to process in a single timer execution.
47 #define MESSAGES_IN_INTERVAL 1500
49 // This tag is used in the Structured Clone Algorithm to move js values from
50 // worker thread to main thread
51 #define CONSOLE_TAG JS_SCTAG_USER_MIN
53 using namespace mozilla::dom::exceptions
;
54 using namespace mozilla::dom::workers
;
60 * Console API in workers uses the Structured Clone Algorithm to move any value
61 * from the worker thread to the main-thread. Some object cannot be moved and,
62 * in these cases, we convert them to strings.
63 * It's not the best, but at least we are able to show something.
66 // This method is called by the Structured Clone Algorithm when some data has
69 ConsoleStructuredCloneCallbacksRead(JSContext
* aCx
,
70 JSStructuredCloneReader
* /* unused */,
71 uint32_t aTag
, uint32_t aData
,
74 AssertIsOnMainThread();
76 if (aTag
!= CONSOLE_TAG
) {
80 nsTArray
<nsString
>* strings
= static_cast<nsTArray
<nsString
>*>(aClosure
);
81 MOZ_ASSERT(strings
->Length() > aData
);
83 JS::Rooted
<JS::Value
> value(aCx
);
84 if (!xpc::StringToJsval(aCx
, strings
->ElementAt(aData
), &value
)) {
88 JS::Rooted
<JSObject
*> obj(aCx
);
89 if (!JS_ValueToObject(aCx
, value
, &obj
)) {
96 // This method is called by the Structured Clone Algorithm when some data has
99 ConsoleStructuredCloneCallbacksWrite(JSContext
* aCx
,
100 JSStructuredCloneWriter
* aWriter
,
101 JS::Handle
<JSObject
*> aObj
,
104 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectOrNullValue(aObj
));
105 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
110 nsAutoJSString string
;
111 if (!string
.init(aCx
, jsString
)) {
115 nsTArray
<nsString
>* strings
= static_cast<nsTArray
<nsString
>*>(aClosure
);
117 if (!JS_WriteUint32Pair(aWriter
, CONSOLE_TAG
, strings
->Length())) {
121 strings
->AppendElement(string
);
127 ConsoleStructuredCloneCallbacksError(JSContext
* /* aCx */,
128 uint32_t /* aErrorId */)
130 NS_WARNING("Failed to clone data for the Console API in workers.");
133 JSStructuredCloneCallbacks gConsoleCallbacks
= {
134 ConsoleStructuredCloneCallbacksRead
,
135 ConsoleStructuredCloneCallbacksWrite
,
136 ConsoleStructuredCloneCallbacksError
139 class ConsoleCallData MOZ_FINAL
: public LinkedListElement
<ConsoleCallData
>
143 : mMethodName(Console::MethodLog
)
145 , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC
)
148 MOZ_COUNT_CTOR(ConsoleCallData
);
153 MOZ_COUNT_DTOR(ConsoleCallData
);
157 Initialize(JSContext
* aCx
, Console::MethodName aName
,
158 const nsAString
& aString
, const Sequence
<JS::Value
>& aArguments
)
160 mGlobal
= JS::CurrentGlobalOrNull(aCx
);
162 mMethodString
= aString
;
164 for (uint32_t i
= 0; i
< aArguments
.Length(); ++i
) {
165 mArguments
.AppendElement(aArguments
[i
]);
169 JS::Heap
<JSObject
*> mGlobal
;
171 Console::MethodName mMethodName
;
174 DOMHighResTimeStamp mMonotonicTimer
;
176 nsString mMethodString
;
177 nsTArray
<JS::Heap
<JS::Value
>> mArguments
;
179 // Stack management is complicated, because we want to do it as
180 // lazily as possible. Therefore, we have the following behavior:
181 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
182 // 2) mReifiedStack is initialized if we're created in a worker.
183 // 3) mStack is set (possibly to null if there is no JS on the stack) if
184 // we're created on main thread.
185 Maybe
<ConsoleStackEntry
> mTopStackFrame
;
186 Maybe
<nsTArray
<ConsoleStackEntry
>> mReifiedStack
;
187 nsCOMPtr
<nsIStackFrame
> mStack
;
190 // This class is used to clear any exception at the end of this method.
194 explicit ClearException(JSContext
* aCx
)
201 JS_ClearPendingException(mCx
);
208 class ConsoleRunnable
: public nsRunnable
212 : mWorkerPrivate(GetCurrentThreadWorkerPrivate())
214 MOZ_ASSERT(mWorkerPrivate
);
225 mWorkerPrivate
->AssertIsOnWorkerThread();
227 JSContext
* cx
= mWorkerPrivate
->GetJSContext();
229 if (!PreDispatch(cx
)) {
233 AutoSyncLoopHolder
syncLoop(mWorkerPrivate
);
234 mSyncLoopTarget
= syncLoop
.EventTarget();
236 if (NS_FAILED(NS_DispatchToMainThread(this))) {
238 "Failed to dispatch to main thread for the Console API!");
242 return syncLoop
.Run();
248 AssertIsOnMainThread();
252 nsRefPtr
<MainThreadStopSyncLoopRunnable
> response
=
253 new MainThreadStopSyncLoopRunnable(mWorkerPrivate
,
254 mSyncLoopTarget
.forget(),
256 if (!response
->Dispatch(nullptr)) {
257 NS_WARNING("Failed to dispatch response!");
265 PreDispatch(JSContext
* aCx
) = 0;
270 WorkerPrivate
* mWorkerPrivate
;
273 nsCOMPtr
<nsIEventTarget
> mSyncLoopTarget
;
276 // This runnable appends a CallData object into the Console queue running on
278 class ConsoleCallDataRunnable MOZ_FINAL
: public ConsoleRunnable
281 explicit ConsoleCallDataRunnable(ConsoleCallData
* aCallData
)
282 : mCallData(aCallData
)
288 PreDispatch(JSContext
* aCx
) MOZ_OVERRIDE
290 ClearException
ce(aCx
);
291 JSAutoCompartment
ac(aCx
, mCallData
->mGlobal
);
293 JS::Rooted
<JSObject
*> arguments(aCx
,
294 JS_NewArrayObject(aCx
, mCallData
->mArguments
.Length()));
299 JS::Rooted
<JS::Value
> arg(aCx
);
300 for (uint32_t i
= 0; i
< mCallData
->mArguments
.Length(); ++i
) {
301 arg
= mCallData
->mArguments
[i
];
302 if (!JS_DefineElement(aCx
, arguments
, i
, arg
, JSPROP_ENUMERATE
)) {
307 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*arguments
));
309 if (!mArguments
.write(aCx
, value
, &gConsoleCallbacks
, &mStrings
)) {
313 mCallData
->mArguments
.Clear();
314 mCallData
->mGlobal
= nullptr;
319 RunConsole() MOZ_OVERRIDE
321 // Walk up to our containing page
322 WorkerPrivate
* wp
= mWorkerPrivate
;
323 while (wp
->GetParent()) {
324 wp
= wp
->GetParent();
327 nsPIDOMWindow
* window
= wp
->GetWindow();
328 NS_ENSURE_TRUE_VOID(window
);
330 nsRefPtr
<nsGlobalWindow
> win
= static_cast<nsGlobalWindow
*>(window
);
331 NS_ENSURE_TRUE_VOID(win
);
334 if (NS_WARN_IF(!jsapi
.Init(win
))) {
337 JSContext
* cx
= jsapi
.cx();
338 ClearException
ce(cx
);
341 nsRefPtr
<Console
> console
= win
->GetConsole(error
);
342 if (error
.Failed()) {
343 NS_WARNING("Failed to get console from the window.");
347 JS::Rooted
<JS::Value
> argumentsValue(cx
);
348 if (!mArguments
.read(cx
, &argumentsValue
, &gConsoleCallbacks
, &mStrings
)) {
352 MOZ_ASSERT(argumentsValue
.isObject());
353 JS::Rooted
<JSObject
*> argumentsObj(cx
, &argumentsValue
.toObject());
354 MOZ_ASSERT(JS_IsArrayObject(cx
, argumentsObj
));
357 if (!JS_GetArrayLength(cx
, argumentsObj
, &length
)) {
361 for (uint32_t i
= 0; i
< length
; ++i
) {
362 JS::Rooted
<JS::Value
> value(cx
);
364 if (!JS_GetElement(cx
, argumentsObj
, i
, &value
)) {
368 mCallData
->mArguments
.AppendElement(value
);
371 MOZ_ASSERT(mCallData
->mArguments
.Length() == length
);
373 mCallData
->mGlobal
= JS::CurrentGlobalOrNull(cx
);
374 console
->AppendCallData(mCallData
.forget());
378 nsAutoPtr
<ConsoleCallData
> mCallData
;
380 JSAutoStructuredCloneBuffer mArguments
;
381 nsTArray
<nsString
> mStrings
;
384 // This runnable calls ProfileMethod() on the console on the main-thread.
385 class ConsoleProfileRunnable MOZ_FINAL
: public ConsoleRunnable
388 ConsoleProfileRunnable(const nsAString
& aAction
,
389 const Sequence
<JS::Value
>& aArguments
)
391 , mArguments(aArguments
)
397 PreDispatch(JSContext
* aCx
) MOZ_OVERRIDE
399 ClearException
ce(aCx
);
401 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
406 JSAutoCompartment
ac(aCx
, global
);
408 JS::Rooted
<JSObject
*> arguments(aCx
,
409 JS_NewArrayObject(aCx
, mArguments
.Length()));
414 JS::Rooted
<JS::Value
> arg(aCx
);
415 for (uint32_t i
= 0; i
< mArguments
.Length(); ++i
) {
417 if (!JS_DefineElement(aCx
, arguments
, i
, arg
, JSPROP_ENUMERATE
)) {
422 JS::Rooted
<JS::Value
> value(aCx
, JS::ObjectValue(*arguments
));
424 if (!mBuffer
.write(aCx
, value
, &gConsoleCallbacks
, &mStrings
)) {
432 RunConsole() MOZ_OVERRIDE
434 // Walk up to our containing page
435 WorkerPrivate
* wp
= mWorkerPrivate
;
436 while (wp
->GetParent()) {
437 wp
= wp
->GetParent();
440 nsPIDOMWindow
* window
= wp
->GetWindow();
441 NS_ENSURE_TRUE_VOID(window
);
443 nsRefPtr
<nsGlobalWindow
> win
= static_cast<nsGlobalWindow
*>(window
);
444 NS_ENSURE_TRUE_VOID(win
);
447 if (NS_WARN_IF(!jsapi
.Init(win
))) {
450 JSContext
* cx
= jsapi
.cx();
451 ClearException
ce(cx
);
454 nsRefPtr
<Console
> console
= win
->GetConsole(error
);
455 if (error
.Failed()) {
456 NS_WARNING("Failed to get console from the window.");
460 JS::Rooted
<JS::Value
> argumentsValue(cx
);
461 if (!mBuffer
.read(cx
, &argumentsValue
, &gConsoleCallbacks
, &mStrings
)) {
465 MOZ_ASSERT(argumentsValue
.isObject());
466 JS::Rooted
<JSObject
*> argumentsObj(cx
, &argumentsValue
.toObject());
467 MOZ_ASSERT(JS_IsArrayObject(cx
, argumentsObj
));
470 if (!JS_GetArrayLength(cx
, argumentsObj
, &length
)) {
474 Sequence
<JS::Value
> arguments
;
476 for (uint32_t i
= 0; i
< length
; ++i
) {
477 JS::Rooted
<JS::Value
> value(cx
);
479 if (!JS_GetElement(cx
, argumentsObj
, i
, &value
)) {
483 arguments
.AppendElement(value
);
486 console
->ProfileMethod(cx
, mAction
, arguments
);
491 Sequence
<JS::Value
> mArguments
;
493 JSAutoStructuredCloneBuffer mBuffer
;
494 nsTArray
<nsString
> mStrings
;
497 NS_IMPL_CYCLE_COLLECTION_CLASS(Console
)
499 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console
)
500 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow
)
501 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer
)
502 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage
)
503 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
505 tmp
->ClearConsoleData();
507 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
509 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console
)
510 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow
)
511 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer
)
512 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage
)
513 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
514 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
516 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console
)
517 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
519 for (ConsoleCallData
* data
= tmp
->mQueuedCalls
.getFirst(); data
!= nullptr;
520 data
= data
->getNext()) {
522 aCallbacks
.Trace(&data
->mGlobal
, "data->mGlobal", aClosure
);
525 for (uint32_t i
= 0; i
< data
->mArguments
.Length(); ++i
) {
526 aCallbacks
.Trace(&data
->mArguments
[i
], "data->mArguments[i]", aClosure
);
530 NS_IMPL_CYCLE_COLLECTION_TRACE_END
532 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console
)
533 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console
)
535 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console
)
536 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
537 NS_INTERFACE_MAP_ENTRY(nsITimerCallback
)
538 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
539 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsITimerCallback
)
542 Console::Console(nsPIDOMWindow
* aWindow
)
548 MOZ_ASSERT(mWindow
->IsInnerWindow());
549 mInnerID
= mWindow
->WindowID();
551 nsPIDOMWindow
* outerWindow
= mWindow
->GetOuterWindow();
552 MOZ_ASSERT(outerWindow
);
553 mOuterID
= outerWindow
->WindowID();
556 if (NS_IsMainThread()) {
557 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
559 obs
->AddObserver(this, "inner-window-destroyed", false);
564 mozilla::HoldJSObjects(this);
569 mozilla::DropJSObjects(this);
573 Console::Observe(nsISupports
* aSubject
, const char* aTopic
,
574 const char16_t
* aData
)
576 MOZ_ASSERT(NS_IsMainThread());
578 if (strcmp(aTopic
, "inner-window-destroyed")) {
582 nsCOMPtr
<nsISupportsPRUint64
> wrapper
= do_QueryInterface(aSubject
);
583 NS_ENSURE_TRUE(wrapper
, NS_ERROR_FAILURE
);
586 nsresult rv
= wrapper
->GetData(&innerID
);
587 NS_ENSURE_SUCCESS(rv
, rv
);
589 if (innerID
== mInnerID
) {
590 nsCOMPtr
<nsIObserverService
> obs
=
591 do_GetService("@mozilla.org/observer-service;1");
593 obs
->RemoveObserver(this, "inner-window-destroyed");
597 mTimerRegistry
.Clear();
609 Console::WrapObject(JSContext
* aCx
)
611 return ConsoleBinding::Wrap(aCx
, this);
614 #define METHOD(name, string) \
616 Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
618 Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
624 METHOD(Error
, "error")
625 METHOD(Exception
, "exception")
626 METHOD(Debug
, "debug")
627 METHOD(Table
, "table")
630 Console::Trace(JSContext
* aCx
)
632 const Sequence
<JS::Value
> data
;
633 Method(aCx
, MethodTrace
, NS_LITERAL_STRING("trace"), data
);
636 // Displays an interactive listing of all the properties of an object.
639 METHOD(Group
, "group")
640 METHOD(GroupCollapsed
, "groupCollapsed")
641 METHOD(GroupEnd
, "groupEnd")
644 Console::Time(JSContext
* aCx
, const JS::Handle
<JS::Value
> aTime
)
646 Sequence
<JS::Value
> data
;
647 SequenceRooter
<JS::Value
> rooter(aCx
, &data
);
649 if (!aTime
.isUndefined()) {
650 data
.AppendElement(aTime
);
653 Method(aCx
, MethodTime
, NS_LITERAL_STRING("time"), data
);
657 Console::TimeEnd(JSContext
* aCx
, const JS::Handle
<JS::Value
> aTime
)
659 Sequence
<JS::Value
> data
;
660 SequenceRooter
<JS::Value
> rooter(aCx
, &data
);
662 if (!aTime
.isUndefined()) {
663 data
.AppendElement(aTime
);
666 Method(aCx
, MethodTimeEnd
, NS_LITERAL_STRING("timeEnd"), data
);
670 Console::Profile(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
)
672 ProfileMethod(aCx
, NS_LITERAL_STRING("profile"), aData
);
676 Console::ProfileEnd(JSContext
* aCx
, const Sequence
<JS::Value
>& aData
)
678 ProfileMethod(aCx
, NS_LITERAL_STRING("profileEnd"), aData
);
682 Console::ProfileMethod(JSContext
* aCx
, const nsAString
& aAction
,
683 const Sequence
<JS::Value
>& aData
)
685 if (!NS_IsMainThread()) {
686 // Here we are in a worker thread.
687 nsRefPtr
<ConsoleProfileRunnable
> runnable
=
688 new ConsoleProfileRunnable(aAction
, aData
);
689 runnable
->Dispatch();
693 ClearException
ce(aCx
);
695 RootedDictionary
<ConsoleProfileEvent
> event(aCx
);
696 event
.mAction
= aAction
;
698 event
.mArguments
.Construct();
699 Sequence
<JS::Value
>& sequence
= event
.mArguments
.Value();
701 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
702 sequence
.AppendElement(aData
[i
]);
705 JS::Rooted
<JS::Value
> eventValue(aCx
);
706 if (!ToJSValue(aCx
, event
, &eventValue
)) {
710 JS::Rooted
<JSObject
*> eventObj(aCx
, &eventValue
.toObject());
711 MOZ_ASSERT(eventObj
);
713 if (!JS_DefineProperty(aCx
, eventObj
, "wrappedJSObject", eventValue
,
718 nsXPConnect
* xpc
= nsXPConnect::XPConnect();
719 nsCOMPtr
<nsISupports
> wrapper
;
720 const nsIID
& iid
= NS_GET_IID(nsISupports
);
722 if (NS_FAILED(xpc
->WrapJS(aCx
, eventObj
, iid
, getter_AddRefs(wrapper
)))) {
726 nsCOMPtr
<nsIObserverService
> obs
=
727 do_GetService("@mozilla.org/observer-service;1");
729 obs
->NotifyObservers(wrapper
, "console-api-profiler", nullptr);
734 Console::Assert(JSContext
* aCx
, bool aCondition
,
735 const Sequence
<JS::Value
>& aData
)
738 Method(aCx
, MethodAssert
, NS_LITERAL_STRING("assert"), aData
);
742 METHOD(Count
, "count")
745 Console::__noSuchMethod__()
752 StackFrameToStackEntry(nsIStackFrame
* aStackFrame
,
753 ConsoleStackEntry
& aStackEntry
,
756 MOZ_ASSERT(aStackFrame
);
758 nsresult rv
= aStackFrame
->GetFilename(aStackEntry
.mFilename
);
759 NS_ENSURE_SUCCESS(rv
, rv
);
762 rv
= aStackFrame
->GetLineNumber(&lineNumber
);
763 NS_ENSURE_SUCCESS(rv
, rv
);
765 aStackEntry
.mLineNumber
= lineNumber
;
767 rv
= aStackFrame
->GetName(aStackEntry
.mFunctionName
);
768 NS_ENSURE_SUCCESS(rv
, rv
);
770 aStackEntry
.mLanguage
= aLanguage
;
776 ReifyStack(nsIStackFrame
* aStack
, nsTArray
<ConsoleStackEntry
>& aRefiedStack
)
778 nsCOMPtr
<nsIStackFrame
> stack(aStack
);
782 nsresult rv
= stack
->GetLanguage(&language
);
783 NS_ENSURE_SUCCESS(rv
, rv
);
785 if (language
== nsIProgrammingLanguage::JAVASCRIPT
||
786 language
== nsIProgrammingLanguage::JAVASCRIPT2
) {
787 ConsoleStackEntry
& data
= *aRefiedStack
.AppendElement();
788 rv
= StackFrameToStackEntry(stack
, data
, language
);
789 NS_ENSURE_SUCCESS(rv
, rv
);
792 nsCOMPtr
<nsIStackFrame
> caller
;
793 rv
= stack
->GetCaller(getter_AddRefs(caller
));
794 NS_ENSURE_SUCCESS(rv
, rv
);
802 // Queue a call to a console method. See the CALL_DELAY constant.
804 Console::Method(JSContext
* aCx
, MethodName aMethodName
,
805 const nsAString
& aMethodString
,
806 const Sequence
<JS::Value
>& aData
)
808 // This RAII class removes the last element of the mQueuedCalls if something
812 explicit RAII(LinkedList
<ConsoleCallData
>& aList
)
821 ConsoleCallData
* data
= mList
.popLast();
834 LinkedList
<ConsoleCallData
>& mList
;
838 ConsoleCallData
* callData
= new ConsoleCallData();
839 mQueuedCalls
.insertBack(callData
);
841 ClearException
ce(aCx
);
843 callData
->Initialize(aCx
, aMethodName
, aMethodString
, aData
);
844 RAII
raii(mQueuedCalls
);
847 nsCOMPtr
<nsIWebNavigation
> webNav
= do_GetInterface(mWindow
);
852 nsCOMPtr
<nsILoadContext
> loadContext
= do_QueryInterface(webNav
);
853 MOZ_ASSERT(loadContext
);
855 loadContext
->GetUsePrivateBrowsing(&callData
->mPrivate
);
858 uint32_t maxDepth
= ShouldIncludeStackrace(aMethodName
) ?
859 DEFAULT_MAX_STACKTRACE_DEPTH
: 1;
860 nsCOMPtr
<nsIStackFrame
> stack
= CreateStack(aCx
, maxDepth
);
866 // Walk up to the first JS stack frame and save it if we find it.
869 nsresult rv
= stack
->GetLanguage(&language
);
874 if (language
== nsIProgrammingLanguage::JAVASCRIPT
||
875 language
== nsIProgrammingLanguage::JAVASCRIPT2
) {
876 callData
->mTopStackFrame
.emplace();
877 nsresult rv
= StackFrameToStackEntry(stack
,
878 *callData
->mTopStackFrame
,
887 nsCOMPtr
<nsIStackFrame
> caller
;
888 rv
= stack
->GetCaller(getter_AddRefs(caller
));
896 if (NS_IsMainThread()) {
897 callData
->mStack
= stack
;
899 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
900 // before we post our runnable to the main thread.
901 callData
->mReifiedStack
.emplace();
902 nsresult rv
= ReifyStack(stack
, *callData
->mReifiedStack
);
903 if (NS_WARN_IF(NS_FAILED(rv
))) {
908 // Monotonic timer for 'time' and 'timeEnd'
909 if ((aMethodName
== MethodTime
|| aMethodName
== MethodTimeEnd
)) {
911 nsGlobalWindow
*win
= static_cast<nsGlobalWindow
*>(mWindow
.get());
915 nsRefPtr
<nsPerformance
> performance
= win
->GetPerformance(rv
);
916 if (rv
.Failed() || !performance
) {
920 callData
->mMonotonicTimer
= performance
->Now();
922 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
923 MOZ_ASSERT(workerPrivate
);
925 TimeDuration duration
=
926 mozilla::TimeStamp::Now() - workerPrivate
->CreationTimeStamp();
928 callData
->mMonotonicTimer
= duration
.ToMilliseconds();
932 // The operation is completed. RAII class has to be disabled.
935 if (!NS_IsMainThread()) {
936 // Here we are in a worker thread. The ConsoleCallData has to been removed
937 // from the list and it will be deleted by the ConsoleCallDataRunnable or
938 // by the Main-Thread Console object.
939 mQueuedCalls
.popLast();
941 nsRefPtr
<ConsoleCallDataRunnable
> runnable
=
942 new ConsoleCallDataRunnable(callData
);
943 runnable
->Dispatch();
948 mTimer
= do_CreateInstance("@mozilla.org/timer;1");
949 mTimer
->InitWithCallback(this, CALL_DELAY
,
950 nsITimer::TYPE_REPEATING_SLACK
);
955 Console::AppendCallData(ConsoleCallData
* aCallData
)
957 mQueuedCalls
.insertBack(aCallData
);
960 mTimer
= do_CreateInstance("@mozilla.org/timer;1");
961 mTimer
->InitWithCallback(this, CALL_DELAY
,
962 nsITimer::TYPE_REPEATING_SLACK
);
966 // Timer callback used to process each of the queued calls.
968 Console::Notify(nsITimer
*timer
)
970 MOZ_ASSERT(!mQueuedCalls
.isEmpty());
972 for (uint32_t i
= 0; i
< MESSAGES_IN_INTERVAL
; ++i
) {
973 ConsoleCallData
* data
= mQueuedCalls
.popFirst();
978 ProcessCallData(data
);
982 if (mQueuedCalls
.isEmpty() && mTimer
) {
990 // We store information to lazily compute the stack in the reserved slots of
991 // LazyStackGetter. The first slot always stores a JS object: it's either the
992 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
993 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
994 // reified the stack yet, or an UndefinedValue() otherwise.
1001 LazyStackGetter(JSContext
* aCx
, unsigned aArgc
, JS::Value
* aVp
)
1003 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
1004 JS::Rooted
<JSObject
*> callee(aCx
, &args
.callee());
1006 JS::Value v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_RAW_STACK
);
1007 if (v
.isUndefined()) {
1009 args
.rval().set(js::GetFunctionNativeReserved(callee
, SLOT_STACKOBJ
));
1013 nsIStackFrame
* stack
= reinterpret_cast<nsIStackFrame
*>(v
.toPrivate());
1014 nsTArray
<ConsoleStackEntry
> reifiedStack
;
1015 nsresult rv
= ReifyStack(stack
, reifiedStack
);
1016 if (NS_FAILED(rv
)) {
1021 JS::Rooted
<JS::Value
> stackVal(aCx
);
1022 if (!ToJSValue(aCx
, reifiedStack
, &stackVal
)) {
1026 MOZ_ASSERT(stackVal
.isObject());
1028 js::SetFunctionNativeReserved(callee
, SLOT_STACKOBJ
, stackVal
);
1029 js::SetFunctionNativeReserved(callee
, SLOT_RAW_STACK
, JS::UndefinedValue());
1031 args
.rval().set(stackVal
);
1036 Console::ProcessCallData(ConsoleCallData
* aData
)
1039 MOZ_ASSERT(NS_IsMainThread());
1041 ConsoleStackEntry frame
;
1042 if (aData
->mTopStackFrame
) {
1043 frame
= *aData
->mTopStackFrame
;
1046 AutoSafeJSContext cx
;
1047 ClearException
ce(cx
);
1048 RootedDictionary
<ConsoleEvent
> event(cx
);
1050 JSAutoCompartment
ac(cx
, aData
->mGlobal
);
1052 event
.mID
.Construct();
1053 event
.mInnerID
.Construct();
1055 event
.mID
.Value().SetAsUnsignedLong() = mOuterID
;
1056 event
.mInnerID
.Value().SetAsUnsignedLong() = mInnerID
;
1058 // If we are in a JSM, the window doesn't exist.
1059 event
.mID
.Value().SetAsString() = NS_LITERAL_STRING("jsm");
1060 event
.mInnerID
.Value().SetAsString() = frame
.mFilename
;
1063 event
.mLevel
= aData
->mMethodString
;
1064 event
.mFilename
= frame
.mFilename
;
1065 event
.mLineNumber
= frame
.mLineNumber
;
1066 event
.mFunctionName
= frame
.mFunctionName
;
1067 event
.mTimeStamp
= aData
->mTimeStamp
;
1068 event
.mPrivate
= aData
->mPrivate
;
1070 switch (aData
->mMethodName
) {
1075 case MethodException
:
1078 event
.mArguments
.Construct();
1079 event
.mStyles
.Construct();
1080 ProcessArguments(cx
, aData
->mArguments
, event
.mArguments
.Value(),
1081 event
.mStyles
.Value());
1085 event
.mArguments
.Construct();
1086 ArgumentsToValueList(aData
->mArguments
, event
.mArguments
.Value());
1089 if (aData
->mMethodName
== MethodGroup
||
1090 aData
->mMethodName
== MethodGroupCollapsed
||
1091 aData
->mMethodName
== MethodGroupEnd
) {
1092 ComposeGroupName(cx
, aData
->mArguments
, event
.mGroupName
);
1095 else if (aData
->mMethodName
== MethodTime
&& !aData
->mArguments
.IsEmpty()) {
1096 event
.mTimer
= StartTimer(cx
, aData
->mArguments
[0], aData
->mMonotonicTimer
);
1099 else if (aData
->mMethodName
== MethodTimeEnd
&& !aData
->mArguments
.IsEmpty()) {
1100 event
.mTimer
= StopTimer(cx
, aData
->mArguments
[0], aData
->mMonotonicTimer
);
1103 else if (aData
->mMethodName
== MethodCount
) {
1104 event
.mCounter
= IncreaseCounter(cx
, frame
, aData
->mArguments
);
1107 // We want to create a console event object and pass it to our
1108 // nsIConsoleAPIStorage implementation. We want to define some accessor
1109 // properties on this object, and those will need to keep an nsIStackFrame
1110 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1111 // further, passing untrusted objects to system code is likely to run afoul of
1112 // Object Xrays. So we want to wrap in a system-principal scope here. But
1113 // which one? We could cheat and try to get the underlying JSObject* of
1114 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1115 // with explicit permission from the XPConnect module owner. If you're
1116 // tempted to do that anywhere else, talk to said module owner first.
1117 JSAutoCompartment
ac2(cx
, xpc::PrivilegedJunkScope());
1119 JS::Rooted
<JS::Value
> eventValue(cx
);
1120 if (!ToJSValue(cx
, event
, &eventValue
)) {
1124 JS::Rooted
<JSObject
*> eventObj(cx
, &eventValue
.toObject());
1125 MOZ_ASSERT(eventObj
);
1127 if (!JS_DefineProperty(cx
, eventObj
, "wrappedJSObject", eventValue
, JSPROP_ENUMERATE
)) {
1131 if (ShouldIncludeStackrace(aData
->mMethodName
)) {
1132 // Now define the "stacktrace" property on eventObj. There are two cases
1133 // here. Either we came from a worker and have a reified stack, or we want
1134 // to define a getter that will lazily reify the stack.
1135 if (aData
->mReifiedStack
) {
1136 JS::Rooted
<JS::Value
> stacktrace(cx
);
1137 if (!ToJSValue(cx
, *aData
->mReifiedStack
, &stacktrace
) ||
1138 !JS_DefineProperty(cx
, eventObj
, "stacktrace", stacktrace
,
1139 JSPROP_ENUMERATE
)) {
1143 JSFunction
* fun
= js::NewFunctionWithReserved(cx
, LazyStackGetter
, 0, 0,
1144 eventObj
, "stacktrace");
1149 JS::Rooted
<JSObject
*> funObj(cx
, JS_GetFunctionObject(fun
));
1151 // We want to store our stack in the function and have it stay alive. But
1152 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1153 // wrapper and the raw pointer: the former will keep the latter alive.
1154 JS::Rooted
<JS::Value
> stackVal(cx
);
1155 nsresult rv
= nsContentUtils::WrapNative(cx
, aData
->mStack
,
1157 if (NS_FAILED(rv
)) {
1161 js::SetFunctionNativeReserved(funObj
, SLOT_STACKOBJ
, stackVal
);
1162 js::SetFunctionNativeReserved(funObj
, SLOT_RAW_STACK
,
1163 JS::PrivateValue(aData
->mStack
.get()));
1165 if (!JS_DefineProperty(cx
, eventObj
, "stacktrace",
1166 JS::UndefinedHandleValue
,
1167 JSPROP_ENUMERATE
| JSPROP_SHARED
| JSPROP_GETTER
|
1169 JS_DATA_TO_FUNC_PTR(JSPropertyOp
, funObj
.get()),
1177 mStorage
= do_GetService("@mozilla.org/consoleAPI-storage;1");
1181 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1185 nsAutoString innerID
;
1186 innerID
.AppendInt(mInnerID
);
1188 if (NS_FAILED(mStorage
->RecordEvent(innerID
, eventValue
))) {
1189 NS_WARNING("Failed to record a console event.");
1192 nsXPConnect
* xpc
= nsXPConnect::XPConnect();
1193 nsCOMPtr
<nsISupports
> wrapper
;
1194 const nsIID
& iid
= NS_GET_IID(nsISupports
);
1196 if (NS_FAILED(xpc
->WrapJS(cx
, eventObj
, iid
, getter_AddRefs(wrapper
)))) {
1200 nsCOMPtr
<nsIObserverService
> obs
=
1201 do_GetService("@mozilla.org/observer-service;1");
1203 nsAutoString outerID
;
1204 outerID
.AppendInt(mOuterID
);
1206 obs
->NotifyObservers(wrapper
, "console-api-log-event", outerID
.get());
1211 Console::ProcessArguments(JSContext
* aCx
,
1212 const nsTArray
<JS::Heap
<JS::Value
>>& aData
,
1213 Sequence
<JS::Value
>& aSequence
,
1214 Sequence
<JS::Value
>& aStyles
)
1216 if (aData
.IsEmpty()) {
1220 if (aData
.Length() == 1 || !aData
[0].isString()) {
1221 ArgumentsToValueList(aData
, aSequence
);
1225 JS::Rooted
<JS::Value
> format(aCx
, aData
[0]);
1226 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, format
));
1231 nsAutoJSString string
;
1232 if (!string
.init(aCx
, jsString
)) {
1236 nsString::const_iterator start
, end
;
1237 string
.BeginReading(start
);
1238 string
.EndReading(end
);
1243 while (start
!= end
) {
1244 if (*start
!= '%') {
1245 output
.Append(*start
);
1256 if (*start
== '%') {
1257 output
.Append(*start
);
1265 int32_t integer
= -1;
1266 int32_t mantissa
= -1;
1268 // Let's parse %<number>.<number> for %d and %f
1269 if (*start
>= '0' && *start
<= '9') {
1273 integer
= integer
* 10 + *start
- '0';
1276 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1284 if (*start
== '.') {
1293 // '.' must be followed by a number.
1294 if (*start
< '0' || *start
> '9') {
1302 mantissa
= mantissa
* 10 + *start
- '0';
1305 } while (*start
>= '0' && *start
<= '9' && start
!= end
);
1321 if (!output
.IsEmpty()) {
1322 JS::Rooted
<JSString
*> str(aCx
, JS_NewUCStringCopyN(aCx
,
1329 aSequence
.AppendElement(JS::StringValue(str
));
1333 JS::Rooted
<JS::Value
> v(aCx
);
1334 if (index
< aData
.Length()) {
1338 aSequence
.AppendElement(v
);
1344 if (!output
.IsEmpty()) {
1345 JS::Rooted
<JSString
*> str(aCx
, JS_NewUCStringCopyN(aCx
,
1352 aSequence
.AppendElement(JS::StringValue(str
));
1356 if (index
< aData
.Length()) {
1357 JS::Rooted
<JS::Value
> v(aCx
, aData
[index
++]);
1358 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, v
));
1363 int32_t diff
= aSequence
.Length() - aStyles
.Length();
1365 for (int32_t i
= 0; i
< diff
; i
++) {
1366 aStyles
.AppendElement(JS::NullValue());
1369 aStyles
.AppendElement(JS::StringValue(jsString
));
1375 if (index
< aData
.Length()) {
1376 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1377 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
1383 if (!v
.init(aCx
, jsString
)) {
1393 if (index
< aData
.Length()) {
1394 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1397 if (!JS::ToInt32(aCx
, value
, &v
)) {
1402 MakeFormatString(format
, integer
, mantissa
, 'd');
1403 output
.AppendPrintf(format
.get(), v
);
1408 if (index
< aData
.Length()) {
1409 JS::Rooted
<JS::Value
> value(aCx
, aData
[index
++]);
1412 if (!JS::ToNumber(aCx
, value
, &v
)) {
1417 MakeFormatString(format
, integer
, mantissa
, 'f');
1418 output
.AppendPrintf(format
.get(), v
);
1428 if (!output
.IsEmpty()) {
1429 JS::Rooted
<JSString
*> str(aCx
, JS_NewUCStringCopyN(aCx
, output
.get(),
1435 aSequence
.AppendElement(JS::StringValue(str
));
1438 // The rest of the array, if unused by the format string.
1439 for (; index
< aData
.Length(); ++index
) {
1440 aSequence
.AppendElement(aData
[index
]);
1445 Console::MakeFormatString(nsCString
& aFormat
, int32_t aInteger
,
1446 int32_t aMantissa
, char aCh
)
1448 aFormat
.Append('%');
1449 if (aInteger
>= 0) {
1450 aFormat
.AppendInt(aInteger
);
1453 if (aMantissa
>= 0) {
1454 aFormat
.Append('.');
1455 aFormat
.AppendInt(aMantissa
);
1458 aFormat
.Append(aCh
);
1462 Console::ComposeGroupName(JSContext
* aCx
,
1463 const nsTArray
<JS::Heap
<JS::Value
>>& aData
,
1466 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1468 aName
.AppendASCII(" ");
1471 JS::Rooted
<JS::Value
> value(aCx
, aData
[i
]);
1472 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, value
));
1477 nsAutoJSString string
;
1478 if (!string
.init(aCx
, jsString
)) {
1482 aName
.Append(string
);
1487 Console::StartTimer(JSContext
* aCx
, const JS::Value
& aName
,
1488 DOMHighResTimeStamp aTimestamp
)
1490 if (mTimerRegistry
.Count() >= MAX_PAGE_TIMERS
) {
1491 RootedDictionary
<ConsoleTimerError
> error(aCx
);
1493 JS::Rooted
<JS::Value
> value(aCx
);
1494 if (!ToJSValue(aCx
, error
, &value
)) {
1495 return JS::UndefinedValue();
1501 RootedDictionary
<ConsoleTimerStart
> timer(aCx
);
1503 JS::Rooted
<JS::Value
> name(aCx
, aName
);
1504 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
1506 return JS::UndefinedValue();
1510 if (!key
.init(aCx
, jsString
)) {
1511 return JS::UndefinedValue();
1516 DOMHighResTimeStamp entry
;
1517 if (!mTimerRegistry
.Get(key
, &entry
)) {
1518 mTimerRegistry
.Put(key
, aTimestamp
);
1523 timer
.mStarted
= aTimestamp
;
1525 JS::Rooted
<JS::Value
> value(aCx
);
1526 if (!ToJSValue(aCx
, timer
, &value
)) {
1527 return JS::UndefinedValue();
1534 Console::StopTimer(JSContext
* aCx
, const JS::Value
& aName
,
1535 DOMHighResTimeStamp aTimestamp
)
1537 JS::Rooted
<JS::Value
> name(aCx
, aName
);
1538 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, name
));
1540 return JS::UndefinedValue();
1544 if (!key
.init(aCx
, jsString
)) {
1545 return JS::UndefinedValue();
1548 DOMHighResTimeStamp entry
;
1549 if (!mTimerRegistry
.Get(key
, &entry
)) {
1550 return JS::UndefinedValue();
1553 mTimerRegistry
.Remove(key
);
1555 RootedDictionary
<ConsoleTimerEnd
> timer(aCx
);
1557 timer
.mDuration
= aTimestamp
- entry
;
1559 JS::Rooted
<JS::Value
> value(aCx
);
1560 if (!ToJSValue(aCx
, timer
, &value
)) {
1561 return JS::UndefinedValue();
1568 Console::ArgumentsToValueList(const nsTArray
<JS::Heap
<JS::Value
>>& aData
,
1569 Sequence
<JS::Value
>& aSequence
)
1571 for (uint32_t i
= 0; i
< aData
.Length(); ++i
) {
1572 aSequence
.AppendElement(aData
[i
]);
1577 Console::IncreaseCounter(JSContext
* aCx
, const ConsoleStackEntry
& aFrame
,
1578 const nsTArray
<JS::Heap
<JS::Value
>>& aArguments
)
1580 ClearException
ce(aCx
);
1585 if (!aArguments
.IsEmpty()) {
1586 JS::Rooted
<JS::Value
> labelValue(aCx
, aArguments
[0]);
1587 JS::Rooted
<JSString
*> jsString(aCx
, JS::ToString(aCx
, labelValue
));
1589 nsAutoJSString string
;
1590 if (jsString
&& string
.init(aCx
, jsString
)) {
1596 if (key
.IsEmpty()) {
1597 key
.Append(aFrame
.mFilename
);
1599 key
.AppendInt(aFrame
.mLineNumber
);
1603 if (!mCounterRegistry
.Get(key
, &count
)) {
1604 if (mCounterRegistry
.Count() >= MAX_PAGE_COUNTERS
) {
1605 RootedDictionary
<ConsoleCounterError
> error(aCx
);
1607 JS::Rooted
<JS::Value
> value(aCx
);
1608 if (!ToJSValue(aCx
, error
, &value
)) {
1609 return JS::UndefinedValue();
1617 mCounterRegistry
.Put(key
, count
);
1619 RootedDictionary
<ConsoleCounter
> data(aCx
);
1620 data
.mLabel
= label
;
1621 data
.mCount
= count
;
1623 JS::Rooted
<JS::Value
> value(aCx
);
1624 if (!ToJSValue(aCx
, data
, &value
)) {
1625 return JS::UndefinedValue();
1632 Console::ClearConsoleData()
1634 while (ConsoleCallData
* data
= mQueuedCalls
.popFirst()) {
1640 Console::ShouldIncludeStackrace(MethodName aMethodName
)
1642 switch (aMethodName
) {
1644 case MethodException
:
1654 } // namespace mozilla