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


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


#include "mozilla/dom/Console.h"
#include "mozilla/dom/ConsoleInstance.h"
#include "mozilla/dom/ConsoleBinding.h"
#include "ConsoleCommon.h"

#include "js/Array.h"               // JS::GetArrayLength, JS::NewArrayObject
#include "js/PropertyAndElement.h"  // JS_DefineElement, JS_DefineProperty, JS_GetElement
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/WorkletGlobalScope.h"
#include "mozilla/dom/WorkletImpl.h"
#include "mozilla/dom/WorkletThread.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/JSObjectHolder.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_devtools.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDOMNavigationTiming.h"
#include "nsGlobalWindowInner.h"
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "xpcpublic.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsProxyRelease.h"
#include "nsReadableUtils.h"

#include "nsIConsoleAPIStorage.h"
#include "nsIException.h"  // for nsIStackFrame
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadContext.h"
#include "nsISensitiveInfoHiddenURI.h"
#include "nsISupportsPrimitives.h"
#include "nsIWebNavigation.h"
#include "nsIXPConnect.h"

// The maximum allowed number of concurrent timers per page.
#define MAX_PAGE_TIMERS 10000

// The maximum allowed number of concurrent counters per page.
#define MAX_PAGE_COUNTERS 10000

// The maximum stacktrace depth when populating the stacktrace array used for
// console.trace().
#define DEFAULT_MAX_STACKTRACE_DEPTH 200

// This tags are used in the Structured Clone Algorithm to move js values from
// worker thread to main thread
#define CONSOLE_TAG_BLOB JS_SCTAG_USER_MIN

// This value is taken from ConsoleAPIStorage.js
#define STORAGE_MAX_EVENTS 1000

using namespace mozilla::dom::exceptions;

namespace mozilla::dom {

struct ConsoleStructuredCloneData {
  nsCOMPtr<nsIGlobalObject> mGlobal;
  nsTArray<RefPtr<BlobImpl>> mBlobs;
};

static void ComposeAndStoreGroupName(JSContext* aCx,
                                     const Sequence<JS::Value>& aData,
                                     nsAString& aName,
                                     nsTArray<nsString>* aGroupStack);
static bool UnstoreGroupName(nsAString& aName, nsTArray<nsString>* aGroupStack);

static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
                             Sequence<JS::Value>& aSequence,
                             Sequence<nsString>& aStyles);

static JS::Value CreateCounterOrResetCounterValue(JSContext* aCx,
                                                  const nsAString& aCountLabel,
                                                  uint32_t aCountValue);

/**
 * Console API in workers uses the Structured Clone Algorithm to move any value
 * from the worker thread to the main-thread. Some object cannot be moved and,
 * in these cases, we convert them to strings.
 * It's not the best, but at least we are able to show something.
 */


class ConsoleCallData final {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData)

  ConsoleCallData(Console::MethodName aName, const nsAString& aString,
                  Console* aConsole)
      : mMutex("ConsoleCallData"),
        mConsoleID(aConsole->mConsoleID),
        mPrefix(aConsole->mPrefix),
        mMethodName(aName),
        mMicroSecondTimeStamp(JS_Now()),
        mStartTimerValue(0),
        mStartTimerStatus(Console::eTimerUnknown),
        mLogTimerDuration(0),
        mLogTimerStatus(Console::eTimerUnknown),
        mCountValue(MAX_PAGE_COUNTERS),
        mIDType(eUnknown),
        mOuterIDNumber(0),
        mInnerIDNumber(0),
        mMethodString(aString) {}

  void SetIDs(uint64_t aOuterID, uint64_t aInnerID) MOZ_REQUIRES(mMutex) {
    MOZ_ASSERT(mIDType == eUnknown);

    mOuterIDNumber = aOuterID;
    mInnerIDNumber = aInnerID;
    mIDType = eNumber;
  }

  void SetIDs(const nsAString& aOuterID, const nsAString& aInnerID)
      MOZ_REQUIRES(mMutex) {
    MOZ_ASSERT(mIDType == eUnknown);

    mOuterIDString = aOuterID;
    mInnerIDString = aInnerID;
    mIDType = eString;
  }

  void SetOriginAttributes(const OriginAttributes& aOriginAttributes)
      MOZ_REQUIRES(mMutex) {
    mOriginAttributes = aOriginAttributes;
  }

  void SetAddonId(nsIPrincipal* aPrincipal) MOZ_REQUIRES(mMutex) {
    nsAutoString addonId;
    aPrincipal->GetAddonId(addonId);

    mAddonId = addonId;
  }

  void AssertIsOnOwningThread() const {
    NS_ASSERT_OWNINGTHREAD(ConsoleCallData);
  }

  Mutex mMutex;

  const nsString mConsoleID MOZ_GUARDED_BY(mMutex);
  const nsString mPrefix MOZ_GUARDED_BY(mMutex);

  const Console::MethodName mMethodName MOZ_GUARDED_BY(mMutex);
  int64_t mMicroSecondTimeStamp MOZ_GUARDED_BY(mMutex);

  // These values are set in the owning thread and they contain the timestamp of
  // when the new timer has started, the name of it and the status of the
  // creation of it. If status is false, something went wrong. User
  // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
  // monotonicTimer from Performance.now();
  // They will be set on the owning thread and never touched again on that
  // thread. They will be used in order to create a ConsoleTimerStart dictionary
  // when console.time() is used.
  DOMHighResTimeStamp mStartTimerValue MOZ_GUARDED_BY(mMutex);
  nsString mStartTimerLabel MOZ_GUARDED_BY(mMutex);
  Console::TimerStatus mStartTimerStatus MOZ_GUARDED_BY(mMutex);

  // These values are set in the owning thread and they contain the duration,
  // the name and the status of the LogTimer method. If status is false,
  // something went wrong. They will be set on the owning thread and never
  // touched again on that thread. They will be used in order to create a
  // ConsoleTimerLogOrEnd dictionary. This members are set when
  // console.timeEnd() or console.timeLog() are called.
  double mLogTimerDuration MOZ_GUARDED_BY(mMutex);
  nsString mLogTimerLabel MOZ_GUARDED_BY(mMutex);
  Console::TimerStatus mLogTimerStatus MOZ_GUARDED_BY(mMutex);

  // These 2 values are set by IncreaseCounter or ResetCounter on the owning
  // thread and they are used by CreateCounterOrResetCounterValue.
  // These members are set when console.count() or console.countReset() are
  // called.
  nsString mCountLabel MOZ_GUARDED_BY(mMutex);
  uint32_t mCountValue MOZ_GUARDED_BY(mMutex);

  // The concept of outerID and innerID is misleading because when a
  // ConsoleCallData is created from a window, these are the window IDs, but
  // when the object is created from a SharedWorker, a ServiceWorker or a
  // subworker of a ChromeWorker these IDs are the type of worker and the
  // filename of the callee.
  // In Console.sys.mjs the ID is 'jsm'.
  enum { eString, eNumber, eUnknown } mIDType MOZ_GUARDED_BY(mMutex);

  uint64_t mOuterIDNumber MOZ_GUARDED_BY(mMutex);
  nsString mOuterIDString MOZ_GUARDED_BY(mMutex);

  uint64_t mInnerIDNumber MOZ_GUARDED_BY(mMutex);
  nsString mInnerIDString MOZ_GUARDED_BY(mMutex);

  OriginAttributes mOriginAttributes MOZ_GUARDED_BY(mMutex);

  nsString mAddonId MOZ_GUARDED_BY(mMutex);

  const nsString mMethodString MOZ_GUARDED_BY(mMutex);

  // Stack management is complicated, because we want to do it as
  // lazily as possible.  Therefore, we have the following behavior:
  // 1)  mTopStackFrame is initialized whenever we have any JS on the stack
  // 2)  mReifiedStack is initialized if we're created in a worker.
  // 3)  mStack is set (possibly to null if there is no JS on the stack) if
  //     we're created on main thread.
  Maybe<ConsoleStackEntry> mTopStackFrame MOZ_GUARDED_BY(mMutex);
  Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack MOZ_GUARDED_BY(mMutex);
  nsCOMPtr<nsIStackFrame> mStack MOZ_GUARDED_BY(mMutex);

 private:
  ~ConsoleCallData() = default;

  NS_DECL_OWNINGTHREAD;
};

