1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Simon Bünzli <zeniko@gmail.com>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /*
40 : * Maintains a circular buffer of recent messages, and notifies
41 : * listeners when new messages are logged.
42 : */
43 :
44 : /* Threadsafe. */
45 :
46 : #include "nsMemory.h"
47 : #include "nsIServiceManager.h"
48 : #include "nsCOMArray.h"
49 : #include "nsThreadUtils.h"
50 :
51 : #include "nsConsoleService.h"
52 : #include "nsConsoleMessage.h"
53 : #include "nsIClassInfoImpl.h"
54 :
55 : #if defined(ANDROID)
56 : #include <android/log.h>
57 : #endif
58 :
59 : using namespace mozilla;
60 :
61 32331 : NS_IMPL_THREADSAFE_ADDREF(nsConsoleService)
62 33721 : NS_IMPL_THREADSAFE_RELEASE(nsConsoleService)
63 : NS_IMPL_CLASSINFO(nsConsoleService, NULL, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_CONSOLESERVICE_CID)
64 8738 : NS_IMPL_QUERY_INTERFACE1_CI(nsConsoleService, nsIConsoleService)
65 371 : NS_IMPL_CI_INTERFACE_GETTER1(nsConsoleService, nsIConsoleService)
66 :
67 1390 : nsConsoleService::nsConsoleService()
68 : : mMessages(nsnull)
69 : , mCurrent(0)
70 : , mFull(false)
71 : , mDeliveringMessage(false)
72 1390 : , mLock("nsConsoleService.mLock")
73 : {
74 : // XXX grab this from a pref!
75 : // hm, but worry about circularity, bc we want to be able to report
76 : // prefs errs...
77 1390 : mBufferSize = 250;
78 1390 : }
79 :
80 2780 : nsConsoleService::~nsConsoleService()
81 : {
82 1390 : PRUint32 i = 0;
83 19604 : while (i < mBufferSize && mMessages[i] != nsnull) {
84 16824 : NS_RELEASE(mMessages[i]);
85 16824 : i++;
86 : }
87 :
88 1390 : if (mMessages)
89 1390 : nsMemory::Free(mMessages);
90 1390 : }
91 :
92 : nsresult
93 1390 : nsConsoleService::Init()
94 : {
95 : mMessages = (nsIConsoleMessage **)
96 1390 : nsMemory::Alloc(mBufferSize * sizeof(nsIConsoleMessage *));
97 1390 : if (!mMessages)
98 0 : return NS_ERROR_OUT_OF_MEMORY;
99 :
100 : // Array elements should be 0 initially for circular buffer algorithm.
101 1390 : memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *));
102 :
103 1390 : mListeners.Init();
104 :
105 1390 : return NS_OK;
106 : }
107 :
108 : namespace {
109 :
110 : class LogMessageRunnable : public nsRunnable
111 81584 : {
112 : public:
113 20396 : LogMessageRunnable(nsIConsoleMessage* message, nsConsoleService* service)
114 : : mMessage(message)
115 20396 : , mService(service)
116 20396 : { }
117 :
118 8 : void AddListener(nsIConsoleListener* listener) {
119 8 : mListeners.AppendObject(listener);
120 8 : }
121 :
122 : NS_DECL_NSIRUNNABLE
123 :
124 : private:
125 : nsCOMPtr<nsIConsoleMessage> mMessage;
126 : nsRefPtr<nsConsoleService> mService;
127 : nsCOMArray<nsIConsoleListener> mListeners;
128 : };
129 :
130 : NS_IMETHODIMP
131 20396 : LogMessageRunnable::Run()
132 : {
133 20396 : MOZ_ASSERT(NS_IsMainThread());
134 :
135 20396 : mService->SetIsDelivering();
136 :
137 20404 : for (PRInt32 i = 0; i < mListeners.Count(); ++i)
138 8 : mListeners[i]->Observe(mMessage);
139 :
140 20396 : mService->SetDoneDelivering();
141 :
142 20396 : return NS_OK;
143 : }
144 :
145 : PLDHashOperator
146 8 : CollectCurrentListeners(nsISupports* aKey, nsIConsoleListener* aValue,
147 : void* closure)
148 : {
149 8 : LogMessageRunnable* r = static_cast<LogMessageRunnable*>(closure);
150 8 : r->AddListener(aValue);
151 8 : return PL_DHASH_NEXT;
152 : }
153 :
154 : } // anonymous namespace
155 :
156 : // nsIConsoleService methods
157 : NS_IMETHODIMP
158 20396 : nsConsoleService::LogMessage(nsIConsoleMessage *message)
159 : {
160 20396 : if (message == nsnull)
161 0 : return NS_ERROR_INVALID_ARG;
162 :
163 20396 : if (NS_IsMainThread() && mDeliveringMessage) {
164 0 : NS_WARNING("Some console listener threw an error while inside itself. Discarding this message");
165 0 : return NS_ERROR_FAILURE;
166 : }
167 :
168 40792 : nsRefPtr<LogMessageRunnable> r = new LogMessageRunnable(message, this);
169 : nsIConsoleMessage *retiredMessage;
170 :
171 20396 : NS_ADDREF(message); // early, in case it's same as replaced below.
172 :
173 : /*
174 : * Lock while updating buffer, and while taking snapshot of
175 : * listeners array.
176 : */
177 : {
178 40792 : MutexAutoLock lock(mLock);
179 :
180 : #if defined(ANDROID)
181 : {
182 : nsXPIDLString msg;
183 : message->GetMessageMoz(getter_Copies(msg));
184 : __android_log_print(ANDROID_LOG_ERROR, "GeckoConsole",
185 : "%s",
186 : NS_LossyConvertUTF16toASCII(msg).get());
187 : }
188 : #endif
189 :
190 : /*
191 : * If there's already a message in the slot we're about to replace,
192 : * we've wrapped around, and we need to release the old message. We
193 : * save a pointer to it, so we can release below outside the lock.
194 : */
195 20396 : retiredMessage = mMessages[mCurrent];
196 :
197 20396 : mMessages[mCurrent++] = message;
198 20396 : if (mCurrent == mBufferSize) {
199 24 : mCurrent = 0; // wrap around.
200 24 : mFull = true;
201 : }
202 :
203 : /*
204 : * Copy the listeners into the snapshot array - in case a listener
205 : * is removed during an Observe(...) notification...
206 : */
207 20396 : mListeners.EnumerateRead(CollectCurrentListeners, r);
208 : }
209 20396 : if (retiredMessage != nsnull)
210 3472 : NS_RELEASE(retiredMessage);
211 :
212 20396 : NS_DispatchToMainThread(r);
213 :
214 20396 : return NS_OK;
215 : }
216 :
217 : NS_IMETHODIMP
218 17337 : nsConsoleService::LogStringMessage(const PRUnichar *message)
219 : {
220 17337 : nsConsoleMessage *msg = new nsConsoleMessage(message);
221 17337 : return this->LogMessage(msg);
222 : }
223 :
224 : NS_IMETHODIMP
225 1 : nsConsoleService::GetMessageArray(nsIConsoleMessage ***messages, PRUint32 *count)
226 : {
227 : nsIConsoleMessage **messageArray;
228 :
229 : /*
230 : * Lock the whole method, as we don't want anyone mucking with mCurrent or
231 : * mFull while we're copying out the buffer.
232 : */
233 2 : MutexAutoLock lock(mLock);
234 :
235 1 : if (mCurrent == 0 && !mFull) {
236 : /*
237 : * Make a 1-length output array so that nobody gets confused,
238 : * and return a count of 0. This should result in a 0-length
239 : * array object when called from script.
240 : */
241 : messageArray = (nsIConsoleMessage **)
242 1 : nsMemory::Alloc(sizeof (nsIConsoleMessage *));
243 1 : *messageArray = nsnull;
244 1 : *messages = messageArray;
245 1 : *count = 0;
246 :
247 1 : return NS_OK;
248 : }
249 :
250 0 : PRUint32 resultSize = mFull ? mBufferSize : mCurrent;
251 : messageArray =
252 : (nsIConsoleMessage **)nsMemory::Alloc((sizeof (nsIConsoleMessage *))
253 0 : * resultSize);
254 :
255 0 : if (messageArray == nsnull) {
256 0 : *messages = nsnull;
257 0 : *count = 0;
258 0 : return NS_ERROR_FAILURE;
259 : }
260 :
261 : PRUint32 i;
262 0 : if (mFull) {
263 0 : for (i = 0; i < mBufferSize; i++) {
264 : // if full, fill the buffer starting from mCurrent (which'll be
265 : // oldest) wrapping around the buffer to the most recent.
266 0 : messageArray[i] = mMessages[(mCurrent + i) % mBufferSize];
267 0 : NS_ADDREF(messageArray[i]);
268 : }
269 : } else {
270 0 : for (i = 0; i < mCurrent; i++) {
271 0 : messageArray[i] = mMessages[i];
272 0 : NS_ADDREF(messageArray[i]);
273 : }
274 : }
275 0 : *count = resultSize;
276 0 : *messages = messageArray;
277 :
278 0 : return NS_OK;
279 : }
280 :
281 : NS_IMETHODIMP
282 10 : nsConsoleService::RegisterListener(nsIConsoleListener *listener)
283 : {
284 10 : if (!NS_IsMainThread()) {
285 0 : NS_ERROR("nsConsoleService::RegisterListener is main thread only.");
286 0 : return NS_ERROR_NOT_SAME_THREAD;
287 : }
288 :
289 20 : nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener);
290 :
291 20 : MutexAutoLock lock(mLock);
292 10 : if (mListeners.GetWeak(canonical)) {
293 : // Reregistering a listener isn't good
294 0 : return NS_ERROR_FAILURE;
295 : }
296 10 : mListeners.Put(canonical, listener);
297 10 : return NS_OK;
298 : }
299 :
300 : NS_IMETHODIMP
301 10 : nsConsoleService::UnregisterListener(nsIConsoleListener *listener)
302 : {
303 10 : if (!NS_IsMainThread()) {
304 0 : NS_ERROR("nsConsoleService::UnregisterListener is main thread only.");
305 0 : return NS_ERROR_NOT_SAME_THREAD;
306 : }
307 :
308 20 : nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener);
309 :
310 20 : MutexAutoLock lock(mLock);
311 :
312 10 : if (!mListeners.GetWeak(canonical)) {
313 : // Unregistering a listener that was never registered?
314 0 : return NS_ERROR_FAILURE;
315 : }
316 10 : mListeners.Remove(canonical);
317 10 : return NS_OK;
318 : }
319 :
320 : NS_IMETHODIMP
321 75 : nsConsoleService::Reset()
322 : {
323 : /*
324 : * Make sure nobody trips into the buffer while it's being reset
325 : */
326 150 : MutexAutoLock lock(mLock);
327 :
328 75 : mCurrent = 0;
329 75 : mFull = false;
330 :
331 : /*
332 : * Free all messages stored so far (cf. destructor)
333 : */
334 175 : for (PRUint32 i = 0; i < mBufferSize && mMessages[i] != nsnull; i++)
335 100 : NS_RELEASE(mMessages[i]);
336 :
337 75 : return NS_OK;
338 : }
|