/src/mozilla-central/xpcom/base/nsConsoleService.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | /* |
8 | | * Maintains a circular buffer of recent messages, and notifies |
9 | | * listeners when new messages are logged. |
10 | | */ |
11 | | |
12 | | /* Threadsafe. */ |
13 | | |
14 | | #include "nsMemory.h" |
15 | | #include "nsCOMArray.h" |
16 | | #include "nsThreadUtils.h" |
17 | | |
18 | | #include "nsConsoleService.h" |
19 | | #include "nsConsoleMessage.h" |
20 | | #include "nsIClassInfoImpl.h" |
21 | | #include "nsIConsoleListener.h" |
22 | | #include "nsIObserverService.h" |
23 | | #include "nsPrintfCString.h" |
24 | | #include "nsProxyRelease.h" |
25 | | #include "nsIScriptError.h" |
26 | | #include "nsISupportsPrimitives.h" |
27 | | |
28 | | #include "mozilla/Preferences.h" |
29 | | #include "mozilla/Services.h" |
30 | | #include "mozilla/SystemGroup.h" |
31 | | |
32 | | #if defined(ANDROID) |
33 | | #include <android/log.h> |
34 | | #include "mozilla/dom/ContentChild.h" |
35 | | #endif |
36 | | #ifdef XP_WIN |
37 | | #include <windows.h> |
38 | | #endif |
39 | | |
40 | | #ifdef MOZ_TASK_TRACER |
41 | | #include "GeckoTaskTracer.h" |
42 | | using namespace mozilla::tasktracer; |
43 | | #endif |
44 | | |
45 | | using namespace mozilla; |
46 | | |
47 | | NS_IMPL_ADDREF(nsConsoleService) |
48 | | NS_IMPL_RELEASE(nsConsoleService) |
49 | | NS_IMPL_CLASSINFO(nsConsoleService, nullptr, |
50 | | nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, |
51 | | NS_CONSOLESERVICE_CID) |
52 | | NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver) |
53 | | NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver) |
54 | | |
55 | | static const bool gLoggingEnabled = true; |
56 | | static const bool gLoggingBuffered = true; |
57 | | #ifdef XP_WIN |
58 | | static bool gLoggingToDebugger = true; |
59 | | #endif // XP_WIN |
60 | | #if defined(ANDROID) |
61 | | static bool gLoggingLogcat = false; |
62 | | #endif // defined(ANDROID) |
63 | | |
64 | | nsConsoleService::MessageElement::~MessageElement() |
65 | 8.65k | { |
66 | 8.65k | } |
67 | | |
68 | | nsConsoleService::nsConsoleService() |
69 | | : mCurrentSize(0) |
70 | | , mDeliveringMessage(false) |
71 | | , mLock("nsConsoleService.mLock") |
72 | 1 | { |
73 | 1 | // XXX grab this from a pref! |
74 | 1 | // hm, but worry about circularity, bc we want to be able to report |
75 | 1 | // prefs errs... |
76 | 1 | mMaximumSize = 250; |
77 | 1 | |
78 | | #ifdef XP_WIN |
79 | | // This environment variable controls whether the console service |
80 | | // should be prevented from putting output to the attached debugger. |
81 | | // It only affects the Windows platform. |
82 | | // |
83 | | // To disable OutputDebugString, set: |
84 | | // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1 |
85 | | // |
86 | | const char* disableDebugLoggingVar = getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT"); |
87 | | gLoggingToDebugger = !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0'); |
88 | | #endif // XP_WIN |
89 | | } |
90 | | |
91 | | |
92 | | void |
93 | | nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID) |
94 | 0 | { |
95 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
96 | 0 | MutexAutoLock lock(mLock); |
97 | 0 |
|
98 | 0 | for (MessageElement* e = mMessages.getFirst(); e != nullptr; ) { |
99 | 0 | // Only messages implementing nsIScriptError interface expose the |
100 | 0 | // inner window ID. |
101 | 0 | nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get()); |
102 | 0 | if (!scriptError) { |
103 | 0 | e = e->getNext(); |
104 | 0 | continue; |
105 | 0 | } |
106 | 0 | uint64_t innerWindowID; |
107 | 0 | nsresult rv = scriptError->GetInnerWindowID(&innerWindowID); |
108 | 0 | if (NS_FAILED(rv) || innerWindowID != innerID) { |
109 | 0 | e = e->getNext(); |
110 | 0 | continue; |
111 | 0 | } |
112 | 0 | |
113 | 0 | MessageElement* next = e->getNext(); |
114 | 0 | e->remove(); |
115 | 0 | delete e; |
116 | 0 | mCurrentSize--; |
117 | 0 | MOZ_ASSERT(mCurrentSize < mMaximumSize); |
118 | 0 |
|
119 | 0 | e = next; |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | | void |
124 | | nsConsoleService::ClearMessages() |
125 | 0 | { |
126 | 0 | // NB: A lock is not required here as it's only called from |Reset| which |
127 | 0 | // locks for us and from the dtor. |
128 | 0 | while (!mMessages.isEmpty()) { |
129 | 0 | MessageElement* e = mMessages.popFirst(); |
130 | 0 | delete e; |
131 | 0 | } |
132 | 0 | mCurrentSize = 0; |
133 | 0 | } |
134 | | |
135 | | nsConsoleService::~nsConsoleService() |
136 | 0 | { |
137 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
138 | 0 |
|
139 | 0 | ClearMessages(); |
140 | 0 | } |
141 | | |
142 | | class AddConsolePrefWatchers : public Runnable |
143 | | { |
144 | | public: |
145 | | explicit AddConsolePrefWatchers(nsConsoleService* aConsole) |
146 | | : mozilla::Runnable("AddConsolePrefWatchers") |
147 | | , mConsole(aConsole) |
148 | 1 | { |
149 | 1 | } |
150 | | |
151 | | NS_IMETHOD Run() override |
152 | 0 | { |
153 | | #if defined(ANDROID) |
154 | | Preferences::AddBoolVarCache(&gLoggingLogcat, "consoleservice.logcat", |
155 | | #ifdef RELEASE_OR_BETA |
156 | | false |
157 | | #else |
158 | | true |
159 | | #endif |
160 | | ); |
161 | | #endif // defined(ANDROID) |
162 | |
|
163 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
164 | 0 | MOZ_ASSERT(obs); |
165 | 0 | obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); |
166 | 0 | obs->AddObserver(mConsole, "inner-window-destroyed", false); |
167 | 0 |
|
168 | 0 | if (!gLoggingBuffered) { |
169 | 0 | mConsole->Reset(); |
170 | 0 | } |
171 | 0 | return NS_OK; |
172 | 0 | } |
173 | | |
174 | | private: |
175 | | RefPtr<nsConsoleService> mConsole; |
176 | | }; |
177 | | |
178 | | nsresult |
179 | | nsConsoleService::Init() |
180 | 1 | { |
181 | 1 | NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); |
182 | 1 | |
183 | 1 | return NS_OK; |
184 | 1 | } |
185 | | |
186 | | namespace { |
187 | | |
188 | | class LogMessageRunnable : public Runnable |
189 | | { |
190 | | public: |
191 | | LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService) |
192 | | : mozilla::Runnable("LogMessageRunnable") |
193 | | , mMessage(aMessage) |
194 | | , mService(aService) |
195 | 0 | { } |
196 | | |
197 | | NS_DECL_NSIRUNNABLE |
198 | | |
199 | | private: |
200 | | nsCOMPtr<nsIConsoleMessage> mMessage; |
201 | | RefPtr<nsConsoleService> mService; |
202 | | }; |
203 | | |
204 | | NS_IMETHODIMP |
205 | | LogMessageRunnable::Run() |
206 | 0 | { |
207 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
208 | 0 |
|
209 | 0 | // Snapshot of listeners so that we don't reenter this hash during |
210 | 0 | // enumeration. |
211 | 0 | nsCOMArray<nsIConsoleListener> listeners; |
212 | 0 | mService->CollectCurrentListeners(listeners); |
213 | 0 |
|
214 | 0 | mService->SetIsDelivering(); |
215 | 0 |
|
216 | 0 | for (int32_t i = 0; i < listeners.Count(); ++i) { |
217 | 0 | listeners[i]->Observe(mMessage); |
218 | 0 | } |
219 | 0 |
|
220 | 0 | mService->SetDoneDelivering(); |
221 | 0 |
|
222 | 0 | return NS_OK; |
223 | 0 | } |
224 | | |
225 | | } // namespace |
226 | | |
227 | | // nsIConsoleService methods |
228 | | NS_IMETHODIMP |
229 | | nsConsoleService::LogMessage(nsIConsoleMessage* aMessage) |
230 | 8.90k | { |
231 | 8.90k | return LogMessageWithMode(aMessage, OutputToLog); |
232 | 8.90k | } |
233 | | |
234 | | // This can be called off the main thread. |
235 | | nsresult |
236 | | nsConsoleService::LogMessageWithMode(nsIConsoleMessage* aMessage, |
237 | | nsConsoleService::OutputMode aOutputMode) |
238 | 8.90k | { |
239 | 8.90k | if (!aMessage) { |
240 | 0 | return NS_ERROR_INVALID_ARG; |
241 | 0 | } |
242 | 8.90k | |
243 | 8.90k | if (!gLoggingEnabled) { |
244 | 0 | return NS_OK; |
245 | 0 | } |
246 | 8.90k | |
247 | 8.90k | if (NS_IsMainThread() && mDeliveringMessage) { |
248 | 0 | nsCString msg; |
249 | 0 | aMessage->ToString(msg); |
250 | 0 | NS_WARNING(nsPrintfCString("Reentrancy error: some client attempted " |
251 | 0 | "to display a message to the console while in a console listener. " |
252 | 0 | "The following message was discarded: \"%s\"", msg.get()).get()); |
253 | 0 | return NS_ERROR_FAILURE; |
254 | 0 | } |
255 | 8.90k | |
256 | 8.90k | RefPtr<LogMessageRunnable> r; |
257 | 8.90k | nsCOMPtr<nsIConsoleMessage> retiredMessage; |
258 | 8.90k | |
259 | 8.90k | /* |
260 | 8.90k | * Lock while updating buffer, and while taking snapshot of |
261 | 8.90k | * listeners array. |
262 | 8.90k | */ |
263 | 8.90k | { |
264 | 8.90k | MutexAutoLock lock(mLock); |
265 | 8.90k | |
266 | | #if defined(ANDROID) |
267 | | if (gLoggingLogcat && aOutputMode == OutputToLog) { |
268 | | nsCString msg; |
269 | | aMessage->ToString(msg); |
270 | | |
271 | | /** Attempt to use the process name as the log tag. */ |
272 | | mozilla::dom::ContentChild* child = |
273 | | mozilla::dom::ContentChild::GetSingleton(); |
274 | | nsCString appName; |
275 | | if (child) { |
276 | | child->GetProcessName(appName); |
277 | | } else { |
278 | | appName = "GeckoConsole"; |
279 | | } |
280 | | |
281 | | uint32_t logLevel = 0; |
282 | | aMessage->GetLogLevel(&logLevel); |
283 | | |
284 | | android_LogPriority logPriority = ANDROID_LOG_INFO; |
285 | | switch (logLevel) { |
286 | | case nsIConsoleMessage::debug: |
287 | | logPriority = ANDROID_LOG_DEBUG; |
288 | | break; |
289 | | case nsIConsoleMessage::info: |
290 | | logPriority = ANDROID_LOG_INFO; |
291 | | break; |
292 | | case nsIConsoleMessage::warn: |
293 | | logPriority = ANDROID_LOG_WARN; |
294 | | break; |
295 | | case nsIConsoleMessage::error: |
296 | | logPriority = ANDROID_LOG_ERROR; |
297 | | break; |
298 | | } |
299 | | |
300 | | __android_log_print(logPriority, appName.get(), "%s", msg.get()); |
301 | | } |
302 | | #endif |
303 | | #ifdef XP_WIN |
304 | | if (gLoggingToDebugger && IsDebuggerPresent()) { |
305 | | nsString msg; |
306 | | aMessage->GetMessageMoz(msg); |
307 | | msg.Append('\n'); |
308 | | OutputDebugStringW(msg.get()); |
309 | | } |
310 | | #endif |
311 | | #ifdef MOZ_TASK_TRACER |
312 | | if (IsStartLogging()) { |
313 | | nsCString msg; |
314 | | aMessage->ToString(msg); |
315 | | int prefixPos = msg.Find(GetJSLabelPrefix()); |
316 | | if (prefixPos >= 0) { |
317 | | nsDependentCSubstring submsg(msg, prefixPos); |
318 | | AddLabel("%s", submsg.BeginReading()); |
319 | | } |
320 | | } |
321 | | #endif |
322 | | |
323 | 8.90k | if (gLoggingBuffered) { |
324 | 8.90k | MessageElement* e = new MessageElement(aMessage); |
325 | 8.90k | mMessages.insertBack(e); |
326 | 8.90k | if (mCurrentSize != mMaximumSize) { |
327 | 250 | mCurrentSize++; |
328 | 8.65k | } else { |
329 | 8.65k | MessageElement* p = mMessages.popFirst(); |
330 | 8.65k | MOZ_ASSERT(p); |
331 | 8.65k | p->swapMessage(retiredMessage); |
332 | 8.65k | delete p; |
333 | 8.65k | } |
334 | 8.90k | } |
335 | 8.90k | |
336 | 8.90k | if (mListeners.Count() > 0) { |
337 | 0 | r = new LogMessageRunnable(aMessage, this); |
338 | 0 | } |
339 | 8.90k | } |
340 | 8.90k | |
341 | 8.90k | if (retiredMessage) { |
342 | 8.65k | // Release |retiredMessage| on the main thread in case it is an instance of |
343 | 8.65k | // a mainthread-only class like nsScriptErrorWithStack and we're off the |
344 | 8.65k | // main thread. |
345 | 8.65k | NS_ReleaseOnMainThreadSystemGroup( |
346 | 8.65k | "nsConsoleService::retiredMessage", retiredMessage.forget()); |
347 | 8.65k | } |
348 | 8.90k | |
349 | 8.90k | if (r) { |
350 | 0 | // avoid failing in XPCShell tests |
351 | 0 | nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); |
352 | 0 | if (mainThread) { |
353 | 0 | SystemGroup::Dispatch(TaskCategory::Other, r.forget()); |
354 | 0 | } |
355 | 0 | } |
356 | 8.90k | |
357 | 8.90k | return NS_OK; |
358 | 8.90k | } |
359 | | |
360 | | void |
361 | | nsConsoleService::CollectCurrentListeners( |
362 | | nsCOMArray<nsIConsoleListener>& aListeners) |
363 | 0 | { |
364 | 0 | MutexAutoLock lock(mLock); |
365 | 0 | for (auto iter = mListeners.Iter(); !iter.Done(); iter.Next()) { |
366 | 0 | nsIConsoleListener* value = iter.UserData(); |
367 | 0 | aListeners.AppendObject(value); |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | NS_IMETHODIMP |
372 | | nsConsoleService::LogStringMessage(const char16_t* aMessage) |
373 | 8.90k | { |
374 | 8.90k | if (!gLoggingEnabled) { |
375 | 0 | return NS_OK; |
376 | 0 | } |
377 | 8.90k | |
378 | 8.90k | RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage)); |
379 | 8.90k | return this->LogMessage(msg); |
380 | 8.90k | } |
381 | | |
382 | | NS_IMETHODIMP |
383 | | nsConsoleService::GetMessageArray(uint32_t* aCount, |
384 | | nsIConsoleMessage*** aMessages) |
385 | 0 | { |
386 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
387 | 0 |
|
388 | 0 | MutexAutoLock lock(mLock); |
389 | 0 |
|
390 | 0 | if (mMessages.isEmpty()) { |
391 | 0 | /* |
392 | 0 | * Make a 1-length output array so that nobody gets confused, |
393 | 0 | * and return a count of 0. This should result in a 0-length |
394 | 0 | * array object when called from script. |
395 | 0 | */ |
396 | 0 | nsIConsoleMessage** messageArray = (nsIConsoleMessage**) |
397 | 0 | moz_xmalloc(sizeof(nsIConsoleMessage*)); |
398 | 0 | *messageArray = nullptr; |
399 | 0 | *aMessages = messageArray; |
400 | 0 | *aCount = 0; |
401 | 0 |
|
402 | 0 | return NS_OK; |
403 | 0 | } |
404 | 0 | |
405 | 0 | MOZ_ASSERT(mCurrentSize <= mMaximumSize); |
406 | 0 | nsIConsoleMessage** messageArray = |
407 | 0 | static_cast<nsIConsoleMessage**>(moz_xmalloc(sizeof(nsIConsoleMessage*) |
408 | 0 | * mCurrentSize)); |
409 | 0 |
|
410 | 0 | uint32_t i = 0; |
411 | 0 | for (MessageElement* e = mMessages.getFirst(); e != nullptr; e = e->getNext()) { |
412 | 0 | nsCOMPtr<nsIConsoleMessage> m = e->Get(); |
413 | 0 | m.forget(&messageArray[i]); |
414 | 0 | i++; |
415 | 0 | } |
416 | 0 |
|
417 | 0 | MOZ_ASSERT(i == mCurrentSize); |
418 | 0 |
|
419 | 0 | *aCount = i; |
420 | 0 | *aMessages = messageArray; |
421 | 0 |
|
422 | 0 | return NS_OK; |
423 | 0 | } |
424 | | |
425 | | NS_IMETHODIMP |
426 | | nsConsoleService::RegisterListener(nsIConsoleListener* aListener) |
427 | 0 | { |
428 | 0 | if (!NS_IsMainThread()) { |
429 | 0 | NS_ERROR("nsConsoleService::RegisterListener is main thread only."); |
430 | 0 | return NS_ERROR_NOT_SAME_THREAD; |
431 | 0 | } |
432 | 0 |
|
433 | 0 | nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener); |
434 | 0 |
|
435 | 0 | MutexAutoLock lock(mLock); |
436 | 0 | if (mListeners.GetWeak(canonical)) { |
437 | 0 | // Reregistering a listener isn't good |
438 | 0 | return NS_ERROR_FAILURE; |
439 | 0 | } |
440 | 0 | mListeners.Put(canonical, aListener); |
441 | 0 | return NS_OK; |
442 | 0 | } |
443 | | |
444 | | NS_IMETHODIMP |
445 | | nsConsoleService::UnregisterListener(nsIConsoleListener* aListener) |
446 | 0 | { |
447 | 0 | if (!NS_IsMainThread()) { |
448 | 0 | NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); |
449 | 0 | return NS_ERROR_NOT_SAME_THREAD; |
450 | 0 | } |
451 | 0 |
|
452 | 0 | nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener); |
453 | 0 |
|
454 | 0 | MutexAutoLock lock(mLock); |
455 | 0 |
|
456 | 0 | if (!mListeners.GetWeak(canonical)) { |
457 | 0 | // Unregistering a listener that was never registered? |
458 | 0 | return NS_ERROR_FAILURE; |
459 | 0 | } |
460 | 0 | mListeners.Remove(canonical); |
461 | 0 | return NS_OK; |
462 | 0 | } |
463 | | |
464 | | NS_IMETHODIMP |
465 | | nsConsoleService::Reset() |
466 | 0 | { |
467 | 0 | MOZ_RELEASE_ASSERT(NS_IsMainThread()); |
468 | 0 |
|
469 | 0 | /* |
470 | 0 | * Make sure nobody trips into the buffer while it's being reset |
471 | 0 | */ |
472 | 0 | MutexAutoLock lock(mLock); |
473 | 0 |
|
474 | 0 | ClearMessages(); |
475 | 0 | return NS_OK; |
476 | 0 | } |
477 | | |
478 | | NS_IMETHODIMP |
479 | | nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic, |
480 | | const char16_t* aData) |
481 | 0 | { |
482 | 0 | if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { |
483 | 0 | // Dump all our messages, in case any are cycle collected. |
484 | 0 | Reset(); |
485 | 0 | // We could remove ourselves from the observer service, but it is about to |
486 | 0 | // drop all observers anyways, so why bother. |
487 | 0 | } else if (!strcmp(aTopic, "inner-window-destroyed")) { |
488 | 0 | nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject); |
489 | 0 | MOZ_ASSERT(supportsInt); |
490 | 0 |
|
491 | 0 | uint64_t windowId; |
492 | 0 | MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); |
493 | 0 |
|
494 | 0 | ClearMessagesForWindowID(windowId); |
495 | 0 | } else { |
496 | 0 | MOZ_CRASH(); |
497 | 0 | } |
498 | 0 | return NS_OK; |
499 | 0 | } |