tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DDMediaLogs.cpp (25641B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "DDMediaLogs.h"
      8 
      9 #include "DDLogUtils.h"
     10 #include "mozilla/JSONStringWriteFuncs.h"
     11 #include "nsIThread.h"
     12 #include "nsIThreadManager.h"
     13 
     14 namespace mozilla {
     15 
     16 /* static */ DDMediaLogs::ConstructionResult DDMediaLogs::New() {
     17  nsCOMPtr<nsIThread> mThread;
     18  nsresult rv =
     19      NS_NewNamedThread("DDMediaLogs", getter_AddRefs(mThread), nullptr,
     20                        {.stackSize = nsIThreadManager::kThreadPoolStackSize});
     21  if (NS_WARN_IF(NS_FAILED(rv))) {
     22    return {rv, nullptr};
     23  }
     24 
     25  return {rv, new DDMediaLogs(std::move(mThread))};
     26 }
     27 
     28 DDMediaLogs::DDMediaLogs(nsCOMPtr<nsIThread>&& aThread)
     29    : mMediaLogs(1), mMutex("DDMediaLogs"), mThread(std::move(aThread)) {
     30  mMediaLogs.SetLength(1);
     31  mMediaLogs[0].mMediaElement = nullptr;
     32  DDL_INFO("DDMediaLogs constructed, processing thread: %p", mThread.get());
     33 }
     34 
     35 DDMediaLogs::~DDMediaLogs() {
     36  // Perform end-of-life processing, ensure the processing thread is shutdown.
     37  Shutdown(/* aPanic = */ false);
     38 }
     39 
     40 void DDMediaLogs::Panic() { Shutdown(/* aPanic = */ true); }
     41 
     42 void DDMediaLogs::Shutdown(bool aPanic) {
     43  nsCOMPtr<nsIThread> thread;
     44  {
     45    MutexAutoLock lock(mMutex);
     46    thread.swap(mThread);
     47  }
     48  if (!thread) {
     49    // Already shutdown, nothing more to do.
     50    return;
     51  }
     52 
     53  DDL_INFO("DDMediaLogs::Shutdown will shutdown thread: %p", thread.get());
     54  // Will block until pending tasks have completed, and thread is dead.
     55  thread->Shutdown();
     56 
     57  if (aPanic) {
     58    mMessagesQueue.PopAll([](const DDLogMessage&) {});
     59    MutexAutoLock lock(mMutex);
     60    mLifetimes.Clear();
     61    mMediaLogs.Clear();
     62    mObjectLinks.Clear();
     63    mPendingPromises.Clear();
     64    return;
     65  }
     66 
     67  // Final processing is only necessary to output to MOZ_LOG=DDLoggerEnd,
     68  // so there's no point doing any of it if that MOZ_LOG is not enabled.
     69  if (MOZ_LOG_TEST(sDecoderDoctorLoggerEndLog, mozilla::LogLevel::Info)) {
     70    DDL_DEBUG("Perform final DDMediaLogs processing...");
     71    // The processing thread is dead, so we can safely call ProcessLog()
     72    // directly from this thread.
     73    ProcessLog();
     74 
     75    for (const DDMediaLog& mediaLog : mMediaLogs) {
     76      if (mediaLog.mMediaElement) {
     77        DDLE_INFO("---");
     78      }
     79      DDLE_INFO("--- Log for HTMLMediaElement[%p] ---", mediaLog.mMediaElement);
     80      for (const DDLogMessage& message : mediaLog.mMessages) {
     81        DDLE_LOG(message.mCategory <= DDLogCategory::_Unlink
     82                     ? mozilla::LogLevel::Debug
     83                     : mozilla::LogLevel::Info,
     84                 "%s", message.Print(mLifetimes).get());
     85      }
     86      DDLE_DEBUG("--- End log for HTMLMediaElement[%p] ---",
     87                 mediaLog.mMediaElement);
     88    }
     89  }
     90 }
     91 
     92 DDMediaLog& DDMediaLogs::LogForUnassociatedMessages() {
     93  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
     94  return mMediaLogs[0];
     95 }
     96 const DDMediaLog& DDMediaLogs::LogForUnassociatedMessages() const {
     97  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
     98  return mMediaLogs[0];
     99 }
    100 
    101 DDMediaLog* DDMediaLogs::GetLogFor(const dom::HTMLMediaElement* aMediaElement) {
    102  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    103  if (!aMediaElement) {
    104    return &LogForUnassociatedMessages();
    105  }
    106  for (DDMediaLog& log : mMediaLogs) {
    107    if (log.mMediaElement == aMediaElement) {
    108      return &log;
    109    }
    110  }
    111  return nullptr;
    112 }
    113 
    114 DDMediaLog& DDMediaLogs::LogFor(const dom::HTMLMediaElement* aMediaElement) {
    115  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    116  DDMediaLog* log = GetLogFor(aMediaElement);
    117  if (!log) {
    118    log = mMediaLogs.AppendElement();
    119    log->mMediaElement = aMediaElement;
    120  }
    121  return *log;
    122 }
    123 
    124 void DDMediaLogs::SetMediaElement(DDLifetime& aLifetime,
    125                                  const dom::HTMLMediaElement* aMediaElement) {
    126  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    127  DDMediaLog& log = LogFor(aMediaElement);
    128 
    129  // List of lifetimes that are to be linked to aMediaElement.
    130  nsTArray<DDLifetime*> lifetimes;
    131  // We start with the given lifetime.
    132  lifetimes.AppendElement(&aLifetime);
    133  for (size_t i = 0; i < lifetimes.Length(); ++i) {
    134    DDLifetime& lifetime = *lifetimes[i];
    135    // Link the lifetime to aMediaElement.
    136    lifetime.mMediaElement = aMediaElement;
    137    // Classified lifetime's tag is a positive index from the DDMediaLog.
    138    lifetime.mTag = ++log.mLifetimeCount;
    139    DDL_DEBUG("%s -> HTMLMediaElement[%p]", lifetime.Printf().get(),
    140              aMediaElement);
    141 
    142    // Go through the lifetime's existing linked lifetimes, if any is not
    143    // already linked to aMediaElement, add it to the list so it will get
    144    // linked in a later loop.
    145    for (auto& link : mObjectLinks) {
    146      if (lifetime.IsAliveAt(link.mLinkingIndex)) {
    147        if (lifetime.mObject == link.mParent) {
    148          DDLifetime* childLifetime =
    149              mLifetimes.FindLifetime(link.mChild, link.mLinkingIndex);
    150          if (childLifetime && !childLifetime->mMediaElement &&
    151              !lifetimes.Contains(childLifetime)) {
    152            lifetimes.AppendElement(childLifetime);
    153          }
    154        } else if (lifetime.mObject == link.mChild) {
    155          DDLifetime* parentLifetime =
    156              mLifetimes.FindLifetime(link.mParent, link.mLinkingIndex);
    157          if (parentLifetime && !parentLifetime->mMediaElement &&
    158              !lifetimes.Contains(parentLifetime)) {
    159            lifetimes.AppendElement(parentLifetime);
    160          }
    161        }
    162      }
    163    }
    164  }
    165 
    166  // Now we need to move yet-unclassified messages related to the just-set
    167  // elements, to the appropriate MediaElement list.
    168  DDMediaLog::LogMessages& messages = log.mMessages;
    169  DDMediaLog::LogMessages& messages0 = LogForUnassociatedMessages().mMessages;
    170  for (size_t i = 0; i < messages0.Length();
    171       /* increment inside the loop */) {
    172    DDLogMessage& message = messages0[i];
    173    bool found = false;
    174    for (const DDLifetime* lifetime : lifetimes) {
    175      if (lifetime->mObject == message.mObject) {
    176        found = true;
    177        break;
    178      }
    179    }
    180    if (found) {
    181      messages.AppendElement(std::move(message));
    182      messages0.RemoveElementAt(i);
    183      // No increment, as we've removed this element; next element is now at
    184      // the same index.
    185    } else {
    186      // Not touching this element, increment index to go to next element.
    187      ++i;
    188    }
    189  }
    190 }
    191 
    192 DDLifetime& DDMediaLogs::FindOrCreateLifetime(const DDLogObject& aObject,
    193                                              DDMessageIndex aIndex,
    194                                              const DDTimeStamp& aTimeStamp) {
    195  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    196  // Try to find lifetime corresponding to message object.
    197  DDLifetime* lifetime = mLifetimes.FindLifetime(aObject, aIndex);
    198  if (!lifetime) {
    199    // No lifetime yet, create one.
    200    lifetime = &(mLifetimes.CreateLifetime(aObject, aIndex, aTimeStamp));
    201    if (MOZ_UNLIKELY(aObject.TypeName() ==
    202                     DDLoggedTypeTraits<dom::HTMLMediaElement>::Name())) {
    203      const dom::HTMLMediaElement* mediaElement =
    204          static_cast<const dom::HTMLMediaElement*>(aObject.Pointer());
    205      SetMediaElement(*lifetime, mediaElement);
    206      DDL_DEBUG("%s -> new lifetime: %s with MediaElement %p",
    207                aObject.Printf().get(), lifetime->Printf().get(), mediaElement);
    208    } else {
    209      DDL_DEBUG("%s -> new lifetime: %s", aObject.Printf().get(),
    210                lifetime->Printf().get());
    211    }
    212  }
    213 
    214  return *lifetime;
    215 }
    216 
    217 void DDMediaLogs::LinkLifetimes(DDLifetime& aParentLifetime,
    218                                const char* aLinkName,
    219                                DDLifetime& aChildLifetime,
    220                                DDMessageIndex aIndex) {
    221  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    222  mObjectLinks.AppendElement(DDObjectLink{
    223      aParentLifetime.mObject, aChildLifetime.mObject, aLinkName, aIndex});
    224  if (aParentLifetime.mMediaElement) {
    225    if (!aChildLifetime.mMediaElement) {
    226      SetMediaElement(aChildLifetime, aParentLifetime.mMediaElement);
    227    }
    228  } else if (aChildLifetime.mMediaElement) {
    229    if (!aParentLifetime.mMediaElement) {
    230      SetMediaElement(aParentLifetime, aChildLifetime.mMediaElement);
    231    }
    232  }
    233 }
    234 
    235 void DDMediaLogs::UnlinkLifetime(DDLifetime& aLifetime, DDMessageIndex aIndex) {
    236  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    237  for (DDObjectLink& link : mObjectLinks) {
    238    if ((link.mParent == aLifetime.mObject ||
    239         link.mChild == aLifetime.mObject) &&
    240        aLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
    241      link.mUnlinkingIndex = Some(aIndex);
    242    }
    243  }
    244 };
    245 
    246 void DDMediaLogs::UnlinkLifetimes(DDLifetime& aParentLifetime,
    247                                  DDLifetime& aChildLifetime,
    248                                  DDMessageIndex aIndex) {
    249  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    250  for (DDObjectLink& link : mObjectLinks) {
    251    if ((link.mParent == aParentLifetime.mObject &&
    252         link.mChild == aChildLifetime.mObject) &&
    253        aParentLifetime.IsAliveAt(link.mLinkingIndex) &&
    254        aChildLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
    255      link.mUnlinkingIndex = Some(aIndex);
    256    }
    257  }
    258 }
    259 
    260 void DDMediaLogs::DestroyLifetimeLinks(const DDLifetime& aLifetime) {
    261  mObjectLinks.RemoveElementsBy([&](DDObjectLink& link) {
    262    return (link.mParent == aLifetime.mObject ||
    263            link.mChild == aLifetime.mObject) &&
    264           aLifetime.IsAliveAt(link.mLinkingIndex);
    265  });
    266 }
    267 
    268 size_t DDMediaLogs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    269  size_t size = aMallocSizeOf(this) +
    270                // This will usually be called after processing, so negligible
    271                // external data should still be present in the queue.
    272                mMessagesQueue.ShallowSizeOfExcludingThis(aMallocSizeOf) +
    273                mLifetimes.SizeOfExcludingThis(aMallocSizeOf) +
    274                mMediaLogs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
    275                mObjectLinks.ShallowSizeOfExcludingThis(aMallocSizeOf) +
    276                mPendingPromises.ShallowSizeOfExcludingThis(aMallocSizeOf);
    277  for (const DDMediaLog& log : mMediaLogs) {
    278    size += log.SizeOfExcludingThis(aMallocSizeOf);
    279  }
    280  return size;
    281 }
    282 
    283 void DDMediaLogs::ProcessBuffer() {
    284  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    285 
    286  mMessagesQueue.PopAll([this](const DDLogMessage& message) {
    287    DDL_DEBUG("Processing: %s", message.Print().Data());
    288 
    289    // Either this message will carry a new object for which to create a
    290    // lifetime, or we'll find an existing one.
    291    DDLifetime& lifetime = FindOrCreateLifetime(message.mObject, message.mIndex,
    292                                                message.mTimeStamp);
    293 
    294    // Copy the message contents (without the mValid flag) to the
    295    // appropriate MediaLog corresponding to the message's object lifetime.
    296    LogFor(lifetime.mMediaElement)
    297        .mMessages.AppendElement(static_cast<const DDLogMessage&>(message));
    298 
    299    switch (message.mCategory) {
    300      case DDLogCategory::_Construction:
    301        // The FindOrCreateLifetime above will have set a construction time,
    302        // so there's nothing more we need to do here.
    303        MOZ_ASSERT(lifetime.mConstructionTimeStamp);
    304        break;
    305 
    306      case DDLogCategory::_DerivedConstruction:
    307        // The FindOrCreateLifetime above will have set a construction time.
    308        MOZ_ASSERT(lifetime.mConstructionTimeStamp);
    309        // A derived construction must come with the base object.
    310        MOZ_ASSERT(message.mValue.is<DDLogObject>());
    311        {
    312          const DDLogObject& base = message.mValue.as<DDLogObject>();
    313          DDLifetime& baseLifetime =
    314              FindOrCreateLifetime(base, message.mIndex, message.mTimeStamp);
    315          // FindOrCreateLifetime could have moved `lifetime`.
    316          DDLifetime* lifetime2 =
    317              mLifetimes.FindLifetime(message.mObject, message.mIndex);
    318          MOZ_ASSERT(lifetime2);
    319          // Assume there's no multiple-inheritance (at least for the types
    320          // we're watching.)
    321          if (baseLifetime.mDerivedObject.Pointer()) {
    322            DDL_WARN(
    323                "base '%s' was already derived as '%s', now deriving as '%s'",
    324                baseLifetime.Printf().get(),
    325                baseLifetime.mDerivedObject.Printf().get(),
    326                lifetime2->Printf().get());
    327          }
    328          baseLifetime.mDerivedObject = lifetime2->mObject;
    329          baseLifetime.mDerivedObjectLinkingIndex = message.mIndex;
    330          // Link the base and derived objects, to ensure they go to the same
    331          // log.
    332          LinkLifetimes(*lifetime2, "is-a", baseLifetime, message.mIndex);
    333        }
    334        break;
    335 
    336      case DDLogCategory::_Destruction:
    337        lifetime.mDestructionIndex = message.mIndex;
    338        lifetime.mDestructionTimeStamp = message.mTimeStamp;
    339        UnlinkLifetime(lifetime, message.mIndex);
    340        break;
    341 
    342      case DDLogCategory::_Link:
    343        MOZ_ASSERT(message.mValue.is<DDLogObject>());
    344        {
    345          const DDLogObject& child = message.mValue.as<DDLogObject>();
    346          DDLifetime& childLifetime =
    347              FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
    348          // FindOrCreateLifetime could have moved `lifetime`.
    349          DDLifetime* lifetime2 =
    350              mLifetimes.FindLifetime(message.mObject, message.mIndex);
    351          MOZ_ASSERT(lifetime2);
    352          LinkLifetimes(*lifetime2, message.mLabel, childLifetime,
    353                        message.mIndex);
    354        }
    355        break;
    356 
    357      case DDLogCategory::_Unlink:
    358        MOZ_ASSERT(message.mValue.is<DDLogObject>());
    359        {
    360          const DDLogObject& child = message.mValue.as<DDLogObject>();
    361          DDLifetime& childLifetime =
    362              FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
    363          // FindOrCreateLifetime could have moved `lifetime`.
    364          DDLifetime* lifetime2 =
    365              mLifetimes.FindLifetime(message.mObject, message.mIndex);
    366          MOZ_ASSERT(lifetime2);
    367          UnlinkLifetimes(*lifetime2, childLifetime, message.mIndex);
    368        }
    369        break;
    370 
    371      default:
    372        // Anything else: Nothing more to do.
    373        break;
    374    }
    375  });
    376 }
    377 
    378 void DDMediaLogs::FulfillPromises() {
    379  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    380 
    381  MozPromiseHolder<LogMessagesPromise> promiseHolder;
    382  const dom::HTMLMediaElement* mediaElement = nullptr;
    383  {
    384    // Grab the first pending promise (if any).
    385    // Note that we don't pop it yet, so we don't potentially leave the list
    386    // empty and therefore allow another processing task to be dispatched.
    387    MutexAutoLock lock(mMutex);
    388    if (mPendingPromises.IsEmpty()) {
    389      return;
    390    }
    391    promiseHolder = std::move(mPendingPromises[0].mPromiseHolder);
    392    mediaElement = mPendingPromises[0].mMediaElement;
    393  }
    394  for (;;) {
    395    DDMediaLog* log = GetLogFor(mediaElement);
    396    if (!log) {
    397      // No such media element -> Reject this promise.
    398      DDL_INFO("Rejecting promise for HTMLMediaElement[%p] - Cannot find log",
    399               mediaElement);
    400      promiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__);
    401      // Pop this rejected promise, fetch next one.
    402      MutexAutoLock lock(mMutex);
    403      mPendingPromises.RemoveElementAt(0);
    404      if (mPendingPromises.IsEmpty()) {
    405        break;
    406      }
    407      promiseHolder = std::move(mPendingPromises[0].mPromiseHolder);
    408      mediaElement = mPendingPromises[0].mMediaElement;
    409      continue;
    410    }
    411 
    412    JSONStringWriteFunc<nsCString> json;
    413    JSONWriter jw{json};
    414    jw.Start();
    415    jw.StartArrayProperty("messages");
    416    for (const DDLogMessage& message : log->mMessages) {
    417      jw.StartObjectElement(JSONWriter::SingleLineStyle);
    418      jw.IntProperty("i", message.mIndex.Value());
    419      jw.DoubleProperty("ts", ToSeconds(message.mTimeStamp));
    420      DDLifetime* lifetime =
    421          mLifetimes.FindLifetime(message.mObject, message.mIndex);
    422      if (lifetime) {
    423        jw.IntProperty("ob", lifetime->mTag);
    424      } else {
    425        jw.StringProperty(
    426            "ob", nsPrintfCString(R"("%s[%p]")", message.mObject.TypeName(),
    427                                  message.mObject.Pointer()));
    428      }
    429      jw.StringProperty("cat",
    430                        MakeStringSpan(ToShortString(message.mCategory)));
    431      if (message.mLabel && message.mLabel[0] != '\0') {
    432        jw.StringProperty("lbl", MakeStringSpan(message.mLabel));
    433      }
    434      if (!message.mValue.is<DDNoValue>()) {
    435        if (message.mValue.is<DDLogObject>()) {
    436          const DDLogObject& ob2 = message.mValue.as<DDLogObject>();
    437          DDLifetime* lifetime2 = mLifetimes.FindLifetime(ob2, message.mIndex);
    438          if (lifetime2) {
    439            jw.IntProperty("val", lifetime2->mTag);
    440          } else {
    441            ToJSON(message.mValue, jw, "val");
    442          }
    443        } else {
    444          ToJSON(message.mValue, jw, "val");
    445        }
    446      }
    447      jw.EndObject();
    448    }
    449    jw.EndArray();
    450    jw.StartObjectProperty("objects");
    451    mLifetimes.Visit(
    452        mediaElement,
    453        [&](const DDLifetime& lifetime) {
    454          jw.StartObjectProperty(nsPrintfCString("%" PRIi32, lifetime.mTag),
    455                                 JSONWriter::SingleLineStyle);
    456          jw.IntProperty("tag", lifetime.mTag);
    457          jw.StringProperty("cls", MakeStringSpan(lifetime.mObject.TypeName()));
    458          jw.StringProperty("ptr",
    459                            nsPrintfCString("%p", lifetime.mObject.Pointer()));
    460          jw.IntProperty("con", lifetime.mConstructionIndex.Value());
    461          jw.DoubleProperty("con_ts",
    462                            ToSeconds(lifetime.mConstructionTimeStamp));
    463          if (lifetime.mDestructionTimeStamp) {
    464            jw.IntProperty("des", lifetime.mDestructionIndex.Value());
    465            jw.DoubleProperty("des_ts",
    466                              ToSeconds(lifetime.mDestructionTimeStamp));
    467          }
    468          if (lifetime.mDerivedObject.Pointer()) {
    469            DDLifetime* derived = mLifetimes.FindLifetime(
    470                lifetime.mDerivedObject, lifetime.mDerivedObjectLinkingIndex);
    471            if (derived) {
    472              jw.IntProperty("drvd", derived->mTag);
    473            }
    474          }
    475          jw.EndObject();
    476        },
    477        // If there were no (new) messages, only give the main HTMLMediaElement
    478        // object (used to identify this log against the correct element.)
    479        log->mMessages.IsEmpty());
    480    jw.EndObject();
    481    jw.End();
    482    DDL_DEBUG("RetrieveMessages(%p) ->\n%s", mediaElement,
    483              json.StringCRef().get());
    484 
    485    // This log exists (new messages or not) -> Resolve this promise.
    486    DDL_INFO("Resolving promise for HTMLMediaElement[%p] with messages %" PRImi
    487             "-%" PRImi,
    488             mediaElement,
    489             log->mMessages.IsEmpty() ? 0 : log->mMessages[0].mIndex.Value(),
    490             log->mMessages.IsEmpty()
    491                 ? 0
    492                 : log->mMessages[log->mMessages.Length() - 1].mIndex.Value());
    493    promiseHolder.Resolve(std::move(json).StringRRef(), __func__);
    494 
    495    // Remove exported messages.
    496    log->mMessages.Clear();
    497 
    498    // Pop this resolved promise, fetch next one.
    499    MutexAutoLock lock(mMutex);
    500    mPendingPromises.RemoveElementAt(0);
    501    if (mPendingPromises.IsEmpty()) {
    502      break;
    503    }
    504    promiseHolder = std::move(mPendingPromises[0].mPromiseHolder);
    505    mediaElement = mPendingPromises[0].mMediaElement;
    506  }
    507 }
    508 
    509 void DDMediaLogs::CleanUpLogs() {
    510  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    511 
    512  DDTimeStamp now = DDNow();
    513 
    514  // Keep up to 30s of unclassified messages (if a message doesn't get
    515  // classified this quickly, it probably never will be.)
    516  static const double sMaxAgeUnclassifiedMessages_s = 30.0;
    517  // Keep "dead" log (video element and dependents were destroyed) for up to
    518  // 2 minutes, in case the user wants to look at it after the facts.
    519  static const double sMaxAgeDeadLog_s = 120.0;
    520  // Keep old messages related to a live video for up to 5 minutes.
    521  static const double sMaxAgeClassifiedMessages_s = 300.0;
    522 
    523  for (size_t logIndexPlus1 = mMediaLogs.Length(); logIndexPlus1 != 0;
    524       --logIndexPlus1) {
    525    DDMediaLog& log = mMediaLogs[logIndexPlus1 - 1];
    526    if (log.mMediaElement) {
    527      // Remove logs for which no lifetime still existed some time ago.
    528      bool used = mLifetimes.VisitBreakable(
    529          log.mMediaElement, [&](const DDLifetime& lifetime) {
    530            // Do we still have a lifetime that existed recently enough?
    531            return !lifetime.mDestructionTimeStamp ||
    532                   (now - lifetime.mDestructionTimeStamp).ToSeconds() <=
    533                       sMaxAgeDeadLog_s;
    534          });
    535      if (!used) {
    536        DDL_INFO("Removed old log for media element %p", log.mMediaElement);
    537        mLifetimes.Visit(log.mMediaElement, [&](const DDLifetime& lifetime) {
    538          DestroyLifetimeLinks(lifetime);
    539        });
    540        mLifetimes.RemoveLifetimesFor(log.mMediaElement);
    541        mMediaLogs.RemoveElementAt(logIndexPlus1 - 1);
    542        continue;
    543      }
    544    }
    545 
    546    // Remove old messages.
    547    size_t old = 0;
    548    const size_t len = log.mMessages.Length();
    549    while (old < len &&
    550           (now - log.mMessages[old].mTimeStamp).ToSeconds() >
    551               (log.mMediaElement ? sMaxAgeClassifiedMessages_s
    552                                  : sMaxAgeUnclassifiedMessages_s)) {
    553      ++old;
    554    }
    555    if (old != 0) {
    556      // We are going to remove `old` messages.
    557      // First, remove associated destroyed lifetimes that are not used after
    558      // these old messages. (We want to keep non-destroyed lifetimes, in
    559      // case they get used later on.)
    560      size_t removedLifetimes = 0;
    561      for (size_t i = 0; i < old; ++i) {
    562        auto RemoveDestroyedUnusedLifetime = [&](DDLifetime* lifetime) {
    563          if (!lifetime->mDestructionTimeStamp) {
    564            // Lifetime is still alive, keep it.
    565            return;
    566          }
    567          bool used = false;
    568          for (size_t after = old; after < len; ++after) {
    569            const DDLogMessage message = log.mMessages[i];
    570            if (!lifetime->IsAliveAt(message.mIndex)) {
    571              // Lifetime is already dead, and not used yet -> kill it.
    572              break;
    573            }
    574            const DDLogObject& ob = message.mObject;
    575            if (lifetime->mObject == ob) {
    576              used = true;
    577              break;
    578            }
    579            if (message.mValue.is<DDLogObject>()) {
    580              if (lifetime->mObject == message.mValue.as<DDLogObject>()) {
    581                used = true;
    582                break;
    583              }
    584            }
    585          }
    586          if (!used) {
    587            DestroyLifetimeLinks(*lifetime);
    588            mLifetimes.RemoveLifetime(lifetime);
    589            ++removedLifetimes;
    590          }
    591        };
    592 
    593        const DDLogMessage message = log.mMessages[i];
    594        const DDLogObject& ob = message.mObject;
    595 
    596        DDLifetime* lifetime1 = mLifetimes.FindLifetime(ob, message.mIndex);
    597        if (lifetime1) {
    598          RemoveDestroyedUnusedLifetime(lifetime1);
    599        }
    600 
    601        if (message.mValue.is<DDLogObject>()) {
    602          DDLifetime* lifetime2 = mLifetimes.FindLifetime(
    603              message.mValue.as<DDLogObject>(), message.mIndex);
    604          if (lifetime2) {
    605            RemoveDestroyedUnusedLifetime(lifetime2);
    606          }
    607        }
    608      }
    609      DDL_INFO("Removed %zu messages (#%" PRImi " %f - #%" PRImi
    610               " %f) and %zu lifetimes from log for media element %p",
    611               old, log.mMessages[0].mIndex.Value(),
    612               ToSeconds(log.mMessages[0].mTimeStamp),
    613               log.mMessages[old - 1].mIndex.Value(),
    614               ToSeconds(log.mMessages[old - 1].mTimeStamp), removedLifetimes,
    615               log.mMediaElement);
    616      log.mMessages.RemoveElementsAt(0, old);
    617    }
    618  }
    619 }
    620 
    621 void DDMediaLogs::ProcessLog() {
    622  MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
    623  ProcessBuffer();
    624  FulfillPromises();
    625  CleanUpLogs();
    626  DDL_INFO("ProcessLog() completed - DDMediaLog size: %zu",
    627           SizeOfIncludingThis(moz_malloc_size_of));
    628 }
    629 
    630 nsresult DDMediaLogs::DispatchProcessLog(const MutexAutoLock& aProofOfLock) {
    631  if (!mThread) {
    632    return NS_ERROR_SERVICE_NOT_AVAILABLE;
    633  }
    634  return mThread->Dispatch(
    635      NS_NewRunnableFunction("ProcessLog", [this] { ProcessLog(); }),
    636      NS_DISPATCH_NORMAL);
    637 }
    638 
    639 nsresult DDMediaLogs::DispatchProcessLog() {
    640  DDL_INFO("DispatchProcessLog() - Yet-unprocessed message buffers: %d",
    641           mMessagesQueue.LiveBuffersStats().mCount);
    642  MutexAutoLock lock(mMutex);
    643  return DispatchProcessLog(lock);
    644 }
    645 
    646 RefPtr<DDMediaLogs::LogMessagesPromise> DDMediaLogs::RetrieveMessages(
    647    const dom::HTMLMediaElement* aMediaElement) {
    648  MozPromiseHolder<LogMessagesPromise> holder;
    649  RefPtr<LogMessagesPromise> promise = holder.Ensure(__func__);
    650  {
    651    MutexAutoLock lock(mMutex);
    652    // If there were unfulfilled promises, we know processing has already
    653    // been requested.
    654    if (mPendingPromises.IsEmpty()) {
    655      // But if we're the first one, start processing.
    656      nsresult rv = DispatchProcessLog(lock);
    657      if (NS_FAILED(rv)) {
    658        holder.Reject(rv, __func__);
    659      }
    660    }
    661    mPendingPromises.AppendElement(
    662        PendingPromise{std::move(holder), aMediaElement});
    663  }
    664  return promise;
    665 }
    666 
    667 }  // namespace mozilla