/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* 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 "nsPerformanceStats.h"

#include "nsMemory.h"
#include "nsLiteralString.h"
#include "nsCRTGlue.h"
#include "nsServiceManagerUtils.h"

#include "nsCOMArray.h"
#include "nsContentUtils.h"
#include "nsIMutableArray.h"
#include "nsReadableUtils.h"

#include "jsapi.h"
#include "nsJSUtils.h"
#include "xpcpublic.h"
#include "jspubtd.h"

#include "nsIDOMWindow.h"
#include "nsGlobalWindow.h"
#include "nsRefreshDriver.h"

#include "mozilla/Unused.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Services.h"

#if defined(XP_WIN)
#include <processthreadsapi.h>
#include <windows.h>
#else
#include <unistd.h>
#endif // defined(XP_WIN)

#if defined(XP_MACOSX)
#include <mach/mach_init.h>
#include <mach/mach_interface.h>
#include <mach/mach_port.h>
#include <mach/mach_types.h>
#include <mach/message.h>
#include <mach/thread_info.h>
#elif defined(XP_UNIX)
#include <sys/time.h>
#include <sys/resource.h>
#endif // defined(XP_UNIX)
/* ------------------------------------------------------
 *
 * Utility functions.
 *
 */

namespace {

/**
 * Get the private window for the current compartment.
 *
 * @return null if the code is not executed in a window or in
 * case of error, a nsPIDOMWindow otherwise.
 */
already_AddRefed<nsPIDOMWindowOuter>
GetPrivateWindow(JSContext* cx) {
  nsGlobalWindow* win = xpc::CurrentWindowOrNull(cx);
  if (!win) {
    return nullptr;
  }

  nsPIDOMWindowOuter* outer = win->AsInner()->GetOuterWindow();
  if (!outer) {
    return nullptr;
  }

  nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
  if (!top) {
    return nullptr;
  }

  return top.forget();
}

bool
URLForGlobal(JSContext* cx, JS::Handle<JSObject*> global, nsAString& url) {
  nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global);
  if (!principal) {
    return false;
  }

  nsCOMPtr<nsIURI> uri;
  nsresult rv = principal->GetURI(getter_AddRefs(uri));
  if (NS_FAILED(rv) || !uri) {
    return false;
  }

  nsAutoCString spec;
  rv = uri->GetSpec(spec);
  if (NS_FAILED(rv)) {
    return false;
  }

  url.Assign(NS_ConvertUTF8toUTF16(spec));
  return true;
}

/**
 * Extract a somewhat human-readable name from the current context.
 */
void
CompartmentName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) {
  // Attempt to use the URL as name.
  if (URLForGlobal(cx, global, name)) {
    return;
  }

  // Otherwise, fallback to XPConnect's less readable but more
  // complete naming scheme.
  nsAutoCString cname;
  xpc::GetCurrentCompartmentName(cx, cname);
  name.Assign(NS_ConvertUTF8toUTF16(cname));
}

/**
 * Generate a unique-to-the-application identifier for a group.
 */
void
GenerateUniqueGroupId(const JSContext* cx, uint64_t uid, uint64_t processId, nsAString& groupId) {
  uint64_t contextId = reinterpret_cast<uintptr_t>(cx);

  groupId.AssignLiteral("process: ");
  groupId.AppendInt(processId);
  groupId.AppendLiteral(", thread: ");
  groupId.AppendInt(contextId);
  groupId.AppendLiteral(", group: ");
  groupId.AppendInt(uid);
}

static const char* TOPICS[] = {
  "profile-before-change",
  "quit-application",
  "quit-application-granted",
  "xpcom-will-shutdown"
};

} // namespace

/* ------------------------------------------------------
 *
 * class nsPerformanceObservationTarget
 *
 */


NS_IMPL_ISUPPORTS(nsPerformanceObservationTarget, nsIPerformanceObservable)



NS_IMETHODIMP
nsPerformanceObservationTarget::GetTarget(nsIPerformanceGroupDetails** _result) {
  if (mDetails) {
    NS_IF_ADDREF(*_result = mDetails);
  }
  return NS_OK;
};

void
nsPerformanceObservationTarget::SetTarget(nsPerformanceGroupDetails* details) {
  MOZ_ASSERT(!mDetails);
  mDetails = details;
};

NS_IMETHODIMP
nsPerformanceObservationTarget::AddJankObserver(nsIPerformanceObserver* observer) {
  if (!mObservers.append(observer)) {
    MOZ_CRASH();
  }
  return NS_OK;
};

NS_IMETHODIMP
nsPerformanceObservationTarget::RemoveJankObserver(nsIPerformanceObserver* observer) {
  for (auto iter = mObservers.begin(), end = mObservers.end(); iter < end; ++iter) {
    if (*iter == observer) {
      mObservers.erase(iter);
      return NS_OK;
    }
  }
  return NS_OK;
};

bool
nsPerformanceObservationTarget::HasObservers() const {
  return !mObservers.empty();
}

void
nsPerformanceObservationTarget::NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity) {
  // Copy the vector to make sure that it won't change under our feet.
  mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> observers;
  if (!observers.appendAll(mObservers)) {
    MOZ_CRASH();
  }

  // Now actually notify.
  for (auto iter = observers.begin(), end = observers.end(); iter < end; ++iter) {
    nsCOMPtr<nsIPerformanceObserver> observer = *iter;
    mozilla::Unused << observer->Observe(source, gravity);
  }
}

