1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Maintains a circular buffer of recent messages, and notifies
9 * listeners when new messages are logged.
14 #include "nsCOMArray.h"
15 #include "nsThreadUtils.h"
17 #include "nsConsoleService.h"
18 #include "nsConsoleMessage.h"
19 #include "nsIClassInfoImpl.h"
20 #include "nsIConsoleListener.h"
21 #include "nsIObserverService.h"
22 #include "nsPrintfCString.h"
23 #include "nsProxyRelease.h"
24 #include "nsIScriptError.h"
25 #include "nsISupportsPrimitives.h"
26 #include "js/friend/ErrorMessages.h"
27 #include "mozilla/dom/WindowGlobalParent.h"
28 #include "mozilla/dom/ContentParent.h"
29 #include "mozilla/dom/BrowserParent.h"
30 #include "mozilla/dom/ScriptSettings.h"
32 #include "mozilla/SchedulerGroup.h"
33 #include "mozilla/Services.h"
36 # include <android/log.h>
37 # include "mozilla/dom/ContentChild.h"
38 # include "mozilla/StaticPrefs_consoleservice.h"
44 using namespace mozilla
;
46 NS_IMPL_ADDREF(nsConsoleService
)
47 NS_IMPL_RELEASE(nsConsoleService
)
48 NS_IMPL_CLASSINFO(nsConsoleService
, nullptr,
49 nsIClassInfo::THREADSAFE
| nsIClassInfo::SINGLETON
,
50 NS_CONSOLESERVICE_CID
)
51 NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService
, nsIConsoleService
, nsIObserver
)
52 NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService
, nsIConsoleService
, nsIObserver
)
54 static const bool gLoggingEnabled
= true;
55 static const bool gLoggingBuffered
= true;
57 static bool gLoggingToDebugger
= true;
60 nsConsoleService::MessageElement::~MessageElement() = default;
62 nsConsoleService::nsConsoleService()
64 // XXX grab this from a pref!
65 // hm, but worry about circularity, bc we want to be able to report
68 mDeliveringMessage(false),
69 mLock("nsConsoleService.mLock") {
71 // This environment variable controls whether the console service
72 // should be prevented from putting output to the attached debugger.
73 // It only affects the Windows platform.
75 // To disable OutputDebugString, set:
76 // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1
78 const char* disableDebugLoggingVar
=
79 getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT");
81 !disableDebugLoggingVar
|| (disableDebugLoggingVar
[0] == '0');
85 void nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID
) {
86 MOZ_RELEASE_ASSERT(NS_IsMainThread());
87 MutexAutoLock
lock(mLock
);
89 for (MessageElement
* e
= mMessages
.getFirst(); e
!= nullptr;) {
90 // Only messages implementing nsIScriptError interface expose the
92 nsCOMPtr
<nsIScriptError
> scriptError
= do_QueryInterface(e
->Get());
97 uint64_t innerWindowID
;
98 nsresult rv
= scriptError
->GetInnerWindowID(&innerWindowID
);
99 if (NS_FAILED(rv
) || innerWindowID
!= innerID
) {
104 MessageElement
* next
= e
->getNext();
108 MOZ_ASSERT(mCurrentSize
< mMaximumSize
);
114 void nsConsoleService::ClearMessages() {
115 // NB: A lock is not required here as it's only called from |Reset| which
116 // locks for us and from the dtor.
117 while (!mMessages
.isEmpty()) {
118 MessageElement
* e
= mMessages
.popFirst();
124 nsConsoleService::~nsConsoleService() {
125 MOZ_RELEASE_ASSERT(NS_IsMainThread());
130 class AddConsolePrefWatchers
: public Runnable
{
132 explicit AddConsolePrefWatchers(nsConsoleService
* aConsole
)
133 : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole
) {}
135 NS_IMETHOD
Run() override
{
136 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
138 obs
->AddObserver(mConsole
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
139 obs
->AddObserver(mConsole
, "inner-window-destroyed", false);
141 if (!gLoggingBuffered
) {
148 RefPtr
<nsConsoleService
> mConsole
;
151 nsresult
nsConsoleService::Init() {
152 NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
157 nsresult
nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage
* aMessage
,
161 nsCOMPtr
<nsIScriptError
> scriptError
= do_QueryInterface(aMessage
);
163 // Not an nsIScriptError
169 rv
= scriptError
->GetInnerWindowID(&windowID
);
170 NS_ENSURE_SUCCESS(rv
, rv
);
172 // Does not set window id
176 RefPtr
<mozilla::dom::WindowGlobalParent
> windowGlobalParent
=
177 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID
);
178 if (!windowGlobalParent
) {
179 // Could not find parent window by id
183 RefPtr
<mozilla::dom::BrowserParent
> browserParent
=
184 windowGlobalParent
->GetBrowserParent();
185 if (!browserParent
) {
189 mozilla::dom::ContentParent
* contentParent
= browserParent
->Manager();
190 if (!contentParent
) {
191 return NS_ERROR_FAILURE
;
194 nsAutoString msg
, sourceName
, sourceLine
;
196 uint32_t lineNum
, colNum
, flags
;
197 uint64_t innerWindowId
;
198 bool fromPrivateWindow
, fromChromeContext
;
200 rv
= scriptError
->GetErrorMessage(msg
);
201 NS_ENSURE_SUCCESS(rv
, rv
);
202 rv
= scriptError
->GetSourceName(sourceName
);
203 NS_ENSURE_SUCCESS(rv
, rv
);
204 rv
= scriptError
->GetSourceLine(sourceLine
);
205 NS_ENSURE_SUCCESS(rv
, rv
);
207 rv
= scriptError
->GetCategory(getter_Copies(category
));
208 NS_ENSURE_SUCCESS(rv
, rv
);
209 rv
= scriptError
->GetLineNumber(&lineNum
);
210 NS_ENSURE_SUCCESS(rv
, rv
);
211 rv
= scriptError
->GetColumnNumber(&colNum
);
212 NS_ENSURE_SUCCESS(rv
, rv
);
213 rv
= scriptError
->GetFlags(&flags
);
214 NS_ENSURE_SUCCESS(rv
, rv
);
215 rv
= scriptError
->GetIsFromPrivateWindow(&fromPrivateWindow
);
216 NS_ENSURE_SUCCESS(rv
, rv
);
217 rv
= scriptError
->GetIsFromChromeContext(&fromChromeContext
);
218 NS_ENSURE_SUCCESS(rv
, rv
);
219 rv
= scriptError
->GetInnerWindowID(&innerWindowId
);
220 NS_ENSURE_SUCCESS(rv
, rv
);
222 *sent
= contentParent
->SendScriptError(
223 msg
, sourceName
, sourceLine
, lineNum
, colNum
, flags
, category
,
224 fromPrivateWindow
, innerWindowId
, fromChromeContext
);
230 class LogMessageRunnable
: public Runnable
{
232 LogMessageRunnable(nsIConsoleMessage
* aMessage
, nsConsoleService
* aService
)
233 : mozilla::Runnable("LogMessageRunnable"),
235 mService(aService
) {}
240 nsCOMPtr
<nsIConsoleMessage
> mMessage
;
241 RefPtr
<nsConsoleService
> mService
;
245 LogMessageRunnable::Run() {
246 // Snapshot of listeners so that we don't reenter this hash during
248 nsCOMArray
<nsIConsoleListener
> listeners
;
249 mService
->CollectCurrentListeners(listeners
);
251 mService
->SetIsDelivering();
253 for (int32_t i
= 0; i
< listeners
.Count(); ++i
) {
254 listeners
[i
]->Observe(mMessage
);
257 mService
->SetDoneDelivering();
264 // nsIConsoleService methods
266 nsConsoleService::LogMessage(nsIConsoleMessage
* aMessage
) {
267 return LogMessageWithMode(aMessage
, nsIConsoleService::OutputToLog
);
270 // This can be called off the main thread.
271 nsresult
nsConsoleService::LogMessageWithMode(
272 nsIConsoleMessage
* aMessage
, nsIConsoleService::OutputMode aOutputMode
) {
274 return NS_ERROR_INVALID_ARG
;
277 if (!gLoggingEnabled
) {
281 if (NS_IsMainThread() && mDeliveringMessage
) {
283 aMessage
->ToString(msg
);
286 "Reentrancy error: some client attempted to display a message to "
287 "the console while in a console listener. The following message "
288 "was discarded: \"%s\"",
291 return NS_ERROR_FAILURE
;
294 if (XRE_IsParentProcess() && NS_IsMainThread()) {
295 // If mMessage is a scriptError with an innerWindowId set,
296 // forward it to the matching ContentParent
297 // This enables logging from parent to content process
299 nsresult rv
= MaybeForwardScriptError(aMessage
, &sent
);
300 NS_ENSURE_SUCCESS(rv
, rv
);
306 RefPtr
<LogMessageRunnable
> r
;
307 nsCOMPtr
<nsIConsoleMessage
> retiredMessage
;
310 * Lock while updating buffer, and while taking snapshot of
314 MutexAutoLock
lock(mLock
);
317 if (StaticPrefs::consoleservice_logcat() && aOutputMode
== OutputToLog
) {
319 aMessage
->ToString(msg
);
321 /** Attempt to use the process name as the log tag. */
322 mozilla::dom::ContentChild
* child
=
323 mozilla::dom::ContentChild::GetSingleton();
326 child
->GetProcessName(appName
);
328 appName
= "GeckoConsole";
331 uint32_t logLevel
= 0;
332 aMessage
->GetLogLevel(&logLevel
);
334 android_LogPriority logPriority
= ANDROID_LOG_INFO
;
336 case nsIConsoleMessage::debug
:
337 logPriority
= ANDROID_LOG_DEBUG
;
339 case nsIConsoleMessage::info
:
340 logPriority
= ANDROID_LOG_INFO
;
342 case nsIConsoleMessage::warn
:
343 logPriority
= ANDROID_LOG_WARN
;
345 case nsIConsoleMessage::error
:
346 logPriority
= ANDROID_LOG_ERROR
;
350 __android_log_print(logPriority
, appName
.get(), "%s", msg
.get());
354 if (gLoggingToDebugger
&& IsDebuggerPresent()) {
356 aMessage
->GetMessageMoz(msg
);
358 OutputDebugStringW(msg
.get());
362 if (gLoggingBuffered
) {
363 MessageElement
* e
= new MessageElement(aMessage
);
364 mMessages
.insertBack(e
);
365 if (mCurrentSize
!= mMaximumSize
) {
368 MessageElement
* p
= mMessages
.popFirst();
370 p
->swapMessage(retiredMessage
);
375 if (mListeners
.Count() > 0) {
376 r
= new LogMessageRunnable(aMessage
, this);
380 if (retiredMessage
) {
381 // Release |retiredMessage| on the main thread in case it is an instance of
382 // a mainthread-only class like nsScriptErrorWithStack and we're off the
384 NS_ReleaseOnMainThread("nsConsoleService::retiredMessage",
385 retiredMessage
.forget());
389 // avoid failing in XPCShell tests
390 nsCOMPtr
<nsIThread
> mainThread
= do_GetMainThread();
392 SchedulerGroup::Dispatch(r
.forget());
399 // See nsIConsoleService.idl for more info about this method
401 nsConsoleService::CallFunctionAndLogException(
402 JS::Handle
<JS::Value
> targetGlobal
, JS::HandleValue function
, JSContext
* cx
,
403 JS::MutableHandleValue retval
) {
404 if (!targetGlobal
.isObject() || !function
.isObject()) {
405 return NS_ERROR_INVALID_ARG
;
408 JS::Rooted
<JS::Realm
*> contextRealm(cx
, JS::GetCurrentRealmOrNull(cx
));
410 return NS_ERROR_INVALID_ARG
;
413 JS::Rooted
<JSObject
*> global(
414 cx
, js::CheckedUnwrapDynamic(&targetGlobal
.toObject(), cx
));
416 return NS_ERROR_INVALID_ARG
;
419 // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException
420 // which will do most of the work required for this function.
422 // We only have to pick the right global for which we want to flag
423 // the exception against.
424 dom::AutoJSAPI jsapi
;
425 if (!jsapi
.Init(global
)) {
426 return NS_ERROR_UNEXPECTED
;
428 JSContext
* ccx
= jsapi
.cx();
430 // AutoJSAPI picks `targetGlobal` as execution compartment
431 // whereas we expect to run `function` from the callsites compartment.
432 JSAutoRealm
ar(ccx
, JS::GetRealmGlobalOrNull(contextRealm
));
434 JS::RootedValue
funVal(ccx
, function
);
435 if (!JS_WrapValue(ccx
, &funVal
)) {
436 return NS_ERROR_FAILURE
;
438 if (!JS_CallFunctionValue(ccx
, nullptr, funVal
, JS::HandleValueArray::empty(),
440 return NS_ERROR_XPC_JAVASCRIPT_ERROR
;
446 void nsConsoleService::CollectCurrentListeners(
447 nsCOMArray
<nsIConsoleListener
>& aListeners
) {
448 MutexAutoLock
lock(mLock
);
449 // XXX When MakeBackInserter(nsCOMArray<T>&) is added, we can do:
450 // AppendToArray(aListeners, mListeners.Values());
451 for (const auto& listener
: mListeners
.Values()) {
452 aListeners
.AppendObject(listener
);
457 nsConsoleService::LogStringMessage(const char16_t
* aMessage
) {
458 if (!gLoggingEnabled
) {
462 RefPtr
<nsConsoleMessage
> msg(new nsConsoleMessage(
463 aMessage
? nsDependentString(aMessage
) : EmptyString()));
464 return LogMessage(msg
);
468 nsConsoleService::GetMessageArray(
469 nsTArray
<RefPtr
<nsIConsoleMessage
>>& aMessages
) {
470 MOZ_RELEASE_ASSERT(NS_IsMainThread());
472 MutexAutoLock
lock(mLock
);
474 if (mMessages
.isEmpty()) {
478 MOZ_ASSERT(mCurrentSize
<= mMaximumSize
);
479 aMessages
.SetCapacity(mCurrentSize
);
481 for (MessageElement
* e
= mMessages
.getFirst(); e
!= nullptr;
483 aMessages
.AppendElement(e
->Get());
490 nsConsoleService::RegisterListener(nsIConsoleListener
* aListener
) {
491 if (!NS_IsMainThread()) {
492 NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
493 return NS_ERROR_NOT_SAME_THREAD
;
496 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aListener
);
497 MOZ_ASSERT(canonical
);
499 MutexAutoLock
lock(mLock
);
500 return mListeners
.WithEntryHandle(canonical
, [&](auto&& entry
) {
502 // Reregistering a listener isn't good
503 return NS_ERROR_FAILURE
;
505 entry
.Insert(aListener
);
511 nsConsoleService::UnregisterListener(nsIConsoleListener
* aListener
) {
512 if (!NS_IsMainThread()) {
513 NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
514 return NS_ERROR_NOT_SAME_THREAD
;
517 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aListener
);
519 MutexAutoLock
lock(mLock
);
521 return mListeners
.Remove(canonical
)
523 // Unregistering a listener that was never registered?
528 nsConsoleService::Reset() {
529 MOZ_RELEASE_ASSERT(NS_IsMainThread());
532 * Make sure nobody trips into the buffer while it's being reset
534 MutexAutoLock
lock(mLock
);
541 nsConsoleService::ResetWindow(uint64_t windowInnerId
) {
542 MOZ_RELEASE_ASSERT(NS_IsMainThread());
544 ClearMessagesForWindowID(windowInnerId
);
549 nsConsoleService::Observe(nsISupports
* aSubject
, const char* aTopic
,
550 const char16_t
* aData
) {
551 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
552 // Dump all our messages, in case any are cycle collected.
554 // We could remove ourselves from the observer service, but it is about to
555 // drop all observers anyways, so why bother.
556 } else if (!strcmp(aTopic
, "inner-window-destroyed")) {
557 nsCOMPtr
<nsISupportsPRUint64
> supportsInt
= do_QueryInterface(aSubject
);
558 MOZ_ASSERT(supportsInt
);
561 MOZ_ALWAYS_SUCCEEDS(supportsInt
->GetData(&windowId
));
563 ClearMessagesForWindowID(windowId
);