Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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 "WorkerPrivate.h"
19 #include "WorkerRunnable.h"
20 #include "xpcprivate.h"
21 #include "nsContentUtils.h"
23 #include "nsIConsoleAPIStorage.h"
24 #include "nsIDOMWindowUtils.h"
25 #include "nsIInterfaceRequestorUtils.h"
26 #include "nsILoadContext.h"
27 #include "nsIServiceManager.h"
28 #include "nsISupportsPrimitives.h"
29 #include "nsIWebNavigation.h"
31 // The maximum allowed number of concurrent timers per page.
32 #define MAX_PAGE_TIMERS 10000
34 // The maximum allowed number of concurrent counters per page.
35 #define MAX_PAGE_COUNTERS 10000
37 // The maximum stacktrace depth when populating the stacktrace array used for
38 // console.trace().
39 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
41 // The console API methods are async and their action is executed later. This
42 // delay tells how much later.
43 #define CALL_DELAY 15 // milliseconds
45 // This constant tells how many messages to process in a single timer execution.
46 #define MESSAGES_IN_INTERVAL 1500
48 // This tag is used in the Structured Clone Algorithm to move js values from
49 // worker thread to main thread
50 #define CONSOLE_TAG JS_SCTAG_USER_MIN
52 using namespace mozilla::dom::exceptions;
53 using namespace mozilla::dom::workers;
55 namespace mozilla {
56 namespace dom {
58 /**
59 * Console API in workers uses the Structured Clone Algorithm to move any value
60 * from the worker thread to the main-thread. Some object cannot be moved and,
61 * in these cases, we convert them to strings.
62 * It's not the best, but at least we are able to show something.
63 */
65 // This method is called by the Structured Clone Algorithm when some data has
66 // to be read.
67 static JSObject*
68 ConsoleStructuredCloneCallbacksRead(JSContext* aCx,
69 JSStructuredCloneReader* /* unused */,
70 uint32_t aTag, uint32_t aData,
71 void* aClosure)
72 {
73 AssertIsOnMainThread();
75 if (aTag != CONSOLE_TAG) {
76 return nullptr;
77 }
79 nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
80 MOZ_ASSERT(strings->Length() > aData);
82 JS::Rooted<JS::Value> value(aCx);
83 if (!xpc::StringToJsval(aCx, strings->ElementAt(aData), &value)) {
84 return nullptr;
85 }
87 JS::Rooted<JSObject*> obj(aCx);
88 if (!JS_ValueToObject(aCx, value, &obj)) {
89 return nullptr;
90 }
92 return obj;
93 }
95 // This method is called by the Structured Clone Algorithm when some data has
96 // to be written.
97 static bool
98 ConsoleStructuredCloneCallbacksWrite(JSContext* aCx,
99 JSStructuredCloneWriter* aWriter,
100 JS::Handle<JSObject*> aObj,
101 void* aClosure)
102 {
103 JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
104 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
105 if (!jsString) {
106 return false;
107 }
109 nsDependentJSString string;
110 if (!string.init(aCx, jsString)) {
111 return false;
112 }
114 nsTArray<nsString>* strings = static_cast<nsTArray<nsString>*>(aClosure);
116 if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG, strings->Length())) {
117 return false;
118 }
120 strings->AppendElement(string);
122 return true;
123 }
125 static void
126 ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */,
127 uint32_t /* aErrorId */)
128 {
129 NS_WARNING("Failed to clone data for the Console API in workers.");
130 }
132 JSStructuredCloneCallbacks gConsoleCallbacks = {
133 ConsoleStructuredCloneCallbacksRead,
134 ConsoleStructuredCloneCallbacksWrite,
135 ConsoleStructuredCloneCallbacksError
136 };
138 class ConsoleCallData MOZ_FINAL : public LinkedListElement<ConsoleCallData>
139 {
140 public:
141 ConsoleCallData()
142 : mMethodName(Console::MethodLog)
143 , mPrivate(false)
144 , mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
145 , mMonotonicTimer(0)
146 {
147 MOZ_COUNT_CTOR(ConsoleCallData);
148 }
150 ~ConsoleCallData()
151 {
152 MOZ_COUNT_DTOR(ConsoleCallData);
153 }
155 void
156 Initialize(JSContext* aCx, Console::MethodName aName,
157 const nsAString& aString, const Sequence<JS::Value>& aArguments)
158 {
159 mGlobal = JS::CurrentGlobalOrNull(aCx);
160 mMethodName = aName;
161 mMethodString = aString;
163 for (uint32_t i = 0; i < aArguments.Length(); ++i) {
164 mArguments.AppendElement(aArguments[i]);
165 }
166 }
168 JS::Heap<JSObject*> mGlobal;
170 Console::MethodName mMethodName;
171 bool mPrivate;
172 int64_t mTimeStamp;
173 DOMHighResTimeStamp mMonotonicTimer;
175 nsString mMethodString;
176 nsTArray<JS::Heap<JS::Value>> mArguments;
178 // Stack management is complicated, because we want to do it as
179 // lazily as possible. Therefore, we have the following behavior:
180 // 1) mTopStackFrame is initialized whenever we have any JS on the stack
181 // 2) mReifiedStack is initialized if we're created in a worker.
182 // 3) mStack is set (possibly to null if there is no JS on the stack) if
183 // we're created on main thread.
184 Maybe<ConsoleStackEntry> mTopStackFrame;
185 Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
186 nsCOMPtr<nsIStackFrame> mStack;
187 };
189 // This class is used to clear any exception at the end of this method.
190 class ClearException
191 {
192 public:
193 ClearException(JSContext* aCx)
194 : mCx(aCx)
195 {
196 }
198 ~ClearException()
199 {
200 JS_ClearPendingException(mCx);
201 }
203 private:
204 JSContext* mCx;
205 };
207 class ConsoleRunnable : public nsRunnable
208 {
209 public:
210 ConsoleRunnable()
211 : mWorkerPrivate(GetCurrentThreadWorkerPrivate())
212 {
213 MOZ_ASSERT(mWorkerPrivate);
214 }
216 virtual
217 ~ConsoleRunnable()
218 {
219 }
221 bool
222 Dispatch()
223 {
224 mWorkerPrivate->AssertIsOnWorkerThread();
226 JSContext* cx = mWorkerPrivate->GetJSContext();
228 if (!PreDispatch(cx)) {
229 return false;
230 }
232 AutoSyncLoopHolder syncLoop(mWorkerPrivate);
233 mSyncLoopTarget = syncLoop.EventTarget();
235 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
236 JS_ReportError(cx,
237 "Failed to dispatch to main thread for the Console API!");
238 return false;
239 }
241 return syncLoop.Run();
242 }
244 private:
245 NS_IMETHOD Run()
246 {
247 AssertIsOnMainThread();
249 RunConsole();
251 nsRefPtr<MainThreadStopSyncLoopRunnable> response =
252 new MainThreadStopSyncLoopRunnable(mWorkerPrivate,
253 mSyncLoopTarget.forget(),
254 true);
255 if (!response->Dispatch(nullptr)) {
256 NS_WARNING("Failed to dispatch response!");
257 }
259 return NS_OK;
260 }
262 protected:
263 virtual bool
264 PreDispatch(JSContext* aCx) = 0;
266 virtual void
267 RunConsole() = 0;
269 WorkerPrivate* mWorkerPrivate;
271 private:
272 nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
273 };
275 // This runnable appends a CallData object into the Console queue running on
276 // the main-thread.
277 class ConsoleCallDataRunnable MOZ_FINAL : public ConsoleRunnable
278 {
279 public:
280 ConsoleCallDataRunnable(ConsoleCallData* aCallData)
281 : mCallData(aCallData)
282 {
283 }
285 private:
286 bool
287 PreDispatch(JSContext* aCx) MOZ_OVERRIDE
288 {
289 ClearException ce(aCx);
290 JSAutoCompartment ac(aCx, mCallData->mGlobal);
292 JS::Rooted<JSObject*> arguments(aCx,
293 JS_NewArrayObject(aCx, mCallData->mArguments.Length()));
294 if (!arguments) {
295 return false;
296 }
298 for (uint32_t i = 0; i < mCallData->mArguments.Length(); ++i) {
299 if (!JS_DefineElement(aCx, arguments, i, mCallData->mArguments[i],
300 nullptr, nullptr, JSPROP_ENUMERATE)) {
301 return false;
302 }
303 }
305 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
307 if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
308 return false;
309 }
311 mCallData->mArguments.Clear();
312 mCallData->mGlobal = nullptr;
313 return true;
314 }
316 void
317 RunConsole() MOZ_OVERRIDE
318 {
319 // Walk up to our containing page
320 WorkerPrivate* wp = mWorkerPrivate;
321 while (wp->GetParent()) {
322 wp = wp->GetParent();
323 }
325 AutoPushJSContext cx(wp->ParentJSContext());
326 ClearException ce(cx);
328 nsPIDOMWindow* window = wp->GetWindow();
329 NS_ENSURE_TRUE_VOID(window);
331 nsRefPtr<nsGlobalWindow> win = static_cast<nsGlobalWindow*>(window);
332 NS_ENSURE_TRUE_VOID(win);
334 ErrorResult error;
335 nsRefPtr<Console> console = win->GetConsole(error);
336 if (error.Failed()) {
337 NS_WARNING("Failed to get console from the window.");
338 return;
339 }
341 JS::Rooted<JS::Value> argumentsValue(cx);
342 if (!mArguments.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
343 return;
344 }
346 MOZ_ASSERT(argumentsValue.isObject());
347 JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
348 MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
350 uint32_t length;
351 if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
352 return;
353 }
355 for (uint32_t i = 0; i < length; ++i) {
356 JS::Rooted<JS::Value> value(cx);
358 if (!JS_GetElement(cx, argumentsObj, i, &value)) {
359 return;
360 }
362 mCallData->mArguments.AppendElement(value);
363 }
365 MOZ_ASSERT(mCallData->mArguments.Length() == length);
367 mCallData->mGlobal = JS::CurrentGlobalOrNull(cx);
368 console->AppendCallData(mCallData.forget());
369 }
371 private:
372 nsAutoPtr<ConsoleCallData> mCallData;
374 JSAutoStructuredCloneBuffer mArguments;
375 nsTArray<nsString> mStrings;
376 };
378 // This runnable calls ProfileMethod() on the console on the main-thread.
379 class ConsoleProfileRunnable MOZ_FINAL : public ConsoleRunnable
380 {
381 public:
382 ConsoleProfileRunnable(const nsAString& aAction,
383 const Sequence<JS::Value>& aArguments)
384 : mAction(aAction)
385 , mArguments(aArguments)
386 {
387 }
389 private:
390 bool
391 PreDispatch(JSContext* aCx) MOZ_OVERRIDE
392 {
393 ClearException ce(aCx);
395 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
396 if (!global) {
397 return false;
398 }
400 JSAutoCompartment ac(aCx, global);
402 JS::Rooted<JSObject*> arguments(aCx,
403 JS_NewArrayObject(aCx, mArguments.Length()));
404 if (!arguments) {
405 return false;
406 }
408 for (uint32_t i = 0; i < mArguments.Length(); ++i) {
409 if (!JS_DefineElement(aCx, arguments, i, mArguments[i], nullptr, nullptr,
410 JSPROP_ENUMERATE)) {
411 return false;
412 }
413 }
415 JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
417 if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mStrings)) {
418 return false;
419 }
421 return true;
422 }
424 void
425 RunConsole() MOZ_OVERRIDE
426 {
427 // Walk up to our containing page
428 WorkerPrivate* wp = mWorkerPrivate;
429 while (wp->GetParent()) {
430 wp = wp->GetParent();
431 }
433 AutoPushJSContext cx(wp->ParentJSContext());
434 ClearException ce(cx);
436 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
437 NS_ENSURE_TRUE_VOID(global);
438 JSAutoCompartment ac(cx, global);
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 ErrorResult error;
447 nsRefPtr<Console> console = win->GetConsole(error);
448 if (error.Failed()) {
449 NS_WARNING("Failed to get console from the window.");
450 return;
451 }
453 JS::Rooted<JS::Value> argumentsValue(cx);
454 if (!mBuffer.read(cx, &argumentsValue, &gConsoleCallbacks, &mStrings)) {
455 return;
456 }
458 MOZ_ASSERT(argumentsValue.isObject());
459 JS::Rooted<JSObject*> argumentsObj(cx, &argumentsValue.toObject());
460 MOZ_ASSERT(JS_IsArrayObject(cx, argumentsObj));
462 uint32_t length;
463 if (!JS_GetArrayLength(cx, argumentsObj, &length)) {
464 return;
465 }
467 Sequence<JS::Value> arguments;
469 for (uint32_t i = 0; i < length; ++i) {
470 JS::Rooted<JS::Value> value(cx);
472 if (!JS_GetElement(cx, argumentsObj, i, &value)) {
473 return;
474 }
476 arguments.AppendElement(value);
477 }
479 console->ProfileMethod(cx, mAction, arguments);
480 }
482 private:
483 nsString mAction;
484 Sequence<JS::Value> mArguments;
486 JSAutoStructuredCloneBuffer mBuffer;
487 nsTArray<nsString> mStrings;
488 };
490 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
492 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
493 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
494 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTimer)
495 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStorage)
496 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
498 tmp->ClearConsoleData();
500 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
502 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
503 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
504 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
505 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorage)
506 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
507 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
509 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
510 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
512 for (ConsoleCallData* data = tmp->mQueuedCalls.getFirst(); data != nullptr;
513 data = data->getNext()) {
514 if (data->mGlobal) {
515 aCallbacks.Trace(&data->mGlobal, "data->mGlobal", aClosure);
516 }
518 for (uint32_t i = 0; i < data->mArguments.Length(); ++i) {
519 aCallbacks.Trace(&data->mArguments[i], "data->mArguments[i]", aClosure);
520 }
521 }
523 NS_IMPL_CYCLE_COLLECTION_TRACE_END
525 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
526 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
528 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
529 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
530 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
531 NS_INTERFACE_MAP_ENTRY(nsIObserver)
532 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
533 NS_INTERFACE_MAP_END
535 Console::Console(nsPIDOMWindow* aWindow)
536 : mWindow(aWindow)
537 , mOuterID(0)
538 , mInnerID(0)
539 {
540 if (mWindow) {
541 MOZ_ASSERT(mWindow->IsInnerWindow());
542 mInnerID = mWindow->WindowID();
544 nsPIDOMWindow* outerWindow = mWindow->GetOuterWindow();
545 MOZ_ASSERT(outerWindow);
546 mOuterID = outerWindow->WindowID();
547 }
549 if (NS_IsMainThread()) {
550 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
551 if (obs) {
552 obs->AddObserver(this, "inner-window-destroyed", false);
553 }
554 }
556 SetIsDOMBinding();
557 mozilla::HoldJSObjects(this);
558 }
560 Console::~Console()
561 {
562 mozilla::DropJSObjects(this);
563 }
565 NS_IMETHODIMP
566 Console::Observe(nsISupports* aSubject, const char* aTopic,
567 const char16_t* aData)
568 {
569 MOZ_ASSERT(NS_IsMainThread());
571 if (strcmp(aTopic, "inner-window-destroyed")) {
572 return NS_OK;
573 }
575 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
576 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
578 uint64_t innerID;
579 nsresult rv = wrapper->GetData(&innerID);
580 NS_ENSURE_SUCCESS(rv, rv);
582 if (innerID == mInnerID) {
583 nsCOMPtr<nsIObserverService> obs =
584 do_GetService("@mozilla.org/observer-service;1");
585 if (obs) {
586 obs->RemoveObserver(this, "inner-window-destroyed");
587 }
589 ClearConsoleData();
590 mTimerRegistry.Clear();
592 if (mTimer) {
593 mTimer->Cancel();
594 mTimer = nullptr;
595 }
596 }
598 return NS_OK;
599 }
601 JSObject*
602 Console::WrapObject(JSContext* aCx)
603 {
604 return ConsoleBinding::Wrap(aCx, this);
605 }
607 #define METHOD(name, string) \
608 void \
609 Console::name(JSContext* aCx, const Sequence<JS::Value>& aData) \
610 { \
611 Method(aCx, Method##name, NS_LITERAL_STRING(string), aData); \
612 }
614 METHOD(Log, "log")
615 METHOD(Info, "info")
616 METHOD(Warn, "warn")
617 METHOD(Error, "error")
618 METHOD(Exception, "exception")
619 METHOD(Debug, "debug")
621 void
622 Console::Trace(JSContext* aCx)
623 {
624 const Sequence<JS::Value> data;
625 Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
626 }
628 // Displays an interactive listing of all the properties of an object.
629 METHOD(Dir, "dir");
631 METHOD(Group, "group")
632 METHOD(GroupCollapsed, "groupCollapsed")
633 METHOD(GroupEnd, "groupEnd")
635 void
636 Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
637 {
638 Sequence<JS::Value> data;
639 SequenceRooter<JS::Value> rooter(aCx, &data);
641 if (!aTime.isUndefined()) {
642 data.AppendElement(aTime);
643 }
645 Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
646 }
648 void
649 Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
650 {
651 Sequence<JS::Value> data;
652 SequenceRooter<JS::Value> rooter(aCx, &data);
654 if (!aTime.isUndefined()) {
655 data.AppendElement(aTime);
656 }
658 Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
659 }
661 void
662 Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
663 {
664 ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
665 }
667 void
668 Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
669 {
670 ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
671 }
673 void
674 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
675 const Sequence<JS::Value>& aData)
676 {
677 if (!NS_IsMainThread()) {
678 // Here we are in a worker thread.
679 nsRefPtr<ConsoleProfileRunnable> runnable =
680 new ConsoleProfileRunnable(aAction, aData);
681 runnable->Dispatch();
682 return;
683 }
685 ClearException ce(aCx);
687 RootedDictionary<ConsoleProfileEvent> event(aCx);
688 event.mAction = aAction;
690 event.mArguments.Construct();
691 Sequence<JS::Value>& sequence = event.mArguments.Value();
693 for (uint32_t i = 0; i < aData.Length(); ++i) {
694 sequence.AppendElement(aData[i]);
695 }
697 JS::Rooted<JS::Value> eventValue(aCx);
698 if (!event.ToObject(aCx, &eventValue)) {
699 return;
700 }
702 JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
703 MOZ_ASSERT(eventObj);
705 if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
706 JSPROP_ENUMERATE)) {
707 return;
708 }
710 nsXPConnect* xpc = nsXPConnect::XPConnect();
711 nsCOMPtr<nsISupports> wrapper;
712 const nsIID& iid = NS_GET_IID(nsISupports);
714 if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
715 return;
716 }
718 nsCOMPtr<nsIObserverService> obs =
719 do_GetService("@mozilla.org/observer-service;1");
720 if (obs) {
721 obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
722 }
723 }
725 void
726 Console::Assert(JSContext* aCx, bool aCondition,
727 const Sequence<JS::Value>& aData)
728 {
729 if (!aCondition) {
730 Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
731 }
732 }
734 METHOD(Count, "count")
736 void
737 Console::__noSuchMethod__()
738 {
739 // Nothing to do.
740 }
742 static
743 nsresult
744 StackFrameToStackEntry(nsIStackFrame* aStackFrame,
745 ConsoleStackEntry& aStackEntry,
746 uint32_t aLanguage)
747 {
748 MOZ_ASSERT(aStackFrame);
750 nsresult rv = aStackFrame->GetFilename(aStackEntry.mFilename);
751 NS_ENSURE_SUCCESS(rv, rv);
753 int32_t lineNumber;
754 rv = aStackFrame->GetLineNumber(&lineNumber);
755 NS_ENSURE_SUCCESS(rv, rv);
757 aStackEntry.mLineNumber = lineNumber;
759 rv = aStackFrame->GetName(aStackEntry.mFunctionName);
760 NS_ENSURE_SUCCESS(rv, rv);
762 aStackEntry.mLanguage = aLanguage;
763 return NS_OK;
764 }
766 static
767 nsresult
768 ReifyStack(nsIStackFrame* aStack, nsTArray<ConsoleStackEntry>& aRefiedStack)
769 {
770 nsCOMPtr<nsIStackFrame> stack(aStack);
772 while (stack) {
773 uint32_t language;
774 nsresult rv = stack->GetLanguage(&language);
775 NS_ENSURE_SUCCESS(rv, rv);
777 if (language == nsIProgrammingLanguage::JAVASCRIPT ||
778 language == nsIProgrammingLanguage::JAVASCRIPT2) {
779 ConsoleStackEntry& data = *aRefiedStack.AppendElement();
780 rv = StackFrameToStackEntry(stack, data, language);
781 NS_ENSURE_SUCCESS(rv, rv);
782 }
784 nsCOMPtr<nsIStackFrame> caller;
785 rv = stack->GetCaller(getter_AddRefs(caller));
786 NS_ENSURE_SUCCESS(rv, rv);
788 stack.swap(caller);
789 }
791 return NS_OK;
792 }
794 // Queue a call to a console method. See the CALL_DELAY constant.
795 void
796 Console::Method(JSContext* aCx, MethodName aMethodName,
797 const nsAString& aMethodString,
798 const Sequence<JS::Value>& aData)
799 {
800 // This RAII class removes the last element of the mQueuedCalls if something
801 // goes wrong.
802 class RAII {
803 public:
804 RAII(LinkedList<ConsoleCallData>& aList)
805 : mList(aList)
806 , mUnfinished(true)
807 {
808 }
810 ~RAII()
811 {
812 if (mUnfinished) {
813 ConsoleCallData* data = mList.popLast();
814 MOZ_ASSERT(data);
815 delete data;
816 }
817 }
819 void
820 Finished()
821 {
822 mUnfinished = false;
823 }
825 private:
826 LinkedList<ConsoleCallData>& mList;
827 bool mUnfinished;
828 };
830 ConsoleCallData* callData = new ConsoleCallData();
831 mQueuedCalls.insertBack(callData);
833 ClearException ce(aCx);
835 callData->Initialize(aCx, aMethodName, aMethodString, aData);
836 RAII raii(mQueuedCalls);
838 if (mWindow) {
839 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
840 if (!webNav) {
841 return;
842 }
844 nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
845 MOZ_ASSERT(loadContext);
847 loadContext->GetUsePrivateBrowsing(&callData->mPrivate);
848 }
850 uint32_t maxDepth = ShouldIncludeStackrace(aMethodName) ?
851 DEFAULT_MAX_STACKTRACE_DEPTH : 1;
852 nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, maxDepth);
854 if (!stack) {
855 return;
856 }
858 // Walk up to the first JS stack frame and save it if we find it.
859 do {
860 uint32_t language;
861 nsresult rv = stack->GetLanguage(&language);
862 if (NS_FAILED(rv)) {
863 return;
864 }
866 if (language == nsIProgrammingLanguage::JAVASCRIPT ||
867 language == nsIProgrammingLanguage::JAVASCRIPT2) {
868 callData->mTopStackFrame.construct();
869 nsresult rv = StackFrameToStackEntry(stack,
870 callData->mTopStackFrame.ref(),
871 language);
872 if (NS_FAILED(rv)) {
873 return;
874 }
876 break;
877 }
879 nsCOMPtr<nsIStackFrame> caller;
880 rv = stack->GetCaller(getter_AddRefs(caller));
881 if (NS_FAILED(rv)) {
882 return;
883 }
885 stack.swap(caller);
886 } while (stack);
888 if (NS_IsMainThread()) {
889 callData->mStack = stack;
890 } else {
891 // nsIStackFrame is not threadsafe, so we need to snapshot it now,
892 // before we post our runnable to the main thread.
893 callData->mReifiedStack.construct();
894 nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref());
895 if (NS_WARN_IF(NS_FAILED(rv))) {
896 return;
897 }
898 }
900 // Monotonic timer for 'time' and 'timeEnd'
901 if ((aMethodName == MethodTime || aMethodName == MethodTimeEnd)) {
902 if (mWindow) {
903 nsGlobalWindow *win = static_cast<nsGlobalWindow*>(mWindow.get());
904 MOZ_ASSERT(win);
906 ErrorResult rv;
907 nsRefPtr<nsPerformance> performance = win->GetPerformance(rv);
908 if (rv.Failed()) {
909 return;
910 }
912 callData->mMonotonicTimer = performance->Now();
913 } else {
914 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
915 MOZ_ASSERT(workerPrivate);
917 TimeDuration duration =
918 mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
920 callData->mMonotonicTimer = duration.ToMilliseconds();
921 }
922 }
924 // The operation is completed. RAII class has to be disabled.
925 raii.Finished();
927 if (!NS_IsMainThread()) {
928 // Here we are in a worker thread. The ConsoleCallData has to been removed
929 // from the list and it will be deleted by the ConsoleCallDataRunnable or
930 // by the Main-Thread Console object.
931 mQueuedCalls.popLast();
933 nsRefPtr<ConsoleCallDataRunnable> runnable =
934 new ConsoleCallDataRunnable(callData);
935 runnable->Dispatch();
936 return;
937 }
939 if (!mTimer) {
940 mTimer = do_CreateInstance("@mozilla.org/timer;1");
941 mTimer->InitWithCallback(this, CALL_DELAY,
942 nsITimer::TYPE_REPEATING_SLACK);
943 }
944 }
946 void
947 Console::AppendCallData(ConsoleCallData* aCallData)
948 {
949 mQueuedCalls.insertBack(aCallData);
951 if (!mTimer) {
952 mTimer = do_CreateInstance("@mozilla.org/timer;1");
953 mTimer->InitWithCallback(this, CALL_DELAY,
954 nsITimer::TYPE_REPEATING_SLACK);
955 }
956 }
958 // Timer callback used to process each of the queued calls.
959 NS_IMETHODIMP
960 Console::Notify(nsITimer *timer)
961 {
962 MOZ_ASSERT(!mQueuedCalls.isEmpty());
964 for (uint32_t i = 0; i < MESSAGES_IN_INTERVAL; ++i) {
965 ConsoleCallData* data = mQueuedCalls.popFirst();
966 if (!data) {
967 break;
968 }
970 ProcessCallData(data);
971 delete data;
972 }
974 if (mQueuedCalls.isEmpty() && mTimer) {
975 mTimer->Cancel();
976 mTimer = nullptr;
977 }
979 return NS_OK;
980 }
982 // We store information to lazily compute the stack in the reserved slots of
983 // LazyStackGetter. The first slot always stores a JS object: it's either the
984 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
985 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
986 // reified the stack yet, or an UndefinedValue() otherwise.
987 enum {
988 SLOT_STACKOBJ,
989 SLOT_RAW_STACK
990 };
992 bool
993 LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
994 {
995 JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
996 JS::Rooted<JSObject*> callee(aCx, &args.callee());
998 JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
999 if (v.isUndefined()) {
1000 // Already reified.
1001 args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
1002 return true;
1003 }
1005 nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
1006 nsTArray<ConsoleStackEntry> reifiedStack;
1007 nsresult rv = ReifyStack(stack, reifiedStack);
1008 if (NS_FAILED(rv)) {
1009 Throw(aCx, rv);
1010 return false;
1011 }
1013 JS::Rooted<JS::Value> stackVal(aCx);
1014 if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
1015 return false;
1016 }
1018 MOZ_ASSERT(stackVal.isObject());
1020 js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
1021 js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
1023 args.rval().set(stackVal);
1024 return true;
1025 }
1027 void
1028 Console::ProcessCallData(ConsoleCallData* aData)
1029 {
1030 MOZ_ASSERT(aData);
1031 MOZ_ASSERT(NS_IsMainThread());
1033 ConsoleStackEntry frame;
1034 if (!aData->mTopStackFrame.empty()) {
1035 frame = aData->mTopStackFrame.ref();
1036 }
1038 AutoSafeJSContext cx;
1039 ClearException ce(cx);
1040 RootedDictionary<ConsoleEvent> event(cx);
1042 JSAutoCompartment ac(cx, aData->mGlobal);
1044 event.mID.Construct();
1045 event.mInnerID.Construct();
1046 if (mWindow) {
1047 event.mID.Value().SetAsUnsignedLong() = mOuterID;
1048 event.mInnerID.Value().SetAsUnsignedLong() = mInnerID;
1049 } else {
1050 // If we are in a JSM, the window doesn't exist.
1051 event.mID.Value().SetAsString() = NS_LITERAL_STRING("jsm");
1052 event.mInnerID.Value().SetAsString() = frame.mFilename;
1053 }
1055 event.mLevel = aData->mMethodString;
1056 event.mFilename = frame.mFilename;
1057 event.mLineNumber = frame.mLineNumber;
1058 event.mFunctionName = frame.mFunctionName;
1059 event.mTimeStamp = aData->mTimeStamp;
1060 event.mPrivate = aData->mPrivate;
1062 switch (aData->mMethodName) {
1063 case MethodLog:
1064 case MethodInfo:
1065 case MethodWarn:
1066 case MethodError:
1067 case MethodException:
1068 case MethodDebug:
1069 case MethodAssert:
1070 event.mArguments.Construct();
1071 event.mStyles.Construct();
1072 ProcessArguments(cx, aData->mArguments, event.mArguments.Value(),
1073 event.mStyles.Value());
1074 break;
1076 default:
1077 event.mArguments.Construct();
1078 ArgumentsToValueList(aData->mArguments, event.mArguments.Value());
1079 }
1081 if (aData->mMethodName == MethodGroup ||
1082 aData->mMethodName == MethodGroupCollapsed ||
1083 aData->mMethodName == MethodGroupEnd) {
1084 ComposeGroupName(cx, aData->mArguments, event.mGroupName);
1085 }
1087 else if (aData->mMethodName == MethodTime && !aData->mArguments.IsEmpty()) {
1088 event.mTimer = StartTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
1089 }
1091 else if (aData->mMethodName == MethodTimeEnd && !aData->mArguments.IsEmpty()) {
1092 event.mTimer = StopTimer(cx, aData->mArguments[0], aData->mMonotonicTimer);
1093 }
1095 else if (aData->mMethodName == MethodCount) {
1096 event.mCounter = IncreaseCounter(cx, frame, aData->mArguments);
1097 }
1099 // We want to create a console event object and pass it to our
1100 // nsIConsoleAPIStorage implementation. We want to define some accessor
1101 // properties on this object, and those will need to keep an nsIStackFrame
1102 // alive. But nsIStackFrame cannot be wrapped in an untrusted scope. And
1103 // further, passing untrusted objects to system code is likely to run afoul of
1104 // Object Xrays. So we want to wrap in a system-principal scope here. But
1105 // which one? We could cheat and try to get the underlying JSObject* of
1106 // mStorage, but that's a bit fragile. Instead, we just use the junk scope,
1107 // with explicit permission from the XPConnect module owner. If you're
1108 // tempted to do that anywhere else, talk to said module owner first.
1109 JSAutoCompartment ac2(cx, xpc::GetJunkScope());
1111 JS::Rooted<JS::Value> eventValue(cx);
1112 if (!event.ToObject(cx, &eventValue)) {
1113 return;
1114 }
1116 JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
1117 MOZ_ASSERT(eventObj);
1119 if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
1120 return;
1121 }
1123 if (ShouldIncludeStackrace(aData->mMethodName)) {
1124 // Now define the "stacktrace" property on eventObj. There are two cases
1125 // here. Either we came from a worker and have a reified stack, or we want
1126 // to define a getter that will lazily reify the stack.
1127 if (!aData->mReifiedStack.empty()) {
1128 JS::Rooted<JS::Value> stacktrace(cx);
1129 if (!ToJSValue(cx, aData->mReifiedStack.ref(), &stacktrace) ||
1130 !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
1131 JSPROP_ENUMERATE)) {
1132 return;
1133 }
1134 } else {
1135 JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
1136 eventObj, "stacktrace");
1137 if (!fun) {
1138 return;
1139 }
1141 JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
1143 // We want to store our stack in the function and have it stay alive. But
1144 // we also need sane access to the C++ nsIStackFrame. So store both a JS
1145 // wrapper and the raw pointer: the former will keep the latter alive.
1146 JS::Rooted<JS::Value> stackVal(cx);
1147 nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
1148 &stackVal);
1149 if (NS_FAILED(rv)) {
1150 return;
1151 }
1153 js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
1154 js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
1155 JS::PrivateValue(aData->mStack.get()));
1157 if (!JS_DefineProperty(cx, eventObj, "stacktrace",
1158 JS::UndefinedHandleValue,
1159 JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
1160 JSPROP_SETTER,
1161 JS_DATA_TO_FUNC_PTR(JSPropertyOp, funObj.get()),
1162 nullptr)) {
1163 return;
1164 }
1165 }
1166 }
1168 if (!mStorage) {
1169 mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
1170 }
1172 if (!mStorage) {
1173 NS_WARNING("Failed to get the ConsoleAPIStorage service.");
1174 return;
1175 }
1177 nsAutoString innerID;
1178 innerID.AppendInt(mInnerID);
1180 if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
1181 NS_WARNING("Failed to record a console event.");
1182 }
1184 nsXPConnect* xpc = nsXPConnect::XPConnect();
1185 nsCOMPtr<nsISupports> wrapper;
1186 const nsIID& iid = NS_GET_IID(nsISupports);
1188 if (NS_FAILED(xpc->WrapJS(cx, eventObj, iid, getter_AddRefs(wrapper)))) {
1189 return;
1190 }
1192 nsCOMPtr<nsIObserverService> obs =
1193 do_GetService("@mozilla.org/observer-service;1");
1194 if (obs) {
1195 nsAutoString outerID;
1196 outerID.AppendInt(mOuterID);
1198 obs->NotifyObservers(wrapper, "console-api-log-event", outerID.get());
1199 }
1200 }
1202 void
1203 Console::ProcessArguments(JSContext* aCx,
1204 const nsTArray<JS::Heap<JS::Value>>& aData,
1205 Sequence<JS::Value>& aSequence,
1206 Sequence<JS::Value>& aStyles)
1207 {
1208 if (aData.IsEmpty()) {
1209 return;
1210 }
1212 if (aData.Length() == 1 || !aData[0].isString()) {
1213 ArgumentsToValueList(aData, aSequence);
1214 return;
1215 }
1217 JS::Rooted<JS::Value> format(aCx, aData[0]);
1218 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
1219 if (!jsString) {
1220 return;
1221 }
1223 nsDependentJSString string;
1224 if (!string.init(aCx, jsString)) {
1225 return;
1226 }
1228 nsString::const_iterator start, end;
1229 string.BeginReading(start);
1230 string.EndReading(end);
1232 nsString output;
1233 uint32_t index = 1;
1235 while (start != end) {
1236 if (*start != '%') {
1237 output.Append(*start);
1238 ++start;
1239 continue;
1240 }
1242 ++start;
1243 if (start == end) {
1244 output.Append('%');
1245 break;
1246 }
1248 if (*start == '%') {
1249 output.Append(*start);
1250 ++start;
1251 continue;
1252 }
1254 nsAutoString tmp;
1255 tmp.Append('%');
1257 int32_t integer = -1;
1258 int32_t mantissa = -1;
1260 // Let's parse %<number>.<number> for %d and %f
1261 if (*start >= '0' && *start <= '9') {
1262 integer = 0;
1264 do {
1265 integer = integer * 10 + *start - '0';
1266 tmp.Append(*start);
1267 ++start;
1268 } while (*start >= '0' && *start <= '9' && start != end);
1269 }
1271 if (start == end) {
1272 output.Append(tmp);
1273 break;
1274 }
1276 if (*start == '.') {
1277 tmp.Append(*start);
1278 ++start;
1280 if (start == end) {
1281 output.Append(tmp);
1282 break;
1283 }
1285 // '.' must be followed by a number.
1286 if (*start < '0' || *start > '9') {
1287 output.Append(tmp);
1288 continue;
1289 }
1291 mantissa = 0;
1293 do {
1294 mantissa = mantissa * 10 + *start - '0';
1295 tmp.Append(*start);
1296 ++start;
1297 } while (*start >= '0' && *start <= '9' && start != end);
1299 if (start == end) {
1300 output.Append(tmp);
1301 break;
1302 }
1303 }
1305 char ch = *start;
1306 tmp.Append(ch);
1307 ++start;
1309 switch (ch) {
1310 case 'o':
1311 case 'O':
1312 {
1313 if (!output.IsEmpty()) {
1314 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1315 output.get(),
1316 output.Length()));
1317 if (!str) {
1318 return;
1319 }
1321 aSequence.AppendElement(JS::StringValue(str));
1322 output.Truncate();
1323 }
1325 JS::Rooted<JS::Value> v(aCx);
1326 if (index < aData.Length()) {
1327 v = aData[index++];
1328 }
1330 aSequence.AppendElement(v);
1331 break;
1332 }
1334 case 'c':
1335 {
1336 if (!output.IsEmpty()) {
1337 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
1338 output.get(),
1339 output.Length()));
1340 if (!str) {
1341 return;
1342 }
1344 aSequence.AppendElement(JS::StringValue(str));
1345 output.Truncate();
1346 }
1348 if (index < aData.Length()) {
1349 JS::Rooted<JS::Value> v(aCx, aData[index++]);
1350 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
1351 if (!jsString) {
1352 return;
1353 }
1355 int32_t diff = aSequence.Length() - aStyles.Length();
1356 if (diff > 0) {
1357 for (int32_t i = 0; i < diff; i++) {
1358 aStyles.AppendElement(JS::NullValue());
1359 }
1360 }
1361 aStyles.AppendElement(JS::StringValue(jsString));
1362 }
1363 break;
1364 }
1366 case 's':
1367 if (index < aData.Length()) {
1368 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1369 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1370 if (!jsString) {
1371 return;
1372 }
1374 nsDependentJSString v;
1375 if (!v.init(aCx, jsString)) {
1376 return;
1377 }
1379 output.Append(v);
1380 }
1381 break;
1383 case 'd':
1384 case 'i':
1385 if (index < aData.Length()) {
1386 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1388 int32_t v;
1389 if (!JS::ToInt32(aCx, value, &v)) {
1390 return;
1391 }
1393 nsCString format;
1394 MakeFormatString(format, integer, mantissa, 'd');
1395 output.AppendPrintf(format.get(), v);
1396 }
1397 break;
1399 case 'f':
1400 if (index < aData.Length()) {
1401 JS::Rooted<JS::Value> value(aCx, aData[index++]);
1403 double v;
1404 if (!JS::ToNumber(aCx, value, &v)) {
1405 return;
1406 }
1408 nsCString format;
1409 MakeFormatString(format, integer, mantissa, 'f');
1410 output.AppendPrintf(format.get(), v);
1411 }
1412 break;
1414 default:
1415 output.Append(tmp);
1416 break;
1417 }
1418 }
1420 if (!output.IsEmpty()) {
1421 JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx, output.get(),
1422 output.Length()));
1423 if (!str) {
1424 return;
1425 }
1427 aSequence.AppendElement(JS::StringValue(str));
1428 }
1430 // The rest of the array, if unused by the format string.
1431 for (; index < aData.Length(); ++index) {
1432 aSequence.AppendElement(aData[index]);
1433 }
1434 }
1436 void
1437 Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
1438 int32_t aMantissa, char aCh)
1439 {
1440 aFormat.Append("%");
1441 if (aInteger >= 0) {
1442 aFormat.AppendInt(aInteger);
1443 }
1445 if (aMantissa >= 0) {
1446 aFormat.Append(".");
1447 aFormat.AppendInt(aMantissa);
1448 }
1450 aFormat.Append(aCh);
1451 }
1453 void
1454 Console::ComposeGroupName(JSContext* aCx,
1455 const nsTArray<JS::Heap<JS::Value>>& aData,
1456 nsAString& aName)
1457 {
1458 for (uint32_t i = 0; i < aData.Length(); ++i) {
1459 if (i != 0) {
1460 aName.AppendASCII(" ");
1461 }
1463 JS::Rooted<JS::Value> value(aCx, aData[i]);
1464 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
1465 if (!jsString) {
1466 return;
1467 }
1469 nsDependentJSString string;
1470 if (!string.init(aCx, jsString)) {
1471 return;
1472 }
1474 aName.Append(string);
1475 }
1476 }
1478 JS::Value
1479 Console::StartTimer(JSContext* aCx, const JS::Value& aName,
1480 DOMHighResTimeStamp aTimestamp)
1481 {
1482 if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
1483 RootedDictionary<ConsoleTimerError> error(aCx);
1485 JS::Rooted<JS::Value> value(aCx);
1486 if (!error.ToObject(aCx, &value)) {
1487 return JS::UndefinedValue();
1488 }
1490 return value;
1491 }
1493 RootedDictionary<ConsoleTimerStart> timer(aCx);
1495 JS::Rooted<JS::Value> name(aCx, aName);
1496 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1497 if (!jsString) {
1498 return JS::UndefinedValue();
1499 }
1501 nsDependentJSString key;
1502 if (!key.init(aCx, jsString)) {
1503 return JS::UndefinedValue();
1504 }
1506 timer.mName = key;
1508 DOMHighResTimeStamp entry;
1509 if (!mTimerRegistry.Get(key, &entry)) {
1510 mTimerRegistry.Put(key, aTimestamp);
1511 } else {
1512 aTimestamp = entry;
1513 }
1515 timer.mStarted = aTimestamp;
1517 JS::Rooted<JS::Value> value(aCx);
1518 if (!timer.ToObject(aCx, &value)) {
1519 return JS::UndefinedValue();
1520 }
1522 return value;
1523 }
1525 JS::Value
1526 Console::StopTimer(JSContext* aCx, const JS::Value& aName,
1527 DOMHighResTimeStamp aTimestamp)
1528 {
1529 JS::Rooted<JS::Value> name(aCx, aName);
1530 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
1531 if (!jsString) {
1532 return JS::UndefinedValue();
1533 }
1535 nsDependentJSString key;
1536 if (!key.init(aCx, jsString)) {
1537 return JS::UndefinedValue();
1538 }
1540 DOMHighResTimeStamp entry;
1541 if (!mTimerRegistry.Get(key, &entry)) {
1542 return JS::UndefinedValue();
1543 }
1545 mTimerRegistry.Remove(key);
1547 RootedDictionary<ConsoleTimerEnd> timer(aCx);
1548 timer.mName = key;
1549 timer.mDuration = aTimestamp - entry;
1551 JS::Rooted<JS::Value> value(aCx);
1552 if (!timer.ToObject(aCx, &value)) {
1553 return JS::UndefinedValue();
1554 }
1556 return value;
1557 }
1559 void
1560 Console::ArgumentsToValueList(const nsTArray<JS::Heap<JS::Value>>& aData,
1561 Sequence<JS::Value>& aSequence)
1562 {
1563 for (uint32_t i = 0; i < aData.Length(); ++i) {
1564 aSequence.AppendElement(aData[i]);
1565 }
1566 }
1568 JS::Value
1569 Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
1570 const nsTArray<JS::Heap<JS::Value>>& aArguments)
1571 {
1572 ClearException ce(aCx);
1574 nsAutoString key;
1575 nsAutoString label;
1577 if (!aArguments.IsEmpty()) {
1578 JS::Rooted<JS::Value> labelValue(aCx, aArguments[0]);
1579 JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, labelValue));
1581 nsDependentJSString string;
1582 if (jsString && string.init(aCx, jsString)) {
1583 label = string;
1584 key = string;
1585 }
1586 }
1588 if (key.IsEmpty()) {
1589 key.Append(aFrame.mFilename);
1590 key.Append(NS_LITERAL_STRING(":"));
1591 key.AppendInt(aFrame.mLineNumber);
1592 }
1594 uint32_t count = 0;
1595 if (!mCounterRegistry.Get(key, &count)) {
1596 if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
1597 RootedDictionary<ConsoleCounterError> error(aCx);
1599 JS::Rooted<JS::Value> value(aCx);
1600 if (!error.ToObject(aCx, &value)) {
1601 return JS::UndefinedValue();
1602 }
1604 return value;
1605 }
1606 }
1608 ++count;
1609 mCounterRegistry.Put(key, count);
1611 RootedDictionary<ConsoleCounter> data(aCx);
1612 data.mLabel = label;
1613 data.mCount = count;
1615 JS::Rooted<JS::Value> value(aCx);
1616 if (!data.ToObject(aCx, &value)) {
1617 return JS::UndefinedValue();
1618 }
1620 return value;
1621 }
1623 void
1624 Console::ClearConsoleData()
1625 {
1626 while (ConsoleCallData* data = mQueuedCalls.popFirst()) {
1627 delete data;
1628 }
1629 }
1631 bool
1632 Console::ShouldIncludeStackrace(MethodName aMethodName)
1633 {
1634 switch (aMethodName) {
1635 case MethodError:
1636 case MethodException:
1637 case MethodAssert:
1638 case MethodTrace:
1639 return true;
1640 default:
1641 return false;
1642 }
1643 }
1645 } // namespace dom
1646 } // namespace mozilla