/* ------------------------------------------------------
 *
 * class nsGroupHolder
 *
 */

nsPerformanceObservationTarget*
nsGroupHolder::ObservationTarget() {
  if (!mPendingObservationTarget) {
    mPendingObservationTarget = new nsPerformanceObservationTarget();
  }
  return mPendingObservationTarget;
}

nsPerformanceGroup*
nsGroupHolder::GetGroup() {
  return mGroup;
}

void
nsGroupHolder::SetGroup(nsPerformanceGroup* group) {
  MOZ_ASSERT(!mGroup);
  mGroup = group;
  group->SetObservationTarget(ObservationTarget());
  mPendingObservationTarget->SetTarget(group->Details());
}

/* ------------------------------------------------------
 *
 * struct PerformanceData
 *
 */

PerformanceData::PerformanceData()
  : mTotalUserTime(0)
  , mTotalSystemTime(0)
  , mTotalCPOWTime(0)
  , mTicks(0)
{
  mozilla::PodArrayZero(mDurations);
}

/* ------------------------------------------------------
 *
 * class nsPerformanceGroupDetails
 *
 */

NS_IMPL_ISUPPORTS(nsPerformanceGroupDetails, nsIPerformanceGroupDetails)

const nsAString&
nsPerformanceGroupDetails::Name() const {
  return mName;
}

const nsAString&
nsPerformanceGroupDetails::GroupId() const {
  return mGroupId;
}

const nsAString&
nsPerformanceGroupDetails::AddonId() const {
  return mAddonId;
}

uint64_t
nsPerformanceGroupDetails::WindowId() const {
  return mWindowId;
}

uint64_t
nsPerformanceGroupDetails::ProcessId() const {
  return mProcessId;
}

bool
nsPerformanceGroupDetails::IsSystem() const {
  return mIsSystem;
}

bool
nsPerformanceGroupDetails::IsAddon() const {
  return mAddonId.Length() != 0;
}

bool
nsPerformanceGroupDetails::IsWindow() const {
  return mWindowId != 0;
}

bool
nsPerformanceGroupDetails::IsContentProcess() const {
  return XRE_GetProcessType() == GeckoProcessType_Content;
}

/* readonly attribute AString name; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetName(nsAString& aName) {
  aName.Assign(Name());
  return NS_OK;
};

/* readonly attribute AString groupId; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetGroupId(nsAString& aGroupId) {
  aGroupId.Assign(GroupId());
  return NS_OK;
};

/* readonly attribute AString addonId; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetAddonId(nsAString& aAddonId) {
  aAddonId.Assign(AddonId());
  return NS_OK;
};

/* readonly attribute uint64_t windowId; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetWindowId(uint64_t *aWindowId) {
  *aWindowId = WindowId();
  return NS_OK;
}

/* readonly attribute bool isSystem; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetIsSystem(bool *_retval) {
  *_retval = IsSystem();
  return NS_OK;
}

/*
  readonly attribute unsigned long long processId;
*/
NS_IMETHODIMP
nsPerformanceGroupDetails::GetProcessId(uint64_t* processId) {
  *processId = ProcessId();
  return NS_OK;
}

/* readonly attribute bool IsContentProcess; */
NS_IMETHODIMP
nsPerformanceGroupDetails::GetIsContentProcess(bool *_retval) {
  *_retval = IsContentProcess();
  return NS_OK;
}


/* ------------------------------------------------------
 *
 * class nsPerformanceStats
 *
 */

class nsPerformanceStats final: public nsIPerformanceStats
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIPERFORMANCESTATS
  NS_FORWARD_NSIPERFORMANCEGROUPDETAILS(mDetails->)

  nsPerformanceStats(nsPerformanceGroupDetails* item,
                     const PerformanceData& aPerformanceData)
    : mDetails(item)
    , mPerformanceData(aPerformanceData)
  {
  }


private:
  RefPtr<nsPerformanceGroupDetails> mDetails;
  PerformanceData mPerformanceData;

  ~nsPerformanceStats() {}
};

NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats, nsIPerformanceGroupDetails)

/* readonly attribute unsigned long long totalUserTime; */
NS_IMETHODIMP
nsPerformanceStats::GetTotalUserTime(uint64_t *aTotalUserTime) {
  *aTotalUserTime = mPerformanceData.mTotalUserTime;
  return NS_OK;
};

/* readonly attribute unsigned long long totalSystemTime; */
NS_IMETHODIMP
nsPerformanceStats::GetTotalSystemTime(uint64_t *aTotalSystemTime) {
  *aTotalSystemTime = mPerformanceData.mTotalSystemTime;
  return NS_OK;
};

/* readonly attribute unsigned long long totalCPOWTime; */
NS_IMETHODIMP
nsPerformanceStats::GetTotalCPOWTime(uint64_t *aCpowTime) {
  *aCpowTime = mPerformanceData.mTotalCPOWTime;
  return NS_OK;
};

/* readonly attribute unsigned long long ticks; */
NS_IMETHODIMP
nsPerformanceStats::GetTicks(uint64_t *aTicks) {
  *aTicks = mPerformanceData.mTicks;
  return NS_OK;
};

/* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */
NS_IMETHODIMP
nsPerformanceStats::GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) {
  const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations);
  if (aCount) {
    *aCount = length;
  }
  *aNumberOfOccurrences = new uint64_t[length];
  for (size_t i = 0; i < length; ++i) {
    (*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i];
  }
  return NS_OK;
};


/* ------------------------------------------------------
 *
 * struct nsPerformanceSnapshot
 *
 */

class nsPerformanceSnapshot final : public nsIPerformanceSnapshot
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIPERFORMANCESNAPSHOT

  nsPerformanceSnapshot() {}

  /**
   * Append statistics to the list of components data.
   */
  void AppendComponentsStats(nsIPerformanceStats* stats);

  /**
   * Set the statistics attached to process data.
   */
  void SetProcessStats(nsIPerformanceStats* group);

private:
  ~nsPerformanceSnapshot() {}

private:
  /**
   * The data for all components.
   */
  nsCOMArray<nsIPerformanceStats> mComponentsData;

  /**
   * The data for the process.
   */
  nsCOMPtr<nsIPerformanceStats> mProcessData;
};

NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)


/* nsIArray getComponentsData (); */
NS_IMETHODIMP
nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
{
  const size_t length = mComponentsData.Length();
  nsCOMPtr<nsIMutableArray> components = do_CreateInstance(NS_ARRAY_CONTRACTID);
  for (size_t i = 0; i < length; ++i) {
    nsCOMPtr<nsIPerformanceStats> stats = mComponentsData[i];
    mozilla::DebugOnly<nsresult> rv = components->AppendElement(stats, false);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
  components.forget(aComponents);
  return NS_OK;
}

/* nsIPerformanceStats getProcessData (); */
NS_IMETHODIMP
nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
{
  NS_IF_ADDREF(*aProcess = mProcessData);
  return NS_OK;
}

void
nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats)
{
  mComponentsData.AppendElement(stats);
}

void
nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats)
{
  mProcessData = stats;
}



/* ------------------------------------------------------
 *
 * class PerformanceAlert
 *
 */
class PerformanceAlert final: public nsIPerformanceAlert {
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIPERFORMANCEALERT

  PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source);
private:
  ~PerformanceAlert() {}

  const uint32_t mReason;

  // The highest values reached by this group since the latest alert,
  // in microseconds.
  const uint64_t mHighestJank;
  const uint64_t mHighestCPOW;
};

NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert);

PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source)
  : mReason(reason)
  , mHighestJank(source->HighestRecentJank())
  , mHighestCPOW(source->HighestRecentCPOW())
{ }

NS_IMETHODIMP
PerformanceAlert::GetHighestJank(uint64_t* result) {
  *result = mHighestJank;
  return NS_OK;
}

NS_IMETHODIMP
PerformanceAlert::GetHighestCPOW(uint64_t* result) {
  *result = mHighestCPOW;
  return NS_OK;
}

NS_IMETHODIMP
PerformanceAlert::GetReason(uint32_t* result) {
  *result = mReason;
  return NS_OK;
}
/* ------------------------------------------------------
 *
 * class PendingAlertsCollector
 *
 */

/**
 * A timer callback in charge of collecting the groups in
 * `mPendingAlerts` and triggering dispatch of performance alerts.
 */
class PendingAlertsCollector final: public nsITimerCallback {
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSITIMERCALLBACK

  explicit PendingAlertsCollector(nsPerformanceStatsService* service)
    : mService(service)
    , mPending(false)
  { }

  nsresult Start(uint32_t timerDelayMS);
  nsresult Dispose();

private:
  ~PendingAlertsCollector() {}

  RefPtr<nsPerformanceStatsService> mService;
  bool mPending;

  nsCOMPtr<nsITimer> mTimer;

  mozilla::Vector<uint64_t> mJankLevels;
};

NS_IMPL_ISUPPORTS(PendingAlertsCollector, nsITimerCallback);

NS_IMETHODIMP
PendingAlertsCollector::Notify(nsITimer*) {
  mPending = false;
  mService->NotifyJankObservers(mJankLevels);
  return NS_OK;
}

nsresult
PendingAlertsCollector::Start(uint32_t timerDelayMS) {
  if (mPending) {
    // Collector is already started.
    return NS_OK;
  }

  if (!mTimer) {
    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
  }

  nsresult rv = mTimer->InitWithCallback(this, timerDelayMS, nsITimer::TYPE_ONE_SHOT);
  if (NS_FAILED(rv)) {
    return rv;
  }

  mPending = true;
  {
    mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(mJankLevels);
    MOZ_ASSERT(result);
  }

  return NS_OK;
}

nsresult
PendingAlertsCollector::Dispose() {
  if (mTimer) {
    mozilla::Unused << mTimer->Cancel();
    mTimer = nullptr;
  }
  mService = nullptr;
  return NS_OK;
}



/* ------------------------------------------------------
 *
 * class nsPerformanceStatsService
 *
 */

NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)

nsPerformanceStatsService::nsPerformanceStatsService()
  : mIsAvailable(false)
  , mDisposed(false)
#if defined(XP_WIN)
  , mProcessId(GetCurrentProcessId())
#else
  , mProcessId(getpid())
