|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 * Maintains a circular buffer of recent messages, and notifies |
|
8 * listeners when new messages are logged. |
|
9 */ |
|
10 |
|
11 /* Threadsafe. */ |
|
12 |
|
13 #include "nsMemory.h" |
|
14 #include "nsCOMArray.h" |
|
15 #include "nsThreadUtils.h" |
|
16 |
|
17 #include "nsConsoleService.h" |
|
18 #include "nsConsoleMessage.h" |
|
19 #include "nsIClassInfoImpl.h" |
|
20 #include "nsIConsoleListener.h" |
|
21 #include "nsPrintfCString.h" |
|
22 |
|
23 #include "mozilla/Preferences.h" |
|
24 |
|
25 #if defined(ANDROID) |
|
26 #include <android/log.h> |
|
27 #endif |
|
28 #ifdef XP_WIN |
|
29 #include <windows.h> |
|
30 #endif |
|
31 |
|
32 using namespace mozilla; |
|
33 |
|
34 NS_IMPL_ADDREF(nsConsoleService) |
|
35 NS_IMPL_RELEASE(nsConsoleService) |
|
36 NS_IMPL_CLASSINFO(nsConsoleService, nullptr, nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, NS_CONSOLESERVICE_CID) |
|
37 NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService) |
|
38 NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService) |
|
39 |
|
40 static bool sLoggingEnabled = true; |
|
41 static bool sLoggingBuffered = true; |
|
42 |
|
43 nsConsoleService::nsConsoleService() |
|
44 : mMessages(nullptr) |
|
45 , mCurrent(0) |
|
46 , mFull(false) |
|
47 , mDeliveringMessage(false) |
|
48 , mLock("nsConsoleService.mLock") |
|
49 { |
|
50 // XXX grab this from a pref! |
|
51 // hm, but worry about circularity, bc we want to be able to report |
|
52 // prefs errs... |
|
53 mBufferSize = 250; |
|
54 } |
|
55 |
|
56 nsConsoleService::~nsConsoleService() |
|
57 { |
|
58 uint32_t i = 0; |
|
59 while (i < mBufferSize && mMessages[i] != nullptr) { |
|
60 NS_RELEASE(mMessages[i]); |
|
61 i++; |
|
62 } |
|
63 |
|
64 if (mMessages) |
|
65 nsMemory::Free(mMessages); |
|
66 } |
|
67 |
|
68 class AddConsolePrefWatchers : public nsRunnable |
|
69 { |
|
70 public: |
|
71 AddConsolePrefWatchers(nsConsoleService* aConsole) : mConsole(aConsole) {} |
|
72 |
|
73 NS_IMETHOD Run() |
|
74 { |
|
75 Preferences::AddBoolVarCache(&sLoggingEnabled, "consoleservice.enabled", true); |
|
76 Preferences::AddBoolVarCache(&sLoggingBuffered, "consoleservice.buffered", true); |
|
77 if (!sLoggingBuffered) { |
|
78 mConsole->Reset(); |
|
79 } |
|
80 return NS_OK; |
|
81 } |
|
82 |
|
83 private: |
|
84 nsRefPtr<nsConsoleService> mConsole; |
|
85 }; |
|
86 |
|
87 nsresult |
|
88 nsConsoleService::Init() |
|
89 { |
|
90 mMessages = (nsIConsoleMessage **) |
|
91 nsMemory::Alloc(mBufferSize * sizeof(nsIConsoleMessage *)); |
|
92 if (!mMessages) |
|
93 return NS_ERROR_OUT_OF_MEMORY; |
|
94 |
|
95 // Array elements should be 0 initially for circular buffer algorithm. |
|
96 memset(mMessages, 0, mBufferSize * sizeof(nsIConsoleMessage *)); |
|
97 |
|
98 NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); |
|
99 |
|
100 return NS_OK; |
|
101 } |
|
102 |
|
103 namespace { |
|
104 |
|
105 class LogMessageRunnable : public nsRunnable |
|
106 { |
|
107 public: |
|
108 LogMessageRunnable(nsIConsoleMessage* message, nsConsoleService* service) |
|
109 : mMessage(message) |
|
110 , mService(service) |
|
111 { } |
|
112 |
|
113 NS_DECL_NSIRUNNABLE |
|
114 |
|
115 private: |
|
116 nsCOMPtr<nsIConsoleMessage> mMessage; |
|
117 nsRefPtr<nsConsoleService> mService; |
|
118 }; |
|
119 |
|
120 typedef nsCOMArray<nsIConsoleListener> ListenerArrayType; |
|
121 |
|
122 PLDHashOperator |
|
123 CollectCurrentListeners(nsISupports* aKey, nsIConsoleListener* aValue, |
|
124 void* closure) |
|
125 { |
|
126 ListenerArrayType* listeners = static_cast<ListenerArrayType*>(closure); |
|
127 listeners->AppendObject(aValue); |
|
128 return PL_DHASH_NEXT; |
|
129 } |
|
130 |
|
131 NS_IMETHODIMP |
|
132 LogMessageRunnable::Run() |
|
133 { |
|
134 MOZ_ASSERT(NS_IsMainThread()); |
|
135 |
|
136 // Snapshot of listeners so that we don't reenter this hash during |
|
137 // enumeration. |
|
138 nsCOMArray<nsIConsoleListener> listeners; |
|
139 mService->EnumerateListeners(CollectCurrentListeners, &listeners); |
|
140 |
|
141 mService->SetIsDelivering(); |
|
142 |
|
143 for (int32_t i = 0; i < listeners.Count(); ++i) |
|
144 listeners[i]->Observe(mMessage); |
|
145 |
|
146 mService->SetDoneDelivering(); |
|
147 |
|
148 return NS_OK; |
|
149 } |
|
150 |
|
151 } // anonymous namespace |
|
152 |
|
153 // nsIConsoleService methods |
|
154 NS_IMETHODIMP |
|
155 nsConsoleService::LogMessage(nsIConsoleMessage *message) |
|
156 { |
|
157 return LogMessageWithMode(message, OutputToLog); |
|
158 } |
|
159 |
|
160 nsresult |
|
161 nsConsoleService::LogMessageWithMode(nsIConsoleMessage *message, nsConsoleService::OutputMode outputMode) |
|
162 { |
|
163 if (message == nullptr) |
|
164 return NS_ERROR_INVALID_ARG; |
|
165 |
|
166 if (!sLoggingEnabled) { |
|
167 return NS_OK; |
|
168 } |
|
169 |
|
170 if (NS_IsMainThread() && mDeliveringMessage) { |
|
171 nsCString msg; |
|
172 message->ToString(msg); |
|
173 NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted " |
|
174 "to display a message to the console while in a console listener. " |
|
175 "The following message was discarded: \"%s\"", msg.get()).get()); |
|
176 return NS_ERROR_FAILURE; |
|
177 } |
|
178 |
|
179 nsRefPtr<LogMessageRunnable> r; |
|
180 nsIConsoleMessage *retiredMessage; |
|
181 |
|
182 if (sLoggingBuffered) { |
|
183 NS_ADDREF(message); // early, in case it's same as replaced below. |
|
184 } |
|
185 |
|
186 /* |
|
187 * Lock while updating buffer, and while taking snapshot of |
|
188 * listeners array. |
|
189 */ |
|
190 { |
|
191 MutexAutoLock lock(mLock); |
|
192 |
|
193 #if defined(ANDROID) |
|
194 if (outputMode == OutputToLog) |
|
195 { |
|
196 nsCString msg; |
|
197 message->ToString(msg); |
|
198 __android_log_print(ANDROID_LOG_ERROR, "GeckoConsole", |
|
199 "%s", msg.get()); |
|
200 } |
|
201 #endif |
|
202 #ifdef XP_WIN |
|
203 if (IsDebuggerPresent()) { |
|
204 nsString msg; |
|
205 message->GetMessageMoz(getter_Copies(msg)); |
|
206 msg.AppendLiteral("\n"); |
|
207 OutputDebugStringW(msg.get()); |
|
208 } |
|
209 #endif |
|
210 |
|
211 /* |
|
212 * If there's already a message in the slot we're about to replace, |
|
213 * we've wrapped around, and we need to release the old message. We |
|
214 * save a pointer to it, so we can release below outside the lock. |
|
215 */ |
|
216 retiredMessage = mMessages[mCurrent]; |
|
217 |
|
218 if (sLoggingBuffered) { |
|
219 mMessages[mCurrent++] = message; |
|
220 if (mCurrent == mBufferSize) { |
|
221 mCurrent = 0; // wrap around. |
|
222 mFull = true; |
|
223 } |
|
224 } |
|
225 |
|
226 if (mListeners.Count() > 0) { |
|
227 r = new LogMessageRunnable(message, this); |
|
228 } |
|
229 } |
|
230 |
|
231 if (retiredMessage != nullptr) |
|
232 NS_RELEASE(retiredMessage); |
|
233 |
|
234 if (r) |
|
235 NS_DispatchToMainThread(r); |
|
236 |
|
237 return NS_OK; |
|
238 } |
|
239 |
|
240 void |
|
241 nsConsoleService::EnumerateListeners(ListenerHash::EnumReadFunction aFunction, |
|
242 void* aClosure) |
|
243 { |
|
244 MutexAutoLock lock(mLock); |
|
245 mListeners.EnumerateRead(aFunction, aClosure); |
|
246 } |
|
247 |
|
248 NS_IMETHODIMP |
|
249 nsConsoleService::LogStringMessage(const char16_t *message) |
|
250 { |
|
251 if (!sLoggingEnabled) { |
|
252 return NS_OK; |
|
253 } |
|
254 |
|
255 nsRefPtr<nsConsoleMessage> msg(new nsConsoleMessage(message)); |
|
256 return this->LogMessage(msg); |
|
257 } |
|
258 |
|
259 NS_IMETHODIMP |
|
260 nsConsoleService::GetMessageArray(uint32_t *count, nsIConsoleMessage ***messages) |
|
261 { |
|
262 nsIConsoleMessage **messageArray; |
|
263 |
|
264 /* |
|
265 * Lock the whole method, as we don't want anyone mucking with mCurrent or |
|
266 * mFull while we're copying out the buffer. |
|
267 */ |
|
268 MutexAutoLock lock(mLock); |
|
269 |
|
270 if (mCurrent == 0 && !mFull) { |
|
271 /* |
|
272 * Make a 1-length output array so that nobody gets confused, |
|
273 * and return a count of 0. This should result in a 0-length |
|
274 * array object when called from script. |
|
275 */ |
|
276 messageArray = (nsIConsoleMessage **) |
|
277 nsMemory::Alloc(sizeof (nsIConsoleMessage *)); |
|
278 *messageArray = nullptr; |
|
279 *messages = messageArray; |
|
280 *count = 0; |
|
281 |
|
282 return NS_OK; |
|
283 } |
|
284 |
|
285 uint32_t resultSize = mFull ? mBufferSize : mCurrent; |
|
286 messageArray = |
|
287 (nsIConsoleMessage **)nsMemory::Alloc((sizeof (nsIConsoleMessage *)) |
|
288 * resultSize); |
|
289 |
|
290 if (messageArray == nullptr) { |
|
291 *messages = nullptr; |
|
292 *count = 0; |
|
293 return NS_ERROR_FAILURE; |
|
294 } |
|
295 |
|
296 uint32_t i; |
|
297 if (mFull) { |
|
298 for (i = 0; i < mBufferSize; i++) { |
|
299 // if full, fill the buffer starting from mCurrent (which'll be |
|
300 // oldest) wrapping around the buffer to the most recent. |
|
301 messageArray[i] = mMessages[(mCurrent + i) % mBufferSize]; |
|
302 NS_ADDREF(messageArray[i]); |
|
303 } |
|
304 } else { |
|
305 for (i = 0; i < mCurrent; i++) { |
|
306 messageArray[i] = mMessages[i]; |
|
307 NS_ADDREF(messageArray[i]); |
|
308 } |
|
309 } |
|
310 *count = resultSize; |
|
311 *messages = messageArray; |
|
312 |
|
313 return NS_OK; |
|
314 } |
|
315 |
|
316 NS_IMETHODIMP |
|
317 nsConsoleService::RegisterListener(nsIConsoleListener *listener) |
|
318 { |
|
319 if (!NS_IsMainThread()) { |
|
320 NS_ERROR("nsConsoleService::RegisterListener is main thread only."); |
|
321 return NS_ERROR_NOT_SAME_THREAD; |
|
322 } |
|
323 |
|
324 nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener); |
|
325 |
|
326 MutexAutoLock lock(mLock); |
|
327 if (mListeners.GetWeak(canonical)) { |
|
328 // Reregistering a listener isn't good |
|
329 return NS_ERROR_FAILURE; |
|
330 } |
|
331 mListeners.Put(canonical, listener); |
|
332 return NS_OK; |
|
333 } |
|
334 |
|
335 NS_IMETHODIMP |
|
336 nsConsoleService::UnregisterListener(nsIConsoleListener *listener) |
|
337 { |
|
338 if (!NS_IsMainThread()) { |
|
339 NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); |
|
340 return NS_ERROR_NOT_SAME_THREAD; |
|
341 } |
|
342 |
|
343 nsCOMPtr<nsISupports> canonical = do_QueryInterface(listener); |
|
344 |
|
345 MutexAutoLock lock(mLock); |
|
346 |
|
347 if (!mListeners.GetWeak(canonical)) { |
|
348 // Unregistering a listener that was never registered? |
|
349 return NS_ERROR_FAILURE; |
|
350 } |
|
351 mListeners.Remove(canonical); |
|
352 return NS_OK; |
|
353 } |
|
354 |
|
355 NS_IMETHODIMP |
|
356 nsConsoleService::Reset() |
|
357 { |
|
358 /* |
|
359 * Make sure nobody trips into the buffer while it's being reset |
|
360 */ |
|
361 MutexAutoLock lock(mLock); |
|
362 |
|
363 mCurrent = 0; |
|
364 mFull = false; |
|
365 |
|
366 /* |
|
367 * Free all messages stored so far (cf. destructor) |
|
368 */ |
|
369 for (uint32_t i = 0; i < mBufferSize && mMessages[i] != nullptr; i++) |
|
370 NS_RELEASE(mMessages[i]); |
|
371 |
|
372 return NS_OK; |
|
373 } |