Bumping manifests a=b2g-bump
[gecko.git] / dom / base / Console.cpp
blobc309a15a14f27ffa6300c997d192d66730651a50
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
39 // console.trace().
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;
56 namespace mozilla {
57 namespace dom {
59 /**
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
67 // to be read.
68 static JSObject*
69 ConsoleStructuredCloneCallbacksRead(JSContext* aCx,
70 JSStructuredCloneReader* /* unused */,
71 uint32_t aTag, uint32_t aData,
72 void* aClosure)
74 AssertIsOnMainThread();
76 if (aTag != CONSOLE_TAG) {
77 return nullptr;
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)) {
85 return nullptr;
88 JS::Rooted<JSObject*> obj(aCx);
89 if (!JS_ValueToObject(aCx, value, &obj)) {
90 return nullptr;
93 return obj;
96 // This method is called by the Structured Clone Algorithm when some data has
97 // to be written.
98 static bool
99 ConsoleStructuredCloneCallbacksWrite(JSContext* aCx,
100 JSStructuredCloneWriter* aWriter,
101 JS::Handle<JSObject*> aObj,
102 void* aClosure)
104 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
105 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
106 if (!jsString) {
107 return false;
110 nsAutoJSString string;
111 if (!string.init(aCx, jsString)) {
112 return false;
115 nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
117 if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) {
118 return false;
121 strings->AppendElement(string);
123 return true;
126 static void
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>
141 public:
142 ConsoleCallData()
143 : mMethodName(Console::MethodLog)
144 , mPrivate(false)
145 , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
146 , mMonotonicTimer(0)
148 MOZ_COUNT_CTOR(ConsoleCallData);
151 ~ConsoleCallData()
153 MOZ_COUNT_DTOR(ConsoleCallData);
156 void
157 Initialize(JSContext* aCx, Console::MethodName aName,
158 const nsAString& aString, const Sequence<JS::Value>& aArguments)
160 mGlobal = JS::CurrentGlobalOrNull(aCx);
161 mMethodName = aName;
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;
172 bool mPrivate;
173 int64_t mTimeStamp;
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.
191 class ClearException
193 public:
194 explicit ClearException(JSContext* aCx)
195 : mCx(aCx)
199 ~ClearException()
201 JS_ClearPendingException(mCx);
204 private:
205 JSContext* mCx;
208 class ConsoleRunnable : public nsRunnable
210 public:
211 ConsoleRunnable()
212 : mWorkerPrivate(GetCurrentThreadWorkerPrivate())
214 MOZ_ASSERT(mWorkerPrivate);
217 virtual
218 ~ConsoleRunnable()
222 bool
223 Dispatch()
225 mWorkerPrivate->AssertIsOnWorkerThread();
227 JSContext* cx = mWorkerPrivate->GetJSContext();
229 if (!PreDispatch(cx)) {
230 return false;
233 AutoSyncLoopHolder syncLoop(mWorkerPrivate);
234 mSyncLoopTarget = syncLoop.EventTarget();
236 if (NS_FAILED(NS_DispatchToMainThread(this))) {
237 JS_ReportError(cx,
238 "Failed to dispatch to main thread for the Console API!");
239 return false;
242 return syncLoop.Run();
245 private:
246 NS_IMETHOD Run()
248 AssertIsOnMainThread();
250 RunConsole();
252 nsRefPtr<MainThreadStopSyncLoopRunnable> response =
253 new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
254 mSyncLoopTarget.forget(),
255 true);
256 if (!response->Dispatch(nullptr)) {
257 NS_WARNING("Failed to dispatch response!");
260 return NS_OK;
263 protected:
264 virtual bool
265 PreDispatch(JSContext* aCx) = 0;
267 virtual void
268 RunConsole() = 0;
270 WorkerPrivate* mWorkerPrivate;
272 private:
273 nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
276 // This runnable appends a CallData object into the Console queue running on
277 // the main-thread.
278 class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
280 public:
281 explicit ConsoleCallDataRunnable(ConsoleCallData* aCallData)
282 : mCallData(aCallData)
286 private:
287 bool
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()));
295 if (!arguments) {
296 return false;
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)) {
303 return false;
307 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
309 if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
310 return false;
313 mCallData->mArguments.Clear();
314 mCallData->mGlobal = nullptr;
315 return true;
318 void
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);
333 AutoJSAPI jsapi;
334 if (NS_WARN_IF(!jsapi.Init(win))) {
335 return;
337 JSContext* cx = jsapi.cx();
338 ClearException ce(cx);
340 ErrorResult error;
341 nsRefPtr<Console> console = win->GetConsole(error);
342 if (error.Failed()) {
343 NS_WARNING("Failed to get console from the window.");
344 return;
347 JS::Rooted<JS::Value> argumentsValue(cx);
348 if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
349 return;
352 MOZ_ASSERT(argumentsValue.isObject());
353 JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
354 MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
356 uint32_t length;
357 if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
358 return;
361 for (uint32_t i = 0; i < length; ++i) {
362 JS::Rooted<JS::Value> value(cx);
364 if (!JS_GetElement(cx, argumentsObj, i, &value)) {
365 return;
368 mCallData->mArguments.AppendElement(value);
371 MOZ_ASSERT(mCallData->mArguments.Length() == length);
373 mCallData->mGlobal = JS::CurrentGlobalOrNull(cx);
374 console->AppendCallData(mCallData.forget());
377 private:
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
387 public:
388 ConsoleProfileRunnable(const nsAString& aAction,
389 const Sequence<JS::Value>& aArguments)
390 : mAction(aAction)
391 , mArguments(aArguments)
395 private:
396 bool
397 PreDispatch(JSContext* aCx) MOZ_OVERRIDE
399 ClearException ce(aCx);
401 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
402 if (!global) {
403 return false;
406 JSAutoCompartment ac(aCx, global);
408 JS::Rooted<JSObject*> arguments(aCx,
409 JS_NewArrayObject(aCx, mArguments.Length()));
410 if (!arguments) {
411 return false;
414 JS::Rooted<JS::Value> arg(aCx);
415 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
416 arg = mArguments[i];
417 if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
418 return false;
422 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
424 if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
425 return false;
428 return true;
431 void
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);
446 AutoJSAPI jsapi;
447 if (NS_WARN_IF(!jsapi.Init(win))) {
448 return;
450 JSContext* cx = jsapi.cx();
451 ClearException ce(cx);
453 ErrorResult error;
454 nsRefPtr<Console> console = win->GetConsole(error);
455 if (error.Failed()) {
456 NS_WARNING("Failed to get console from the window.");
457 return;
460 JS::Rooted<JS::Value> argumentsValue(cx);
461 if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
462 return;
465 MOZ_ASSERT(argumentsValue.isObject());
466 JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
467 MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
469 uint32_t length;
470 if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
471 return;
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)) {
480 return;
483 arguments.AppendElement(value);
486 console->ProfileMethod(cx, mAction, arguments);
489 private:
490 nsString mAction;
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()) {
521 if (data->mGlobal) {
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)
540 NS_INTERFACE_MAP_END
542 Console::Console(nsPIDOMWindow* aWindow)
543 : mWindow(aWindow)
544 , mOuterID(0)
545 , mInnerID(0)
547 if (mWindow) {
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();
558 if (obs) {
559 obs->AddObserver(this, "inner-window-destroyed", false);
563 SetIsDOMBinding();
564 mozilla::HoldJSObjects(this);
567 Console::~Console()
569 mozilla::DropJSObjects(this);
572 NS_IMETHODIMP
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")) {
579 return NS_OK;
582 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
583 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
585 uint64_t innerID;
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");
592 if (obs) {
593 obs->RemoveObserver(this, "inner-window-destroyed");
596 ClearConsoleData();
597 mTimerRegistry.Clear();
599 if (mTimer) {
600 mTimer->Cancel();
601 mTimer = nullptr;
605 return NS_OK;
608 JSObject*
609 Console::WrapObject(JSContext* aCx)
611 return ConsoleBinding::Wrap(aCx, this);
614 #define METHOD(name, string) \
615 void \
616 Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
618 Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
621 METHOD(Log, "log")
622 METHOD(Info, "info")
623 METHOD(Warn, "warn")
624 METHOD(Error, "error")
625 METHOD(Exception, "exception")
626 METHOD(Debug, "debug")
627 METHOD(Table, "table")
629 void
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.
637 METHOD(Dir, "dir");
639 METHOD(Group, "group")
640 METHOD(GroupCollapsed, "groupCollapsed")
641 METHOD(GroupEnd, "groupEnd")
643 void
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);
656 void
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);
669 void
670 Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
672 ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
675 void
676 Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
678 ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
681 void
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();
690 return;
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)) {
707 return;
710 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
711 MOZ_ASSERT(eventObj);
713 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
714 JSPROP_ENUMERATE)) {
715 return;
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)))) {
723 return;
726 nsCOMPtr<nsIObserverService> obs =
727 do_GetService("@mozilla.org/observer-service;1");
728 if (obs) {
729 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
733 void
734 Console::Assert(JSContext* aCx, bool aCondition,
735 const Sequence<JS::Value>& aData)
737 if (!aCondition) {
738 Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
742 METHOD(Count, "count")
744 void
745 Console::__noSuchMethod__()
747 // Nothing to do.
750 static
751 nsresult
752 StackFrameToStackEntry(nsIStackFrame* aStackFrame,
753 ConsoleStackEntry& aStackEntry,
754 uint32_t aLanguage)
756 MOZ_ASSERT(aStackFrame);
758 nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
759 NS_ENSURE_SUCCESS(rv, rv);
761 int32_t lineNumber;
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;
771 return NS_OK;
774 static
775 nsresult
776 ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
778 nsCOMPtr<nsIStackFrame> stack(aStack);
780 while (stack) {
781 uint32_t language;
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);
796 stack.swap(caller);
799 return NS_OK;
802 // Queue a call to a console method. See the CALL_DELAY constant.
803 void
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
809 // goes wrong.
810 class RAII {
811 public:
812 explicit RAII(LinkedList<ConsoleCallData>& aList)
813 : mList(aList)
814 , mUnfinished(true)
818 ~RAII()
820 if (mUnfinished) {
821 ConsoleCallData* data = mList.popLast();
822 MOZ_ASSERT(data);
823 delete data;
827 void
828 Finished()
830 mUnfinished = false;
833 private:
834 LinkedList<ConsoleCallData>& mList;
835 bool mUnfinished;
838 ConsoleCallData* callData = new ConsoleCallData();
839 mQueuedCalls.insertBack(callData);
841 ClearException ce(aCx);
843 callData->Initialize(aCx, aMethodName, aMethodString, aData);
844 RAII raii(mQueuedCalls);
846 if (mWindow) {
847 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
848 if (!webNav) {
849 return;
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);
862 if (!stack) {
863 return;
866 // Walk up to the first JS stack frame and save it if we find it.
867 do {
868 uint32_t language;
869 nsresult rv = stack->GetLanguage(&language);
870 if (NS_FAILED(rv)) {
871 return;
874 if (language == nsIProgrammingLanguage::JAVASCRIPT ||
875 language == nsIProgrammingLanguage::JAVASCRIPT2) {
876 callData->mTopStackFrame.emplace();
877 nsresult rv = StackFrameToStackEntry(stack,
878 *callData->mTopStackFrame,
879 language);
880 if (NS_FAILED(rv)) {
881 return;
884 break;
887 nsCOMPtr<nsIStackFrame> caller;
888 rv = stack->GetCaller(getter_AddRefs(caller));
889 if (NS_FAILED(rv)) {
890 return;
893 stack.swap(caller);
894 } while (stack);
896 if (NS_IsMainThread()) {
897 callData->mStack = stack;
898 } else {
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))) {
904 return;
908 // Monotonic timer for 'time' and 'timeEnd'
909 if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd)) {
910 if (mWindow) {
911 nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
912 MOZ_ASSERT(win);
914 ErrorResult rv;
915 nsRefPtr<nsPerformance> performance = win->GetPerformance(rv);
916 if (rv.Failed() || !performance) {
917 return;
920 callData->mMonotonicTimer = performance->Now();
921 } else {
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.
933 raii.Finished();
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();
944 return;
947 if (!mTimer) {
948 mTimer = do_CreateInstance("@mozilla.org/timer;1");
949 mTimer->InitWithCallback(this, CALL_DELAY,
950 nsITimer::TYPE_REPEATING_SLACK);
954 void
955 Console::AppendCallData(ConsoleCallData* aCallData)
957 mQueuedCalls.insertBack(aCallData);
959 if (!mTimer) {
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.
967 NS_IMETHODIMP
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();
974 if (!data) {
975 break;
978 ProcessCallData(data);
979 delete data;
982 if (mQueuedCalls.isEmpty() && mTimer) {
983 mTimer->Cancel();
984 mTimer = nullptr;
987 return NS_OK;
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.
995 enum {
996 SLOT_STACKOBJ,
997 SLOT_RAW_STACK
1000 bool
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()) {
1008 // Already reified.
1009 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1010 return true;
1013 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1014 nsTArray<ConsoleStackEntry> reifiedStack;
1015 nsresult rv = ReifyStack(stack, reifiedStack);
1016 if (NS_FAILED(rv)) {
1017 Throw(aCx, rv);
1018 return false;
1021 JS::Rooted<JS::Value> stackVal(aCx);
1022 if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
1023 return false;
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);
1032 return true;
1035 void
1036 Console::ProcessCallData(ConsoleCallData* aData)
1038 MOZ_ASSERT(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();
1054 if (mWindow) {
1055 event.mID.Value().SetAsUnsignedLong() = mOuterID;
1056 event.mInnerID.Value().SetAsUnsignedLong() = mInnerID;
1057 } else {
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) {
1071 case MethodLog:
1072 case MethodInfo:
1073 case MethodWarn:
1074 case MethodError:
1075 case MethodException:
1076 case MethodDebug:
1077 case MethodAssert:
1078 event.mArguments.Construct();
1079 event.mStyles.Construct();
1080 ProcessArguments(cx, aData->mArguments, event.mArguments.Value(),
1081 event.mStyles.Value());
1082 break;
1084 default:
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)) {
1121 return;
1124 JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
1125 MOZ_ASSERT(eventObj);
1127 if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
1128 return;
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)) {
1140 return;
1142 } else {
1143 JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
1144 eventObj, "stacktrace");
1145 if (!fun) {
1146 return;
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,
1156 &stackVal);
1157 if (NS_FAILED(rv)) {
1158 return;
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 |
1168 JSPROP_SETTER,
1169 JS_DATA_TO_FUNC_PTR(JSPropertyOp, funObj.get()),
1170 nullptr)) {
1171 return;
1176 if (!mStorage) {
1177 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1180 if (!mStorage) {
1181 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1182 return;
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)))) {
1197 return;
1200 nsCOMPtr<nsIObserverService> obs =
1201 do_GetService("@mozilla.org/observer-service;1");
1202 if (obs) {
1203 nsAutoString outerID;
1204 outerID.AppendInt(mOuterID);
1206 obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get());
1210 void
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()) {
1217 return;
1220 if (aData.Length() == 1 || !aData[0].isString()) {
1221 ArgumentsToValueList(aData, aSequence);
1222 return;
1225 JS::Rooted<JS::Value> format(aCx, aData[0]);
1226 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1227 if (!jsString) {
1228 return;
1231 nsAutoJSString string;
1232 if (!string.init(aCx, jsString)) {
1233 return;
1236 nsString::const_iterator start, end;
1237 string.BeginReading(start);
1238 string.EndReading(end);
1240 nsString output;
1241 uint32_t index = 1;
1243 while (start != end) {
1244 if (*start != '%') {
1245 output.Append(*start);
1246 ++start;
1247 continue;
1250 ++start;
1251 if (start == end) {
1252 output.Append('%');
1253 break;
1256 if (*start == '%') {
1257 output.Append(*start);
1258 ++start;
1259 continue;
1262 nsAutoString tmp;
1263 tmp.Append('%');
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') {
1270 integer = 0;
1272 do {
1273 integer = integer * 10 + *start - '0';
1274 tmp.Append(*start);
1275 ++start;
1276 } while (*start >= '0' && *start <= '9' && start != end);
1279 if (start == end) {
1280 output.Append(tmp);
1281 break;
1284 if (*start == '.') {
1285 tmp.Append(*start);
1286 ++start;
1288 if (start == end) {
1289 output.Append(tmp);
1290 break;
1293 // '.' must be followed by a number.
1294 if (*start < '0' || *start > '9') {
1295 output.Append(tmp);
1296 continue;
1299 mantissa = 0;
1301 do {
1302 mantissa = mantissa * 10 + *start - '0';
1303 tmp.Append(*start);
1304 ++start;
1305 } while (*start >= '0' && *start <= '9' && start != end);
1307 if (start == end) {
1308 output.Append(tmp);
1309 break;
1313 char ch = *start;
1314 tmp.Append(ch);
1315 ++start;
1317 switch (ch) {
1318 case 'o':
1319 case 'O':
1321 if (!output.IsEmpty()) {
1322 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1323 output.get(),
1324 output.Length()));
1325 if (!str) {
1326 return;
1329 aSequence.AppendElement(JS::StringValue(str));
1330 output.Truncate();
1333 JS::Rooted<JS::Value> v(aCx);
1334 if (index < aData.Length()) {
1335 v = aData[index++];
1338 aSequence.AppendElement(v);
1339 break;
1342 case 'c':
1344 if (!output.IsEmpty()) {
1345 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1346 output.get(),
1347 output.Length()));
1348 if (!str) {
1349 return;
1352 aSequence.AppendElement(JS::StringValue(str));
1353 output.Truncate();
1356 if (index < aData.Length()) {
1357 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1358 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1359 if (!jsString) {
1360 return;
1363 int32_t diff = aSequence.Length() - aStyles.Length();
1364 if (diff > 0) {
1365 for (int32_t i = 0; i < diff; i++) {
1366 aStyles.AppendElement(JS::NullValue());
1369 aStyles.AppendElement(JS::StringValue(jsString));
1371 break;
1374 case 's':
1375 if (index < aData.Length()) {
1376 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1377 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1378 if (!jsString) {
1379 return;
1382 nsAutoJSString v;
1383 if (!v.init(aCx, jsString)) {
1384 return;
1387 output.Append(v);
1389 break;
1391 case 'd':
1392 case 'i':
1393 if (index < aData.Length()) {
1394 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1396 int32_t v;
1397 if (!JS::ToInt32(aCx, value, &v)) {
1398 return;
1401 nsCString format;
1402 MakeFormatString(format, integer, mantissa, 'd');
1403 output.AppendPrintf(format.get(), v);
1405 break;
1407 case 'f':
1408 if (index < aData.Length()) {
1409 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1411 double v;
1412 if (!JS::ToNumber(aCx, value, &v)) {
1413 return;
1416 nsCString format;
1417 MakeFormatString(format, integer, mantissa, 'f');
1418 output.AppendPrintf(format.get(), v);
1420 break;
1422 default:
1423 output.Append(tmp);
1424 break;
1428 if (!output.IsEmpty()) {
1429 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
1430 output.Length()));
1431 if (!str) {
1432 return;
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]);
1444 void
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);
1461 void
1462 Console::ComposeGroupName(JSContext* aCx,
1463 const nsTArray<JS::Heap<JS::Value>>& aData,
1464 nsAString& aName)
1466 for (uint32_t i = 0; i < aData.Length(); ++i) {
1467 if (i != 0) {
1468 aName.AppendASCII(" ");
1471 JS::Rooted<JS::Value> value(aCx, aData[i]);
1472 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1473 if (!jsString) {
1474 return;
1477 nsAutoJSString string;
1478 if (!string.init(aCx, jsString)) {
1479 return;
1482 aName.Append(string);
1486 JS::Value
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();
1498 return value;
1501 RootedDictionary<ConsoleTimerStart> timer(aCx);
1503 JS::Rooted<JS::Value> name(aCx, aName);
1504 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1505 if (!jsString) {
1506 return JS::UndefinedValue();
1509 nsAutoJSString key;
1510 if (!key.init(aCx, jsString)) {
1511 return JS::UndefinedValue();
1514 timer.mName = key;
1516 DOMHighResTimeStamp entry;
1517 if (!mTimerRegistry.Get(key, &entry)) {
1518 mTimerRegistry.Put(key, aTimestamp);
1519 } else {
1520 aTimestamp = entry;
1523 timer.mStarted = aTimestamp;
1525 JS::Rooted<JS::Value> value(aCx);
1526 if (!ToJSValue(aCx, timer, &value)) {
1527 return JS::UndefinedValue();
1530 return value;
1533 JS::Value
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));
1539 if (!jsString) {
1540 return JS::UndefinedValue();
1543 nsAutoJSString key;
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);
1556 timer.mName = key;
1557 timer.mDuration = aTimestamp - entry;
1559 JS::Rooted<JS::Value> value(aCx);
1560 if (!ToJSValue(aCx, timer, &value)) {
1561 return JS::UndefinedValue();
1564 return value;
1567 void
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]);
1576 JS::Value
1577 Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
1578 const nsTArray<JS::Heap<JS::Value>>& aArguments)
1580 ClearException ce(aCx);
1582 nsAutoString key;
1583 nsAutoString label;
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)) {
1591 label = string;
1592 key = string;
1596 if (key.IsEmpty()) {
1597 key.Append(aFrame.mFilename);
1598 key.Append(':');
1599 key.AppendInt(aFrame.mLineNumber);
1602 uint32_t count = 0;
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();
1612 return value;
1616 ++count;
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();
1628 return value;
1631 void
1632 Console::ClearConsoleData()
1634 while (ConsoleCallData* data = mQueuedCalls.popFirst()) {
1635 delete data;
1639 bool
1640 Console::ShouldIncludeStackrace(MethodName aMethodName)
1642 switch (aMethodName) {
1643 case MethodError:
1644 case MethodException:
1645 case MethodAssert:
1646 case MethodTrace:
1647 return true;
1648 default:
1649 return false;
1653 } // namespace dom
1654 } // namespace mozilla