#endif
  , mContext(mozilla::dom::danger::GetJSContext())
  , mUIdCounter(0)
  , mTopGroup(nsPerformanceGroup::Make(mContext,
                                       this,
                                       NS_LITERAL_STRING("<process>"), // name
                                       NS_LITERAL_STRING(""),          // addonid
                                       0,    // windowId
                                       mProcessId,
                                       true, // isSystem
                                       nsPerformanceGroup::GroupScope::RUNTIME // scope
                                     ))
  , mIsHandlingUserInput(false)
  , mProcessStayed(0)
  , mProcessMoved(0)
  , mProcessUpdateCounter(0)
  , mIsMonitoringPerCompartment(false)
  , mJankAlertThreshold(mozilla::MaxValue<uint64_t>::value) // By default, no alerts
  , mJankAlertBufferingDelay(1000 /* ms */)
  , mJankLevelVisibilityThreshold(/* 2 ^ */ 8 /* ms */)
  , mMaxExpectedDurationOfInteractionUS(150 * 1000)
{
  mPendingAlertsCollector = new PendingAlertsCollector(this);

  // Attach some artificial group information to the universal listeners, to aid with debugging.
  nsString groupIdForAddons;
  GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForAddons);
  mUniversalTargets.mAddons->
    SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal add-on listener>"),
                                            groupIdForAddons,
                                            NS_LITERAL_STRING("<universal add-on listener>"),
                                            0, // window id
                                            mProcessId,
                                            false));


  nsString groupIdForWindows;
  GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForWindows);
  mUniversalTargets.mWindows->
    SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"),
                                            groupIdForWindows,
                                            NS_LITERAL_STRING("<universal window listener>"),
                                            0, // window id
                                            mProcessId,
                                            false));
}

nsPerformanceStatsService::~nsPerformanceStatsService()
{ }

/**
 * Clean up the service.
 *
 * Called during shutdown. Idempotent.
 */
void
nsPerformanceStatsService::Dispose()
{
  // Make sure that we do not accidentally destroy `this` while we are
  // cleaning up back references.
  RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this);
  mIsAvailable = false;

  if (mDisposed) {
    // Make sure that we don't double-dispose.
    return;
  }
  mDisposed = true;

  // Disconnect from nsIObserverService.
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
      mozilla::Unused << obs->RemoveObserver(this, TOPICS[i]);
    }
  }

  // Clear up and disconnect from JSAPI.
  JSContext* cx = mContext;
  js::DisposePerformanceMonitoring(cx);

  mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(cx, false);
  mozilla::Unused << js::SetStopwatchIsMonitoringJank(cx, false);

  mozilla::Unused << js::SetStopwatchStartCallback(cx, nullptr, nullptr);
  mozilla::Unused << js::SetStopwatchCommitCallback(cx, nullptr, nullptr);
  mozilla::Unused << js::SetGetPerformanceGroupsCallback(cx, nullptr, nullptr);

  // Clear up and disconnect the alerts collector.
  if (mPendingAlertsCollector) {
    mPendingAlertsCollector->Dispose();
    mPendingAlertsCollector = nullptr;
  }
  mPendingAlerts.clear();

  // Disconnect universal observers. Per-group observers will be
  // disconnected below as part of `group->Dispose()`.
  mUniversalTargets.mAddons = nullptr;
  mUniversalTargets.mWindows = nullptr;

  // At this stage, the JS VM may still be holding references to
  // instances of PerformanceGroup on the stack. To let the service be
  // collected, we need to break the references from these groups to
  // `this`.
  mTopGroup->Dispose();
  mTopGroup = nullptr;

  // Copy references to the groups to a vector to ensure that we do
  // not modify the hashtable while iterating it.
  GroupVector groups;
  for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
    if (!groups.append(iter.Get()->GetKey())) {
      MOZ_CRASH();
    }
  }
  for (auto iter = groups.begin(), end = groups.end(); iter < end; ++iter) {
    RefPtr<nsPerformanceGroup> group = *iter;
    group->Dispose();
  }

  // Any remaining references to PerformanceGroup will be released as
  // the VM unrolls the stack. If there are any nested event loops,
  // this may take time.
}

nsresult
nsPerformanceStatsService::Init()
{
  nsresult rv = InitInternal();
  if (NS_FAILED(rv)) {
    // Attempt to clean up.
    Dispose();
  }
  return rv;
}

nsresult
nsPerformanceStatsService::InitInternal()
{
  // Make sure that we release everything during shutdown.
  // We are a bit defensive here, as we know that some strange behavior can break the
  // regular shutdown order.
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
      mozilla::Unused << obs->AddObserver(this, TOPICS[i], false);
    }
  }

  // Connect to JSAPI.
  JSContext* cx = mContext;
  if (!js::SetStopwatchStartCallback(cx, StopwatchStartCallback, this)) {
    return NS_ERROR_UNEXPECTED;
  }
  if (!js::SetStopwatchCommitCallback(cx, StopwatchCommitCallback, this)) {
    return NS_ERROR_UNEXPECTED;
  }
  if (!js::SetGetPerformanceGroupsCallback(cx, GetPerformanceGroupsCallback, this)) {
    return NS_ERROR_UNEXPECTED;
  }

  mTopGroup->setIsActive(true);
  mIsAvailable = true;

  return NS_OK;
}

// Observe shutdown events.
NS_IMETHODIMP
nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic,
                                   const char16_t *aData)
{
  MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0
             || strcmp(aTopic, "quit-application") == 0
             || strcmp(aTopic, "quit-application-granted") == 0
             || strcmp(aTopic, "xpcom-will-shutdown") == 0);

  Dispose();
  return NS_OK;
}

