xpcom/base/nsConsoleService.cpp

changeset 0
6474c204b198
     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 +}

mercurial