tor-browser

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

TextTrack.cpp (12805B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 et tw=78: */
      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 "mozilla/dom/TextTrack.h"
      8 
      9 #include "mozilla/AsyncEventDispatcher.h"
     10 #include "mozilla/BinarySearch.h"
     11 #include "mozilla/dom/HTMLMediaElement.h"
     12 #include "mozilla/dom/HTMLTrackElement.h"
     13 #include "mozilla/dom/TextTrackBinding.h"
     14 #include "mozilla/dom/TextTrackCue.h"
     15 #include "mozilla/dom/TextTrackCueList.h"
     16 #include "mozilla/dom/TextTrackList.h"
     17 #include "mozilla/dom/TextTrackRegion.h"
     18 #include "nsGlobalWindowInner.h"
     19 
     20 extern mozilla::LazyLogModule gTextTrackLog;
     21 
     22 #define WEBVTT_LOG(msg, ...)              \
     23  MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
     24          ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
     25 #define WEBVTT_LOGV(msg, ...)               \
     26  MOZ_LOG(gTextTrackLog, LogLevel::Verbose, \
     27          ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
     28 
     29 namespace mozilla::dom {
     30 
     31 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack, DOMEventTargetHelper, mCueList,
     32                                   mActiveCueList, mTextTrackList,
     33                                   mTrackElement)
     34 
     35 NS_IMPL_ADDREF_INHERITED(TextTrack, DOMEventTargetHelper)
     36 NS_IMPL_RELEASE_INHERITED(TextTrack, DOMEventTargetHelper)
     37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack)
     38 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     39 
     40 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow, TextTrackKind aKind,
     41                     const nsAString& aLabel, const nsAString& aLanguage,
     42                     TextTrackMode aMode, TextTrackReadyState aReadyState,
     43                     TextTrackSource aTextTrackSource)
     44    : DOMEventTargetHelper(aOwnerWindow),
     45      mKind(aKind),
     46      mLabel(aLabel),
     47      mLanguage(aLanguage),
     48      mMode(aMode),
     49      mReadyState(aReadyState),
     50      mTextTrackSource(aTextTrackSource) {
     51  SetDefaultSettings();
     52 }
     53 
     54 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow,
     55                     TextTrackList* aTextTrackList, TextTrackKind aKind,
     56                     const nsAString& aLabel, const nsAString& aLanguage,
     57                     TextTrackMode aMode, TextTrackReadyState aReadyState,
     58                     TextTrackSource aTextTrackSource)
     59    : DOMEventTargetHelper(aOwnerWindow),
     60      mTextTrackList(aTextTrackList),
     61      mKind(aKind),
     62      mLabel(aLabel),
     63      mLanguage(aLanguage),
     64      mMode(aMode),
     65      mReadyState(aReadyState),
     66      mTextTrackSource(aTextTrackSource) {
     67  SetDefaultSettings();
     68 }
     69 
     70 TextTrack::~TextTrack() = default;
     71 
     72 void TextTrack::SetDefaultSettings() {
     73  nsPIDOMWindowInner* ownerWindow = GetOwnerWindow();
     74  mCueList = new TextTrackCueList(ownerWindow);
     75  mActiveCueList = new TextTrackCueList(ownerWindow);
     76  mCuePos = 0;
     77  mDirty = false;
     78 }
     79 
     80 JSObject* TextTrack::WrapObject(JSContext* aCx,
     81                                JS::Handle<JSObject*> aGivenProto) {
     82  return TextTrack_Binding::Wrap(aCx, this, aGivenProto);
     83 }
     84 
     85 void TextTrack::SetMode(TextTrackMode aValue) {
     86  if (mMode == aValue) {
     87    return;
     88  }
     89  WEBVTT_LOG("Set mode=%s for track kind %s", GetEnumString(aValue).get(),
     90             GetEnumString(mKind).get());
     91  mMode = aValue;
     92 
     93  HTMLMediaElement* mediaElement = GetMediaElement();
     94  if (aValue == TextTrackMode::Disabled) {
     95    for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
     96      mediaElement->NotifyCueRemoved(*(*mCueList)[i]);
     97    }
     98    SetCuesInactive();
     99  } else {
    100    for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
    101      mediaElement->NotifyCueAdded(*(*mCueList)[i]);
    102    }
    103  }
    104  if (mediaElement) {
    105    mediaElement->NotifyTextTrackModeChanged();
    106  }
    107  // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:start-the-track-processing-model
    108  // Run the `start-the-track-processing-model` to track's corresponding track
    109  // element whenever track's mode changes.
    110  if (mTrackElement) {
    111    mTrackElement->MaybeDispatchLoadResource();
    112  }
    113  // Ensure the TimeMarchesOn is called in case that the mCueList
    114  // is empty.
    115  NotifyCueUpdated(nullptr);
    116 }
    117 
    118 void TextTrack::GetId(nsAString& aId) const {
    119  // If the track has a track element then its id should be the same as the
    120  // track element's id.
    121  if (mTrackElement) {
    122    mTrackElement->GetAttr(nsGkAtoms::id, aId);
    123  }
    124 }
    125 
    126 void TextTrack::AddCue(TextTrackCue& aCue) {
    127  WEBVTT_LOG("AddCue %p [%f:%f]", &aCue, aCue.StartTime(), aCue.EndTime());
    128  TextTrack* oldTextTrack = aCue.GetTrack();
    129  if (oldTextTrack) {
    130    ErrorResult dummy;
    131    oldTextTrack->RemoveCue(aCue, dummy);
    132  }
    133  mCueList->AddCue(aCue);
    134  aCue.SetTrack(this);
    135  HTMLMediaElement* mediaElement = GetMediaElement();
    136  if (mediaElement && (mMode != TextTrackMode::Disabled)) {
    137    mediaElement->NotifyCueAdded(aCue);
    138  }
    139 }
    140 
    141 void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) {
    142  WEBVTT_LOG("RemoveCue %p", &aCue);
    143  // Bug1304948, check the aCue belongs to the TextTrack.
    144  mCueList->RemoveCue(aCue, aRv);
    145  if (aRv.Failed()) {
    146    return;
    147  }
    148  aCue.SetActive(false);
    149  aCue.SetTrack(nullptr);
    150  HTMLMediaElement* mediaElement = GetMediaElement();
    151  if (mediaElement) {
    152    mediaElement->NotifyCueRemoved(aCue);
    153  }
    154 }
    155 
    156 void TextTrack::ClearAllCues() {
    157  WEBVTT_LOG("ClearAllCues");
    158  ErrorResult dummy;
    159  while (!mCueList->IsEmpty()) {
    160    RemoveCue(*(*mCueList)[0], dummy);
    161  }
    162 }
    163 
    164 void TextTrack::SetCuesDirty() {
    165  for (uint32_t i = 0; i < mCueList->Length(); i++) {
    166    ((*mCueList)[i])->Reset();
    167  }
    168 }
    169 
    170 TextTrackCueList* TextTrack::GetActiveCues() {
    171  if (mMode != TextTrackMode::Disabled) {
    172    return mActiveCueList;
    173  }
    174  return nullptr;
    175 }
    176 
    177 void TextTrack::GetActiveCueArray(nsTArray<RefPtr<TextTrackCue>>& aCues) {
    178  if (mMode != TextTrackMode::Disabled) {
    179    mActiveCueList->GetArray(aCues);
    180  }
    181 }
    182 
    183 TextTrackReadyState TextTrack::ReadyState() const { return mReadyState; }
    184 
    185 void TextTrack::SetReadyState(TextTrackReadyState aState) {
    186  WEBVTT_LOG("SetReadyState=%s", EnumValueToString(aState));
    187  mReadyState = aState;
    188  HTMLMediaElement* mediaElement = GetMediaElement();
    189  if (mediaElement && (mReadyState == TextTrackReadyState::Loaded ||
    190                       mReadyState == TextTrackReadyState::FailedToLoad)) {
    191    mediaElement->RemoveTextTrack(this, true);
    192    mediaElement->UpdateReadyState();
    193  }
    194 }
    195 
    196 TextTrackList* TextTrack::GetTextTrackList() { return mTextTrackList; }
    197 
    198 void TextTrack::SetTextTrackList(TextTrackList* aTextTrackList) {
    199  mTextTrackList = aTextTrackList;
    200 }
    201 
    202 HTMLTrackElement* TextTrack::GetTrackElement() { return mTrackElement; }
    203 
    204 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) {
    205  mTrackElement = aTrackElement;
    206 }
    207 
    208 void TextTrack::SetCuesInactive() {
    209  WEBVTT_LOG("SetCuesInactive");
    210  mCueList->SetCuesInactive();
    211 }
    212 
    213 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) {
    214  WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
    215  mCueList->NotifyCueUpdated(aCue);
    216  HTMLMediaElement* mediaElement = GetMediaElement();
    217  if (mediaElement) {
    218    mediaElement->NotifyCueUpdated(aCue);
    219  }
    220 }
    221 
    222 void TextTrack::GetLabel(nsAString& aLabel) const {
    223  if (mTrackElement) {
    224    mTrackElement->GetLabel(aLabel);
    225  } else {
    226    aLabel = mLabel;
    227  }
    228 }
    229 void TextTrack::GetLanguage(nsAString& aLanguage) const {
    230  if (mTrackElement) {
    231    mTrackElement->GetSrclang(aLanguage);
    232  } else {
    233    aLanguage = mLanguage;
    234  }
    235 }
    236 
    237 void TextTrack::DispatchAsyncTrustedEvent(const nsString& aEventName) {
    238  nsGlobalWindowInner* win = GetOwnerWindow();
    239  if (!win) {
    240    return;
    241  }
    242  win->Dispatch(
    243      NS_NewRunnableFunction("dom::TextTrack::DispatchAsyncTrustedEvent",
    244                             [self = RefPtr{this}, aEventName]() {
    245                               self->DispatchTrustedEvent(aEventName);
    246                             }));
    247 }
    248 
    249 bool TextTrack::IsLoaded() {
    250  if (mMode == TextTrackMode::Disabled) {
    251    return true;
    252  }
    253  // If the TrackElement's src is null, we can not block the
    254  // MediaElement.
    255  if (mTrackElement) {
    256    nsAutoString src;
    257    if (!(mTrackElement->GetAttr(nsGkAtoms::src, src))) {
    258      return true;
    259    }
    260  }
    261  return mReadyState >= TextTrackReadyState::Loaded;
    262 }
    263 
    264 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue* aCue) {
    265  MOZ_ASSERT(aCue);
    266  if (aCue->GetActive()) {
    267    MOZ_ASSERT(!mActiveCueList->IsCueExist(aCue));
    268    WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list",
    269               aCue);
    270    mActiveCueList->AddCue(*aCue);
    271  } else {
    272    MOZ_ASSERT(mActiveCueList->IsCueExist(aCue));
    273    WEBVTT_LOG(
    274        "NotifyCueActiveStateChanged, remove cue %p from the active list",
    275        aCue);
    276    mActiveCueList->RemoveCue(*aCue);
    277  }
    278 }
    279 
    280 void TextTrack::GetOverlappingCurrentOtherAndMissCues(
    281    CueBuckets* aCurrentCues, CueBuckets* aOtherCues, CueBuckets* aMissCues,
    282    const media::TimeInterval& aInterval,
    283    const Maybe<double>& aLastTime) const {
    284  const HTMLMediaElement* mediaElement = GetMediaElement();
    285  if (!mediaElement || Mode() == TextTrackMode::Disabled ||
    286      mCueList->IsEmpty()) {
    287    return;
    288  }
    289 
    290  // According to `time marches on` step1, current cue list contains the cues
    291  // whose start times are less than or equal to the current playback position
    292  // and whose end times are greater than the current playback position.
    293  // https://html.spec.whatwg.org/multipage/media.html#time-marches-on
    294  MOZ_ASSERT(aCurrentCues && aOtherCues);
    295  const double playbackTime = mediaElement->CurrentTime();
    296  const double intervalStart = aInterval.mStart.ToSeconds();
    297  const double intervalEnd = aInterval.mEnd.ToSeconds();
    298 
    299  if (intervalEnd < (*mCueList)[0]->StartTime()) {
    300    WEBVTT_LOGV("Abort : interval ends before the first cue starts");
    301    return;
    302  }
    303  // Optimize the loop range by identifying the first cue that starts after the
    304  // interval.
    305  size_t lastIdx = 0;
    306  struct LastIdxComparator {
    307    const double mIntervalEnd;
    308    explicit LastIdxComparator(double aIntervalEnd)
    309        : mIntervalEnd(aIntervalEnd) {}
    310    int operator()(const TextTrackCue* aCue) const {
    311      return aCue->StartTime() > mIntervalEnd ? 0 : -1;
    312    }
    313  } compLast(intervalEnd);
    314  if (!BinarySearchIf(mCueList->GetCuesArray(), 0, mCueList->Length(), compLast,
    315                      &lastIdx)) {
    316    // Failed to find the match, set it to the last idx.
    317    lastIdx = mCueList->Length() - 1;
    318  }
    319 
    320  // Search cues in the partial range.
    321  for (size_t idx = 0; idx <= lastIdx; ++idx) {
    322    TextTrackCue* cue = (*mCueList)[idx];
    323    double cueStart = cue->StartTime();
    324    double cueEnd = cue->EndTime();
    325    if (cueStart <= playbackTime && cueEnd > playbackTime) {
    326      WEBVTT_LOG("Add cue %p [%f:%f] to current cue list", cue, cueStart,
    327                 cueEnd);
    328      aCurrentCues->AddCue(cue);
    329    } else {
    330      // As the spec doesn't have a restriction for the negative duration, it
    331      // does happen sometime if user sets it explicitly. It will be treated as
    332      // a `missing cue` (a subset of the `other cues`) and it won't be
    333      // displayed.
    334      if (cueEnd < cueStart) {
    335        // Add cue into `otherCue` only when its start time is contained by the
    336        // current time interval.
    337        if (intervalStart <= cueStart && cueStart < intervalEnd) {
    338          WEBVTT_LOG(
    339              "[Negative duration] Add cue %p [%f:%f] to other cues and "
    340              "missing cues list",
    341              cue, cueStart, cueEnd);
    342          aOtherCues->AddCue(cue);
    343          aMissCues->AddCue(cue);
    344        }
    345        continue;
    346      }
    347      // Cues are completely outside the time interval.
    348      if (cueEnd < intervalStart || cueStart > intervalEnd) {
    349        continue;
    350      }
    351      WEBVTT_LOG("Add cue %p [%f:%f] to other cue list", cue, cueStart, cueEnd);
    352      aOtherCues->AddCue(cue);
    353      if (aLastTime && cueStart >= *aLastTime && cueEnd <= playbackTime) {
    354        WEBVTT_LOG("Add cue %p [%f:%f] to missing cues list", cue, cueStart,
    355                   cueEnd);
    356        aMissCues->AddCue(cue);
    357      }
    358    }
    359  }
    360 }
    361 
    362 HTMLMediaElement* TextTrack::GetMediaElement() const {
    363  return mTextTrackList ? mTextTrackList->GetMediaElement() : nullptr;
    364 }
    365 
    366 void TextTrack::CueBuckets::AddCue(TextTrackCue* aCue) {
    367  if (aCue->GetActive()) {
    368    ActiveCues().AppendElement(aCue);
    369    if (aCue->PauseOnExit()) {
    370      mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::Active)] = true;
    371    }
    372  } else {
    373    InactiveCues().AppendElement(aCue);
    374    if (aCue->PauseOnExit()) {
    375      mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::Inactive)] = true;
    376    }
    377  }
    378  AllCues().AppendElement(aCue);
    379  if (aCue->PauseOnExit()) {
    380    mHasPauseOnExist[static_cast<uint8_t>(CueActivityState::All)] = true;
    381  }
    382 }
    383 
    384 }  // namespace mozilla::dom
    385 
    386 #undef WEBVTT_LOG
    387 #undef WEBVTT_LOGV