// MainThreadConsoleData instances are created on the Console thread and
// referenced from both main and Console threads in order to provide the same
// object for any ConsoleRunnables relating to the same Console.  A Console
// owns a MainThreadConsoleData; MainThreadConsoleData does not keep its
// Console alive.
class MainThreadConsoleData final {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MainThreadConsoleData);

  JSObject* GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
  // This method must receive aCx and aArguments in the same JS::Compartment.
  void ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
                       const Sequence<JS::Value>& aArguments);

 private:
  ~MainThreadConsoleData() {
    NS_ReleaseOnMainThread("MainThreadConsoleData::mStorage",
                           mStorage.forget());
    NS_ReleaseOnMainThread("MainThreadConsoleData::mSandbox",
                           mSandbox.forget());
  }

  // All members, except for mRefCnt, are accessed only on the main thread,
  // except in MainThreadConsoleData destruction, at which point there are no
  // other references.
  nsCOMPtr<nsIConsoleAPIStorage> mStorage;
  RefPtr<JSObjectHolder> mSandbox;
  nsTArray<nsString> mGroupStack;
};

// This base class must be extended for Worker and for Worklet.
class ConsoleRunnable : public StructuredCloneHolderBase {
 public:
  ~ConsoleRunnable() override {
    MOZ_ASSERT(!mClonedData.mGlobal,
               "mClonedData.mGlobal is set and cleared in a main thread scope");
    // Clear the StructuredCloneHolderBase class.
    Clear();
  }

 protected:
  JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader,
                              const JS::CloneDataPolicy& aCloneDataPolicy,
                              uint32_t aTag, uint32_t aIndex) override {
    AssertIsOnMainThread();

    if (aTag == CONSOLE_TAG_BLOB) {
      MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex);

      JS::Rooted<JS::Value> val(aCx);
      {
        nsCOMPtr<nsIGlobalObject> global = mClonedData.mGlobal;
        RefPtr<Blob> blob =
            Blob::Create(global, mClonedData.mBlobs.ElementAt(aIndex));
        if (!ToJSValue(aCx, blob, &val)) {
          return nullptr;
        }
      }

      return &val.toObject();
    }

    MOZ_CRASH("No other tags are supported.");
    return nullptr;
  }

  bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter,
                          JS::Handle<JSObject*> aObj,
                          bool* aSameProcessScopeRequired) override {
    RefPtr<Blob> blob;
    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
      if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
                                         mClonedData.mBlobs.Length()))) {
        return false;
      }

      mClonedData.mBlobs.AppendElement(blob->Impl());
      return true;
    }

    if (!JS_ObjectNotWritten(aWriter, aObj)) {
      return false;
    }

    JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
    JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
    if (NS_WARN_IF(!jsString)) {
      return false;
    }

    if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
      return false;
    }

    return true;
  }

  // Helper method for CallData
  void ProcessCallData(JSContext* aCx, MainThreadConsoleData* aConsoleData,
                       ConsoleCallData* aCallData) {
    AssertIsOnMainThread();

    ConsoleCommon::ClearException ce(aCx);

    // This is the same policy as when writing from the other side, in
    // WriteData.
    JS::CloneDataPolicy cloneDataPolicy;
    cloneDataPolicy.allowIntraClusterClonableSharedObjects();
    cloneDataPolicy.allowSharedMemoryObjects();

    JS::Rooted<JS::Value> argumentsValue(aCx);
    if (!Read(aCx, &argumentsValue, cloneDataPolicy)) {
      return;
    }

    MOZ_ASSERT(argumentsValue.isObject());

    JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());

    uint32_t length;
    if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
      return;
    }

    Sequence<JS::Value> values;
    SequenceRooter<JS::Value> arguments(aCx, &values);

    for (uint32_t i = 0; i < length; ++i) {
      JS::Rooted<JS::Value> value(aCx);

      if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
        return;
      }

      if (!values.AppendElement(value, fallible)) {
        return;
      }
    }

    MOZ_ASSERT(values.Length() == length);

    aConsoleData->ProcessCallData(aCx, aCallData, values);
  }

  // Generic
  bool WriteArguments(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
    ConsoleCommon::ClearException ce(aCx);

    JS::Rooted<JSObject*> arguments(
        aCx, JS::NewArrayObject(aCx, aArguments.Length()));
    if (NS_WARN_IF(!arguments)) {
      return false;
    }

    JS::Rooted<JS::Value> arg(aCx);
    for (uint32_t i = 0; i < aArguments.Length(); ++i) {
      arg = aArguments[i];
      if (NS_WARN_IF(
              !JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE))) {
        return false;
      }
    }

    JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
    return WriteData(aCx, value);
  }

  // Helper method for Profile calls
  void ProcessProfileData(JSContext* aCx, Console::MethodName aMethodName,
                          const nsAString& aAction) {
    AssertIsOnMainThread();

    ConsoleCommon::ClearException ce(aCx);

    JS::Rooted<JS::Value> argumentsValue(aCx);
    bool ok = Read(aCx, &argumentsValue);
    mClonedData.mGlobal = nullptr;

    if (!ok) {
      return;
    }

    MOZ_ASSERT(argumentsValue.isObject());
    JS::Rooted<JSObject*> argumentsObj(aCx, &argumentsValue.toObject());
    if (NS_WARN_IF(!argumentsObj)) {
      return;
    }

    uint32_t length;
    if (!JS::GetArrayLength(aCx, argumentsObj, &length)) {
      return;
    }

    Sequence<JS::Value> arguments;

    for (uint32_t i = 0; i < length; ++i) {
      JS::Rooted<JS::Value> value(aCx);

      if (!JS_GetElement(aCx, argumentsObj, i, &value)) {
        return;
      }

      if (!arguments.AppendElement(value, fallible)) {
        return;
      }
    }

    Console::ProfileMethodMainthread(aCx, aAction, arguments);
  }

  bool WriteData(JSContext* aCx, JS::Handle<JS::Value> aValue) {
    // We use structuredClone to send the JSValue to the main-thread, in order
    // to store it into the Console API Service. The consumer will be the
    // console panel in the devtools and, because of this, we want to allow the
    // cloning of sharedArrayBuffers and WASM modules.
    JS::CloneDataPolicy cloneDataPolicy;
    cloneDataPolicy.allowIntraClusterClonableSharedObjects();
    cloneDataPolicy.allowSharedMemoryObjects();

    if (NS_WARN_IF(
            !Write(aCx, aValue, JS::UndefinedHandleValue, cloneDataPolicy))) {
      // Ignore the message.
      return false;
    }

    return true;
  }

  ConsoleStructuredCloneData mClonedData;
};

class ConsoleWorkletRunnable : public Runnable, public ConsoleRunnable {
 protected:
  explicit ConsoleWorkletRunnable(Console* aConsole)
      : Runnable("dom::console::ConsoleWorkletRunnable"),
        mConsoleData(aConsole->GetOrCreateMainThreadData()) {
    WorkletThread::AssertIsOnWorkletThread();
    nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(aConsole->mGlobal);
    MOZ_ASSERT(global);
    mWorkletImpl = global->Impl();
    MOZ_ASSERT(mWorkletImpl);
  }

  ~ConsoleWorkletRunnable() override = default;

 protected:
  RefPtr<MainThreadConsoleData> mConsoleData;

  RefPtr<WorkletImpl> mWorkletImpl;
};

