Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nsConsoleService.cpp   Sprache: C

 
/* -*- 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.
 */


/* Threadsafe. */

#include "nsCOMArray.h"
#include "nsThreadUtils.h"

#include "nsConsoleService.h"
#include "nsConsoleMessage.h"
#include "nsIClassInfoImpl.h"
#include "nsIConsoleListener.h"
#include "nsIObserverService.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsIScriptError.h"
#include "nsISupportsPrimitives.h"
#include "js/friend/ErrorMessages.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/ScriptSettings.h"

#include "mozilla/SchedulerGroup.h"
#include "mozilla/Services.h"

#if defined(ANDROID)
#  include <android/log.h>
#  include "mozilla/dom/ContentChild.h"
#  include "mozilla/StaticPrefs_consoleservice.h"
#endif
#ifdef XP_WIN
#  include <windows.h>
#endif

using namespace mozilla;

NS_IMPL_ADDREF(nsConsoleService)
NS_IMPL_RELEASE(nsConsoleService)
NS_IMPL_CLASSINFO(nsConsoleService, nullptr,
                  nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON,
                  NS_CONSOLESERVICE_CID)
NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver)
NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver)

static const bool gLoggingEnabled = true;
static const bool gLoggingBuffered = true;
#ifdef XP_WIN
static bool gLoggingToDebugger = true;
#endif  // XP_WIN

nsConsoleService::MessageElement::~MessageElement() = default;

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
  //
  const char* disableDebugLoggingVar =
      getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT");
  gLoggingToDebugger =
      !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0');
#endif  // XP_WIN
}

void nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  MutexAutoLock lock(mLock);

  for (MessageElement* e = mMessages.getFirst(); e != nullptr;) {
    // Only messages implementing nsIScriptError interface expose the
    // inner window ID.
    nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(e->Get());
    if (!scriptError) {
      e = e->getNext();
      continue;
    }
    uint64_t innerWindowID;
    nsresult rv = scriptError->GetInnerWindowID(&innerWindowID);
    if (NS_FAILED(rv) || innerWindowID != innerID) {
      e = e->getNext();
      continue;
    }

    MessageElement* next = e->getNext();
    e->remove();
    delete e;
    mCurrentSize--;
    MOZ_ASSERT(mCurrentSize < mMaximumSize);

    e = next;
  }
}

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;
}

nsConsoleService::~nsConsoleService() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  ClearMessages();
}

class AddConsolePrefWatchers : public Runnable {
 public:
  explicit AddConsolePrefWatchers(nsConsoleService* aConsole)
      : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole) {}

  NS_IMETHOD Run() override {
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    MOZ_ASSERT(obs);
    obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
    obs->AddObserver(mConsole, "inner-window-destroyed"false);

    if (!gLoggingBuffered) {
      mConsole->Reset();
    }
    return NS_OK;
  }

 private:
  RefPtr<nsConsoleService> mConsole;
};

nsresult nsConsoleService::Init() {
  NS_DispatchToMainThread(new AddConsolePrefWatchers(this));

  return NS_OK;
}

nsresult nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage* aMessage,
                                                   bool* sent) {
  *sent = false;

  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;
  }

  nsAutoString msg;
  nsAutoCString sourceName;
  nsCString category;
  uint32_t lineNum, colNum, flags;
  uint64_t innerWindowId;
  bool fromPrivateWindow, fromChromeContext;

  rv = scriptError->GetErrorMessage(msg);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetSourceName(sourceName);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = scriptError->GetCategory(getter_Copies(category));
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetLineNumber(&lineNum);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetColumnNumber(&colNum);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetFlags(&flags);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetIsFromPrivateWindow(&fromPrivateWindow);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetIsFromChromeContext(&fromChromeContext);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = scriptError->GetInnerWindowID(&innerWindowId);
  NS_ENSURE_SUCCESS(rv, rv);

  *sent = contentParent->SendScriptError(msg, sourceName, lineNum, colNum,
                                         flags, category, fromPrivateWindow,
                                         innerWindowId, fromChromeContext);
  return NS_OK;
}

namespace {

class LogMessageRunnable : public Runnable {
 public:
  LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService)
      : mozilla::Runnable("LogMessageRunnable"),
        mMessage(aMessage),
        mService(aService) {}