/*static*/ bool
nsPerformanceStatsService::IsHandlingUserInput() {
  if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) {
    return false;
  }
  bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS);
  return result;
}

/* [implicit_jscontext] attribute bool isMonitoringCPOW; */
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(cx);
  return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (!js::SetStopwatchIsMonitoringCPOW(cx, aIsStopwatchActive)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

/* [implicit_jscontext] attribute bool isMonitoringJank; */
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(cx);
  return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (!js::SetStopwatchIsMonitoringJank(cx, aIsStopwatchActive)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

/* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */
NS_IMETHODIMP
nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
  return NS_OK;
}
NS_IMETHODIMP
nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) {
    return NS_OK;
  }

  // Relatively slow update: walk the entire lost of performance groups,
  // update the active flag of those that have changed.
  //
  // Alternative strategies could be envisioned to make the update
  // much faster, at the expense of the speed of calling `isActive()`,
  // (e.g. deferring `isActive()` to the nsPerformanceStatsService),
  // but we expect that `isActive()` can be called thousands of times
  // per second, while `SetIsMonitoringPerCompartment` is not called
  // at all during most Firefox runs.

  for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
    RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey();
    if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) {
      group->setIsActive(aIsMonitoringPerCompartment);
    }
  }
  mIsMonitoringPerCompartment = aIsMonitoringPerCompartment;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::GetJankAlertThreshold(uint64_t* result) {
  *result = mJankAlertThreshold;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::SetJankAlertThreshold(uint64_t value) {
  mJankAlertThreshold = value;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::GetJankAlertBufferingDelay(uint32_t* result) {
  *result = mJankAlertBufferingDelay;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::SetJankAlertBufferingDelay(uint32_t value) {
  mJankAlertBufferingDelay = value;
  return NS_OK;
}

nsresult
nsPerformanceStatsService::UpdateTelemetry()
{
  /* STUB */
  return NS_OK;
}


/* static */ nsIPerformanceStats*
nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group)
{
  return GetStatsForGroup(nsPerformanceGroup::Get(group));
}

/* static */ nsIPerformanceStats*
nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group)
{
  return new nsPerformanceStats(group->Details(), group->data);
}

/* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */
NS_IMETHODIMP
nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
{
  if (!mIsAvailable) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
  snapshot->SetProcessStats(GetStatsForGroup(mTopGroup));

  for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
    auto* entry = iter.Get();
    nsPerformanceGroup* group = entry->GetKey();
    if (group->isActive()) {
      snapshot->AppendComponentsStats(GetStatsForGroup(group));
    }
  }

  js::GetPerfMonitoringTestCpuRescheduling(cx, &mProcessStayed, &mProcessMoved);

  if (++mProcessUpdateCounter % 10 == 0) {
    mozilla::Unused << UpdateTelemetry();
  }

  snapshot.forget(aSnapshot);

  return NS_OK;
}

uint64_t
nsPerformanceStatsService::GetNextId() {
  return ++mUIdCounter;
}

/* static*/ bool
nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx,
                                                        js::PerformanceGroupVector& out,
                                                        void* closure)
{
  RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
  return self->GetPerformanceGroups(cx, out);
}

bool
nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx,
                                                js::PerformanceGroupVector& out)
{
  JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
  if (!global) {
    // While it is possible for a compartment to have no global
    // (e.g. atoms), this compartment is not very interesting for us.
    return true;
  }

  // All compartments belong to the top group.
  if (!out.append(mTopGroup)) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  nsAutoString name;
  CompartmentName(cx, global, name);
  bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));

  // Find out if the compartment is executed by an add-on. If so, its
  // duration should count towards the total duration of the add-on.
  JSAddonId* jsaddonId = AddonIdOfObject(global);
  nsString addonId;
  if (jsaddonId) {
    AssignJSFlatString(addonId, (JSFlatString*)jsaddonId);
    auto entry = mAddonIdToGroup.PutEntry(addonId);
    if (!entry->GetGroup()) {
      nsString addonName = name;
      addonName.AppendLiteral(" (as addon ");
      addonName.Append(addonId);
      addonName.AppendLiteral(")");
      entry->
        SetGroup(nsPerformanceGroup::Make(mContext, this,
                                          addonName, addonId, 0,
                                          mProcessId, isSystem,
                                          nsPerformanceGroup::GroupScope::ADDON)
                 );
    }
    if (!out.append(entry->GetGroup())) {
      JS_ReportOutOfMemory(cx);
      return false;
    }
  }

  // Find out if the compartment is executed by a window. If so, its
  // duration should count towards the total duration of the window.
  uint64_t windowId = 0;
  if (nsCOMPtr<nsPIDOMWindowOuter> ptop = GetPrivateWindow(cx)) {
    windowId = ptop->WindowID();
    auto entry = mWindowIdToGroup.PutEntry(windowId);
    if (!entry->GetGroup()) {
      nsString windowName = name;
      windowName.AppendLiteral(" (as window ");
      windowName.AppendInt(windowId);
      windowName.AppendLiteral(")");
      entry->
        SetGroup(nsPerformanceGroup::Make(mContext, this,
                                          windowName, EmptyString(), windowId,
                                          mProcessId, isSystem,
                                          nsPerformanceGroup::GroupScope::WINDOW)
                 );
    }
    if (!out.append(entry->GetGroup())) {
      JS_ReportOutOfMemory(cx);
      return false;
    }
  }

  // All compartments have their own group.
  auto group =
    nsPerformanceGroup::Make(mContext, this,
                             name, addonId, windowId,
                             mProcessId, isSystem,
                             nsPerformanceGroup::GroupScope::COMPARTMENT);
  if (!out.append(group)) {
    JS_ReportOutOfMemory(cx);
    return false;
  }

  return true;
}