// This runnable appends a CallData object into the Console queue running on
// the main-thread.
class ConsoleCallDataWorkletRunnable final : public ConsoleWorkletRunnable {
 public:
  static already_AddRefed<ConsoleCallDataWorkletRunnable> Create(
      JSContext* aCx, Console* aConsole, ConsoleCallData* aConsoleData,
      const Sequence<JS::Value>& aArguments) {
    WorkletThread::AssertIsOnWorkletThread();

    RefPtr<ConsoleCallDataWorkletRunnable> runnable =
        new ConsoleCallDataWorkletRunnable(aConsole, aConsoleData);

    if (!runnable->WriteArguments(aCx, aArguments)) {
      return nullptr;
    }

    return runnable.forget();
  }

 private:
  ConsoleCallDataWorkletRunnable(Console* aConsole, ConsoleCallData* aCallData)
      : ConsoleWorkletRunnable(aConsole), mCallData(aCallData) {
    WorkletThread::AssertIsOnWorkletThread();
    MOZ_ASSERT(aCallData);
    aCallData->AssertIsOnOwningThread();

    const WorkletLoadInfo& loadInfo = mWorkletImpl->LoadInfo();
    mCallData->SetIDs(loadInfo.OuterWindowID(), loadInfo.InnerWindowID());
  }

  ~ConsoleCallDataWorkletRunnable() override = default;

  NS_IMETHOD Run() override {
    AssertIsOnMainThread();
    AutoJSAPI jsapi;
    jsapi.Init();
    JSContext* cx = jsapi.cx();

    {
      MutexAutoLock lock(mCallData->mMutex);

      JSObject* sandbox =
          mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
      JS::Rooted<JSObject*> global(cx, sandbox);
      if (NS_WARN_IF(!global)) {
        return NS_ERROR_FAILURE;
      }

      // The CreateSandbox call returns a proxy to the actual sandbox object. We
      // don't need a proxy here.
      global = js::UncheckedUnwrap(global);
      JSAutoRealm ar(cx, global);

      // We don't need to set a parent object in mCallData bacause there are not
      // DOM objects exposed to worklet.

      ProcessCallData(cx, mConsoleData, mCallData);
    }

    return NS_OK;
  }

  RefPtr<ConsoleCallData> mCallData;
};

class ConsoleWorkerRunnable : public WorkerProxyToMainThreadRunnable,
                              public ConsoleRunnable {
 public:
  explicit ConsoleWorkerRunnable(Console* aConsole)
      : mConsoleData(aConsole->GetOrCreateMainThreadData()) {}

  ~ConsoleWorkerRunnable() override = default;

  bool Dispatch(JSContext* aCx, const Sequence<JS::Value>& aArguments) {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);

    if (NS_WARN_IF(!WriteArguments(aCx, aArguments))) {
      RunBackOnWorkerThreadForCleanup(workerPrivate);
      return false;
    }

    if (NS_WARN_IF(!WorkerProxyToMainThreadRunnable::Dispatch(workerPrivate))) {
      // RunBackOnWorkerThreadForCleanup() will be called by
      // WorkerProxyToMainThreadRunnable::Dispatch().
      return false;
    }

    return true;
  }

 protected:
  void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
    MOZ_ASSERT(aWorkerPrivate);
    AssertIsOnMainThread();

    // Walk up to our containing page
    WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker();

    nsCOMPtr<nsPIDOMWindowInner> window = wp->GetWindow();
    if (!window) {
      RunWindowless(aWorkerPrivate);
    } else {
      RunWithWindow(aWorkerPrivate, window);
    }
  }

  void RunWithWindow(WorkerPrivate* aWorkerPrivate,
                     nsPIDOMWindowInner* aWindow) {
    MOZ_ASSERT(aWorkerPrivate);
    AssertIsOnMainThread();

    AutoJSAPI jsapi;
    MOZ_ASSERT(aWindow);

    RefPtr<nsGlobalWindowInner> win = nsGlobalWindowInner::Cast(aWindow);
    if (NS_WARN_IF(!jsapi.Init(win))) {
      return;
    }

    nsCOMPtr<nsPIDOMWindowOuter> outerWindow = aWindow->GetOuterWindow();
    if (NS_WARN_IF(!outerWindow)) {
      return;
    }

    RunConsole(jsapi.cx(), aWindow->AsGlobal(), aWorkerPrivate, outerWindow,
               aWindow);
  }

  void RunWindowless(WorkerPrivate* aWorkerPrivate) {
    MOZ_ASSERT(aWorkerPrivate);
    AssertIsOnMainThread();

    WorkerPrivate* wp = aWorkerPrivate->GetTopLevelWorker();

    MOZ_ASSERT(!wp->GetWindow());

    AutoJSAPI jsapi;
    jsapi.Init();

    JSContext* cx = jsapi.cx();

    JS::Rooted<JSObject*> global(
        cx, mConsoleData->GetOrCreateSandbox(cx, wp->GetPrincipal()));
    if (NS_WARN_IF(!global)) {
      return;
    }

    // The GetOrCreateSandbox call returns a proxy to the actual sandbox object.
    // We don't need a proxy here.
    global = js::UncheckedUnwrap(global);

    JSAutoRealm ar(cx, global);

    nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(global);
    if (NS_WARN_IF(!globalObject)) {
      return;
    }

    RunConsole(cx, globalObject, aWorkerPrivate, nullptr, nullptr);
  }

  void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  // This method is called in the main-thread.
  virtual void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
                          WorkerPrivate* aWorkerPrivate,
                          nsPIDOMWindowOuter* aOuterWindow,
                          nsPIDOMWindowInner* aInnerWindow) = 0;

  bool ForMessaging() const override { return true; }

  RefPtr<MainThreadConsoleData> mConsoleData;
};

// This runnable appends a CallData object into the Console queue running on
// the main-thread.
class ConsoleCallDataWorkerRunnable final : public ConsoleWorkerRunnable {
 public:
  ConsoleCallDataWorkerRunnable(Console* aConsole, ConsoleCallData* aCallData)
      : ConsoleWorkerRunnable(aConsole), mCallData(aCallData) {
    MOZ_ASSERT(aCallData);
    mCallData->AssertIsOnOwningThread();
  }

 private:
  ~ConsoleCallDataWorkerRunnable() override = default;

  void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
                  WorkerPrivate* aWorkerPrivate,
                  nsPIDOMWindowOuter* aOuterWindow,
                  nsPIDOMWindowInner* aInnerWindow) override {
    MOZ_ASSERT(aGlobal);
    MOZ_ASSERT(aWorkerPrivate);
    AssertIsOnMainThread();

    // The windows have to run in parallel.
    MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);

    {
      MutexAutoLock lock(mCallData->mMutex);
      if (aOuterWindow) {
        mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
      } else {
        ConsoleStackEntry frame;
        if (mCallData->mTopStackFrame) {
          frame = *mCallData->mTopStackFrame;
        }

        nsCString id = frame.mFilename;
        nsString innerID;
        if (aWorkerPrivate->IsSharedWorker()) {
          innerID = u"SharedWorker"_ns;
        } else if (aWorkerPrivate->IsServiceWorker()) {
          innerID = u"ServiceWorker"_ns;
          // Use scope as ID so the webconsole can decide if the message should
          // show up per tab
          id = aWorkerPrivate->ServiceWorkerScope();
        } else {
          innerID = u"Worker"_ns;
        }

        mCallData->SetIDs(NS_ConvertUTF8toUTF16(id), innerID);
      }

      mClonedData.mGlobal = aGlobal;

      ProcessCallData(aCx, mConsoleData, mCallData);

      mClonedData.mGlobal = nullptr;
    }
  }

  RefPtr<ConsoleCallData> mCallData;
};

