diff -r 000000000000 -r 6474c204b198 xpcom/base/nsConsoleService.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xpcom/base/nsConsoleService.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,373 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Maintains a circular buffer of recent messages, and notifies + * listeners when new messages are logged. + */ + +/* Threadsafe. */ + +#include "nsMemory.h" +#include "nsCOMArray.h" +#include "nsThreadUtils.h" + +#include "nsConsoleService.h" +#include "nsConsoleMessage.h" +#include "nsIClassInfoImpl.h" +#include "nsIConsoleListener.h" +#include "nsPrintfCString.h" + +#include "mozilla/Preferences.h" + +#if defined(ANDROID) +#include +#endif +#ifdef XP_WIN +#include +#endif + +using namespace mozilla; + +NS_IMPL_ADDREF(nsConsoleService) +NS_IMPL_RELEASE(nsConsoleService) +NS_IMPL_CLASSINFO(nsConsoleService, nullptr, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_CONSOLESERVICE_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService) +NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService) + +static bool sLoggingEnabled = true; +static bool sLoggingBuffered = true; + +nsConsoleService::nsConsoleService() + : mMessages(nullptr) + , mCurrent(0) + , mFull(false) + , mDeliveringMessage(false) + , mLock("nsConsoleService.mLock") +{ + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mBufferSize = 250; +} + +nsConsoleService::~nsConsoleService() +{ + uint32_t i = 0; + while (i < mBufferSize && mMessages[i] != nullptr) { + NS_RELEASE(mMessages[i]); + i++; + } + + if (mMessages) + nsMemory::Free(mMessages); +} + +class AddConsolePrefWatchers : public nsRunnable +{ +public: + AddConsolePrefWatchers(nsConsoleService* aConsole) : mConsole(aConsole) {} + + NS_IMETHOD Run() + { + Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); + Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true); + if (!sLoggingBuffered) { + mConsole->Reset(); + } + return NS_OK; + } + +private: + nsRefPtr mConsole; +}; + +nsresult +nsConsoleService::Init() +{ + mMessages = (nsIConsoleMessage **) + nsMemory::Alloc(mBufferSize * sizeof(nsIConsoleMessage *)); + if (!mMessages) + return NS_ERROR_OUT_OF_MEMORY; + + // Array elements should be 0 initially for circular buffer algorithm. + memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *)); + + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); + + return NS_OK; +} + +namespace { + +class LogMessageRunnable : public nsRunnable +{ +public: + LogMessageRunnable(nsIConsoleMessage* message, nsConsoleService* service) + : mMessage(message) + , mService(service) + { } + + NS_DECL_NSIRUNNABLE + +private: + nsCOMPtr mMessage; + nsRefPtr mService; +}; + +typedef nsCOMArray ListenerArrayType; + +PLDHashOperator +CollectCurrentListeners(nsISupports* aKey, nsIConsoleListener* aValue, + void* closure) +{ + ListenerArrayType* listeners = static_cast(closure); + listeners->AppendObject(aValue); + return PL_DHASH_NEXT; +} + +NS_IMETHODIMP +LogMessageRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Snapshot of listeners so that we don't reenter this hash during + // enumeration. + nsCOMArray listeners; + mService->EnumerateListeners(CollectCurrentListeners, &listeners); + + mService->SetIsDelivering(); + + for (int32_t i = 0; i < listeners.Count(); ++i) + listeners[i]->Observe(mMessage); + + mService->SetDoneDelivering(); + + return NS_OK; +} + +} // anonymous namespace + +// nsIConsoleService methods +NS_IMETHODIMP +nsConsoleService::LogMessage(nsIConsoleMessage *message) +{ + return LogMessageWithMode(message, OutputToLog); +} + +nsresult +nsConsoleService::LogMessageWithMode(nsIConsoleMessage *message, nsConsoleService::OutputMode outputMode) +{ + if (message == nullptr) + return NS_ERROR_INVALID_ARG; + + if (!sLoggingEnabled) { + return NS_OK; + } + + if (NS_IsMainThread() && mDeliveringMessage) { + nsCString msg; + message->ToString(msg); + NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted " + "to display a message to the console while in a console listener. " + "The following message was discarded: \"%s\"", msg.get()).get()); + return NS_ERROR_FAILURE; + } + + nsRefPtr r; + nsIConsoleMessage *retiredMessage; + + if (sLoggingBuffered) { + NS_ADDREF(message); // early, in case it's same as replaced below. + } + + /* + * Lock while updating buffer, and while taking snapshot of + * listeners array. + */ + { + MutexAutoLock lock(mLock); + +#if defined(ANDROID) + if (outputMode == OutputToLog) + { + nsCString msg; + message->ToString(msg); + __android_log_print(ANDROID_LOG_ERROR, "GeckoConsole", + "%s", msg.get()); + } +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsString msg; + message->GetMessageMoz(getter_Copies(msg)); + msg.AppendLiteral("\n"); + OutputDebugStringW(msg.get()); + } +#endif + + /* + * If there's already a message in the slot we're about to replace, + * we've wrapped around, and we need to release the old message. We + * save a pointer to it, so we can release below outside the lock. + */ + retiredMessage = mMessages[mCurrent]; + + if (sLoggingBuffered) { + mMessages[mCurrent++] = message; + if (mCurrent == mBufferSize) { + mCurrent = 0; // wrap around. + mFull = true; + } + } + + if (mListeners.Count() > 0) { + r = new LogMessageRunnable(message, this); + } + } + + if (retiredMessage != nullptr) + NS_RELEASE(retiredMessage); + + if (r) + NS_DispatchToMainThread(r); + + return NS_OK; +} + +void +nsConsoleService::EnumerateListeners(ListenerHash::EnumReadFunction aFunction, + void* aClosure) +{ + MutexAutoLock lock(mLock); + mListeners.EnumerateRead(aFunction, aClosure); +} + +NS_IMETHODIMP +nsConsoleService::LogStringMessage(const char16_t *message) +{ + if (!sLoggingEnabled) { + return NS_OK; + } + + nsRefPtr msg(new nsConsoleMessage(message)); + return this->LogMessage(msg); +} + +NS_IMETHODIMP +nsConsoleService::GetMessageArray(uint32_t *count, nsIConsoleMessage ***messages) +{ + nsIConsoleMessage **messageArray; + + /* + * Lock the whole method, as we don't want anyone mucking with mCurrent or + * mFull while we're copying out the buffer. + */ + MutexAutoLock lock(mLock); + + if (mCurrent == 0 && !mFull) { + /* + * Make a 1-length output array so that nobody gets confused, + * and return a count of 0. This should result in a 0-length + * array object when called from script. + */ + messageArray = (nsIConsoleMessage **) + nsMemory::Alloc(sizeof (nsIConsoleMessage *)); + *messageArray = nullptr; + *messages = messageArray; + *count = 0; + + return NS_OK; + } + + uint32_t resultSize = mFull ? mBufferSize : mCurrent; + messageArray = + (nsIConsoleMessage **)nsMemory::Alloc((sizeof (nsIConsoleMessage *)) + * resultSize); + + if (messageArray == nullptr) { + *messages = nullptr; + *count = 0; + return NS_ERROR_FAILURE; + } + + uint32_t i; + if (mFull) { + for (i = 0; i < mBufferSize; i++) { + // if full, fill the buffer starting from mCurrent (which'll be + // oldest) wrapping around the buffer to the most recent. + messageArray[i] = mMessages[(mCurrent + i) % mBufferSize]; + NS_ADDREF(messageArray[i]); + } + } else { + for (i = 0; i < mCurrent; i++) { + messageArray[i] = mMessages[i]; + NS_ADDREF(messageArray[i]); + } + } + *count = resultSize; + *messages = messageArray; + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::RegisterListener(nsIConsoleListener *listener) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::RegisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr canonical = do_QueryInterface(listener); + + MutexAutoLock lock(mLock); + if (mListeners.GetWeak(canonical)) { + // Reregistering a listener isn't good + return NS_ERROR_FAILURE; + } + mListeners.Put(canonical, listener); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::UnregisterListener(nsIConsoleListener *listener) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr canonical = do_QueryInterface(listener); + + MutexAutoLock lock(mLock); + + if (!mListeners.GetWeak(canonical)) { + // Unregistering a listener that was never registered? + return NS_ERROR_FAILURE; + } + mListeners.Remove(canonical); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::Reset() +{ + /* + * Make sure nobody trips into the buffer while it's being reset + */ + MutexAutoLock lock(mLock); + + mCurrent = 0; + mFull = false; + + /* + * Free all messages stored so far (cf. destructor) + */ + for (uint32_t i = 0; i < mBufferSize && mMessages[i] != nullptr; i++) + NS_RELEASE(mMessages[i]); + + return NS_OK; +}