summaryrefslogtreecommitdiffstats
path: root/dom/media/doctor/DDMediaLogs.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/media/doctor/DDMediaLogs.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/doctor/DDMediaLogs.cpp')
-rw-r--r--dom/media/doctor/DDMediaLogs.cpp667
1 files changed, 667 insertions, 0 deletions
diff --git a/dom/media/doctor/DDMediaLogs.cpp b/dom/media/doctor/DDMediaLogs.cpp
new file mode 100644
index 0000000000..442e03d3fa
--- /dev/null
+++ b/dom/media/doctor/DDMediaLogs.cpp
@@ -0,0 +1,667 @@
+/* -*- 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 "nsIThread.h"
+#include "nsIThreadManager.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+
+namespace mozilla {
+
+/* static */ DDMediaLogs::ConstructionResult DDMediaLogs::New() {
+ nsCOMPtr<nsIThread> mThread;
+ nsresult rv =
+ NS_NewNamedThread("DDMediaLogs", getter_AddRefs(mThread), nullptr,
+ {.stackSize = nsIThreadManager::kThreadPoolStackSize});
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return {rv, nullptr};
+ }
+
+ return {rv, new DDMediaLogs(std::move(mThread))};
+}
+
+DDMediaLogs::DDMediaLogs(nsCOMPtr<nsIThread>&& aThread)
+ : mMediaLogs(1), mMutex("DDMediaLogs"), mThread(std::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(std::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) +
+ // This will usually be called after processing, so negligible
+ // external data should still be present in the queue.
+ mMessagesQueue.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+ 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;
+ }
+ });
+}
+
+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 = std::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 = std::move(mPendingPromises[0].mPromiseHolder);
+ mediaElement = mPendingPromises[0].mMediaElement;
+ continue;
+ }
+
+ JSONStringWriteFunc<nsCString> json;
+ JSONWriter jw{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()));
+ }
+ jw.StringProperty("cat",
+ MakeStringSpan(ToShortString(message.mCategory)));
+ if (message.mLabel && message.mLabel[0] != '\0') {
+ jw.StringProperty("lbl", MakeStringSpan(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),
+ JSONWriter::SingleLineStyle);
+ jw.IntProperty("tag", lifetime.mTag);
+ jw.StringProperty("cls", MakeStringSpan(lifetime.mObject.TypeName()));
+ jw.StringProperty("ptr",
+ nsPrintfCString("%p", lifetime.mObject.Pointer()));
+ 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.StringCRef().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(std::move(json).StringRRef(), __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 = std::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("ProcessLog() completed - 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() {
+ DDL_INFO("DispatchProcessLog() - Yet-unprocessed message buffers: %d",
+ mMessagesQueue.LiveBuffersStats().mCount);
+ 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{std::move(holder), aMediaElement});
+ }
+ return promise;
+}
+
+} // namespace mozilla