// This runnable calls ProfileMethod() on the console on the main-thread.
class ConsoleProfileWorkletRunnable final : public ConsoleWorkletRunnable {
 public:
  static already_AddRefed<ConsoleProfileWorkletRunnable> Create(
      JSContext* aCx, Console* aConsole, Console::MethodName aName,
      const nsAString& aAction, const Sequence<JS::Value>& aArguments) {
    WorkletThread::AssertIsOnWorkletThread();

    RefPtr<ConsoleProfileWorkletRunnable> runnable =
        new ConsoleProfileWorkletRunnable(aConsole, aName, aAction);

    if (!runnable->WriteArguments(aCx, aArguments)) {
      return nullptr;
    }

    return runnable.forget();
  }

 private:
  ConsoleProfileWorkletRunnable(Console* aConsole, Console::MethodName aName,
                                const nsAString& aAction)
      : ConsoleWorkletRunnable(aConsole), mName(aName), mAction(aAction) {
    MOZ_ASSERT(aConsole);
  }

  NS_IMETHOD Run() override {
    AssertIsOnMainThread();

    AutoJSAPI jsapi;
    jsapi.Init();
    JSContext* cx = jsapi.cx();

    JSObject* sandbox =
        mConsoleData->GetOrCreateSandbox(cx, mWorkletImpl->Principal());
    JS::Rooted<JSObject*> global(cx, sandbox);
    if (NS_WARN_IF(!global)) {
      return NS_ERROR_FAILURE;
    }

    // The CreateSandbox call returns a proxy to the actual sandbox object. We
    // don't need a proxy here.
    global = js::UncheckedUnwrap(global);

    JSAutoRealm ar(cx, global);

    // We don't need to set a parent object in mCallData bacause there are not
    // DOM objects exposed to worklet.
    ProcessProfileData(cx, mName, mAction);

    return NS_OK;
  }

  Console::MethodName mName;
  nsString mAction;
};

// This runnable calls ProfileMethod() on the console on the main-thread.
class ConsoleProfileWorkerRunnable final : public ConsoleWorkerRunnable {
 public:
  ConsoleProfileWorkerRunnable(Console* aConsole, Console::MethodName aName,
                               const nsAString& aAction)
      : ConsoleWorkerRunnable(aConsole), mName(aName), mAction(aAction) {
    MOZ_ASSERT(aConsole);
  }

 private:
  void RunConsole(JSContext* aCx, nsIGlobalObject* aGlobal,
                  WorkerPrivate* aWorkerPrivate,
                  nsPIDOMWindowOuter* aOuterWindow,
                  nsPIDOMWindowInner* aInnerWindow) override {
    AssertIsOnMainThread();
    MOZ_ASSERT(aGlobal);

    mClonedData.mGlobal = aGlobal;

    ProcessProfileData(aCx, mName, mAction);

    mClonedData.mGlobal = nullptr;
  }

  Console::MethodName mName;
  nsString mAction;
};

NS_IMPL_CYCLE_COLLECTION_CLASS(Console)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDumpFunction)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
  tmp->Shutdown();
  tmp->mArgumentStorage.clearAndFree();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDumpFunction)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
  for (uint32_t i = 0; i < tmp->mArgumentStorage.length(); ++i) {
    tmp->mArgumentStorage[i].Trace(aCallbacks, aClosure);
  }
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

/* static */
already_AddRefed<Console> Console::Create(JSContext* aCx,
                                          nsPIDOMWindowInner* aWindow,
                                          ErrorResult& aRv) {
  MOZ_ASSERT_IF(NS_IsMainThread(), aWindow);

  uint64_t outerWindowID = 0;
  uint64_t innerWindowID = 0;

  if (aWindow) {
    innerWindowID = aWindow->WindowID();

    // Without outerwindow any console message coming from this object will not
    // shown in the devtools webconsole. But this should be fine because
    // probably we are shutting down, or the window is CCed/GCed.
    nsPIDOMWindowOuter* outerWindow = aWindow->GetOuterWindow();
    if (outerWindow) {
      outerWindowID = outerWindow->WindowID();
    }
  }

  RefPtr<Console> console = new Console(aCx, nsGlobalWindowInner::Cast(aWindow),
                                        outerWindowID, innerWindowID);
  console->Initialize(aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  return console.forget();
}

/* static */
already_AddRefed<Console> Console::CreateForWorklet(JSContext* aCx,
                                                    nsIGlobalObject* aGlobal,
                                                    uint64_t aOuterWindowID,
                                                    uint64_t aInnerWindowID,
                                                    ErrorResult& aRv) {
  WorkletThread::AssertIsOnWorkletThread();

  RefPtr<Console> console =
      new Console(aCx, aGlobal, aOuterWindowID, aInnerWindowID);
  console->Initialize(aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  return console.forget();
}

Console::Console(JSContext* aCx, nsIGlobalObject* aGlobal,
                 uint64_t aOuterWindowID, uint64_t aInnerWindowID,
                 const nsAString& aPrefix)
    : mGlobal(aGlobal),
      mOuterID(aOuterWindowID),
      mInnerID(aInnerWindowID),
      mDumpToStdout(false),
      mLogModule(nullptr),
      mPrefix(aPrefix),
      mChromeInstance(false),
      mCurrentLogLevel(WebIDLLogLevelToInteger(ConsoleLogLevel::All)),
      mStatus(eUnknown),
      mCreationTimeStamp(TimeStamp::Now()) {
  // Let's enable the dumping to stdout by default for chrome.
  if (nsContentUtils::ThreadsafeIsSystemCaller(aCx)) {
    mDumpToStdout = StaticPrefs::devtools_console_stdout_chrome();
  } else {
    mDumpToStdout = StaticPrefs::devtools_console_stdout_content();
  }

  // By default, the console uses "console" MOZ_LOG module name,
  // but ConsoleInstance may pass a custom prefix which we will use a module
  // name.
  mLogModule = mPrefix.IsEmpty()
                   ? LogModule::Get("console")
                   : LogModule::Get(NS_ConvertUTF16toUTF8(mPrefix).get());

  mozilla::HoldJSObjects(this);
}

Console::~Console() {
  AssertIsOnOwningThread();
  Shutdown();
  mozilla::DropJSObjects(this);
}

void Console::Initialize(ErrorResult& aRv) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mStatus == eUnknown);

  if (NS_IsMainThread()) {
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    if (NS_WARN_IF(!obs)) {
      aRv.Throw(NS_ERROR_FAILURE);
      return;
    }

    if (mInnerID) {
      aRv = obs->AddObserver(this"inner-window-destroyed"true);
      if (NS_WARN_IF(aRv.Failed())) {
        return;
      }
    }

    aRv = obs->AddObserver(this"memory-pressure"true);
    if (NS_WARN_IF(aRv.Failed())) {
      return;
    }
  }

  mStatus = eInitialized;
}

void Console::Shutdown() {
  AssertIsOnOwningThread();

  if (mStatus == eUnknown || mStatus == eShuttingDown) {
    return;
  }

  if (NS_IsMainThread()) {
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    if (obs) {
      obs->RemoveObserver(this"inner-window-destroyed");
      obs->RemoveObserver(this"memory-pressure");
    }
  }

  mTimerRegistry.Clear();
  mCounterRegistry.Clear();

  ClearStorage();
  mCallDataStorage.Clear();

  mStatus = eShuttingDown;
}

NS_IMETHODIMP
Console::Observe(nsISupports* aSubject, const char* aTopic,
                 const char16_t* aData) {
  AssertIsOnMainThread();

  if (!strcmp(aTopic, "inner-window-destroyed")) {
    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);

    uint64_t innerID;
    nsresult rv = wrapper->GetData(&innerID);
    NS_ENSURE_SUCCESS(rv, rv);

    if (innerID == mInnerID) {
      Shutdown();
    }

    return NS_OK;
  }

  if (!strcmp(aTopic, "memory-pressure")) {
    ClearStorage();
    return NS_OK;
  }

  return NS_OK;
}

