tor-browser

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

TextTrackManager.cpp (29173B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/TextTrackManager.h"
      8 
      9 #include "mozilla/ClearOnShutdown.h"
     10 #include "mozilla/CycleCollectedJSContext.h"
     11 #include "mozilla/Maybe.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/dom/Event.h"
     14 #include "mozilla/dom/HTMLMediaElement.h"
     15 #include "mozilla/dom/HTMLTrackElement.h"
     16 #include "mozilla/dom/HTMLVideoElement.h"
     17 #include "mozilla/dom/TextTrack.h"
     18 #include "mozilla/dom/TextTrackCue.h"
     19 #include "mozilla/nsVideoFrame.h"
     20 #include "nsComponentManagerUtils.h"
     21 #include "nsGlobalWindowInner.h"
     22 #include "nsIFrame.h"
     23 #include "nsIWebVTTParserWrapper.h"
     24 #include "nsVariant.h"
     25 
     26 mozilla::LazyLogModule gTextTrackLog("WebVTT");
     27 
     28 #define WEBVTT_LOG(msg, ...)              \
     29  MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
     30          ("TextTrackManager=%p, " msg, this, ##__VA_ARGS__))
     31 #define WEBVTT_LOGV(msg, ...)               \
     32  MOZ_LOG(gTextTrackLog, LogLevel::Verbose, \
     33          ("TextTrackManager=%p, " msg, this, ##__VA_ARGS__))
     34 
     35 namespace mozilla::dom {
     36 
     37 NS_IMPL_ISUPPORTS(TextTrackManager::ShutdownObserverProxy, nsIObserver);
     38 
     39 void TextTrackManager::ShutdownObserverProxy::Unregister() {
     40  nsContentUtils::UnregisterShutdownObserver(this);
     41  mManager = nullptr;
     42 }
     43 
     44 CompareTextTracks::CompareTextTracks(HTMLMediaElement* aMediaElement) {
     45  mMediaElement = aMediaElement;
     46 }
     47 
     48 Maybe<uint32_t> CompareTextTracks::TrackChildPosition(
     49    TextTrack* aTextTrack) const {
     50  MOZ_DIAGNOSTIC_ASSERT(aTextTrack);
     51  HTMLTrackElement* trackElement = aTextTrack->GetTrackElement();
     52  if (!trackElement) {
     53    return Nothing();
     54  }
     55  return mMediaElement->ComputeIndexOf(trackElement);
     56 }
     57 
     58 bool CompareTextTracks::Equals(TextTrack* aOne, TextTrack* aTwo) const {
     59  // Two tracks can never be equal. If they have corresponding TrackElements
     60  // they would need to occupy the same tree position (impossible) and in the
     61  // case of tracks coming from AddTextTrack source we put the newest at the
     62  // last position, so they won't be equal as well.
     63  return false;
     64 }
     65 
     66 bool CompareTextTracks::LessThan(TextTrack* aOne, TextTrack* aTwo) const {
     67  // Protect against nullptr TextTrack objects; treat them as
     68  // sorting toward the end.
     69  if (!aOne) {
     70    return false;
     71  }
     72  if (!aTwo) {
     73    return true;
     74  }
     75  TextTrackSource sourceOne = aOne->GetTextTrackSource();
     76  TextTrackSource sourceTwo = aTwo->GetTextTrackSource();
     77  if (sourceOne != sourceTwo) {
     78    return sourceOne == TextTrackSource::Track ||
     79           (sourceOne == TextTrackSource::AddTextTrack &&
     80            sourceTwo == TextTrackSource::MediaResourceSpecific);
     81  }
     82  switch (sourceOne) {
     83    case TextTrackSource::Track: {
     84      Maybe<uint32_t> positionOne = TrackChildPosition(aOne);
     85      Maybe<uint32_t> positionTwo = TrackChildPosition(aTwo);
     86      // If either position one or positiontwo are Nothing then something has
     87      // gone wrong. In this case we should just put them at the back of the
     88      // list.
     89      return positionOne.isSome() && positionTwo.isSome() &&
     90             *positionOne < *positionTwo;
     91    }
     92    case TextTrackSource::AddTextTrack:
     93      // For AddTextTrack sources the tracks will already be in the correct
     94      // relative order in the source array. Assume we're called in iteration
     95      // order and can therefore always report aOne < aTwo to maintain the
     96      // original temporal ordering.
     97      return true;
     98    case TextTrackSource::MediaResourceSpecific:
     99      // No rules for Media Resource Specific tracks yet.
    100      break;
    101  }
    102  return true;
    103 }
    104 
    105 NS_IMPL_CYCLE_COLLECTION(TextTrackManager, mMediaElement, mTextTracks,
    106                         mPendingTextTracks, mNewCues)
    107 
    108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackManager)
    109  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
    110 NS_INTERFACE_MAP_END
    111 
    112 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackManager)
    113 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextTrackManager)
    114 
    115 StaticRefPtr<nsIWebVTTParserWrapper> TextTrackManager::sParserWrapper;
    116 
    117 TextTrackManager::TextTrackManager(HTMLMediaElement* aMediaElement)
    118    : mMediaElement(aMediaElement),
    119      mHasSeeked(false),
    120      mLastTimeMarchesOnCalled(media::TimeUnit::Zero()),
    121      mTimeMarchesOnDispatched(false),
    122      mUpdateCueDisplayDispatched(false),
    123      performedTrackSelection(false),
    124      mShutdown(false) {
    125  nsISupports* parentObject = mMediaElement->OwnerDoc()->GetParentObject();
    126 
    127  NS_ENSURE_TRUE_VOID(parentObject);
    128  WEBVTT_LOG("Create TextTrackManager");
    129  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
    130  mNewCues = new TextTrackCueList(window);
    131  mTextTracks = new TextTrackList(window, this);
    132  mPendingTextTracks = new TextTrackList(window, this);
    133 
    134  if (!sParserWrapper) {
    135    nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
    136        do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID);
    137    MOZ_ASSERT(parserWrapper, "Can't create nsIWebVTTParserWrapper");
    138    sParserWrapper = parserWrapper;
    139    ClearOnShutdown(&sParserWrapper);
    140  }
    141  mShutdownProxy = new ShutdownObserverProxy(this);
    142 }
    143 
    144 TextTrackManager::~TextTrackManager() {
    145  WEBVTT_LOG("~TextTrackManager");
    146  mShutdownProxy->Unregister();
    147 }
    148 
    149 TextTrackList* TextTrackManager::GetTextTracks() const { return mTextTracks; }
    150 
    151 already_AddRefed<TextTrack> TextTrackManager::AddTextTrack(
    152    TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage,
    153    TextTrackMode aMode, TextTrackReadyState aReadyState,
    154    TextTrackSource aTextTrackSource) {
    155  if (!mMediaElement || !mTextTracks) {
    156    return nullptr;
    157  }
    158  RefPtr<TextTrack> track = mTextTracks->AddTextTrack(
    159      aKind, aLabel, aLanguage, aMode, aReadyState, aTextTrackSource,
    160      CompareTextTracks(mMediaElement));
    161  WEBVTT_LOG("AddTextTrack %p kind %" PRIu32 " Label %s Language %s",
    162             track.get(), static_cast<uint32_t>(aKind),
    163             NS_ConvertUTF16toUTF8(aLabel).get(),
    164             NS_ConvertUTF16toUTF8(aLanguage).get());
    165  AddCues(track);
    166 
    167  if (aTextTrackSource == TextTrackSource::Track) {
    168    RefPtr<nsIRunnable> task = NewRunnableMethod(
    169        "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", this,
    170        &TextTrackManager::HonorUserPreferencesForTrackSelection);
    171    NS_DispatchToMainThread(task.forget());
    172  }
    173 
    174  return track.forget();
    175 }
    176 
    177 void TextTrackManager::AddTextTrack(TextTrack* aTextTrack) {
    178  if (!mMediaElement || !mTextTracks) {
    179    return;
    180  }
    181  WEBVTT_LOG("AddTextTrack TextTrack %p", aTextTrack);
    182  mTextTracks->AddTextTrack(aTextTrack, CompareTextTracks(mMediaElement));
    183  AddCues(aTextTrack);
    184 
    185  if (aTextTrack->GetTextTrackSource() == TextTrackSource::Track) {
    186    RefPtr<nsIRunnable> task = NewRunnableMethod(
    187        "dom::TextTrackManager::HonorUserPreferencesForTrackSelection", this,
    188        &TextTrackManager::HonorUserPreferencesForTrackSelection);
    189    NS_DispatchToMainThread(task.forget());
    190  }
    191 }
    192 
    193 void TextTrackManager::AddCues(TextTrack* aTextTrack) {
    194  if (!mNewCues) {
    195    WEBVTT_LOG("AddCues mNewCues is null");
    196    return;
    197  }
    198 
    199  TextTrackCueList* cueList = aTextTrack->GetCues();
    200  if (cueList) {
    201    bool dummy;
    202    WEBVTT_LOGV("AddCues, CuesNum=%d", cueList->Length());
    203    for (uint32_t i = 0; i < cueList->Length(); ++i) {
    204      mNewCues->AddCue(*cueList->IndexedGetter(i, dummy));
    205    }
    206    MaybeRunTimeMarchesOn();
    207  }
    208 }
    209 
    210 void TextTrackManager::RemoveTextTrack(TextTrack* aTextTrack,
    211                                       bool aPendingListOnly) {
    212  if (!mPendingTextTracks || !mTextTracks) {
    213    return;
    214  }
    215 
    216  WEBVTT_LOG("RemoveTextTrack TextTrack %p", aTextTrack);
    217  mPendingTextTracks->RemoveTextTrack(aTextTrack);
    218  if (aPendingListOnly) {
    219    return;
    220  }
    221 
    222  mTextTracks->RemoveTextTrack(aTextTrack);
    223  // Remove the cues in mNewCues belong to aTextTrack.
    224  TextTrackCueList* removeCueList = aTextTrack->GetCues();
    225  if (removeCueList) {
    226    WEBVTT_LOGV("RemoveTextTrack removeCuesNum=%d", removeCueList->Length());
    227    for (uint32_t i = 0; i < removeCueList->Length(); ++i) {
    228      mNewCues->RemoveCue(*((*removeCueList)[i]));
    229    }
    230    MaybeRunTimeMarchesOn();
    231  }
    232 }
    233 
    234 void TextTrackManager::DidSeek() {
    235  WEBVTT_LOG("DidSeek");
    236  mHasSeeked = true;
    237 }
    238 
    239 void TextTrackManager::UpdateCueDisplay() {
    240  WEBVTT_LOG("UpdateCueDisplay");
    241  mUpdateCueDisplayDispatched = false;
    242 
    243  if (!mMediaElement || !mTextTracks || IsShutdown()) {
    244    WEBVTT_LOG("Abort UpdateCueDisplay.");
    245    return;
    246  }
    247 
    248  nsIFrame* frame = mMediaElement->GetPrimaryFrame();
    249  nsVideoFrame* videoFrame = do_QueryFrame(frame);
    250  if (!videoFrame) {
    251    WEBVTT_LOG("Abort UpdateCueDisplay, because of no video frame.");
    252    return;
    253  }
    254 
    255  nsCOMPtr<nsIContent> overlay = videoFrame->GetCaptionOverlay();
    256  if (!overlay) {
    257    WEBVTT_LOG("Abort UpdateCueDisplay, because of no overlay.");
    258    return;
    259  }
    260 
    261  RefPtr<nsPIDOMWindowInner> window =
    262      mMediaElement->OwnerDoc()->GetInnerWindow();
    263  if (!window) {
    264    WEBVTT_LOG("Abort UpdateCueDisplay, because of no window.");
    265  }
    266 
    267  nsTArray<RefPtr<TextTrackCue>> showingCues;
    268  mTextTracks->GetShowingCues(showingCues);
    269 
    270  WEBVTT_LOG("UpdateCueDisplay, processCues, showingCuesNum=%zu",
    271             showingCues.Length());
    272  RefPtr<nsVariantCC> jsCues = new nsVariantCC();
    273  jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE, &NS_GET_IID(EventTarget),
    274                     showingCues.Length(),
    275                     static_cast<void*>(showingCues.Elements()));
    276  nsCOMPtr<nsIContent> controls = videoFrame->GetVideoControls();
    277 
    278  nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
    279      "TextTrackManager::UpdateCueDisplay",
    280      [window, jsCues, overlay, controls]() {
    281        if (sParserWrapper) {
    282          sParserWrapper->ProcessCues(window, jsCues, overlay, controls);
    283        }
    284      }));
    285 }
    286 
    287 void TextTrackManager::NotifyCueAdded(TextTrackCue& aCue) {
    288  WEBVTT_LOG("NotifyCueAdded, cue=%p", &aCue);
    289  if (mNewCues) {
    290    mNewCues->AddCue(aCue);
    291  }
    292  MaybeRunTimeMarchesOn();
    293 }
    294 
    295 void TextTrackManager::NotifyCueRemoved(TextTrackCue& aCue) {
    296  WEBVTT_LOG("NotifyCueRemoved, cue=%p", &aCue);
    297  if (mNewCues) {
    298    mNewCues->RemoveCue(aCue);
    299  }
    300  MaybeRunTimeMarchesOn();
    301  DispatchUpdateCueDisplay();
    302 }
    303 
    304 void TextTrackManager::PopulatePendingList() {
    305  if (!mTextTracks || !mPendingTextTracks || !mMediaElement) {
    306    return;
    307  }
    308  uint32_t len = mTextTracks->Length();
    309  bool dummy;
    310  for (uint32_t index = 0; index < len; ++index) {
    311    TextTrack* ttrack = mTextTracks->IndexedGetter(index, dummy);
    312    if (ttrack && ttrack->Mode() != TextTrackMode::Disabled &&
    313        ttrack->ReadyState() == TextTrackReadyState::Loading) {
    314      mPendingTextTracks->AddTextTrack(ttrack,
    315                                       CompareTextTracks(mMediaElement));
    316    }
    317  }
    318 }
    319 
    320 void TextTrackManager::AddListeners() {
    321  if (mMediaElement) {
    322    mMediaElement->AddEventListener(u"resizecaption"_ns, this, false, false);
    323    mMediaElement->AddEventListener(u"resizevideocontrols"_ns, this, false,
    324                                    false);
    325    mMediaElement->AddEventListener(u"seeked"_ns, this, false, false);
    326    mMediaElement->AddEventListener(u"controlbarchange"_ns, this, false, true);
    327  }
    328 }
    329 
    330 void TextTrackManager::HonorUserPreferencesForTrackSelection() {
    331  if (performedTrackSelection || !mTextTracks) {
    332    return;
    333  }
    334  WEBVTT_LOG("HonorUserPreferencesForTrackSelection");
    335  TextTrackKind ttKinds[] = {TextTrackKind::Captions, TextTrackKind::Subtitles};
    336 
    337  // Steps 1 - 3: Perform automatic track selection for different TextTrack
    338  // Kinds.
    339  PerformTrackSelection(ttKinds, std::size(ttKinds));
    340  PerformTrackSelection(TextTrackKind::Descriptions);
    341  PerformTrackSelection(TextTrackKind::Chapters);
    342 
    343  // Step 4: Set all TextTracks with a kind of metadata that are disabled
    344  // to hidden.
    345  for (uint32_t i = 0; i < mTextTracks->Length(); i++) {
    346    TextTrack* track = (*mTextTracks)[i];
    347    if (track->Kind() == TextTrackKind::Metadata && TrackIsDefault(track) &&
    348        track->Mode() == TextTrackMode::Disabled) {
    349      track->SetMode(TextTrackMode::Hidden);
    350    }
    351  }
    352 
    353  performedTrackSelection = true;
    354 }
    355 
    356 bool TextTrackManager::TrackIsDefault(TextTrack* aTextTrack) {
    357  HTMLTrackElement* trackElement = aTextTrack->GetTrackElement();
    358  if (!trackElement) {
    359    return false;
    360  }
    361  return trackElement->Default();
    362 }
    363 
    364 void TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKind) {
    365  TextTrackKind ttKinds[] = {aTextTrackKind};
    366  PerformTrackSelection(ttKinds, std::size(ttKinds));
    367 }
    368 
    369 void TextTrackManager::PerformTrackSelection(TextTrackKind aTextTrackKinds[],
    370                                             uint32_t size) {
    371  nsTArray<TextTrack*> candidates;
    372  GetTextTracksOfKinds(aTextTrackKinds, size, candidates);
    373 
    374  // Step 3: If any TextTracks in candidates are showing then abort these steps.
    375  for (uint32_t i = 0; i < candidates.Length(); i++) {
    376    if (candidates[i]->Mode() == TextTrackMode::Showing) {
    377      WEBVTT_LOGV("PerformTrackSelection Showing return kind %d",
    378                  static_cast<int>(candidates[i]->Kind()));
    379      return;
    380    }
    381  }
    382 
    383  // Step 4: Honor user preferences for track selection, otherwise, set the
    384  // first TextTrack in candidates with a default attribute to showing.
    385  // TODO: Bug 981691 - Honor user preferences for text track selection.
    386  for (uint32_t i = 0; i < candidates.Length(); i++) {
    387    if (TrackIsDefault(candidates[i]) &&
    388        candidates[i]->Mode() == TextTrackMode::Disabled) {
    389      candidates[i]->SetMode(TextTrackMode::Showing);
    390      WEBVTT_LOGV("PerformTrackSelection set Showing kind %d",
    391                  static_cast<int>(candidates[i]->Kind()));
    392      return;
    393    }
    394  }
    395 }
    396 
    397 void TextTrackManager::GetTextTracksOfKinds(TextTrackKind aTextTrackKinds[],
    398                                            uint32_t size,
    399                                            nsTArray<TextTrack*>& aTextTracks) {
    400  for (uint32_t i = 0; i < size; i++) {
    401    GetTextTracksOfKind(aTextTrackKinds[i], aTextTracks);
    402  }
    403 }
    404 
    405 void TextTrackManager::GetTextTracksOfKind(TextTrackKind aTextTrackKind,
    406                                           nsTArray<TextTrack*>& aTextTracks) {
    407  if (!mTextTracks) {
    408    return;
    409  }
    410  for (uint32_t i = 0; i < mTextTracks->Length(); i++) {
    411    TextTrack* textTrack = (*mTextTracks)[i];
    412    if (textTrack->Kind() == aTextTrackKind) {
    413      aTextTracks.AppendElement(textTrack);
    414    }
    415  }
    416 }
    417 
    418 NS_IMETHODIMP
    419 TextTrackManager::HandleEvent(Event* aEvent) {
    420  if (!mTextTracks) {
    421    return NS_OK;
    422  }
    423 
    424  nsAutoString type;
    425  aEvent->GetType(type);
    426  WEBVTT_LOG("Handle event %s", NS_ConvertUTF16toUTF8(type).get());
    427 
    428  const bool setDirty = type.EqualsLiteral("seeked") ||
    429                        type.EqualsLiteral("resizecaption") ||
    430                        type.EqualsLiteral("resizevideocontrols");
    431  const bool updateDisplay = type.EqualsLiteral("controlbarchange") ||
    432                             type.EqualsLiteral("resizecaption");
    433 
    434  if (setDirty) {
    435    for (uint32_t i = 0; i < mTextTracks->Length(); i++) {
    436      ((*mTextTracks)[i])->SetCuesDirty();
    437    }
    438  }
    439  if (updateDisplay) {
    440    UpdateCueDisplay();
    441  }
    442 
    443  return NS_OK;
    444 }
    445 
    446 class SimpleTextTrackEvent : public Runnable {
    447 public:
    448  friend class CompareSimpleTextTrackEvents;
    449  SimpleTextTrackEvent(const nsAString& aEventName, double aTime,
    450                       TextTrack* aTrack, TextTrackCue* aCue)
    451      : Runnable("dom::SimpleTextTrackEvent"),
    452        mName(aEventName),
    453        mTime(aTime),
    454        mTrack(aTrack),
    455        mCue(aCue) {}
    456 
    457  NS_IMETHOD Run() override {
    458    WEBVTT_LOGV("SimpleTextTrackEvent cue %p mName %s mTime %lf", mCue.get(),
    459                NS_ConvertUTF16toUTF8(mName).get(), mTime);
    460    mCue->DispatchTrustedEvent(mName);
    461    return NS_OK;
    462  }
    463 
    464  void Dispatch() {
    465    if (nsCOMPtr<nsIGlobalObject> global = mCue->GetOwnerGlobal()) {
    466      global->Dispatch(do_AddRef(this));
    467    } else {
    468      NS_DispatchToMainThread(do_AddRef(this));
    469    }
    470  }
    471 
    472 private:
    473  nsString mName;
    474  double mTime;
    475  TextTrack* mTrack;
    476  RefPtr<TextTrackCue> mCue;
    477 };
    478 
    479 class CompareSimpleTextTrackEvents {
    480 private:
    481  Maybe<uint32_t> TrackChildPosition(SimpleTextTrackEvent* aEvent) const {
    482    if (aEvent->mTrack) {
    483      HTMLTrackElement* trackElement = aEvent->mTrack->GetTrackElement();
    484      if (trackElement) {
    485        return mMediaElement->ComputeIndexOf(trackElement);
    486      }
    487    }
    488    return Nothing();
    489  }
    490  HTMLMediaElement* mMediaElement;
    491 
    492 public:
    493  explicit CompareSimpleTextTrackEvents(HTMLMediaElement* aMediaElement) {
    494    mMediaElement = aMediaElement;
    495  }
    496 
    497  bool Equals(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const {
    498    return false;
    499  }
    500 
    501  bool LessThan(SimpleTextTrackEvent* aOne, SimpleTextTrackEvent* aTwo) const {
    502    // TimeMarchesOn step 13.1.
    503    if (aOne->mTime < aTwo->mTime) {
    504      return true;
    505    }
    506    if (aOne->mTime > aTwo->mTime) {
    507      return false;
    508    }
    509 
    510    // TimeMarchesOn step 13.2 text track cue order.
    511    // TextTrack position in TextTrackList
    512    TextTrack* t1 = aOne->mTrack;
    513    TextTrack* t2 = aTwo->mTrack;
    514    MOZ_ASSERT(t1, "CompareSimpleTextTrackEvents t1 is null");
    515    MOZ_ASSERT(t2, "CompareSimpleTextTrackEvents t2 is null");
    516    if (t1 != t2) {
    517      TextTrackList* tList = t1->GetTextTrackList();
    518      MOZ_ASSERT(tList, "CompareSimpleTextTrackEvents tList is null");
    519      nsTArray<RefPtr<TextTrack>>& textTracks = tList->GetTextTrackArray();
    520      auto index1 = textTracks.IndexOf(t1);
    521      auto index2 = textTracks.IndexOf(t2);
    522      if (index1 < index2) {
    523        return true;
    524      }
    525      if (index1 > index2) {
    526        return false;
    527      }
    528    }
    529 
    530    MOZ_ASSERT(t1 == t2, "CompareSimpleTextTrackEvents t1 != t2");
    531    // c1 and c2 are both belongs to t1.
    532    TextTrackCue* c1 = aOne->mCue;
    533    TextTrackCue* c2 = aTwo->mCue;
    534    if (c1 != c2) {
    535      if (c1->StartTime() < c2->StartTime()) {
    536        return true;
    537      }
    538      if (c1->StartTime() > c2->StartTime()) {
    539        return false;
    540      }
    541      if (c1->EndTime() < c2->EndTime()) {
    542        return true;
    543      }
    544      if (c1->EndTime() > c2->EndTime()) {
    545        return false;
    546      }
    547 
    548      TextTrackCueList* cueList = t1->GetCues();
    549      MOZ_ASSERT(cueList);
    550      nsTArray<RefPtr<TextTrackCue>>& cues = cueList->GetCuesArray();
    551      auto index1 = cues.IndexOf(c1);
    552      auto index2 = cues.IndexOf(c2);
    553      if (index1 < index2) {
    554        return true;
    555      }
    556      if (index1 > index2) {
    557        return false;
    558      }
    559    }
    560 
    561    // TimeMarchesOn step 13.3.
    562    if (aOne->mName.EqualsLiteral("enter") ||
    563        aTwo->mName.EqualsLiteral("exit")) {
    564      return true;
    565    }
    566    return false;
    567  }
    568 };
    569 
    570 class TextTrackListInternal {
    571 public:
    572  void AddTextTrack(TextTrack* aTextTrack,
    573                    const CompareTextTracks& aCompareTT) {
    574    if (!mTextTracks.Contains(aTextTrack)) {
    575      mTextTracks.InsertElementSorted(aTextTrack, aCompareTT);
    576    }
    577  }
    578  uint32_t Length() const { return mTextTracks.Length(); }
    579  TextTrack* operator[](uint32_t aIndex) {
    580    return mTextTracks.SafeElementAt(aIndex, nullptr);
    581  }
    582 
    583 private:
    584  nsTArray<RefPtr<TextTrack>> mTextTracks;
    585 };
    586 
    587 void TextTrackManager::DispatchUpdateCueDisplay() {
    588  if (!mUpdateCueDisplayDispatched && !IsShutdown()) {
    589    WEBVTT_LOG("DispatchUpdateCueDisplay");
    590    if (nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow()) {
    591      nsGlobalWindowInner::Cast(win)->Dispatch(
    592          NewRunnableMethod("dom::TextTrackManager::UpdateCueDisplay", this,
    593                            &TextTrackManager::UpdateCueDisplay));
    594      mUpdateCueDisplayDispatched = true;
    595    }
    596  }
    597 }
    598 
    599 void TextTrackManager::DispatchTimeMarchesOn() {
    600  // Run the algorithm if no previous instance is still running, otherwise
    601  // enqueue the current playback position and whether only that changed
    602  // through its usual monotonic increase during normal playback; current
    603  // executing call upon completion will check queue for further 'work'.
    604  if (!mTimeMarchesOnDispatched && !IsShutdown()) {
    605    WEBVTT_LOG("DispatchTimeMarchesOn");
    606    if (nsPIDOMWindowInner* win = mMediaElement->OwnerDoc()->GetInnerWindow()) {
    607      nsGlobalWindowInner::Cast(win)->Dispatch(
    608          NewRunnableMethod("dom::TextTrackManager::TimeMarchesOn", this,
    609                            &TextTrackManager::TimeMarchesOn));
    610      mTimeMarchesOnDispatched = true;
    611    }
    612  }
    613 }
    614 
    615 // https://html.spec.whatwg.org/multipage/embedded-content.html#time-marches-on
    616 void TextTrackManager::TimeMarchesOn() {
    617  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
    618  mTimeMarchesOnDispatched = false;
    619 
    620  CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
    621  if (context && context->IsInStableOrMetaStableState()) {
    622    // FireTimeUpdate can be called while at stable state following a
    623    // current position change which triggered a state watcher in MediaDecoder
    624    // (see bug 1443429).
    625    // TimeMarchesOn() will modify JS attributes which is forbidden while in
    626    // stable state. So we dispatch a task to perform such operation later
    627    // instead.
    628    DispatchTimeMarchesOn();
    629    return;
    630  }
    631  WEBVTT_LOG("TimeMarchesOn");
    632 
    633  // Early return if we don't have any TextTracks or shutting down.
    634  if (!mTextTracks || mTextTracks->Length() == 0 || IsShutdown() ||
    635      !mMediaElement) {
    636    return;
    637  }
    638 
    639  if (mMediaElement->ReadyState() == HTMLMediaElement_Binding::HAVE_NOTHING) {
    640    WEBVTT_LOG(
    641        "TimeMarchesOn return because media doesn't contain any data yet");
    642    return;
    643  }
    644 
    645  if (mMediaElement->Seeking()) {
    646    WEBVTT_LOG("TimeMarchesOn return during seeking");
    647    return;
    648  }
    649 
    650  // Step 1, 2, 3, 4
    651  using CueBuckets = TextTrack::CueBuckets;
    652  CueBuckets currentCues;
    653  CueBuckets otherCues;
    654  CueBuckets missedCues;
    655  auto currentPlaybackTime =
    656      media::TimeUnit::FromSeconds(mMediaElement->CurrentTime());
    657  bool hasNormalPlayback = !mHasSeeked;
    658  mHasSeeked = false;
    659  WEBVTT_LOG(
    660      "TimeMarchesOn mLastTimeMarchesOnCalled %lf currentPlaybackTime %lf "
    661      "hasNormalPlayback %d",
    662      mLastTimeMarchesOnCalled.ToSeconds(), currentPlaybackTime.ToSeconds(),
    663      hasNormalPlayback);
    664 
    665  // The reason we collect other cues is (1) to change active cues to inactive,
    666  // (2) find missing cues, so we actually no need to process all cues. We just
    667  // need to handle cues which are in the time interval [lastTime:currentTime]
    668  // or [currentTime:lastTime] (seeking forward). That can help us to reduce the
    669  // size of other cues, which can improve execution time.
    670  auto start = std::min(mLastTimeMarchesOnCalled, currentPlaybackTime);
    671  auto end = std::max(mLastTimeMarchesOnCalled, currentPlaybackTime);
    672  media::TimeInterval interval(start, end);
    673  WEBVTT_LOGV("TimeMarchesOn Time interval [%f:%f]", start.ToSeconds(),
    674              end.ToSeconds());
    675  for (uint32_t idx = 0; idx < mTextTracks->Length(); ++idx) {
    676    TextTrack* track = (*mTextTracks)[idx];
    677    if (track) {
    678      track->GetOverlappingCurrentOtherAndMissCues(
    679          &currentCues, &otherCues, &missedCues, interval,
    680          hasNormalPlayback ? Some(mLastTimeMarchesOnCalled.ToSeconds())
    681                            : Nothing());
    682    }
    683  }
    684 
    685  WEBVTT_LOGV("TimeMarchesOn currentCues %zu", currentCues.AllCues().Length());
    686  WEBVTT_LOGV("TimeMarchesOn otherCues %zu", otherCues.AllCues().Length());
    687  WEBVTT_LOGV("TimeMarchesOn missedCues %zu", missedCues.AllCues().Length());
    688  // Step 5. Empty now.
    689  // TODO: Step 6: fire timeupdate?
    690 
    691  // Step 7. Abort steps if condition 1, 2, 3 are satisfied.
    692  // 1. All of the cues in current cues have their active flag set.
    693  // 2. None of the cues in other cues have their active flag set.
    694  // 3. Missed cues is empty.
    695  const bool hasOnlyActiveCurrentCues = currentCues.InactiveCues().IsEmpty();
    696  const bool hasNoActiveOtherCues = otherCues.ActiveCues().IsEmpty();
    697  const bool hasNoMissedCues = missedCues.AllCues().IsEmpty();
    698  if (hasOnlyActiveCurrentCues && hasNoActiveOtherCues && hasNoMissedCues) {
    699    mLastTimeMarchesOnCalled = currentPlaybackTime;
    700    WEBVTT_LOG("TimeMarchesOn step 7 return, mLastTimeMarchesOnCalled %lf",
    701               mLastTimeMarchesOnCalled.ToSeconds());
    702    return;
    703  }
    704 
    705  // Step 8. Respect PauseOnExit flag if not seek.
    706  if (hasNormalPlayback) {
    707    if (otherCues.HasPauseOnExit(TextTrack::CueActivityState::Active) ||
    708        missedCues.HasPauseOnExit(TextTrack::CueActivityState::All)) {
    709      WEBVTT_LOG("TimeMarchesOn pause the MediaElement");
    710      mMediaElement->Pause();
    711    }
    712  }
    713 
    714  // Step 15.
    715  // Sort text tracks in the same order as the text tracks appear
    716  // in the media element's list of text tracks, and remove
    717  // duplicates.
    718  TextTrackListInternal affectedTracks;
    719  // Step 13, 14.
    720  nsTArray<RefPtr<SimpleTextTrackEvent>> eventList;
    721  // Step 9, 10.
    722  // For each text track cue in missed cues, prepare an event named
    723  // enter for the TextTrackCue object with the cue start time.
    724  for (const auto& cue : missedCues.AllCues()) {
    725    WEBVTT_LOG("Prepare 'enter' event for cue %p [%f, %f] in missing cues",
    726               cue.get(), cue->StartTime(), cue->EndTime());
    727    SimpleTextTrackEvent* event = new SimpleTextTrackEvent(
    728        u"enter"_ns, cue->StartTime(), cue->GetTrack(), cue);
    729    eventList.InsertElementSorted(event,
    730                                  CompareSimpleTextTrackEvents(mMediaElement));
    731    affectedTracks.AddTextTrack(cue->GetTrack(),
    732                                CompareTextTracks(mMediaElement));
    733  }
    734 
    735  // Step 11, 17.
    736  nsTArray<RefPtr<TextTrackCue>> cuesShouldDispatchExit;
    737  for (const auto& cue : otherCues.AllCues()) {
    738    if (cue->GetActive() || missedCues.AllCues().Contains(cue)) {
    739      double time =
    740          cue->StartTime() > cue->EndTime() ? cue->StartTime() : cue->EndTime();
    741      WEBVTT_LOG("Prepare 'exit' event for cue %p [%f, %f] in other cues",
    742                 cue.get(), cue->StartTime(), cue->EndTime());
    743      SimpleTextTrackEvent* event =
    744          new SimpleTextTrackEvent(u"exit"_ns, time, cue->GetTrack(), cue);
    745      eventList.InsertElementSorted(
    746          event, CompareSimpleTextTrackEvents(mMediaElement));
    747      affectedTracks.AddTextTrack(cue->GetTrack(),
    748                                  CompareTextTracks(mMediaElement));
    749    }
    750    cue->SetActive(false);
    751  }
    752 
    753  // Step 12, 17.
    754  for (const auto& cue : currentCues.InactiveCues()) {
    755    WEBVTT_LOG("Prepare 'enter' event for cue %p [%f, %f] in current cues",
    756               cue.get(), cue->StartTime(), cue->EndTime());
    757    SimpleTextTrackEvent* event = new SimpleTextTrackEvent(
    758        u"enter"_ns, cue->StartTime(), cue->GetTrack(), cue);
    759    eventList.InsertElementSorted(event,
    760                                  CompareSimpleTextTrackEvents(mMediaElement));
    761    affectedTracks.AddTextTrack(cue->GetTrack(),
    762                                CompareTextTracks(mMediaElement));
    763    cue->SetActive(true);
    764  }
    765 
    766  // Fire the eventList
    767  for (uint32_t i = 0; i < eventList.Length(); ++i) {
    768    eventList[i]->Dispatch();
    769  }
    770 
    771  // Step 16.
    772  for (uint32_t i = 0; i < affectedTracks.Length(); ++i) {
    773    TextTrack* ttrack = affectedTracks[i];
    774    if (ttrack) {
    775      ttrack->DispatchAsyncTrustedEvent(u"cuechange"_ns);
    776      HTMLTrackElement* trackElement = ttrack->GetTrackElement();
    777      if (trackElement) {
    778        trackElement->DispatchTrackRunnable(u"cuechange"_ns);
    779      }
    780    }
    781  }
    782 
    783  mLastTimeMarchesOnCalled = currentPlaybackTime;
    784 
    785  // Step 18.
    786  UpdateCueDisplay();
    787 }
    788 
    789 void TextTrackManager::NotifyCueUpdated(TextTrackCue* aCue) {
    790  // TODO: Add/Reorder the cue to mNewCues if we have some optimization?
    791  WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
    792  MaybeRunTimeMarchesOn();
    793  // For the case "Texttrack.mode = hidden/showing", if the mode
    794  // changing between showing and hidden, TimeMarchesOn
    795  // doesn't render the cue. Call DispatchUpdateCueDisplay() explicitly.
    796  DispatchUpdateCueDisplay();
    797 }
    798 
    799 void TextTrackManager::NotifyReset() {
    800  // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
    801  // This will unset all cues' active flag and update the cue display.
    802  WEBVTT_LOG("NotifyReset");
    803  mLastTimeMarchesOnCalled = media::TimeUnit::Zero();
    804  for (uint32_t idx = 0; idx < mTextTracks->Length(); ++idx) {
    805    (*mTextTracks)[idx]->SetCuesInactive();
    806  }
    807  UpdateCueDisplay();
    808 }
    809 
    810 bool TextTrackManager::IsLoaded() {
    811  return mTextTracks ? mTextTracks->AreTextTracksLoaded() : true;
    812 }
    813 
    814 bool TextTrackManager::IsShutdown() const {
    815  return (mShutdown || !sParserWrapper);
    816 }
    817 
    818 void TextTrackManager::MaybeRunTimeMarchesOn() {
    819  MOZ_ASSERT(mMediaElement);
    820  // According to spec, we should check media element's show poster flag before
    821  // running `TimeMarchesOn` in following situations, (1) add cue (2) remove cue
    822  // (3) cue's start time changes (4) cues's end time changes
    823  // https://html.spec.whatwg.org/multipage/media.html#playing-the-media-resource:time-marches-on
    824  // https://html.spec.whatwg.org/multipage/media.html#text-track-api:time-marches-on
    825  if (mMediaElement->GetShowPosterFlag()) {
    826    return;
    827  }
    828  TimeMarchesOn();
    829 }
    830 
    831 }  // namespace mozilla::dom
    832 
    833 #undef WEBVTT_LOG
    834 #undef WEBVTT_LOGV