Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / base / nsConsoleService.cpp
blob06045176e40b02bbaf8f2638011ae13b936fcf32
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/. */
7 /*
8 * Maintains a circular buffer of recent messages, and notifies
9 * listeners when new messages are logged.
12 /* Threadsafe. */
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"
35 #if defined(ANDROID)
36 # include <android/log.h>
37 # include "mozilla/dom/ContentChild.h"
38 # include "mozilla/StaticPrefs_consoleservice.h"
39 #endif
40 #ifdef XP_WIN
41 # include <windows.h>
42 #endif
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;
56 #ifdef XP_WIN
57 static bool gLoggingToDebugger = true;
58 #endif // XP_WIN
60 nsConsoleService::MessageElement::~MessageElement() = default;
62 nsConsoleService::nsConsoleService()
63 : mCurrentSize(0),
64 // XXX grab this from a pref!
65 // hm, but worry about circularity, bc we want to be able to report
66 // prefs errs...
67 mMaximumSize(250),
68 mDeliveringMessage(false),
69 mLock("nsConsoleService.mLock") {
70 #ifdef XP_WIN
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");
80 gLoggingToDebugger =
81 !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0');
82 #endif // XP_WIN
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
91 // inner window ID.
92 nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
93 if (!scriptError) {
94 e = e->getNext();
95 continue;
97 uint64_t innerWindowID;
98 nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
99 if (NS_FAILED(rv) || innerWindowID != innerID) {
100 e = e->getNext();
101 continue;
104 MessageElement* next = e->getNext();
105 e->remove();
106 delete e;
107 mCurrentSize--;
108 MOZ_ASSERT(mCurrentSize < mMaximumSize);
110 e = next;
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();
119 delete e;
121 mCurrentSize = 0;
124 nsConsoleService::~nsConsoleService() {
125 MOZ_RELEASE_ASSERT(NS_IsMainThread());
127 ClearMessages();
130 class AddConsolePrefWatchers : public Runnable {
131 public:
132 explicit AddConsolePrefWatchers(nsConsoleService* aConsole)
133 : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole) {}
135 NS_IMETHOD Run() override {
136 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
137 MOZ_ASSERT(obs);
138 obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
139 obs->AddObserver(mConsole, "inner-window-destroyed", false);
141 if (!gLoggingBuffered) {
142 mConsole->Reset();
144 return NS_OK;
147 private:
148 RefPtr<nsConsoleService> mConsole;
151 nsresult nsConsoleService::Init() {
152 NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
154 return NS_OK;
157 nsresult nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage* aMessage,
158 bool* sent) {
159 *sent = false;
161 nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage);
162 if (!scriptError) {
163 // Not an nsIScriptError
164 return NS_OK;
167 uint64_t windowID;
168 nsresult rv;
169 rv = scriptError->GetInnerWindowID(&windowID);
170 NS_ENSURE_SUCCESS(rv, rv);
171 if (!windowID) {
172 // Does not set window id
173 return NS_OK;
176 RefPtr<mozilla::dom::WindowGlobalParent> windowGlobalParent =
177 mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID);
178 if (!windowGlobalParent) {
179 // Could not find parent window by id
180 return NS_OK;
183 RefPtr<mozilla::dom::BrowserParent> browserParent =
184 windowGlobalParent->GetBrowserParent();
185 if (!browserParent) {
186 return NS_OK;
189 mozilla::dom::ContentParent* contentParent = browserParent->Manager();
190 if (!contentParent) {
191 return NS_ERROR_FAILURE;
194 nsAutoString msg;
195 nsAutoCString sourceName;
196 nsCString category;
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);
224 return NS_OK;
227 namespace {
229 class LogMessageRunnable : public Runnable {
230 public:
231 LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService)
232 : mozilla::Runnable("LogMessageRunnable"),
233 mMessage(aMessage),
234 mService(aService) {}
236 NS_DECL_NSIRUNNABLE
238 private:
239 nsCOMPtr<nsIConsoleMessage> mMessage;
240 RefPtr<nsConsoleService> mService;
243 NS_IMETHODIMP
244 LogMessageRunnable::Run() {
245 // Snapshot of listeners so that we don't reenter this hash during
246 // enumeration.
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();
258 return NS_OK;
261 } // namespace
263 // nsIConsoleService methods
264 NS_IMETHODIMP
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) {
272 if (!aMessage) {
273 return NS_ERROR_INVALID_ARG;
276 if (!gLoggingEnabled) {
277 return NS_OK;
280 if (NS_IsMainThread() && mDeliveringMessage) {
281 nsCString msg;
282 aMessage->ToString(msg);
283 NS_WARNING(
284 nsPrintfCString(
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\"",
288 msg.get())
289 .get());
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
297 bool sent;
298 nsresult rv = MaybeForwardScriptError(aMessage, &sent);
299 NS_ENSURE_SUCCESS(rv, rv);
300 if (sent) {
301 return NS_OK;
305 RefPtr<LogMessageRunnable> r;
306 nsCOMPtr<nsIConsoleMessage> retiredMessage;
309 * Lock while updating buffer, and while taking snapshot of
310 * listeners array.
313 MutexAutoLock lock(mLock);
315 #if defined(ANDROID)
316 if (StaticPrefs::consoleservice_logcat() && aOutputMode == OutputToLog) {
317 nsCString msg;
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();
323 nsCString appName;
324 if (child) {
325 child->GetProcessName(appName);
326 } else {
327 appName = "GeckoConsole";
330 uint32_t logLevel = 0;
331 aMessage->GetLogLevel(&logLevel);
333 android_LogPriority logPriority = ANDROID_LOG_INFO;
334 switch (logLevel) {
335 case nsIConsoleMessage::debug:
336 logPriority = ANDROID_LOG_DEBUG;
337 break;
338 case nsIConsoleMessage::info:
339 logPriority = ANDROID_LOG_INFO;
340 break;
341 case nsIConsoleMessage::warn:
342 logPriority = ANDROID_LOG_WARN;
343 break;
344 case nsIConsoleMessage::error:
345 logPriority = ANDROID_LOG_ERROR;
346 break;
349 __android_log_print(logPriority, appName.get(), "%s", msg.get());
351 #endif
352 #ifdef XP_WIN
353 if (gLoggingToDebugger && IsDebuggerPresent()) {
354 nsString msg;
355 aMessage->GetMessageMoz(msg);
356 msg.Append('\n');
357 OutputDebugStringW(msg.get());
359 #endif
361 if (gLoggingBuffered) {
362 MessageElement* e = new MessageElement(aMessage);
363 mMessages.insertBack(e);
364 if (mCurrentSize != mMaximumSize) {
365 mCurrentSize++;
366 } else {
367 MessageElement* p = mMessages.popFirst();
368 MOZ_ASSERT(p);
369 p->swapMessage(retiredMessage);
370 delete p;
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
382 // main thread.
383 NS_ReleaseOnMainThread("nsConsoleService::retiredMessage",
384 retiredMessage.forget());
387 if (r) {
388 // avoid failing in XPCShell tests
389 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
390 if (mainThread) {
391 SchedulerGroup::Dispatch(r.forget());
395 return NS_OK;
398 // See nsIConsoleService.idl for more info about this method
399 NS_IMETHODIMP
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));
408 if (!contextRealm) {
409 return NS_ERROR_INVALID_ARG;
412 JS::Rooted<JSObject*> global(
413 cx, js::CheckedUnwrapDynamic(&targetGlobal.toObject(), cx));
414 if (!global) {
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(),
438 retval)) {
439 return NS_ERROR_XPC_JAVASCRIPT_ERROR;
442 return NS_OK;
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);
455 NS_IMETHODIMP
456 nsConsoleService::LogStringMessage(const char16_t* aMessage) {
457 if (!gLoggingEnabled) {
458 return NS_OK;
461 RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(
462 aMessage ? nsDependentString(aMessage) : EmptyString()));
463 return LogMessage(msg);
466 NS_IMETHODIMP
467 nsConsoleService::GetMessageArray(
468 nsTArray<RefPtr<nsIConsoleMessage>>& aMessages) {
469 MOZ_RELEASE_ASSERT(NS_IsMainThread());
471 MutexAutoLock lock(mLock);
473 if (mMessages.isEmpty()) {
474 return NS_OK;
477 MOZ_ASSERT(mCurrentSize <= mMaximumSize);
478 aMessages.SetCapacity(mCurrentSize);
480 for (MessageElement* e = mMessages.getFirst(); e != nullptr;
481 e = e->getNext()) {
482 aMessages.AppendElement(e->Get());
485 return NS_OK;
488 NS_IMETHODIMP
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) {
500 if (entry) {
501 // Reregistering a listener isn't good
502 return NS_ERROR_FAILURE;
504 entry.Insert(aListener);
505 return NS_OK;
509 NS_IMETHODIMP
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)
521 ? NS_OK
522 // Unregistering a listener that was never registered?
523 : NS_ERROR_FAILURE;
526 NS_IMETHODIMP
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);
535 ClearMessages();
536 return NS_OK;
539 NS_IMETHODIMP
540 nsConsoleService::ResetWindow(uint64_t windowInnerId) {
541 MOZ_RELEASE_ASSERT(NS_IsMainThread());
543 ClearMessagesForWindowID(windowInnerId);
544 return NS_OK;
547 NS_IMETHODIMP
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.
552 Reset();
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);
559 uint64_t windowId;
560 MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));
562 ClearMessagesForWindowID(windowId);
563 } else {
564 MOZ_CRASH();
566 return NS_OK;