void Console::ClearStorage() {
  mCallDataStorage.Clear();
  mArgumentStorage.clearAndFree();
}

#define METHOD(name, string)                                          \
  /* static */ void Console::name(const GlobalObject& aGlobal,        \
                                  const Sequence<JS::Value>& aData) { \
    Method(aGlobal, Method##name, nsLiteralString(string), aData);    \
  }

METHOD(Log, u"log")
METHOD(Info, u"info")
METHOD(Warn, u"warn")
METHOD(Error, u"error")
METHOD(Exception, u"exception")
METHOD(Debug, u"debug")
METHOD(Table, u"table")
METHOD(Trace, u"trace")

// Displays an interactive listing of all the properties of an object.
METHOD(Dir, u"dir");
METHOD(Dirxml, u"dirxml");

METHOD(Group, u"group")
METHOD(GroupCollapsed, u"groupCollapsed")

#undef METHOD

/* static */
void Console::Clear(const GlobalObject& aGlobal) {
  const Sequence<JS::Value> data;
  Method(aGlobal, MethodClear, u"clear"_ns, data);
}

/* static */
void Console::GroupEnd(const GlobalObject& aGlobal) {
  const Sequence<JS::Value> data;
  Method(aGlobal, MethodGroupEnd, u"groupEnd"_ns, data);
}

/* static */
void Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel) {
  StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime, u"time"_ns);
}

/* static */
void Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel) {
  StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
               u"timeEnd"_ns);
}

/* static */
void Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
                      const Sequence<JS::Value>& aData) {
  StringMethod(aGlobal, aLabel, aData, MethodTimeLog, u"timeLog"_ns);
}

/* static */
void Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
                           const Sequence<JS::Value>& aData,
                           MethodName aMethodName,
                           const nsAString& aMethodString) {
  RefPtr<Console> console = GetConsole(aGlobal);
  if (!console) {
    return;
  }

  console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
                                aMethodString);
}

void Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
                                   const Sequence<JS::Value>& aData,
                                   MethodName aMethodName,
                                   const nsAString& aMethodString) {
  ConsoleCommon::ClearException ce(aCx);

  Sequence<JS::Value> data;
  SequenceRooter<JS::Value> rooter(aCx, &data);

  JS::Rooted<JS::Value> value(aCx);
  if (!dom::ToJSValue(aCx, aLabel, &value)) {
    return;
  }

  if (!data.AppendElement(value, fallible)) {
    return;
  }

  for (uint32_t i = 0; i < aData.Length(); ++i) {
    if (!data.AppendElement(aData[i], fallible)) {
      return;
    }
  }

  MethodInternal(aCx, aMethodName, aMethodString, data);
}

/* static */
void Console::TimeStamp(const GlobalObject& aGlobal,
                        const JS::Handle<JS::Value> aData) {
  JSContext* cx = aGlobal.Context();

  ConsoleCommon::ClearException ce(cx);

  Sequence<JS::Value> data;
  SequenceRooter<JS::Value> rooter(cx, &data);

  if (aData.isString() && !data.AppendElement(aData, fallible)) {
    return;
  }

  Method(aGlobal, MethodTimeStamp, u"timeStamp"_ns, data);
}

/* static */
void Console::Profile(const GlobalObject& aGlobal,
                      const Sequence<JS::Value>& aData) {
  ProfileMethod(aGlobal, MethodProfile, u"profile"_ns, aData);
}

/* static */
void Console::ProfileEnd(const GlobalObject& aGlobal,
                         const Sequence<JS::Value>& aData) {
  ProfileMethod(aGlobal, MethodProfileEnd, u"profileEnd"_ns, aData);
}

/* static */
void Console::ProfileMethod(const GlobalObject& aGlobal, MethodName aName,
                            const nsAString& aAction,
                            const Sequence<JS::Value>& aData) {
  RefPtr<Console> console = GetConsole(aGlobal);
  if (!console) {
    return;
  }

  JSContext* cx = aGlobal.Context();
  console->ProfileMethodInternal(cx, aName, aAction, aData);
}

void Console::ProfileMethodInternal(JSContext* aCx, MethodName aMethodName,
                                    const nsAString& aAction,
                                    const Sequence<JS::Value>& aData) {
  if (!ShouldProceed(aMethodName)) {
    return;
  }

  MaybeExecuteDumpFunction(aCx, aMethodName, aAction, aData, nullptr,
                           DOMHighResTimeStamp(0.0));

  if (WorkletThread::IsOnWorkletThread()) {
    RefPtr<ConsoleProfileWorkletRunnable> runnable =
        ConsoleProfileWorkletRunnable::Create(aCx, this, aMethodName, aAction,
                                              aData);
    if (!runnable) {
      return;
    }

    NS_DispatchToMainThread(runnable.forget());
    return;
  }

  if (!NS_IsMainThread()) {
    // Here we are in a worker thread.
    RefPtr<ConsoleProfileWorkerRunnable> runnable =
        new ConsoleProfileWorkerRunnable(this, aMethodName, aAction);

    runnable->Dispatch(aCx, aData);
    return;
  }

  ProfileMethodMainthread(aCx, aAction, aData);
}

// static
void Console::ProfileMethodMainthread(JSContext* aCx, const nsAString& aAction,
                                      const Sequence<JS::Value>& aData) {
  MOZ_ASSERT(NS_IsMainThread());
  ConsoleCommon::ClearException ce(aCx);

  RootedDictionary<ConsoleProfileEvent> event(aCx);
  event.mAction = aAction;
  event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);

  event.mArguments.Construct();
  Sequence<JS::Value>& sequence = event.mArguments.Value();

  for (uint32_t i = 0; i < aData.Length(); ++i) {
    if (!sequence.AppendElement(aData[i], fallible)) {
      return;
    }
  }

  JS::Rooted<JS::Value> eventValue(aCx);
  if (!ToJSValue(aCx, event, &eventValue)) {
    return;
  }

  JS::Rooted<JSObject*> eventObj(aCx, &eventValue.toObject());
  MOZ_ASSERT(eventObj);

  if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue,
                         JSPROP_ENUMERATE)) {
    return;
  }

  nsIXPConnect* xpc = nsContentUtils::XPConnect();
  nsCOMPtr<nsISupports> wrapper;
  const nsIID& iid = NS_GET_IID(nsISupports);

  if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
    return;
  }

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
  }
}

/* static */
void Console::Assert(const GlobalObject& aGlobal, bool aCondition,
                     const Sequence<JS::Value>& aData) {
  if (!aCondition) {
    Method(aGlobal, MethodAssert, u"assert"_ns, aData);
  }
}

/* static */
void Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel) {
  StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
               u"count"_ns);
}

/* static */
void Console::CountReset(const GlobalObject& aGlobal, const nsAString& aLabel) {
  StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCountReset,
               u"countReset"_ns);
}

namespace {

void StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
                            ConsoleStackEntry& aStackEntry) {
  MOZ_ASSERT(aStackFrame);

  aStackFrame->GetFilename(aCx, aStackEntry.mFilename);
  aStackEntry.mSourceId = aStackFrame->GetSourceId(aCx);
  aStackEntry.mLineNumber = aStackFrame->GetLineNumber(aCx);
  aStackEntry.mColumnNumber = aStackFrame->GetColumnNumber(aCx);

  aStackFrame->GetName(aCx, aStackEntry.mFunctionName);

  nsString cause;
  aStackFrame->GetAsyncCause(aCx, cause);
  if (!cause.IsEmpty()) {
    aStackEntry.mAsyncCause.Construct(cause);
  }
}