  NS_DECL_NSIRUNNABLE

 private:
  nsCOMPtr<nsIConsoleMessage> mMessage;
  RefPtr<nsConsoleService> mService;
};

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);
  }

  mService->SetDoneDelivering();

  return NS_OK;
}

}  // namespace

// nsIConsoleService methods
NS_IMETHODIMP
nsConsoleService::LogMessage(nsIConsoleMessage* aMessage) {
  return LogMessageWithMode(aMessage, nsIConsoleService::OutputToLog);
}

// 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;
    }
  }

  RefPtr<LogMessageRunnable> r;
  nsCOMPtr<nsIConsoleMessage> retiredMessage;

  /*
   * Lock while updating buffer, and while taking snapshot of
   * listeners array.
   */

  {
    MutexAutoLock lock(mLock);

#if defined(ANDROID)
    if (StaticPrefs::consoleservice_logcat() && aOutputMode == OutputToLog) {
      nsCString msg;
      aMessage->ToString(msg);

      /** 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";
      }

      uint32_t logLevel = 0;
      aMessage->GetLogLevel(&logLevel);

      android_LogPriority logPriority = ANDROID_LOG_INFO;
      switch (logLevel) {
        case nsIConsoleMessage::debug:
          logPriority = ANDROID_LOG_DEBUG;
          break;
        case nsIConsoleMessage::info:
          logPriority = ANDROID_LOG_INFO;
          break;
        case nsIConsoleMessage::warn:
          logPriority = ANDROID_LOG_WARN;
          break;
        case nsIConsoleMessage::error:
          logPriority = ANDROID_LOG_ERROR;
          break;
      }

      __android_log_print(logPriority, appName.get(), "%s", msg.get());
    }
#endif
#ifdef XP_WIN
    if (gLoggingToDebugger && IsDebuggerPresent()) {
      nsString msg;
      aMessage->GetMessageMoz(msg);
      msg.Append('\n');
      OutputDebugStringW(msg.get());
    }
#endif

    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;
  }

  JS::Rooted<JSObject*> global(
      cx, js::CheckedUnwrapDynamic(&targetGlobal.toObject(), cx));
  if (!global) {
    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 (const auto& listener : mListeners.Values()) {
    aListeners.AppendObject(listener);
  }
}

NS_IMETHODIMP
nsConsoleService::LogStringMessage(const char16_t* aMessage) {
  if (!gLoggingEnabled) {
    return NS_OK;
  }

  RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(
      aMessage ? nsDependentString(aMessage) : EmptyString()));
  return LogMessage(msg);
}

NS_IMETHODIMP
nsConsoleService::GetMessageArray(
    nsTArray<RefPtr<nsIConsoleMessage>>& aMessages) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  MutexAutoLock lock(mLock);

  if (mMessages.isEmpty()) {
    return NS_OK;
  }

  MOZ_ASSERT(mCurrentSize <= mMaximumSize);
  aMessages.SetCapacity(mCurrentSize);

  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;
  }

  nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
  MOZ_ASSERT(canonical);

  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;
  }

  nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);

  MutexAutoLock lock(mLock);

  return mListeners.Remove(canonical)
             ? NS_OK
             // Unregistering a listener that was never registered?
             : NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsConsoleService::Reset() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  /*
   * Make sure nobody trips into the buffer while it's being reset
   */

  MutexAutoLock lock(mLock);

  ClearMessages();
  return NS_OK;
}

NS_IMETHODIMP
nsConsoleService::ResetWindow(uint64_t windowInnerId) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  ClearMessagesForWindowID(windowInnerId);
  return NS_OK;
}

NS_IMETHODIMP
nsConsoleService::Observe(nsISupports* aSubject, const char* 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.
  } else if (!strcmp(aTopic, "inner-window-destroyed")) {
    nsCOMPtr<nsISupportsPRUint64> supportsInt = do_QueryInterface(aSubject);
    MOZ_ASSERT(supportsInt);

    uint64_t windowId;
    MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId));

    ClearMessagesForWindowID(windowId);
  } else {
    MOZ_CRASH();
  }
  return NS_OK;
}

94%


¤ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge