1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/base/nsConsoleService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,373 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* 1.10 + * Maintains a circular buffer of recent messages, and notifies 1.11 + * listeners when new messages are logged. 1.12 + */ 1.13 + 1.14 +/* Threadsafe. */ 1.15 + 1.16 +#include "nsMemory.h" 1.17 +#include "nsCOMArray.h" 1.18 +#include "nsThreadUtils.h" 1.19 + 1.20 +#include "nsConsoleService.h" 1.21 +#include "nsConsoleMessage.h" 1.22 +#include "nsIClassInfoImpl.h" 1.23 +#include "nsIConsoleListener.h" 1.24 +#include "nsPrintfCString.h" 1.25 + 1.26 +#include "mozilla/Preferences.h" 1.27 + 1.28 +#if defined(ANDROID) 1.29 +#include <android/log.h> 1.30 +#endif 1.31 +#ifdef XP_WIN 1.32 +#include <windows.h> 1.33 +#endif 1.34 + 1.35 +using namespace mozilla; 1.36 + 1.37 +NS_IMPL_ADDREF(nsConsoleService) 1.38 +NS_IMPL_RELEASE(nsConsoleService) 1.39 +NS_IMPL_CLASSINFO(nsConsoleService, nullptr, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_CONSOLESERVICE_CID) 1.40 +NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService) 1.41 +NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService) 1.42 + 1.43 +static bool sLoggingEnabled = true; 1.44 +static bool sLoggingBuffered = true; 1.45 + 1.46 +nsConsoleService::nsConsoleService() 1.47 + : mMessages(nullptr) 1.48 + , mCurrent(0) 1.49 + , mFull(false) 1.50 + , mDeliveringMessage(false) 1.51 + , mLock("nsConsoleService.mLock") 1.52 +{ 1.53 + // XXX grab this from a pref! 1.54 + // hm, but worry about circularity, bc we want to be able to report 1.55 + // prefs errs... 1.56 + mBufferSize = 250; 1.57 +} 1.58 + 1.59 +nsConsoleService::~nsConsoleService() 1.60 +{ 1.61 + uint32_t i = 0; 1.62 + while (i < mBufferSize && mMessages[i] != nullptr) { 1.63 + NS_RELEASE(mMessages[i]); 1.64 + i++; 1.65 + } 1.66 + 1.67 + if (mMessages) 1.68 + nsMemory::Free(mMessages); 1.69 +} 1.70 + 1.71 +class AddConsolePrefWatchers : public nsRunnable 1.72 +{ 1.73 +public: 1.74 + AddConsolePrefWatchers(nsConsoleService* aConsole) : mConsole(aConsole) {} 1.75 + 1.76 + NS_IMETHOD Run() 1.77 + { 1.78 + Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); 1.79 + Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true); 1.80 + if (!sLoggingBuffered) { 1.81 + mConsole->Reset(); 1.82 + } 1.83 + return NS_OK; 1.84 + } 1.85 + 1.86 +private: 1.87 + nsRefPtr<nsConsoleService> mConsole; 1.88 +}; 1.89 + 1.90 +nsresult 1.91 +nsConsoleService::Init() 1.92 +{ 1.93 + mMessages = (nsIConsoleMessage **) 1.94 + nsMemory::Alloc(mBufferSize * sizeof(nsIConsoleMessage *)); 1.95 + if (!mMessages) 1.96 + return NS_ERROR_OUT_OF_MEMORY; 1.97 + 1.98 + // Array elements should be 0 initially for circular buffer algorithm. 1.99 + memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *)); 1.100 + 1.101 + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); 1.102 + 1.103 + return NS_OK; 1.104 +} 1.105 + 1.106 +namespace { 1.107 + 1.108 +class LogMessageRunnable : public nsRunnable 1.109 +{ 1.110 +public: 1.111 + LogMessageRunnable(nsIConsoleMessage* message, nsConsoleService* service) 1.112 + : mMessage(message) 1.113 + , mService(service) 1.114 + { } 1.115 + 1.116 + NS_DECL_NSIRUNNABLE 1.117 + 1.118 +private: 1.119 + nsCOMPtr<nsIConsoleMessage> mMessage; 1.120 + nsRefPtr<nsConsoleService> mService; 1.121 +}; 1.122 + 1.123 +typedef nsCOMArray<nsIConsoleListener> ListenerArrayType; 1.124 + 1.125 +PLDHashOperator 1.126 +CollectCurrentListeners(nsISupports* aKey, nsIConsoleListener* aValue, 1.127 + void* closure) 1.128 +{ 1.129 + ListenerArrayType* listeners = static_cast<ListenerArrayType*>(closure); 1.130 + listeners->AppendObject(aValue); 1.131 + return PL_DHASH_NEXT; 1.132 +} 1.133 + 1.134 +NS_IMETHODIMP 1.135 +LogMessageRunnable::Run() 1.136 +{ 1.137 + MOZ_ASSERT(NS_IsMainThread()); 1.138 + 1.139 + // Snapshot of listeners so that we don't reenter this hash during 1.140 + // enumeration. 1.141 + nsCOMArray<nsIConsoleListener> listeners; 1.142 + mService->EnumerateListeners(CollectCurrentListeners, &listeners); 1.143 + 1.144 + mService->SetIsDelivering(); 1.145 + 1.146 + for (int32_t i = 0; i < listeners.Count(); ++i) 1.147 + listeners[i]->Observe(mMessage); 1.148 + 1.149 + mService->SetDoneDelivering(); 1.150 + 1.151 + return NS_OK; 1.152 +} 1.153 + 1.154 +} // anonymous namespace 1.155 + 1.156 +// nsIConsoleService methods 1.157 +NS_IMETHODIMP 1.158 +nsConsoleService::LogMessage(nsIConsoleMessage *message) 1.159 +{ 1.160 + return LogMessageWithMode(message, OutputToLog); 1.161 +} 1.162 + 1.163 +nsresult 1.164 +nsConsoleService::LogMessageWithMode(nsIConsoleMessage *message, nsConsoleService::OutputMode outputMode) 1.165 +{ 1.166 + if (message == nullptr) 1.167 + return NS_ERROR_INVALID_ARG; 1.168 + 1.169 + if (!sLoggingEnabled) { 1.170 + return NS_OK; 1.171 + } 1.172 + 1.173 + if (NS_IsMainThread() && mDeliveringMessage) { 1.174 + nsCString msg; 1.175 + message->ToString(msg); 1.176 + NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted " 1.177 + "to display a message to the console while in a console listener. " 1.178 + "The following message was discarded: \"%s\"", msg.get()).get()); 1.179 + return NS_ERROR_FAILURE; 1.180 + } 1.181 + 1.182 + nsRefPtr<LogMessageRunnable> r; 1.183 + nsIConsoleMessage *retiredMessage; 1.184 + 1.185 + if (sLoggingBuffered) { 1.186 + NS_ADDREF(message); // early, in case it's same as replaced below. 1.187 + } 1.188 + 1.189 + /* 1.190 + * Lock while updating buffer, and while taking snapshot of 1.191 + * listeners array. 1.192 + */ 1.193 + { 1.194 + MutexAutoLock lock(mLock); 1.195 + 1.196 +#if defined(ANDROID) 1.197 + if (outputMode == OutputToLog) 1.198 + { 1.199 + nsCString msg; 1.200 + message->ToString(msg); 1.201 + __android_log_print(ANDROID_LOG_ERROR, "GeckoConsole", 1.202 + "%s", msg.get()); 1.203 + } 1.204 +#endif 1.205 +#ifdef XP_WIN 1.206 + if (IsDebuggerPresent()) { 1.207 + nsString msg; 1.208 + message->GetMessageMoz(getter_Copies(msg)); 1.209 + msg.AppendLiteral("\n"); 1.210 + OutputDebugStringW(msg.get()); 1.211 + } 1.212 +#endif 1.213 + 1.214 + /* 1.215 + * If there's already a message in the slot we're about to replace, 1.216 + * we've wrapped around, and we need to release the old message. We 1.217 + * save a pointer to it, so we can release below outside the lock. 1.218 + */ 1.219 + retiredMessage = mMessages[mCurrent]; 1.220 + 1.221 + if (sLoggingBuffered) { 1.222 + mMessages[mCurrent++] = message; 1.223 + if (mCurrent == mBufferSize) { 1.224 + mCurrent = 0; // wrap around. 1.225 + mFull = true; 1.226 + } 1.227 + } 1.228 + 1.229 + if (mListeners.Count() > 0) { 1.230 + r = new LogMessageRunnable(message, this); 1.231 + } 1.232 + } 1.233 + 1.234 + if (retiredMessage != nullptr) 1.235 + NS_RELEASE(retiredMessage); 1.236 + 1.237 + if (r) 1.238 + NS_DispatchToMainThread(r); 1.239 + 1.240 + return NS_OK; 1.241 +} 1.242 + 1.243 +void 1.244 +nsConsoleService::EnumerateListeners(ListenerHash::EnumReadFunction aFunction, 1.245 + void* aClosure) 1.246 +{ 1.247 + MutexAutoLock lock(mLock); 1.248 + mListeners.EnumerateRead(aFunction, aClosure); 1.249 +} 1.250 + 1.251 +NS_IMETHODIMP 1.252 +nsConsoleService::LogStringMessage(const char16_t *message) 1.253 +{ 1.254 + if (!sLoggingEnabled) { 1.255 + return NS_OK; 1.256 + } 1.257 + 1.258 + nsRefPtr<nsConsoleMessage> msg(new nsConsoleMessage(message)); 1.259 + return this->LogMessage(msg); 1.260 +} 1.261 + 1.262 +NS_IMETHODIMP 1.263 +nsConsoleService::GetMessageArray(uint32_t *count, nsIConsoleMessage ***messages) 1.264 +{ 1.265 + nsIConsoleMessage **messageArray; 1.266 + 1.267 + /* 1.268 + * Lock the whole method, as we don't want anyone mucking with mCurrent or 1.269 + * mFull while we're copying out the buffer. 1.270 + */ 1.271 + MutexAutoLock lock(mLock); 1.272 + 1.273 + if (mCurrent == 0 && !mFull) { 1.274 + /* 1.275 + * Make a 1-length output array so that nobody gets confused, 1.276 + * and return a count of 0. This should result in a 0-length 1.277 + * array object when called from script. 1.278 + */ 1.279 + messageArray = (nsIConsoleMessage **) 1.280 + nsMemory::Alloc(sizeof (nsIConsoleMessage *)); 1.281 + *messageArray = nullptr; 1.282 + *messages = messageArray; 1.283 + *count = 0; 1.284 + 1.285 + return NS_OK; 1.286 + } 1.287 + 1.288 + uint32_t resultSize = mFull ? mBufferSize : mCurrent; 1.289 + messageArray = 1.290 + (nsIConsoleMessage **)nsMemory::Alloc((sizeof (nsIConsoleMessage *)) 1.291 + * resultSize); 1.292 + 1.293 + if (messageArray == nullptr) { 1.294 + *messages = nullptr; 1.295 + *count = 0; 1.296 + return NS_ERROR_FAILURE; 1.297 + } 1.298 + 1.299 + uint32_t i; 1.300 + if (mFull) { 1.301 + for (i = 0; i < mBufferSize; i++) { 1.302 + // if full, fill the buffer starting from mCurrent (which'll be 1.303 + // oldest) wrapping around the buffer to the most recent. 1.304 + messageArray[i] = mMessages[(mCurrent + i) % mBufferSize]; 1.305 + NS_ADDREF(messageArray[i]); 1.306 + } 1.307 + } else { 1.308 + for (i = 0; i < mCurrent; i++) { 1.309 + messageArray[i] = mMessages[i]; 1.310 + NS_ADDREF(messageArray[i]); 1.311 + } 1.312 + } 1.313 + *count = resultSize; 1.314 + *messages = messageArray; 1.315 + 1.316 + return NS_OK; 1.317 +} 1.318 + 1.319 +NS_IMETHODIMP 1.320 +nsConsoleService::RegisterListener(nsIConsoleListener *listener) 1.321 +{ 1.322 + if (!NS_IsMainThread()) { 1.323 + NS_ERROR("nsConsoleService::RegisterListener is main thread only."); 1.324 + return NS_ERROR_NOT_SAME_THREAD; 1.325 + } 1.326 + 1.327 + nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener); 1.328 + 1.329 + MutexAutoLock lock(mLock); 1.330 + if (mListeners.GetWeak(canonical)) { 1.331 + // Reregistering a listener isn't good 1.332 + return NS_ERROR_FAILURE; 1.333 + } 1.334 + mListeners.Put(canonical, listener); 1.335 + return NS_OK; 1.336 +} 1.337 + 1.338 +NS_IMETHODIMP 1.339 +nsConsoleService::UnregisterListener(nsIConsoleListener *listener) 1.340 +{ 1.341 + if (!NS_IsMainThread()) { 1.342 + NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); 1.343 + return NS_ERROR_NOT_SAME_THREAD; 1.344 + } 1.345 + 1.346 + nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener); 1.347 + 1.348 + MutexAutoLock lock(mLock); 1.349 + 1.350 + if (!mListeners.GetWeak(canonical)) { 1.351 + // Unregistering a listener that was never registered? 1.352 + return NS_ERROR_FAILURE; 1.353 + } 1.354 + mListeners.Remove(canonical); 1.355 + return NS_OK; 1.356 +} 1.357 + 1.358 +NS_IMETHODIMP 1.359 +nsConsoleService::Reset() 1.360 +{ 1.361 + /* 1.362 + * Make sure nobody trips into the buffer while it's being reset 1.363 + */ 1.364 + MutexAutoLock lock(mLock); 1.365 + 1.366 + mCurrent = 0; 1.367 + mFull = false; 1.368 + 1.369 + /* 1.370 + * Free all messages stored so far (cf. destructor) 1.371 + */ 1.372 + for (uint32_t i = 0; i < mBufferSize && mMessages[i] != nullptr; i++) 1.373 + NS_RELEASE(mMessages[i]); 1.374 + 1.375 + return NS_OK; 1.376 +}