void ReifyStack(JSContext* aCx, nsIStackFrame* aStack,
                nsTArray<ConsoleStackEntry>& aRefiedStack) {
  nsCOMPtr<nsIStackFrame> stack(aStack);

  while (stack) {
    ConsoleStackEntry& data = *aRefiedStack.AppendElement();
    StackFrameToStackEntry(aCx, stack, data);

    nsCOMPtr<nsIStackFrame> caller = stack->GetCaller(aCx);

    if (!caller) {
      caller = stack->GetAsyncCaller(aCx);
    }
    stack.swap(caller);
  }
}

}  // anonymous namespace

// Queue a call to a console method. See the CALL_DELAY constant.
/* static */
void Console::Method(const GlobalObject& aGlobal, MethodName aMethodName,
                     const nsAString& aMethodString,
                     const Sequence<JS::Value>& aData) {
  RefPtr<Console> console = GetConsole(aGlobal);
  if (!console) {
    return;
  }

  console->MethodInternal(aGlobal.Context(), aMethodName, aMethodString, aData);
}

void Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
                             const nsAString& aMethodString,
                             const Sequence<JS::Value>& aData) {
  if (!ShouldProceed(aMethodName)) {
    return;
  }

  AssertIsOnOwningThread();

  ConsoleCommon::ClearException ce(aCx);

  RefPtr<ConsoleCallData> callData =
      new ConsoleCallData(aMethodName, aMethodString, this);

  MutexAutoLock lock(callData->mMutex);

  if (!StoreCallData(aCx, callData, aData)) {
    return;
  }

  OriginAttributes oa;

  if (NS_IsMainThread()) {
    if (mGlobal) {
      // Save the principal's OriginAttributes in the console event data
      // so that we will be able to filter messages by origin attributes.
      nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
      if (NS_WARN_IF(!sop)) {
        return;
      }

      nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
      if (NS_WARN_IF(!principal)) {
        return;
      }

      oa = principal->OriginAttributesRef();
      callData->SetAddonId(principal);

#ifdef DEBUG
      if (!principal->IsSystemPrincipal()) {
        nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mGlobal);
        if (webNav) {
          nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
          MOZ_ASSERT(loadContext);

          bool pb;
          if (NS_SUCCEEDED(loadContext->GetUsePrivateBrowsing(&pb))) {
            MOZ_ASSERT(pb == oa.IsPrivateBrowsing());
          }
        }
      }
#endif
    }
  } else if (WorkletThread::IsOnWorkletThread()) {
    nsCOMPtr<WorkletGlobalScope> global = do_QueryInterface(mGlobal);
    MOZ_ASSERT(global);
    oa = global->Impl()->OriginAttributesRef();
  } else {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);
    oa = workerPrivate->GetOriginAttributes();
  }

  callData->SetOriginAttributes(oa);

  JS::StackCapture captureMode =
      ShouldIncludeStackTrace(aMethodName)
          ? JS::StackCapture(JS::MaxFrames(DEFAULT_MAX_STACKTRACE_DEPTH))
          : JS::StackCapture(JS::FirstSubsumedFrame(aCx));
  nsCOMPtr<nsIStackFrame> stack = CreateStack(aCx, std::move(captureMode));

  if (stack) {
    callData->mTopStackFrame.emplace();
    StackFrameToStackEntry(aCx, stack, *callData->mTopStackFrame);
  }

  if (NS_IsMainThread()) {
    callData->mStack = stack;
  } else {
    // nsIStackFrame is not threadsafe, so we need to snapshot it now,
    // before we post our runnable to the main thread.
    callData->mReifiedStack.emplace();
    ReifyStack(aCx, stack, *callData->mReifiedStack);
  }

  DOMHighResTimeStamp monotonicTimer = 0.0;

  // Monotonic timer for 'time', 'timeLog' and 'timeEnd'
  if ((aMethodName == MethodTime || aMethodName == MethodTimeLog ||
       aMethodName == MethodTimeEnd || aMethodName == MethodTimeStamp) &&
      !MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
    return;
  }

  if (aMethodName == MethodTime && !aData.IsEmpty()) {
    callData->mStartTimerStatus =
        StartTimer(aCx, aData[0], monotonicTimer, callData->mStartTimerLabel,
                   &callData->mStartTimerValue);
  }

  else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
    callData->mLogTimerStatus =
        LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
                 &callData->mLogTimerDuration, true /* Cancel timer */);
  }

  else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
    callData->mLogTimerStatus =
        LogTimer(aCx, aData[0], monotonicTimer, callData->mLogTimerLabel,
                 &callData->mLogTimerDuration, false /* Cancel timer */);
  }

  else if (aMethodName == MethodCount) {
    callData->mCountValue = IncreaseCounter(aCx, aData, callData->mCountLabel);
    if (!callData->mCountValue) {
      return;
    }
  }

  else if (aMethodName == MethodCountReset) {
    callData->mCountValue = ResetCounter(aCx, aData, callData->mCountLabel);
    if (callData->mCountLabel.IsEmpty()) {
      return;
    }
  }

  // Before processing this CallData differently, it's time to call the dump
  // function.
  //
  // Only log the stack trace for console.trace() and console.assert()
  if (aMethodName == MethodTrace || aMethodName == MethodAssert) {
    MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, stack,
                             monotonicTimer);
  } else {
    MaybeExecuteDumpFunction(aCx, aMethodName, aMethodString, aData, nullptr,
                             monotonicTimer);
  }

  if (NS_IsMainThread()) {
    if (mInnerID) {
      callData->SetIDs(mOuterID, mInnerID);
    } else if (!mPassedInnerID.IsEmpty()) {
      callData->SetIDs(u"jsm"_ns, mPassedInnerID);
    } else {
      nsAutoCString filename;
      if (callData->mTopStackFrame.isSome()) {
        filename = callData->mTopStackFrame->mFilename;
      }
      callData->SetIDs(u"jsm"_ns, NS_ConvertUTF8toUTF16(filename));
    }

    GetOrCreateMainThreadData()->ProcessCallData(aCx, callData, aData);

    // Just because we don't want to expose
    // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
    // cleanup the mCallDataStorage:
    UnstoreCallData(callData);
    return;
  }

  if (WorkletThread::IsOnWorkletThread()) {
    RefPtr<ConsoleCallDataWorkletRunnable> runnable =
        ConsoleCallDataWorkletRunnable::Create(aCx, this, callData, aData);
    if (!runnable) {
      return;
    }

    NS_DispatchToMainThread(runnable);
    return;
  }

  // We do this only in workers for now.
  NotifyHandler(aCx, aData, callData);

  if (StaticPrefs::dom_worker_console_dispatch_events_to_main_thread()) {
    RefPtr<ConsoleCallDataWorkerRunnable> runnable =
        new ConsoleCallDataWorkerRunnable(this, callData);
    Unused << NS_WARN_IF(!runnable->Dispatch(aCx, aData));
  }
}

MainThreadConsoleData* Console::GetOrCreateMainThreadData() {
  AssertIsOnOwningThread();

  if (!mMainThreadData) {
    mMainThreadData = new MainThreadConsoleData();
  }

  return mMainThreadData;
}

// We store information to lazily compute the stack in the reserved slots of
// LazyStackGetter.  The first slot always stores a JS object: it's either the
// JS wrapper of the nsIStackFrame or the actual reified stack representation.
// The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
// reified the stack yet, or an UndefinedValue() otherwise.
enum { SLOT_STACKOBJ, SLOT_RAW_STACK };

bool LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp) {
  JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
  JS::Rooted<JSObject*> callee(aCx, &args.callee());

  JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
  if (v.isUndefined()) {
    // Already reified.
    args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
    return true;
  }

  nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
  nsTArray<ConsoleStackEntry> reifiedStack;
  ReifyStack(aCx, stack, reifiedStack);

  JS::Rooted<JS::Value> stackVal(aCx);
  if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
    return false;
  }

  MOZ_ASSERT(stackVal.isObject());

  js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
  js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());

  args.rval().set(stackVal);
  return true;
}