/*static*/ bool
nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
  RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
  return self->StopwatchStart(iteration);
}

bool
nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
  mIteration = iteration;

  mIsHandlingUserInput = IsHandlingUserInput();
  mUserInputCount = mozilla::EventStateManager::UserInputCount();

  nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
  if (NS_FAILED(rv)) {
    return false;
  }

  return true;
}

/*static*/ bool
nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration,
                                                   js::PerformanceGroupVector& recentGroups,
                                                   void* closure)
{
  RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
  return self->StopwatchCommit(iteration, recentGroups);
}

bool
nsPerformanceStatsService::StopwatchCommit(uint64_t iteration,
                                           js::PerformanceGroupVector& recentGroups)
{
  MOZ_ASSERT(iteration == mIteration);
  MOZ_ASSERT(!recentGroups.empty());

  uint64_t userTimeStop, systemTimeStop;
  nsresult rv = GetResources(&userTimeStop, &systemTimeStop);
  if (NS_FAILED(rv)) {
    return false;
  }

  // `GetResources` is not guaranteed to be monotonic, so round up
  // any negative result to 0 milliseconds.
  uint64_t userTimeDelta = 0;
  if (userTimeStop > mUserTimeStart)
    userTimeDelta = userTimeStop - mUserTimeStart;

  uint64_t systemTimeDelta = 0;
  if (systemTimeStop > mSystemTimeStart)
    systemTimeDelta = systemTimeStop - mSystemTimeStart;

  MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
  const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);

  const bool isHandlingUserInput = mIsHandlingUserInput || mozilla::EventStateManager::UserInputCount() > mUserInputCount;

  // We should only reach this stage if `group` has had some activity.
  MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0);
  for (auto iter = recentGroups.begin(), end = recentGroups.end(); iter != end; ++iter) {
    RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter);
    CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, isHandlingUserInput, group);
  }

  // Make sure that `group` was treated along with the other items of `recentGroups`.
  MOZ_ASSERT(!mTopGroup->isUsedInThisIteration());
  MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0);

  if (!mPendingAlerts.empty()) {
    mPendingAlertsCollector->Start(mJankAlertBufferingDelay);
  }

  return true;
}

void
nsPerformanceStatsService::CommitGroup(uint64_t iteration,
                                       uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
                                       uint64_t totalCyclesDelta,
                                       bool isHandlingUserInput,
                                       nsPerformanceGroup* group) {

  MOZ_ASSERT(group->isUsedInThisIteration());

  const uint64_t ticksDelta = group->recentTicks(iteration);
  const uint64_t cpowTimeDelta = group->recentCPOW(iteration);
  const uint64_t cyclesDelta = group->recentCycles(iteration);
  group->resetRecentData();

  // We have now performed all cleanup and may `return` at any time without fear of leaks.

  if (group->iteration() != iteration) {
    // Stale data, don't commit.
    return;
  }

  // When we add a group as changed, we immediately set its
  // `recentTicks` from 0 to 1.  If we have `ticksDelta == 0` at
  // this stage, we have already called `resetRecentData` but we
  // haven't removed it from the list.
  MOZ_ASSERT(ticksDelta != 0);
  MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
  if (cyclesDelta == 0 || totalCyclesDelta == 0) {
    // Nothing useful, don't commit.
    return;
  }

  double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
  MOZ_ASSERT(proportion <= 1);

  const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
  const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;

  group->data.mTotalUserTime += userTimeDelta;
  group->data.mTotalSystemTime += systemTimeDelta;
  group->data.mTotalCPOWTime += cpowTimeDelta;
  group->data.mTicks += ticksDelta;

  const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta;
  uint64_t duration = 1000;   // 1ms in µs
  for (size_t i = 0;
       i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta;
       ++i, duration *= 2) {
    group->data.mDurations[i]++;
  }

  group->RecordJank(totalTimeDelta);
  group->RecordCPOW(cpowTimeDelta);
  if (isHandlingUserInput) {
    group->RecordUserInput();
  }

  if (totalTimeDelta >= mJankAlertThreshold) {
    if (!group->HasPendingAlert()) {
      if (mPendingAlerts.append(group)) {
        group->SetHasPendingAlert(true);
      }
      return;
    }
  }

  return;
}

