зеркало из https://github.com/mozilla/gecko-dev.git
283 строки
9.8 KiB
C++
283 строки
9.8 KiB
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 "TimelineConsumers.h"
|
|
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "jsapi.h"
|
|
#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess
|
|
#include "nsDocShell.h"
|
|
|
|
namespace mozilla {
|
|
|
|
NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver);
|
|
|
|
StaticMutex TimelineConsumers::sMutex;
|
|
|
|
// Manually manage this singleton's lifetime and destroy it before shutdown.
|
|
// This avoids the leakchecker detecting false-positive memory leaks when
|
|
// using automatic memory management (i.e. statically instantiating this
|
|
// singleton inside the `Get` method), which would automatically destroy it on
|
|
// application shutdown, but too late for the leakchecker. Sigh...
|
|
StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance;
|
|
|
|
// This flag makes sure the singleton never gets instantiated while a shutdown
|
|
// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work
|
|
// in these cases.
|
|
bool TimelineConsumers::sInShutdown = false;
|
|
|
|
already_AddRefed<TimelineConsumers> TimelineConsumers::Get() {
|
|
// Using this class is not supported yet for other processes other than
|
|
// parent or content. To avoid accidental checks to methods like `IsEmpty`,
|
|
// which would probably always be true in those cases, assert here.
|
|
// Remember, there will be different singletons available to each process.
|
|
|
|
// TODO: we have to avoid calling this function in socket process.
|
|
MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess());
|
|
|
|
// If we are shutting down, don't bother doing anything. Note: we can only
|
|
// know whether or not we're in shutdown if we're instantiated.
|
|
if (sInShutdown) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: We don't simply check `sInstance` for null-ness here, since otherwise
|
|
// this can resurrect the TimelineConsumers pretty late during shutdown.
|
|
// We won't know if we're in shutdown or not though, because the singleton
|
|
// could have been destroyed or just never instantiated, so in the previous
|
|
// conditional `sInShutdown` would be false.
|
|
static bool firstTime = true;
|
|
if (firstTime) {
|
|
firstTime = false;
|
|
|
|
StaticMutexAutoLock lock(sMutex);
|
|
sInstance = new TimelineConsumers();
|
|
|
|
// Make sure the initialization actually suceeds, otherwise don't allow
|
|
// access by destroying the instance immediately.
|
|
if (sInstance->Init()) {
|
|
ClearOnShutdown(&sInstance);
|
|
} else {
|
|
sInstance->RemoveObservers();
|
|
sInstance = nullptr;
|
|
}
|
|
}
|
|
|
|
RefPtr<TimelineConsumers> copy = sInstance.get();
|
|
return copy.forget();
|
|
}
|
|
|
|
bool TimelineConsumers::Init() {
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (!obs) {
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TimelineConsumers::RemoveObservers() {
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (!obs) {
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsresult TimelineConsumers::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
|
sInShutdown = true;
|
|
RemoveObservers();
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
TimelineConsumers::TimelineConsumers() : mActiveConsumers(0) {}
|
|
|
|
void TimelineConsumers::AddConsumer(nsDocShell* aDocShell) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
StaticMutexAutoLock lock(
|
|
sMutex); // for `mActiveConsumers` and `mMarkersStores`.
|
|
|
|
UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
|
|
MOZ_ASSERT(!observed);
|
|
|
|
if (mActiveConsumers == 0) {
|
|
JS::SetProfileTimelineRecordingEnabled(true);
|
|
}
|
|
mActiveConsumers++;
|
|
|
|
ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell);
|
|
MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell);
|
|
|
|
observed.reset(obsDocShell);
|
|
mMarkersStores.insertFront(storage);
|
|
}
|
|
|
|
void TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
StaticMutexAutoLock lock(
|
|
sMutex); // for `mActiveConsumers` and `mMarkersStores`.
|
|
|
|
UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
|
|
MOZ_ASSERT(observed);
|
|
|
|
mActiveConsumers--;
|
|
if (mActiveConsumers == 0) {
|
|
JS::SetProfileTimelineRecordingEnabled(false);
|
|
}
|
|
|
|
// Clear all markers from the `mTimelineMarkers` store.
|
|
observed.get()->ClearMarkers();
|
|
// Remove self from the `mMarkersStores` store.
|
|
observed.get()->remove();
|
|
// Prepare for becoming a consumer later.
|
|
observed.reset(nullptr);
|
|
}
|
|
|
|
bool TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return aDocShell ? aDocShell->GetRecordProfileTimelineMarkers() : false;
|
|
}
|
|
|
|
bool TimelineConsumers::IsEmpty() {
|
|
StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`.
|
|
return mActiveConsumers == 0;
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
|
|
const char* aName,
|
|
MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (HasConsumer(aDocShell)) {
|
|
aDocShell->mObserved->AddMarker(
|
|
MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest));
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
|
|
const char* aName,
|
|
const TimeStamp& aTime,
|
|
MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (HasConsumer(aDocShell)) {
|
|
aDocShell->mObserved->AddMarker(
|
|
MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest));
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(
|
|
nsDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
if (HasConsumer(aDocShell)) {
|
|
aDocShell->mObserved->AddMarker(std::move(aMarker));
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
|
|
const char* aName,
|
|
MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType,
|
|
aStackRequest);
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
|
|
const char* aName,
|
|
const TimeStamp& aTime,
|
|
MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime,
|
|
aTracingType, aStackRequest);
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForDocShell(
|
|
nsIDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), std::move(aMarker));
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForAllObservedDocShells(
|
|
const char* aName, MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest /* = STACK */) {
|
|
bool isMainThread = NS_IsMainThread();
|
|
StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
|
|
|
|
for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
|
|
storage = storage->getNext()) {
|
|
UniquePtr<AbstractTimelineMarker> marker =
|
|
MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest);
|
|
if (isMainThread) {
|
|
storage->AddMarker(std::move(marker));
|
|
} else {
|
|
storage->AddOTMTMarker(std::move(marker));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForAllObservedDocShells(
|
|
const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType,
|
|
MarkerStackRequest aStackRequest /* = STACK */) {
|
|
bool isMainThread = NS_IsMainThread();
|
|
StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
|
|
|
|
for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
|
|
storage = storage->getNext()) {
|
|
UniquePtr<AbstractTimelineMarker> marker =
|
|
MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest);
|
|
if (isMainThread) {
|
|
storage->AddMarker(std::move(marker));
|
|
} else {
|
|
storage->AddOTMTMarker(std::move(marker));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::AddMarkerForAllObservedDocShells(
|
|
UniquePtr<AbstractTimelineMarker>& aMarker) {
|
|
bool isMainThread = NS_IsMainThread();
|
|
StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`.
|
|
|
|
for (MarkersStorage* storage = mMarkersStores.getFirst(); storage != nullptr;
|
|
storage = storage->getNext()) {
|
|
UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone();
|
|
if (isMainThread) {
|
|
storage->AddMarker(std::move(clone));
|
|
} else {
|
|
storage->AddOTMTMarker(std::move(clone));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineConsumers::PopMarkers(
|
|
nsDocShell* aDocShell, JSContext* aCx,
|
|
nsTArray<dom::ProfileTimelineMarker>& aStore) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aDocShell || !aDocShell->mObserved) {
|
|
return;
|
|
}
|
|
|
|
aDocShell->mObserved->PopMarkers(aCx, aStore);
|
|
}
|
|
|
|
} // namespace mozilla
|