зеркало из https://github.com/mozilla/gecko-dev.git
723 строки
25 KiB
C++
723 строки
25 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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 "DDMediaLogs.h"
|
|
|
|
#include "DDLogUtils.h"
|
|
#include "mozilla/JSONWriter.h"
|
|
#include "mozilla/SharedThreadPool.h"
|
|
|
|
namespace mozilla {
|
|
|
|
/* static */ DDMediaLogs::ConstructionResult
|
|
DDMediaLogs::New()
|
|
{
|
|
nsCOMPtr<nsIThread> mThread;
|
|
nsresult rv = NS_NewNamedThread("DDMediaLogs",
|
|
getter_AddRefs(mThread),
|
|
nullptr,
|
|
SharedThreadPool::kStackSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return { rv, nullptr };
|
|
}
|
|
|
|
return { rv, new DDMediaLogs(Move(mThread)) };
|
|
}
|
|
|
|
DDMediaLogs::DDMediaLogs(nsCOMPtr<nsIThread>&& aThread)
|
|
: mMediaLogs(1)
|
|
, mMutex("DDMediaLogs")
|
|
, mThread(Move(aThread))
|
|
{
|
|
mMediaLogs.SetLength(1);
|
|
mMediaLogs[0].mMediaElement = nullptr;
|
|
DDL_INFO("DDMediaLogs constructed, processing thread: %p", mThread.get());
|
|
}
|
|
|
|
DDMediaLogs::~DDMediaLogs()
|
|
{
|
|
// Perform end-of-life processing, ensure the processing thread is shutdown.
|
|
Shutdown(/* aPanic = */ false);
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::Panic()
|
|
{
|
|
Shutdown(/* aPanic = */ true);
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::Shutdown(bool aPanic)
|
|
{
|
|
nsCOMPtr<nsIThread> thread;
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
thread.swap(mThread);
|
|
}
|
|
if (!thread) {
|
|
// Already shutdown, nothing more to do.
|
|
return;
|
|
}
|
|
|
|
DDL_INFO("DDMediaLogs::Shutdown will shutdown thread: %p", thread.get());
|
|
// Will block until pending tasks have completed, and thread is dead.
|
|
thread->Shutdown();
|
|
|
|
if (aPanic) {
|
|
mMessagesQueue.PopAll([](const DDLogMessage&) {});
|
|
MutexAutoLock lock(mMutex);
|
|
mLifetimes.Clear();
|
|
mMediaLogs.Clear();
|
|
mObjectLinks.Clear();
|
|
mPendingPromises.Clear();
|
|
return;
|
|
}
|
|
|
|
// Final processing is only necessary to output to MOZ_LOG=DDLoggerEnd,
|
|
// so there's no point doing any of it if that MOZ_LOG is not enabled.
|
|
if (MOZ_LOG_TEST(sDecoderDoctorLoggerEndLog, mozilla::LogLevel::Info)) {
|
|
DDL_DEBUG("Perform final DDMediaLogs processing...");
|
|
// The processing thread is dead, so we can safely call ProcessLog()
|
|
// directly from this thread.
|
|
ProcessLog();
|
|
|
|
for (const DDMediaLog& mediaLog : mMediaLogs) {
|
|
if (mediaLog.mMediaElement) {
|
|
DDLE_INFO("---");
|
|
}
|
|
DDLE_INFO("--- Log for HTMLMediaElement[%p] ---", mediaLog.mMediaElement);
|
|
for (const DDLogMessage& message : mediaLog.mMessages) {
|
|
DDLE_LOG(message.mCategory <= DDLogCategory::_Unlink
|
|
? mozilla::LogLevel::Debug
|
|
: mozilla::LogLevel::Info,
|
|
"%s",
|
|
message.Print(mLifetimes).get());
|
|
}
|
|
DDLE_DEBUG("--- End log for HTMLMediaElement[%p] ---",
|
|
mediaLog.mMediaElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
DDMediaLog&
|
|
DDMediaLogs::LogForUnassociatedMessages()
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
return mMediaLogs[0];
|
|
}
|
|
const DDMediaLog&
|
|
DDMediaLogs::LogForUnassociatedMessages() const
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
return mMediaLogs[0];
|
|
}
|
|
|
|
DDMediaLog*
|
|
DDMediaLogs::GetLogFor(const dom::HTMLMediaElement* aMediaElement)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
if (!aMediaElement) {
|
|
return &LogForUnassociatedMessages();
|
|
}
|
|
for (DDMediaLog& log : mMediaLogs) {
|
|
if (log.mMediaElement == aMediaElement) {
|
|
return &log;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
DDMediaLog&
|
|
DDMediaLogs::LogFor(const dom::HTMLMediaElement* aMediaElement)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
DDMediaLog* log = GetLogFor(aMediaElement);
|
|
if (!log) {
|
|
log = mMediaLogs.AppendElement();
|
|
log->mMediaElement = aMediaElement;
|
|
}
|
|
return *log;
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::SetMediaElement(DDLifetime& aLifetime,
|
|
const dom::HTMLMediaElement* aMediaElement)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
DDMediaLog& log = LogFor(aMediaElement);
|
|
|
|
// List of lifetimes that are to be linked to aMediaElement.
|
|
nsTArray<DDLifetime*> lifetimes;
|
|
// We start with the given lifetime.
|
|
lifetimes.AppendElement(&aLifetime);
|
|
for (size_t i = 0; i < lifetimes.Length(); ++i) {
|
|
DDLifetime& lifetime = *lifetimes[i];
|
|
// Link the lifetime to aMediaElement.
|
|
lifetime.mMediaElement = aMediaElement;
|
|
// Classified lifetime's tag is a positive index from the DDMediaLog.
|
|
lifetime.mTag = ++log.mLifetimeCount;
|
|
DDL_DEBUG(
|
|
"%s -> HTMLMediaElement[%p]", lifetime.Printf().get(), aMediaElement);
|
|
|
|
// Go through the lifetime's existing linked lifetimes, if any is not
|
|
// already linked to aMediaElement, add it to the list so it will get
|
|
// linked in a later loop.
|
|
for (auto& link : mObjectLinks) {
|
|
if (lifetime.IsAliveAt(link.mLinkingIndex)) {
|
|
if (lifetime.mObject == link.mParent) {
|
|
DDLifetime* childLifetime =
|
|
mLifetimes.FindLifetime(link.mChild, link.mLinkingIndex);
|
|
if (childLifetime && !childLifetime->mMediaElement &&
|
|
!lifetimes.Contains(childLifetime)) {
|
|
lifetimes.AppendElement(childLifetime);
|
|
}
|
|
} else if (lifetime.mObject == link.mChild) {
|
|
DDLifetime* parentLifetime =
|
|
mLifetimes.FindLifetime(link.mParent, link.mLinkingIndex);
|
|
if (parentLifetime && !parentLifetime->mMediaElement &&
|
|
!lifetimes.Contains(parentLifetime)) {
|
|
lifetimes.AppendElement(parentLifetime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now we need to move yet-unclassified messages related to the just-set
|
|
// elements, to the appropriate MediaElement list.
|
|
DDMediaLog::LogMessages& messages = log.mMessages;
|
|
DDMediaLog::LogMessages& messages0 = LogForUnassociatedMessages().mMessages;
|
|
for (size_t i = 0; i < messages0.Length();
|
|
/* increment inside the loop */) {
|
|
DDLogMessage& message = messages0[i];
|
|
bool found = false;
|
|
for (const DDLifetime* lifetime : lifetimes) {
|
|
if (lifetime->mObject == message.mObject) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
messages.AppendElement(Move(message));
|
|
messages0.RemoveElementAt(i);
|
|
// No increment, as we've removed this element; next element is now at
|
|
// the same index.
|
|
} else {
|
|
// Not touching this element, increment index to go to next element.
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
DDLifetime&
|
|
DDMediaLogs::FindOrCreateLifetime(const DDLogObject& aObject,
|
|
DDMessageIndex aIndex,
|
|
const DDTimeStamp& aTimeStamp)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
// Try to find lifetime corresponding to message object.
|
|
DDLifetime* lifetime = mLifetimes.FindLifetime(aObject, aIndex);
|
|
if (!lifetime) {
|
|
// No lifetime yet, create one.
|
|
lifetime = &(mLifetimes.CreateLifetime(aObject, aIndex, aTimeStamp));
|
|
if (MOZ_UNLIKELY(aObject.TypeName() ==
|
|
DDLoggedTypeTraits<dom::HTMLMediaElement>::Name())) {
|
|
const dom::HTMLMediaElement* mediaElement =
|
|
static_cast<const dom::HTMLMediaElement*>(aObject.Pointer());
|
|
SetMediaElement(*lifetime, mediaElement);
|
|
DDL_DEBUG("%s -> new lifetime: %s with MediaElement %p",
|
|
aObject.Printf().get(),
|
|
lifetime->Printf().get(),
|
|
mediaElement);
|
|
} else {
|
|
DDL_DEBUG("%s -> new lifetime: %s",
|
|
aObject.Printf().get(),
|
|
lifetime->Printf().get());
|
|
}
|
|
}
|
|
|
|
return *lifetime;
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::LinkLifetimes(DDLifetime& aParentLifetime,
|
|
const char* aLinkName,
|
|
DDLifetime& aChildLifetime,
|
|
DDMessageIndex aIndex)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
mObjectLinks.AppendElement(DDObjectLink{
|
|
aParentLifetime.mObject, aChildLifetime.mObject, aLinkName, aIndex });
|
|
if (aParentLifetime.mMediaElement) {
|
|
if (!aChildLifetime.mMediaElement) {
|
|
SetMediaElement(aChildLifetime, aParentLifetime.mMediaElement);
|
|
}
|
|
} else if (aChildLifetime.mMediaElement) {
|
|
if (!aParentLifetime.mMediaElement) {
|
|
SetMediaElement(aParentLifetime, aChildLifetime.mMediaElement);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::UnlinkLifetime(DDLifetime& aLifetime, DDMessageIndex aIndex)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
for (DDObjectLink& link : mObjectLinks) {
|
|
if ((link.mParent == aLifetime.mObject ||
|
|
link.mChild == aLifetime.mObject) &&
|
|
aLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
|
|
link.mUnlinkingIndex = Some(aIndex);
|
|
}
|
|
}
|
|
};
|
|
|
|
void
|
|
DDMediaLogs::UnlinkLifetimes(DDLifetime& aParentLifetime,
|
|
DDLifetime& aChildLifetime,
|
|
DDMessageIndex aIndex)
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
for (DDObjectLink& link : mObjectLinks) {
|
|
if ((link.mParent == aParentLifetime.mObject &&
|
|
link.mChild == aChildLifetime.mObject) &&
|
|
aParentLifetime.IsAliveAt(link.mLinkingIndex) &&
|
|
aChildLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
|
|
link.mUnlinkingIndex = Some(aIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::DestroyLifetimeLinks(const DDLifetime& aLifetime)
|
|
{
|
|
mObjectLinks.RemoveElementsBy([&](DDObjectLink& link) {
|
|
return (link.mParent == aLifetime.mObject ||
|
|
link.mChild == aLifetime.mObject) &&
|
|
aLifetime.IsAliveAt(link.mLinkingIndex);
|
|
});
|
|
}
|
|
|
|
size_t
|
|
DDMediaLogs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
size_t size = aMallocSizeOf(this) +
|
|
mLifetimes.SizeOfExcludingThis(aMallocSizeOf) +
|
|
mMediaLogs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
|
|
mObjectLinks.ShallowSizeOfExcludingThis(aMallocSizeOf) +
|
|
mPendingPromises.ShallowSizeOfExcludingThis(aMallocSizeOf);
|
|
for (const DDMediaLog& log : mMediaLogs) {
|
|
size += log.SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::ProcessBuffer()
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
|
|
mMessagesQueue.PopAll([this](const DDLogMessage& message) {
|
|
DDL_DEBUG("Processing: %s", message.Print().Data());
|
|
|
|
// Either this message will carry a new object for which to create a
|
|
// lifetime, or we'll find an existing one.
|
|
DDLifetime& lifetime =
|
|
FindOrCreateLifetime(message.mObject, message.mIndex, message.mTimeStamp);
|
|
|
|
// Copy the message contents (without the mValid flag) to the
|
|
// appropriate MediaLog corresponding to the message's object lifetime.
|
|
LogFor(lifetime.mMediaElement)
|
|
.mMessages.AppendElement(static_cast<const DDLogMessage&>(message));
|
|
|
|
switch (message.mCategory) {
|
|
case DDLogCategory::_Construction:
|
|
// The FindOrCreateLifetime above will have set a construction time,
|
|
// so there's nothing more we need to do here.
|
|
MOZ_ASSERT(lifetime.mConstructionTimeStamp);
|
|
break;
|
|
|
|
case DDLogCategory::_DerivedConstruction:
|
|
// The FindOrCreateLifetime above will have set a construction time.
|
|
MOZ_ASSERT(lifetime.mConstructionTimeStamp);
|
|
// A derived construction must come with the base object.
|
|
MOZ_ASSERT(message.mValue.is<DDLogObject>());
|
|
{
|
|
const DDLogObject& base = message.mValue.as<DDLogObject>();
|
|
DDLifetime& baseLifetime =
|
|
FindOrCreateLifetime(base, message.mIndex, message.mTimeStamp);
|
|
// FindOrCreateLifetime could have moved `lifetime`.
|
|
DDLifetime* lifetime2 =
|
|
mLifetimes.FindLifetime(message.mObject, message.mIndex);
|
|
MOZ_ASSERT(lifetime2);
|
|
// Assume there's no multiple-inheritance (at least for the types
|
|
// we're watching.)
|
|
if (baseLifetime.mDerivedObject.Pointer()) {
|
|
DDL_WARN(
|
|
"base '%s' was already derived as '%s', now deriving as '%s'",
|
|
baseLifetime.Printf().get(),
|
|
baseLifetime.mDerivedObject.Printf().get(),
|
|
lifetime2->Printf().get());
|
|
}
|
|
baseLifetime.mDerivedObject = lifetime2->mObject;
|
|
baseLifetime.mDerivedObjectLinkingIndex = message.mIndex;
|
|
// Link the base and derived objects, to ensure they go to the same
|
|
// log.
|
|
LinkLifetimes(*lifetime2, "is-a", baseLifetime, message.mIndex);
|
|
}
|
|
break;
|
|
|
|
case DDLogCategory::_Destruction:
|
|
lifetime.mDestructionIndex = message.mIndex;
|
|
lifetime.mDestructionTimeStamp = message.mTimeStamp;
|
|
UnlinkLifetime(lifetime, message.mIndex);
|
|
break;
|
|
|
|
case DDLogCategory::_Link:
|
|
MOZ_ASSERT(message.mValue.is<DDLogObject>());
|
|
{
|
|
const DDLogObject& child = message.mValue.as<DDLogObject>();
|
|
DDLifetime& childLifetime =
|
|
FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
|
|
// FindOrCreateLifetime could have moved `lifetime`.
|
|
DDLifetime* lifetime2 =
|
|
mLifetimes.FindLifetime(message.mObject, message.mIndex);
|
|
MOZ_ASSERT(lifetime2);
|
|
LinkLifetimes(
|
|
*lifetime2, message.mLabel, childLifetime, message.mIndex);
|
|
}
|
|
break;
|
|
|
|
case DDLogCategory::_Unlink:
|
|
MOZ_ASSERT(message.mValue.is<DDLogObject>());
|
|
{
|
|
const DDLogObject& child = message.mValue.as<DDLogObject>();
|
|
DDLifetime& childLifetime =
|
|
FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
|
|
// FindOrCreateLifetime could have moved `lifetime`.
|
|
DDLifetime* lifetime2 =
|
|
mLifetimes.FindLifetime(message.mObject, message.mIndex);
|
|
MOZ_ASSERT(lifetime2);
|
|
UnlinkLifetimes(*lifetime2, childLifetime, message.mIndex);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Anything else: Nothing more to do.
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
struct StringWriteFunc : public JSONWriteFunc
|
|
{
|
|
nsCString& mCString;
|
|
explicit StringWriteFunc(nsCString& aCString)
|
|
: mCString(aCString)
|
|
{
|
|
}
|
|
void Write(const char* aStr) override { mCString.Append(aStr); }
|
|
};
|
|
|
|
void
|
|
DDMediaLogs::FulfillPromises()
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
|
|
MozPromiseHolder<LogMessagesPromise> promiseHolder;
|
|
const dom::HTMLMediaElement* mediaElement = nullptr;
|
|
{
|
|
// Grab the first pending promise (if any).
|
|
// Note that we don't pop it yet, so we don't potentially leave the list
|
|
// empty and therefore allow another processing task to be dispatched.
|
|
MutexAutoLock lock(mMutex);
|
|
if (mPendingPromises.IsEmpty()) {
|
|
return;
|
|
}
|
|
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
|
|
mediaElement = mPendingPromises[0].mMediaElement;
|
|
}
|
|
for (;;) {
|
|
DDMediaLog* log = GetLogFor(mediaElement);
|
|
if (!log) {
|
|
// No such media element -> Reject this promise.
|
|
DDL_INFO("Rejecting promise for HTMLMediaElement[%p] - Cannot find log",
|
|
mediaElement);
|
|
promiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__);
|
|
// Pop this rejected promise, fetch next one.
|
|
MutexAutoLock lock(mMutex);
|
|
mPendingPromises.RemoveElementAt(0);
|
|
if (mPendingPromises.IsEmpty()) {
|
|
break;
|
|
}
|
|
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
|
|
mediaElement = mPendingPromises[0].mMediaElement;
|
|
continue;
|
|
}
|
|
|
|
nsCString json;
|
|
JSONWriter jw{ MakeUnique<StringWriteFunc>(json) };
|
|
jw.Start();
|
|
jw.StartArrayProperty("messages");
|
|
for (const DDLogMessage& message : log->mMessages) {
|
|
jw.StartObjectElement(JSONWriter::SingleLineStyle);
|
|
jw.IntProperty("i", message.mIndex.Value());
|
|
jw.DoubleProperty("ts", ToSeconds(message.mTimeStamp));
|
|
DDLifetime* lifetime =
|
|
mLifetimes.FindLifetime(message.mObject, message.mIndex);
|
|
if (lifetime) {
|
|
jw.IntProperty("ob", lifetime->mTag);
|
|
} else {
|
|
jw.StringProperty("ob",
|
|
nsPrintfCString(R"("%s[%p]")",
|
|
message.mObject.TypeName(),
|
|
message.mObject.Pointer())
|
|
.get());
|
|
}
|
|
jw.StringProperty("cat", ToShortString(message.mCategory));
|
|
if (message.mLabel && message.mLabel[0] != '\0') {
|
|
jw.StringProperty("lbl", message.mLabel);
|
|
}
|
|
if (!message.mValue.is<DDNoValue>()) {
|
|
if (message.mValue.is<DDLogObject>()) {
|
|
const DDLogObject& ob2 = message.mValue.as<DDLogObject>();
|
|
DDLifetime* lifetime2 = mLifetimes.FindLifetime(ob2, message.mIndex);
|
|
if (lifetime2) {
|
|
jw.IntProperty("val", lifetime2->mTag);
|
|
} else {
|
|
ToJSON(message.mValue, jw, "val");
|
|
}
|
|
} else {
|
|
ToJSON(message.mValue, jw, "val");
|
|
}
|
|
}
|
|
jw.EndObject();
|
|
}
|
|
jw.EndArray();
|
|
jw.StartObjectProperty("objects");
|
|
mLifetimes.Visit(
|
|
mediaElement,
|
|
[&](const DDLifetime& lifetime) {
|
|
jw.StartObjectProperty(nsPrintfCString("%" PRIi32, lifetime.mTag).get(),
|
|
JSONWriter::SingleLineStyle);
|
|
jw.IntProperty("tag", lifetime.mTag);
|
|
jw.StringProperty("cls", lifetime.mObject.TypeName());
|
|
jw.StringProperty(
|
|
"ptr", nsPrintfCString("%p", lifetime.mObject.Pointer()).get());
|
|
jw.IntProperty("con", lifetime.mConstructionIndex.Value());
|
|
jw.DoubleProperty("con_ts", ToSeconds(lifetime.mConstructionTimeStamp));
|
|
if (lifetime.mDestructionTimeStamp) {
|
|
jw.IntProperty("des", lifetime.mDestructionIndex.Value());
|
|
jw.DoubleProperty("des_ts",
|
|
ToSeconds(lifetime.mDestructionTimeStamp));
|
|
}
|
|
if (lifetime.mDerivedObject.Pointer()) {
|
|
DDLifetime* derived = mLifetimes.FindLifetime(
|
|
lifetime.mDerivedObject, lifetime.mDerivedObjectLinkingIndex);
|
|
if (derived) {
|
|
jw.IntProperty("drvd", derived->mTag);
|
|
}
|
|
}
|
|
jw.EndObject();
|
|
},
|
|
// If there were no (new) messages, only give the main HTMLMediaElement
|
|
// object (used to identify this log against the correct element.)
|
|
log->mMessages.IsEmpty());
|
|
jw.EndObject();
|
|
jw.End();
|
|
DDL_DEBUG("RetrieveMessages(%p) ->\n%s", mediaElement, json.get());
|
|
|
|
// This log exists (new messages or not) -> Resolve this promise.
|
|
DDL_INFO("Resolving promise for HTMLMediaElement[%p] with messages %" PRImi
|
|
"-%" PRImi,
|
|
mediaElement,
|
|
log->mMessages.IsEmpty() ? 0 : log->mMessages[0].mIndex.Value(),
|
|
log->mMessages.IsEmpty()
|
|
? 0
|
|
: log->mMessages[log->mMessages.Length() - 1].mIndex.Value());
|
|
promiseHolder.Resolve(Move(json), __func__);
|
|
|
|
// Remove exported messages.
|
|
log->mMessages.Clear();
|
|
|
|
// Pop this resolved promise, fetch next one.
|
|
MutexAutoLock lock(mMutex);
|
|
mPendingPromises.RemoveElementAt(0);
|
|
if (mPendingPromises.IsEmpty()) {
|
|
break;
|
|
}
|
|
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
|
|
mediaElement = mPendingPromises[0].mMediaElement;
|
|
}
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::CleanUpLogs()
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
|
|
DDTimeStamp now = DDNow();
|
|
|
|
// Keep up to 30s of unclassified messages (if a message doesn't get
|
|
// classified this quickly, it probably never will be.)
|
|
static const double sMaxAgeUnclassifiedMessages_s = 30.0;
|
|
// Keep "dead" log (video element and dependents were destroyed) for up to
|
|
// 2 minutes, in case the user wants to look at it after the facts.
|
|
static const double sMaxAgeDeadLog_s = 120.0;
|
|
// Keep old messages related to a live video for up to 5 minutes.
|
|
static const double sMaxAgeClassifiedMessages_s = 300.0;
|
|
|
|
for (size_t logIndexPlus1 = mMediaLogs.Length(); logIndexPlus1 != 0;
|
|
--logIndexPlus1) {
|
|
DDMediaLog& log = mMediaLogs[logIndexPlus1 - 1];
|
|
if (log.mMediaElement) {
|
|
// Remove logs for which no lifetime still existed some time ago.
|
|
bool used = mLifetimes.VisitBreakable(
|
|
log.mMediaElement, [&](const DDLifetime& lifetime) {
|
|
// Do we still have a lifetime that existed recently enough?
|
|
return !lifetime.mDestructionTimeStamp ||
|
|
(now - lifetime.mDestructionTimeStamp).ToSeconds() <=
|
|
sMaxAgeDeadLog_s;
|
|
});
|
|
if (!used) {
|
|
DDL_INFO("Removed old log for media element %p", log.mMediaElement);
|
|
mLifetimes.Visit(log.mMediaElement, [&](const DDLifetime& lifetime) {
|
|
DestroyLifetimeLinks(lifetime);
|
|
});
|
|
mLifetimes.RemoveLifetimesFor(log.mMediaElement);
|
|
mMediaLogs.RemoveElementAt(logIndexPlus1 - 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Remove old messages.
|
|
size_t old = 0;
|
|
const size_t len = log.mMessages.Length();
|
|
while (old < len && (now - log.mMessages[old].mTimeStamp).ToSeconds() >
|
|
(log.mMediaElement ? sMaxAgeClassifiedMessages_s
|
|
: sMaxAgeUnclassifiedMessages_s)) {
|
|
++old;
|
|
}
|
|
if (old != 0) {
|
|
// We are going to remove `old` messages.
|
|
// First, remove associated destroyed lifetimes that are not used after
|
|
// these old messages. (We want to keep non-destroyed lifetimes, in
|
|
// case they get used later on.)
|
|
size_t removedLifetimes = 0;
|
|
for (size_t i = 0; i < old; ++i) {
|
|
auto RemoveDestroyedUnusedLifetime = [&](DDLifetime* lifetime) {
|
|
if (!lifetime->mDestructionTimeStamp) {
|
|
// Lifetime is still alive, keep it.
|
|
return;
|
|
}
|
|
bool used = false;
|
|
for (size_t after = old; after < len; ++after) {
|
|
const DDLogMessage message = log.mMessages[i];
|
|
if (!lifetime->IsAliveAt(message.mIndex)) {
|
|
// Lifetime is already dead, and not used yet -> kill it.
|
|
break;
|
|
}
|
|
const DDLogObject& ob = message.mObject;
|
|
if (lifetime->mObject == ob) {
|
|
used = true;
|
|
break;
|
|
}
|
|
if (message.mValue.is<DDLogObject>()) {
|
|
if (lifetime->mObject == message.mValue.as<DDLogObject>()) {
|
|
used = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!used) {
|
|
DestroyLifetimeLinks(*lifetime);
|
|
mLifetimes.RemoveLifetime(lifetime);
|
|
++removedLifetimes;
|
|
}
|
|
};
|
|
|
|
const DDLogMessage message = log.mMessages[i];
|
|
const DDLogObject& ob = message.mObject;
|
|
|
|
DDLifetime* lifetime1 = mLifetimes.FindLifetime(ob, message.mIndex);
|
|
if (lifetime1) {
|
|
RemoveDestroyedUnusedLifetime(lifetime1);
|
|
}
|
|
|
|
if (message.mValue.is<DDLogObject>()) {
|
|
DDLifetime* lifetime2 = mLifetimes.FindLifetime(
|
|
message.mValue.as<DDLogObject>(), message.mIndex);
|
|
if (lifetime2) {
|
|
RemoveDestroyedUnusedLifetime(lifetime2);
|
|
}
|
|
}
|
|
}
|
|
DDL_INFO("Removed %zu messages (#%" PRImi " %f - #%" PRImi
|
|
" %f) and %zu lifetimes from log for media element %p",
|
|
old,
|
|
log.mMessages[0].mIndex.Value(),
|
|
ToSeconds(log.mMessages[0].mTimeStamp),
|
|
log.mMessages[old - 1].mIndex.Value(),
|
|
ToSeconds(log.mMessages[old - 1].mTimeStamp),
|
|
removedLifetimes,
|
|
log.mMediaElement);
|
|
log.mMessages.RemoveElementsAt(0, old);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DDMediaLogs::ProcessLog()
|
|
{
|
|
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
|
|
ProcessBuffer();
|
|
FulfillPromises();
|
|
CleanUpLogs();
|
|
DDL_INFO("DDMediaLog size: %zu", SizeOfIncludingThis(moz_malloc_size_of));
|
|
}
|
|
|
|
nsresult
|
|
DDMediaLogs::DispatchProcessLog(const MutexAutoLock& aProofOfLock)
|
|
{
|
|
if (!mThread) {
|
|
return NS_ERROR_SERVICE_NOT_AVAILABLE;
|
|
}
|
|
return mThread->Dispatch(
|
|
NS_NewRunnableFunction("ProcessLog", [this] { ProcessLog(); }),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
nsresult
|
|
DDMediaLogs::DispatchProcessLog()
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
return DispatchProcessLog(lock);
|
|
}
|
|
|
|
RefPtr<DDMediaLogs::LogMessagesPromise>
|
|
DDMediaLogs::RetrieveMessages(const dom::HTMLMediaElement* aMediaElement)
|
|
{
|
|
MozPromiseHolder<LogMessagesPromise> holder;
|
|
RefPtr<LogMessagesPromise> promise = holder.Ensure(__func__);
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
// If there were unfulfilled promises, we know processing has already
|
|
// been requested.
|
|
if (mPendingPromises.IsEmpty()) {
|
|
// But if we're the first one, start processing.
|
|
nsresult rv = DispatchProcessLog(lock);
|
|
if (NS_FAILED(rv)) {
|
|
holder.Reject(rv, __func__);
|
|
}
|
|
}
|
|
mPendingPromises.AppendElement(
|
|
PendingPromise{ Move(holder), aMediaElement });
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
} // namespace mozilla
|