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
;
195 nsAutoCString sourceName
;
197 uint32_t lineNum
, colNum
, flags
;
198 uint64_t innerWindowId
;
199 bool fromPrivateWindow
, fromChromeContext
;
201 rv
= scriptError
->GetErrorMessage(msg
);
202 NS_ENSURE_SUCCESS(rv
, rv
);
203 rv
= scriptError
->GetSourceName(sourceName
);
204 NS_ENSURE_SUCCESS(rv
, rv
);
206 rv
= scriptError
->GetCategory(getter_Copies(category
));
207 NS_ENSURE_SUCCESS(rv
, rv
);
208 rv
= scriptError
->GetLineNumber(&lineNum
);
209 NS_ENSURE_SUCCESS(rv
, rv
);
210 rv
= scriptError
->GetColumnNumber(&colNum
);
211 NS_ENSURE_SUCCESS(rv
, rv
);
212 rv
= scriptError
->GetFlags(&flags
);
213 NS_ENSURE_SUCCESS(rv
, rv
);
214 rv
= scriptError
->GetIsFromPrivateWindow(&fromPrivateWindow
);
215 NS_ENSURE_SUCCESS(rv
, rv
);
216 rv
= scriptError
->GetIsFromChromeContext(&fromChromeContext
);
217 NS_ENSURE_SUCCESS(rv
, rv
);
218 rv
= scriptError
->GetInnerWindowID(&innerWindowId
);
219 NS_ENSURE_SUCCESS(rv
, rv
);
221 *sent
= contentParent
->SendScriptError(msg
, sourceName
, lineNum
, colNum
,
222 flags
, category
, fromPrivateWindow
,
223 innerWindowId
, fromChromeContext
);
229 class LogMessageRunnable
: public Runnable
{
231 LogMessageRunnable(nsIConsoleMessage
* aMessage
, nsConsoleService
* aService
)
232 : mozilla::Runnable("LogMessageRunnable"),
234 mService(aService
) {}
239 nsCOMPtr
<nsIConsoleMessage
> mMessage
;
240 RefPtr
<nsConsoleService
> mService
;
244 LogMessageRunnable::Run() {
245 // Snapshot of listeners so that we don't reenter this hash during
247 nsCOMArray
<nsIConsoleListener
> listeners
;
248 mService
->CollectCurrentListeners(listeners
);
250 mService
->SetIsDelivering();
252 for (int32_t i
= 0; i
< listeners
.Count(); ++i
) {
253 listeners
[i
]->Observe(mMessage
);
256 mService
->SetDoneDelivering();
263 // nsIConsoleService methods
265 nsConsoleService::LogMessage(nsIConsoleMessage
* aMessage
) {
266 return LogMessageWithMode(aMessage
, nsIConsoleService::OutputToLog
);
269 // This can be called off the main thread.
270 nsresult
nsConsoleService::LogMessageWithMode(
271 nsIConsoleMessage
* aMessage
, nsIConsoleService::OutputMode aOutputMode
) {
273 return NS_ERROR_INVALID_ARG
;
276 if (!gLoggingEnabled
) {
280 if (NS_IsMainThread() && mDeliveringMessage
) {
282 aMessage
->ToString(msg
);
285 "Reentrancy error: some client attempted to display a message to "
286 "the console while in a console listener. The following message "
287 "was discarded: \"%s\"",
290 return NS_ERROR_FAILURE
;
293 if (XRE_IsParentProcess() && NS_IsMainThread()) {
294 // If mMessage is a scriptError with an innerWindowId set,
295 // forward it to the matching ContentParent
296 // This enables logging from parent to content process
298 nsresult rv
= MaybeForwardScriptError(aMessage
, &sent
);
299 NS_ENSURE_SUCCESS(rv
, rv
);
305 RefPtr
<LogMessageRunnable
> r
;
306 nsCOMPtr
<nsIConsoleMessage
> retiredMessage
;
309 * Lock while updating buffer, and while taking snapshot of
313 MutexAutoLock
lock(mLock
);
316 if (StaticPrefs::consoleservice_logcat() && aOutputMode
== OutputToLog
) {
318 aMessage
->ToString(msg
);
320 /** Attempt to use the process name as the log tag. */
321 mozilla::dom::ContentChild
* child
=
322 mozilla::dom::ContentChild::GetSingleton();
325 child
->GetProcessName(appName
);
327 appName
= "GeckoConsole";
330 uint32_t logLevel
= 0;
331 aMessage
->GetLogLevel(&logLevel
);
333 android_LogPriority logPriority
= ANDROID_LOG_INFO
;
335 case nsIConsoleMessage::debug
:
336 logPriority
= ANDROID_LOG_DEBUG
;
338 case nsIConsoleMessage::info
:
339 logPriority
= ANDROID_LOG_INFO
;
341 case nsIConsoleMessage::warn
:
342 logPriority
= ANDROID_LOG_WARN
;
344 case nsIConsoleMessage::error
:
345 logPriority
= ANDROID_LOG_ERROR
;
349 __android_log_print(logPriority
, appName
.get(), "%s", msg
.get());
353 if (gLoggingToDebugger
&& IsDebuggerPresent()) {
355 aMessage
->GetMessageMoz(msg
);
357 OutputDebugStringW(msg
.get());
361 if (gLoggingBuffered
) {
362 MessageElement
* e
= new MessageElement(aMessage
);
363 mMessages
.insertBack(e
);
364 if (mCurrentSize
!= mMaximumSize
) {
367 MessageElement
* p
= mMessages
.popFirst();
369 p
->swapMessage(retiredMessage
);
374 if (mListeners
.Count() > 0) {
375 r
= new LogMessageRunnable(aMessage
, this);
379 if (retiredMessage
) {
380 // Release |retiredMessage| on the main thread in case it is an instance of
381 // a mainthread-only class like nsScriptErrorWithStack and we're off the
383 NS_ReleaseOnMainThread("nsConsoleService::retiredMessage",
384 retiredMessage
.forget());
388 // avoid failing in XPCShell tests
389 nsCOMPtr
<nsIThread
> mainThread
= do_GetMainThread();
391 SchedulerGroup::Dispatch(r
.forget());
398 // See nsIConsoleService.idl for more info about this method
400 nsConsoleService::CallFunctionAndLogException(
401 JS::Handle
<JS::Value
> targetGlobal
, JS::HandleValue function
, JSContext
* cx
,
402 JS::MutableHandleValue retval
) {
403 if (!targetGlobal
.isObject() || !function
.isObject()) {
404 return NS_ERROR_INVALID_ARG
;
407 JS::Rooted
<JS::Realm
*> contextRealm(cx
, JS::GetCurrentRealmOrNull(cx
));
409 return NS_ERROR_INVALID_ARG
;
412 JS::Rooted
<JSObject
*> global(
413 cx
, js::CheckedUnwrapDynamic(&targetGlobal
.toObject(), cx
));
415 return NS_ERROR_INVALID_ARG
;
418 // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException
419 // which will do most of the work required for this function.
421 // We only have to pick the right global for which we want to flag
422 // the exception against.
423 dom::AutoJSAPI jsapi
;
424 if (!jsapi
.Init(global
)) {
425 return NS_ERROR_UNEXPECTED
;
427 JSContext
* ccx
= jsapi
.cx();
429 // AutoJSAPI picks `targetGlobal` as execution compartment
430 // whereas we expect to run `function` from the callsites compartment.
431 JSAutoRealm
ar(ccx
, JS::GetRealmGlobalOrNull(contextRealm
));
433 JS::RootedValue
funVal(ccx
, function
);
434 if (!JS_WrapValue(ccx
, &funVal
)) {
435 return NS_ERROR_FAILURE
;
437 if (!JS_CallFunctionValue(ccx
, nullptr, funVal
, JS::HandleValueArray::empty(),
439 return NS_ERROR_XPC_JAVASCRIPT_ERROR
;
445 void nsConsoleService::CollectCurrentListeners(
446 nsCOMArray
<nsIConsoleListener
>& aListeners
) {
447 MutexAutoLock
lock(mLock
);
448 // XXX When MakeBackInserter(nsCOMArray<T>&) is added, we can do:
449 // AppendToArray(aListeners, mListeners.Values());
450 for (const auto& listener
: mListeners
.Values()) {
451 aListeners
.AppendObject(listener
);
456 nsConsoleService::LogStringMessage(const char16_t
* aMessage
) {
457 if (!gLoggingEnabled
) {
461 RefPtr
<nsConsoleMessage
> msg(new nsConsoleMessage(
462 aMessage
? nsDependentString(aMessage
) : EmptyString()));
463 return LogMessage(msg
);
467 nsConsoleService::GetMessageArray(
468 nsTArray
<RefPtr
<nsIConsoleMessage
>>& aMessages
) {
469 MOZ_RELEASE_ASSERT(NS_IsMainThread());
471 MutexAutoLock
lock(mLock
);
473 if (mMessages
.isEmpty()) {
477 MOZ_ASSERT(mCurrentSize
<= mMaximumSize
);
478 aMessages
.SetCapacity(mCurrentSize
);
480 for (MessageElement
* e
= mMessages
.getFirst(); e
!= nullptr;
482 aMessages
.AppendElement(e
->Get());
489 nsConsoleService::RegisterListener(nsIConsoleListener
* aListener
) {
490 if (!NS_IsMainThread()) {
491 NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
492 return NS_ERROR_NOT_SAME_THREAD
;
495 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aListener
);
496 MOZ_ASSERT(canonical
);
498 MutexAutoLock
lock(mLock
);
499 return mListeners
.WithEntryHandle(canonical
, [&](auto&& entry
) {
501 // Reregistering a listener isn't good
502 return NS_ERROR_FAILURE
;
504 entry
.Insert(aListener
);
510 nsConsoleService::UnregisterListener(nsIConsoleListener
* aListener
) {
511 if (!NS_IsMainThread()) {
512 NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
513 return NS_ERROR_NOT_SAME_THREAD
;
516 nsCOMPtr
<nsISupports
> canonical
= do_QueryInterface(aListener
);
518 MutexAutoLock
lock(mLock
);
520 return mListeners
.Remove(canonical
)
522 // Unregistering a listener that was never registered?
527 nsConsoleService::Reset() {
528 MOZ_RELEASE_ASSERT(NS_IsMainThread());
531 * Make sure nobody trips into the buffer while it's being reset
533 MutexAutoLock
lock(mLock
);
540 nsConsoleService::ResetWindow(uint64_t windowInnerId
) {
541 MOZ_RELEASE_ASSERT(NS_IsMainThread());
543 ClearMessagesForWindowID(windowInnerId
);
548 nsConsoleService::Observe(nsISupports
* aSubject
, const char* aTopic
,
549 const char16_t
* aData
) {
550 if (!strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
551 // Dump all our messages, in case any are cycle collected.
553 // We could remove ourselves from the observer service, but it is about to
554 // drop all observers anyways, so why bother.
555 } else if (!strcmp(aTopic
, "inner-window-destroyed")) {
556 nsCOMPtr
<nsISupportsPRUint64
> supportsInt
= do_QueryInterface(aSubject
);
557 MOZ_ASSERT(supportsInt
);
560 MOZ_ALWAYS_SUCCEEDS(supportsInt
->GetData(&windowId
));
562 ClearMessagesForWindowID(windowId
);