void MainThreadConsoleData::ProcessCallData(
    JSContext* aCx, ConsoleCallData* aData,
    const Sequence<JS::Value>& aArguments) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aData);
  aData->mMutex.AssertCurrentThreadOwns();

  JS::Rooted<JS::Value> eventValue(aCx);

  // We want to create a console event object and pass it to our
  // nsIConsoleAPIStorage implementation.  We want to define some accessor
  // properties on this object, and those will need to keep an nsIStackFrame
  // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
  // further, passing untrusted objects to system code is likely to run afoul of
  // Object Xrays.  So we want to wrap in a system-principal scope here.  But
  // which one?  We could cheat and try to get the underlying JSObject* of
  // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
  // with explicit permission from the XPConnect module owner.  If you're
  // tempted to do that anywhere else, talk to said module owner first.

  // aCx and aArguments are in the same compartment.
  JS::Rooted<JSObject*> targetScope(aCx, xpc::PrivilegedJunkScope());
  if (NS_WARN_IF(!Console::PopulateConsoleNotificationInTheTargetScope(
          aCx, aArguments, targetScope, &eventValue, aData, &mGroupStack))) {
    return;
  }

  if (!mStorage) {
    mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
  }

  if (!mStorage) {
    NS_WARNING("Failed to get the ConsoleAPIStorage service.");
    return;
  }

  nsAutoString innerID;

  MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
  if (aData->mIDType == ConsoleCallData::eString) {
    innerID = aData->mInnerIDString;
  } else {
    MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
    innerID.AppendInt(aData->mInnerIDNumber);
  }

  if (aData->mMethodName == Console::MethodClear) {
    DebugOnly<nsresult> rv = mStorage->ClearEvents(innerID);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
  }

  if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
    NS_WARNING("Failed to record a console event.");
  }
}

/* static */
bool Console::PopulateConsoleNotificationInTheTargetScope(
    JSContext* aCx, const Sequence<JS::Value>& aArguments,
    JS::Handle<JSObject*> aTargetScope,
    JS::MutableHandle<JS::Value> aEventValue, ConsoleCallData* aData,
    nsTArray<nsString>* aGroupStack) {
  MOZ_ASSERT(aCx);
  MOZ_ASSERT(aData);
  MOZ_ASSERT(aTargetScope);
  MOZ_ASSERT(JS_IsGlobalObject(aTargetScope));

  aData->mMutex.AssertCurrentThreadOwns();

  ConsoleStackEntry frame;
  if (aData->mTopStackFrame) {
    frame = *aData->mTopStackFrame;
  }

  ConsoleCommon::ClearException ce(aCx);
  RootedDictionary<ConsoleEvent> event(aCx);

  event.mAddonId = aData->mAddonId;

  event.mID.Construct();
  event.mInnerID.Construct();

  event.mChromeContext = nsContentUtils::ThreadsafeIsSystemCaller(aCx);

  if (aData->mIDType == ConsoleCallData::eString) {
    event.mID.Value().SetAsString() = aData->mOuterIDString;
    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
  } else if (aData->mIDType == ConsoleCallData::eNumber) {
    event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
    event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
  } else {
    // aData->mIDType can be eUnknown when we dispatch notifications via
    // mConsoleEventNotifier.
    event.mID.Value().SetAsUnsignedLongLong() = 0;
    event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
  }

  event.mConsoleID = aData->mConsoleID;
  event.mLevel = aData->mMethodString;
  event.mFilename = frame.mFilename;
  event.mPrefix = aData->mPrefix;

  nsCOMPtr<nsIURI> filenameURI;
  nsAutoCString pass;
  if (NS_IsMainThread() &&
      NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI =
        do_QueryInterface(filenameURI);
    nsAutoCString spec;
    if (safeURI && NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
      event.mFilename = spec;
    }
  }

  event.mSourceId = frame.mSourceId;
  event.mLineNumber = frame.mLineNumber;
  event.mColumnNumber = frame.mColumnNumber;
  event.mFunctionName = frame.mFunctionName;
  event.mTimeStamp = aData->mMicroSecondTimeStamp / PR_USEC_PER_MSEC;
  event.mMicroSecondTimeStamp = aData->mMicroSecondTimeStamp;
  event.mPrivate = aData->mOriginAttributes.IsPrivateBrowsing();

  switch (aData->mMethodName) {
    case MethodLog:
    case MethodInfo:
    case MethodWarn:
    case MethodError:
    case MethodException:
    case MethodDebug:
    case MethodAssert:
    case MethodGroup:
    case MethodGroupCollapsed:
    case MethodTrace:
      event.mArguments.Construct();
      event.mStyles.Construct();
      if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
                                       event.mArguments.Value(),
                                       event.mStyles.Value()))) {
        return false;
      }

      break;

    default:
      event.mArguments.Construct();
      if (NS_WARN_IF(
              !event.mArguments.Value().AppendElements(aArguments, fallible))) {
        return false;
      }
  }

  if (aData->mMethodName == MethodGroup ||
      aData->mMethodName == MethodGroupCollapsed) {
    ComposeAndStoreGroupName(aCx, event.mArguments.Value(), event.mGroupName,
                             aGroupStack);
  }

  else if (aData->mMethodName == MethodGroupEnd) {
    if (!UnstoreGroupName(event.mGroupName, aGroupStack)) {
      return false;
    }
  }

  else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
    event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
                                         aData->mStartTimerStatus);
  }

  else if ((aData->mMethodName == MethodTimeEnd ||
            aData->mMethodName == MethodTimeLog) &&
           !aArguments.IsEmpty()) {
    event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
                                            aData->mLogTimerDuration,
                                            aData->mLogTimerStatus);
  }

  else if (aData->mMethodName == MethodCount ||
           aData->mMethodName == MethodCountReset) {
    event.mCounter = CreateCounterOrResetCounterValue(aCx, aData->mCountLabel,
                                                      aData->mCountValue);
  }

  JSAutoRealm ar2(aCx, aTargetScope);

  if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
    return false;
  }

  JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
  if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
                                    JSPROP_ENUMERATE))) {
    return false;
  }

  if (ShouldIncludeStackTrace(aData->mMethodName)) {
    // Now define the "stacktrace" property on eventObj.  There are two cases
    // here.  Either we came from a worker and have a reified stack, or we want
    // to define a getter that will lazily reify the stack.
    if (aData->mReifiedStack) {
      JS::Rooted<JS::Value> stacktrace(aCx);
      if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
          NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
                                        JSPROP_ENUMERATE))) {
        return false;
      }
    } else {
      JSFunction* fun =
          js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0, "stacktrace");
      if (NS_WARN_IF(!fun)) {
        return false;
      }

      JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));

      // We want to store our stack in the function and have it stay alive.  But
      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
      // wrapper and the raw pointer: the former will keep the latter alive.
      JS::Rooted<JS::Value> stackVal(aCx);
      nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack, &stackVal);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return false;
      }

      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
                                    JS::PrivateValue(aData->mStack.get()));

      if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", funObj,
                                        nullptr, JSPROP_ENUMERATE))) {
        return false;
      }
    }
  }

  return true;
}

namespace {

// Helper method for ProcessArguments. Flushes output, if non-empty, to
// aSequence.
bool FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence,
                 nsString& aOutput) {
  if (!aOutput.IsEmpty()) {
    JS::Rooted<JSString*> str(
        aCx, JS_NewUCStringCopyN(aCx, aOutput.get(), aOutput.Length()));
    if (NS_WARN_IF(!str)) {
      return false;
    }

    if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
      return false;
    }

    aOutput.Truncate();
  }

  return true;
}

}  // namespace

static void MakeFormatString(nsCString& aFormat, int32_t aInteger,
                             int32_t aMantissa, char aCh) {
  aFormat.Append('%');
  if (aInteger >= 0) {
    aFormat.AppendInt(aInteger);
  }

  if (aMantissa >= 0) {
    aFormat.Append('.');
    aFormat.AppendInt(aMantissa);
  }

  aFormat.Append(aCh);
}

