LCOV - code coverage report
Current view: top level - xpcom/base - nsConsoleService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 71 173 41.0 %
Date: 2017-07-14 16:53:18 Functions: 14 27 51.9 %
Legend: Lines: hit not hit

          Line data    Source code
       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/. */
       6             : 
       7             : /*
       8             :  * Maintains a circular buffer of recent messages, and notifies
       9             :  * listeners when new messages are logged.
      10             :  */
      11             : 
      12             : /* Threadsafe. */
      13             : 
      14             : #include "nsMemory.h"
      15             : #include "nsCOMArray.h"
      16             : #include "nsThreadUtils.h"
      17             : 
      18             : #include "nsConsoleService.h"
      19             : #include "nsConsoleMessage.h"
      20             : #include "nsIClassInfoImpl.h"
      21             : #include "nsIConsoleListener.h"
      22             : #include "nsIObserverService.h"
      23             : #include "nsPrintfCString.h"
      24             : #include "nsProxyRelease.h"
      25             : #include "nsIScriptError.h"
      26             : #include "nsISupportsPrimitives.h"
      27             : 
      28             : #include "mozilla/Preferences.h"
      29             : #include "mozilla/Services.h"
      30             : #include "mozilla/SystemGroup.h"
      31             : 
      32             : #if defined(ANDROID)
      33             : #include <android/log.h>
      34             : #include "mozilla/dom/ContentChild.h"
      35             : #endif
      36             : #ifdef XP_WIN
      37             : #include <windows.h>
      38             : #endif
      39             : 
      40             : #ifdef MOZ_TASK_TRACER
      41             : #include "GeckoTaskTracer.h"
      42             : using namespace mozilla::tasktracer;
      43             : #endif
      44             : 
      45             : using namespace mozilla;
      46             : 
      47          30 : NS_IMPL_ADDREF(nsConsoleService)
      48          19 : NS_IMPL_RELEASE(nsConsoleService)
      49           3 : NS_IMPL_CLASSINFO(nsConsoleService, nullptr,
      50             :                   nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
      51             :                   NS_CONSOLESERVICE_CID)
      52           9 : NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver)
      53           0 : NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver)
      54             : 
      55             : static bool sLoggingEnabled = true;
      56             : static bool sLoggingBuffered = true;
      57             : #if defined(ANDROID)
      58             : static bool sLoggingLogcat = true;
      59             : #endif // defined(ANDROID)
      60             : 
      61           0 : nsConsoleService::MessageElement::~MessageElement()
      62             : {
      63           0 : }
      64             : 
      65           3 : nsConsoleService::nsConsoleService()
      66             :   : mCurrentSize(0)
      67             :   , mDeliveringMessage(false)
      68           3 :   , mLock("nsConsoleService.mLock")
      69             : {
      70             :   // XXX grab this from a pref!
      71             :   // hm, but worry about circularity, bc we want to be able to report
      72             :   // prefs errs...
      73           3 :   mMaximumSize = 250;
      74           3 : }
      75             : 
      76             : 
      77             : void
      78           3 : nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID)
      79             : {
      80           3 :   MOZ_RELEASE_ASSERT(NS_IsMainThread());
      81           6 :   MutexAutoLock lock(mLock);
      82             : 
      83           6 :   for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) {
      84             :     // Only messages implementing nsIScriptError interface expose the
      85             :     // inner window ID.
      86           3 :     nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
      87           3 :     if (!scriptError) {
      88           2 :       e = e->getNext();
      89           2 :       continue;
      90             :     }
      91             :     uint64_t innerWindowID;
      92           1 :     nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
      93           1 :     if (NS_FAILED(rv) || innerWindowID != innerID) {
      94           1 :       e = e->getNext();
      95           1 :       continue;
      96             :     }
      97             : 
      98           0 :     MessageElement* next = e->getNext();
      99           0 :     e->remove();
     100           0 :     delete e;
     101           0 :     mCurrentSize--;
     102           0 :     MOZ_ASSERT(mCurrentSize < mMaximumSize);
     103             : 
     104           0 :     e = next;
     105             :   }
     106           3 : }
     107             : 
     108             : void
     109           0 : nsConsoleService::ClearMessages()
     110             : {
     111             :   // NB: A lock is not required here as it's only called from |Reset| which
     112             :   //     locks for us and from the dtor.
     113           0 :   while (!mMessages.isEmpty()) {
     114           0 :     MessageElement* e = mMessages.popFirst();
     115           0 :     delete e;
     116             :   }
     117           0 :   mCurrentSize = 0;
     118           0 : }
     119             : 
     120           0 : nsConsoleService::~nsConsoleService()
     121             : {
     122           0 :   MOZ_RELEASE_ASSERT(NS_IsMainThread());
     123             : 
     124           0 :   ClearMessages();
     125           0 : }
     126             : 
     127           9 : class AddConsolePrefWatchers : public Runnable
     128             : {
     129             : public:
     130           3 :   explicit AddConsolePrefWatchers(nsConsoleService* aConsole)
     131           3 :     : mozilla::Runnable("AddConsolePrefWatchers")
     132           3 :     , mConsole(aConsole)
     133             :   {
     134           3 :   }
     135             : 
     136           3 :   NS_IMETHOD Run() override
     137             :   {
     138           3 :     Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true);
     139           3 :     Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true);
     140             : #if defined(ANDROID)
     141             :     Preferences::AddBoolVarCache(&sLoggingLogcat, "consoleservice.logcat", true);
     142             : #endif // defined(ANDROID)
     143             : 
     144           6 :     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     145           3 :     MOZ_ASSERT(obs);
     146           3 :     obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
     147           3 :     obs->AddObserver(mConsole, "inner-window-destroyed", false);
     148             : 
     149           3 :     if (!sLoggingBuffered) {
     150           0 :       mConsole->Reset();
     151             :     }
     152           6 :     return NS_OK;
     153             :   }
     154             : 
     155             : private:
     156             :   RefPtr<nsConsoleService> mConsole;
     157             : };
     158             : 
     159             : nsresult
     160           3 : nsConsoleService::Init()
     161             : {
     162           3 :   NS_DispatchToMainThread(new AddConsolePrefWatchers(this));
     163             : 
     164           3 :   return NS_OK;
     165             : }
     166             : 
     167             : namespace {
     168             : 
     169           0 : class LogMessageRunnable : public Runnable
     170             : {
     171             : public:
     172           0 :   LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService)
     173           0 :     : mozilla::Runnable("LogMessageRunnable")
     174             :     , mMessage(aMessage)
     175           0 :     , mService(aService)
     176           0 :   { }
     177             : 
     178             :   NS_DECL_NSIRUNNABLE
     179             : 
     180             : private:
     181             :   nsCOMPtr<nsIConsoleMessage> mMessage;
     182             :   RefPtr<nsConsoleService> mService;
     183             : };
     184             : 
     185             : NS_IMETHODIMP
     186           0 : LogMessageRunnable::Run()
     187             : {
     188           0 :   MOZ_ASSERT(NS_IsMainThread());
     189             : 
     190             :   // Snapshot of listeners so that we don't reenter this hash during
     191             :   // enumeration.
     192           0 :   nsCOMArray<nsIConsoleListener> listeners;
     193           0 :   mService->CollectCurrentListeners(listeners);
     194             : 
     195           0 :   mService->SetIsDelivering();
     196             : 
     197           0 :   for (int32_t i = 0; i < listeners.Count(); ++i) {
     198           0 :     listeners[i]->Observe(mMessage);
     199             :   }
     200             : 
     201           0 :   mService->SetDoneDelivering();
     202             : 
     203           0 :   return NS_OK;
     204             : }
     205             : 
     206             : } // namespace
     207             : 
     208             : // nsIConsoleService methods
     209             : NS_IMETHODIMP
     210           3 : nsConsoleService::LogMessage(nsIConsoleMessage* aMessage)
     211             : {
     212           3 :   return LogMessageWithMode(aMessage, OutputToLog);
     213             : }
     214             : 
     215             : // This can be called off the main thread.
     216             : nsresult
     217           3 : nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage,
     218             :                                      nsConsoleService::OutputMode aOutputMode)
     219             : {
     220           3 :   if (!aMessage) {
     221           0 :     return NS_ERROR_INVALID_ARG;
     222             :   }
     223             : 
     224           3 :   if (!sLoggingEnabled) {
     225           0 :     return NS_OK;
     226             :   }
     227             : 
     228           3 :   if (NS_IsMainThread() && mDeliveringMessage) {
     229           0 :     nsCString msg;
     230           0 :     aMessage->ToString(msg);
     231           0 :     NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted "
     232             :       "to display a message to the console while in a console listener. "
     233           0 :       "The following message was discarded: \"%s\"", msg.get()).get());
     234           0 :     return NS_ERROR_FAILURE;
     235             :   }
     236             : 
     237           6 :   RefPtr<LogMessageRunnable> r;
     238           6 :   nsCOMPtr<nsIConsoleMessage> retiredMessage;
     239             : 
     240             :   /*
     241             :    * Lock while updating buffer, and while taking snapshot of
     242             :    * listeners array.
     243             :    */
     244             :   {
     245           6 :     MutexAutoLock lock(mLock);
     246             : 
     247             : #if defined(ANDROID)
     248             :     if (sLoggingLogcat && aOutputMode == OutputToLog) {
     249             :       nsCString msg;
     250             :       aMessage->ToString(msg);
     251             : 
     252             :       /** Attempt to use the process name as the log tag. */
     253             :       mozilla::dom::ContentChild* child =
     254             :           mozilla::dom::ContentChild::GetSingleton();
     255             :       nsCString appName;
     256             :       if (child) {
     257             :         child->GetProcessName(appName);
     258             :       } else {
     259             :         appName = "GeckoConsole";
     260             :       }
     261             : 
     262             :       uint32_t logLevel = 0;
     263             :       aMessage->GetLogLevel(&logLevel);
     264             : 
     265             :       android_LogPriority logPriority = ANDROID_LOG_INFO;
     266             :       switch (logLevel) {
     267             :         case nsIConsoleMessage::debug:
     268             :           logPriority = ANDROID_LOG_DEBUG;
     269             :           break;
     270             :         case nsIConsoleMessage::info:
     271             :           logPriority = ANDROID_LOG_INFO;
     272             :           break;
     273             :         case nsIConsoleMessage::warn:
     274             :           logPriority = ANDROID_LOG_WARN;
     275             :           break;
     276             :         case nsIConsoleMessage::error:
     277             :           logPriority = ANDROID_LOG_ERROR;
     278             :           break;
     279             :       }
     280             : 
     281             :       __android_log_print(logPriority, appName.get(), "%s", msg.get());
     282             :     }
     283             : #endif
     284             : #ifdef XP_WIN
     285             :     if (IsDebuggerPresent()) {
     286             :       nsString msg;
     287             :       aMessage->GetMessageMoz(getter_Copies(msg));
     288             :       msg.Append('\n');
     289             :       OutputDebugStringW(msg.get());
     290             :     }
     291             : #endif
     292             : #ifdef MOZ_TASK_TRACER
     293             :     if (IsStartLogging()) {
     294             :       nsCString msg;
     295             :       aMessage->ToString(msg);
     296             :       int prefixPos = msg.Find(GetJSLabelPrefix());
     297             :       if (prefixPos >= 0) {
     298             :         nsDependentCSubstring submsg(msg, prefixPos);
     299             :         AddLabel("%s", submsg.BeginReading());
     300             :       }
     301             :     }
     302             : #endif
     303             : 
     304           3 :     if (sLoggingBuffered) {
     305           3 :       MessageElement* e = new MessageElement(aMessage);
     306           3 :       mMessages.insertBack(e);
     307           3 :       if (mCurrentSize != mMaximumSize) {
     308           3 :         mCurrentSize++;
     309             :       } else {
     310           0 :         MessageElement* p = mMessages.popFirst();
     311           0 :         MOZ_ASSERT(p);
     312           0 :         p->swapMessage(retiredMessage);
     313           0 :         delete p;
     314             :       }
     315             :     }
     316             : 
     317           3 :     if (mListeners.Count() > 0) {
     318           0 :       r = new LogMessageRunnable(aMessage, this);
     319             :     }
     320             :   }
     321             : 
     322           3 :   if (retiredMessage) {
     323             :     // Release |retiredMessage| on the main thread in case it is an instance of
     324             :     // a mainthread-only class like nsScriptErrorWithStack and we're off the
     325             :     // main thread.
     326             :     NS_ReleaseOnMainThread(
     327           0 :       "nsConsoleService::retiredMessage", retiredMessage.forget());
     328             :   }
     329             : 
     330           3 :   if (r) {
     331             :     // avoid failing in XPCShell tests
     332           0 :     nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
     333           0 :     if (mainThread) {
     334           0 :       SystemGroup::Dispatch("LogMessageRunnable", TaskCategory::Other, r.forget());
     335             :     }
     336             :   }
     337             : 
     338           3 :   return NS_OK;
     339             : }
     340             : 
     341             : void
     342           0 : nsConsoleService::CollectCurrentListeners(
     343             :   nsCOMArray<nsIConsoleListener>& aListeners)
     344             : {
     345           0 :   MutexAutoLock lock(mLock);
     346           0 :   for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) {
     347           0 :     nsIConsoleListener* value = iter.UserData();
     348           0 :     aListeners.AppendObject(value);
     349             :   }
     350           0 : }
     351             : 
     352             : NS_IMETHODIMP
     353           0 : nsConsoleService::LogStringMessage(const char16_t* aMessage)
     354             : {
     355           0 :   if (!sLoggingEnabled) {
     356           0 :     return NS_OK;
     357             :   }
     358             : 
     359           0 :   RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage));
     360           0 :   return this->LogMessage(msg);
     361             : }
     362             : 
     363             : NS_IMETHODIMP
     364           0 : nsConsoleService::GetMessageArray(uint32_t* aCount,
     365             :                                   nsIConsoleMessage*** aMessages)
     366             : {
     367           0 :   MOZ_RELEASE_ASSERT(NS_IsMainThread());
     368             : 
     369           0 :   MutexAutoLock lock(mLock);
     370             : 
     371           0 :   if (mMessages.isEmpty()) {
     372             :     /*
     373             :      * Make a 1-length output array so that nobody gets confused,
     374             :      * and return a count of 0.  This should result in a 0-length
     375             :      * array object when called from script.
     376             :      */
     377             :     nsIConsoleMessage** messageArray = (nsIConsoleMessage**)
     378           0 :       moz_xmalloc(sizeof(nsIConsoleMessage*));
     379           0 :     *messageArray = nullptr;
     380           0 :     *aMessages = messageArray;
     381           0 :     *aCount = 0;
     382             : 
     383           0 :     return NS_OK;
     384             :   }
     385             : 
     386           0 :   MOZ_ASSERT(mCurrentSize <= mMaximumSize);
     387             :   nsIConsoleMessage** messageArray =
     388           0 :     static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*)
     389           0 :                                                  * mCurrentSize));
     390             : 
     391           0 :   uint32_t i = 0;
     392           0 :   for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) {
     393           0 :     nsCOMPtr<nsIConsoleMessage> m = e->Get();
     394           0 :     m.forget(&messageArray[i]);
     395           0 :     i++;
     396             :   }
     397             : 
     398           0 :   MOZ_ASSERT(i == mCurrentSize);
     399             : 
     400           0 :   *aCount = i;
     401           0 :   *aMessages = messageArray;
     402             : 
     403           0 :   return NS_OK;
     404             : }
     405             : 
     406             : NS_IMETHODIMP
     407           2 : nsConsoleService::RegisterListener(nsIConsoleListener* aListener)
     408             : {
     409           2 :   if (!NS_IsMainThread()) {
     410           0 :     NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
     411           0 :     return NS_ERROR_NOT_SAME_THREAD;
     412             :   }
     413             : 
     414           4 :   nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
     415             : 
     416           4 :   MutexAutoLock lock(mLock);
     417           2 :   if (mListeners.GetWeak(canonical)) {
     418             :     // Reregistering a listener isn't good
     419           0 :     return NS_ERROR_FAILURE;
     420             :   }
     421           2 :   mListeners.Put(canonical, aListener);
     422           2 :   return NS_OK;
     423             : }
     424             : 
     425             : NS_IMETHODIMP
     426           0 : nsConsoleService::UnregisterListener(nsIConsoleListener* aListener)
     427             : {
     428           0 :   if (!NS_IsMainThread()) {
     429           0 :     NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
     430           0 :     return NS_ERROR_NOT_SAME_THREAD;
     431             :   }
     432             : 
     433           0 :   nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
     434             : 
     435           0 :   MutexAutoLock lock(mLock);
     436             : 
     437           0 :   if (!mListeners.GetWeak(canonical)) {
     438             :     // Unregistering a listener that was never registered?
     439           0 :     return NS_ERROR_FAILURE;
     440             :   }
     441           0 :   mListeners.Remove(canonical);
     442           0 :   return NS_OK;
     443             : }
     444             : 
     445             : NS_IMETHODIMP
     446           0 : nsConsoleService::Reset()
     447             : {
     448           0 :   MOZ_RELEASE_ASSERT(NS_IsMainThread());
     449             : 
     450             :   /*
     451             :    * Make sure nobody trips into the buffer while it's being reset
     452             :    */
     453           0 :   MutexAutoLock lock(mLock);
     454             : 
     455           0 :   ClearMessages();
     456           0 :   return NS_OK;
     457             : }
     458             : 
     459             : NS_IMETHODIMP
     460           3 : nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic,
     461             :                           const char16_t* aData)
     462             : {
     463           3 :   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     464             :     // Dump all our messages, in case any are cycle collected.
     465           0 :     Reset();
     466             :     // We could remove ourselves from the observer service, but it is about to
     467             :     // drop all observers anyways, so why bother.
     468           3 :   } else if (!strcmp(aTopic, "inner-window-destroyed")) {
     469           6 :     nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
     470           3 :     MOZ_ASSERT(supportsInt);
     471             : 
     472             :     uint64_t windowId;
     473           3 :     MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));
     474             : 
     475           3 :     ClearMessagesForWindowID(windowId);
     476             :   } else {
     477           0 :     MOZ_CRASH();
     478             :   }
     479           3 :   return NS_OK;
     480             : }

Generated by: LCOV version 1.13