nsresult
nsPerformanceStatsService::GetResources(uint64_t* userTime,
                                        uint64_t* systemTime) const {
  MOZ_ASSERT(userTime);
  MOZ_ASSERT(systemTime);

#if defined(XP_MACOSX)
  // On MacOS X, to get we per-thread data, we need to
  // reach into the kernel.

  mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
  thread_basic_info_data_t info;
  mach_port_t port = mach_thread_self();
  kern_return_t err =
    thread_info(/* [in] targeted thread*/ port,
                /* [in] nature of information*/ THREAD_BASIC_INFO,
                /* [out] thread information */  (thread_info_t)&info,
                /* [inout] number of items */   &count);

  // We do not need ability to communicate with the thread, so
  // let's release the port.
  mach_port_deallocate(mach_task_self(), port);

  if (err != KERN_SUCCESS)
    return NS_ERROR_FAILURE;

  *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
  *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;

#elif defined(XP_UNIX)
  struct rusage rusage;
#if defined(RUSAGE_THREAD)
  // Under Linux, we can obtain per-thread statistics
  int err = getrusage(RUSAGE_THREAD, &rusage);
#else
  // Under other Unices, we need to do with more noisy
  // per-process statistics.
  int err = getrusage(RUSAGE_SELF, &rusage);
#endif // defined(RUSAGE_THREAD)

  if (err)
    return NS_ERROR_FAILURE;

  *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
  *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;

#elif defined(XP_WIN)
  // Under Windows, we can obtain per-thread statistics. Experience
  // seems to suggest that they are not very accurate under Windows
  // XP, though.
  FILETIME creationFileTime; // Ignored
  FILETIME exitFileTime; // Ignored
  FILETIME kernelFileTime;
  FILETIME userFileTime;
  BOOL success = GetThreadTimes(GetCurrentThread(),
                                &creationFileTime, &exitFileTime,
                                &kernelFileTime, &userFileTime);

  if (!success)
    return NS_ERROR_FAILURE;

  ULARGE_INTEGER kernelTimeInt;
  kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
  kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
  // Convert 100 ns to 1 us.
  *systemTime = kernelTimeInt.QuadPart / 10;

  ULARGE_INTEGER userTimeInt;
  userTimeInt.LowPart = userFileTime.dwLowDateTime;
  userTimeInt.HighPart = userFileTime.dwHighDateTime;
  // Convert 100 ns to 1 us.
  *userTime = userTimeInt.QuadPart / 10;

#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)

  return NS_OK;
}

void
nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) {
  GroupVector alerts;
  mPendingAlerts.swap(alerts);
  if (!mPendingAlertsCollector) {
    // We are shutting down.
    return;
  }

  // Find out if we have noticed any user-noticeable delay in an
  // animation recently (i.e. since the start of the execution of JS
  // code that caused this collector to start). If so, we'll mark any
  // alert as part of a user-noticeable jank. Note that this doesn't
  // mean with any certainty that the alert is the only cause of jank,
  // or even the main cause of jank.
  mozilla::Vector<uint64_t> latestJankLevels;
  {
    mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(latestJankLevels);
    MOZ_ASSERT(result);
  }
  MOZ_ASSERT(latestJankLevels.length() == aPreviousJankLevels.length());

  bool isJankInAnimation = false;
  for (size_t i = mJankLevelVisibilityThreshold; i < latestJankLevels.length(); ++i) {
    if (latestJankLevels[i] > aPreviousJankLevels[i]) {
      isJankInAnimation = true;
      break;
    }
  }

  MOZ_ASSERT(!alerts.empty());
  const bool hasUniversalAddonObservers = mUniversalTargets.mAddons->HasObservers();
  const bool hasUniversalWindowObservers = mUniversalTargets.mWindows->HasObservers();
  for (auto iter = alerts.begin(); iter < alerts.end(); ++iter) {
    MOZ_ASSERT(iter);
    RefPtr<nsPerformanceGroup> group = *iter;
    group->SetHasPendingAlert(false);

    RefPtr<nsPerformanceGroupDetails> details = group->Details();
    nsPerformanceObservationTarget* targets[3] = {
      hasUniversalAddonObservers && details->IsAddon() ? mUniversalTargets.mAddons.get() : nullptr,
      hasUniversalWindowObservers && details->IsWindow() ? mUniversalTargets.mWindows.get() : nullptr,
      group->ObservationTarget()
    };

    bool isJankInInput = group->HasRecentUserInput();

    RefPtr<PerformanceAlert> alert;
    for (nsPerformanceObservationTarget* target : targets) {
      if (!target) {
        continue;
      }
      if (!alert) {
        const uint32_t reason = nsIPerformanceAlert::REASON_SLOWDOWN
          | (isJankInAnimation ? nsIPerformanceAlert::REASON_JANK_IN_ANIMATION : 0)
          | (isJankInInput ? nsIPerformanceAlert::REASON_JANK_IN_INPUT : 0);
        // Wait until we are sure we need to allocate before we allocate.
        alert = new PerformanceAlert(reason, group);
      }
      target->NotifyJankObservers(details, alert);
    }

    group->ResetRecent();
  }

}

