/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* * Maintains a circular buffer of recent messages, and notifies * listeners when new messages are logged.
*/
#ifdefined(ANDROID) # include <android/log.h> # include "mozilla/dom/ContentChild.h" # include "mozilla/StaticPrefs_consoleservice.h" #endif #ifdef XP_WIN # include <windows.h> #endif
nsConsoleService::nsConsoleService()
: mCurrentSize(0), // XXX grab this from a pref! // hm, but worry about circularity, bc we want to be able to report // prefs errs...
mMaximumSize(250),
mDeliveringMessage(false),
mLock("nsConsoleService.mLock") { #ifdef XP_WIN // This environment variable controls whether the console service // should be prevented from putting output to the attached debugger. // It only affects the Windows platform. // // To disable OutputDebugString, set: // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1 // constchar* disableDebugLoggingVar =
getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT");
gLoggingToDebugger =
!disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0'); #endif// XP_WIN
}
void nsConsoleService::ClearMessages() { // NB: A lock is not required here as it's only called from |Reset| which // locks for us and from the dtor. while (!mMessages.isEmpty()) {
MessageElement* e = mMessages.popFirst(); delete e;
}
mCurrentSize = 0;
}
nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage); if (!scriptError) { // Not an nsIScriptError return NS_OK;
}
uint64_t windowID;
nsresult rv;
rv = scriptError->GetInnerWindowID(&windowID);
NS_ENSURE_SUCCESS(rv, rv); if (!windowID) { // Does not set window id return NS_OK;
}
RefPtr<mozilla::dom::WindowGlobalParent> windowGlobalParent =
mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID); if (!windowGlobalParent) { // Could not find parent window by id return NS_OK;
}
RefPtr<mozilla::dom::BrowserParent> browserParent =
windowGlobalParent->GetBrowserParent(); if (!browserParent) { return NS_OK;
}
mozilla::dom::ContentParent* contentParent = browserParent->Manager(); if (!contentParent) { return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
LogMessageRunnable::Run() { // Snapshot of listeners so that we don't reenter this hash during // enumeration.
nsCOMArray<nsIConsoleListener> listeners;
mService->CollectCurrentListeners(listeners);
mService->SetIsDelivering();
for (int32_t i = 0; i < listeners.Count(); ++i) {
listeners[i]->Observe(mMessage);
}
// This can be called off the main thread.
nsresult nsConsoleService::LogMessageWithMode(
nsIConsoleMessage* aMessage, nsIConsoleService::OutputMode aOutputMode) { if (!aMessage) { return NS_ERROR_INVALID_ARG;
}
if (!gLoggingEnabled) { return NS_OK;
}
if (NS_IsMainThread() && mDeliveringMessage) {
nsCString msg;
aMessage->ToString(msg);
NS_WARNING(
nsPrintfCString( "Reentrancy error: some client attempted to display a message to " "the console while in a console listener. The following message " "was discarded: \"%s\"",
msg.get())
.get()); return NS_ERROR_FAILURE;
}
if (XRE_IsParentProcess() && NS_IsMainThread()) { // If mMessage is a scriptError with an innerWindowId set, // forward it to the matching ContentParent // This enables logging from parent to content process bool sent;
nsresult rv = MaybeForwardScriptError(aMessage, &sent);
NS_ENSURE_SUCCESS(rv, rv); if (sent) { return NS_OK;
}
}
/** Attempt to use the process name as the log tag. */
mozilla::dom::ContentChild* child =
mozilla::dom::ContentChild::GetSingleton();
nsCString appName; if (child) {
child->GetProcessName(appName);
} else {
appName = "GeckoConsole";
}
if (gLoggingBuffered) {
MessageElement* e = new MessageElement(aMessage);
mMessages.insertBack(e); if (mCurrentSize != mMaximumSize) {
mCurrentSize++;
} else {
MessageElement* p = mMessages.popFirst();
MOZ_ASSERT(p);
p->swapMessage(retiredMessage); delete p;
}
}
if (mListeners.Count() > 0) {
r = new LogMessageRunnable(aMessage, this);
}
}
if (retiredMessage) { // Release |retiredMessage| on the main thread in case it is an instance of // a mainthread-only class like nsScriptErrorWithStack and we're off the // main thread.
NS_ReleaseOnMainThread("nsConsoleService::retiredMessage",
retiredMessage.forget());
}
if (r) { // avoid failing in XPCShell tests
nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); if (mainThread) {
SchedulerGroup::Dispatch(r.forget());
}
}
return NS_OK;
}
// See nsIConsoleService.idl for more info about this method
NS_IMETHODIMP
nsConsoleService::CallFunctionAndLogException(
JS::Handle<JS::Value> targetGlobal, JS::HandleValue function, JSContext* cx,
JS::MutableHandleValue retval) { if (!targetGlobal.isObject() || !function.isObject()) { return NS_ERROR_INVALID_ARG;
}
JS::Rooted<JS::Realm*> contextRealm(cx, JS::GetCurrentRealmOrNull(cx)); if (!contextRealm) { return NS_ERROR_INVALID_ARG;
}
// Use AutoJSAPI in order to trigger AutoJSAPI::ReportException // which will do most of the work required for this function. // // We only have to pick the right global for which we want to flag // the exception against.
dom::AutoJSAPI jsapi; if (!jsapi.Init(global)) { return NS_ERROR_UNEXPECTED;
}
JSContext* ccx = jsapi.cx();
// AutoJSAPI picks `targetGlobal` as execution compartment // whereas we expect to run `function` from the callsites compartment.
JSAutoRealm ar(ccx, JS::GetRealmGlobalOrNull(contextRealm));
JS::RootedValue funVal(ccx, function); if (!JS_WrapValue(ccx, &funVal)) { return NS_ERROR_FAILURE;
} if (!JS_CallFunctionValue(ccx, nullptr, funVal, JS::HandleValueArray::empty(),
retval)) { return NS_ERROR_XPC_JAVASCRIPT_ERROR;
}
return NS_OK;
}
void nsConsoleService::CollectCurrentListeners(
nsCOMArray<nsIConsoleListener>& aListeners) {
MutexAutoLock lock(mLock); // XXX When MakeBackInserter(nsCOMArray<T>&) is added, we can do: // AppendToArray(aListeners, mListeners.Values()); for (constauto& listener : mListeners.Values()) {
aListeners.AppendObject(listener);
}
}
for (MessageElement* e = mMessages.getFirst(); e != nullptr;
e = e->getNext()) {
aMessages.AppendElement(e->Get());
}
return NS_OK;
}
NS_IMETHODIMP
nsConsoleService::RegisterListener(nsIConsoleListener* aListener) { if (!NS_IsMainThread()) {
NS_ERROR("nsConsoleService::RegisterListener is main thread only."); return NS_ERROR_NOT_SAME_THREAD;
}
MutexAutoLock lock(mLock); return mListeners.WithEntryHandle(canonical, [&](auto&& entry) { if (entry) { // Reregistering a listener isn't good return NS_ERROR_FAILURE;
}
entry.Insert(aListener); return NS_OK;
});
}
NS_IMETHODIMP
nsConsoleService::UnregisterListener(nsIConsoleListener* aListener) { if (!NS_IsMainThread()) {
NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); return NS_ERROR_NOT_SAME_THREAD;
}
NS_IMETHODIMP
nsConsoleService::Observe(nsISupports* aSubject, constchar* aTopic, const char16_t* aData) { if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { // Dump all our messages, in case any are cycle collected.
Reset(); // We could remove ourselves from the observer service, but it is about to // drop all observers anyways, so why bother.
} elseif (!strcmp(aTopic, "inner-window-destroyed")) {
nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
MOZ_ASSERT(supportsInt);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.