// If the first JS::Value of the array is a string, this method uses it to
// format a string. The supported sequences are:
//   %s    - string
//   %d,%i - integer
//   %f    - double
//   %o,%O - a JS object.
//   %c    - style string.
// The output is an array where any object is a separated item, the rest is
// unified in a format string.
// Example if the input is:
//   "string: %s, integer: %d, object: %o, double: %f", 's', 1, window, 0.9
// The output will be:
//   [ "string: s, integer: 1, object: ", window, ", double: 0.9" ]
//
// The aStyles array is populated with the style strings that the function
// finds based the format string. The index of the styles matches the indexes
// of elements that need the custom styling from aSequence. For elements with
// no custom styling the array is padded with null elements.
static bool ProcessArguments(JSContext* aCx, const Sequence<JS::Value>& aData,
                             Sequence<JS::Value>& aSequence,
                             Sequence<nsString>& aStyles) {
  // This method processes the arguments as format strings (%d, %i, %s...)
  // only if the first element of them is a valid and not-empty string.

  if (aData.IsEmpty()) {
    return true;
  }

  if (aData.Length() == 1 || !aData[0].isString()) {
    return aSequence.AppendElements(aData, fallible);
  }

  JS::Rooted<JS::Value> format(aCx, aData[0]);
  JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
  if (NS_WARN_IF(!jsString)) {
    return false;
  }

  nsAutoJSString string;
  if (NS_WARN_IF(!string.init(aCx, jsString))) {
    return false;
  }

  if (string.IsEmpty()) {
    return aSequence.AppendElements(aData, fallible);
  }

  nsString::const_iterator start, end;
  string.BeginReading(start);
  string.EndReading(end);

  nsString output;
  uint32_t index = 1;

  while (start != end) {
    if (*start != '%') {
      output.Append(*start);
      ++start;
      continue;
    }

    ++start;
    if (start == end) {
      output.Append('%');
      break;
    }

    if (*start == '%') {
      output.Append(*start);
      ++start;
      continue;
    }

    nsAutoString tmp;
    tmp.Append('%');

    int32_t integer = -1;
    int32_t mantissa = -1;

    // Let's parse %<number>.<number> for %d and %f
    if (*start >= '0' && *start <= '9') {
      integer = 0;

      do {
        integer = integer * 10 + *start - '0';
        tmp.Append(*start);
        ++start;
      } while (*start >= '0' && *start <= '9' && start != end);
    }

    if (start == end) {
      output.Append(tmp);
      break;
    }

    if (*start == '.') {
      tmp.Append(*start);
      ++start;

      if (start == end) {
        output.Append(tmp);
        break;
      }

      // '.' must be followed by a number.
      if (*start < '0' || *start > '9') {
        output.Append(tmp);
        continue;
      }

      mantissa = 0;

      do {
        mantissa = mantissa * 10 + *start - '0';
        tmp.Append(*start);
        ++start;
      } while (*start >= '0' && *start <= '9' && start != end);

      if (start == end) {
        output.Append(tmp);
        break;
      }
    }

    char ch = *start;
    tmp.Append(ch);
    ++start;

    switch (ch) {
      case 'o':
      case 'O': {
        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
          return false;
        }

        JS::Rooted<JS::Value> v(aCx);
        if (index < aData.Length()) {
          v = aData[index++];
        }

        if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
          return false;
        }

        break;
      }

      case 'c': {
        // If there isn't any output but there's already a style, then
        // discard the previous style and use the next one instead.
        if (output.IsEmpty() && !aStyles.IsEmpty()) {
          aStyles.RemoveLastElement();
        }

        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
          return false;
        }

        if (index < aData.Length()) {
          JS::Rooted<JS::Value> v(aCx, aData[index++]);
          JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
          if (NS_WARN_IF(!jsString)) {
            return false;
          }

          int32_t diff = aSequence.Length() - aStyles.Length();
          if (diff > 0) {
            for (int32_t i = 0; i < diff; i++) {
              if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
                return false;
              }
            }
          }

          nsAutoJSString string;
          if (NS_WARN_IF(!string.init(aCx, jsString))) {
            return false;
          }

          if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
            return false;
          }
        }
        break;
      }

      case 's':
        if (index < aData.Length()) {
          JS::Rooted<JS::Value> value(aCx, aData[index++]);
          JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
          if (NS_WARN_IF(!jsString)) {
            return false;
          }

          nsAutoJSString v;
          if (NS_WARN_IF(!v.init(aCx, jsString))) {
            return false;
          }

          output.Append(v);
        }
        break;

      case 'd':
      case 'i':
        if (index < aData.Length()) {
          JS::Rooted<JS::Value> value(aCx, aData[index++]);

          if (value.isBigInt()) {
            JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
            if (NS_WARN_IF(!jsString)) {
              return false;
            }

            nsAutoJSString v;
            if (NS_WARN_IF(!v.init(aCx, jsString))) {
              return false;
            }
            output.Append(v);
            break;
          }

          int32_t v;
          if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
            return false;
          }

          nsCString format;
          MakeFormatString(format, integer, mantissa, 'd');
          output.AppendPrintf(format.get(), v);
        }
        break;

      case 'f':
        if (index < aData.Length()) {
          JS::Rooted<JS::Value> value(aCx, aData[index++]);

          double v;
          if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
            return false;
          }

          // nspr returns "nan", but we want to expose it as "NaN"
          if (std::isnan(v)) {
            output.AppendFloat(v);
          } else {
            nsCString format;
            MakeFormatString(format, integer, std::min(mantissa, 15), 'f');
            output.AppendPrintf(format.get(), v);
          }
        }
        break;

      default:
        output.Append(tmp);
        break;
    }
  }

  if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
    return false;
  }

  // Discard trailing style element if there is no output to apply it to.
  if (aStyles.Length() > aSequence.Length()) {
    aStyles.TruncateLength(aSequence.Length());
  }

  // The rest of the array, if unused by the format string.
  for (; index < aData.Length(); ++index) {
    if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
      return false;
    }
  }

  return true;
}

// Stringify and Concat all the JS::Value in a single string using ' ' as
// separator. The new group name will be stored in aGroupStack array.
static void ComposeAndStoreGroupName(JSContext* aCx,
                                     const Sequence<JS::Value>& aData,
                                     nsAString& aName,
                                     nsTArray<nsString>* aGroupStack) {
  StringJoinAppend(
      aName, u" "_ns, aData, [aCx](nsAString& dest, const JS::Value& valueRef) {
        JS::Rooted<JS::Value> value(aCx, valueRef);
        JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
        if (!jsString) {
          return;
        }

        nsAutoJSString string;
        if (!string.init(aCx, jsString)) {
          return;
        }

        dest.Append(string);
      });

  aGroupStack->AppendElement(aName);
}

// Remove the last group name and return that name. It returns false if
// aGroupStack is empty.
static bool UnstoreGroupName(nsAString& aName,
                             nsTArray<nsString>* aGroupStack) {
  if (aGroupStack->IsEmpty()) {
    return false;
  }

  aName = aGroupStack->PopLastElement();
  return true;
}

Console::TimerStatus Console::StartTimer(JSContext* aCx, const JS::Value& aName,
                                         DOMHighResTimeStamp aTimestamp,
--> --------------------

--> maximum size reached

--> --------------------

96%


¤ Dauer der Verarbeitung: 0.31 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

Produkte

     letzte wissenschaftliche Artikel weltweit
     Neues von dieser Firma

letze Version des Agenda Kalenders

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

letze Version der Autor Authoringsoftware

     neueste Artikel weltweit
     letze Version des Bille Abgleichprogramms
     Bilder

Jenseits des Üblichen ....

Besucher

Besucher

Monitoring

Montastic status badge