NS_IMETHODIMP
nsPerformanceStatsService::GetObservableAddon(const nsAString& addonId,
                                              nsIPerformanceObservable** result) {
  if (addonId.Equals(NS_LITERAL_STRING("*"))) {
    NS_IF_ADDREF(*result = mUniversalTargets.mAddons);
  } else {
    auto entry = mAddonIdToGroup.PutEntry(addonId);
    NS_IF_ADDREF(*result = entry->ObservationTarget());
  }
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::GetObservableWindow(uint64_t windowId,
                                               nsIPerformanceObservable** result) {
  if (windowId == 0) {
    NS_IF_ADDREF(*result = mUniversalTargets.mWindows);
  } else {
    auto entry = mWindowIdToGroup.PutEntry(windowId);
    NS_IF_ADDREF(*result = entry->ObservationTarget());
  }
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::GetAnimationJankLevelThreshold(short* result) {
  *result = mJankLevelVisibilityThreshold;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::SetAnimationJankLevelThreshold(short value) {
  mJankLevelVisibilityThreshold = value;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::GetUserInputDelayThreshold(uint64_t* result) {
  *result = mMaxExpectedDurationOfInteractionUS;
  return NS_OK;
}

NS_IMETHODIMP
nsPerformanceStatsService::SetUserInputDelayThreshold(uint64_t value) {
  mMaxExpectedDurationOfInteractionUS = value;
  return NS_OK;
}



nsPerformanceStatsService::UniversalTargets::UniversalTargets()
  : mAddons(new nsPerformanceObservationTarget())
  , mWindows(new nsPerformanceObservationTarget())
{ }

/* ------------------------------------------------------
 *
 * Class nsPerformanceGroup
 *
 */

/*static*/ nsPerformanceGroup*
nsPerformanceGroup::Make(JSContext* cx,
                         nsPerformanceStatsService* service,
                         const nsAString& name,
                         const nsAString& addonId,
                         uint64_t windowId,
                         uint64_t processId,
                         bool isSystem,
                         GroupScope scope)
{
  nsString groupId;
  ::GenerateUniqueGroupId(cx, service->GetNextId(), processId, groupId);
  return new nsPerformanceGroup(service, name, groupId, addonId, windowId, processId, isSystem, scope);
}

nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
                                       const nsAString& name,
                                       const nsAString& groupId,
                                       const nsAString& addonId,
                                       uint64_t windowId,
                                       uint64_t processId,
                                       bool isSystem,
                                       GroupScope scope)
  : mDetails(new nsPerformanceGroupDetails(name, groupId, addonId, windowId, processId, isSystem))
  , mService(service)
  , mScope(scope)
  , mHighestJank(0)
  , mHighestCPOW(0)
  , mHasRecentUserInput(false)
  , mHasPendingAlert(false)
{
  mozilla::Unused << mService->mGroups.PutEntry(this);

#if defined(DEBUG)
  if (scope == GroupScope::ADDON) {
    MOZ_ASSERT(mDetails->IsAddon());
    MOZ_ASSERT(!mDetails->IsWindow());
  } else if (scope == GroupScope::WINDOW) {
    MOZ_ASSERT(mDetails->IsWindow());
    MOZ_ASSERT(!mDetails->IsAddon());
  } else if (scope == GroupScope::RUNTIME) {
    MOZ_ASSERT(!mDetails->IsWindow());
    MOZ_ASSERT(!mDetails->IsAddon());
  }
#endif // defined(DEBUG)
  setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment);
}

void
nsPerformanceGroup::Dispose() {
  if (!mService) {
    // We have already called `Dispose()`.
    return;
  }
  if (mObservationTarget) {
    mObservationTarget = nullptr;
  }

  // Remove any reference to the service.
  RefPtr<nsPerformanceStatsService> service;
  service.swap(mService);

  // Remove any dangling pointer to `this`.
  service->mGroups.RemoveEntry(this);

  if (mScope == GroupScope::ADDON) {
    MOZ_ASSERT(mDetails->IsAddon());
    service->mAddonIdToGroup.RemoveEntry(mDetails->AddonId());
  } else if (mScope == GroupScope::WINDOW) {
    MOZ_ASSERT(mDetails->IsWindow());
    service->mWindowIdToGroup.RemoveEntry(mDetails->WindowId());
  }
}

nsPerformanceGroup::~nsPerformanceGroup() {
  Dispose();
}

nsPerformanceGroup::GroupScope
nsPerformanceGroup::Scope() const {
  return mScope;
}

nsPerformanceGroupDetails*
nsPerformanceGroup::Details() const {
  return mDetails;
}

void
nsPerformanceGroup::SetObservationTarget(nsPerformanceObservationTarget* target) {
  MOZ_ASSERT(!mObservationTarget);
  mObservationTarget = target;
}

nsPerformanceObservationTarget*
nsPerformanceGroup::ObservationTarget() const {
  return mObservationTarget;
}

bool
nsPerformanceGroup::HasPendingAlert() const {
  return mHasPendingAlert;
}

void
nsPerformanceGroup::SetHasPendingAlert(bool value) {
  mHasPendingAlert = value;
}


void
nsPerformanceGroup::RecordJank(uint64_t jank) {
  if (jank > mHighestJank) {
    mHighestJank = jank;
  }
}

void
nsPerformanceGroup::RecordCPOW(uint64_t cpow) {
  if (cpow > mHighestCPOW) {
    mHighestCPOW = cpow;
  }
}

uint64_t
nsPerformanceGroup::HighestRecentJank() {
  return mHighestJank;
}

uint64_t
nsPerformanceGroup::HighestRecentCPOW() {
  return mHighestCPOW;
}

bool
nsPerformanceGroup::HasRecentUserInput() {
  return mHasRecentUserInput;
}

void
nsPerformanceGroup::RecordUserInput() {
  mHasRecentUserInput = true;
}

void
nsPerformanceGroup::ResetRecent() {
  mHighestJank = 0;
  mHighestCPOW = 0;
  mHasRecentUserInput = false;
}