tor-browser

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

HTMLMediaElement.cpp (286257B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/HTMLMediaElement.h"
      8 
      9 #include <algorithm>
     10 #include <cmath>
     11 #include <limits>
     12 #include <type_traits>
     13 
     14 #include "AudioDeviceInfo.h"
     15 #include "AudioStreamTrack.h"
     16 #include "AutoplayPolicy.h"
     17 #include "ChannelMediaDecoder.h"
     18 #include "CrossGraphPort.h"
     19 #include "DOMMediaStream.h"
     20 #include "DecoderDoctorDiagnostics.h"
     21 #include "DecoderDoctorLogger.h"
     22 #include "DecoderTraits.h"
     23 #include "FrameStatistics.h"
     24 #include "GMPCrashHelper.h"
     25 #include "GVAutoplayPermissionRequest.h"
     26 #include "nsString.h"
     27 #ifdef MOZ_ANDROID_HLS_SUPPORT
     28 #  include "HLSDecoder.h"
     29 #endif
     30 #include "HTMLMediaElement.h"
     31 #include "ImageContainer.h"
     32 #include "MP4Decoder.h"
     33 #include "MediaContainerType.h"
     34 #include "MediaError.h"
     35 #include "MediaManager.h"
     36 #include "MediaMetadataManager.h"
     37 #include "MediaProfilerMarkers.h"
     38 #include "MediaResource.h"
     39 #include "MediaShutdownManager.h"
     40 #include "MediaSourceDecoder.h"
     41 #include "MediaStreamError.h"
     42 #include "MediaStreamWindowCapturer.h"
     43 #include "MediaTrack.h"
     44 #include "MediaTrackGraphImpl.h"
     45 #include "MediaTrackList.h"
     46 #include "MediaTrackListener.h"
     47 #include "Navigator.h"
     48 #include "ReferrerInfo.h"
     49 #include "TimeRanges.h"
     50 #include "TimeUnits.h"
     51 #include "VideoFrameContainer.h"
     52 #include "VideoOutput.h"
     53 #include "VideoStreamTrack.h"
     54 #include "base/basictypes.h"
     55 #include "js/PropertyAndElement.h"  // JS_DefineProperty
     56 #include "jsapi.h"
     57 #include "mozilla/AppShutdown.h"
     58 #include "mozilla/AsyncEventDispatcher.h"
     59 #include "mozilla/EMEUtils.h"
     60 #include "mozilla/EventDispatcher.h"
     61 #include "mozilla/MathAlgorithms.h"
     62 #include "mozilla/MediaFragmentURIParser.h"
     63 #include "mozilla/Preferences.h"
     64 #include "mozilla/PresShell.h"
     65 #include "mozilla/SVGObserverUtils.h"
     66 #include "mozilla/SchedulerGroup.h"
     67 #include "mozilla/ScopeExit.h"
     68 #include "mozilla/Sprintf.h"
     69 #include "mozilla/StaticPrefs_media.h"
     70 #include "mozilla/dom/AncestorIterator.h"
     71 #include "mozilla/dom/AudioTrack.h"
     72 #include "mozilla/dom/AudioTrackList.h"
     73 #include "mozilla/dom/BlobURLProtocolHandler.h"
     74 #include "mozilla/dom/ContentMediaController.h"
     75 #include "mozilla/dom/Document.h"
     76 #include "mozilla/dom/ElementInlines.h"
     77 #include "mozilla/dom/FeaturePolicyUtils.h"
     78 #include "mozilla/dom/HTMLAudioElement.h"
     79 #include "mozilla/dom/HTMLInputElement.h"
     80 #include "mozilla/dom/HTMLMediaElementBinding.h"
     81 #include "mozilla/dom/HTMLSourceElement.h"
     82 #include "mozilla/dom/HTMLVideoElement.h"
     83 #include "mozilla/dom/MediaControlUtils.h"
     84 #include "mozilla/dom/MediaDevices.h"
     85 #include "mozilla/dom/MediaEncryptedEvent.h"
     86 #include "mozilla/dom/MediaErrorBinding.h"
     87 #include "mozilla/dom/MediaSource.h"
     88 #include "mozilla/dom/PlayPromise.h"
     89 #include "mozilla/dom/Promise.h"
     90 #include "mozilla/dom/TextTrack.h"
     91 #include "mozilla/dom/UserActivation.h"
     92 #include "mozilla/dom/VideoPlaybackQuality.h"
     93 #include "mozilla/dom/VideoTrack.h"
     94 #include "mozilla/dom/VideoTrackList.h"
     95 #include "mozilla/dom/WakeLock.h"
     96 #include "mozilla/dom/WindowGlobalChild.h"
     97 #include "mozilla/dom/power/PowerManagerService.h"
     98 #include "mozilla/glean/DomMediaMetrics.h"
     99 #include "mozilla/net/UrlClassifierFeatureFactory.h"
    100 #include "mozilla/nsVideoFrame.h"
    101 #include "nsAttrValueInlines.h"
    102 #include "nsAttrValueOrString.h"
    103 #include "nsContentPolicyUtils.h"
    104 #include "nsContentUtils.h"
    105 #include "nsCycleCollectionParticipant.h"
    106 #include "nsDisplayList.h"
    107 #include "nsDocShell.h"
    108 #include "nsError.h"
    109 #include "nsGenericHTMLElement.h"
    110 #include "nsGkAtoms.h"
    111 #include "nsGlobalWindowInner.h"
    112 #include "nsIAsyncVerifyRedirectCallback.h"
    113 #include "nsICachingChannel.h"
    114 #include "nsIClassOfService.h"
    115 #include "nsIContentPolicy.h"
    116 #include "nsIDocShell.h"
    117 #include "nsIFrame.h"
    118 #include "nsIHttpChannel.h"
    119 #include "nsIObserverService.h"
    120 #include "nsIRequest.h"
    121 #include "nsIScriptError.h"
    122 #include "nsISupportsPrimitives.h"
    123 #include "nsIThreadRetargetableStreamListener.h"
    124 #include "nsITimer.h"
    125 #include "nsJSUtils.h"
    126 #include "nsLayoutUtils.h"
    127 #include "nsMimeTypes.h"
    128 #include "nsNetUtil.h"
    129 #include "nsNodeInfoManager.h"
    130 #include "nsPresContext.h"
    131 #include "nsQueryObject.h"
    132 #include "nsRange.h"
    133 #include "nsSize.h"
    134 #include "nsThreadUtils.h"
    135 #include "nsURIHashKey.h"
    136 #include "nsURLHelper.h"
    137 #ifdef XP_WIN
    138 #  include "objbase.h"
    139 #endif
    140 #include "xpcpublic.h"
    141 
    142 mozilla::LazyLogModule gMediaElementLog("HTMLMediaElement");
    143 mozilla::LazyLogModule gMediaElementEventsLog("HTMLMediaElementEvents");
    144 
    145 extern mozilla::LazyLogModule gAutoplayPermissionLog;
    146 #define AUTOPLAY_LOG(msg, ...) \
    147  MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
    148 
    149 // avoid redefined macro in unified build
    150 #undef MEDIACONTROL_LOG
    151 #define MEDIACONTROL_LOG(msg, ...)           \
    152  MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
    153          ("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__))
    154 
    155 #undef CONTROLLER_TIMER_LOG
    156 #define CONTROLLER_TIMER_LOG(element, msg, ...) \
    157  MOZ_LOG(gMediaControlLog, LogLevel::Debug,    \
    158          ("HTMLMediaElement=%p, " msg, element, ##__VA_ARGS__))
    159 
    160 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
    161 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
    162 
    163 using namespace mozilla::layers;
    164 using namespace mozilla::dom::HTMLMediaElement_Binding;
    165 
    166 namespace mozilla::dom {
    167 
    168 using AudibleState = AudioChannelService::AudibleState;
    169 using SinkInfoPromise = MediaDevices::SinkInfoPromise;
    170 
    171 // Number of milliseconds between progress events as defined by spec
    172 static const uint32_t PROGRESS_MS = 350;
    173 
    174 // Number of milliseconds of no data before a stall event is fired as defined by
    175 // spec
    176 static const uint32_t STALL_MS = 3000;
    177 
    178 // Used by AudioChannel for suppresssing the volume to this ratio.
    179 #define FADED_VOLUME_RATIO 0.25
    180 
    181 // These constants are arbitrary
    182 // Minimum playbackRate for a media
    183 static const double MIN_PLAYBACKRATE = 1.0 / 16;
    184 // Maximum playbackRate for a media
    185 static const double MAX_PLAYBACKRATE = 16.0;
    186 
    187 static double ClampPlaybackRate(double aPlaybackRate) {
    188  MOZ_ASSERT(aPlaybackRate >= 0.0);
    189  MOZ_ASSERT(std::isfinite(aPlaybackRate));
    190 
    191  if (aPlaybackRate == 0.0) {
    192    return aPlaybackRate;
    193  }
    194  if (aPlaybackRate < MIN_PLAYBACKRATE) {
    195    return MIN_PLAYBACKRATE;
    196  }
    197  if (aPlaybackRate > MAX_PLAYBACKRATE) {
    198    return MAX_PLAYBACKRATE;
    199  }
    200  return aPlaybackRate;
    201 }
    202 
    203 // Media error values.  These need to match the ones in MediaError.webidl.
    204 static const unsigned short MEDIA_ERR_ABORTED = 1;
    205 static const unsigned short MEDIA_ERR_NETWORK = 2;
    206 static const unsigned short MEDIA_ERR_DECODE = 3;
    207 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
    208 
    209 /**
    210 * EventBlocker helps media element to postpone the event delivery by storing
    211 * the event runner, and execute them once media element decides not to postpone
    212 * the event delivery. If media element never resumes the event delivery, then
    213 * those runner would be cancelled.
    214 * For example, we postpone the event delivery when media element entering to
    215 * the bf-cache.
    216 */
    217 class HTMLMediaElement::EventBlocker final : public nsISupports {
    218 public:
    219  NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
    220  NS_DECL_CYCLE_COLLECTION_CLASS(EventBlocker)
    221 
    222  explicit EventBlocker(HTMLMediaElement* aElement) : mElement(aElement) {}
    223 
    224  void SetBlockEventDelivery(bool aShouldBlock) {
    225    MOZ_ASSERT(NS_IsMainThread());
    226    if (mShouldBlockEventDelivery == aShouldBlock) {
    227      return;
    228    }
    229    LOG_EVENT(LogLevel::Debug,
    230              ("%p %s event delivery", mElement.get(),
    231               mShouldBlockEventDelivery ? "block" : "unblock"));
    232    mShouldBlockEventDelivery = aShouldBlock;
    233    if (!mShouldBlockEventDelivery) {
    234      DispatchPendingMediaEvents();
    235    }
    236  }
    237 
    238  void PostponeEvent(nsMediaEventRunner* aRunner) {
    239    MOZ_ASSERT(NS_IsMainThread());
    240    // Element has been CCed, which would break the weak pointer.
    241    if (!mElement) {
    242      return;
    243    }
    244    MOZ_ASSERT(mShouldBlockEventDelivery);
    245    MOZ_ASSERT(mElement);
    246    LOG_EVENT(LogLevel::Debug,
    247              ("%p postpone runner %s for %s", mElement.get(), aRunner->Name(),
    248               NS_ConvertUTF16toUTF8(aRunner->EventName()).get()));
    249    mPendingEventRunners.AppendElement(aRunner);
    250  }
    251 
    252  void Shutdown() {
    253    MOZ_ASSERT(NS_IsMainThread());
    254    for (auto& runner : mPendingEventRunners) {
    255      runner->Cancel();
    256    }
    257    mPendingEventRunners.Clear();
    258  }
    259 
    260  bool ShouldBlockEventDelivery() const {
    261    MOZ_ASSERT(NS_IsMainThread());
    262    return mShouldBlockEventDelivery;
    263  }
    264 
    265  size_t SizeOfExcludingThis(MallocSizeOf& aMallocSizeOf) const {
    266    MOZ_ASSERT(NS_IsMainThread());
    267    size_t total = 0;
    268    for (const auto& runner : mPendingEventRunners) {
    269      total += aMallocSizeOf(runner);
    270    }
    271    return total;
    272  }
    273 
    274 private:
    275  ~EventBlocker() = default;
    276 
    277  void DispatchPendingMediaEvents() {
    278    MOZ_ASSERT(mElement);
    279    for (auto& runner : mPendingEventRunners) {
    280      LOG_EVENT(LogLevel::Debug,
    281                ("%p execute runner %s for %s", mElement.get(), runner->Name(),
    282                 NS_ConvertUTF16toUTF8(runner->EventName()).get()));
    283      GetMainThreadSerialEventTarget()->Dispatch(runner.forget());
    284    }
    285    mPendingEventRunners.Clear();
    286  }
    287 
    288  WeakPtr<HTMLMediaElement> mElement;
    289  bool mShouldBlockEventDelivery = false;
    290  // Contains event runners which should not be run for now because we want
    291  // to block all events delivery. They would be dispatched once media element
    292  // decides unblocking them.
    293  nsTArray<RefPtr<nsMediaEventRunner>> mPendingEventRunners;
    294 };
    295 
    296 NS_IMPL_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker, mPendingEventRunners)
    297 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::EventBlocker)
    298 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::EventBlocker)
    299 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker)
    300  NS_INTERFACE_MAP_ENTRY(nsISupports)
    301 NS_INTERFACE_MAP_END
    302 
    303 /**
    304 * We use MediaControlKeyListener to listen to media control key in order to
    305 * play and pause media element when user press media control keys and update
    306 * media's playback and audible state to the media controller.
    307 *
    308 * Use `Start()` to start listening event and use `Stop()` to stop listening
    309 * event. In addition, notifying any change to media controller MUST be done
    310 * after successfully calling `Start()`.
    311 */
    312 class HTMLMediaElement::MediaControlKeyListener final
    313    : public ContentMediaControlKeyReceiver {
    314 public:
    315  NS_INLINE_DECL_REFCOUNTING(MediaControlKeyListener, override)
    316 
    317  MOZ_INIT_OUTSIDE_CTOR explicit MediaControlKeyListener(
    318      HTMLMediaElement* aElement)
    319      : mElement(aElement), mElementId(nsID::GenerateUUID()) {
    320    MOZ_ASSERT(NS_IsMainThread());
    321    MOZ_ASSERT(aElement);
    322  }
    323 
    324  /**
    325   * Start listening to the media control keys which would make media being able
    326   * to be controlled via pressing media control keys.
    327   */
    328  void Start() {
    329    MOZ_ASSERT(NS_IsMainThread());
    330    if (IsStarted()) {
    331      // We have already been started, do not notify start twice.
    332      return;
    333    }
    334 
    335    // Fail to init media agent, we are not able to notify the media controller
    336    // any update and also are not able to receive media control key events.
    337    if (!InitMediaAgent()) {
    338      MEDIACONTROL_LOG("Failed to start due to not able to init media agent!");
    339      return;
    340    }
    341 
    342    NotifyPlaybackStateChanged(MediaPlaybackState::eStarted);
    343    // If owner has started playing before the listener starts, we should update
    344    // the playing state as well. Eg. media starts inaudily and becomes audible
    345    // later.
    346    if (!Owner()->Paused()) {
    347      NotifyMediaStartedPlaying();
    348    }
    349    if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
    350      auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
    351          Owner(), u"MozStartMediaControl"_ns, CanBubble::eYes,
    352          ChromeOnlyDispatch::eYes);
    353      dispatcher->PostDOMEvent();
    354    }
    355  }
    356 
    357  /**
    358   * Stop listening to the media control keys which would make media not be able
    359   * to be controlled via pressing media control keys. If we haven't started
    360   * listening to the media control keys, then nothing would happen.
    361   */
    362  void StopIfNeeded() {
    363    MOZ_ASSERT(NS_IsMainThread());
    364    if (!IsStarted()) {
    365      // We have already been stopped, do not notify stop twice.
    366      return;
    367    }
    368    NotifyMediaStoppedPlaying();
    369    NotifyPlaybackStateChanged(MediaPlaybackState::eStopped);
    370 
    371    // Remove ourselves from media agent, which would stop receiving event.
    372    mControlAgent->RemoveReceiver(this);
    373    mControlAgent = nullptr;
    374  }
    375 
    376  bool IsStarted() const { return mState != MediaPlaybackState::eStopped; }
    377 
    378  bool IsPlaying() const override {
    379    return Owner() ? !Owner()->Paused() : false;
    380  }
    381 
    382  /**
    383   * Following methods should only be used after starting listener.
    384   */
    385  void NotifyMediaStartedPlaying() {
    386    MOZ_ASSERT(NS_IsMainThread());
    387    MOZ_ASSERT(IsStarted());
    388    if (mState == MediaPlaybackState::eStarted ||
    389        mState == MediaPlaybackState::ePaused) {
    390      NotifyPlaybackStateChanged(MediaPlaybackState::ePlayed);
    391      // If media is `inaudible` in the beginning, then we don't need to notify
    392      // the state, because notifying `inaudible` should always come after
    393      // notifying `audible`.
    394      if (mIsOwnerAudible) {
    395        NotifyAudibleStateChanged(MediaAudibleState::eAudible);
    396      }
    397    }
    398  }
    399 
    400  void NotifyMediaStoppedPlaying() {
    401    MOZ_ASSERT(NS_IsMainThread());
    402    MOZ_ASSERT(IsStarted());
    403    if (mState == MediaPlaybackState::ePlayed) {
    404      NotifyPlaybackStateChanged(MediaPlaybackState::ePaused);
    405      // As media are going to be paused, so no sound is possible to be heard.
    406      if (mIsOwnerAudible) {
    407        NotifyAudibleStateChanged(MediaAudibleState::eInaudible);
    408      }
    409    }
    410  }
    411 
    412  void NotifyMediaPositionState() {
    413    if (!IsStarted()) {
    414      return;
    415    }
    416 
    417    MOZ_ASSERT(mControlAgent);
    418    auto* owner = Owner();
    419    PositionState state(owner->Duration(),
    420                        owner->Paused() ? 0.0 : owner->PlaybackRate(),
    421                        owner->CurrentTime(), TimeStamp::Now());
    422    MEDIACONTROL_LOG(
    423        "Notify media position state (duration=%f, playbackRate=%f, "
    424        "position=%f)",
    425        state.mDuration, state.mPlaybackRate,
    426        state.mLastReportedPlaybackPosition);
    427    mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
    428                                              mElementId, Some(state));
    429  }
    430 
    431  void Shutdown() {
    432    StopIfNeeded();
    433    if (!mControlAgent) {
    434      return;
    435    }
    436    mControlAgent->UpdateGuessedPositionState(mOwnerBrowsingContextId,
    437                                              mElementId, Nothing());
    438  }
    439 
    440  // This method can be called before the listener starts, which would cache
    441  // the audible state and update after the listener starts.
    442  void UpdateMediaAudibleState(bool aIsOwnerAudible) {
    443    MOZ_ASSERT(NS_IsMainThread());
    444    if (mIsOwnerAudible == aIsOwnerAudible) {
    445      return;
    446    }
    447    mIsOwnerAudible = aIsOwnerAudible;
    448    MEDIACONTROL_LOG("Media becomes %s",
    449                     mIsOwnerAudible ? "audible" : "inaudible");
    450    // If media hasn't started playing, it doesn't make sense to update media
    451    // audible state. Therefore, in that case we would noitfy the audible state
    452    // when media starts playing.
    453    if (mState == MediaPlaybackState::ePlayed) {
    454      NotifyAudibleStateChanged(mIsOwnerAudible
    455                                    ? MediaAudibleState::eAudible
    456                                    : MediaAudibleState::eInaudible);
    457    }
    458  }
    459 
    460  void SetPictureInPictureModeEnabled(bool aIsEnabled) {
    461    MOZ_ASSERT(NS_IsMainThread());
    462    if (mIsPictureInPictureEnabled == aIsEnabled) {
    463      return;
    464    }
    465    // PIP state changes might happen before the listener starts or stops where
    466    // we haven't call `InitMediaAgent()` yet. Eg. Reset the PIP video's src,
    467    // then cancel the PIP. In addition, not like playback and audible state
    468    // which should be restricted to update via the same agent in order to keep
    469    // those states correct in each `ContextMediaInfo`, PIP state can be updated
    470    // through any browsing context, so we would use `ContentMediaAgent::Get()`
    471    // directly to update PIP state.
    472    mIsPictureInPictureEnabled = aIsEnabled;
    473    if (RefPtr<IMediaInfoUpdater> updater =
    474            ContentMediaAgent::Get(GetCurrentBrowsingContext())) {
    475      updater->SetIsInPictureInPictureMode(mOwnerBrowsingContextId,
    476                                           mIsPictureInPictureEnabled);
    477    }
    478  }
    479 
    480  void HandleMediaKey(MediaControlKey aKey,
    481                      Maybe<SeekDetails> aDetails) override {
    482    MOZ_ASSERT(NS_IsMainThread());
    483    MOZ_ASSERT(IsStarted());
    484    MEDIACONTROL_LOG("HandleEvent '%s'", GetEnumString(aKey).get());
    485    switch (aKey) {
    486      case MediaControlKey::Play:
    487        Owner()->Play();
    488        break;
    489      case MediaControlKey::Pause:
    490        Owner()->Pause();
    491        break;
    492      case MediaControlKey::Stop:
    493        Owner()->Pause();
    494        StopIfNeeded();
    495        break;
    496      case MediaControlKey::Seekto:
    497        MOZ_ASSERT(aDetails->mAbsolute);
    498        if (aDetails->mAbsolute->mFastSeek) {
    499          Owner()->FastSeek(aDetails->mAbsolute->mSeekTime, IgnoreErrors());
    500        } else {
    501          Owner()->SetCurrentTime(aDetails->mAbsolute->mSeekTime);
    502        }
    503        break;
    504      case MediaControlKey::Seekforward:
    505        MOZ_ASSERT(aDetails->mRelativeSeekOffset);
    506        Owner()->SetCurrentTime(Owner()->CurrentTime() +
    507                                aDetails->mRelativeSeekOffset.value());
    508        break;
    509      case MediaControlKey::Seekbackward:
    510        MOZ_ASSERT(aDetails->mRelativeSeekOffset);
    511        Owner()->SetCurrentTime(Owner()->CurrentTime() -
    512                                aDetails->mRelativeSeekOffset.value());
    513        break;
    514      default:
    515        MOZ_ASSERT_UNREACHABLE(
    516            "Unsupported media control key for media element!");
    517    }
    518  }
    519 
    520  void UpdateOwnerBrowsingContextIfNeeded() {
    521    // Has not notified any information about the owner context yet.
    522    if (!IsStarted()) {
    523      return;
    524    }
    525 
    526    BrowsingContext* currentBC = GetCurrentBrowsingContext();
    527    MOZ_ASSERT(currentBC);
    528    // Still in the same browsing context, no need to update.
    529    if (currentBC->Id() == mOwnerBrowsingContextId) {
    530      return;
    531    }
    532    MEDIACONTROL_LOG("Change browsing context from %" PRIu64 " to %" PRIu64,
    533                     mOwnerBrowsingContextId, currentBC->Id());
    534    // This situation would happen when we start a media in an original browsing
    535    // context, then we move it to another browsing context, such as an iframe,
    536    // so its owner browsing context would be changed. Therefore, we should
    537    // reset the media status for the previous browsing context by calling
    538    // `Stop()`, in which the listener would notify `ePaused` (if it's playing)
    539    // and `eStop`. Then calls `Start()`, in which the listener would notify
    540    // `eStart` to the new browsing context. If the media was playing before,
    541    // we would also notify `ePlayed`.
    542    bool wasInPlayingState = mState == MediaPlaybackState::ePlayed;
    543    StopIfNeeded();
    544    Start();
    545    if (wasInPlayingState) {
    546      NotifyMediaStartedPlaying();
    547    }
    548  }
    549 
    550 private:
    551  ~MediaControlKeyListener() = default;
    552 
    553  // The media can be moved around different browsing contexts, so this context
    554  // might be different from the one that we used to initialize
    555  // `ContentMediaAgent`.
    556  BrowsingContext* GetCurrentBrowsingContext() const {
    557    // Owner has been CCed, which would break the link of the weaker pointer.
    558    if (!Owner()) {
    559      return nullptr;
    560    }
    561    nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow();
    562    return window ? window->GetBrowsingContext() : nullptr;
    563  }
    564 
    565  bool InitMediaAgent() {
    566    MOZ_ASSERT(NS_IsMainThread());
    567    BrowsingContext* currentBC = GetCurrentBrowsingContext();
    568    mControlAgent = ContentMediaAgent::Get(currentBC);
    569    if (!mControlAgent) {
    570      return false;
    571    }
    572    MOZ_ASSERT(currentBC);
    573    mOwnerBrowsingContextId = currentBC->Id();
    574    MEDIACONTROL_LOG("Init agent in browsing context %" PRIu64,
    575                     mOwnerBrowsingContextId);
    576    mControlAgent->AddReceiver(this);
    577    return true;
    578  }
    579 
    580  HTMLMediaElement* Owner() const {
    581    // `mElement` would be clear during CC unlinked, but it would only happen
    582    // after stopping the listener.
    583    MOZ_ASSERT(mElement || !IsStarted());
    584    return mElement.get();
    585  }
    586 
    587  void NotifyPlaybackStateChanged(MediaPlaybackState aState) {
    588    MOZ_ASSERT(NS_IsMainThread());
    589    MOZ_ASSERT(mControlAgent);
    590    MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'",
    591                     dom::EnumValueToString(mState),
    592                     dom::EnumValueToString(aState));
    593    MOZ_ASSERT(mState != aState, "Should not notify same state again!");
    594    mState = aState;
    595    mControlAgent->NotifyMediaPlaybackChanged(mOwnerBrowsingContextId, mState);
    596 
    597    if (aState == MediaPlaybackState::ePlayed ||
    598        aState == MediaPlaybackState::ePaused) {
    599      NotifyMediaPositionState();
    600    }
    601  }
    602 
    603  void NotifyAudibleStateChanged(MediaAudibleState aState) {
    604    MOZ_ASSERT(NS_IsMainThread());
    605    MOZ_ASSERT(IsStarted());
    606    mControlAgent->NotifyMediaAudibleChanged(mOwnerBrowsingContextId, aState);
    607  }
    608 
    609  MediaPlaybackState mState = MediaPlaybackState::eStopped;
    610  WeakPtr<HTMLMediaElement> mElement;
    611  RefPtr<ContentMediaAgent> mControlAgent;
    612  bool mIsPictureInPictureEnabled = false;
    613  bool mIsOwnerAudible = false;
    614  MOZ_INIT_OUTSIDE_CTOR uint64_t mOwnerBrowsingContextId;
    615  const nsID mElementId;
    616 };
    617 
    618 class HTMLMediaElement::MediaStreamTrackListener
    619    : public DOMMediaStream::TrackListener {
    620 public:
    621  explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
    622      : mElement(aElement) {}
    623 
    624  NS_DECL_ISUPPORTS_INHERITED
    625  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrackListener,
    626                                           DOMMediaStream::TrackListener)
    627 
    628  void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
    629    if (!mElement) {
    630      return;
    631    }
    632    mElement->NotifyMediaStreamTrackAdded(aTrack);
    633  }
    634 
    635  void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
    636    if (!mElement) {
    637      return;
    638    }
    639    mElement->NotifyMediaStreamTrackRemoved(aTrack);
    640  }
    641 
    642  void OnActive() {
    643    MOZ_ASSERT(mElement);
    644 
    645    // mediacapture-main says:
    646    // Note that once ended equals true the HTMLVideoElement will not play media
    647    // even if new MediaStreamTracks are added to the MediaStream (causing it to
    648    // return to the active state) unless autoplay is true or the web
    649    // application restarts the element, e.g., by calling play().
    650    //
    651    // This is vague on exactly how to go from becoming active to playing, when
    652    // autoplaying. However, per the media element spec, to play an autoplaying
    653    // media element, we must load the source and reach readyState
    654    // HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media
    655    // element and becoming active runs the load algorithm, so that it can
    656    // eventually be played.
    657    //
    658    // [1]
    659    // https://html.spec.whatwg.org/multipage/media.html#ready-states:event-media-play
    660 
    661    LOG(LogLevel::Debug, ("%p, mSrcStream %p became active, checking if we "
    662                          "need to run the load algorithm",
    663                          mElement.get(), mElement->mSrcStream.get()));
    664    if (!mElement->IsPlaybackEnded()) {
    665      return;
    666    }
    667    if (!mElement->Autoplay()) {
    668      return;
    669    }
    670    LOG(LogLevel::Info, ("%p, mSrcStream %p became active on autoplaying, "
    671                         "ended element. Reloading.",
    672                         mElement.get(), mElement->mSrcStream.get()));
    673    mElement->DoLoad();
    674  }
    675 
    676  void NotifyActive() override {
    677    if (!mElement) {
    678      return;
    679    }
    680 
    681    if (!mElement->IsVideo()) {
    682      // Audio elements use NotifyAudible().
    683      return;
    684    }
    685 
    686    OnActive();
    687  }
    688 
    689  void NotifyAudible() override {
    690    if (!mElement) {
    691      return;
    692    }
    693 
    694    if (mElement->IsVideo()) {
    695      // Video elements use NotifyActive().
    696      return;
    697    }
    698 
    699    OnActive();
    700  }
    701 
    702  void OnInactive() {
    703    MOZ_ASSERT(mElement);
    704 
    705    if (mElement->IsPlaybackEnded()) {
    706      return;
    707    }
    708    LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(),
    709                          mElement->mSrcStream.get()));
    710 
    711    mElement->PlaybackEnded();
    712  }
    713 
    714  void NotifyInactive() override {
    715    if (!mElement) {
    716      return;
    717    }
    718 
    719    if (!mElement->IsVideo()) {
    720      // Audio elements use NotifyInaudible().
    721      return;
    722    }
    723 
    724    OnInactive();
    725  }
    726 
    727  void NotifyInaudible() override {
    728    if (!mElement) {
    729      return;
    730    }
    731 
    732    if (mElement->IsVideo()) {
    733      // Video elements use NotifyInactive().
    734      return;
    735    }
    736 
    737    OnInactive();
    738  }
    739 
    740 protected:
    741  virtual ~MediaStreamTrackListener() = default;
    742  RefPtr<HTMLMediaElement> mElement;
    743 };
    744 
    745 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
    746                                   DOMMediaStream::TrackListener, mElement)
    747 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
    748                         DOMMediaStream::TrackListener)
    749 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaStreamTrackListener,
    750                          DOMMediaStream::TrackListener)
    751 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
    752    HTMLMediaElement::MediaStreamTrackListener)
    753 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream::TrackListener)
    754 
    755 /**
    756 * Helper class that manages audio and video outputs for all enabled tracks in a
    757 * media element. It also manages calculating the current time when playing a
    758 * MediaStream.
    759 */
    760 class HTMLMediaElement::MediaStreamRenderer {
    761 public:
    762  NS_INLINE_DECL_REFCOUNTING(MediaStreamRenderer)
    763 
    764  MediaStreamRenderer(AbstractThread* aMainThread,
    765                      VideoFrameContainer* aVideoContainer,
    766                      FirstFrameVideoOutput* aFirstFrameVideoOutput,
    767                      void* aAudioOutputKey)
    768      : mVideoContainer(aVideoContainer),
    769        mAudioOutputKey(aAudioOutputKey),
    770        mWatchManager(this, aMainThread),
    771        mFirstFrameVideoOutput(aFirstFrameVideoOutput) {
    772    if (mFirstFrameVideoOutput) {
    773      mWatchManager.Watch(mFirstFrameVideoOutput->mFirstFrameRendered,
    774                          &MediaStreamRenderer::SetFirstFrameRendered);
    775    }
    776  }
    777 
    778  void Shutdown() {
    779    for (const auto& t : mAudioTracks.Clone()) {
    780      if (t) {
    781        RemoveTrack(t->AsAudioStreamTrack());
    782      }
    783    }
    784    if (mVideoTrack) {
    785      RemoveTrack(mVideoTrack->AsVideoStreamTrack());
    786    }
    787    mWatchManager.Shutdown();
    788    mFirstFrameVideoOutput = nullptr;
    789  }
    790 
    791  void UpdateGraphTime() {
    792    mGraphTime =
    793        mGraphTimeDummy->mTrack->Graph()->CurrentTime() - *mGraphTimeOffset;
    794  }
    795 
    796  void SetFirstFrameRendered() {
    797    if (!mFirstFrameVideoOutput) {
    798      return;
    799    }
    800    if (mVideoTrack) {
    801      mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(
    802          mFirstFrameVideoOutput);
    803    }
    804    mWatchManager.Unwatch(mFirstFrameVideoOutput->mFirstFrameRendered,
    805                          &MediaStreamRenderer::SetFirstFrameRendered);
    806    mFirstFrameVideoOutput = nullptr;
    807  }
    808 
    809  void SetProgressingCurrentTime(bool aProgress) {
    810    if (aProgress == mProgressingCurrentTime) {
    811      return;
    812    }
    813 
    814    MOZ_DIAGNOSTIC_ASSERT(mGraphTimeDummy);
    815    mProgressingCurrentTime = aProgress;
    816    MediaTrackGraph* graph = mGraphTimeDummy->mTrack->Graph();
    817    if (mProgressingCurrentTime) {
    818      mGraphTimeOffset = Some(graph->CurrentTime().Ref() - mGraphTime);
    819      mWatchManager.Watch(graph->CurrentTime(),
    820                          &MediaStreamRenderer::UpdateGraphTime);
    821    } else {
    822      mWatchManager.Unwatch(graph->CurrentTime(),
    823                            &MediaStreamRenderer::UpdateGraphTime);
    824    }
    825  }
    826 
    827  void Start() {
    828    if (mRendering) {
    829      return;
    830    }
    831 
    832    LOG(LogLevel::Info, ("MediaStreamRenderer=%p Start", this));
    833    mRendering = true;
    834 
    835    if (!mGraphTimeDummy) {
    836      return;
    837    }
    838 
    839    for (const auto& t : mAudioTracks) {
    840      if (t) {
    841        t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey,
    842                                                mAudioOutputSink);
    843        t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
    844                                                      mAudioOutputVolume);
    845      }
    846    }
    847 
    848    if (mVideoTrack) {
    849      mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer);
    850    }
    851  }
    852 
    853  void Stop() {
    854    if (!mRendering) {
    855      return;
    856    }
    857 
    858    LOG(LogLevel::Info, ("MediaStreamRenderer=%p Stop", this));
    859    mRendering = false;
    860 
    861    if (!mGraphTimeDummy) {
    862      return;
    863    }
    864 
    865    for (const auto& t : mAudioTracks) {
    866      if (t) {
    867        t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
    868      }
    869    }
    870    // There is no longer an audio output that needs the device so the
    871    // device may not start.  Ensure the promise is resolved.
    872    ResolveAudioDevicePromiseIfExists(__func__);
    873 
    874    if (mVideoTrack) {
    875      mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer);
    876    }
    877  }
    878 
    879  void SetAudioOutputVolume(float aVolume) {
    880    if (mAudioOutputVolume == aVolume) {
    881      return;
    882    }
    883    mAudioOutputVolume = aVolume;
    884    if (!mRendering) {
    885      return;
    886    }
    887    for (const auto& t : mAudioTracks) {
    888      if (t) {
    889        t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
    890                                                      mAudioOutputVolume);
    891      }
    892    }
    893  }
    894 
    895  RefPtr<GenericPromise> SetAudioOutputDevice(AudioDeviceInfo* aSink) {
    896    MOZ_ASSERT(aSink);
    897    MOZ_ASSERT(mAudioOutputSink != aSink);
    898    LOG(LogLevel::Info,
    899        ("MediaStreamRenderer=%p SetAudioOutputDevice name=%s\n", this,
    900         NS_ConvertUTF16toUTF8(aSink->Name()).get()));
    901 
    902    mAudioOutputSink = aSink;
    903 
    904    if (!mRendering) {
    905      MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty());
    906      return GenericPromise::CreateAndResolve(true, __func__);
    907    }
    908 
    909    nsTArray<RefPtr<GenericPromise>> promises;
    910    for (const auto& t : mAudioTracks) {
    911      t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
    912      promises.AppendElement(t->AsAudioStreamTrack()->AddAudioOutput(
    913          mAudioOutputKey, mAudioOutputSink));
    914      t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
    915                                                    mAudioOutputVolume);
    916    }
    917    if (!promises.Length()) {
    918      // Not active track, save it for later
    919      MOZ_ASSERT(mSetAudioDevicePromise.IsEmpty());
    920      return GenericPromise::CreateAndResolve(true, __func__);
    921    }
    922 
    923    // Resolve any existing promise for a previous device so that promises
    924    // resolve in order of setSinkId() invocation.
    925    ResolveAudioDevicePromiseIfExists(__func__);
    926 
    927    RefPtr promise = mSetAudioDevicePromise.Ensure(__func__);
    928    GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
    929        ->Then(GetMainThreadSerialEventTarget(), __func__,
    930               [self = RefPtr{this},
    931                this](const GenericPromise::AllSettledPromiseType::
    932                          ResolveOrRejectValue& aValue) {
    933                 // This handler should have been disconnected if
    934                 // mSetAudioDevicePromise has been settled.
    935                 MOZ_ASSERT(!mSetAudioDevicePromise.IsEmpty());
    936                 mDeviceStartedRequest.Complete();
    937                 // The AudioStreamTrack::AddAudioOutput() promise is rejected
    938                 // either when the graph no longer needs the device, in which
    939                 // case this handler would have already been disconnected, or
    940                 // the graph is force shutdown.
    941                 // mSetAudioDevicePromise is resolved regardless of whether
    942                 // the AddAudioOutput() promises resolve or reject because
    943                 // the underlying device has been changed.
    944                 LOG(LogLevel::Info,
    945                     ("MediaStreamRenderer=%p SetAudioOutputDevice settled",
    946                      this));
    947                 mSetAudioDevicePromise.Resolve(true, __func__);
    948               })
    949        ->Track(mDeviceStartedRequest);
    950 
    951    return promise;
    952  }
    953 
    954  void AddTrack(AudioStreamTrack* aTrack) {
    955    MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks.Contains(aTrack));
    956    mAudioTracks.AppendElement(aTrack);
    957    EnsureGraphTimeDummy();
    958    if (mRendering) {
    959      aTrack->AddAudioOutput(mAudioOutputKey, mAudioOutputSink);
    960      aTrack->SetAudioOutputVolume(mAudioOutputKey, mAudioOutputVolume);
    961    }
    962  }
    963  void AddTrack(VideoStreamTrack* aTrack) {
    964    MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack);
    965    if (!mVideoContainer) {
    966      return;
    967    }
    968    mVideoTrack = aTrack;
    969    EnsureGraphTimeDummy();
    970    if (mFirstFrameVideoOutput) {
    971      // Add the first frame output even if we are rendering. It will only
    972      // accept one frame. If we are rendering, then the main output will
    973      // overwrite that with the same frame (and possibly more frames).
    974      aTrack->AddVideoOutput(mFirstFrameVideoOutput);
    975    }
    976    if (mRendering) {
    977      aTrack->AddVideoOutput(mVideoContainer);
    978    }
    979  }
    980 
    981  void RemoveTrack(AudioStreamTrack* aTrack) {
    982    MOZ_DIAGNOSTIC_ASSERT(mAudioTracks.Contains(aTrack));
    983    if (mRendering) {
    984      aTrack->RemoveAudioOutput(mAudioOutputKey);
    985    }
    986    mAudioTracks.RemoveElement(aTrack);
    987 
    988    if (mAudioTracks.IsEmpty()) {
    989      // There is no longer an audio output that needs the device so the
    990      // device may not start.  Ensure the promise is resolved.
    991      ResolveAudioDevicePromiseIfExists(__func__);
    992    }
    993  }
    994  void RemoveTrack(VideoStreamTrack* aTrack) {
    995    MOZ_DIAGNOSTIC_ASSERT(mVideoTrack == aTrack);
    996    if (!mVideoContainer) {
    997      return;
    998    }
    999    if (mFirstFrameVideoOutput) {
   1000      aTrack->RemoveVideoOutput(mFirstFrameVideoOutput);
   1001    }
   1002    if (mRendering) {
   1003      aTrack->RemoveVideoOutput(mVideoContainer);
   1004    }
   1005    mVideoTrack = nullptr;
   1006  }
   1007 
   1008  double CurrentTime() const {
   1009    if (!mGraphTimeDummy) {
   1010      return 0.0;
   1011    }
   1012 
   1013    return mGraphTimeDummy->mTrack->GraphImpl()->MediaTimeToSeconds(mGraphTime);
   1014  }
   1015 
   1016  Watchable<GraphTime>& CurrentGraphTime() { return mGraphTime; }
   1017 
   1018  // Set if we're rendering video.
   1019  const RefPtr<VideoFrameContainer> mVideoContainer;
   1020 
   1021  // Set if we're rendering audio, nullptr otherwise.
   1022  void* const mAudioOutputKey;
   1023 
   1024 private:
   1025  ~MediaStreamRenderer() { Shutdown(); }
   1026 
   1027  void EnsureGraphTimeDummy() {
   1028    if (mGraphTimeDummy) {
   1029      return;
   1030    }
   1031 
   1032    MediaTrackGraph* graph = nullptr;
   1033    for (const auto& t : mAudioTracks) {
   1034      if (t && !t->Ended()) {
   1035        graph = t->Graph();
   1036        break;
   1037      }
   1038    }
   1039 
   1040    if (!graph && mVideoTrack && !mVideoTrack->Ended()) {
   1041      graph = mVideoTrack->Graph();
   1042    }
   1043 
   1044    if (!graph) {
   1045      return;
   1046    }
   1047 
   1048    // This dummy keeps `graph` alive and ensures access to it.
   1049    mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>(
   1050        graph->CreateSourceTrack(MediaSegment::AUDIO));
   1051  }
   1052 
   1053  void ResolveAudioDevicePromiseIfExists(StaticString aMethodName) {
   1054    if (mSetAudioDevicePromise.IsEmpty()) {
   1055      return;
   1056    }
   1057    LOG(LogLevel::Info,
   1058        ("MediaStreamRenderer=%p resolve audio device promise", this));
   1059    mSetAudioDevicePromise.Resolve(true, aMethodName);
   1060    mDeviceStartedRequest.Disconnect();
   1061  }
   1062 
   1063  // True when all tracks are being rendered, i.e., when the media element is
   1064  // playing.
   1065  bool mRendering = false;
   1066 
   1067  // True while we're progressing mGraphTime. False otherwise.
   1068  bool mProgressingCurrentTime = false;
   1069 
   1070  // The audio output volume for all audio tracks.
   1071  float mAudioOutputVolume = 1.0f;
   1072 
   1073  // The sink device for all audio tracks.
   1074  RefPtr<AudioDeviceInfo> mAudioOutputSink;
   1075  // The promise returned from SetAudioOutputDevice() when an output is
   1076  // active.
   1077  MozPromiseHolder<GenericPromise> mSetAudioDevicePromise;
   1078  // Request tracking the promise to indicate when the device passed to
   1079  // SetAudioOutputDevice() is running.
   1080  MozPromiseRequestHolder<GenericPromise::AllSettledPromiseType>
   1081      mDeviceStartedRequest;
   1082 
   1083  // WatchManager for mGraphTime.
   1084  WatchManager<MediaStreamRenderer> mWatchManager;
   1085 
   1086  // A dummy MediaTrack to guarantee a MediaTrackGraph is kept alive while
   1087  // we're actively rendering, so we can track the graph's current time. Set
   1088  // when the first track is added, never unset.
   1089  RefPtr<SharedDummyTrack> mGraphTimeDummy;
   1090 
   1091  // Watchable that relays the graph's currentTime updates to the media element
   1092  // only while we're rendering. This is the current time of the rendering in
   1093  // GraphTime units.
   1094  Watchable<GraphTime> mGraphTime = {0, "MediaStreamRenderer::mGraphTime"};
   1095 
   1096  // Nothing until a track has been added. Then, the current GraphTime at the
   1097  // time when we were last Start()ed.
   1098  Maybe<GraphTime> mGraphTimeOffset;
   1099 
   1100  // Currently enabled (and rendered) audio tracks.
   1101  nsTArray<WeakPtr<MediaStreamTrack>> mAudioTracks;
   1102 
   1103  // Currently selected (and rendered) video track.
   1104  WeakPtr<MediaStreamTrack> mVideoTrack;
   1105 
   1106  // Holds a reference to the first-frame-getting video output attached to
   1107  // mVideoTrack. Set by the constructor, unset when the media element tells us
   1108  // it has rendered the first frame.
   1109  RefPtr<FirstFrameVideoOutput> mFirstFrameVideoOutput;
   1110 };
   1111 
   1112 static uint32_t sDecoderCaptureSourceId = 0;
   1113 static uint32_t sStreamCaptureSourceId = 0;
   1114 class HTMLMediaElement::MediaElementTrackSource
   1115    : public MediaStreamTrackSource,
   1116      public MediaStreamTrackSource::Sink,
   1117      public MediaStreamTrackConsumer {
   1118 public:
   1119  NS_DECL_ISUPPORTS_INHERITED
   1120  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementTrackSource,
   1121                                           MediaStreamTrackSource)
   1122 
   1123  /* MediaDecoder track source */
   1124  MediaElementTrackSource(HTMLMediaElement* aOwner, ProcessedMediaTrack* aTrack,
   1125                          nsIPrincipal* aPrincipal, OutputMuteState aMuteState,
   1126                          bool aHasAlpha)
   1127      : MediaStreamTrackSource(
   1128            aPrincipal, nsString(),
   1129            TrackingId(TrackingId::Source::MediaElementDecoder,
   1130                       sDecoderCaptureSourceId++,
   1131                       TrackingId::TrackAcrossProcesses::Yes)),
   1132        mOwner(aOwner),
   1133        mTrack(aTrack),
   1134        mIntendedElementMuteState(aMuteState),
   1135        mElementMuteState(aMuteState),
   1136        mMediaDecoderHasAlpha(Some(aHasAlpha)) {
   1137    MOZ_ASSERT(mTrack);
   1138  }
   1139 
   1140  /* MediaStream track source */
   1141  MediaElementTrackSource(HTMLMediaElement* aOwner,
   1142                          MediaStreamTrack* aCapturedTrack,
   1143                          MediaStreamTrackSource* aCapturedTrackSource,
   1144                          ProcessedMediaTrack* aTrack, MediaInputPort* aPort,
   1145                          OutputMuteState aMuteState)
   1146      : MediaStreamTrackSource(
   1147            aCapturedTrackSource->GetPrincipal(), nsString(),
   1148            TrackingId(TrackingId::Source::MediaElementStream,
   1149                       sStreamCaptureSourceId++,
   1150                       TrackingId::TrackAcrossProcesses::Yes)),
   1151        mOwner(aOwner),
   1152        mCapturedTrack(aCapturedTrack),
   1153        mCapturedTrackSource(aCapturedTrackSource),
   1154        mTrack(aTrack),
   1155        mPort(aPort),
   1156        mIntendedElementMuteState(aMuteState),
   1157        mElementMuteState(aMuteState) {
   1158    MOZ_ASSERT(mTrack);
   1159    MOZ_ASSERT(mCapturedTrack);
   1160    MOZ_ASSERT(mCapturedTrackSource);
   1161    MOZ_ASSERT(mPort);
   1162 
   1163    mCapturedTrack->AddConsumer(this);
   1164    mCapturedTrackSource->RegisterSink(this);
   1165  }
   1166 
   1167  void SetEnabled(bool aEnabled) {
   1168    if (!mTrack) {
   1169      return;
   1170    }
   1171    mTrack->SetDisabledTrackMode(aEnabled ? DisabledTrackMode::ENABLED
   1172                                          : DisabledTrackMode::SILENCE_FREEZE);
   1173  }
   1174 
   1175  void SetPrincipal(RefPtr<nsIPrincipal> aPrincipal) {
   1176    mPrincipal = std::move(aPrincipal);
   1177    MediaStreamTrackSource::PrincipalChanged();
   1178  }
   1179 
   1180  void SetMutedByElement(OutputMuteState aMuteState) {
   1181    if (mIntendedElementMuteState == aMuteState) {
   1182      return;
   1183    }
   1184    mIntendedElementMuteState = aMuteState;
   1185    GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
   1186        "MediaElementTrackSource::SetMutedByElement",
   1187        [self = RefPtr<MediaElementTrackSource>(this), this, aMuteState] {
   1188          mElementMuteState = aMuteState;
   1189          MediaStreamTrackSource::MutedChanged(Muted());
   1190        }));
   1191  }
   1192 
   1193  void Destroy() override {
   1194    if (mCapturedTrack) {
   1195      mCapturedTrack->RemoveConsumer(this);
   1196      mCapturedTrack = nullptr;
   1197    }
   1198    if (mCapturedTrackSource) {
   1199      mCapturedTrackSource->UnregisterSink(this);
   1200      mCapturedTrackSource = nullptr;
   1201    }
   1202    if (mTrack && !mTrack->IsDestroyed()) {
   1203      mTrack->Destroy();
   1204    }
   1205    if (mPort) {
   1206      mPort->Destroy();
   1207      mPort = nullptr;
   1208    }
   1209  }
   1210 
   1211  MediaSourceEnum GetMediaSource() const override {
   1212    return MediaSourceEnum::Other;
   1213  }
   1214 
   1215  void Stop() override {
   1216    // Do nothing. There may appear new output streams
   1217    // that need tracks sourced from this source, so we
   1218    // cannot destroy things yet.
   1219  }
   1220 
   1221  /**
   1222   * Do not keep the track source alive. The source lifetime is controlled by
   1223   * its associated tracks.
   1224   */
   1225  bool KeepsSourceAlive() const override { return false; }
   1226 
   1227  /**
   1228   * Do not keep the track source on. It is controlled by its associated tracks.
   1229   */
   1230  bool Enabled() const override { return false; }
   1231 
   1232  void Disable() override {}
   1233 
   1234  void Enable() override {}
   1235 
   1236  void PrincipalChanged() override {
   1237    if (!mCapturedTrackSource) {
   1238      // This could happen during shutdown.
   1239      return;
   1240    }
   1241 
   1242    SetPrincipal(mCapturedTrackSource->GetPrincipal());
   1243  }
   1244 
   1245  void MutedChanged(bool aNewState) override {
   1246    MediaStreamTrackSource::MutedChanged(Muted());
   1247  }
   1248 
   1249  void ConstraintsChanged(const MediaTrackConstraints& aConstraints) override {}
   1250 
   1251  void OverrideEnded() override {
   1252    Destroy();
   1253    MediaStreamTrackSource::OverrideEnded();
   1254  }
   1255 
   1256  void NotifyEnabledChanged(MediaStreamTrack* aTrack, bool aEnabled) override {
   1257    MediaStreamTrackSource::MutedChanged(Muted());
   1258  }
   1259 
   1260  bool Muted() const {
   1261    return mElementMuteState == OutputMuteState::Muted ||
   1262           (mCapturedTrack &&
   1263            (mCapturedTrack->Muted() || !mCapturedTrack->Enabled()));
   1264  }
   1265 
   1266  bool HasAlpha() const override {
   1267    if (mCapturedTrack) {
   1268      return mCapturedTrack->AsVideoStreamTrack()
   1269                 ? mCapturedTrack->AsVideoStreamTrack()->HasAlpha()
   1270                 : false;
   1271    }
   1272    return mMediaDecoderHasAlpha.valueOr(false);
   1273  }
   1274 
   1275  void GetSettings(dom::MediaTrackSettings& aResult) override {
   1276    if (!mOwner) {
   1277      return;
   1278    }
   1279 
   1280    auto* elem = mOwner->AsHTMLVideoElement();
   1281    if (!elem) {
   1282      return;
   1283    }
   1284 
   1285    aResult.mWidth.Construct(elem->VideoWidth());
   1286    aResult.mHeight.Construct(elem->VideoHeight());
   1287  }
   1288 
   1289  ProcessedMediaTrack* Track() const { return mTrack; }
   1290 
   1291 private:
   1292  virtual ~MediaElementTrackSource() { Destroy(); };
   1293 
   1294  WeakPtr<HTMLMediaElement> mOwner;
   1295  RefPtr<MediaStreamTrack> mCapturedTrack;
   1296  RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
   1297  const RefPtr<ProcessedMediaTrack> mTrack;
   1298  RefPtr<MediaInputPort> mPort;
   1299  // The mute state as intended by the media element.
   1300  OutputMuteState mIntendedElementMuteState;
   1301  // The mute state as applied to this track source. It is applied async, so
   1302  // needs to be tracked separately from the intended state.
   1303  OutputMuteState mElementMuteState;
   1304  // Some<bool> if this is a MediaDecoder track source.
   1305  const Maybe<bool> mMediaDecoderHasAlpha;
   1306 };
   1307 
   1308 HTMLMediaElement::OutputMediaStream::OutputMediaStream(
   1309    RefPtr<DOMMediaStream> aStream, bool aCapturingAudioOnly,
   1310    bool aFinishWhenEnded)
   1311    : mStream(std::move(aStream)),
   1312      mCapturingAudioOnly(aCapturingAudioOnly),
   1313      mFinishWhenEnded(aFinishWhenEnded) {}
   1314 HTMLMediaElement::OutputMediaStream::~OutputMediaStream() = default;
   1315 
   1316 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
   1317                                 HTMLMediaElement::OutputMediaStream& aField,
   1318                                 const char* aName, uint32_t aFlags) {
   1319  ImplCycleCollectionTraverse(aCallback, aField.mStream, "mStream", aFlags);
   1320  ImplCycleCollectionTraverse(aCallback, aField.mLiveTracks, "mLiveTracks",
   1321                              aFlags);
   1322  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedLoadingSrc,
   1323                              "mFinishWhenEndedLoadingSrc", aFlags);
   1324  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedAttrStream,
   1325                              "mFinishWhenEndedAttrStream", aFlags);
   1326  ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedMediaSource,
   1327                              "mFinishWhenEndedMediaSource", aFlags);
   1328 }
   1329 
   1330 void ImplCycleCollectionUnlink(HTMLMediaElement::OutputMediaStream& aField) {
   1331  ImplCycleCollectionUnlink(aField.mStream);
   1332  ImplCycleCollectionUnlink(aField.mLiveTracks);
   1333  ImplCycleCollectionUnlink(aField.mFinishWhenEndedLoadingSrc);
   1334  ImplCycleCollectionUnlink(aField.mFinishWhenEndedAttrStream);
   1335  ImplCycleCollectionUnlink(aField.mFinishWhenEndedMediaSource);
   1336 }
   1337 
   1338 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaElementTrackSource,
   1339                         MediaStreamTrackSource)
   1340 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaElementTrackSource,
   1341                          MediaStreamTrackSource)
   1342 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   1343    HTMLMediaElement::MediaElementTrackSource)
   1344 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
   1345 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::MediaElementTrackSource)
   1346 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
   1347    HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
   1348  tmp->Destroy();
   1349  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrack)
   1350  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrackSource)
   1351 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   1352 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
   1353    HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
   1354  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrack)
   1355  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrackSource)
   1356 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   1357 
   1358 /**
   1359 * There is a reference cycle involving this class: MediaLoadListener
   1360 * holds a reference to the HTMLMediaElement, which holds a reference
   1361 * to an nsIChannel, which holds a reference to this listener.
   1362 * We break the reference cycle in OnStartRequest by clearing mElement.
   1363 */
   1364 class HTMLMediaElement::MediaLoadListener final
   1365    : public nsIChannelEventSink,
   1366      public nsIInterfaceRequestor,
   1367      public nsIObserver,
   1368      public nsIThreadRetargetableStreamListener {
   1369  ~MediaLoadListener() = default;
   1370 
   1371  NS_DECL_THREADSAFE_ISUPPORTS
   1372  NS_DECL_NSIREQUESTOBSERVER
   1373  NS_DECL_NSISTREAMLISTENER
   1374  NS_DECL_NSICHANNELEVENTSINK
   1375  NS_DECL_NSIOBSERVER
   1376  NS_DECL_NSIINTERFACEREQUESTOR
   1377  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   1378 
   1379 public:
   1380  explicit MediaLoadListener(HTMLMediaElement* aElement)
   1381      : mElement(aElement), mLoadID(aElement->GetCurrentLoadID()) {
   1382    MOZ_ASSERT(mElement, "Must pass an element to call back");
   1383  }
   1384 
   1385 private:
   1386  RefPtr<HTMLMediaElement> mElement;
   1387  nsCOMPtr<nsIStreamListener> mNextListener;
   1388  const uint32_t mLoadID;
   1389 };
   1390 
   1391 NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver,
   1392                  nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
   1393                  nsIObserver, nsIThreadRetargetableStreamListener)
   1394 
   1395 NS_IMETHODIMP
   1396 HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
   1397                                             const char* aTopic,
   1398                                             const char16_t* aData) {
   1399  nsContentUtils::UnregisterShutdownObserver(this);
   1400 
   1401  // Clear mElement to break cycle so we don't leak on shutdown
   1402  mElement = nullptr;
   1403  return NS_OK;
   1404 }
   1405 
   1406 NS_IMETHODIMP
   1407 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) {
   1408  nsContentUtils::UnregisterShutdownObserver(this);
   1409 
   1410  if (!mElement) {
   1411    // We've been notified by the shutdown observer, and are shutting down.
   1412    return NS_BINDING_ABORTED;
   1413  }
   1414 
   1415  // The element is only needed until we've had a chance to call
   1416  // InitializeDecoderForChannel. So make sure mElement is cleared here.
   1417  RefPtr<HTMLMediaElement> element;
   1418  element.swap(mElement);
   1419 
   1420  if (mLoadID != element->GetCurrentLoadID()) {
   1421    // The channel has been cancelled before we had a chance to create
   1422    // a decoder. Abort, don't dispatch an "error" event, as the new load
   1423    // may not be in an error state.
   1424    return NS_BINDING_ABORTED;
   1425  }
   1426 
   1427  // Don't continue to load if the request failed or has been canceled.
   1428  nsresult status;
   1429  nsresult rv = aRequest->GetStatus(&status);
   1430  NS_ENSURE_SUCCESS(rv, rv);
   1431  if (NS_FAILED(status)) {
   1432    if (element) {
   1433      // Handle media not loading error because source was a tracking URL (or
   1434      // fingerprinting, cryptomining, etc).
   1435      // We make a note of this media node by including it in a dedicated
   1436      // array of blocked tracking nodes under its parent document.
   1437      if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
   1438              status)) {
   1439        element->OwnerDoc()->AddBlockedNodeByClassifier(element);
   1440      }
   1441      element->NotifyLoadError(
   1442          nsPrintfCString("%u: %s", uint32_t(status), "Request failed"));
   1443    }
   1444    return status;
   1445  }
   1446 
   1447  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
   1448  bool succeeded;
   1449  if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
   1450    uint32_t responseStatus = 0;
   1451    (void)hc->GetResponseStatus(&responseStatus);
   1452    nsAutoCString statusText;
   1453    (void)hc->GetResponseStatusText(statusText);
   1454    // we need status text for resist fingerprinting mode's message allowlist
   1455    if (statusText.IsEmpty()) {
   1456      net_GetDefaultStatusTextForCode(responseStatus, statusText);
   1457    }
   1458    element->NotifyLoadError(
   1459        nsPrintfCString("%u: %s", responseStatus, statusText.get()));
   1460 
   1461    nsAutoString code;
   1462    code.AppendInt(responseStatus);
   1463    nsAutoString src;
   1464    element->GetCurrentSrc(src);
   1465    AutoTArray<nsString, 2> params = {code, src};
   1466    element->ReportLoadError("MediaLoadHttpError", params);
   1467    return NS_BINDING_ABORTED;
   1468  }
   1469 
   1470  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   1471  if (channel &&
   1472      NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(
   1473                       channel, getter_AddRefs(mNextListener))) &&
   1474      mNextListener) {
   1475    rv = mNextListener->OnStartRequest(aRequest);
   1476  } else {
   1477    // If InitializeDecoderForChannel() returned an error, fire a network error.
   1478    if (NS_FAILED(rv) && !mNextListener) {
   1479      // Load failed, attempt to load the next candidate resource. If there
   1480      // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
   1481      element->NotifyLoadError("Failed to init decoder"_ns);
   1482    }
   1483    // If InitializeDecoderForChannel did not return a listener (but may
   1484    // have otherwise succeeded), we abort the connection since we aren't
   1485    // interested in keeping the channel alive ourselves.
   1486    rv = NS_BINDING_ABORTED;
   1487  }
   1488 
   1489  return rv;
   1490 }
   1491 
   1492 NS_IMETHODIMP
   1493 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
   1494                                                   nsresult aStatus) {
   1495  if (mNextListener) {
   1496    return mNextListener->OnStopRequest(aRequest, aStatus);
   1497  }
   1498  return NS_OK;
   1499 }
   1500 
   1501 NS_IMETHODIMP
   1502 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
   1503                                                     nsIInputStream* aStream,
   1504                                                     uint64_t aOffset,
   1505                                                     uint32_t aCount) {
   1506  if (!mNextListener) {
   1507    NS_ERROR(
   1508        "Must have a chained listener; OnStartRequest should have "
   1509        "canceled this request");
   1510    return NS_BINDING_ABORTED;
   1511  }
   1512  return mNextListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
   1513 }
   1514 
   1515 NS_IMETHODIMP
   1516 HTMLMediaElement::MediaLoadListener::OnDataFinished(nsresult aStatus) {
   1517  if (!mNextListener) {
   1518    return NS_ERROR_FAILURE;
   1519  }
   1520  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
   1521      do_QueryInterface(mNextListener);
   1522  if (retargetable) {
   1523    return retargetable->OnDataFinished(aStatus);
   1524  }
   1525 
   1526  return NS_OK;
   1527 }
   1528 
   1529 NS_IMETHODIMP
   1530 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
   1531    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
   1532    nsIAsyncVerifyRedirectCallback* cb) {
   1533  // TODO is this really correct?? See bug #579329.
   1534  if (mElement) {
   1535    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
   1536  }
   1537  nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
   1538  if (sink) {
   1539    return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
   1540  }
   1541  cb->OnRedirectVerifyCallback(NS_OK);
   1542  return NS_OK;
   1543 }
   1544 
   1545 NS_IMETHODIMP
   1546 HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
   1547  MOZ_ASSERT(mNextListener);
   1548  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
   1549      do_QueryInterface(mNextListener);
   1550  if (retargetable) {
   1551    return retargetable->CheckListenerChain();
   1552  }
   1553  return NS_ERROR_NO_INTERFACE;
   1554 }
   1555 
   1556 NS_IMETHODIMP
   1557 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
   1558                                                  void** aResult) {
   1559  return QueryInterface(aIID, aResult);
   1560 }
   1561 
   1562 void HTMLMediaElement::ReportLoadError(const char* aMsg,
   1563                                       const nsTArray<nsString>& aParams) {
   1564  ReportToConsole(nsIScriptError::warningFlag, aMsg, aParams);
   1565 }
   1566 
   1567 void HTMLMediaElement::ReportToConsole(
   1568    uint32_t aErrorFlags, const char* aMsg,
   1569    const nsTArray<nsString>& aParams) const {
   1570  nsContentUtils::ReportToConsole(aErrorFlags, "Media"_ns, OwnerDoc(),
   1571                                  nsContentUtils::eDOM_PROPERTIES, aMsg,
   1572                                  aParams);
   1573 }
   1574 
   1575 class HTMLMediaElement::AudioChannelAgentCallback final
   1576    : public nsIAudioChannelAgentCallback {
   1577 public:
   1578  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   1579  NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
   1580 
   1581  explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner)
   1582      : mOwner(aOwner),
   1583        mAudioChannelVolume(1.0),
   1584        mPlayingThroughTheAudioChannel(false),
   1585        mIsOwnerAudible(IsOwnerAudible()),
   1586        mIsShutDown(false) {
   1587    MOZ_ASSERT(mOwner);
   1588    MaybeCreateAudioChannelAgent();
   1589  }
   1590 
   1591  void UpdateAudioChannelPlayingState() {
   1592    MOZ_ASSERT(!mIsShutDown);
   1593    bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
   1594 
   1595    if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
   1596      if (!MaybeCreateAudioChannelAgent()) {
   1597        return;
   1598      }
   1599 
   1600      mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
   1601      if (mPlayingThroughTheAudioChannel) {
   1602        StartAudioChannelAgent();
   1603      } else {
   1604        StopAudioChanelAgent();
   1605      }
   1606    }
   1607  }
   1608 
   1609  void NotifyPlayStateChanged() {
   1610    MOZ_ASSERT(!mIsShutDown);
   1611    UpdateAudioChannelPlayingState();
   1612  }
   1613 
   1614  NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override {
   1615    MOZ_ASSERT(mAudioChannelAgent);
   1616 
   1617    MOZ_LOG(
   1618        AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
   1619        ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
   1620         "this = %p, aVolume = %f, aMuted = %s\n",
   1621         this, aVolume, aMuted ? "true" : "false"));
   1622 
   1623    if (mAudioChannelVolume != aVolume) {
   1624      mAudioChannelVolume = aVolume;
   1625      mOwner->SetVolumeInternal();
   1626    }
   1627 
   1628    const uint32_t muted = mOwner->mMuted;
   1629    if (aMuted && !mOwner->ComputedMuted()) {
   1630      mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
   1631    } else if (!aMuted && mOwner->ComputedMuted()) {
   1632      mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
   1633    }
   1634 
   1635    return NS_OK;
   1636  }
   1637 
   1638  NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override {
   1639    // Currently this method is only be used for delaying autoplay, and we've
   1640    // separated related codes to `MediaPlaybackDelayPolicy`.
   1641    return NS_OK;
   1642  }
   1643 
   1644  NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override {
   1645    MOZ_ASSERT(mAudioChannelAgent);
   1646    AudioCaptureTrackChangeIfNeeded();
   1647    return NS_OK;
   1648  }
   1649 
   1650  void AudioCaptureTrackChangeIfNeeded() {
   1651    MOZ_ASSERT(!mIsShutDown);
   1652    if (!IsPlayingStarted()) {
   1653      return;
   1654    }
   1655 
   1656    MOZ_ASSERT(mAudioChannelAgent);
   1657    bool isCapturing = mAudioChannelAgent->IsWindowAudioCapturingEnabled();
   1658    mOwner->AudioCaptureTrackChange(isCapturing);
   1659  }
   1660 
   1661  void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) {
   1662    MOZ_ASSERT(!mIsShutDown);
   1663    AudibleState newAudibleState = IsOwnerAudible();
   1664    MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
   1665            ("HTMLMediaElement::AudioChannelAgentCallback, "
   1666             "NotifyAudioPlaybackChanged, this=%p, current=%s, new=%s",
   1667             this, AudioChannelService::EnumValueToString(mIsOwnerAudible),
   1668             AudioChannelService::EnumValueToString(newAudibleState)));
   1669    if (mIsOwnerAudible == newAudibleState) {
   1670      return;
   1671    }
   1672 
   1673    mIsOwnerAudible = newAudibleState;
   1674    if (IsPlayingStarted()) {
   1675      mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
   1676    }
   1677  }
   1678 
   1679  void Shutdown() {
   1680    MOZ_ASSERT(!mIsShutDown);
   1681    if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) {
   1682      StopAudioChanelAgent();
   1683    }
   1684    mAudioChannelAgent = nullptr;
   1685    mIsShutDown = true;
   1686  }
   1687 
   1688  float GetEffectiveVolume() const {
   1689    MOZ_ASSERT(!mIsShutDown);
   1690    return static_cast<float>(mOwner->Volume()) * mAudioChannelVolume;
   1691  }
   1692 
   1693 private:
   1694  ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); };
   1695 
   1696  bool MaybeCreateAudioChannelAgent() {
   1697    if (mAudioChannelAgent) {
   1698      return true;
   1699    }
   1700 
   1701    mAudioChannelAgent = new AudioChannelAgent();
   1702    nsresult rv =
   1703        mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this);
   1704    if (NS_WARN_IF(NS_FAILED(rv))) {
   1705      mAudioChannelAgent = nullptr;
   1706      MOZ_LOG(
   1707          AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
   1708          ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
   1709           "the audio channel agent, this = %p\n",
   1710           this));
   1711      return false;
   1712    }
   1713 
   1714    return true;
   1715  }
   1716 
   1717  void StartAudioChannelAgent() {
   1718    MOZ_ASSERT(mAudioChannelAgent);
   1719    MOZ_ASSERT(!mAudioChannelAgent->IsPlayingStarted());
   1720    if (NS_WARN_IF(NS_FAILED(
   1721            mAudioChannelAgent->NotifyStartedPlaying(IsOwnerAudible())))) {
   1722      return;
   1723    }
   1724    mAudioChannelAgent->PullInitialUpdate();
   1725  }
   1726 
   1727  void StopAudioChanelAgent() {
   1728    MOZ_ASSERT(mAudioChannelAgent);
   1729    MOZ_ASSERT(mAudioChannelAgent->IsPlayingStarted());
   1730    mAudioChannelAgent->NotifyStoppedPlaying();
   1731    // If we have started audio capturing before, we have to tell media element
   1732    // to clear the output capturing track.
   1733    mOwner->AudioCaptureTrackChange(false);
   1734  }
   1735 
   1736  bool IsPlayingStarted() {
   1737    if (MaybeCreateAudioChannelAgent()) {
   1738      return mAudioChannelAgent->IsPlayingStarted();
   1739    }
   1740    return false;
   1741  }
   1742 
   1743  AudibleState IsOwnerAudible() const {
   1744    // paused media doesn't produce any sound.
   1745    if (mOwner->mPaused) {
   1746      return AudibleState::eNotAudible;
   1747    }
   1748    return mOwner->IsAudible() ? AudibleState::eAudible
   1749                               : AudibleState::eNotAudible;
   1750  }
   1751 
   1752  bool IsPlayingThroughTheAudioChannel() const {
   1753    // If we have an error, we are not playing.
   1754    if (mOwner->GetError()) {
   1755      return false;
   1756    }
   1757 
   1758    // We should consider any bfcached page or inactive document as non-playing.
   1759    if (!mOwner->OwnerDoc()->IsActive()) {
   1760      return false;
   1761    }
   1762 
   1763    // Media is suspended by the docshell.
   1764    if (mOwner->ShouldBeSuspendedByInactiveDocShell()) {
   1765      return false;
   1766    }
   1767 
   1768    // Are we paused
   1769    if (mOwner->mPaused) {
   1770      return false;
   1771    }
   1772 
   1773    // No audio track
   1774    if (!mOwner->HasAudio()) {
   1775      return false;
   1776    }
   1777 
   1778    // A loop always is playing
   1779    if (mOwner->HasAttr(nsGkAtoms::loop)) {
   1780      return true;
   1781    }
   1782 
   1783    // If we are actually playing...
   1784    if (mOwner->IsCurrentlyPlaying()) {
   1785      return true;
   1786    }
   1787 
   1788    // If we are playing an external stream.
   1789    if (mOwner->mSrcAttrStream) {
   1790      return true;
   1791    }
   1792 
   1793    return false;
   1794  }
   1795 
   1796  RefPtr<AudioChannelAgent> mAudioChannelAgent;
   1797  HTMLMediaElement* mOwner;
   1798 
   1799  // The audio channel volume
   1800  float mAudioChannelVolume;
   1801  // Is this media element playing?
   1802  bool mPlayingThroughTheAudioChannel;
   1803  // Indicate whether media element is audible for users.
   1804  AudibleState mIsOwnerAudible;
   1805  bool mIsShutDown;
   1806 };
   1807 
   1808 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
   1809 
   1810 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   1811    HTMLMediaElement::AudioChannelAgentCallback)
   1812  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
   1813 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   1814 
   1815 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
   1816    HTMLMediaElement::AudioChannelAgentCallback)
   1817  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
   1818 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   1819 
   1820 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   1821    HTMLMediaElement::AudioChannelAgentCallback)
   1822  NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
   1823 NS_INTERFACE_MAP_END
   1824 
   1825 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
   1826 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)
   1827 
   1828 class HTMLMediaElement::ChannelLoader final {
   1829 public:
   1830  NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
   1831 
   1832  explicit ChannelLoader(const JSCallingLocation& aCallingLocation)
   1833      : mCallingLocation(aCallingLocation) {}
   1834 
   1835  void LoadInternal(HTMLMediaElement* aElement) {
   1836    if (mCancelled) {
   1837      return;
   1838    }
   1839 
   1840    JSCallingLocation::AutoFallback fallback(&mCallingLocation);
   1841 
   1842    // determine what security checks need to be performed in AsyncOpen().
   1843    nsSecurityFlags securityFlags =
   1844        aElement->ShouldCheckAllowOrigin()
   1845            ? nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
   1846            : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
   1847 
   1848    if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
   1849      securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
   1850    }
   1851 
   1852    securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
   1853 
   1854    MOZ_ASSERT(
   1855        aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
   1856    nsContentPolicyType contentPolicyType =
   1857        aElement->IsHTMLElement(nsGkAtoms::audio)
   1858            ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
   1859            : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
   1860 
   1861    // If aElement has 'triggeringprincipal' attribute, we will use the value as
   1862    // triggeringPrincipal for the channel, otherwise it will default to use
   1863    // aElement->NodePrincipal().
   1864    // This function returns true when aElement has 'triggeringprincipal', so if
   1865    // setAttrs is true we will override the origin attributes on the channel
   1866    // later.
   1867    nsCOMPtr<nsIPrincipal> triggeringPrincipal;
   1868    bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
   1869        aElement, aElement->mLoadingSrcTriggeringPrincipal,
   1870        getter_AddRefs(triggeringPrincipal));
   1871 
   1872    nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
   1873    nsCOMPtr<nsIChannel> channel;
   1874    nsresult rv = NS_NewChannelWithTriggeringPrincipal(
   1875        getter_AddRefs(channel), aElement->mLoadingSrc,
   1876        static_cast<Element*>(aElement), triggeringPrincipal, securityFlags,
   1877        contentPolicyType,
   1878        nullptr,  // aPerformanceStorage
   1879        loadGroup,
   1880        nullptr,  // aCallbacks
   1881        nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
   1882            nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
   1883            nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
   1884 
   1885    if (NS_FAILED(rv)) {
   1886      // Notify load error so the element will try next resource candidate.
   1887      aElement->NotifyLoadError("Fail to create channel"_ns);
   1888      return;
   1889    }
   1890 
   1891    nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
   1892    if (setAttrs) {
   1893      // The function simply returns NS_OK, so we ignore the return value.
   1894      (void)loadInfo->SetOriginAttributes(
   1895          triggeringPrincipal->OriginAttributesRef());
   1896    }
   1897    loadInfo->SetIsMediaRequest(true);
   1898    loadInfo->SetIsMediaInitialRequest(true);
   1899 
   1900    if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
   1901      nsString initiatorType =
   1902          aElement->IsHTMLElement(nsGkAtoms::audio) ? u"audio"_ns : u"video"_ns;
   1903      timedChannel->SetInitiatorType(initiatorType);
   1904    }
   1905 
   1906    nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
   1907    if (cos) {
   1908      if (aElement->mUseUrgentStartForChannel) {
   1909        cos->AddClassFlags(nsIClassOfService::UrgentStart);
   1910 
   1911        // Reset the flag to avoid loading again without initiated by user
   1912        // interaction.
   1913        aElement->mUseUrgentStartForChannel = false;
   1914      }
   1915 
   1916      // Unconditionally disable throttling since we want the media to fluently
   1917      // play even when we switch the tab to background.
   1918      cos->AddClassFlags(nsIClassOfService::DontThrottle);
   1919    }
   1920 
   1921    // The listener holds a strong reference to us.  This creates a
   1922    // reference cycle, once we've set mChannel, which is manually broken
   1923    // in the listener's OnStartRequest method after it is finished with
   1924    // the element. The cycle will also be broken if we get a shutdown
   1925    // notification before OnStartRequest fires.  Necko guarantees that
   1926    // OnStartRequest will eventually fire if we don't shut down first.
   1927    RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
   1928 
   1929    channel->SetNotificationCallbacks(loadListener);
   1930 
   1931    nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
   1932    if (hc) {
   1933      // Use a byte range request from the start of the resource.
   1934      // This enables us to detect if the stream supports byte range
   1935      // requests, and therefore seeking, early.
   1936      rv = hc->SetRequestHeader("Range"_ns, "bytes=0-"_ns, false);
   1937      MOZ_ASSERT(NS_SUCCEEDED(rv));
   1938      aElement->SetRequestHeaders(hc);
   1939    }
   1940 
   1941    rv = channel->AsyncOpen(loadListener);
   1942    if (NS_FAILED(rv)) {
   1943      // Notify load error so the element will try next resource candidate.
   1944      aElement->NotifyLoadError("Failed to open channel"_ns);
   1945      return;
   1946    }
   1947 
   1948    // Else the channel must be open and starting to download. If it encounters
   1949    // a non-catastrophic failure, it will set a new task to continue loading
   1950    // another candidate.  It's safe to set it as mChannel now.
   1951    mChannel = channel;
   1952 
   1953    // loadListener will be unregistered either on shutdown or when
   1954    // OnStartRequest for the channel we just opened fires.
   1955    nsContentUtils::RegisterShutdownObserver(loadListener);
   1956  }
   1957 
   1958  nsresult Load(HTMLMediaElement* aElement) {
   1959    MOZ_ASSERT(aElement);
   1960    // Per bug 1235183 comment 8, we can't spin the event loop from stable
   1961    // state. Defer NS_NewChannel() to a new regular runnable.
   1962    return aElement->OwnerDoc()->Dispatch(NewRunnableMethod<HTMLMediaElement*>(
   1963        "ChannelLoader::LoadInternal", this, &ChannelLoader::LoadInternal,
   1964        aElement));
   1965  }
   1966 
   1967  void Cancel() {
   1968    mCancelled = true;
   1969    if (mChannel) {
   1970      mChannel->CancelWithReason(NS_BINDING_ABORTED,
   1971                                 "HTMLMediaElement::ChannelLoader::Cancel"_ns);
   1972      mChannel = nullptr;
   1973    }
   1974  }
   1975 
   1976  void Done() {
   1977    MOZ_ASSERT(mChannel);
   1978    // Decoder successfully created, the decoder now owns the MediaResource
   1979    // which owns the channel.
   1980    mChannel = nullptr;
   1981  }
   1982 
   1983  nsresult Redirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
   1984                    uint32_t aFlags) {
   1985    NS_ASSERTION(aChannel == mChannel, "Channels should match!");
   1986    mChannel = aNewChannel;
   1987 
   1988    // Handle forwarding of Range header so that the intial detection
   1989    // of seeking support (via result code 206) works across redirects.
   1990    nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
   1991    NS_ENSURE_STATE(http);
   1992 
   1993    constexpr auto rangeHdr = "Range"_ns;
   1994 
   1995    nsAutoCString rangeVal;
   1996    if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) {
   1997      NS_ENSURE_STATE(!rangeVal.IsEmpty());
   1998 
   1999      http = do_QueryInterface(aNewChannel);
   2000      NS_ENSURE_STATE(http);
   2001 
   2002      nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false);
   2003      NS_ENSURE_SUCCESS(rv, rv);
   2004    }
   2005 
   2006    return NS_OK;
   2007  }
   2008 
   2009 private:
   2010  ~ChannelLoader() { MOZ_ASSERT(!mChannel); }
   2011  // Holds a reference to the first channel we open to the media resource.
   2012  // Once the decoder is created, control over the channel passes to the
   2013  // decoder, and we null out this reference. We must store this in case
   2014  // we need to cancel the channel before control of it passes to the decoder.
   2015  nsCOMPtr<nsIChannel> mChannel;
   2016 
   2017  bool mCancelled = false;
   2018  JSCallingLocation mCallingLocation;
   2019 };
   2020 
   2021 class HTMLMediaElement::ErrorSink {
   2022 public:
   2023  explicit ErrorSink(HTMLMediaElement* aOwner) : mOwner(aOwner) {
   2024    MOZ_ASSERT(mOwner);
   2025  }
   2026 
   2027  void SetError(uint16_t aErrorCode, const Maybe<MediaResult>& aResult) {
   2028    // Since we have multiple paths calling into DecodeError, e.g.
   2029    // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
   2030    // one only in order not to fire multiple 'error' events.
   2031    if (mError) {
   2032      return;
   2033    }
   2034 
   2035    if (!IsValidErrorCode(aErrorCode)) {
   2036      NS_ASSERTION(false, "Undefined MediaError codes!");
   2037      return;
   2038    }
   2039 
   2040    ReportErrorProbe(aErrorCode, aResult);
   2041    if (mOwner->ReadyState() == HAVE_NOTHING &&
   2042        aErrorCode == MEDIA_ERR_ABORTED) {
   2043      // https://html.spec.whatwg.org/multipage/media.html#media-data-processing-steps-list
   2044      // "If the media data fetching process is aborted by the user"
   2045      mOwner->QueueEvent(u"abort"_ns);
   2046      mOwner->ChangeNetworkState(NETWORK_EMPTY);
   2047      mOwner->QueueEvent(u"emptied"_ns);
   2048      if (mOwner->mDecoder) {
   2049        mOwner->ShutdownDecoder();
   2050      }
   2051      return;
   2052    }
   2053 
   2054    if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
   2055      // https://html.spec.whatwg.org/multipage/media.html#dedicated-media-source-failure-steps
   2056      mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
   2057    } else {
   2058      // https://html.spec.whatwg.org/multipage/media.html#media-data-processing-steps-list
   2059      // "If the connection is interrupted after some media data has been
   2060      // received" or "If the media data is corrupted"
   2061      mOwner->ChangeNetworkState(NETWORK_IDLE);
   2062    }
   2063    mError = new MediaError(mOwner, aErrorCode,
   2064                            aResult ? aResult->Message() : nsCString());
   2065    mOwner->QueueEvent(u"error"_ns);
   2066  }
   2067 
   2068  void ResetError() { mError = nullptr; }
   2069 
   2070  RefPtr<MediaError> mError;
   2071 
   2072 private:
   2073  bool IsValidErrorCode(const uint16_t& aErrorCode) const {
   2074    return (aErrorCode == MEDIA_ERR_DECODE || aErrorCode == MEDIA_ERR_NETWORK ||
   2075            aErrorCode == MEDIA_ERR_ABORTED ||
   2076            aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
   2077  }
   2078 
   2079  void ReportErrorProbe(uint16_t aErrorCode,
   2080                        const Maybe<MediaResult>& aResult) {
   2081    MOZ_ASSERT(IsValidErrorCode(aErrorCode));
   2082    auto getErrorType = [&]() {
   2083      if (aErrorCode == MEDIA_ERR_ABORTED) {
   2084        return "AbortError"_ns;
   2085      }
   2086      if (aErrorCode == MEDIA_ERR_NETWORK) {
   2087        return "NetworkError"_ns;
   2088      }
   2089      if (aErrorCode == MEDIA_ERR_DECODE) {
   2090        return "DecodeErr"_ns;
   2091      }
   2092      return "SrcNotSupportedErr"_ns;
   2093    };
   2094 
   2095    glean::media::ErrorExtra extraData;
   2096    extraData.errorType = Some(getErrorType());
   2097    if (aResult) {
   2098      extraData.errorName = Some(aResult->ErrorName());
   2099    }
   2100    nsAutoString keySystem;
   2101    if (mOwner->mMediaKeys) {
   2102      mOwner->mMediaKeys->GetKeySystem(keySystem);
   2103      extraData.keySystem = Some(NS_ConvertUTF16toUTF8(keySystem));
   2104    }
   2105    glean::media::error.Record(Some(extraData));
   2106  }
   2107 
   2108  // Media elememt's life cycle would be longer than error sink, so we use the
   2109  // raw pointer and this class would only be referenced by media element.
   2110  HTMLMediaElement* mOwner;
   2111 };
   2112 
   2113 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
   2114 
   2115 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement,
   2116                                                  nsGenericHTMLElement)
   2117  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStreamWindowCapturer)
   2118  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
   2119  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
   2120  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
   2121  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
   2122  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
   2123  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
   2124  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
   2125  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper)
   2126  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
   2127  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams)
   2128  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputTrackSources);
   2129  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
   2130  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
   2131  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
   2132  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
   2133  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaStreamTrackListener)
   2134  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
   2135  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
   2136  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
   2137  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
   2138  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
   2139  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
   2140  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventBlocker)
   2141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   2142 
   2143 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement,
   2144                                                nsGenericHTMLElement)
   2145  tmp->RemoveMutationObserver(tmp);
   2146  if (tmp->mSrcStream) {
   2147    // Need to unhook everything that EndSrcMediaStreamPlayback would normally
   2148    // do, without creating any new strong references.
   2149    if (tmp->mSelectedVideoStreamTrack) {
   2150      tmp->mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(tmp);
   2151    }
   2152    if (tmp->mMediaStreamRenderer) {
   2153      tmp->mMediaStreamRenderer->Shutdown();
   2154      // We null out mMediaStreamRenderer here since Shutdown() will shut down
   2155      // its WatchManager, and UpdateSrcStreamPotentiallyPlaying() contains a
   2156      // guard for this.
   2157      tmp->mMediaStreamRenderer = nullptr;
   2158    }
   2159    if (tmp->mSecondaryMediaStreamRenderer) {
   2160      tmp->mSecondaryMediaStreamRenderer->Shutdown();
   2161      tmp->mSecondaryMediaStreamRenderer = nullptr;
   2162    }
   2163    if (tmp->mMediaStreamTrackListener) {
   2164      tmp->mSrcStream->UnregisterTrackListener(
   2165          tmp->mMediaStreamTrackListener.get());
   2166    }
   2167  }
   2168  NS_IMPL_CYCLE_COLLECTION_UNLINK(mStreamWindowCapturer)
   2169  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcStream)
   2170  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
   2171  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
   2172  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
   2173  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
   2174  NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
   2175  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
   2176  if (tmp->mAudioChannelWrapper) {
   2177    tmp->mAudioChannelWrapper->Shutdown();
   2178  }
   2179  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
   2180  NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
   2181  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams)
   2182  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputTrackSources)
   2183  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
   2184  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
   2185  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
   2186  NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
   2187  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaStreamTrackListener)
   2188  NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
   2189  NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
   2190  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
   2191  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
   2192  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
   2193  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
   2194  if (tmp->mMediaControlKeyListener) {
   2195    tmp->mMediaControlKeyListener->Shutdown();
   2196  }
   2197  if (tmp->mEventBlocker) {
   2198    tmp->mEventBlocker->Shutdown();
   2199  }
   2200  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
   2201 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   2202 
   2203 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement,
   2204                                               nsGenericHTMLElement)
   2205 
   2206 void HTMLMediaElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
   2207                                              size_t* aNodeSize) const {
   2208  nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize);
   2209 
   2210  // There are many other fields that might be worth reporting, but as seen in
   2211  // bug 1595603, the event we postpone to dispatch can grow to be very large
   2212  // sometimes, so at least report that.
   2213  if (mEventBlocker) {
   2214    *aNodeSize +=
   2215        mEventBlocker->SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
   2216  }
   2217 }
   2218 
   2219 void HTMLMediaElement::ContentWillBeRemoved(nsIContent* aChild,
   2220                                            const ContentRemoveInfo&) {
   2221  if (aChild == mSourcePointer) {
   2222    mSourcePointer = aChild->GetPreviousSibling();
   2223  }
   2224 }
   2225 
   2226 already_AddRefed<MediaSource> HTMLMediaElement::GetMozMediaSourceObject()
   2227    const {
   2228  RefPtr<MediaSource> source = mMediaSource;
   2229  return source.forget();
   2230 }
   2231 
   2232 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugInfo(
   2233    ErrorResult& aRv) {
   2234  RefPtr<Promise> promise = CreateDOMPromise(aRv);
   2235  if (NS_WARN_IF(aRv.Failed())) {
   2236    return nullptr;
   2237  }
   2238  auto result = MakeUnique<dom::HTMLMediaElementDebugInfo>();
   2239  if (mMediaKeys) {
   2240    GetEMEInfo(result->mEMEInfo);
   2241  }
   2242  if (mVideoFrameContainer) {
   2243    result->mCompositorDroppedFrames =
   2244        mVideoFrameContainer->GetDroppedImageCount();
   2245  }
   2246  if (mDecoder) {
   2247    mDecoder->RequestDebugInfo(result->mDecoder)
   2248        ->Then(
   2249            AbstractMainThread(), __func__,
   2250            [promise, ptr = std::move(result)]() {
   2251              promise->MaybeResolve(ptr.get());
   2252            },
   2253            []() {
   2254              MOZ_ASSERT_UNREACHABLE("Unexpected RequestDebugInfo() rejection");
   2255            });
   2256  } else {
   2257    promise->MaybeResolve(result.get());
   2258  }
   2259  return promise.forget();
   2260 }
   2261 
   2262 /* static */
   2263 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject&) {
   2264  DecoderDoctorLogger::EnableLogging();
   2265 }
   2266 
   2267 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugLog(
   2268    ErrorResult& aRv) {
   2269  RefPtr<Promise> promise = CreateDOMPromise(aRv);
   2270  if (NS_WARN_IF(aRv.Failed())) {
   2271    return nullptr;
   2272  }
   2273 
   2274  DecoderDoctorLogger::RetrieveMessages(this)->Then(
   2275      AbstractMainThread(), __func__,
   2276      [promise](const nsACString& aString) {
   2277        promise->MaybeResolve(NS_ConvertUTF8toUTF16(aString));
   2278      },
   2279      [promise](nsresult rv) { promise->MaybeReject(rv); });
   2280 
   2281  return promise.forget();
   2282 }
   2283 
   2284 void HTMLMediaElement::SetVisible(bool aVisible) {
   2285  mForcedHidden = !aVisible;
   2286  if (mDecoder) {
   2287    mDecoder->SetForcedHidden(!aVisible);
   2288  }
   2289 }
   2290 
   2291 bool HTMLMediaElement::IsVideoDecodingSuspended() const {
   2292  return mDecoder && mDecoder->IsVideoDecodingSuspended();
   2293 }
   2294 
   2295 double HTMLMediaElement::TotalVideoPlayTime() const {
   2296  return mDecoder ? mDecoder->GetTotalVideoPlayTimeInSeconds() : -1.0;
   2297 }
   2298 
   2299 double HTMLMediaElement::TotalVideoHDRPlayTime() const {
   2300  return mDecoder ? mDecoder->GetTotalVideoHDRPlayTimeInSeconds() : -1.0;
   2301 }
   2302 
   2303 double HTMLMediaElement::VisiblePlayTime() const {
   2304  return mDecoder ? mDecoder->GetVisibleVideoPlayTimeInSeconds() : -1.0;
   2305 }
   2306 
   2307 double HTMLMediaElement::InvisiblePlayTime() const {
   2308  return mDecoder ? mDecoder->GetInvisibleVideoPlayTimeInSeconds() : -1.0;
   2309 }
   2310 
   2311 double HTMLMediaElement::TotalAudioPlayTime() const {
   2312  return mDecoder ? mDecoder->GetTotalAudioPlayTimeInSeconds() : -1.0;
   2313 }
   2314 
   2315 double HTMLMediaElement::AudiblePlayTime() const {
   2316  return mDecoder ? mDecoder->GetAudiblePlayTimeInSeconds() : -1.0;
   2317 }
   2318 
   2319 double HTMLMediaElement::InaudiblePlayTime() const {
   2320  return mDecoder ? mDecoder->GetInaudiblePlayTimeInSeconds() : -1.0;
   2321 }
   2322 
   2323 double HTMLMediaElement::MutedPlayTime() const {
   2324  return mDecoder ? mDecoder->GetMutedPlayTimeInSeconds() : -1.0;
   2325 }
   2326 
   2327 void HTMLMediaElement::SetFormatDiagnosticsReportForMimeType(
   2328    const nsAString& aMimeType, DecoderDoctorReportType aType) {
   2329  DecoderDoctorDiagnostics diagnostics;
   2330  diagnostics.SetDecoderDoctorReportType(aType);
   2331  diagnostics.StoreFormatDiagnostics(OwnerDoc(), aMimeType, false /* can play*/,
   2332                                     __func__);
   2333 }
   2334 
   2335 void HTMLMediaElement::SetDecodeError(const nsAString& aError,
   2336                                      ErrorResult& aRv) {
   2337  // The reason we use this map-ish structure is because we can't use
   2338  // `CR.NS_ERROR.*` directly in test. In order to use them in test, we have to
   2339  // add them into `xpc.msg`. As we won't use `CR.NS_ERROR.*` in the production
   2340  // code, adding them to `xpc.msg` seems an overdesign and adding maintenance
   2341  // effort (exposing them in CR also needs to add a description, which is
   2342  // useless because we won't show them to users)
   2343  static struct {
   2344    const char* mName;
   2345    nsresult mResult;
   2346  } kSupportedErrorList[] = {
   2347      {"NS_ERROR_DOM_MEDIA_ABORT_ERR", NS_ERROR_DOM_MEDIA_ABORT_ERR},
   2348      {"NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR",
   2349       NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR},
   2350      {"NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR",
   2351       NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR},
   2352      {"NS_ERROR_DOM_MEDIA_DECODE_ERR", NS_ERROR_DOM_MEDIA_DECODE_ERR},
   2353      {"NS_ERROR_DOM_MEDIA_FATAL_ERR", NS_ERROR_DOM_MEDIA_FATAL_ERR},
   2354      {"NS_ERROR_DOM_MEDIA_METADATA_ERR", NS_ERROR_DOM_MEDIA_METADATA_ERR},
   2355      {"NS_ERROR_DOM_MEDIA_OVERFLOW_ERR", NS_ERROR_DOM_MEDIA_OVERFLOW_ERR},
   2356      {"NS_ERROR_DOM_MEDIA_MEDIASINK_ERR", NS_ERROR_DOM_MEDIA_MEDIASINK_ERR},
   2357      {"NS_ERROR_DOM_MEDIA_DEMUXER_ERR", NS_ERROR_DOM_MEDIA_DEMUXER_ERR},
   2358      {"NS_ERROR_DOM_MEDIA_CDM_ERR", NS_ERROR_DOM_MEDIA_CDM_ERR},
   2359      {"NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR",
   2360       NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR}};
   2361  for (auto& error : kSupportedErrorList) {
   2362    if (strcmp(error.mName, NS_ConvertUTF16toUTF8(aError).get()) == 0) {
   2363      DecoderDoctorDiagnostics diagnostics;
   2364      diagnostics.StoreDecodeError(OwnerDoc(), error.mResult, u""_ns, __func__);
   2365      return;
   2366    }
   2367  }
   2368  aRv.Throw(NS_ERROR_FAILURE);
   2369 }
   2370 
   2371 void HTMLMediaElement::SetAudioSinkFailedStartup() {
   2372  DecoderDoctorDiagnostics diagnostics;
   2373  diagnostics.StoreEvent(OwnerDoc(),
   2374                         {DecoderDoctorEvent::eAudioSinkStartup,
   2375                          NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR},
   2376                         __func__);
   2377 }
   2378 
   2379 already_AddRefed<layers::Image> HTMLMediaElement::GetCurrentImage() {
   2380  MarkAsTainted();
   2381 
   2382  // TODO: In bug 1345404, handle case when video decoder is already suspended.
   2383  ImageContainer* container = GetImageContainer();
   2384  if (!container) {
   2385    return nullptr;
   2386  }
   2387 
   2388  AutoLockImage lockImage(container);
   2389  RefPtr<layers::Image> image = lockImage.GetImage(TimeStamp::Now());
   2390  return image.forget();
   2391 }
   2392 
   2393 bool HTMLMediaElement::HasSuspendTaint() const {
   2394  MOZ_ASSERT(!mDecoder || (mDecoder->HasSuspendTaint() == mHasSuspendTaint));
   2395  return mHasSuspendTaint;
   2396 }
   2397 
   2398 already_AddRefed<DOMMediaStream> HTMLMediaElement::GetSrcObject() const {
   2399  return do_AddRef(mSrcAttrStream);
   2400 }
   2401 
   2402 void HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue) {
   2403  SetSrcObject(&aValue);
   2404 }
   2405 
   2406 void HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue) {
   2407  for (auto& outputStream : mOutputStreams) {
   2408    if (aValue == outputStream.mStream) {
   2409      ReportToConsole(nsIScriptError::warningFlag,
   2410                      "MediaElementStreamCaptureCycle");
   2411      return;
   2412    }
   2413  }
   2414  mSrcAttrStream = aValue;
   2415  UpdateAudioChannelPlayingState();
   2416  DoLoad();
   2417 }
   2418 
   2419 bool HTMLMediaElement::Ended() {
   2420  return (mDecoder && mDecoder->IsEnded()) ||
   2421         (mSrcStream && mSrcStreamReportPlaybackEnded);
   2422 }
   2423 
   2424 void HTMLMediaElement::GetCurrentSrc(nsAString& aCurrentSrc) {
   2425  nsAutoCString src;
   2426  GetCurrentSpec(src);
   2427  CopyUTF8toUTF16(src, aCurrentSrc);
   2428 }
   2429 
   2430 nsresult HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel,
   2431                                             nsIChannel* aNewChannel,
   2432                                             uint32_t aFlags) {
   2433  MOZ_ASSERT(mChannelLoader);
   2434  return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
   2435 }
   2436 
   2437 void HTMLMediaElement::ShutdownDecoder() {
   2438  RemoveMediaElementFromURITable();
   2439  NS_ASSERTION(mDecoder, "Must have decoder to shut down");
   2440 
   2441  mWaitingForKeyListener.DisconnectIfExists();
   2442  if (mMediaSource) {
   2443    mMediaSource->CompletePendingTransactions();
   2444  }
   2445  mDecoder->Shutdown();
   2446  DDUNLINKCHILD(mDecoder.get());
   2447  mDecoder = nullptr;
   2448 }
   2449 
   2450 void HTMLMediaElement::AbortExistingLoads() {
   2451  MOZ_ASSERT(NS_IsMainThread());
   2452  LOG(LogLevel::Debug, ("%p Abort existing loads", this));
   2453  // Abort any already-running instance of the resource selection algorithm.
   2454  mLoadWaitStatus = NOT_WAITING;
   2455 
   2456  // Set a new load ID. This will cause events which were enqueued
   2457  // with a different load ID to silently be cancelled.
   2458  mCurrentLoadID++;
   2459 
   2460  // Immediately reject or resolve the already-dispatched
   2461  // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
   2462  // executed again later since the mCurrentLoadID had been changed.
   2463  for (auto& runner : mPendingPlayPromisesRunners) {
   2464    runner->ResolveOrReject();
   2465  }
   2466  mPendingPlayPromisesRunners.Clear();
   2467 
   2468  if (mChannelLoader) {
   2469    mChannelLoader->Cancel();
   2470    mChannelLoader = nullptr;
   2471  }
   2472 
   2473  bool fireTimeUpdate = false;
   2474 
   2475  if (mDecoder) {
   2476    fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
   2477    ShutdownDecoder();
   2478  }
   2479  if (mSrcStream) {
   2480    EndSrcMediaStreamPlayback();
   2481  }
   2482 
   2483  if (mMediaSource) {
   2484    OwnerDoc()->RemoveMediaElementWithMSE();
   2485  }
   2486 
   2487  RemoveMediaElementFromURITable();
   2488  mLoadingSrcTriggeringPrincipal = nullptr;
   2489  DDLOG(DDLogCategory::Property, "loading_src", "");
   2490  DDUNLINKCHILD(mMediaSource.get());
   2491  mMediaSource = nullptr;
   2492 
   2493  if (mNetworkState == NETWORK_LOADING || mNetworkState == NETWORK_IDLE) {
   2494    QueueEvent(u"abort"_ns);
   2495  }
   2496 
   2497  bool hadVideo = HasVideo();
   2498  mErrorSink->ResetError();
   2499  mCurrentPlayRangeStart = -1.0;
   2500  mPlayed = new TimeRanges(ToSupports(OwnerDoc()));
   2501  mLoadedDataFired = false;
   2502  mCanAutoplayFlag = true;
   2503  mIsLoadingFromSourceChildren = false;
   2504  mSuspendedAfterFirstFrame = false;
   2505  mAllowSuspendAfterFirstFrame = true;
   2506  mHaveQueuedSelectResource = false;
   2507  mSuspendedForPreloadNone = false;
   2508  mDownloadSuspendedByCache = false;
   2509  mMediaInfo = MediaInfo();
   2510  mIsEncrypted = false;
   2511  mPendingEncryptedInitData.Reset();
   2512  mWaitingForKey = NOT_WAITING_FOR_KEY;
   2513  mSourcePointer = nullptr;
   2514  mIsBlessed = false;
   2515  SetAudibleState(false);
   2516 
   2517  mTags = nullptr;
   2518 
   2519  if (mNetworkState != NETWORK_EMPTY) {
   2520    NS_ASSERTION(!mDecoder && !mSrcStream,
   2521                 "How did someone setup a new stream/decoder already?");
   2522 
   2523    QueueEvent(u"emptied"_ns);
   2524 
   2525    // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
   2526    // indirectly which depends on mPaused. So we need to update mPaused first.
   2527    if (!mPaused) {
   2528      mPaused = true;
   2529      PlayPromise::RejectPromises(TakePendingPlayPromises(),
   2530                                  NS_ERROR_DOM_MEDIA_ABORT_ERR);
   2531    }
   2532    ChangeNetworkState(NETWORK_EMPTY);
   2533    RemoveMediaTracks();
   2534    UpdateOutputTrackSources();
   2535    ChangeReadyState(HAVE_NOTHING);
   2536 
   2537    // TODO: Apply the rules for text track cue rendering Bug 865407
   2538    if (mTextTrackManager) {
   2539      mTextTrackManager->GetTextTracks()->SetCuesInactive();
   2540    }
   2541 
   2542    if (fireTimeUpdate) {
   2543      // Since we destroyed the decoder above, the current playback position
   2544      // will now be reported as 0. The playback position was non-zero when
   2545      // we destroyed the decoder, so fire a timeupdate event so that the
   2546      // change will be reflected in the controls.
   2547      FireTimeUpdate(TimeupdateType::eMandatory);
   2548    }
   2549    UpdateAudioChannelPlayingState();
   2550  }
   2551 
   2552  if (IsVideo() && hadVideo) {
   2553    // Ensure we render transparent black after resetting video resolution.
   2554    Maybe<nsIntSize> size = Some(nsIntSize(0, 0));
   2555    Invalidate(ImageSizeChanged::Yes, size, ForceInvalidate::No);
   2556  }
   2557 
   2558  // As aborting current load would stop current playback, so we have no need to
   2559  // resume a paused media element.
   2560  ClearResumeDelayedMediaPlaybackAgentIfNeeded();
   2561 
   2562  mMediaControlKeyListener->StopIfNeeded();
   2563 
   2564  // We may have changed mPaused, mCanAutoplayFlag, and other
   2565  // things which can affect AddRemoveSelfReference
   2566  AddRemoveSelfReference();
   2567 
   2568  mIsRunningSelectResource = false;
   2569 
   2570  AssertReadyStateIsNothing();
   2571 }
   2572 
   2573 void HTMLMediaElement::NoSupportedMediaSourceError(
   2574    const nsACString& aErrorDetails) {
   2575  if (mDecoder) {
   2576    ShutdownDecoder();
   2577  }
   2578 
   2579  bool isSameOriginLoad = false;
   2580  nsresult rv = NS_ERROR_NOT_AVAILABLE;
   2581  if (mSrcAttrTriggeringPrincipal && mLoadingSrc) {
   2582    rv = mSrcAttrTriggeringPrincipal->IsSameOrigin(mLoadingSrc,
   2583                                                   &isSameOriginLoad);
   2584  }
   2585 
   2586  if (NS_SUCCEEDED(rv) && !isSameOriginLoad) {
   2587    // aErrorDetails can include sensitive details like MimeType or HTTP Status
   2588    // Code. In case we're loading a 3rd party resource we should not leak this
   2589    // and pass a Generic Error Message
   2590    mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED,
   2591                         Some(MediaResult{NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR,
   2592                                          "Failed to open media"_ns}));
   2593  } else {
   2594    mErrorSink->SetError(
   2595        MEDIA_ERR_SRC_NOT_SUPPORTED,
   2596        Some(MediaResult{NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, aErrorDetails}));
   2597  }
   2598 
   2599  RemoveMediaTracks();
   2600  ChangeDelayLoadStatus(false);
   2601  UpdateAudioChannelPlayingState();
   2602  PlayPromise::RejectPromises(TakePendingPlayPromises(),
   2603                              NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
   2604 }
   2605 
   2606 // Runs a "synchronous section", a function that must run once the event loop
   2607 // has reached a "stable state"
   2608 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
   2609 void HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable) {
   2610  if (mShuttingDown) {
   2611    return;
   2612  }
   2613 
   2614  nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
   2615      "HTMLMediaElement::RunInStableState",
   2616      [self = RefPtr<HTMLMediaElement>(this), loadId = GetCurrentLoadID(),
   2617       runnable = RefPtr<nsIRunnable>(aRunnable)]() {
   2618        if (self->GetCurrentLoadID() != loadId) {
   2619          return;
   2620        }
   2621        runnable->Run();
   2622      });
   2623  nsContentUtils::RunInStableState(task.forget());
   2624 }
   2625 
   2626 void HTMLMediaElement::QueueLoadFromSourceTask() {
   2627  if (!mIsLoadingFromSourceChildren || mShuttingDown) {
   2628    return;
   2629  }
   2630 
   2631  if (mDecoder) {
   2632    // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
   2633    ShutdownDecoder();
   2634    ChangeReadyState(HAVE_NOTHING);
   2635  }
   2636 
   2637  AssertReadyStateIsNothing();
   2638 
   2639  ChangeDelayLoadStatus(true);
   2640  ChangeNetworkState(NETWORK_LOADING);
   2641  RefPtr<Runnable> r = NewRunnableMethod<JSCallingLocation>(
   2642      "HTMLMediaElement::LoadFromSourceChildren", this,
   2643      &HTMLMediaElement::LoadFromSourceChildren, JSCallingLocation::Get());
   2644  RunInStableState(r);
   2645 }
   2646 
   2647 void HTMLMediaElement::QueueSelectResourceTask() {
   2648  // Don't allow multiple async select resource calls to be queued.
   2649  if (mHaveQueuedSelectResource) {
   2650    return;
   2651  }
   2652  mHaveQueuedSelectResource = true;
   2653  ChangeNetworkState(NETWORK_NO_SOURCE);
   2654  RefPtr<Runnable> r = NewRunnableMethod<JSCallingLocation>(
   2655      "HTMLMediaElement::SelectResourceWrapper", this,
   2656      &HTMLMediaElement::SelectResourceWrapper, JSCallingLocation::Get());
   2657  RunInStableState(r);
   2658 }
   2659 
   2660 static bool HasSourceChildren(nsIContent* aElement) {
   2661  for (nsIContent* child = aElement->GetFirstChild(); child;
   2662       child = child->GetNextSibling()) {
   2663    if (child->IsHTMLElement(nsGkAtoms::source)) {
   2664      return true;
   2665    }
   2666  }
   2667  return false;
   2668 }
   2669 
   2670 static nsCString DocumentOrigin(Document* aDoc) {
   2671  if (!aDoc) {
   2672    return "null"_ns;
   2673  }
   2674  nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
   2675  if (!principal) {
   2676    return "null"_ns;
   2677  }
   2678  nsCString origin;
   2679  if (NS_FAILED(principal->GetOrigin(origin))) {
   2680    return "null"_ns;
   2681  }
   2682  return origin;
   2683 }
   2684 
   2685 void HTMLMediaElement::Load() {
   2686  LOG(LogLevel::Debug,
   2687      ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
   2688       "handlingInput=%d hasAutoplayAttr=%d AllowedToPlay=%d "
   2689       "ownerDoc=%p (%s) ownerDocUserActivated=%d "
   2690       "muted=%d volume=%f",
   2691       this, !!mSrcAttrStream, HasAttr(nsGkAtoms::src), HasSourceChildren(this),
   2692       UserActivation::IsHandlingUserInput(), HasAttr(nsGkAtoms::autoplay),
   2693       AllowedToPlay(), OwnerDoc(), DocumentOrigin(OwnerDoc()).get(),
   2694       OwnerDoc()->HasBeenUserGestureActivated(), mMuted, mVolume));
   2695 
   2696  if (mIsRunningLoadMethod) {
   2697    return;
   2698  }
   2699 
   2700  mIsDoingExplicitLoad = true;
   2701  DoLoad();
   2702 }
   2703 
   2704 void HTMLMediaElement::DoLoad() {
   2705  // Check if media is allowed for the docshell.
   2706  nsCOMPtr<nsIDocShell> docShell = OwnerDoc()->GetDocShell();
   2707  if (docShell && !docShell->GetAllowMedia()) {
   2708    LOG(LogLevel::Debug, ("%p Media not allowed", this));
   2709    return;
   2710  }
   2711 
   2712  if (mIsRunningLoadMethod) {
   2713    return;
   2714  }
   2715 
   2716  if (UserActivation::IsHandlingUserInput()) {
   2717    // Detect if user has interacted with element so that play will not be
   2718    // blocked when initiated by a script. This enables sites to capture user
   2719    // intent to play by calling load() in the click handler of a "catalog
   2720    // view" of a gallery of videos.
   2721    mIsBlessed = true;
   2722    // Mark the channel as urgent-start when autoplay so that it will play the
   2723    // media from src after loading enough resource.
   2724    if (HasAttr(nsGkAtoms::autoplay)) {
   2725      mUseUrgentStartForChannel = true;
   2726    }
   2727  }
   2728 
   2729  SetPlayedOrSeeked(false);
   2730  mIsRunningLoadMethod = true;
   2731  AbortExistingLoads();
   2732  SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
   2733  QueueSelectResourceTask();
   2734  ResetState();
   2735  mIsRunningLoadMethod = false;
   2736 }
   2737 
   2738 void HTMLMediaElement::ResetState() {
   2739  // There might be a pending MediaDecoder::PlaybackPositionChanged() which
   2740  // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
   2741  // staled videoWidth and videoHeight. We have to call ForgetElement() here
   2742  // such that the staled callbacks won't reach us.
   2743  if (mVideoFrameContainer) {
   2744    mVideoFrameContainer->ForgetElement();
   2745    mVideoFrameContainer = nullptr;
   2746  }
   2747  if (mMediaStreamRenderer) {
   2748    // mMediaStreamRenderer, has a strong reference to mVideoFrameContainer.
   2749    mMediaStreamRenderer->Shutdown();
   2750    mMediaStreamRenderer = nullptr;
   2751  }
   2752  if (mSecondaryMediaStreamRenderer) {
   2753    // mSecondaryMediaStreamRenderer, has a strong reference to
   2754    // the secondary VideoFrameContainer.
   2755    mSecondaryMediaStreamRenderer->Shutdown();
   2756    mSecondaryMediaStreamRenderer = nullptr;
   2757  }
   2758 }
   2759 
   2760 void HTMLMediaElement::SelectResourceWrapper(
   2761    const JSCallingLocation& aCallingLocation) {
   2762  SelectResource(aCallingLocation);
   2763  MaybeBeginCloningVisually();
   2764  mIsRunningSelectResource = false;
   2765  mHaveQueuedSelectResource = false;
   2766  mIsDoingExplicitLoad = false;
   2767 }
   2768 
   2769 void HTMLMediaElement::SelectResource(
   2770    const JSCallingLocation& aCallingLocation) {
   2771  if (!mSrcAttrStream && !HasAttr(nsGkAtoms::src) && !HasSourceChildren(this)) {
   2772    // The media element has neither a src attribute nor any source
   2773    // element children, abort the load.
   2774    ChangeNetworkState(NETWORK_EMPTY);
   2775    ChangeDelayLoadStatus(false);
   2776    return;
   2777  }
   2778 
   2779  ChangeDelayLoadStatus(true);
   2780 
   2781  ChangeNetworkState(NETWORK_LOADING);
   2782  QueueEvent(u"loadstart"_ns);
   2783 
   2784  // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
   2785  // so that we don't lose our state change by bailing out of the preload
   2786  // state update
   2787  UpdatePreloadAction(aCallingLocation);
   2788  mIsRunningSelectResource = true;
   2789 
   2790  // If we have a 'src' attribute, use that exclusively.
   2791  nsAutoString src;
   2792  if (mSrcAttrStream) {
   2793    SetupSrcMediaStreamPlayback(mSrcAttrStream);
   2794  } else if (GetAttr(nsGkAtoms::src, src)) {
   2795    nsCOMPtr<nsIURI> uri;
   2796    MediaResult rv = NewURIFromString(src, getter_AddRefs(uri));
   2797    if (NS_SUCCEEDED(rv)) {
   2798      LOG(LogLevel::Debug, ("%p Trying load from src=%s", this,
   2799                            NS_ConvertUTF16toUTF8(src).get()));
   2800      if (profiler_is_collecting_markers()) {
   2801        profiler_add_marker("loadresource",
   2802                            geckoprofiler::category::MEDIA_PLAYBACK, {},
   2803                            LoadSourceMarker{}, nsString{src}, nsString{},
   2804                            nsString{}, Flow::FromPointer(this));
   2805      }
   2806 
   2807      NS_ASSERTION(
   2808          !mIsLoadingFromSourceChildren,
   2809          "Should think we're not loading from source children by default");
   2810 
   2811      RemoveMediaElementFromURITable();
   2812      if (!mSrcMediaSource) {
   2813        mLoadingSrc = uri;
   2814      } else {
   2815        mLoadingSrc = nullptr;
   2816      }
   2817      mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal;
   2818      DDLOG(DDLogCategory::Property, "loading_src",
   2819            nsCString(NS_ConvertUTF16toUTF8(src)));
   2820      bool hadMediaSource = !!mMediaSource;
   2821      mMediaSource = mSrcMediaSource;
   2822      if (mMediaSource && !hadMediaSource) {
   2823        OwnerDoc()->AddMediaElementWithMSE();
   2824      }
   2825      DDLINKCHILD("mediasource", mMediaSource.get());
   2826      UpdatePreloadAction(aCallingLocation);
   2827      if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
   2828        // preload:none media, suspend the load here before we make any
   2829        // network requests.
   2830        SuspendLoad();
   2831        return;
   2832      }
   2833 
   2834      rv = LoadResource(aCallingLocation);
   2835      if (NS_SUCCEEDED(rv)) {
   2836        return;
   2837      }
   2838    } else {
   2839      AutoTArray<nsString, 1> params = {src};
   2840      ReportLoadError("MediaLoadInvalidURI", params);
   2841      rv = MediaResult(rv.Code(), "MediaLoadInvalidURI");
   2842    }
   2843    NoSupportedMediaSourceError(rv.Description());
   2844  } else {
   2845    // Otherwise, the source elements will be used.
   2846    mIsLoadingFromSourceChildren = true;
   2847    LoadFromSourceChildren(aCallingLocation);
   2848  }
   2849 }
   2850 
   2851 void HTMLMediaElement::NotifyLoadError(const nsACString& aErrorDetails) {
   2852  if (!mIsLoadingFromSourceChildren) {
   2853    LOG(LogLevel::Debug, ("NotifyLoadError(), no supported media error"));
   2854    NoSupportedMediaSourceError(aErrorDetails);
   2855  } else if (mSourceLoadCandidate) {
   2856    DispatchAsyncSourceError(mSourceLoadCandidate, aErrorDetails);
   2857    QueueLoadFromSourceTask();
   2858  } else {
   2859    NS_WARNING("Should know the source we were loading from!");
   2860  }
   2861  if (profiler_is_collecting_markers()) {
   2862    profiler_add_marker("loaderror", geckoprofiler::category::MEDIA_PLAYBACK,
   2863                        {}, LoadErrorMarker{}, aErrorDetails,
   2864                        Flow::FromPointer(this));
   2865  }
   2866 }
   2867 
   2868 void HTMLMediaElement::NotifyMediaTrackAdded(dom::MediaTrack* aTrack) {
   2869  // The set of tracks changed.
   2870  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   2871 }
   2872 
   2873 void HTMLMediaElement::NotifyMediaTrackRemoved(dom::MediaTrack* aTrack) {
   2874  // The set of tracks changed.
   2875  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   2876 }
   2877 
   2878 void HTMLMediaElement::NotifyMediaTrackEnabled(dom::MediaTrack* aTrack) {
   2879  MOZ_ASSERT(aTrack);
   2880  if (!aTrack) {
   2881    return;
   2882  }
   2883 #ifdef DEBUG
   2884  nsString id;
   2885  aTrack->GetId(id);
   2886 
   2887  LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s enabled", this,
   2888                        aTrack->AsAudioTrack() ? "Audio" : "Video",
   2889                        NS_ConvertUTF16toUTF8(id).get()));
   2890 #endif
   2891 
   2892  MOZ_ASSERT((aTrack->AsAudioTrack() && aTrack->AsAudioTrack()->Enabled()) ||
   2893             (aTrack->AsVideoTrack() && aTrack->AsVideoTrack()->Selected()));
   2894 
   2895  if (aTrack->AsAudioTrack()) {
   2896    SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
   2897  } else if (aTrack->AsVideoTrack()) {
   2898    if (!IsVideo()) {
   2899      MOZ_ASSERT(false);
   2900      return;
   2901    }
   2902    mDisableVideo = false;
   2903  } else {
   2904    MOZ_ASSERT(false, "Unknown track type");
   2905  }
   2906 
   2907  if (mSrcStream) {
   2908    if (AudioTrack* t = aTrack->AsAudioTrack()) {
   2909      if (mMediaStreamRenderer) {
   2910        mMediaStreamRenderer->AddTrack(t->GetAudioStreamTrack());
   2911      }
   2912    } else if (VideoTrack* t = aTrack->AsVideoTrack()) {
   2913      MOZ_ASSERT(!mSelectedVideoStreamTrack);
   2914 
   2915      mSelectedVideoStreamTrack = t->GetVideoStreamTrack();
   2916      mSelectedVideoStreamTrack->AddPrincipalChangeObserver(this);
   2917      if (mMediaStreamRenderer) {
   2918        mMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
   2919      }
   2920      if (mSecondaryMediaStreamRenderer) {
   2921        mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
   2922      }
   2923      if (mMediaInfo.HasVideo()) {
   2924        mMediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha());
   2925      }
   2926      nsContentUtils::CombineResourcePrincipals(
   2927          &mSrcStreamVideoPrincipal, mSelectedVideoStreamTrack->GetPrincipal());
   2928    }
   2929  }
   2930 
   2931  // The set of enabled/selected tracks changed.
   2932  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   2933 }
   2934 
   2935 void HTMLMediaElement::NotifyMediaTrackDisabled(dom::MediaTrack* aTrack) {
   2936  MOZ_ASSERT(aTrack);
   2937  if (!aTrack) {
   2938    return;
   2939  }
   2940 
   2941  nsString id;
   2942  aTrack->GetId(id);
   2943 
   2944  LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s disabled", this,
   2945                        aTrack->AsAudioTrack() ? "Audio" : "Video",
   2946                        NS_ConvertUTF16toUTF8(id).get()));
   2947 
   2948  MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) &&
   2949             (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected()));
   2950 
   2951  if (AudioTrack* t = aTrack->AsAudioTrack()) {
   2952    if (mSrcStream) {
   2953      if (mMediaStreamRenderer) {
   2954        mMediaStreamRenderer->RemoveTrack(t->GetAudioStreamTrack());
   2955      }
   2956    }
   2957    // If we don't have any live tracks, we don't need to mute MediaElement.
   2958    MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
   2959    if (AudioTracks()->Length() > 0) {
   2960      bool shouldMute = true;
   2961      for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
   2962        if ((*AudioTracks())[i]->Enabled()) {
   2963          shouldMute = false;
   2964          break;
   2965        }
   2966      }
   2967 
   2968      if (shouldMute) {
   2969        SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
   2970      }
   2971    }
   2972  } else if (aTrack->AsVideoTrack()) {
   2973    if (mSrcStream) {
   2974      MOZ_DIAGNOSTIC_ASSERT(mSelectedVideoStreamTrack ==
   2975                            aTrack->AsVideoTrack()->GetVideoStreamTrack());
   2976      if (mMediaStreamRenderer) {
   2977        mMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack);
   2978      }
   2979      if (mSecondaryMediaStreamRenderer) {
   2980        mSecondaryMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack);
   2981      }
   2982      mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this);
   2983      mSelectedVideoStreamTrack = nullptr;
   2984    }
   2985  }
   2986 
   2987  // The set of enabled/selected tracks changed.
   2988  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   2989 }
   2990 
   2991 void HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement) {
   2992  if (mShuttingDown) {
   2993    return;
   2994  }
   2995 
   2996  DispatchAsyncSourceError(aSourceElement, "Failed load on source element"_ns);
   2997  GetMainThreadSerialEventTarget()->Dispatch(
   2998      NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask", this,
   2999                        &HTMLMediaElement::QueueLoadFromSourceTask));
   3000 }
   3001 
   3002 void HTMLMediaElement::LoadFromSourceChildren(
   3003    const JSCallingLocation& aCallingLocation) {
   3004  NS_ASSERTION(mDelayingLoadEvent,
   3005               "Should delay load event (if in document) during load");
   3006  NS_ASSERTION(mIsLoadingFromSourceChildren,
   3007               "Must remember we're loading from source children");
   3008 
   3009  AddMutationObserverUnlessExists(this);
   3010 
   3011  RemoveMediaTracks();
   3012 
   3013  while (true) {
   3014    HTMLSourceElement* child = GetNextSource();
   3015    if (!child) {
   3016      // Exhausted candidates, wait for more candidates to be appended to
   3017      // the media element.
   3018      mLoadWaitStatus = WAITING_FOR_SOURCE;
   3019      ChangeNetworkState(NETWORK_NO_SOURCE);
   3020      ChangeDelayLoadStatus(false);
   3021      ReportLoadError("MediaLoadExhaustedCandidates");
   3022      return;
   3023    }
   3024 
   3025    // Must have src attribute.
   3026    nsAutoString src;
   3027    if (!child->GetAttr(nsGkAtoms::src, src)) {
   3028      ReportLoadError("MediaLoadSourceMissingSrc");
   3029      DealWithFailedElement(child);
   3030      return;
   3031    }
   3032 
   3033    // If we have a type attribute, it must be a supported type.
   3034    nsAutoString type;
   3035    if (child->GetAttr(nsGkAtoms::type, type) && !type.IsEmpty()) {
   3036      DecoderDoctorDiagnostics diagnostics;
   3037      CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
   3038      diagnostics.StoreFormatDiagnostics(OwnerDoc(), type,
   3039                                         canPlay != CANPLAY_NO, __func__);
   3040      if (canPlay == CANPLAY_NO) {
   3041        // Check that at least one other source child exists and report that
   3042        // we will try to load that one next.
   3043        nsIContent* nextChild = mSourcePointer->GetNextSibling();
   3044        AutoTArray<nsString, 2> params = {type, src};
   3045 
   3046        while (nextChild) {
   3047          if (nextChild && nextChild->IsHTMLElement(nsGkAtoms::source)) {
   3048            ReportLoadError("MediaLoadUnsupportedTypeAttributeLoadingNextChild",
   3049                            params);
   3050            break;
   3051          }
   3052 
   3053          nextChild = nextChild->GetNextSibling();
   3054        };
   3055 
   3056        if (!nextChild) {
   3057          ReportLoadError("MediaLoadUnsupportedTypeAttribute", params);
   3058        }
   3059 
   3060        DealWithFailedElement(child);
   3061        return;
   3062      }
   3063    }
   3064    nsAutoString media;
   3065    child->GetAttr(nsGkAtoms::media, media);
   3066    HTMLSourceElement* childSrc = HTMLSourceElement::FromNode(child);
   3067    MOZ_ASSERT(childSrc, "Expect child to be HTMLSourceElement");
   3068    if (childSrc && !childSrc->MatchesCurrentMedia()) {
   3069      AutoTArray<nsString, 2> params = {media, src};
   3070      ReportLoadError("MediaLoadSourceMediaNotMatched", params);
   3071      DealWithFailedElement(child);
   3072      LOG(LogLevel::Debug,
   3073          ("%p Media did not match from <source>=%s type=%s media=%s", this,
   3074           NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get(),
   3075           NS_ConvertUTF16toUTF8(media).get()));
   3076      return;
   3077    }
   3078    LOG(LogLevel::Debug,
   3079        ("%p Trying load from <source>=%s type=%s media=%s", this,
   3080         NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get(),
   3081         NS_ConvertUTF16toUTF8(media).get()));
   3082 
   3083    if (profiler_is_collecting_markers()) {
   3084      profiler_add_marker("loadresource",
   3085                          geckoprofiler::category::MEDIA_PLAYBACK, {},
   3086                          LoadSourceMarker{}, nsString{src}, nsString{type},
   3087                          nsString{media}, Flow::FromPointer(this));
   3088    }
   3089 
   3090    nsCOMPtr<nsIURI> uri;
   3091    NewURIFromString(src, getter_AddRefs(uri));
   3092    if (!uri) {
   3093      AutoTArray<nsString, 1> params = {src};
   3094      ReportLoadError("MediaLoadInvalidURI", params);
   3095      DealWithFailedElement(child);
   3096      return;
   3097    }
   3098 
   3099    RemoveMediaElementFromURITable();
   3100    mLoadingSrc = uri;
   3101    mLoadingSrcTriggeringPrincipal = child->GetSrcTriggeringPrincipal();
   3102    DDLOG(DDLogCategory::Property, "loading_src",
   3103          nsCString(NS_ConvertUTF16toUTF8(src)));
   3104    bool hadMediaSource = !!mMediaSource;
   3105    mMediaSource = child->GetSrcMediaSource();
   3106    if (mMediaSource && !hadMediaSource) {
   3107      OwnerDoc()->AddMediaElementWithMSE();
   3108    }
   3109    DDLINKCHILD("mediasource", mMediaSource.get());
   3110    NS_ASSERTION(mNetworkState == NETWORK_LOADING,
   3111                 "Network state should be loading");
   3112 
   3113    if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
   3114      // preload:none media, suspend the load here before we make any
   3115      // network requests.
   3116      SuspendLoad();
   3117      return;
   3118    }
   3119 
   3120    if (NS_SUCCEEDED(LoadResource(aCallingLocation))) {
   3121      return;
   3122    }
   3123 
   3124    // If we fail to load, loop back and try loading the next resource.
   3125    DispatchAsyncSourceError(child, "Failed load on resource"_ns);
   3126  }
   3127  MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
   3128 }
   3129 
   3130 void HTMLMediaElement::SuspendLoad() {
   3131  mSuspendedForPreloadNone = true;
   3132  ChangeNetworkState(NETWORK_IDLE);
   3133  ChangeDelayLoadStatus(false);
   3134 }
   3135 
   3136 void HTMLMediaElement::ResumeLoad(PreloadAction aAction,
   3137                                  const JSCallingLocation& aCallingLocation) {
   3138  NS_ASSERTION(mSuspendedForPreloadNone,
   3139               "Must be halted for preload:none to resume from preload:none "
   3140               "suspended load.");
   3141  mSuspendedForPreloadNone = false;
   3142  mPreloadAction = aAction;
   3143  ChangeDelayLoadStatus(true);
   3144  ChangeNetworkState(NETWORK_LOADING);
   3145  if (!mIsLoadingFromSourceChildren) {
   3146    // We were loading from the element's src attribute.
   3147    MediaResult rv = LoadResource(aCallingLocation);
   3148    if (NS_FAILED(rv)) {
   3149      NoSupportedMediaSourceError(rv.Description());
   3150    }
   3151  } else {
   3152    // We were loading from a child <source> element. Try to resume the
   3153    // load of that child, and if that fails, try the next child.
   3154    if (NS_FAILED(LoadResource(aCallingLocation))) {
   3155      LoadFromSourceChildren(aCallingLocation);
   3156    }
   3157  }
   3158 }
   3159 
   3160 bool HTMLMediaElement::AllowedToPlay() const {
   3161  return media::AutoplayPolicy::IsAllowedToPlay(*this);
   3162 }
   3163 
   3164 uint32_t HTMLMediaElement::GetPreloadDefault() const {
   3165  if (mMediaSource) {
   3166    return HTMLMediaElement::PRELOAD_METADATA;
   3167  }
   3168  if (ShouldResistFingerprinting(RFPTarget::NetworkConnection)) {
   3169    return HTMLMediaElement::PRELOAD_METADATA;
   3170  }
   3171  if (OnCellularConnection()) {
   3172    return Preferences::GetInt("media.preload.default.cellular",
   3173                               HTMLMediaElement::PRELOAD_NONE);
   3174  }
   3175  return Preferences::GetInt("media.preload.default",
   3176                             HTMLMediaElement::PRELOAD_METADATA);
   3177 }
   3178 
   3179 uint32_t HTMLMediaElement::GetPreloadDefaultAuto() const {
   3180  if (ShouldResistFingerprinting(RFPTarget::NetworkConnection)) {
   3181    return HTMLMediaElement::PRELOAD_ENOUGH;
   3182  }
   3183  if (OnCellularConnection()) {
   3184    return Preferences::GetInt("media.preload.auto.cellular",
   3185                               HTMLMediaElement::PRELOAD_METADATA);
   3186  }
   3187  return Preferences::GetInt("media.preload.auto",
   3188                             HTMLMediaElement::PRELOAD_ENOUGH);
   3189 }
   3190 
   3191 void HTMLMediaElement::UpdatePreloadAction(
   3192    const JSCallingLocation& aCallingLocation) {
   3193  PreloadAction nextAction = PRELOAD_UNDEFINED;
   3194  // If autoplay is set, or we're playing, we should always preload data,
   3195  // as we'll need it to play.
   3196  if ((AllowedToPlay() && HasAttr(nsGkAtoms::autoplay)) || !mPaused) {
   3197    nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
   3198  } else {
   3199    // Find the appropriate preload action by looking at the attribute.
   3200    const nsAttrValue* val =
   3201        mAttrs.GetAttr(nsGkAtoms::preload, kNameSpaceID_None);
   3202    // MSE doesn't work if preload is none, so it ignores the pref when src is
   3203    // from MSE.
   3204    uint32_t preloadDefault = GetPreloadDefault();
   3205    uint32_t preloadAuto = GetPreloadDefaultAuto();
   3206    if (!val) {
   3207      // Attribute is not set. Use the preload action specified by the
   3208      // media.preload.default pref, or just preload metadata if not present.
   3209      nextAction = static_cast<PreloadAction>(preloadDefault);
   3210    } else if (val->Type() == nsAttrValue::eEnum) {
   3211      MediaPreloadAttrValue attr =
   3212          static_cast<MediaPreloadAttrValue>(val->GetEnumValue());
   3213      if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_AUTO) {
   3214        nextAction = static_cast<PreloadAction>(preloadAuto);
   3215      } else if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_METADATA) {
   3216        nextAction = HTMLMediaElement::PRELOAD_METADATA;
   3217      } else if (attr == MediaPreloadAttrValue::PRELOAD_ATTR_NONE) {
   3218        nextAction = HTMLMediaElement::PRELOAD_NONE;
   3219      }
   3220    } else {
   3221      // Use the suggested "missing value default" of "metadata", or the value
   3222      // specified by the media.preload.default, if present.
   3223      nextAction = static_cast<PreloadAction>(preloadDefault);
   3224    }
   3225  }
   3226 
   3227  if (nextAction == HTMLMediaElement::PRELOAD_NONE && mIsDoingExplicitLoad) {
   3228    LOG(LogLevel::Debug, ("%p Force to preload metadata when explicit loading "
   3229                          "a preload none element",
   3230                          this));
   3231    nextAction = HTMLMediaElement::PRELOAD_METADATA;
   3232  }
   3233 
   3234  mPreloadAction = nextAction;
   3235  LOG(LogLevel::Debug, ("%p Preload action=%d", this, nextAction));
   3236 
   3237  if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
   3238    if (mSuspendedForPreloadNone) {
   3239      // Our load was previouly suspended due to the media having preload
   3240      // value "none". The preload value has changed to preload:auto, so
   3241      // resume the load.
   3242      ResumeLoad(PRELOAD_ENOUGH, aCallingLocation);
   3243    } else {
   3244      // Preload as much of the video as we can, i.e. don't suspend after
   3245      // the first frame.
   3246      StopSuspendingAfterFirstFrame();
   3247    }
   3248 
   3249  } else if (nextAction == HTMLMediaElement::PRELOAD_METADATA) {
   3250    // If no preload attribute is present but Load() has been explicitly called,
   3251    // assume more data may be needed and avoid suspending the decoder. This is
   3252    // done because some sites may rely on the `canplaythrough` event after
   3253    // calling load(), even though it’s not guaranteed to fire.
   3254    //
   3255    // This behavior can be reconsidered once bug 1969224 is resolved.
   3256    if (!HasAttr(nsGkAtoms::preload) && mIsDoingExplicitLoad) {
   3257      mAllowSuspendAfterFirstFrame = false;
   3258    } else {
   3259      // Ensure that the video can be suspended after first frame.
   3260      mAllowSuspendAfterFirstFrame = true;
   3261    }
   3262    if (mSuspendedForPreloadNone) {
   3263      // Our load was previouly suspended due to the media having preload
   3264      // value "none". The preload value has changed to preload:metadata, so
   3265      // resume the load. We'll pause the load again after we've read the
   3266      // metadata.
   3267      ResumeLoad(PRELOAD_METADATA, aCallingLocation);
   3268    }
   3269  }
   3270 }
   3271 
   3272 MediaResult HTMLMediaElement::LoadResource(
   3273    const JSCallingLocation& aCallingLocation) {
   3274  NS_ASSERTION(mDelayingLoadEvent,
   3275               "Should delay load event (if in document) during load");
   3276 
   3277  if (mChannelLoader) {
   3278    mChannelLoader->Cancel();
   3279    mChannelLoader = nullptr;
   3280  }
   3281 
   3282  // Set the media element's CORS mode only when loading a resource
   3283  mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
   3284 
   3285  HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
   3286  if (other && other->mDecoder) {
   3287    // Clone it.
   3288    // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
   3289    nsresult rv = InitializeDecoderAsClone(
   3290        static_cast<ChannelMediaDecoder*>(other->mDecoder.get()));
   3291    if (NS_SUCCEEDED(rv)) {
   3292      return rv;
   3293    }
   3294  }
   3295 
   3296  LOG(LogLevel::Debug, ("%p LoadResource", this));
   3297  if (mMediaSource) {
   3298    MediaDecoderInit decoderInit(
   3299        this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
   3300        ClampPlaybackRate(mPlaybackRate),
   3301        mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
   3302        HasAttr(nsGkAtoms::loop),
   3303        MediaContainerType(MEDIAMIMETYPE("application/x.mediasource")));
   3304 
   3305    RefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(decoderInit);
   3306    if (!mMediaSource->Attach(decoder)) {
   3307      // TODO: Handle failure: run "If the media data cannot be fetched at
   3308      // all, due to network errors, causing the user agent to give up
   3309      // trying to fetch the resource" section of resource fetch algorithm.
   3310      decoder->Shutdown();
   3311      return MediaResult(NS_ERROR_FAILURE, "Failed to attach MediaSource");
   3312    }
   3313    ChangeDelayLoadStatus(false);
   3314    nsresult rv = decoder->Load(mMediaSource->GetPrincipal());
   3315    if (NS_FAILED(rv)) {
   3316      decoder->Shutdown();
   3317      LOG(LogLevel::Debug,
   3318          ("%p Failed to load for decoder %p", this, decoder.get()));
   3319      return MediaResult(rv, "Fail to load decoder");
   3320    }
   3321    rv = FinishDecoderSetup(decoder);
   3322    return MediaResult(rv, "Failed to set up decoder");
   3323  }
   3324 
   3325  AssertReadyStateIsNothing();
   3326 
   3327  RefPtr<ChannelLoader> loader = new ChannelLoader(aCallingLocation);
   3328  nsresult rv = loader->Load(this);
   3329  if (NS_SUCCEEDED(rv)) {
   3330    mChannelLoader = std::move(loader);
   3331  }
   3332  return MediaResult(rv, "Failed to load channel");
   3333 }
   3334 
   3335 nsresult HTMLMediaElement::LoadWithChannel(nsIChannel* aChannel,
   3336                                           nsIStreamListener** aListener) {
   3337  NS_ENSURE_ARG_POINTER(aChannel);
   3338  NS_ENSURE_ARG_POINTER(aListener);
   3339 
   3340  *aListener = nullptr;
   3341 
   3342  // Make sure we don't reenter during synchronous abort events.
   3343  if (mIsRunningLoadMethod) {
   3344    return NS_OK;
   3345  }
   3346  mIsRunningLoadMethod = true;
   3347  AbortExistingLoads();
   3348  mIsRunningLoadMethod = false;
   3349 
   3350  mLoadingSrcTriggeringPrincipal = nullptr;
   3351  nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc));
   3352  NS_ENSURE_SUCCESS(rv, rv);
   3353 
   3354  ChangeDelayLoadStatus(true);
   3355  rv = InitializeDecoderForChannel(aChannel, aListener);
   3356  if (NS_FAILED(rv)) {
   3357    ChangeDelayLoadStatus(false);
   3358    return rv;
   3359  }
   3360 
   3361  SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
   3362  QueueEvent(u"loadstart"_ns);
   3363 
   3364  return NS_OK;
   3365 }
   3366 
   3367 bool HTMLMediaElement::Seeking() const {
   3368  return mDecoder && mDecoder->IsSeeking();
   3369 }
   3370 
   3371 double HTMLMediaElement::CurrentTime() const {
   3372  if (mMediaStreamRenderer) {
   3373    return ToMicrosecondResolution(mMediaStreamRenderer->CurrentTime());
   3374  }
   3375 
   3376  if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
   3377    return std::clamp(mDecoder->GetCurrentTime(), 0.0, mDecoder->GetDuration());
   3378  }
   3379 
   3380  return mDefaultPlaybackStartPosition;
   3381 }
   3382 
   3383 void HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv) {
   3384  LOG(LogLevel::Debug, ("%p FastSeek(%f) called by JS", this, aTime));
   3385  Seek(aTime, SeekTarget::PrevSyncPoint, IgnoreErrors());
   3386 }
   3387 
   3388 already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) {
   3389  /* This will cause JIT code to be kept around longer, to help performance
   3390   * when using SeekToNextFrame to iterate through every frame of a video.
   3391   */
   3392  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
   3393 
   3394  if (win) {
   3395    if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
   3396      js::NotifyAnimationActivity(obj);
   3397    }
   3398  }
   3399 
   3400  Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
   3401  if (aRv.Failed()) {
   3402    return nullptr;
   3403  }
   3404 
   3405  mSeekDOMPromise = CreateDOMPromise(aRv);
   3406  if (NS_WARN_IF(aRv.Failed())) {
   3407    return nullptr;
   3408  }
   3409 
   3410  return do_AddRef(mSeekDOMPromise);
   3411 }
   3412 
   3413 void HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) {
   3414  LOG(LogLevel::Debug,
   3415      ("%p SetCurrentTime(%lf) called by JS", this, aCurrentTime));
   3416  Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors());
   3417 }
   3418 
   3419 /**
   3420 * Check if aValue is inside a range of aRanges, and if so returns true
   3421 * and puts the range index in aIntervalIndex. If aValue is not
   3422 * inside a range, returns false, and aIntervalIndex
   3423 * is set to the index of the range which starts immediately after aValue
   3424 * (and can be aRanges.Length() if aValue is after the last range).
   3425 */
   3426 static bool IsInRanges(TimeRanges& aRanges, double aValue,
   3427                       uint32_t& aIntervalIndex) {
   3428  uint32_t length = aRanges.Length();
   3429 
   3430  for (uint32_t i = 0; i < length; i++) {
   3431    double start = aRanges.Start(i);
   3432    if (start > aValue) {
   3433      aIntervalIndex = i;
   3434      return false;
   3435    }
   3436    double end = aRanges.End(i);
   3437    if (aValue <= end) {
   3438      aIntervalIndex = i;
   3439      return true;
   3440    }
   3441  }
   3442  aIntervalIndex = length;
   3443  return false;
   3444 }
   3445 
   3446 void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
   3447                            ErrorResult& aRv) {
   3448  // Note: Seek is called both by synchronous code that expects errors thrown in
   3449  // aRv, as well as asynchronous code that expects a promise. Make sure all
   3450  // synchronous errors are returned using aRv, not promise rejections.
   3451 
   3452  // aTime should be non-NaN.
   3453  MOZ_ASSERT(!std::isnan(aTime));
   3454 
   3455  // Seeking step1, Set the media element's show poster flag to false.
   3456  // https://html.spec.whatwg.org/multipage/media.html#dom-media-seek
   3457  mShowPoster = false;
   3458 
   3459  // Detect if user has interacted with element by seeking so that
   3460  // play will not be blocked when initiated by a script.
   3461  if (UserActivation::IsHandlingUserInput()) {
   3462    mIsBlessed = true;
   3463  }
   3464 
   3465  StopSuspendingAfterFirstFrame();
   3466 
   3467  if (mSrcAttrStream) {
   3468    // do nothing since media streams have an empty Seekable range.
   3469    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3470    return;
   3471  }
   3472 
   3473  if (mPlayed && mCurrentPlayRangeStart != -1.0) {
   3474    double rangeEndTime = CurrentTime();
   3475    LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this,
   3476                          mCurrentPlayRangeStart, rangeEndTime));
   3477    // Multiple seek without playing, or seek while playing.
   3478    if (mCurrentPlayRangeStart != rangeEndTime) {
   3479      // Don't round the left of the interval: it comes from script and needs
   3480      // to be exact.
   3481      mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
   3482    }
   3483    // Reset the current played range start time. We'll re-set it once
   3484    // the seek completes.
   3485    mCurrentPlayRangeStart = -1.0;
   3486  }
   3487 
   3488  if (mReadyState == HAVE_NOTHING) {
   3489    mDefaultPlaybackStartPosition = aTime;
   3490    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3491    return;
   3492  }
   3493 
   3494  if (!mDecoder) {
   3495    // mDecoder must always be set in order to reach this point.
   3496    NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
   3497    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3498    return;
   3499  }
   3500 
   3501  // Clamp the seek target to inside the seekable ranges.
   3502  media::TimeRanges seekableRanges = mDecoder->GetSeekableTimeRanges();
   3503  if (seekableRanges.IsInvalid()) {
   3504    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3505    return;
   3506  }
   3507  RefPtr<TimeRanges> seekable =
   3508      new TimeRanges(ToSupports(OwnerDoc()), seekableRanges);
   3509  uint32_t length = seekable->Length();
   3510  if (length == 0) {
   3511    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3512    return;
   3513  }
   3514 
   3515  // If the position we want to seek to is not in a seekable range, we seek
   3516  // to the closest position in the seekable ranges instead. If two positions
   3517  // are equally close, we seek to the closest position from the currentTime.
   3518  // See seeking spec, point 7 :
   3519  // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
   3520  uint32_t range = 0;
   3521  bool isInRange = IsInRanges(*seekable, aTime, range);
   3522  if (!isInRange) {
   3523    if (range == 0) {
   3524      // aTime is before the first range in |seekable|, the closest point we can
   3525      // seek to is the start of the first range.
   3526      aTime = seekable->Start(0);
   3527    } else if (range == length) {
   3528      // Seek target is after the end last range in seekable data.
   3529      // Clamp the seek target to the end of the last seekable range.
   3530      aTime = seekable->End(length - 1);
   3531    } else {
   3532      double leftBound = seekable->End(range - 1);
   3533      double rightBound = seekable->Start(range);
   3534      double distanceLeft = Abs(leftBound - aTime);
   3535      double distanceRight = Abs(rightBound - aTime);
   3536      if (distanceLeft == distanceRight) {
   3537        double currentTime = CurrentTime();
   3538        distanceLeft = Abs(leftBound - currentTime);
   3539        distanceRight = Abs(rightBound - currentTime);
   3540      }
   3541      aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
   3542    }
   3543  }
   3544 
   3545  // TODO: The spec requires us to update the current time to reflect the
   3546  //       actual seek target before beginning the synchronous section, but
   3547  //       that requires changing all MediaDecoderReaders to support telling
   3548  //       us the fastSeek target, and it's currently not possible to get
   3549  //       this information as we don't yet control the demuxer for all
   3550  //       MediaDecoderReaders.
   3551 
   3552  mPlayingBeforeSeek = IsPotentiallyPlaying();
   3553 
   3554  // The media backend is responsible for dispatching the timeupdate
   3555  // event if it changes the playback position as a result of the seek.
   3556  LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
   3557  mDecoder->Seek(aTime, aSeekType);
   3558 
   3559  // We changed whether we're seeking so we need to AddRemoveSelfReference.
   3560  AddRemoveSelfReference();
   3561 
   3562  mMediaControlKeyListener->NotifyMediaPositionState();
   3563 }
   3564 
   3565 double HTMLMediaElement::Duration() const {
   3566  if (mSrcStream) {
   3567    if (mSrcStreamPlaybackEnded) {
   3568      return CurrentTime();
   3569    }
   3570    return std::numeric_limits<double>::infinity();
   3571  }
   3572 
   3573  if (mDecoder) {
   3574    return mDecoder->GetDuration();
   3575  }
   3576 
   3577  return std::numeric_limits<double>::quiet_NaN();
   3578 }
   3579 
   3580 already_AddRefed<TimeRanges> HTMLMediaElement::Seekable() const {
   3581  media::TimeRanges seekable =
   3582      mDecoder ? mDecoder->GetSeekableTimeRanges() : media::TimeRanges();
   3583  RefPtr<TimeRanges> ranges = new TimeRanges(
   3584      ToSupports(OwnerDoc()), seekable.ToMicrosecondResolution());
   3585  return ranges.forget();
   3586 }
   3587 
   3588 already_AddRefed<TimeRanges> HTMLMediaElement::Played() {
   3589  RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
   3590 
   3591  uint32_t timeRangeCount = 0;
   3592  if (mPlayed) {
   3593    timeRangeCount = mPlayed->Length();
   3594  }
   3595  for (uint32_t i = 0; i < timeRangeCount; i++) {
   3596    double begin = mPlayed->Start(i);
   3597    double end = mPlayed->End(i);
   3598    ranges->Add(begin, end);
   3599  }
   3600 
   3601  if (mCurrentPlayRangeStart != -1.0) {
   3602    double now = CurrentTime();
   3603    if (mCurrentPlayRangeStart != now) {
   3604      // Don't round the left of the interval: it comes from script and needs
   3605      // to be exact.
   3606      ranges->Add(mCurrentPlayRangeStart, now);
   3607    }
   3608  }
   3609 
   3610  ranges->Normalize();
   3611  return ranges.forget();
   3612 }
   3613 
   3614 void HTMLMediaElement::Pause(ErrorResult& aRv) {
   3615  LOG(LogLevel::Debug, ("%p Pause() called by JS", this));
   3616  if (mNetworkState == NETWORK_EMPTY) {
   3617    LOG(LogLevel::Debug, ("Loading due to Pause()"));
   3618    DoLoad();
   3619  }
   3620  PauseInternal();
   3621 }
   3622 
   3623 void HTMLMediaElement::PauseInternal() {
   3624  if (mDecoder && mNetworkState != NETWORK_EMPTY) {
   3625    mDecoder->Pause();
   3626  }
   3627  bool oldPaused = mPaused;
   3628  mPaused = true;
   3629  // Step 1,
   3630  // https://html.spec.whatwg.org/multipage/media.html#internal-pause-steps
   3631  mCanAutoplayFlag = false;
   3632  // We changed mPaused and mCanAutoplayFlag which can affect
   3633  // AddRemoveSelfReference
   3634  AddRemoveSelfReference();
   3635  UpdateSrcMediaStreamPlaying();
   3636  if (mAudioChannelWrapper) {
   3637    mAudioChannelWrapper->NotifyPlayStateChanged();
   3638  }
   3639 
   3640  // We don't need to resume media which is paused explicitly by user.
   3641  ClearResumeDelayedMediaPlaybackAgentIfNeeded();
   3642 
   3643  if (!oldPaused) {
   3644    FireTimeUpdate(TimeupdateType::eMandatory);
   3645    QueueEvent(u"pause"_ns);
   3646    AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
   3647  }
   3648 }
   3649 
   3650 void HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv) {
   3651  LOG(LogLevel::Debug, ("%p SetVolume(%f) called by JS", this, aVolume));
   3652 
   3653  if (aVolume < 0.0 || aVolume > 1.0) {
   3654    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
   3655    return;
   3656  }
   3657 
   3658  if (aVolume == mVolume) {
   3659    return;
   3660  }
   3661 
   3662  mVolume = aVolume;
   3663 
   3664  // Here we want just to update the volume.
   3665  SetVolumeInternal();
   3666 
   3667  QueueEvent(u"volumechange"_ns);
   3668 
   3669  // We allow inaudible autoplay. But changing our volume may make this
   3670  // media audible. So pause if we are no longer supposed to be autoplaying.
   3671  PauseIfShouldNotBePlaying();
   3672 }
   3673 
   3674 void HTMLMediaElement::MozGetMetadata(JSContext* aCx,
   3675                                      JS::MutableHandle<JSObject*> aResult,
   3676                                      ErrorResult& aRv) {
   3677  if (mReadyState < HAVE_METADATA) {
   3678    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   3679    return;
   3680  }
   3681 
   3682  JS::Rooted<JSObject*> tags(aCx, JS_NewPlainObject(aCx));
   3683  if (!tags) {
   3684    aRv.Throw(NS_ERROR_FAILURE);
   3685    return;
   3686  }
   3687  if (mTags) {
   3688    for (const auto& entry : *mTags) {
   3689      nsString wideValue;
   3690      CopyUTF8toUTF16(entry.GetData(), wideValue);
   3691      JS::Rooted<JSString*> string(aCx,
   3692                                   JS_NewUCStringCopyZ(aCx, wideValue.Data()));
   3693      if (!string || !JS_DefineProperty(aCx, tags, entry.GetKey().Data(),
   3694                                        string, JSPROP_ENUMERATE)) {
   3695        NS_WARNING("couldn't create metadata object!");
   3696        aRv.Throw(NS_ERROR_FAILURE);
   3697        return;
   3698      }
   3699    }
   3700  }
   3701 
   3702  aResult.set(tags);
   3703 }
   3704 
   3705 void HTMLMediaElement::SetMutedInternal(uint32_t aMuted) {
   3706  uint32_t oldMuted = mMuted;
   3707  mMuted = aMuted;
   3708 
   3709  if (!!aMuted == !!oldMuted) {
   3710    return;
   3711  }
   3712 
   3713  SetVolumeInternal();
   3714 }
   3715 
   3716 void HTMLMediaElement::PauseIfShouldNotBePlaying() {
   3717  if (GetPaused()) {
   3718    return;
   3719  }
   3720  if (!AllowedToPlay()) {
   3721    AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
   3722    ErrorResult rv;
   3723    Pause(rv);
   3724  }
   3725 }
   3726 
   3727 void HTMLMediaElement::SetVolumeInternal() {
   3728  float effectiveVolume = ComputedVolume();
   3729 
   3730  if (mDecoder) {
   3731    mDecoder->SetVolume(effectiveVolume);
   3732  } else if (mMediaStreamRenderer) {
   3733    mMediaStreamRenderer->SetAudioOutputVolume(effectiveVolume);
   3734  }
   3735 
   3736  NotifyAudioPlaybackChanged(
   3737      AudioChannelService::AudibleChangedReasons::eVolumeChanged);
   3738 }
   3739 
   3740 void HTMLMediaElement::SetMuted(bool aMuted) {
   3741  LOG(LogLevel::Debug, ("%p SetMuted(%d) called by JS", this, aMuted));
   3742  if (aMuted == Muted()) {
   3743    return;
   3744  }
   3745 
   3746  if (aMuted) {
   3747    SetMutedInternal(mMuted | MUTED_BY_CONTENT);
   3748  } else {
   3749    SetMutedInternal(mMuted & ~MUTED_BY_CONTENT);
   3750  }
   3751 
   3752  QueueEvent(u"volumechange"_ns);
   3753 
   3754  // We allow inaudible autoplay. But changing our mute status may make this
   3755  // media audible. So pause if we are no longer supposed to be autoplaying.
   3756  PauseIfShouldNotBePlaying();
   3757 }
   3758 
   3759 void HTMLMediaElement::GetAllEnabledMediaTracks(
   3760    nsTArray<RefPtr<MediaTrack>>& aTracks) {
   3761  if (AudioTrackList* tracks = AudioTracks()) {
   3762    for (size_t i = 0; i < tracks->Length(); ++i) {
   3763      AudioTrack* track = (*tracks)[i];
   3764      if (track->Enabled()) {
   3765        aTracks.AppendElement(track);
   3766      }
   3767    }
   3768  }
   3769  if (IsVideo()) {
   3770    if (VideoTrackList* tracks = VideoTracks()) {
   3771      for (size_t i = 0; i < tracks->Length(); ++i) {
   3772        VideoTrack* track = (*tracks)[i];
   3773        if (track->Selected()) {
   3774          aTracks.AppendElement(track);
   3775        }
   3776      }
   3777    }
   3778  }
   3779 }
   3780 
   3781 void HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled) {
   3782  for (const auto& entry : mOutputTrackSources.Values()) {
   3783    entry->SetEnabled(aEnabled);
   3784  }
   3785 }
   3786 
   3787 HTMLMediaElement::OutputMuteState HTMLMediaElement::OutputTracksMuted() {
   3788  return mPaused || mReadyState <= HAVE_CURRENT_DATA ? OutputMuteState::Muted
   3789                                                     : OutputMuteState::Unmuted;
   3790 }
   3791 
   3792 void HTMLMediaElement::UpdateOutputTracksMuting() {
   3793  for (const auto& entry : mOutputTrackSources.Values()) {
   3794    entry->SetMutedByElement(OutputTracksMuted());
   3795  }
   3796 }
   3797 
   3798 void HTMLMediaElement::AddOutputTrackSourceToOutputStream(
   3799    MediaElementTrackSource* aSource, OutputMediaStream& aOutputStream,
   3800    AddTrackMode aMode) {
   3801  if (aOutputStream.mStream == mSrcStream) {
   3802    // Cycle detected. This can happen since tracks are added async.
   3803    // We avoid forwarding it to the output here or we'd get into an infloop.
   3804    LOG(LogLevel::Warning,
   3805        ("NOT adding output track source %p to output stream "
   3806         "%p -- cycle detected",
   3807         aSource, aOutputStream.mStream.get()));
   3808    return;
   3809  }
   3810 
   3811  LOG(LogLevel::Debug, ("Adding output track source %p to output stream %p",
   3812                        aSource, aOutputStream.mStream.get()));
   3813 
   3814  RefPtr<MediaStreamTrack> domTrack;
   3815  if (aSource->Track()->mType == MediaSegment::AUDIO) {
   3816    domTrack = new AudioStreamTrack(
   3817        aOutputStream.mStream->GetOwnerWindow(), aSource->Track(), aSource,
   3818        MediaStreamTrackState::Live, aSource->Muted());
   3819  } else {
   3820    domTrack = new VideoStreamTrack(
   3821        aOutputStream.mStream->GetOwnerWindow(), aSource->Track(), aSource,
   3822        MediaStreamTrackState::Live, aSource->Muted());
   3823  }
   3824 
   3825  aOutputStream.mLiveTracks.AppendElement(domTrack);
   3826 
   3827  switch (aMode) {
   3828    case AddTrackMode::ASYNC:
   3829      GetMainThreadSerialEventTarget()->Dispatch(
   3830          NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
   3831              "DOMMediaStream::AddTrackInternal", aOutputStream.mStream,
   3832              &DOMMediaStream::AddTrackInternal, domTrack));
   3833      break;
   3834    case AddTrackMode::SYNC:
   3835      aOutputStream.mStream->AddTrackInternal(domTrack);
   3836      break;
   3837    default:
   3838      MOZ_CRASH("Unexpected mode");
   3839  }
   3840 
   3841  LOG(LogLevel::Debug,
   3842      ("Created capture %s track %p",
   3843       domTrack->AsAudioStreamTrack() ? "audio" : "video", domTrack.get()));
   3844 }
   3845 
   3846 void HTMLMediaElement::UpdateOutputTrackSources() {
   3847  // This updates the track sources in mOutputTrackSources so they're in sync
   3848  // with the tracks being currently played, and state saying whether we should
   3849  // be capturing tracks. This method is long so here is a breakdown:
   3850  // - Figure out the tracks that should be captured
   3851  // - Diff those against currently captured tracks (mOutputTrackSources), into
   3852  //   tracks-to-add, and tracks-to-remove
   3853  // - Remove the tracks in tracks-to-remove and dispatch "removetrack" and
   3854  //   "ended" events for them
   3855  // - If playback has ended, or there is no longer a media provider object,
   3856  //   remove any OutputMediaStreams that have the finish-when-ended flag set
   3857  // - Create track sources for, and add to OutputMediaStreams, the tracks in
   3858  //   tracks-to-add
   3859 
   3860  const bool shouldHaveTrackSources = mTracksCaptured.Ref() &&
   3861                                      !IsPlaybackEnded() &&
   3862                                      mReadyState >= HAVE_METADATA;
   3863 
   3864  // Add track sources for all enabled/selected MediaTracks.
   3865  nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   3866  if (!window) {
   3867    return;
   3868  }
   3869 
   3870  if (mDecoder) {
   3871    if (!mTracksCaptured.Ref()) {
   3872      mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::None);
   3873    } else if (!AudioTracks() || !VideoTracks() || !shouldHaveTrackSources) {
   3874      // We've been unlinked, or tracks are not yet known.
   3875      mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Halt);
   3876    } else {
   3877      mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Capture,
   3878                                      mTracksCaptured.Ref().get());
   3879    }
   3880  }
   3881 
   3882  // Start with all MediaTracks
   3883  AutoTArray<RefPtr<MediaTrack>, 4> mediaTracksToAdd;
   3884  if (shouldHaveTrackSources) {
   3885    GetAllEnabledMediaTracks(mediaTracksToAdd);
   3886  }
   3887 
   3888  // ...and all MediaElementTrackSources.
   3889  auto trackSourcesToRemove =
   3890      ToTArray<AutoTArray<nsString, 4>>(mOutputTrackSources.Keys());
   3891 
   3892  // Then work out the differences.
   3893  mediaTracksToAdd.RemoveLastElements(
   3894      mediaTracksToAdd.end() -
   3895      std::remove_if(mediaTracksToAdd.begin(), mediaTracksToAdd.end(),
   3896                     [this, &trackSourcesToRemove](const auto& track) {
   3897                       const bool remove =
   3898                           mOutputTrackSources.GetWeak(track->GetId());
   3899                       if (remove) {
   3900                         trackSourcesToRemove.RemoveElement(track->GetId());
   3901                       }
   3902                       return remove;
   3903                     }));
   3904 
   3905  // First remove stale track sources.
   3906  for (const auto& id : trackSourcesToRemove) {
   3907    RefPtr<MediaElementTrackSource> source = mOutputTrackSources.GetWeak(id);
   3908 
   3909    LOG(LogLevel::Debug, ("Removing output track source %p for track %s",
   3910                          source.get(), NS_ConvertUTF16toUTF8(id).get()));
   3911 
   3912    if (mDecoder) {
   3913      mDecoder->RemoveOutputTrack(source->Track());
   3914    }
   3915 
   3916    // The source of this track just ended. Force-notify that it ended.
   3917    // If we bounce it to the MediaTrackGraph it might not be picked up,
   3918    // for instance if the MediaInputPort was destroyed in the same
   3919    // iteration as it was added.
   3920    GetMainThreadSerialEventTarget()->Dispatch(
   3921        NewRunnableMethod("MediaElementTrackSource::OverrideEnded", source,
   3922                          &MediaElementTrackSource::OverrideEnded));
   3923 
   3924    // Remove the track from the MediaStream after it ended.
   3925    for (OutputMediaStream& ms : mOutputStreams) {
   3926      if (source->Track()->mType == MediaSegment::VIDEO &&
   3927          ms.mCapturingAudioOnly) {
   3928        continue;
   3929      }
   3930      DebugOnly<size_t> length = ms.mLiveTracks.Length();
   3931      ms.mLiveTracks.RemoveElementsBy(
   3932          [&](const RefPtr<MediaStreamTrack>& aTrack) {
   3933            if (&aTrack->GetSource() != source) {
   3934              return false;
   3935            }
   3936            GetMainThreadSerialEventTarget()->Dispatch(
   3937                NewRunnableMethod<RefPtr<MediaStreamTrack>>(
   3938                    "DOMMediaStream::RemoveTrackInternal", ms.mStream,
   3939                    &DOMMediaStream::RemoveTrackInternal, aTrack));
   3940            return true;
   3941          });
   3942      MOZ_ASSERT(ms.mLiveTracks.Length() == length - 1);
   3943    }
   3944 
   3945    mOutputTrackSources.Remove(id);
   3946  }
   3947 
   3948  // Then update finish-when-ended output streams as needed.
   3949  for (size_t i = mOutputStreams.Length(); i-- > 0;) {
   3950    if (!mOutputStreams[i].mFinishWhenEnded) {
   3951      continue;
   3952    }
   3953 
   3954    if (!mOutputStreams[i].mFinishWhenEndedLoadingSrc &&
   3955        !mOutputStreams[i].mFinishWhenEndedAttrStream &&
   3956        !mOutputStreams[i].mFinishWhenEndedMediaSource) {
   3957      // This finish-when-ended stream has not seen any source loaded yet.
   3958      // Update the loading src if it's time.
   3959      if (!IsPlaybackEnded()) {
   3960        if (mLoadingSrc) {
   3961          mOutputStreams[i].mFinishWhenEndedLoadingSrc = mLoadingSrc;
   3962        } else if (mSrcAttrStream) {
   3963          mOutputStreams[i].mFinishWhenEndedAttrStream = mSrcAttrStream;
   3964        } else if (mSrcMediaSource) {
   3965          mOutputStreams[i].mFinishWhenEndedMediaSource = mSrcMediaSource;
   3966        }
   3967      }
   3968      continue;
   3969    }
   3970 
   3971    // Discard finish-when-ended output streams with a loading src set as
   3972    // needed.
   3973    if (!IsPlaybackEnded() &&
   3974        mLoadingSrc == mOutputStreams[i].mFinishWhenEndedLoadingSrc) {
   3975      continue;
   3976    }
   3977    if (!IsPlaybackEnded() &&
   3978        mSrcAttrStream == mOutputStreams[i].mFinishWhenEndedAttrStream) {
   3979      continue;
   3980    }
   3981    if (!IsPlaybackEnded() &&
   3982        mSrcMediaSource == mOutputStreams[i].mFinishWhenEndedMediaSource) {
   3983      continue;
   3984    }
   3985    LOG(LogLevel::Debug,
   3986        ("Playback ended or source changed. Discarding stream %p",
   3987         mOutputStreams[i].mStream.get()));
   3988    mOutputStreams.RemoveElementAt(i);
   3989    if (mOutputStreams.IsEmpty()) {
   3990      mTracksCaptured = nullptr;
   3991      // mTracksCaptured is one of the Watchables triggering this method.
   3992      // Unsetting it here means we'll run through this method again very soon.
   3993      return;
   3994    }
   3995  }
   3996 
   3997  // Finally add new MediaTracks.
   3998  for (const auto& mediaTrack : mediaTracksToAdd) {
   3999    nsAutoString id;
   4000    mediaTrack->GetId(id);
   4001 
   4002    MediaSegment::Type type;
   4003    if (mediaTrack->AsAudioTrack()) {
   4004      type = MediaSegment::AUDIO;
   4005    } else if (mediaTrack->AsVideoTrack()) {
   4006      type = MediaSegment::VIDEO;
   4007    } else {
   4008      MOZ_CRASH("Unknown track type");
   4009    }
   4010 
   4011    RefPtr<ProcessedMediaTrack> track;
   4012    RefPtr<MediaElementTrackSource> source;
   4013    if (mDecoder) {
   4014      track = mTracksCaptured.Ref()->mTrack->Graph()->CreateForwardedInputTrack(
   4015          type);
   4016      RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   4017      if (!principal || IsCORSSameOrigin()) {
   4018        principal = NodePrincipal();
   4019      }
   4020      source = MakeAndAddRef<MediaElementTrackSource>(
   4021          this, track, principal, OutputTracksMuted(),
   4022          type == MediaSegment::VIDEO
   4023              ? HTMLVideoElement::FromNode(this)->HasAlpha()
   4024              : false);
   4025      mDecoder->AddOutputTrack(track);
   4026    } else if (mSrcStream) {
   4027      MediaStreamTrack* inputTrack;
   4028      if (AudioTrack* t = mediaTrack->AsAudioTrack()) {
   4029        inputTrack = t->GetAudioStreamTrack();
   4030      } else if (VideoTrack* t = mediaTrack->AsVideoTrack()) {
   4031        inputTrack = t->GetVideoStreamTrack();
   4032      } else {
   4033        MOZ_CRASH("Unknown track type");
   4034      }
   4035      MOZ_ASSERT(inputTrack);
   4036      if (!inputTrack) {
   4037        NS_ERROR("Input track not found in source stream");
   4038        return;
   4039      }
   4040      MOZ_DIAGNOSTIC_ASSERT(!inputTrack->Ended());
   4041 
   4042      track = inputTrack->Graph()->CreateForwardedInputTrack(type);
   4043      RefPtr<MediaInputPort> port = inputTrack->ForwardTrackContentsTo(track);
   4044      source = MakeAndAddRef<MediaElementTrackSource>(
   4045          this, inputTrack, &inputTrack->GetSource(), track, port,
   4046          OutputTracksMuted());
   4047 
   4048      // Track is muted initially, so we don't leak data if it's added while
   4049      // paused and an MTG iteration passes before the mute comes into effect.
   4050      source->SetEnabled(mSrcStreamIsPlaying);
   4051    } else {
   4052      MOZ_CRASH("Unknown source");
   4053    }
   4054 
   4055    LOG(LogLevel::Debug, ("Adding output track source %p for track %s",
   4056                          source.get(), NS_ConvertUTF16toUTF8(id).get()));
   4057 
   4058    track->QueueSetAutoend(false);
   4059    MOZ_DIAGNOSTIC_ASSERT(!mOutputTrackSources.Contains(id));
   4060    mOutputTrackSources.InsertOrUpdate(id, RefPtr{source});
   4061 
   4062    // Add the new track source to any existing output streams
   4063    for (OutputMediaStream& ms : mOutputStreams) {
   4064      if (source->Track()->mType == MediaSegment::VIDEO &&
   4065          ms.mCapturingAudioOnly) {
   4066        // If the output stream is for audio only we ignore video sources.
   4067        continue;
   4068      }
   4069      AddOutputTrackSourceToOutputStream(source, ms);
   4070    }
   4071  }
   4072 }
   4073 
   4074 bool HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType) {
   4075  // Don't bother capturing when the document has gone away
   4076  nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   4077  if (!window) {
   4078    return false;
   4079  }
   4080 
   4081  // Prevent capturing restricted video
   4082  if (aCaptureType == StreamCaptureType::CAPTURE_ALL_TRACKS &&
   4083      ContainsRestrictedContent()) {
   4084    return false;
   4085  }
   4086  return true;
   4087 }
   4088 
   4089 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureStreamInternal(
   4090    StreamCaptureBehavior aFinishBehavior, StreamCaptureType aStreamCaptureType,
   4091    MediaTrackGraph* aGraph) {
   4092  MOZ_ASSERT(CanBeCaptured(aStreamCaptureType));
   4093 
   4094  LogVisibility(CallerAPI::CAPTURE_STREAM);
   4095  MarkAsTainted();
   4096 
   4097  if (mTracksCaptured.Ref()) {
   4098    // Already have an output stream.  Check whether the graph rate matches if
   4099    // specified.
   4100    if (aGraph && aGraph != mTracksCaptured.Ref()->mTrack->Graph()) {
   4101      return nullptr;
   4102    }
   4103  } else {
   4104    // This is the first output stream, or there are no tracks. If the former,
   4105    // start capturing all tracks. If the latter, they will be added later.
   4106    MediaTrackGraph* graph = aGraph;
   4107    if (!graph) {
   4108      nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   4109      if (!window) {
   4110        return nullptr;
   4111      }
   4112 
   4113      MediaTrackGraph::GraphDriverType graphDriverType =
   4114          HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER
   4115                     : MediaTrackGraph::SYSTEM_THREAD_DRIVER;
   4116      graph = MediaTrackGraph::GetInstance(
   4117          graphDriverType, window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
   4118          MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
   4119    }
   4120    mTracksCaptured = MakeRefPtr<SharedDummyTrack>(
   4121        graph->CreateSourceTrack(MediaSegment::AUDIO));
   4122    UpdateOutputTrackSources();
   4123  }
   4124 
   4125  nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   4126  OutputMediaStream* out = mOutputStreams.EmplaceBack(
   4127      MakeRefPtr<DOMMediaStream>(window),
   4128      aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO,
   4129      aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED);
   4130 
   4131  if (aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED &&
   4132      !mOutputTrackSources.IsEmpty()) {
   4133    // This output stream won't receive any more tracks when playback of the
   4134    // current src of this media element ends, or when the src of this media
   4135    // element changes. If we're currently playing something (i.e., if there are
   4136    // tracks currently captured), set the current src on the output stream so
   4137    // this can be tracked. If we're not playing anything,
   4138    // UpdateOutputTrackSources will set the current src when it becomes
   4139    // available later.
   4140    if (mLoadingSrc) {
   4141      out->mFinishWhenEndedLoadingSrc = mLoadingSrc;
   4142    }
   4143    if (mSrcAttrStream) {
   4144      out->mFinishWhenEndedAttrStream = mSrcAttrStream;
   4145    }
   4146    if (mSrcMediaSource) {
   4147      out->mFinishWhenEndedMediaSource = mSrcMediaSource;
   4148    }
   4149    MOZ_ASSERT(out->mFinishWhenEndedLoadingSrc ||
   4150               out->mFinishWhenEndedAttrStream ||
   4151               out->mFinishWhenEndedMediaSource);
   4152  }
   4153 
   4154  if (aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO) {
   4155    if (mSrcStream) {
   4156      // We don't support applying volume and mute to the captured stream, when
   4157      // capturing a MediaStream.
   4158      ReportToConsole(nsIScriptError::errorFlag,
   4159                      "MediaElementAudioCaptureOfMediaStreamError");
   4160    }
   4161 
   4162    // mAudioCaptured tells the user that the audio played by this media element
   4163    // is being routed to the captureStreams *instead* of being played to
   4164    // speakers.
   4165    mAudioCaptured = true;
   4166  }
   4167 
   4168  for (const RefPtr<MediaElementTrackSource>& source :
   4169       mOutputTrackSources.Values()) {
   4170    if (source->Track()->mType == MediaSegment::VIDEO) {
   4171      // Only add video tracks if we're a video element and the output stream
   4172      // wants video.
   4173      if (!IsVideo()) {
   4174        continue;
   4175      }
   4176      if (out->mCapturingAudioOnly) {
   4177        continue;
   4178      }
   4179    }
   4180    AddOutputTrackSourceToOutputStream(source, *out, AddTrackMode::SYNC);
   4181  }
   4182 
   4183  return do_AddRef(out->mStream);
   4184 }
   4185 
   4186 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureAudio(
   4187    ErrorResult& aRv, MediaTrackGraph* aGraph) {
   4188  MOZ_RELEASE_ASSERT(aGraph);
   4189 
   4190  if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO)) {
   4191    aRv.Throw(NS_ERROR_FAILURE);
   4192    return nullptr;
   4193  }
   4194 
   4195  RefPtr<DOMMediaStream> stream =
   4196      CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
   4197                            StreamCaptureType::CAPTURE_AUDIO, aGraph);
   4198  if (!stream) {
   4199    aRv.Throw(NS_ERROR_FAILURE);
   4200    return nullptr;
   4201  }
   4202 
   4203  return stream.forget();
   4204 }
   4205 
   4206 RefPtr<GenericNonExclusivePromise> HTMLMediaElement::GetAllowedToPlayPromise() {
   4207  MOZ_ASSERT(NS_IsMainThread());
   4208  MOZ_ASSERT(!mOutputStreams.IsEmpty(),
   4209             "This method should only be called during stream capturing!");
   4210  if (AllowedToPlay()) {
   4211    AUTOPLAY_LOG("MediaElement %p has allowed to play, resolve promise", this);
   4212    return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
   4213  }
   4214  AUTOPLAY_LOG("create allow-to-play promise for MediaElement %p", this);
   4215  return mAllowedToPlayPromise.Ensure(__func__);
   4216 }
   4217 
   4218 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStream(
   4219    ErrorResult& aRv) {
   4220  if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
   4221    aRv.Throw(NS_ERROR_FAILURE);
   4222    return nullptr;
   4223  }
   4224 
   4225  RefPtr<DOMMediaStream> stream =
   4226      CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
   4227                            StreamCaptureType::CAPTURE_ALL_TRACKS, nullptr);
   4228  if (!stream) {
   4229    aRv.Throw(NS_ERROR_FAILURE);
   4230    return nullptr;
   4231  }
   4232 
   4233  return stream.forget();
   4234 }
   4235 
   4236 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStreamUntilEnded(
   4237    ErrorResult& aRv) {
   4238  if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
   4239    aRv.Throw(NS_ERROR_FAILURE);
   4240    return nullptr;
   4241  }
   4242 
   4243  RefPtr<DOMMediaStream> stream =
   4244      CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED,
   4245                            StreamCaptureType::CAPTURE_ALL_TRACKS, nullptr);
   4246  if (!stream) {
   4247    aRv.Throw(NS_ERROR_FAILURE);
   4248    return nullptr;
   4249  }
   4250 
   4251  return stream.forget();
   4252 }
   4253 
   4254 class MediaElementSetForURI : public nsURIHashKey {
   4255 public:
   4256  explicit MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {}
   4257  MediaElementSetForURI(MediaElementSetForURI&& aOther) noexcept
   4258      : nsURIHashKey(std::move(aOther)),
   4259        mElements(std::move(aOther.mElements)) {}
   4260  nsTArray<HTMLMediaElement*> mElements;
   4261 };
   4262 
   4263 using MediaElementURITable = nsTHashtable<MediaElementSetForURI>;
   4264 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
   4265 // can't change while the element is in the table. The table is keyed by
   4266 // the element's mLoadingSrc. Each entry has a list of all elements with the
   4267 // same mLoadingSrc.
   4268 static MediaElementURITable* gElementTable;
   4269 
   4270 #ifdef DEBUG
   4271 static bool URISafeEquals(nsIURI* a1, nsIURI* a2) {
   4272  if (!a1 || !a2) {
   4273    // Consider two empty URIs *not* equal!
   4274    return false;
   4275  }
   4276  bool equal = false;
   4277  nsresult rv = a1->Equals(a2, &equal);
   4278  return NS_SUCCEEDED(rv) && equal;
   4279 }
   4280 // Returns the number of times aElement appears in the media element table
   4281 // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
   4282 static unsigned MediaElementTableCount(HTMLMediaElement* aElement,
   4283                                       nsIURI* aURI) {
   4284  if (!gElementTable || !aElement) {
   4285    return 0;
   4286  }
   4287  uint32_t uriCount = 0;
   4288  uint32_t otherCount = 0;
   4289  for (const auto& entry : *gElementTable) {
   4290    uint32_t count = 0;
   4291    for (const auto& elem : entry.mElements) {
   4292      if (elem == aElement) {
   4293        count++;
   4294      }
   4295    }
   4296    if (URISafeEquals(aURI, entry.GetKey())) {
   4297      uriCount = count;
   4298    } else {
   4299      otherCount += count;
   4300    }
   4301  }
   4302  NS_ASSERTION(otherCount == 0, "Should not have entries for unknown URIs");
   4303  return uriCount;
   4304 }
   4305 #endif
   4306 
   4307 void HTMLMediaElement::AddMediaElementToURITable() {
   4308  NS_ASSERTION(mDecoder, "Call this only with decoder Load called");
   4309  NS_ASSERTION(
   4310      MediaElementTableCount(this, mLoadingSrc) == 0,
   4311      "Should not have entry for element in element table before addition");
   4312  if (!gElementTable) {
   4313    gElementTable = new MediaElementURITable();
   4314  }
   4315  MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc);
   4316  entry->mElements.AppendElement(this);
   4317  NS_ASSERTION(
   4318      MediaElementTableCount(this, mLoadingSrc) == 1,
   4319      "Should have a single entry for element in element table after addition");
   4320 }
   4321 
   4322 void HTMLMediaElement::RemoveMediaElementFromURITable() {
   4323  if (!mDecoder || !mLoadingSrc || !gElementTable) {
   4324    return;
   4325  }
   4326  MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc);
   4327  if (!entry) {
   4328    return;
   4329  }
   4330  entry->mElements.RemoveElement(this);
   4331  if (entry->mElements.IsEmpty()) {
   4332    gElementTable->RemoveEntry(entry);
   4333    if (gElementTable->Count() == 0) {
   4334      delete gElementTable;
   4335      gElementTable = nullptr;
   4336    }
   4337  }
   4338  NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
   4339               "After remove, should no longer have an entry in element table");
   4340 }
   4341 
   4342 HTMLMediaElement* HTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI) {
   4343  if (!gElementTable) {
   4344    return nullptr;
   4345  }
   4346  MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
   4347  if (!entry) {
   4348    return nullptr;
   4349  }
   4350  for (uint32_t i = 0; i < entry->mElements.Length(); ++i) {
   4351    HTMLMediaElement* elem = entry->mElements[i];
   4352    bool equal;
   4353    // Look for elements that have the same principal and CORS mode.
   4354    // Ditto for anything else that could cause us to send different headers.
   4355    if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) &&
   4356        equal && elem->mCORSMode == mCORSMode) {
   4357      // See SetupDecoder() below. We only add a element to the table when
   4358      // mDecoder is a ChannelMediaDecoder.
   4359      auto* decoder = static_cast<ChannelMediaDecoder*>(elem->mDecoder.get());
   4360      NS_ASSERTION(decoder, "Decoder gone");
   4361      if (decoder->CanClone()) {
   4362        return elem;
   4363      }
   4364    }
   4365  }
   4366  return nullptr;
   4367 }
   4368 
   4369 // This GeckoView-specific observer ensures that suspended elements resume
   4370 // downloading data after receiving a late autoplay permission grant, as
   4371 // autoplay permissions are granted asynchronously on GeckoView.
   4372 class HTMLMediaElement::GVAutoplayObserver final : public nsIObserver {
   4373  enum class Phase : int8_t { Init, Subscribed, Unsubscribed };
   4374 
   4375 public:
   4376  NS_DECL_ISUPPORTS
   4377 
   4378  explicit GVAutoplayObserver(HTMLMediaElement* aElement)
   4379      : mElement(aElement), mPhase(Phase::Init) {
   4380    MOZ_ASSERT(NS_IsMainThread());
   4381    MOZ_ASSERT(aElement);
   4382 #if !defined(MOZ_WIDGET_ANDROID)
   4383    LOG(LogLevel::Error,
   4384        ("%p GVAutoplayObserver used outside Android!", mElement.get()));
   4385    MOZ_ASSERT_UNREACHABLE(
   4386        "GVAutoplayObserver should never be constructed outside of Android.");
   4387 #endif
   4388    Subscribe();
   4389  }
   4390 
   4391 private:
   4392  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
   4393                     const char16_t*) override {
   4394    if (!mElement || !aTopic || !aSubject || (mPhase != Phase::Subscribed) ||
   4395        strcmp(aTopic, kGVAutoplayAllowedTopic)) {
   4396      LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4397                            "Invalid element/topic/subject/phase, skip.",
   4398                            mElement.get(), this));
   4399      return NS_OK;
   4400    }
   4401 
   4402    if ((mElement->NetworkState() >= NETWORK_LOADING) &&
   4403        (mElement->ReadyState() >= HAVE_CURRENT_DATA)) {
   4404      LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4405                            "Loading or has enough data, skip.",
   4406                            mElement.get(), this));
   4407      return NS_OK;
   4408    }
   4409 
   4410    nsCOMPtr<nsPIDOMWindowInner> inner(do_QueryInterface(aSubject));
   4411    if (!inner) {
   4412      LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4413                            "Couldn't get inner window from subject, skip.",
   4414                            mElement.get(), this));
   4415      return NS_OK;
   4416    }
   4417 
   4418    RefPtr<dom::BrowsingContext> bcSubject = inner->GetBrowsingContext();
   4419    if (!bcSubject) {
   4420      LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4421                            "Couldn't get subject browsing context, skip.",
   4422                            mElement.get(), this));
   4423      return NS_OK;
   4424    }
   4425 
   4426    BrowsingContext* bcElem = mElement->OwnerDoc()->GetBrowsingContext();
   4427    if (!bcSubject || !bcElem || bcSubject->Top() != bcElem->Top()) {
   4428      LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4429                            "Contexts don't match, skip.",
   4430                            mElement.get(), this));
   4431      return NS_OK;
   4432    }
   4433 
   4434    // Element needs more data and the autoplay message is valid - update.
   4435    LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Observe observer=%p "
   4436                          "Updating preload action.",
   4437                          mElement.get(), this));
   4438    mElement->UpdatePreloadAction(JSCallingLocation::Get());
   4439    return NS_OK;
   4440  }
   4441 
   4442  void Subscribe() {
   4443    MOZ_ASSERT(mPhase == Phase::Init);
   4444    MOZ_ASSERT(mElement);
   4445    LOG(LogLevel::Debug,
   4446        ("%p GVAutoplayObserver::Subscribe observer=%p", mElement.get(), this));
   4447    nsCOMPtr<nsIObserverService> observerService =
   4448        mozilla::services::GetObserverService();
   4449    if (mElement && observerService) {
   4450      observerService->AddObserver(this, kGVAutoplayAllowedTopic, false);
   4451    }
   4452    mPhase = Phase::Subscribed;
   4453  }
   4454 
   4455  void Unsubscribe() {
   4456    MOZ_ASSERT(mPhase == Phase::Subscribed);
   4457    // mElement might be null
   4458    LOG(LogLevel::Debug, ("%p GVAutoplayObserver::Unsubscribe observer=%p",
   4459                          mElement.get(), this));
   4460    nsCOMPtr<nsIObserverService> observerService =
   4461        mozilla::services::GetObserverService();
   4462    if (observerService) {
   4463      observerService->RemoveObserver(this, kGVAutoplayAllowedTopic);
   4464    }
   4465    mElement = nullptr;
   4466    mPhase = Phase::Unsubscribed;
   4467  }
   4468 
   4469  virtual ~GVAutoplayObserver() {
   4470    if (mPhase == Phase::Subscribed) {
   4471      Unsubscribe();
   4472    }
   4473    MOZ_ASSERT(!mElement);
   4474  }
   4475 
   4476  WeakPtr<HTMLMediaElement> mElement = nullptr;
   4477  Phase mPhase = Phase::Init;
   4478 };
   4479 
   4480 NS_IMPL_ISUPPORTS(HTMLMediaElement::GVAutoplayObserver, nsIObserver)
   4481 
   4482 class HTMLMediaElement::ShutdownObserver : public nsIObserver {
   4483  enum class Phase : int8_t { Init, Subscribed, Unsubscribed };
   4484 
   4485 public:
   4486  NS_DECL_ISUPPORTS
   4487 
   4488  NS_IMETHOD Observe(nsISupports*, const char* aTopic,
   4489                     const char16_t*) override {
   4490    if (mPhase != Phase::Subscribed) {
   4491      // Bail out if we are not subscribed for this might be called even after
   4492      // |nsContentUtils::UnregisterShutdownObserver(this)|.
   4493      return NS_OK;
   4494    }
   4495    MOZ_DIAGNOSTIC_ASSERT(mWeak);
   4496    if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
   4497      mWeak->NotifyShutdownEvent();
   4498    }
   4499    return NS_OK;
   4500  }
   4501  void Subscribe(HTMLMediaElement* aPtr) {
   4502    MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Init);
   4503    MOZ_DIAGNOSTIC_ASSERT(!mWeak);
   4504    mWeak = aPtr;
   4505    nsContentUtils::RegisterShutdownObserver(this);
   4506    mPhase = Phase::Subscribed;
   4507  }
   4508  void Unsubscribe() {
   4509    MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed);
   4510    MOZ_DIAGNOSTIC_ASSERT(mWeak);
   4511    MOZ_DIAGNOSTIC_ASSERT(!mAddRefed,
   4512                          "ReleaseMediaElement should have been called first");
   4513    mWeak = nullptr;
   4514    nsContentUtils::UnregisterShutdownObserver(this);
   4515    mPhase = Phase::Unsubscribed;
   4516  }
   4517  void AddRefMediaElement() {
   4518    MOZ_DIAGNOSTIC_ASSERT(mWeak);
   4519    MOZ_DIAGNOSTIC_ASSERT(!mAddRefed, "Should only ever AddRef once");
   4520    mWeak->AddRef();
   4521    mAddRefed = true;
   4522  }
   4523  void ReleaseMediaElement() {
   4524    MOZ_DIAGNOSTIC_ASSERT(mWeak);
   4525    MOZ_DIAGNOSTIC_ASSERT(mAddRefed, "Should only release after AddRef");
   4526    mWeak->Release();
   4527    mAddRefed = false;
   4528  }
   4529 
   4530 private:
   4531  virtual ~ShutdownObserver() {
   4532    MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Unsubscribed);
   4533    MOZ_DIAGNOSTIC_ASSERT(!mWeak);
   4534    MOZ_DIAGNOSTIC_ASSERT(!mAddRefed,
   4535                          "ReleaseMediaElement should have been called first");
   4536  }
   4537  // Guaranteed to be valid by HTMLMediaElement.
   4538  HTMLMediaElement* mWeak = nullptr;
   4539  Phase mPhase = Phase::Init;
   4540  bool mAddRefed = false;
   4541 };
   4542 
   4543 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
   4544 
   4545 class HTMLMediaElement::TitleChangeObserver final : public nsIObserver {
   4546 public:
   4547  NS_DECL_ISUPPORTS
   4548 
   4549  explicit TitleChangeObserver(HTMLMediaElement* aElement)
   4550      : mElement(aElement) {
   4551    MOZ_ASSERT(NS_IsMainThread());
   4552    MOZ_ASSERT(aElement);
   4553  }
   4554 
   4555  NS_IMETHOD Observe(nsISupports*, const char* aTopic,
   4556                     const char16_t*) override {
   4557    if (mElement) {
   4558      mElement->UpdateStreamName();
   4559    }
   4560 
   4561    return NS_OK;
   4562  }
   4563 
   4564  void Subscribe() {
   4565    if (!mIsSubscribed) {
   4566      nsCOMPtr<nsIObserverService> observerService =
   4567          mozilla::services::GetObserverService();
   4568      if (observerService) {
   4569        if (NS_WARN_IF(NS_FAILED(observerService->AddObserver(
   4570                this, "document-title-changed", false)))) {
   4571          return;
   4572        }
   4573        mIsSubscribed = true;
   4574      }
   4575    }
   4576  }
   4577 
   4578  void Unsubscribe() {
   4579    if (mIsSubscribed) {
   4580      mIsSubscribed = false;
   4581      nsCOMPtr<nsIObserverService> observerService =
   4582          mozilla::services::GetObserverService();
   4583      if (observerService) {
   4584        observerService->RemoveObserver(this, "document-title-changed");
   4585      }
   4586    }
   4587  }
   4588 
   4589 private:
   4590  ~TitleChangeObserver() = default;
   4591 
   4592  WeakPtr<HTMLMediaElement> mElement;
   4593  bool mIsSubscribed{false};
   4594 };
   4595 
   4596 NS_IMPL_ISUPPORTS(HTMLMediaElement::TitleChangeObserver, nsIObserver)
   4597 
   4598 HTMLMediaElement::HTMLMediaElement(
   4599    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
   4600    : nsGenericHTMLElement(std::move(aNodeInfo)),
   4601      mWatchManager(this, AbstractThread::MainThread()),
   4602      mShutdownObserver(new ShutdownObserver),
   4603      mTitleChangeObserver(new TitleChangeObserver(this)),
   4604      mEventBlocker(new EventBlocker(this)),
   4605      mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
   4606      mTracksCaptured(nullptr, "HTMLMediaElement::mTracksCaptured"),
   4607      mErrorSink(new ErrorSink(this)),
   4608      mAudioChannelWrapper(new AudioChannelAgentCallback(this)),
   4609      mSink(std::pair(nsString(), RefPtr<AudioDeviceInfo>())),
   4610      mShowPoster(IsVideo()),
   4611      mMediaControlKeyListener(new MediaControlKeyListener(this)) {
   4612  MOZ_ASSERT(GetMainThreadSerialEventTarget());
   4613  // Please don't add anything to this constructor or the initialization
   4614  // list that can cause AddRef to be called. This prevents subclasses
   4615  // from overriding AddRef in a way that works with our refcount
   4616  // logging mechanisms. Put these things inside of the ::Init method
   4617  // instead.
   4618 }
   4619 
   4620 void HTMLMediaElement::Init() {
   4621  MOZ_ASSERT(mRefCnt == 0 && !mRefCnt.IsPurple(),
   4622             "HTMLMediaElement::Init called when AddRef has been called "
   4623             "at least once already, probably in the constructor. Please "
   4624             "see the documentation in the HTMLMediaElement constructor.");
   4625  MOZ_ASSERT(!mRefCnt.IsPurple());
   4626 
   4627  mAudioTrackList = new AudioTrackList(OwnerDoc()->GetParentObject(), this);
   4628  mVideoTrackList = new VideoTrackList(OwnerDoc()->GetParentObject(), this);
   4629 
   4630  DecoderDoctorLogger::LogConstruction(this);
   4631 
   4632  mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateWakeLock);
   4633  mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateOutputTracksMuting);
   4634  mWatchManager.Watch(
   4635      mPaused, &HTMLMediaElement::NotifyMediaControlPlaybackStateChanged);
   4636  mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTracksMuting);
   4637 
   4638  mWatchManager.Watch(mTracksCaptured,
   4639                      &HTMLMediaElement::UpdateOutputTrackSources);
   4640  mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTrackSources);
   4641 
   4642  mWatchManager.Watch(mDownloadSuspendedByCache,
   4643                      &HTMLMediaElement::UpdateReadyStateInternal);
   4644  mWatchManager.Watch(mFirstFrameLoaded,
   4645                      &HTMLMediaElement::UpdateReadyStateInternal);
   4646  mWatchManager.Watch(mSrcStreamPlaybackEnded,
   4647                      &HTMLMediaElement::UpdateReadyStateInternal);
   4648 
   4649  ErrorResult rv;
   4650 
   4651  double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
   4652  SetVolume(defaultVolume, rv);
   4653 
   4654  RegisterActivityObserver();
   4655  NotifyOwnerDocumentActivityChanged();
   4656 
   4657  // We initialize the MediaShutdownManager as the HTMLMediaElement is always
   4658  // constructed on the main thread, and not during stable state.
   4659  // (MediaShutdownManager make use of nsIAsyncShutdownClient which is written
   4660  // in JS)
   4661  MediaShutdownManager::InitStatics();
   4662 
   4663 #if defined(MOZ_WIDGET_ANDROID)
   4664  GVAutoplayPermissionRequestor::AskForPermissionIfNeeded(
   4665      OwnerDoc()->GetInnerWindow());
   4666  StartObservingGVAutoplayIfNeeded();
   4667 #endif
   4668 
   4669  OwnerDoc()->SetDocTreeHadMedia();
   4670  mShutdownObserver->Subscribe(this);
   4671  mInitialized = true;
   4672 }
   4673 
   4674 HTMLMediaElement::~HTMLMediaElement() {
   4675  MOZ_ASSERT(mInitialized,
   4676             "HTMLMediaElement must be initialized before it is destroyed.");
   4677  NS_ASSERTION(
   4678      !mHasSelfReference,
   4679      "How can we be destroyed if we're still holding a self reference?");
   4680 
   4681  PROFILER_MARKER("~HTMLMediaElement", MEDIA_PLAYBACK, {},
   4682                  TerminatingFlowMarker, Flow::FromPointer(this));
   4683 
   4684  mWatchManager.Shutdown();
   4685 
   4686 #if defined(MOZ_WIDGET_ANDROID)
   4687  mGVAutoplayObserver = nullptr;
   4688 #endif
   4689 
   4690  mShutdownObserver->Unsubscribe();
   4691 
   4692  mTitleChangeObserver->Unsubscribe();
   4693 
   4694  if (mVideoFrameContainer) {
   4695    mVideoFrameContainer->ForgetElement();
   4696  }
   4697  UnregisterActivityObserver();
   4698 
   4699  mSetCDMRequest.DisconnectIfExists();
   4700  mAllowedToPlayPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
   4701 
   4702  if (mDecoder) {
   4703    ShutdownDecoder();
   4704  }
   4705  if (mProgressTimer) {
   4706    StopProgress();
   4707  }
   4708  if (mSrcStream) {
   4709    EndSrcMediaStreamPlayback();
   4710  }
   4711 
   4712  NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
   4713               "Destroyed media element should no longer be in element table");
   4714 
   4715  if (mChannelLoader) {
   4716    mChannelLoader->Cancel();
   4717  }
   4718 
   4719  if (mAudioChannelWrapper) {
   4720    mAudioChannelWrapper->Shutdown();
   4721    mAudioChannelWrapper = nullptr;
   4722  }
   4723 
   4724  if (mResumeDelayedPlaybackAgent) {
   4725    mResumePlaybackRequest.DisconnectIfExists();
   4726    mResumeDelayedPlaybackAgent = nullptr;
   4727  }
   4728 
   4729  mMediaControlKeyListener->StopIfNeeded();
   4730  mMediaControlKeyListener = nullptr;
   4731 
   4732  WakeLockRelease();
   4733 
   4734  DecoderDoctorLogger::LogDestruction(this);
   4735 }
   4736 
   4737 void HTMLMediaElement::StopSuspendingAfterFirstFrame() {
   4738  mAllowSuspendAfterFirstFrame = false;
   4739  if (!mSuspendedAfterFirstFrame) {
   4740    return;
   4741  }
   4742  mSuspendedAfterFirstFrame = false;
   4743  if (mDecoder) {
   4744    mDecoder->Resume();
   4745  }
   4746 }
   4747 
   4748 void HTMLMediaElement::SetPlayedOrSeeked(bool aValue) {
   4749  if (aValue == mHasPlayedOrSeeked) {
   4750    return;
   4751  }
   4752 
   4753  mHasPlayedOrSeeked = aValue;
   4754 
   4755  // Force a reflow so that the poster frame hides or shows immediately.
   4756  nsIFrame* frame = GetPrimaryFrame();
   4757  if (!frame) {
   4758    return;
   4759  }
   4760  frame->PresShell()->FrameNeedsReflow(frame, IntrinsicDirty::FrameAndAncestors,
   4761                                       NS_FRAME_IS_DIRTY);
   4762 }
   4763 
   4764 void HTMLMediaElement::NotifyXPCOMShutdown() { ShutdownDecoder(); }
   4765 
   4766 already_AddRefed<Promise> HTMLMediaElement::Play(ErrorResult& aRv) {
   4767  LOG(LogLevel::Debug,
   4768      ("%p Play() called by JS readyState=%d", this, mReadyState.Ref()));
   4769 
   4770  // 4.8.12.8
   4771  // When the play() method on a media element is invoked, the user agent must
   4772  // run the following steps.
   4773 
   4774  RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
   4775  if (NS_WARN_IF(aRv.Failed())) {
   4776    return nullptr;
   4777  }
   4778 
   4779  // 4.8.12.8 - Step 1:
   4780  // If the media element is not allowed to play, return a promise rejected
   4781  // with a "NotAllowedError" DOMException and abort these steps.
   4782  // NOTE: we may require requesting permission from the user, so we do the
   4783  // "not allowed" check below.
   4784 
   4785  // 4.8.12.8 - Step 2:
   4786  // If the media element's error attribute is not null and its code
   4787  // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
   4788  // rejected with a "NotSupportedError" DOMException and abort these steps.
   4789  if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
   4790    LOG(LogLevel::Debug,
   4791        ("%p Play() promise rejected because source not supported.", this));
   4792    promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
   4793    return promise.forget();
   4794  }
   4795 
   4796  // 4.8.12.8 - Step 3:
   4797  // Let promise be a new promise and append promise to the list of pending
   4798  // play promises.
   4799  // Note: Promise appended to list of pending promises as needed below.
   4800 
   4801  if (ShouldBeSuspendedByInactiveDocShell()) {
   4802    LOG(LogLevel::Debug, ("%p no allow to play by the docShell for now", this));
   4803    mPendingPlayPromises.AppendElement(promise);
   4804    return promise.forget();
   4805  }
   4806 
   4807  // We may delay starting playback of a media resource for an unvisited tab
   4808  // until it's going to foreground or being resumed by the play tab icon.
   4809  if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
   4810    CreateResumeDelayedMediaPlaybackAgentIfNeeded();
   4811    LOG(LogLevel::Debug, ("%p delay Play() call", this));
   4812    MaybeDoLoad();
   4813    // When play is delayed, save a reference to the promise, and return it.
   4814    // The promise will be resolved when we resume play by either the tab is
   4815    // brought to the foreground, or the audio tab indicator is clicked.
   4816    mPendingPlayPromises.AppendElement(promise);
   4817    return promise.forget();
   4818  }
   4819 
   4820  const bool handlingUserInput = UserActivation::IsHandlingUserInput();
   4821  mPendingPlayPromises.AppendElement(promise);
   4822 
   4823  if (AllowedToPlay()) {
   4824    AUTOPLAY_LOG("allow MediaElement %p to play", this);
   4825    mAllowedToPlayPromise.ResolveIfExists(true, __func__);
   4826    PlayInternal(handlingUserInput);
   4827    UpdateCustomPolicyAfterPlayed();
   4828 
   4829    MaybeMarkSHEntryAsUserInteracted();
   4830  } else {
   4831    AUTOPLAY_LOG("reject MediaElement %p to play", this);
   4832    AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
   4833  }
   4834  return promise.forget();
   4835 }
   4836 
   4837 void HTMLMediaElement::DispatchEventsWhenPlayWasNotAllowed() {
   4838  if (StaticPrefs::media_autoplay_block_event_enabled()) {
   4839    QueueEvent(u"blocked"_ns);
   4840  }
   4841  DispatchBlockEventForVideoControl();
   4842  if (!mHasEverBeenBlockedForAutoplay) {
   4843    MaybeNotifyAutoplayBlocked();
   4844    ReportToConsole(nsIScriptError::warningFlag, "BlockAutoplayError");
   4845    mHasEverBeenBlockedForAutoplay = true;
   4846  }
   4847 }
   4848 
   4849 void HTMLMediaElement::MaybeNotifyAutoplayBlocked() {
   4850  // This event is used to notify front-end side that we've blocked autoplay,
   4851  // so front-end side should show blocking icon as well.
   4852  RefPtr<AsyncEventDispatcher> asyncDispatcher =
   4853      new AsyncEventDispatcher(OwnerDoc(), u"GloballyAutoplayBlocked"_ns,
   4854                               CanBubble::eYes, ChromeOnlyDispatch::eYes);
   4855  asyncDispatcher->PostDOMEvent();
   4856 }
   4857 
   4858 void HTMLMediaElement::DispatchBlockEventForVideoControl() {
   4859 #if defined(MOZ_WIDGET_ANDROID)
   4860  nsVideoFrame* videoFrame = do_QueryFrame(GetPrimaryFrame());
   4861  if (!videoFrame || !videoFrame->GetVideoControls()) {
   4862    return;
   4863  }
   4864 
   4865  RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
   4866      videoFrame->GetVideoControls(), u"MozNoControlsBlockedVideo"_ns,
   4867      CanBubble::eYes);
   4868  asyncDispatcher->PostDOMEvent();
   4869 #endif
   4870 }
   4871 
   4872 void HTMLMediaElement::PlayInternal(bool aHandlingUserInput) {
   4873 #if defined(MOZ_WIDGET_ANDROID)
   4874  AUTOPLAY_LOG("Stop observing GV autoplay permission (PlayInternal starting)");
   4875  StopObservingGVAutoplayIfNeeded();
   4876 #endif
   4877 
   4878  if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
   4879    // The media load algorithm will be initiated by a user interaction.
   4880    // We want to boost the channel priority for better responsiveness.
   4881    // Note this must be done before UpdatePreloadAction() which will
   4882    // update |mPreloadAction|.
   4883    mUseUrgentStartForChannel = true;
   4884  }
   4885 
   4886  StopSuspendingAfterFirstFrame();
   4887  SetPlayedOrSeeked(true);
   4888 
   4889  // 4.8.12.8 - Step 4:
   4890  // If the media element's networkState attribute has the value NETWORK_EMPTY,
   4891  // invoke the media element's resource selection algorithm.
   4892  MaybeDoLoad();
   4893  if (mSuspendedForPreloadNone) {
   4894    ResumeLoad(PRELOAD_ENOUGH, JSCallingLocation::Get());
   4895  }
   4896 
   4897  // 4.8.12.8 - Step 5:
   4898  // If the playback has ended and the direction of playback is forwards,
   4899  // seek to the earliest possible position of the media resource.
   4900 
   4901  // Even if we just did Load() or ResumeLoad(), we could already have a decoder
   4902  // here if we managed to clone an existing decoder.
   4903  if (mDecoder) {
   4904    if (mDecoder->IsEnded()) {
   4905      SetCurrentTime(0);
   4906    }
   4907    if (!mSuspendedByInactiveDocOrDocshell) {
   4908      mDecoder->Play();
   4909    }
   4910  }
   4911 
   4912  if (mCurrentPlayRangeStart == -1.0) {
   4913    mCurrentPlayRangeStart = CurrentTime();
   4914  }
   4915 
   4916  const bool oldPaused = mPaused;
   4917  mPaused = false;
   4918  // Step 5,
   4919  // https://html.spec.whatwg.org/multipage/media.html#internal-play-steps
   4920  mCanAutoplayFlag = false;
   4921 
   4922  // We changed mPaused and mCanAutoplayFlag which can affect
   4923  // AddRemoveSelfReference and our preload status.
   4924  AddRemoveSelfReference();
   4925  UpdatePreloadAction(JSCallingLocation::Get());
   4926  UpdateSrcMediaStreamPlaying();
   4927  StartMediaControlKeyListenerIfNeeded();
   4928 
   4929  // Once play() has been called in a user generated event handler,
   4930  // it is allowed to autoplay. Note: we can reach here when not in
   4931  // a user generated event handler if our readyState has not yet
   4932  // reached HAVE_METADATA.
   4933  mIsBlessed |= aHandlingUserInput;
   4934 
   4935  // TODO: If the playback has ended, then the user agent must set
   4936  // seek to the effective start.
   4937 
   4938  // 4.8.12.8 - Step 6:
   4939  // If the media element's paused attribute is true, run the following steps:
   4940  if (oldPaused) {
   4941    // 6.1. Change the value of paused to false. (Already done.)
   4942    // This step is uplifted because the "block-media-playback" feature needs
   4943    // the mPaused to be false before UpdateAudioChannelPlayingState() being
   4944    // called.
   4945 
   4946    // 6.2. If the show poster flag is true, set the element's show poster flag
   4947    //      to false and run the time marches on steps.
   4948    if (mShowPoster) {
   4949      mShowPoster = false;
   4950      if (mTextTrackManager) {
   4951        mTextTrackManager->TimeMarchesOn();
   4952      }
   4953    }
   4954 
   4955    // 6.3. Queue a task to fire a simple event named play at the element.
   4956    QueueEvent(u"play"_ns);
   4957 
   4958    // 6.4. If the media element's readyState attribute has the value
   4959    //      HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
   4960    //      fire a simple event named waiting at the element.
   4961    //      Otherwise, the media element's readyState attribute has the value
   4962    //      HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
   4963    //      element.
   4964    switch (mReadyState) {
   4965      case HAVE_NOTHING:
   4966        QueueEvent(u"waiting"_ns);
   4967        break;
   4968      case HAVE_METADATA:
   4969      case HAVE_CURRENT_DATA:
   4970        QueueEvent(u"waiting"_ns);
   4971        break;
   4972      case HAVE_FUTURE_DATA:
   4973      case HAVE_ENOUGH_DATA:
   4974        NotifyAboutPlaying();
   4975        break;
   4976    }
   4977  } else if (mReadyState >= HAVE_FUTURE_DATA) {
   4978    // 7. Otherwise, if the media element's readyState attribute has the value
   4979    //    HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
   4980    //    queue a task to resolve pending play promises with the result.
   4981    AsyncResolvePendingPlayPromises();
   4982  }
   4983 
   4984  // 8. Set the media element's autoplaying flag to false. (Already done.)
   4985 
   4986  // 9. Return promise.
   4987  // (Done in caller.)
   4988 }
   4989 
   4990 void HTMLMediaElement::MaybeDoLoad() {
   4991  if (mNetworkState == NETWORK_EMPTY) {
   4992    DoLoad();
   4993  }
   4994 }
   4995 
   4996 void HTMLMediaElement::UpdateWakeLock() {
   4997  MOZ_ASSERT(NS_IsMainThread());
   4998  // Ensure we have a wake lock if we're playing audibly. This ensures the
   4999  // device doesn't sleep while playing.
   5000  bool playing = !mPaused;
   5001  bool isAudible = Volume() > 0.0 && !mMuted && mIsAudioTrackAudible;
   5002  // WakeLock when playing audible media.
   5003  if (playing && isAudible) {
   5004    CreateAudioWakeLockIfNeeded();
   5005  } else {
   5006    ReleaseAudioWakeLockIfExists();
   5007  }
   5008 }
   5009 
   5010 void HTMLMediaElement::CreateAudioWakeLockIfNeeded() {
   5011  MOZ_ASSERT(NS_IsMainThread());
   5012  if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
   5013    return;
   5014  }
   5015  if (mAudioWakelockReleaseScheduler) {
   5016    LOG(LogLevel::Debug,
   5017        ("%p Reuse existing audio wakelock, cancel scheduler", this));
   5018    mAudioWakelockReleaseScheduler->Reset();
   5019    mAudioWakelockReleaseScheduler.reset();
   5020    return;
   5021  }
   5022  if (!mWakeLock) {
   5023    RefPtr<power::PowerManagerService> pmService =
   5024        power::PowerManagerService::GetInstance();
   5025    NS_ENSURE_TRUE_VOID(pmService);
   5026    LOG(LogLevel::Debug, ("%p creating audio wakelock", this));
   5027    ErrorResult rv;
   5028    mWakeLock = pmService->NewWakeLock(u"audio-playing"_ns,
   5029                                       OwnerDoc()->GetInnerWindow(), rv);
   5030  }
   5031 }
   5032 
   5033 void HTMLMediaElement::ReleaseAudioWakeLockIfExists() {
   5034  MOZ_ASSERT(NS_IsMainThread());
   5035  if (mWakeLock) {
   5036    const uint32_t delayMs =
   5037        StaticPrefs::media_wakelock_audio_delay_releasing_ms();
   5038    // Already in shutdown or no delay, release wakelock directly.
   5039    if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) ||
   5040        delayMs == 0) {
   5041      ReleaseAudioWakeLockInternal();
   5042      return;
   5043    }
   5044    if (mAudioWakelockReleaseScheduler) {
   5045      return;
   5046    }
   5047    LOG(LogLevel::Debug,
   5048        ("%p Delaying audio wakelock release by %u ms", this, delayMs));
   5049    AwakeTimeStamp target =
   5050        AwakeTimeStamp ::Now() + AwakeTimeDuration::FromMilliseconds(delayMs);
   5051    mAudioWakelockReleaseScheduler.emplace(
   5052        DelayedScheduler<AwakeTimeStamp>{GetMainThreadSerialEventTarget()});
   5053    mAudioWakelockReleaseScheduler->Ensure(
   5054        target,
   5055        [self = RefPtr<HTMLMediaElement>(this), this]() {
   5056          mAudioWakelockReleaseScheduler->CompleteRequest();
   5057          ReleaseAudioWakeLockInternal();
   5058        },
   5059        [self = RefPtr<HTMLMediaElement>(this), this]() {
   5060          LOG(LogLevel::Debug,
   5061              ("%p Fail to delay audio wakelock releasing?!", this));
   5062          mAudioWakelockReleaseScheduler->CompleteRequest();
   5063          ReleaseAudioWakeLockInternal();
   5064        });
   5065  }
   5066 }
   5067 
   5068 void HTMLMediaElement::ReleaseAudioWakeLockInternal() {
   5069  MOZ_ASSERT(NS_IsMainThread());
   5070  if (mAudioWakelockReleaseScheduler) {
   5071    mAudioWakelockReleaseScheduler->Reset();
   5072    mAudioWakelockReleaseScheduler.reset();
   5073  }
   5074  LOG(LogLevel::Debug, ("%p release audio wakelock", this));
   5075  ErrorResult rv;
   5076  mWakeLock->Unlock(rv);
   5077  rv.SuppressException();
   5078  mWakeLock = nullptr;
   5079 }
   5080 
   5081 void HTMLMediaElement::WakeLockRelease() {
   5082  if (mWakeLock) {
   5083    ReleaseAudioWakeLockInternal();
   5084  }
   5085 }
   5086 
   5087 void HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
   5088  if (!this->Controls() || !aVisitor.mEvent->mFlags.mIsTrusted) {
   5089    nsGenericHTMLElement::GetEventTargetParent(aVisitor);
   5090    return;
   5091  }
   5092 
   5093  // We will need to trap pointer, touch, and mouse events within the media
   5094  // element, allowing media control exclusive consumption on these events,
   5095  // and preventing the content from handling them.
   5096  switch (aVisitor.mEvent->mMessage) {
   5097    case eTouchRawUpdate:
   5098      MOZ_FALLTHROUGH_ASSERT(
   5099          "eTouchRawUpdate event shouldn't be dispatched into the DOM");
   5100    // Always prevent touchmove captured in video element from being handled by
   5101    // content, since we always do that for touchstart.
   5102    case eTouchMove:
   5103    case eTouchEnd:
   5104    case eTouchStart: {
   5105      // We stop the propagation (both at the capture and the bubbling phase)
   5106      // when the original target is the part of the ControlBar or the
   5107      // ClickToPlay button.
   5108      if (ShadowRoot* shadowRoot = GetShadowRoot()) {
   5109        nsINode* node =
   5110            nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
   5111        const bool trap = [&] {
   5112          if (node->SubtreeRoot() != shadowRoot) {
   5113            return false;
   5114          }
   5115 
   5116          for (auto* node : node->InclusiveAncestorsOfType<Element>()) {
   5117            auto* id = node->GetID();
   5118            if (!id) {
   5119              continue;
   5120            }
   5121            if (id == nsGkAtoms::clickToPlay || id == nsGkAtoms::controlBar) {
   5122              return true;
   5123            }
   5124          }
   5125          return false;
   5126        }();
   5127 
   5128        if (trap) {
   5129          aVisitor.mCanHandle = false;
   5130        } else {
   5131          nsGenericHTMLElement::GetEventTargetParent(aVisitor);
   5132        }
   5133        return;
   5134      }
   5135      nsGenericHTMLElement::GetEventTargetParent(aVisitor);
   5136      return;
   5137    }
   5138    case ePointerDown:
   5139    case ePointerUp:
   5140    case ePointerClick:
   5141    case eMouseDoubleClick:
   5142    case eMouseDown:
   5143    case eMouseUp:
   5144      aVisitor.mCanHandle = false;
   5145      return;
   5146 
   5147    // The *move events however are only consumed when the range input is being
   5148    // dragged.
   5149    case eMouseRawUpdate:
   5150      MOZ_FALLTHROUGH_ASSERT(
   5151          "eMouseRawUpdate event shouldn't be dispatched into the DOM");
   5152    case ePointerMove:
   5153    case ePointerRawUpdate:
   5154    case eMouseMove: {
   5155      nsINode* node =
   5156          nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
   5157      if (MOZ_UNLIKELY(!node)) {
   5158        return;
   5159      }
   5160      HTMLInputElement* el = nullptr;
   5161      if (node->ChromeOnlyAccess()) {
   5162        if (node->IsHTMLElement(nsGkAtoms::input)) {
   5163          // The node is a <input type="range">
   5164          el = static_cast<HTMLInputElement*>(node);
   5165        } else if (node->GetParentNode() &&
   5166                   node->GetParentNode()->IsHTMLElement(nsGkAtoms::input)) {
   5167          // The node is a child of <input type="range">
   5168          el = static_cast<HTMLInputElement*>(node->GetParentNode());
   5169        }
   5170      }
   5171      if (el && el->IsDraggingRange()) {
   5172        aVisitor.mCanHandle = false;
   5173        return;
   5174      }
   5175      nsGenericHTMLElement::GetEventTargetParent(aVisitor);
   5176      return;
   5177    }
   5178    default:
   5179      nsGenericHTMLElement::GetEventTargetParent(aVisitor);
   5180      return;
   5181  }
   5182 }
   5183 
   5184 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
   5185                                      const nsAString& aValue,
   5186                                      nsIPrincipal* aMaybeScriptedPrincipal,
   5187                                      nsAttrValue& aResult) {
   5188  if (aNamespaceID == kNameSpaceID_None) {
   5189    if (aAttribute == nsGkAtoms::crossorigin) {
   5190      ParseCORSValue(aValue, aResult);
   5191      return true;
   5192    }
   5193    if (aAttribute == nsGkAtoms::preload) {
   5194      return aResult.ParseEnumValue(aValue, kPreloadTable, false,
   5195                                    // The default value is "auto" if aValue is
   5196                                    // not a recognised value.
   5197                                    kPreloadDefaultType);
   5198    }
   5199  }
   5200 
   5201  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
   5202                                              aMaybeScriptedPrincipal, aResult);
   5203 }
   5204 
   5205 void HTMLMediaElement::DoneCreatingElement() {
   5206  if (HasAttr(nsGkAtoms::muted)) {
   5207    mMuted |= MUTED_BY_CONTENT;
   5208  }
   5209 }
   5210 
   5211 bool HTMLMediaElement::IsHTMLFocusable(IsFocusableFlags aFlags,
   5212                                       bool* aIsFocusable, int32_t* aTabIndex) {
   5213  if (nsGenericHTMLElement::IsHTMLFocusable(aFlags, aIsFocusable, aTabIndex)) {
   5214    return true;
   5215  }
   5216 
   5217  *aIsFocusable = true;
   5218  return false;
   5219 }
   5220 
   5221 int32_t HTMLMediaElement::TabIndexDefault() { return 0; }
   5222 
   5223 void HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
   5224                                    const nsAttrValue* aValue,
   5225                                    const nsAttrValue* aOldValue,
   5226                                    nsIPrincipal* aMaybeScriptedPrincipal,
   5227                                    bool aNotify) {
   5228  if (aNameSpaceID == kNameSpaceID_None) {
   5229    if (aName == nsGkAtoms::src) {
   5230      mSrcMediaSource = nullptr;
   5231      nsAttrValueOrString srcVal(aValue);
   5232      mSrcAttrTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
   5233          this, srcVal.String(), aMaybeScriptedPrincipal);
   5234      if (aValue) {
   5235        nsCOMPtr<nsIURI> uri;
   5236        NewURIFromString(srcVal.String(), getter_AddRefs(uri));
   5237        if (uri && IsMediaSourceURI(uri)) {
   5238          nsresult rv = NS_GetSourceForMediaSourceURI(
   5239              uri, getter_AddRefs(mSrcMediaSource));
   5240          if (NS_FAILED(rv)) {
   5241            nsAutoString spec;
   5242            GetCurrentSrc(spec);
   5243            AutoTArray<nsString, 1> params = {spec};
   5244            ReportLoadError("MediaLoadInvalidURI", params);
   5245          }
   5246        }
   5247      }
   5248    } else if (aName == nsGkAtoms::autoplay) {
   5249      if (aNotify) {
   5250        if (aValue) {
   5251 #if defined(MOZ_WIDGET_ANDROID)
   5252          StartObservingGVAutoplayIfNeeded();
   5253 #endif
   5254          StopSuspendingAfterFirstFrame();
   5255          CheckAutoplayDataReady();
   5256        }
   5257        // This attribute can affect AddRemoveSelfReference
   5258        AddRemoveSelfReference();
   5259        UpdatePreloadAction(JSCallingLocation::Get());
   5260      }
   5261    } else if (aName == nsGkAtoms::preload) {
   5262      UpdatePreloadAction(JSCallingLocation::Get());
   5263    } else if (aName == nsGkAtoms::loop) {
   5264      if (mDecoder) {
   5265        mDecoder->SetLooping(!!aValue);
   5266      }
   5267    } else if (aName == nsGkAtoms::controls && IsInComposedDoc()) {
   5268      NotifyUAWidgetSetupOrChange();
   5269    }
   5270  }
   5271 
   5272  // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
   5273  // *after* any possible changes to mSrcMediaSource.
   5274  if (aValue) {
   5275    AfterMaybeChangeAttr(aNameSpaceID, aName, aNotify);
   5276  }
   5277 
   5278  return nsGenericHTMLElement::AfterSetAttr(
   5279      aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
   5280 }
   5281 
   5282 void HTMLMediaElement::OnAttrSetButNotChanged(int32_t aNamespaceID,
   5283                                              nsAtom* aName,
   5284                                              const nsAttrValueOrString& aValue,
   5285                                              bool aNotify) {
   5286  AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
   5287 
   5288  return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
   5289                                                      aValue, aNotify);
   5290 }
   5291 
   5292 void HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsAtom* aName,
   5293                                            bool aNotify) {
   5294  if (aNamespaceID == kNameSpaceID_None) {
   5295    if (aName == nsGkAtoms::src) {
   5296      DoLoad();
   5297    }
   5298  }
   5299 }
   5300 
   5301 nsresult HTMLMediaElement::BindToTree(BindContext& aContext, nsINode& aParent) {
   5302  nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
   5303 
   5304  if (IsInComposedDoc()) {
   5305    // Construct Shadow Root so web content can be hidden in the DOM.
   5306    AttachAndSetUAShadowRoot();
   5307 
   5308    // The preload action depends on the value of the autoplay attribute.
   5309    // It's value may have changed, so update it.
   5310    UpdatePreloadAction(JSCallingLocation::Get());
   5311  }
   5312 
   5313  NotifyDecoderActivityChanges();
   5314  mMediaControlKeyListener->UpdateOwnerBrowsingContextIfNeeded();
   5315  return rv;
   5316 }
   5317 
   5318 void HTMLMediaElement::UnbindFromTree(UnbindContext& aContext) {
   5319  mVisibilityState = Visibility::Untracked;
   5320 
   5321  if (IsInComposedDoc()) {
   5322    NotifyUAWidgetTeardown();
   5323  }
   5324 
   5325  nsGenericHTMLElement::UnbindFromTree(aContext);
   5326 
   5327  MOZ_ASSERT(IsActuallyInvisible());
   5328  NotifyDecoderActivityChanges();
   5329 
   5330  // https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document
   5331  //
   5332  // Dispatch a task to run once we're in a stable state which ensures we're
   5333  // paused if we're no longer in a document. Note that we need to dispatch this
   5334  // even if there are other tasks in flight for this because these can be
   5335  // cancelled if there's a new load.
   5336  //
   5337  // FIXME(emilio): Per that spec section, we should only do this if we used to
   5338  // be connected, though other browsers match our current behavior...
   5339  //
   5340  // Also, https://github.com/whatwg/html/issues/4928
   5341  nsCOMPtr<nsIRunnable> task =
   5342      NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree",
   5343                             [self = RefPtr<HTMLMediaElement>(this)]() {
   5344                               if (!self->IsInComposedDoc()) {
   5345                                 self->PauseInternal();
   5346                                 self->mMediaControlKeyListener->StopIfNeeded();
   5347                               }
   5348                             });
   5349  RunInStableState(task);
   5350 }
   5351 
   5352 /* static */
   5353 CanPlayStatus HTMLMediaElement::GetCanPlay(
   5354    const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics) {
   5355  Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
   5356  if (!containerType) {
   5357    return CANPLAY_NO;
   5358  }
   5359  CanPlayStatus status =
   5360      DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics);
   5361  if (status == CANPLAY_YES &&
   5362      (*containerType).ExtendedType().Codecs().IsEmpty()) {
   5363    // Per spec: 'Generally, a user agent should never return "probably" for a
   5364    // type that allows the `codecs` parameter if that parameter is not
   5365    // present.' As all our currently-supported types allow for `codecs`, we can
   5366    // do this check here.
   5367    // TODO: Instead, missing `codecs` should be checked in each decoder's
   5368    // `IsSupportedType` call from `CanHandleCodecsType()`.
   5369    // See bug 1399023.
   5370    return CANPLAY_MAYBE;
   5371  }
   5372  return status;
   5373 }
   5374 
   5375 void HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult) {
   5376  DecoderDoctorDiagnostics diagnostics;
   5377  CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
   5378  diagnostics.StoreFormatDiagnostics(OwnerDoc(), aType, canPlay != CANPLAY_NO,
   5379                                     __func__);
   5380  switch (canPlay) {
   5381    case CANPLAY_NO:
   5382      aResult.Truncate();
   5383      break;
   5384    case CANPLAY_YES:
   5385      aResult.AssignLiteral("probably");
   5386      break;
   5387    case CANPLAY_MAYBE:
   5388      aResult.AssignLiteral("maybe");
   5389      break;
   5390    default:
   5391      MOZ_ASSERT_UNREACHABLE("Unexpected case.");
   5392      break;
   5393  }
   5394 
   5395  LOG(LogLevel::Debug,
   5396      ("%p CanPlayType(%s) = \"%s\"", this, NS_ConvertUTF16toUTF8(aType).get(),
   5397       NS_ConvertUTF16toUTF8(aResult).get()));
   5398 }
   5399 
   5400 void HTMLMediaElement::AssertReadyStateIsNothing() {
   5401 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   5402  if (mReadyState != HAVE_NOTHING) {
   5403    char buf[1024];
   5404    SprintfLiteral(buf,
   5405                   "readyState=%d networkState=%d mLoadWaitStatus=%d "
   5406                   "mSourceLoadCandidate=%d "
   5407                   "mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
   5408                   "mSuspendedForPreloadNone=%d error=%d",
   5409                   int(mReadyState), int(mNetworkState), int(mLoadWaitStatus),
   5410                   !!mSourceLoadCandidate, mIsLoadingFromSourceChildren,
   5411                   int(mPreloadAction), mSuspendedForPreloadNone,
   5412                   GetError() ? GetError()->Code() : 0);
   5413    MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf);
   5414  }
   5415 #endif
   5416 }
   5417 
   5418 nsresult HTMLMediaElement::InitializeDecoderAsClone(
   5419    ChannelMediaDecoder* aOriginal) {
   5420  NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
   5421  NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
   5422  AssertReadyStateIsNothing();
   5423 
   5424  MediaDecoderInit decoderInit(
   5425      this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
   5426      ClampPlaybackRate(mPlaybackRate),
   5427      mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
   5428      HasAttr(nsGkAtoms::loop), aOriginal->ContainerType());
   5429 
   5430  RefPtr<ChannelMediaDecoder> decoder = aOriginal->Clone(decoderInit);
   5431  if (!decoder) {
   5432    return NS_ERROR_FAILURE;
   5433  }
   5434 
   5435  LOG(LogLevel::Debug,
   5436      ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
   5437 
   5438  return FinishDecoderSetup(decoder);
   5439 }
   5440 
   5441 template <typename DecoderType, typename... LoadArgs>
   5442 nsresult HTMLMediaElement::SetupDecoder(DecoderType* aDecoder,
   5443                                        LoadArgs&&... aArgs) {
   5444  LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, aDecoder,
   5445                        aDecoder->ContainerType().OriginalString().Data()));
   5446 
   5447  nsresult rv = aDecoder->Load(std::forward<LoadArgs>(aArgs)...);
   5448  if (NS_FAILED(rv)) {
   5449    aDecoder->Shutdown();
   5450    LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
   5451    return rv;
   5452  }
   5453 
   5454  rv = FinishDecoderSetup(aDecoder);
   5455  // Only ChannelMediaDecoder supports resource cloning.
   5456  if (std::is_same_v<DecoderType, ChannelMediaDecoder> && NS_SUCCEEDED(rv)) {
   5457    AddMediaElementToURITable();
   5458    NS_ASSERTION(
   5459        MediaElementTableCount(this, mLoadingSrc) == 1,
   5460        "Media element should have single table entry if decode initialized");
   5461  }
   5462 
   5463  return rv;
   5464 }
   5465 
   5466 nsresult HTMLMediaElement::InitializeDecoderForChannel(
   5467    nsIChannel* aChannel, nsIStreamListener** aListener) {
   5468  NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
   5469  AssertReadyStateIsNothing();
   5470 
   5471  DecoderDoctorDiagnostics diagnostics;
   5472 
   5473  nsAutoCString mimeType;
   5474  aChannel->GetContentType(mimeType);
   5475  NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
   5476  NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
   5477 
   5478  RefPtr<HTMLMediaElement> self = this;
   5479  auto reportCanPlay = [&, self](bool aCanPlay) {
   5480    diagnostics.StoreFormatDiagnostics(self->OwnerDoc(), mimeUTF16, aCanPlay,
   5481                                       __func__);
   5482    if (!aCanPlay) {
   5483      nsAutoString src;
   5484      self->GetCurrentSrc(src);
   5485      AutoTArray<nsString, 2> params = {mimeUTF16, src};
   5486      self->ReportLoadError("MediaLoadUnsupportedMimeType", params);
   5487    }
   5488  };
   5489 
   5490  auto onExit = MakeScopeExit([self] {
   5491    if (self->mChannelLoader) {
   5492      self->mChannelLoader->Done();
   5493      self->mChannelLoader = nullptr;
   5494    }
   5495  });
   5496 
   5497  Maybe<MediaContainerType> containerType = MakeMediaContainerType(mimeType);
   5498  if (!containerType) {
   5499    reportCanPlay(false);
   5500    return NS_ERROR_FAILURE;
   5501  }
   5502 
   5503  MediaDecoderInit decoderInit(
   5504      this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
   5505      ClampPlaybackRate(mPlaybackRate),
   5506      mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
   5507      HasAttr(nsGkAtoms::loop), *containerType);
   5508 
   5509 #ifdef MOZ_ANDROID_HLS_SUPPORT
   5510  if (HLSDecoder::IsSupportedType(*containerType)) {
   5511    RefPtr<HLSDecoder> decoder = HLSDecoder::Create(decoderInit);
   5512    if (!decoder) {
   5513      reportCanPlay(false);
   5514      return NS_ERROR_OUT_OF_MEMORY;
   5515    }
   5516    reportCanPlay(true);
   5517    return SetupDecoder(decoder.get(), aChannel);
   5518  }
   5519 #endif
   5520 
   5521  RefPtr<ChannelMediaDecoder> decoder =
   5522      ChannelMediaDecoder::Create(decoderInit, &diagnostics);
   5523  if (!decoder) {
   5524    reportCanPlay(false);
   5525    return NS_ERROR_FAILURE;
   5526  }
   5527 
   5528  reportCanPlay(true);
   5529  bool isPrivateBrowsing = NodePrincipal()->GetIsInPrivateBrowsing();
   5530  return SetupDecoder(decoder.get(), aChannel, isPrivateBrowsing, aListener);
   5531 }
   5532 
   5533 nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder) {
   5534  ChangeNetworkState(NETWORK_LOADING);
   5535 
   5536  // Set mDecoder now so if methods like GetCurrentSrc get called between
   5537  // here and Load(), they work.
   5538  SetDecoder(aDecoder);
   5539 
   5540  // Notify the decoder of the initial activity status.
   5541  NotifyDecoderActivityChanges();
   5542 
   5543  // Update decoder principal before we start decoding, since it
   5544  // can affect how we feed data to MediaStreams
   5545  NotifyDecoderPrincipalChanged();
   5546 
   5547  // Set sink device if we have one. Otherwise the default is used.
   5548  if (mSink.second) {
   5549    mDecoder->SetSink(mSink.second);
   5550  }
   5551 
   5552  if (mMediaKeys) {
   5553    if (mMediaKeys->GetCDMProxy()) {
   5554      mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
   5555    } else {
   5556      // CDM must have crashed.
   5557      ShutdownDecoder();
   5558      return NS_ERROR_FAILURE;
   5559    }
   5560  }
   5561 
   5562  if (mChannelLoader) {
   5563    mChannelLoader->Done();
   5564    mChannelLoader = nullptr;
   5565  }
   5566 
   5567  // We may want to suspend the new stream now.
   5568  // This will also do an AddRemoveSelfReference.
   5569  NotifyOwnerDocumentActivityChanged();
   5570 
   5571  if (!mDecoder) {
   5572    // NotifyOwnerDocumentActivityChanged may shutdown the decoder if the
   5573    // owning document is inactive and we're in the EME case. We could try and
   5574    // handle this, but at the time of writing it's a pretty niche case, so just
   5575    // bail.
   5576    return NS_ERROR_FAILURE;
   5577  }
   5578 
   5579  if (mSuspendedByInactiveDocOrDocshell) {
   5580    mDecoder->Suspend();
   5581  }
   5582 
   5583  if (!mPaused) {
   5584    SetPlayedOrSeeked(true);
   5585    if (!mSuspendedByInactiveDocOrDocshell) {
   5586      mDecoder->Play();
   5587    }
   5588  }
   5589 
   5590  MaybeBeginCloningVisually();
   5591 
   5592  return NS_OK;
   5593 }
   5594 
   5595 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) {
   5596  if (!mSrcStream) {
   5597    return;
   5598  }
   5599 
   5600  bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused &&
   5601                    !mSuspendedByInactiveDocOrDocshell;
   5602  if (shouldPlay == mSrcStreamIsPlaying) {
   5603    return;
   5604  }
   5605  mSrcStreamIsPlaying = shouldPlay;
   5606 
   5607  LOG(LogLevel::Debug,
   5608      ("MediaElement %p %s playback of DOMMediaStream %p", this,
   5609       shouldPlay ? "Setting up" : "Removing", mSrcStream.get()));
   5610 
   5611  if (shouldPlay) {
   5612    mSrcStreamPlaybackEnded = false;
   5613    mSrcStreamReportPlaybackEnded = false;
   5614 
   5615    if (mMediaStreamRenderer) {
   5616      mMediaStreamRenderer->Start();
   5617    }
   5618    if (mSecondaryMediaStreamRenderer) {
   5619      mSecondaryMediaStreamRenderer->Start();
   5620    }
   5621 
   5622    SetCapturedOutputStreamsEnabled(true);  // Unmute
   5623    // If the input is a media stream, we don't check its data and always regard
   5624    // it as audible when it's playing.
   5625    SetAudibleState(true);
   5626  } else {
   5627    if (mMediaStreamRenderer) {
   5628      mMediaStreamRenderer->Stop();
   5629    }
   5630    if (mSecondaryMediaStreamRenderer) {
   5631      mSecondaryMediaStreamRenderer->Stop();
   5632    }
   5633    SetCapturedOutputStreamsEnabled(false);  // Mute
   5634  }
   5635 }
   5636 
   5637 void HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying() {
   5638  if (!mMediaStreamRenderer) {
   5639    // Notifications are async, the renderer could have been cleared.
   5640    return;
   5641  }
   5642 
   5643  mMediaStreamRenderer->SetProgressingCurrentTime(IsPotentiallyPlaying());
   5644 }
   5645 
   5646 void HTMLMediaElement::UpdateSrcStreamTime() {
   5647  MOZ_ASSERT(NS_IsMainThread());
   5648 
   5649  if (mSrcStreamPlaybackEnded) {
   5650    // We do a separate FireTimeUpdate() when this is set.
   5651    return;
   5652  }
   5653 
   5654  FireTimeUpdate(TimeupdateType::ePeriodic);
   5655 }
   5656 
   5657 void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream) {
   5658  NS_ASSERTION(!mSrcStream, "Should have been ended already");
   5659 
   5660  mLoadingSrc = nullptr;
   5661  mSrcStream = aStream;
   5662 
   5663  VideoFrameContainer* container = GetVideoFrameContainer();
   5664  RefPtr<FirstFrameVideoOutput> firstFrameOutput =
   5665      container ? MakeAndAddRef<FirstFrameVideoOutput>(container,
   5666                                                       AbstractMainThread())
   5667                : nullptr;
   5668  mMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>(
   5669      AbstractMainThread(), container, firstFrameOutput, this);
   5670  mWatchManager.Watch(mPaused,
   5671                      &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5672  mWatchManager.Watch(mReadyState,
   5673                      &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5674  mWatchManager.Watch(mSrcStreamPlaybackEnded,
   5675                      &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5676  mWatchManager.Watch(mSrcStreamPlaybackEnded,
   5677                      &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
   5678  mWatchManager.Watch(mMediaStreamRenderer->CurrentGraphTime(),
   5679                      &HTMLMediaElement::UpdateSrcStreamTime);
   5680  SetVolumeInternal();
   5681  if (mSink.second) {
   5682    mMediaStreamRenderer->SetAudioOutputDevice(mSink.second);
   5683  }
   5684 
   5685  UpdateSrcMediaStreamPlaying();
   5686  UpdateSrcStreamPotentiallyPlaying();
   5687  mSrcStreamVideoPrincipal = NodePrincipal();
   5688 
   5689  // If we pause this media element, track changes in the underlying stream
   5690  // will continue to fire events at this element and alter its track list.
   5691  // That's simpler than delaying the events, but probably confusing...
   5692  nsTArray<RefPtr<MediaStreamTrack>> tracks;
   5693  mSrcStream->GetTracks(tracks);
   5694  for (const RefPtr<MediaStreamTrack>& track : tracks) {
   5695    NotifyMediaStreamTrackAdded(track);
   5696  }
   5697 
   5698  mMediaStreamTrackListener = new MediaStreamTrackListener(this);
   5699  mSrcStream->RegisterTrackListener(mMediaStreamTrackListener.get());
   5700 
   5701  ChangeNetworkState(NETWORK_IDLE);
   5702  ChangeDelayLoadStatus(false);
   5703 
   5704  // FirstFrameLoaded() will be called when the stream has tracks.
   5705 }
   5706 
   5707 void HTMLMediaElement::EndSrcMediaStreamPlayback() {
   5708  MOZ_ASSERT(mSrcStream);
   5709 
   5710  UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
   5711 
   5712  if (mSelectedVideoStreamTrack) {
   5713    mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this);
   5714  }
   5715  mSelectedVideoStreamTrack = nullptr;
   5716 
   5717  MOZ_ASSERT_IF(mSecondaryMediaStreamRenderer,
   5718                !mMediaStreamRenderer == !mSecondaryMediaStreamRenderer);
   5719  if (mMediaStreamRenderer) {
   5720    mWatchManager.Unwatch(mPaused,
   5721                          &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5722    mWatchManager.Unwatch(mReadyState,
   5723                          &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5724    mWatchManager.Unwatch(mSrcStreamPlaybackEnded,
   5725                          &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
   5726    mWatchManager.Unwatch(
   5727        mSrcStreamPlaybackEnded,
   5728        &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
   5729    mWatchManager.Unwatch(mMediaStreamRenderer->CurrentGraphTime(),
   5730                          &HTMLMediaElement::UpdateSrcStreamTime);
   5731    mMediaStreamRenderer->Shutdown();
   5732    mMediaStreamRenderer = nullptr;
   5733  }
   5734  if (mSecondaryMediaStreamRenderer) {
   5735    mSecondaryMediaStreamRenderer->Shutdown();
   5736    mSecondaryMediaStreamRenderer = nullptr;
   5737  }
   5738 
   5739  mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener.get());
   5740  mMediaStreamTrackListener = nullptr;
   5741  mSrcStreamPlaybackEnded = false;
   5742  mSrcStreamReportPlaybackEnded = false;
   5743  mSrcStreamVideoPrincipal = nullptr;
   5744 
   5745  mSrcStream = nullptr;
   5746 }
   5747 
   5748 static already_AddRefed<AudioTrack> CreateAudioTrack(
   5749    AudioStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) {
   5750  nsAutoString id;
   5751  nsAutoString label;
   5752  aStreamTrack->GetId(id);
   5753  aStreamTrack->GetLabel(label, CallerType::System);
   5754 
   5755  return MediaTrackList::CreateAudioTrack(aOwnerGlobal, id, u"main"_ns, label,
   5756                                          u""_ns, true, aStreamTrack);
   5757 }
   5758 
   5759 static already_AddRefed<VideoTrack> CreateVideoTrack(
   5760    VideoStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) {
   5761  nsAutoString id;
   5762  nsAutoString label;
   5763  aStreamTrack->GetId(id);
   5764  aStreamTrack->GetLabel(label, CallerType::System);
   5765 
   5766  return MediaTrackList::CreateVideoTrack(aOwnerGlobal, id, u"main"_ns, label,
   5767                                          u""_ns, aStreamTrack);
   5768 }
   5769 
   5770 void HTMLMediaElement::NotifyMediaStreamTrackAdded(
   5771    const RefPtr<MediaStreamTrack>& aTrack) {
   5772  MOZ_ASSERT(aTrack);
   5773 
   5774  if (aTrack->Ended()) {
   5775    return;
   5776  }
   5777 
   5778 #ifdef DEBUG
   5779  nsAutoString id;
   5780  aTrack->GetId(id);
   5781 
   5782  LOG(LogLevel::Debug, ("%p, Adding %sTrack with id %s", this,
   5783                        aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
   5784                        NS_ConvertUTF16toUTF8(id).get()));
   5785 #endif
   5786 
   5787  if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
   5788    MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
   5789    RefPtr<AudioTrack> audioTrack =
   5790        CreateAudioTrack(t, AudioTracks()->GetOwnerGlobal());
   5791    AudioTracks()->AddTrack(audioTrack);
   5792  } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
   5793    // TODO: Fix this per the spec on bug 1273443.
   5794    if (!IsVideo()) {
   5795      return;
   5796    }
   5797    MOZ_DIAGNOSTIC_ASSERT(VideoTracks(), "Element can't have been unlinked");
   5798    RefPtr<VideoTrack> videoTrack =
   5799        CreateVideoTrack(t, VideoTracks()->GetOwnerGlobal());
   5800    VideoTracks()->AddTrack(videoTrack);
   5801    // New MediaStreamTrack added, set the new added video track as selected
   5802    // video track when there is no selected track.
   5803    if (VideoTracks()->SelectedIndex() == -1) {
   5804      MOZ_ASSERT(!mSelectedVideoStreamTrack);
   5805      videoTrack->SetEnabledInternal(true, dom::MediaTrack::FIRE_NO_EVENTS);
   5806    }
   5807  }
   5808 
   5809  // The set of enabled AudioTracks and selected video track might have changed.
   5810  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   5811  AbstractThread::DispatchDirectTask(
   5812      NewRunnableMethod("HTMLMediaElement::FirstFrameLoaded", this,
   5813                        &HTMLMediaElement::FirstFrameLoaded));
   5814 }
   5815 
   5816 void HTMLMediaElement::NotifyMediaStreamTrackRemoved(
   5817    const RefPtr<MediaStreamTrack>& aTrack) {
   5818  MOZ_ASSERT(aTrack);
   5819 
   5820  nsAutoString id;
   5821  aTrack->GetId(id);
   5822 
   5823  LOG(LogLevel::Debug, ("%p, Removing %sTrack with id %s", this,
   5824                        aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
   5825                        NS_ConvertUTF16toUTF8(id).get()));
   5826 
   5827  MOZ_DIAGNOSTIC_ASSERT(AudioTracks() && VideoTracks(),
   5828                        "Element can't have been unlinked");
   5829  if (dom::MediaTrack* t = AudioTracks()->GetTrackById(id)) {
   5830    AudioTracks()->RemoveTrack(t);
   5831  } else if (dom::MediaTrack* t = VideoTracks()->GetTrackById(id)) {
   5832    VideoTracks()->RemoveTrack(t);
   5833  } else {
   5834    NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(),
   5835                 "MediaStreamTrack ended but did not exist in track lists. "
   5836                 "This is only allowed if a video element ends and we are an "
   5837                 "audio element.");
   5838    return;
   5839  }
   5840 }
   5841 
   5842 void HTMLMediaElement::ProcessMediaFragmentURI() {
   5843  if (!mLoadingSrc) {
   5844    mFragmentStart = mFragmentEnd = -1.0;
   5845    return;
   5846  }
   5847  MediaFragmentURIParser parser(mLoadingSrc);
   5848 
   5849  if (mDecoder && parser.HasEndTime()) {
   5850    mFragmentEnd = parser.GetEndTime();
   5851  }
   5852 
   5853  if (parser.HasStartTime()) {
   5854    SetCurrentTime(parser.GetStartTime());
   5855    mFragmentStart = parser.GetStartTime();
   5856  }
   5857 }
   5858 
   5859 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
   5860                                      UniquePtr<const MetadataTags> aTags) {
   5861  MOZ_ASSERT(NS_IsMainThread());
   5862 
   5863  if (mDecoder) {
   5864    ConstructMediaTracks(aInfo);
   5865  }
   5866 
   5867  SetMediaInfo(*aInfo);
   5868 
   5869  mIsEncrypted =
   5870      aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted();
   5871  mTags = std::move(aTags);
   5872  mLoadedDataFired = false;
   5873  ChangeReadyState(HAVE_METADATA);
   5874 
   5875  // Add output tracks synchronously now to be sure they're available in
   5876  // "loadedmetadata" event handlers.
   5877  UpdateOutputTrackSources();
   5878 
   5879  QueueEvent(u"durationchange"_ns);
   5880  if (IsVideo() && HasVideo()) {
   5881    QueueEvent(u"resize"_ns);
   5882    Invalidate(ImageSizeChanged::No, Some(mMediaInfo.mVideo.mDisplay),
   5883               ForceInvalidate::No);
   5884  }
   5885  NS_ASSERTION(!HasVideo() || (mMediaInfo.mVideo.mDisplay.width > 0 &&
   5886                               mMediaInfo.mVideo.mDisplay.height > 0),
   5887               "Video resolution must be known on 'loadedmetadata'");
   5888  QueueEvent(u"loadedmetadata"_ns);
   5889 
   5890  if (mDecoder && mDecoder->IsTransportSeekable() &&
   5891      mDecoder->IsMediaSeekable()) {
   5892    ProcessMediaFragmentURI();
   5893    mDecoder->SetFragmentEndTime(mFragmentEnd);
   5894  }
   5895  if (mIsEncrypted) {
   5896    // We only support playback of encrypted content via MSE by default.
   5897    if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) {
   5898      DecodeError(
   5899          MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
   5900                      "Encrypted content not supported outside of MSE"));
   5901      return;
   5902    }
   5903 
   5904    // Dispatch a distinct 'encrypted' event for each initData we have.
   5905    for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
   5906      DispatchEncrypted(initData.mInitData, initData.mType);
   5907    }
   5908    mPendingEncryptedInitData.Reset();
   5909  }
   5910 
   5911  if (IsVideo() && aInfo->HasVideo()) {
   5912    // We are a video element playing video so update the screen wakelock
   5913    NotifyOwnerDocumentActivityChanged();
   5914  }
   5915 
   5916  if (mDefaultPlaybackStartPosition != 0.0) {
   5917    SetCurrentTime(mDefaultPlaybackStartPosition);
   5918    mDefaultPlaybackStartPosition = 0.0;
   5919  }
   5920 
   5921  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   5922 }
   5923 
   5924 void HTMLMediaElement::FirstFrameLoaded() {
   5925  LOG(LogLevel::Debug,
   5926      ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d", this,
   5927       mFirstFrameLoaded.Ref(), mWaitingForKey));
   5928 
   5929  NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
   5930 
   5931  if (!mFirstFrameLoaded) {
   5932    mFirstFrameLoaded = true;
   5933  }
   5934 
   5935  ChangeDelayLoadStatus(false);
   5936 
   5937  if (ShouldSuspendDownloadAfterFirstFrameLoaded()) {
   5938    LOG(LogLevel::Debug, ("%p Suspend decoder after first frame loaded", this));
   5939    mSuspendedAfterFirstFrame = true;
   5940    mDecoder->Suspend();
   5941  }
   5942 }
   5943 
   5944 bool HTMLMediaElement::ShouldSuspendDownloadAfterFirstFrameLoaded() const {
   5945  if (!mDecoder) {
   5946    return false;
   5947  }
   5948 
   5949  // If the media is set to autoplay, avoid suspending, to preload as much as
   5950  // possible.
   5951  if (HasAttr(nsGkAtoms::autoplay)) {
   5952    return false;
   5953  }
   5954 
   5955  // If the media is currently playing, do not suspend downloading.
   5956  if (!mPaused) {
   5957    return false;
   5958  }
   5959 
   5960  return mPreloadAction == HTMLMediaElement::PRELOAD_METADATA &&
   5961         mAllowSuspendAfterFirstFrame;
   5962 }
   5963 
   5964 void HTMLMediaElement::NetworkError(const MediaResult& aError) {
   5965  if (mReadyState == HAVE_NOTHING) {
   5966    NoSupportedMediaSourceError(aError.Description());
   5967  } else {
   5968    Error(MEDIA_ERR_NETWORK);
   5969  }
   5970 }
   5971 
   5972 void HTMLMediaElement::DecodeError(const MediaResult& aError) {
   5973  nsAutoString src;
   5974  GetCurrentSrc(src);
   5975  AutoTArray<nsString, 1> params = {src};
   5976  ReportLoadError("MediaLoadDecodeError", params);
   5977 
   5978  DecoderDoctorDiagnostics diagnostics;
   5979  diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
   5980 
   5981  if (mIsLoadingFromSourceChildren) {
   5982    mErrorSink->ResetError();
   5983    if (mSourceLoadCandidate) {
   5984      DispatchAsyncSourceError(mSourceLoadCandidate, aError.Message());
   5985      QueueLoadFromSourceTask();
   5986    } else {
   5987      NS_WARNING("Should know the source we were loading from!");
   5988    }
   5989  } else if (mReadyState == HAVE_NOTHING) {
   5990    NoSupportedMediaSourceError(aError.Description());
   5991  } else if (IsCORSSameOrigin()) {
   5992    Error(MEDIA_ERR_DECODE, Some(aError));
   5993  } else {
   5994    Error(MEDIA_ERR_DECODE, Some(MediaResult{NS_ERROR_DOM_MEDIA_DECODE_ERR,
   5995                                             "Failed to decode media"}));
   5996  }
   5997 }
   5998 
   5999 void HTMLMediaElement::DecodeWarning(const MediaResult& aError) {
   6000  nsAutoString src;
   6001  GetCurrentSrc(src);
   6002  DecoderDoctorDiagnostics diagnostics;
   6003  diagnostics.StoreDecodeWarning(OwnerDoc(), aError, src, __func__);
   6004 }
   6005 
   6006 bool HTMLMediaElement::HasError() const { return GetError(); }
   6007 
   6008 void HTMLMediaElement::LoadAborted() { Error(MEDIA_ERR_ABORTED); }
   6009 
   6010 void HTMLMediaElement::Error(uint16_t aErrorCode,
   6011                             const Maybe<MediaResult>& aResult) {
   6012  mErrorSink->SetError(aErrorCode, aResult);
   6013  ChangeDelayLoadStatus(false);
   6014  UpdateAudioChannelPlayingState();
   6015 }
   6016 
   6017 void HTMLMediaElement::PlaybackEnded() {
   6018  // We changed state which can affect AddRemoveSelfReference
   6019  AddRemoveSelfReference();
   6020 
   6021  NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
   6022               "Decoder fired ended, but not in ended state");
   6023 
   6024  // IsPlaybackEnded() became true.
   6025  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   6026 
   6027  if (mSrcStream) {
   6028    LOG(LogLevel::Debug,
   6029        ("%p, got duration by reaching the end of the resource", this));
   6030    mSrcStreamPlaybackEnded = true;
   6031    QueueEvent(u"durationchange"_ns);
   6032  } else {
   6033    // mediacapture-main:
   6034    // Setting the loop attribute has no effect since a MediaStream has no
   6035    // defined end and therefore cannot be looped.
   6036    if (HasAttr(nsGkAtoms::loop)) {
   6037      SetCurrentTime(0);
   6038      return;
   6039    }
   6040  }
   6041 
   6042  FireTimeUpdate(TimeupdateType::eMandatory);
   6043 
   6044  if (!mPaused) {
   6045    Pause();
   6046  }
   6047 
   6048  if (mSrcStream) {
   6049    // A MediaStream that goes from inactive to active shall be eligible for
   6050    // autoplay again according to the mediacapture-main spec.
   6051    mCanAutoplayFlag = true;
   6052  }
   6053 
   6054  if (StaticPrefs::media_mediacontrol_stopcontrol_aftermediaends()) {
   6055    mMediaControlKeyListener->StopIfNeeded();
   6056  }
   6057  QueueEvent(u"ended"_ns);
   6058 }
   6059 
   6060 void HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded() {
   6061  mSrcStreamReportPlaybackEnded = mSrcStreamPlaybackEnded;
   6062 }
   6063 
   6064 void HTMLMediaElement::SeekStarted() { QueueEvent(u"seeking"_ns); }
   6065 
   6066 void HTMLMediaElement::SeekCompleted() {
   6067  mPlayingBeforeSeek = false;
   6068  SetPlayedOrSeeked(true);
   6069  if (mTextTrackManager) {
   6070    mTextTrackManager->DidSeek();
   6071  }
   6072  // https://html.spec.whatwg.org/multipage/media.html#seeking:dom-media-seek
   6073  // (Step 16)
   6074  // TODO (bug 1688131): run these steps in a stable state.
   6075  FireTimeUpdate(TimeupdateType::eMandatory);
   6076  QueueEvent(u"seeked"_ns);
   6077  // We changed whether we're seeking so we need to AddRemoveSelfReference
   6078  AddRemoveSelfReference();
   6079  if (mCurrentPlayRangeStart == -1.0) {
   6080    mCurrentPlayRangeStart = CurrentTime();
   6081  }
   6082 
   6083  if (mSeekDOMPromise) {
   6084    AbstractMainThread()->Dispatch(NS_NewRunnableFunction(
   6085        __func__, [promise = std::move(mSeekDOMPromise)] {
   6086          promise->MaybeResolveWithUndefined();
   6087        }));
   6088  }
   6089  MOZ_ASSERT(!mSeekDOMPromise);
   6090 }
   6091 
   6092 void HTMLMediaElement::SeekAborted() {
   6093  if (mSeekDOMPromise) {
   6094    AbstractMainThread()->Dispatch(NS_NewRunnableFunction(
   6095        __func__, [promise = std::move(mSeekDOMPromise)] {
   6096          promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   6097        }));
   6098  }
   6099  MOZ_ASSERT(!mSeekDOMPromise);
   6100 }
   6101 
   6102 void HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache) {
   6103  LOG(LogLevel::Debug,
   6104      ("%p, mDownloadSuspendedByCache=%d", this, aSuspendedByCache));
   6105  mDownloadSuspendedByCache = aSuspendedByCache;
   6106 }
   6107 
   6108 void HTMLMediaElement::DownloadSuspended() {
   6109  if (mNetworkState == NETWORK_LOADING) {
   6110    QueueEvent(u"progress"_ns);
   6111  }
   6112  ChangeNetworkState(NETWORK_IDLE);
   6113 }
   6114 
   6115 void HTMLMediaElement::DownloadResumed() {
   6116  ChangeNetworkState(NETWORK_LOADING);
   6117 }
   6118 
   6119 void HTMLMediaElement::CheckProgress(bool aHaveNewProgress) {
   6120  MOZ_ASSERT(NS_IsMainThread());
   6121  MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
   6122 
   6123  TimeStamp now = TimeStamp::Now();
   6124 
   6125  if (aHaveNewProgress) {
   6126    mDataTime = now;
   6127  }
   6128 
   6129  // If this is the first progress, or PROGRESS_MS has passed since the last
   6130  // progress event fired and more data has arrived since then, fire a
   6131  // progress event.
   6132  NS_ASSERTION(
   6133      (mProgressTime.IsNull() && !aHaveNewProgress) || !mDataTime.IsNull(),
   6134      "null TimeStamp mDataTime should not be used in comparison");
   6135  if (mProgressTime.IsNull()
   6136          ? aHaveNewProgress
   6137          : (now - mProgressTime >=
   6138                 TimeDuration::FromMilliseconds(PROGRESS_MS) &&
   6139             mDataTime > mProgressTime)) {
   6140    QueueEvent(u"progress"_ns);
   6141    // Going back 1ms ensures that future data will have now > mProgressTime,
   6142    // and so will trigger another event.  mDataTime is not reset because it
   6143    // is still required to detect stalled; it is similarly offset by
   6144    // 1ms to indicate the new data has not yet arrived.
   6145    mProgressTime = now - TimeDuration::FromMilliseconds(1);
   6146    if (mDataTime > mProgressTime) {
   6147      mDataTime = mProgressTime;
   6148    }
   6149    if (!mProgressTimer) {
   6150      NS_ASSERTION(aHaveNewProgress,
   6151                   "timer dispatched when there was no timer");
   6152      // Were stalled.  Restart timer.
   6153      StartProgressTimer();
   6154      if (!mLoadedDataFired) {
   6155        ChangeDelayLoadStatus(true);
   6156      }
   6157    }
   6158    // Download statistics may have been updated, force a recheck of the
   6159    // readyState.
   6160    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   6161  }
   6162 
   6163  if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
   6164    if (!mMediaSource) {
   6165      QueueEvent(u"stalled"_ns);
   6166    } else {
   6167      ChangeDelayLoadStatus(false);
   6168    }
   6169 
   6170    NS_ASSERTION(mProgressTimer, "detected stalled without timer");
   6171    // Stop timer events, which prevents repeated stalled events until there
   6172    // is more progress.
   6173    StopProgress();
   6174  }
   6175 
   6176  AddRemoveSelfReference();
   6177 }
   6178 
   6179 /* static */
   6180 void HTMLMediaElement::ProgressTimerCallback(nsITimer* aTimer, void* aClosure) {
   6181  auto* decoder = static_cast<HTMLMediaElement*>(aClosure);
   6182  decoder->CheckProgress(false);
   6183 }
   6184 
   6185 void HTMLMediaElement::StartProgressTimer() {
   6186  MOZ_ASSERT(NS_IsMainThread());
   6187  MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
   6188  NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
   6189 
   6190  NS_NewTimerWithFuncCallback(getter_AddRefs(mProgressTimer),
   6191                              ProgressTimerCallback, this, PROGRESS_MS,
   6192                              nsITimer::TYPE_REPEATING_SLACK,
   6193                              "HTMLMediaElement::ProgressTimerCallback"_ns,
   6194                              GetMainThreadSerialEventTarget());
   6195 }
   6196 
   6197 void HTMLMediaElement::StartProgress() {
   6198  // Record the time now for detecting stalled.
   6199  mDataTime = TimeStamp::Now();
   6200  // Reset mProgressTime so that mDataTime is not indicating bytes received
   6201  // after the last progress event.
   6202  mProgressTime = TimeStamp();
   6203  StartProgressTimer();
   6204 }
   6205 
   6206 void HTMLMediaElement::StopProgress() {
   6207  MOZ_ASSERT(NS_IsMainThread());
   6208  if (!mProgressTimer) {
   6209    return;
   6210  }
   6211 
   6212  mProgressTimer->Cancel();
   6213  mProgressTimer = nullptr;
   6214 }
   6215 
   6216 void HTMLMediaElement::DownloadProgressed() {
   6217  if (mNetworkState != NETWORK_LOADING) {
   6218    return;
   6219  }
   6220  CheckProgress(true);
   6221 }
   6222 
   6223 bool HTMLMediaElement::ShouldCheckAllowOrigin() {
   6224  return mCORSMode != CORS_NONE;
   6225 }
   6226 
   6227 bool HTMLMediaElement::IsCORSSameOrigin() {
   6228  bool subsumes;
   6229  RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   6230  return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) &&
   6231          subsumes) ||
   6232         ShouldCheckAllowOrigin();
   6233 }
   6234 
   6235 void HTMLMediaElement::UpdateReadyStateInternal() {
   6236  if (!mDecoder && !mSrcStream) {
   6237    // Not initialized - bail out.
   6238    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6239                          "Not initialized",
   6240                          this));
   6241    return;
   6242  }
   6243 
   6244  if (mDecoder && mReadyState < HAVE_METADATA) {
   6245    // aNextFrame might have a next frame because the decoder can advance
   6246    // on its own thread before MetadataLoaded gets a chance to run.
   6247    // The arrival of more data can't change us out of this readyState.
   6248    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6249                          "Decoder ready state < HAVE_METADATA",
   6250                          this));
   6251    return;
   6252  }
   6253 
   6254  if (mDecoder) {
   6255    // IsPlaybackEnded() might have become false.
   6256    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
   6257  }
   6258 
   6259  if (mSrcStream && mReadyState < HAVE_METADATA) {
   6260    bool hasAudioTracks = AudioTracks() && !AudioTracks()->IsEmpty();
   6261    bool hasVideoTracks = VideoTracks() && !VideoTracks()->IsEmpty();
   6262    if (!hasAudioTracks && !hasVideoTracks) {
   6263      LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6264                            "Stream with no tracks",
   6265                            this));
   6266      // Give it one last chance to remove the self reference if needed.
   6267      AddRemoveSelfReference();
   6268      return;
   6269    }
   6270 
   6271    if (IsVideo() && hasVideoTracks && !HasVideo()) {
   6272      LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6273                            "Stream waiting for video",
   6274                            this));
   6275      return;
   6276    }
   6277 
   6278    LOG(LogLevel::Debug,
   6279        ("MediaElement %p UpdateReadyStateInternal() Stream has "
   6280         "metadata; audioTracks=%d, videoTracks=%d, "
   6281         "hasVideoFrame=%d",
   6282         this, AudioTracks()->Length(), VideoTracks()->Length(), HasVideo()));
   6283 
   6284    // We are playing a stream that has video and a video frame is now set.
   6285    // This means we have all metadata needed to change ready state.
   6286    MediaInfo mediaInfo = mMediaInfo;
   6287    if (hasAudioTracks) {
   6288      mediaInfo.EnableAudio();
   6289    }
   6290    if (hasVideoTracks) {
   6291      mediaInfo.EnableVideo();
   6292      if (mSelectedVideoStreamTrack) {
   6293        mediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha());
   6294      }
   6295    }
   6296    MetadataLoaded(&mediaInfo, nullptr);
   6297  }
   6298 
   6299  if (mMediaSource) {
   6300    // readyState has changed, assuming it's following the pending mediasource
   6301    // operations. Notify the Mediasource that the operations have completed.
   6302    mMediaSource->CompletePendingTransactions();
   6303  }
   6304 
   6305  enum NextFrameStatus nextFrameStatus = NextFrameStatus();
   6306  if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
   6307    if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE && mDecoder &&
   6308        !mDecoder->IsEnded()) {
   6309      nextFrameStatus = mDecoder->NextFrameBufferedStatus();
   6310    }
   6311  } else if (mWaitingForKey == WAITING_FOR_KEY) {
   6312    if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
   6313        nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
   6314      // http://w3c.github.io/encrypted-media/#wait-for-key
   6315      // Continuing 7.3.4 Queue a "waitingforkey" Event
   6316      // 4. Queue a task to fire a simple event named waitingforkey
   6317      // at the media element.
   6318      // 5. Set the readyState of media element to HAVE_METADATA.
   6319      // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
   6320      // depending on whether we've loaded the first frame or not
   6321      // below.
   6322      // 6. Suspend playback.
   6323      // Note: Playback will already be stalled, as the next frame is
   6324      // unavailable.
   6325      mWaitingForKey = WAITING_FOR_KEY_DISPATCHED;
   6326      QueueEvent(u"waitingforkey"_ns);
   6327    }
   6328  } else {
   6329    MOZ_ASSERT(mWaitingForKey == WAITING_FOR_KEY_DISPATCHED);
   6330    if (nextFrameStatus == NEXT_FRAME_AVAILABLE) {
   6331      // We have new frames after dispatching "waitingforkey".
   6332      // This means we've got the key and can reset mWaitingForKey now.
   6333      mWaitingForKey = NOT_WAITING_FOR_KEY;
   6334    }
   6335  }
   6336 
   6337  if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
   6338    LOG(LogLevel::Debug,
   6339        ("MediaElement %p UpdateReadyStateInternal() "
   6340         "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA",
   6341         this));
   6342    ChangeReadyState(HAVE_METADATA);
   6343    return;
   6344  }
   6345 
   6346  if (IsVideo() && VideoTracks() && !VideoTracks()->IsEmpty() &&
   6347      !IsPlaybackEnded() && GetImageContainer() &&
   6348      !GetImageContainer()->HasCurrentImage()) {
   6349    // Don't advance if we are playing video, but don't have a video frame.
   6350    // Also, if video became available after advancing to HAVE_CURRENT_DATA
   6351    // while we are still playing, we need to revert to HAVE_METADATA until
   6352    // a video frame is available.
   6353    LOG(LogLevel::Debug,
   6354        ("MediaElement %p UpdateReadyStateInternal() "
   6355         "Playing video but no video frame; Forcing HAVE_METADATA",
   6356         this));
   6357    ChangeReadyState(HAVE_METADATA);
   6358    return;
   6359  }
   6360 
   6361  if (!mFirstFrameLoaded) {
   6362    // We haven't yet loaded the first frame, making us unable to determine
   6363    // if we have enough valid data at the present stage.
   6364    return;
   6365  }
   6366 
   6367  if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
   6368    // Force HAVE_CURRENT_DATA when buffering.
   6369    ChangeReadyState(HAVE_CURRENT_DATA);
   6370    return;
   6371  }
   6372 
   6373  // TextTracks must be loaded for the HAVE_ENOUGH_DATA and
   6374  // HAVE_FUTURE_DATA.
   6375  // So force HAVE_CURRENT_DATA if text tracks not loaded.
   6376  if (mTextTrackManager && !mTextTrackManager->IsLoaded()) {
   6377    ChangeReadyState(HAVE_CURRENT_DATA);
   6378    return;
   6379  }
   6380 
   6381  if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
   6382    // The decoder has signaled that the download has been suspended by the
   6383    // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
   6384    // script waiting for a "canplaythrough" event; without this forced
   6385    // transition, we will never fire the "canplaythrough" event if the
   6386    // media cache is too small, and scripts are bound to fail. Don't force
   6387    // this transition if the decoder is in ended state; the readyState
   6388    // should remain at HAVE_CURRENT_DATA in this case.
   6389    // Note that this state transition includes the case where we finished
   6390    // downloaded the whole data stream.
   6391    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6392                          "Decoder download suspended by cache",
   6393                          this));
   6394    ChangeReadyState(HAVE_ENOUGH_DATA);
   6395    return;
   6396  }
   6397 
   6398  if (nextFrameStatus != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
   6399    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6400                          "Next frame not available",
   6401                          this));
   6402    ChangeReadyState(HAVE_CURRENT_DATA);
   6403    return;
   6404  }
   6405 
   6406  if (mSrcStream) {
   6407    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6408                          "Stream HAVE_ENOUGH_DATA",
   6409                          this));
   6410    ChangeReadyState(HAVE_ENOUGH_DATA);
   6411    return;
   6412  }
   6413 
   6414  // Now see if we should set HAVE_ENOUGH_DATA.
   6415  // If it's something we don't know the size of, then we can't
   6416  // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
   6417  // we've downloaded enough data that our download rate is considered
   6418  // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
   6419  // autoplay elements for live streams will never play. Otherwise we
   6420  // move to HAVE_ENOUGH_DATA if we can play through the entire media
   6421  // without stopping to buffer.
   6422  if (mDecoder->CanPlayThrough()) {
   6423    LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6424                          "Decoder can play through",
   6425                          this));
   6426    ChangeReadyState(HAVE_ENOUGH_DATA);
   6427    return;
   6428  }
   6429  LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
   6430                        "Default; Decoder has future data",
   6431                        this));
   6432  ChangeReadyState(HAVE_FUTURE_DATA);
   6433 }
   6434 
   6435 static const char* const gReadyStateToString[] = {
   6436    "HAVE_NOTHING", "HAVE_METADATA", "HAVE_CURRENT_DATA", "HAVE_FUTURE_DATA",
   6437    "HAVE_ENOUGH_DATA"};
   6438 
   6439 void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState) {
   6440  if (mReadyState == aState) {
   6441    return;
   6442  }
   6443 
   6444  nsMediaReadyState oldState = mReadyState;
   6445  mReadyState = aState;
   6446  LOG(LogLevel::Debug,
   6447      ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
   6448 
   6449  DDLOG(DDLogCategory::Property, "ready_state", gReadyStateToString[aState]);
   6450 
   6451  // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
   6452  // The user agent must synchronously unset cues' active flag whenever the
   6453  // media element's readyState is changed back to HAVE_NOTHING.
   6454  if (mReadyState == HAVE_NOTHING && mTextTrackManager) {
   6455    mTextTrackManager->NotifyReset();
   6456  }
   6457 
   6458  if (mNetworkState == NETWORK_EMPTY) {
   6459    return;
   6460  }
   6461 
   6462  UpdateAudioChannelPlayingState();
   6463 
   6464  // Handle raising of "waiting" event during seek (see 4.8.10.9)
   6465  // or
   6466  // 4.8.12.7 Ready states:
   6467  // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
   6468  // ready state is HAVE_CURRENT_DATA or less
   6469  // If the media element was potentially playing before its readyState
   6470  // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
   6471  // has not ended playback, and playback has not stopped due to errors,
   6472  // paused for user interaction, or paused for in-band content, the user agent
   6473  // must queue a task to fire a simple event named timeupdate at the element,
   6474  // and queue a task to fire a simple event named waiting at the element."
   6475  if (mPlayingBeforeSeek && mReadyState < HAVE_FUTURE_DATA) {
   6476    QueueEvent(u"waiting"_ns);
   6477  } else if (oldState >= HAVE_FUTURE_DATA && mReadyState < HAVE_FUTURE_DATA &&
   6478             !Paused() && !Ended() && !mErrorSink->mError) {
   6479    FireTimeUpdate(TimeupdateType::eMandatory);
   6480    QueueEvent(u"waiting"_ns);
   6481  }
   6482 
   6483  if (oldState < HAVE_CURRENT_DATA && mReadyState >= HAVE_CURRENT_DATA &&
   6484      !mLoadedDataFired) {
   6485    QueueEvent(u"loadeddata"_ns);
   6486    mLoadedDataFired = true;
   6487  }
   6488 
   6489  if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) {
   6490    QueueEvent(u"canplay"_ns);
   6491    if (!mPaused) {
   6492      if (mDecoder && !mSuspendedByInactiveDocOrDocshell) {
   6493        MOZ_ASSERT(AllowedToPlay());
   6494        mDecoder->Play();
   6495      }
   6496      NotifyAboutPlaying();
   6497    }
   6498  }
   6499 
   6500  CheckAutoplayDataReady();
   6501 
   6502  if (oldState < HAVE_ENOUGH_DATA && mReadyState >= HAVE_ENOUGH_DATA) {
   6503    QueueEvent(u"canplaythrough"_ns);
   6504  }
   6505 }
   6506 
   6507 static const char* const gNetworkStateToString[] = {"EMPTY", "IDLE", "LOADING",
   6508                                                    "NO_SOURCE"};
   6509 
   6510 void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState) {
   6511  if (mNetworkState == aState) {
   6512    return;
   6513  }
   6514 
   6515  nsMediaNetworkState oldState = mNetworkState;
   6516  mNetworkState = aState;
   6517  LOG(LogLevel::Debug,
   6518      ("%p Network state changed to %s", this, gNetworkStateToString[aState]));
   6519  DDLOG(DDLogCategory::Property, "network_state",
   6520        gNetworkStateToString[aState]);
   6521 
   6522  if (oldState == NETWORK_LOADING) {
   6523    // Stop progress notification when exiting NETWORK_LOADING.
   6524    StopProgress();
   6525  }
   6526 
   6527  if (mNetworkState == NETWORK_LOADING) {
   6528    // Start progress notification when entering NETWORK_LOADING.
   6529    StartProgress();
   6530  } else if (mNetworkState == NETWORK_IDLE && !mErrorSink->mError) {
   6531    // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
   6532    QueueEvent(u"suspend"_ns);
   6533  }
   6534 
   6535  // According to the resource selection (step2, step9-18), dedicated media
   6536  // source failure step (step4) and aborting existing load (step4), set show
   6537  // poster flag to true. https://html.spec.whatwg.org/multipage/media.html
   6538  if (mNetworkState == NETWORK_NO_SOURCE || mNetworkState == NETWORK_EMPTY) {
   6539    mShowPoster = true;
   6540  }
   6541 
   6542  // Changing mNetworkState affects AddRemoveSelfReference().
   6543  AddRemoveSelfReference();
   6544 }
   6545 
   6546 void HTMLMediaElement::StartObservingGVAutoplayIfNeeded() {
   6547 #if defined(MOZ_WIDGET_ANDROID)
   6548  if (mGVAutoplayObserver || !StaticPrefs::media_geckoview_autoplay_request()) {
   6549    return;
   6550  }
   6551  nsPIDOMWindowInner* inner = OwnerDoc()->GetInnerWindow();
   6552  RefPtr<BrowsingContext> ctx = inner ? inner->GetBrowsingContext() : nullptr;
   6553  if (ctx && GVAutoplayPermissionRequestor::HasUnresolvedRequest(inner)) {
   6554    mGVAutoplayObserver = MakeAndAddRef<GVAutoplayObserver>(this);
   6555  }
   6556 #endif
   6557 }
   6558 
   6559 void HTMLMediaElement::StopObservingGVAutoplayIfNeeded() {
   6560 #if defined(MOZ_WIDGET_ANDROID)
   6561  if (!mGVAutoplayObserver) {
   6562    return;
   6563  }
   6564  nsPIDOMWindowInner* inner = OwnerDoc()->GetInnerWindow();
   6565  RefPtr<BrowsingContext> ctx = inner ? inner->GetBrowsingContext() : nullptr;
   6566  if (ctx == nullptr ||
   6567      !GVAutoplayPermissionRequestor::HasUnresolvedRequest(inner)) {
   6568    mGVAutoplayObserver = nullptr;
   6569  }
   6570 #endif
   6571 }
   6572 
   6573 bool HTMLMediaElement::IsEligibleForAutoplay() {
   6574  // We also activate autoplay when playing a media source since the data
   6575  // download is controlled by the script and there is no way to evaluate
   6576  // MediaDecoder::CanPlayThrough().
   6577 
   6578  if (!HasAttr(nsGkAtoms::autoplay)) {
   6579    return false;
   6580  }
   6581 
   6582  if (!mCanAutoplayFlag) {
   6583    return false;
   6584  }
   6585 
   6586  if (IsEditable()) {
   6587    return false;
   6588  }
   6589 
   6590  if (!mPaused) {
   6591    return false;
   6592  }
   6593 
   6594  if (mSuspendedByInactiveDocOrDocshell) {
   6595    return false;
   6596  }
   6597 
   6598  // Static document is used for print preview and printing, should not be
   6599  // autoplay
   6600  if (OwnerDoc()->IsStaticDocument()) {
   6601    return false;
   6602  }
   6603 
   6604  if (ShouldBeSuspendedByInactiveDocShell()) {
   6605    LOG(LogLevel::Debug, ("%p prohibiting autoplay by the docShell", this));
   6606    return false;
   6607  }
   6608 
   6609  if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
   6610    CreateResumeDelayedMediaPlaybackAgentIfNeeded();
   6611    LOG(LogLevel::Debug, ("%p delay playing from autoplay", this));
   6612    return false;
   6613  }
   6614 
   6615  return mReadyState >= HAVE_ENOUGH_DATA;
   6616 }
   6617 
   6618 void HTMLMediaElement::CheckAutoplayDataReady() {
   6619  if (!IsEligibleForAutoplay()) {
   6620    return;
   6621  }
   6622  if (!AllowedToPlay()) {
   6623    DispatchEventsWhenPlayWasNotAllowed();
   6624    return;
   6625  }
   6626 #if defined(MOZ_WIDGET_ANDROID)
   6627  StopObservingGVAutoplayIfNeeded();
   6628 #endif
   6629  RunAutoplay();
   6630 }
   6631 
   6632 void HTMLMediaElement::RunAutoplay() {
   6633  mAllowedToPlayPromise.ResolveIfExists(true, __func__);
   6634  mPaused = false;
   6635  // We changed mPaused which can affect AddRemoveSelfReference
   6636  AddRemoveSelfReference();
   6637  UpdateSrcMediaStreamPlaying();
   6638  UpdateAudioChannelPlayingState();
   6639  StartMediaControlKeyListenerIfNeeded();
   6640 
   6641  if (mDecoder) {
   6642    SetPlayedOrSeeked(true);
   6643    if (mCurrentPlayRangeStart == -1.0) {
   6644      mCurrentPlayRangeStart = CurrentTime();
   6645    }
   6646    MOZ_ASSERT(!mSuspendedByInactiveDocOrDocshell);
   6647    mDecoder->Play();
   6648  } else if (mSrcStream) {
   6649    SetPlayedOrSeeked(true);
   6650  }
   6651 
   6652  // https://html.spec.whatwg.org/multipage/media.html#ready-states:show-poster-flag
   6653  if (mShowPoster) {
   6654    mShowPoster = false;
   6655    if (mTextTrackManager) {
   6656      mTextTrackManager->TimeMarchesOn();
   6657    }
   6658  }
   6659 
   6660  // For blocked media, the event would be pending until it is resumed.
   6661  QueueEvent(u"play"_ns);
   6662 
   6663  QueueEvent(u"playing"_ns);
   6664 
   6665  MaybeMarkSHEntryAsUserInteracted();
   6666 }
   6667 
   6668 bool HTMLMediaElement::IsActuallyInvisible() const {
   6669  // That means an element is not connected. It probably hasn't connected to a
   6670  // document tree, or connects to a disconnected DOM tree.
   6671  if (!IsInComposedDoc()) {
   6672    return true;
   6673  }
   6674 
   6675  // An element is not in user's view port, which means it's either existing in
   6676  // somewhere in the page where user hasn't seen yet, or is being set
   6677  // `display:none`.
   6678  if (!IsInViewPort()) {
   6679    return true;
   6680  }
   6681 
   6682  // Element being used in picture-in-picture mode would be always visible.
   6683  if (IsBeingUsedInPictureInPictureMode()) {
   6684    return false;
   6685  }
   6686 
   6687  // That check is the page is in the background.
   6688  return OwnerDoc()->Hidden();
   6689 }
   6690 
   6691 bool HTMLMediaElement::IsInViewPort() const {
   6692  return mVisibilityState == Visibility::ApproximatelyVisible;
   6693 }
   6694 
   6695 VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() {
   6696  if (mShuttingDown) {
   6697    return nullptr;
   6698  }
   6699 
   6700  if (mVideoFrameContainer) {
   6701    return mVideoFrameContainer;
   6702  }
   6703 
   6704  // Only video frames need an image container.
   6705  if (!IsVideo()) {
   6706    return nullptr;
   6707  }
   6708 
   6709  mVideoFrameContainer = new VideoFrameContainer(
   6710      this, MakeAndAddRef<ImageContainer>(ImageUsageType::VideoFrameContainer,
   6711                                          ImageContainer::ASYNCHRONOUS));
   6712 
   6713  return mVideoFrameContainer;
   6714 }
   6715 
   6716 void HTMLMediaElement::PrincipalChanged(MediaStreamTrack* aTrack) {
   6717  if (aTrack != mSelectedVideoStreamTrack) {
   6718    return;
   6719  }
   6720 
   6721  nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal,
   6722                                            aTrack->GetPrincipal());
   6723 
   6724  LOG(LogLevel::Debug,
   6725      ("HTMLMediaElement %p video track principal changed to %p (combined "
   6726       "into %p). Waiting for it to reach VideoFrameContainer before setting.",
   6727       this, aTrack->GetPrincipal(), mSrcStreamVideoPrincipal.get()));
   6728 
   6729  if (mVideoFrameContainer) {
   6730    UpdateSrcStreamVideoPrincipal(
   6731        mVideoFrameContainer->GetLastPrincipalHandle());
   6732  }
   6733 }
   6734 
   6735 void HTMLMediaElement::UpdateSrcStreamVideoPrincipal(
   6736    const PrincipalHandle& aPrincipalHandle) {
   6737  nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
   6738  mSrcStream->GetVideoTracks(videoTracks);
   6739 
   6740  for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
   6741    if (PrincipalHandleMatches(aPrincipalHandle, track->GetPrincipal()) &&
   6742        !track->Ended()) {
   6743      // When the PrincipalHandle for the VideoFrameContainer changes to that of
   6744      // a live track in mSrcStream we know that a removed track was displayed
   6745      // but is no longer so.
   6746      LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's "
   6747                            "PrincipalHandle matches track %p. That's all we "
   6748                            "need.",
   6749                            this, track.get()));
   6750      mSrcStreamVideoPrincipal = track->GetPrincipal();
   6751      break;
   6752    }
   6753  }
   6754 }
   6755 
   6756 void HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
   6757    VideoFrameContainer* aContainer,
   6758    const PrincipalHandle& aNewPrincipalHandle) {
   6759  MOZ_ASSERT(NS_IsMainThread());
   6760 
   6761  if (!mSrcStream) {
   6762    return;
   6763  }
   6764 
   6765  LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in "
   6766                        "VideoFrameContainer.",
   6767                        this));
   6768 
   6769  UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
   6770 }
   6771 
   6772 already_AddRefed<nsMediaEventRunner> HTMLMediaElement::GetEventRunner(
   6773    const nsAString& aName, EventFlag aFlag) {
   6774  RefPtr<nsMediaEventRunner> runner;
   6775  if (aName.EqualsLiteral("playing")) {
   6776    runner = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
   6777  } else if (aName.EqualsLiteral("timeupdate")) {
   6778    runner = new nsTimeupdateRunner(this, aFlag == EventFlag::eMandatory);
   6779  } else {
   6780    runner = new nsAsyncEventRunner(aName, this);
   6781  }
   6782  return runner.forget();
   6783 }
   6784 
   6785 nsresult HTMLMediaElement::FireEvent(const nsAString& aName) {
   6786  if (mEventBlocker->ShouldBlockEventDelivery()) {
   6787    RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName);
   6788    mEventBlocker->PostponeEvent(runner);
   6789    return NS_OK;
   6790  }
   6791 
   6792  LOG_EVENT(LogLevel::Debug,
   6793            ("%p Firing event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
   6794 
   6795  return nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, aName,
   6796                                              CanBubble::eNo, Cancelable::eNo);
   6797 }
   6798 
   6799 void HTMLMediaElement::QueueEvent(const nsAString& aName) {
   6800  RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName);
   6801  QueueTask(std::move(runner));
   6802 }
   6803 
   6804 void HTMLMediaElement::QueueTask(RefPtr<nsMediaEventRunner> aRunner) {
   6805  NS_ConvertUTF16toUTF8 eventName(aRunner->EventName());
   6806  LOG_EVENT(LogLevel::Debug, ("%p Queuing event %s", this, eventName.get()));
   6807  DDLOG(DDLogCategory::Event, "HTMLMediaElement", nsCString(eventName.get()));
   6808  if (mEventBlocker->ShouldBlockEventDelivery()) {
   6809    mEventBlocker->PostponeEvent(aRunner);
   6810    return;
   6811  }
   6812  GetMainThreadSerialEventTarget()->Dispatch(aRunner.forget());
   6813 }
   6814 
   6815 bool HTMLMediaElement::IsPotentiallyPlaying() const {
   6816  // TODO:
   6817  //   playback has not stopped due to errors,
   6818  //   and the element has not paused for user interaction
   6819  return !mPaused &&
   6820         (mReadyState == HAVE_ENOUGH_DATA || mReadyState == HAVE_FUTURE_DATA) &&
   6821         !IsPlaybackEnded();
   6822 }
   6823 
   6824 bool HTMLMediaElement::IsPlaybackEnded() const {
   6825  // TODO:
   6826  //   the current playback position is equal to the effective end of the media
   6827  //   resource. See bug 449157.
   6828  if (mDecoder) {
   6829    return mReadyState >= HAVE_METADATA && mDecoder->IsEnded();
   6830  }
   6831  if (mSrcStream) {
   6832    return mReadyState >= HAVE_METADATA && mSrcStreamPlaybackEnded;
   6833  }
   6834  return false;
   6835 }
   6836 
   6837 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal() {
   6838  if (mDecoder) {
   6839    return mDecoder->GetCurrentPrincipal();
   6840  }
   6841  if (mSrcStream) {
   6842    nsTArray<RefPtr<MediaStreamTrack>> tracks;
   6843    mSrcStream->GetTracks(tracks);
   6844    nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
   6845    return principal.forget();
   6846  }
   6847  return nullptr;
   6848 }
   6849 
   6850 bool HTMLMediaElement::HadCrossOriginRedirects() {
   6851  if (mDecoder) {
   6852    return mDecoder->HadCrossOriginRedirects();
   6853  }
   6854  return false;
   6855 }
   6856 
   6857 bool HTMLMediaElement::ShouldResistFingerprinting(RFPTarget aTarget) const {
   6858  return OwnerDoc()->ShouldResistFingerprinting(aTarget);
   6859 }
   6860 
   6861 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentVideoPrincipal() {
   6862  if (mDecoder) {
   6863    return mDecoder->GetCurrentPrincipal();
   6864  }
   6865  if (mSrcStream) {
   6866    nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal;
   6867    return principal.forget();
   6868  }
   6869  return nullptr;
   6870 }
   6871 
   6872 void HTMLMediaElement::NotifyDecoderPrincipalChanged() {
   6873  RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   6874  bool isSameOrigin = !principal || IsCORSSameOrigin();
   6875  mDecoder->UpdateSameOriginStatus(isSameOrigin);
   6876 
   6877  if (isSameOrigin) {
   6878    principal = NodePrincipal();
   6879  }
   6880  for (const auto& entry : mOutputTrackSources.Values()) {
   6881    entry->SetPrincipal(principal);
   6882  }
   6883  mDecoder->SetOutputTracksPrincipal(principal);
   6884 }
   6885 
   6886 void HTMLMediaElement::Invalidate(ImageSizeChanged aImageSizeChanged,
   6887                                  const Maybe<nsIntSize>& aNewIntrinsicSize,
   6888                                  ForceInvalidate aForceInvalidate) {
   6889  nsIFrame* frame = GetPrimaryFrame();
   6890  if (aNewIntrinsicSize) {
   6891    UpdateMediaSize(aNewIntrinsicSize.value());
   6892    if (frame) {
   6893      nsPresContext* presContext = frame->PresContext();
   6894      PresShell* presShell = presContext->PresShell();
   6895      presShell->FrameNeedsReflow(frame,
   6896                                  IntrinsicDirty::FrameAncestorsAndDescendants,
   6897                                  NS_FRAME_IS_DIRTY);
   6898    }
   6899  }
   6900 
   6901  RefPtr<ImageContainer> imageContainer = GetImageContainer();
   6902  bool asyncInvalidate = imageContainer && imageContainer->IsAsync() &&
   6903                         aForceInvalidate == ForceInvalidate::No;
   6904  if (frame) {
   6905    if (aImageSizeChanged == ImageSizeChanged::Yes) {
   6906      frame->InvalidateFrame();
   6907    } else {
   6908      frame->InvalidateLayer(DisplayItemType::TYPE_VIDEO, nullptr, nullptr,
   6909                             asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
   6910    }
   6911  }
   6912 
   6913  SVGObserverUtils::InvalidateDirectRenderingObservers(this);
   6914 }
   6915 
   6916 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize) {
   6917  MOZ_ASSERT(NS_IsMainThread());
   6918 
   6919  if (IsVideo() && mReadyState != HAVE_NOTHING &&
   6920      mMediaInfo.mVideo.mDisplay != aSize) {
   6921    QueueEvent(u"resize"_ns);
   6922  }
   6923 
   6924  mMediaInfo.mVideo.mDisplay = aSize;
   6925  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   6926 }
   6927 
   6928 void HTMLMediaElement::SuspendOrResumeElement(bool aSuspendElement) {
   6929  LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(suspend=%d) docHidden=%d",
   6930                        this, aSuspendElement, OwnerDoc()->Hidden()));
   6931 
   6932  if (aSuspendElement == mSuspendedByInactiveDocOrDocshell) {
   6933    return;
   6934  }
   6935 
   6936  mSuspendedByInactiveDocOrDocshell = aSuspendElement;
   6937  UpdateSrcMediaStreamPlaying();
   6938  UpdateAudioChannelPlayingState();
   6939 
   6940  if (aSuspendElement) {
   6941    if (mDecoder) {
   6942      mDecoder->Pause();
   6943      mDecoder->Suspend();
   6944      mDecoder->SetDelaySeekMode(true);
   6945    }
   6946    mEventBlocker->SetBlockEventDelivery(true);
   6947    // We won't want to resume media element from the bfcache.
   6948    ClearResumeDelayedMediaPlaybackAgentIfNeeded();
   6949    mMediaControlKeyListener->StopIfNeeded();
   6950  } else {
   6951    if (mDecoder) {
   6952      mDecoder->Resume();
   6953      if (!mPaused && !mDecoder->IsEnded()) {
   6954        mDecoder->Play();
   6955      }
   6956      mDecoder->SetDelaySeekMode(false);
   6957    }
   6958    mEventBlocker->SetBlockEventDelivery(false);
   6959    // If the media element has been blocked and isn't still allowed to play
   6960    // when it comes back from the bfcache, we would notify front end to show
   6961    // the blocking icon in order to inform user that the site is still being
   6962    // blocked.
   6963    if (mHasEverBeenBlockedForAutoplay && !AllowedToPlay()) {
   6964      MaybeNotifyAutoplayBlocked();
   6965    }
   6966    StartMediaControlKeyListenerIfNeeded();
   6967  }
   6968  if (StaticPrefs::media_testing_only_events()) {
   6969    auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
   6970        this, u"MozMediaSuspendChanged"_ns, CanBubble::eYes,
   6971        ChromeOnlyDispatch::eYes);
   6972    dispatcher->PostDOMEvent();
   6973  }
   6974 }
   6975 
   6976 bool HTMLMediaElement::IsBeingDestroyed() {
   6977  nsIDocShell* docShell = OwnerDoc()->GetDocShell();
   6978  bool isBeingDestroyed = false;
   6979  if (docShell) {
   6980    docShell->IsBeingDestroyed(&isBeingDestroyed);
   6981  }
   6982  return isBeingDestroyed;
   6983 }
   6984 
   6985 bool HTMLMediaElement::ShouldBeSuspendedByInactiveDocShell() const {
   6986  BrowsingContext* bc = OwnerDoc()->GetBrowsingContext();
   6987  return bc && !bc->IsActive() && bc->Top()->GetSuspendMediaWhenInactive();
   6988 }
   6989 
   6990 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged() {
   6991  if (mDecoder && !IsBeingDestroyed()) {
   6992    NotifyDecoderActivityChanges();
   6993  }
   6994 
   6995  // We would suspend media when the document is inactive, or its docshell has
   6996  // been set to hidden and explicitly wants to suspend media. In those cases,
   6997  // the media would be not visible and we don't want them to continue playing.
   6998  bool shouldSuspend =
   6999      !OwnerDoc()->IsActive() || ShouldBeSuspendedByInactiveDocShell();
   7000  SuspendOrResumeElement(shouldSuspend);
   7001 
   7002  // If the owning document has become inactive we should shutdown the CDM.
   7003  if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) {
   7004    // We don't shutdown MediaKeys here because it also listens for document
   7005    // activity and will take care of shutting down itself.
   7006    DDUNLINKCHILD(mMediaKeys.get());
   7007    mMediaKeys = nullptr;
   7008    if (mDecoder) {
   7009      ShutdownDecoder();
   7010    }
   7011  }
   7012 
   7013  AddRemoveSelfReference();
   7014 }
   7015 
   7016 void HTMLMediaElement::NotifyFullScreenChanged() {
   7017  const bool isInFullScreen = IsInFullScreen();
   7018  if (isInFullScreen) {
   7019    StartMediaControlKeyListenerIfNeeded();
   7020    if (!mMediaControlKeyListener->IsStarted()) {
   7021      MEDIACONTROL_LOG("Failed to start the listener when entering fullscreen");
   7022    }
   7023  }
   7024  // Updating controller fullscreen state no matter the listener starts or not.
   7025  BrowsingContext* bc = OwnerDoc()->GetBrowsingContext();
   7026  if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(bc)) {
   7027    updater->NotifyMediaFullScreenState(bc->Id(), isInFullScreen);
   7028  }
   7029 }
   7030 
   7031 void HTMLMediaElement::AddRemoveSelfReference() {
   7032  // XXX we could release earlier here in many situations if we examined
   7033  // which event listeners are attached. Right now we assume there is a
   7034  // potential listener for every event. We would also have to keep the
   7035  // element alive if it was playing and producing audio output --- right now
   7036  // that's covered by the !mPaused check.
   7037  Document* ownerDoc = OwnerDoc();
   7038 
   7039  // See the comment at the top of this file for the explanation of this
   7040  // boolean expression.
   7041  bool needSelfReference =
   7042      !mShuttingDown && ownerDoc->IsActive() &&
   7043      (mDelayingLoadEvent || (!mPaused && !Ended()) ||
   7044       (mDecoder && mDecoder->IsSeeking()) || IsEligibleForAutoplay() ||
   7045       (mMediaSource ? mProgressTimer : mNetworkState == NETWORK_LOADING));
   7046 
   7047  if (needSelfReference != mHasSelfReference) {
   7048    mHasSelfReference = needSelfReference;
   7049    RefPtr<HTMLMediaElement> self = this;
   7050    if (needSelfReference) {
   7051      // The shutdown observer will hold a strong reference to us. This
   7052      // will do to keep us alive. We need to know about shutdown so that
   7053      // we can release our self-reference.
   7054      GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
   7055          "dom::HTMLMediaElement::AddSelfReference",
   7056          [self]() { self->mShutdownObserver->AddRefMediaElement(); }));
   7057    } else {
   7058      // Dispatch Release asynchronously so that we don't destroy this object
   7059      // inside a call stack of method calls on this object
   7060      GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
   7061          "dom::HTMLMediaElement::AddSelfReference",
   7062          [self]() { self->mShutdownObserver->ReleaseMediaElement(); }));
   7063    }
   7064  }
   7065 }
   7066 
   7067 void HTMLMediaElement::NotifyShutdownEvent() {
   7068  mShuttingDown = true;
   7069  ResetState();
   7070  AddRemoveSelfReference();
   7071 }
   7072 
   7073 void HTMLMediaElement::DispatchAsyncSourceError(
   7074    nsIContent* aSourceElement, const nsACString& aErrorDetails) {
   7075  LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
   7076 
   7077  nsCOMPtr<nsIRunnable> event =
   7078      new nsSourceErrorEventRunner(this, aSourceElement, aErrorDetails);
   7079  GetMainThreadSerialEventTarget()->Dispatch(event.forget());
   7080 }
   7081 
   7082 void HTMLMediaElement::NotifyAddedSource() {
   7083  // If a source element is inserted as a child of a media element
   7084  // that has no src attribute and whose networkState has the value
   7085  // NETWORK_EMPTY, the user agent must invoke the media element's
   7086  // resource selection algorithm.
   7087  if (!HasAttr(nsGkAtoms::src) && mNetworkState == NETWORK_EMPTY) {
   7088    AssertReadyStateIsNothing();
   7089    QueueSelectResourceTask();
   7090  }
   7091 
   7092  // A load was paused in the resource selection algorithm, waiting for
   7093  // a new source child to be added, resume the resource selection algorithm.
   7094  if (mLoadWaitStatus == WAITING_FOR_SOURCE) {
   7095    // Rest the flag so we don't queue multiple LoadFromSourceTask() when
   7096    // multiple <source> are attached in an event loop.
   7097    mLoadWaitStatus = NOT_WAITING;
   7098    QueueLoadFromSourceTask();
   7099  }
   7100 }
   7101 
   7102 HTMLSourceElement* HTMLMediaElement::GetNextSource() {
   7103  mSourceLoadCandidate = nullptr;
   7104 
   7105  while (true) {
   7106    if (mSourcePointer == nsINode::GetLastChild()) {
   7107      return nullptr;  // no more children
   7108    }
   7109 
   7110    if (!mSourcePointer) {
   7111      mSourcePointer = nsINode::GetFirstChild();
   7112    } else {
   7113      mSourcePointer = mSourcePointer->GetNextSibling();
   7114    }
   7115    nsIContent* child = mSourcePointer;
   7116 
   7117    // If child is a <source> element, it is the next candidate.
   7118    if (auto* source = HTMLSourceElement::FromNodeOrNull(child)) {
   7119      mSourceLoadCandidate = source;
   7120      return source;
   7121    }
   7122  }
   7123  MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
   7124  return nullptr;
   7125 }
   7126 
   7127 void HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay) {
   7128  if (mDelayingLoadEvent == aDelay) {
   7129    return;
   7130  }
   7131 
   7132  mDelayingLoadEvent = aDelay;
   7133 
   7134  LOG(LogLevel::Debug, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay,
   7135                        mLoadBlockedDoc.get()));
   7136  if (mDecoder) {
   7137    mDecoder->SetLoadInBackground(!aDelay);
   7138  }
   7139  if (aDelay) {
   7140    mLoadBlockedDoc = OwnerDoc();
   7141    mLoadBlockedDoc->BlockOnload();
   7142  } else {
   7143    // mLoadBlockedDoc might be null due to GC unlinking
   7144    if (mLoadBlockedDoc) {
   7145      mLoadBlockedDoc->UnblockOnload(false);
   7146      mLoadBlockedDoc = nullptr;
   7147    }
   7148  }
   7149 
   7150  // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
   7151  AddRemoveSelfReference();
   7152 }
   7153 
   7154 already_AddRefed<nsILoadGroup> HTMLMediaElement::GetDocumentLoadGroup() {
   7155  if (!OwnerDoc()->IsActive()) {
   7156    NS_WARNING("Load group requested for media element in inactive document.");
   7157  }
   7158  return OwnerDoc()->GetDocumentLoadGroup();
   7159 }
   7160 
   7161 nsresult HTMLMediaElement::CopyInnerTo(Element* aDest) {
   7162  nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
   7163  NS_ENSURE_SUCCESS(rv, rv);
   7164 
   7165  HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
   7166  if (HasAttr(nsGkAtoms::muted)) {
   7167    dest->mMuted |= MUTED_BY_CONTENT;
   7168  }
   7169 
   7170  if (aDest->OwnerDoc()->IsStaticDocument()) {
   7171    dest->SetMediaInfo(mMediaInfo);
   7172  }
   7173  return rv;
   7174 }
   7175 
   7176 already_AddRefed<TimeRanges> HTMLMediaElement::Buffered() const {
   7177  media::TimeIntervals buffered =
   7178      mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals();
   7179  RefPtr<TimeRanges> ranges = new TimeRanges(
   7180      ToSupports(OwnerDoc()), buffered.ToMicrosecondResolution());
   7181  return ranges.forget();
   7182 }
   7183 
   7184 void HTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel) {
   7185  // Send Accept header for video and audio types only (Bug 489071)
   7186  SetAcceptHeader(aChannel);
   7187 
   7188  // Apache doesn't send Content-Length when gzip transfer encoding is used,
   7189  // which prevents us from estimating the video length (if explicit
   7190  // Content-Duration and a length spec in the container are not present either)
   7191  // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate"
   7192  // that we usually send. See bug 614760.
   7193  DebugOnly<nsresult> rv =
   7194      aChannel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
   7195  MOZ_ASSERT(NS_SUCCEEDED(rv));
   7196 
   7197  // Set the Referrer header
   7198  //
   7199  // FIXME: Shouldn't this use the Element constructor? Though I guess it
   7200  // doesn't matter as no HTMLMediaElement supports the referrerinfo attribute.
   7201  auto referrerInfo = MakeRefPtr<ReferrerInfo>(*OwnerDoc());
   7202  rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo);
   7203  MOZ_ASSERT(NS_SUCCEEDED(rv));
   7204 }
   7205 
   7206 const TimeStamp& HTMLMediaElement::LastTimeupdateDispatchTime() const {
   7207  MOZ_ASSERT(NS_IsMainThread());
   7208  return mLastTimeUpdateDispatchTime;
   7209 }
   7210 
   7211 void HTMLMediaElement::UpdateLastTimeupdateDispatchTime() {
   7212  MOZ_ASSERT(NS_IsMainThread());
   7213  mLastTimeUpdateDispatchTime = TimeStamp::Now();
   7214 }
   7215 
   7216 bool HTMLMediaElement::ShouldQueueTimeupdateAsyncTask(
   7217    TimeupdateType aType) const {
   7218  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   7219  // That means dispatching `timeupdate` is mandatorily required in the spec.
   7220  if (aType == TimeupdateType::eMandatory) {
   7221    return true;
   7222  }
   7223 
   7224  // The timeupdate only occurs when the current playback position changes.
   7225  // https://html.spec.whatwg.org/multipage/media.html#event-media-timeupdate
   7226  if (mLastCurrentTime == CurrentTime()) {
   7227    return false;
   7228  }
   7229 
   7230  // Number of milliseconds between timeupdate events as defined by spec.
   7231  if (!mQueueTimeUpdateRunnerTime.IsNull() &&
   7232      TimeStamp::Now() - mQueueTimeUpdateRunnerTime <
   7233          TimeDuration::FromMilliseconds(TIMEUPDATE_MS)) {
   7234    return false;
   7235  }
   7236  return true;
   7237 }
   7238 
   7239 void HTMLMediaElement::FireTimeUpdate(TimeupdateType aType) {
   7240  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   7241 
   7242  if (ShouldQueueTimeupdateAsyncTask(aType)) {
   7243    RefPtr<nsMediaEventRunner> runner =
   7244        GetEventRunner(u"timeupdate"_ns, aType == TimeupdateType::eMandatory
   7245                                             ? EventFlag::eMandatory
   7246                                             : EventFlag::eNone);
   7247    QueueTask(std::move(runner));
   7248    mQueueTimeUpdateRunnerTime = TimeStamp::Now();
   7249    mLastCurrentTime = CurrentTime();
   7250  }
   7251  if (mFragmentEnd >= 0.0 && CurrentTime() >= mFragmentEnd) {
   7252    Pause();
   7253    mFragmentEnd = -1.0;
   7254    mFragmentStart = -1.0;
   7255    mDecoder->SetFragmentEndTime(mFragmentEnd);
   7256  }
   7257 
   7258  // Update the cues displaying on the video.
   7259  // Here mTextTrackManager can be null if the cycle collector has unlinked
   7260  // us before our parent. In that case UnbindFromTree will call us
   7261  // when our parent is unlinked.
   7262  if (mTextTrackManager) {
   7263    mTextTrackManager->TimeMarchesOn();
   7264  }
   7265 }
   7266 
   7267 MediaError* HTMLMediaElement::GetError() const { return mErrorSink->mError; }
   7268 
   7269 void HTMLMediaElement::GetCurrentSpec(nsCString& aString) {
   7270  // If playing a regular URL, an ObjectURL of a Blob/File, return that.
   7271  if (mLoadingSrc) {
   7272    mLoadingSrc->GetSpec(aString);
   7273  } else if (mSrcMediaSource) {
   7274    // If playing an ObjectURL, and it's a MediaSource, return the value of the
   7275    // `src` attribute.
   7276    nsAutoString src;
   7277    GetSrc(src);
   7278    CopyUTF16toUTF8(src, aString);
   7279  } else {
   7280    // Playing e.g. a MediaStream via an object URL - return an empty string
   7281    aString.Truncate();
   7282  }
   7283 }
   7284 
   7285 double HTMLMediaElement::MozFragmentEnd() {
   7286  double duration = Duration();
   7287 
   7288  // If there is no end fragment, or the fragment end is greater than the
   7289  // duration, return the duration.
   7290  return (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration
   7291                                                         : mFragmentEnd;
   7292 }
   7293 
   7294 void HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate,
   7295                                              ErrorResult& aRv) {
   7296  if (mSrcAttrStream) {
   7297    return;
   7298  }
   7299 
   7300  if (aDefaultPlaybackRate < 0) {
   7301    aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
   7302    return;
   7303  }
   7304 
   7305  double defaultPlaybackRate = ClampPlaybackRate(aDefaultPlaybackRate);
   7306 
   7307  if (mDefaultPlaybackRate == defaultPlaybackRate) {
   7308    return;
   7309  }
   7310 
   7311  mDefaultPlaybackRate = defaultPlaybackRate;
   7312  QueueEvent(u"ratechange"_ns);
   7313 }
   7314 
   7315 void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv) {
   7316  if (mSrcAttrStream) {
   7317    return;
   7318  }
   7319 
   7320  // Changing the playback rate of a media that has more than two channels is
   7321  // not supported.
   7322  if (aPlaybackRate < 0) {
   7323    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   7324    return;
   7325  }
   7326 
   7327  if (mPlaybackRate == aPlaybackRate) {
   7328    return;
   7329  }
   7330 
   7331  mPlaybackRate = aPlaybackRate;
   7332  // Playback rate threshold above which audio is muted.
   7333  uint32_t threshold = StaticPrefs::media_audio_playbackrate_muting_threshold();
   7334  if (mPlaybackRate != 0.0 &&
   7335      (mPlaybackRate > threshold || mPlaybackRate < 1. / threshold)) {
   7336    SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE);
   7337  } else {
   7338    SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE);
   7339  }
   7340 
   7341  if (mDecoder) {
   7342    mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate));
   7343  }
   7344  QueueEvent(u"ratechange"_ns);
   7345  mMediaControlKeyListener->NotifyMediaPositionState();
   7346 }
   7347 
   7348 void HTMLMediaElement::SetPreservesPitch(bool aPreservesPitch) {
   7349  mPreservesPitch = aPreservesPitch;
   7350  if (mDecoder) {
   7351    mDecoder->SetPreservesPitch(mPreservesPitch);
   7352  }
   7353 }
   7354 
   7355 ImageContainer* HTMLMediaElement::GetImageContainer() {
   7356  VideoFrameContainer* container = GetVideoFrameContainer();
   7357  return container ? container->GetImageContainer() : nullptr;
   7358 }
   7359 
   7360 void HTMLMediaElement::UpdateAudioChannelPlayingState() {
   7361  if (mAudioChannelWrapper) {
   7362    mAudioChannelWrapper->UpdateAudioChannelPlayingState();
   7363  }
   7364 }
   7365 
   7366 static const char* VisibilityString(Visibility aVisibility) {
   7367  switch (aVisibility) {
   7368    case Visibility::Untracked: {
   7369      return "Untracked";
   7370    }
   7371    case Visibility::ApproximatelyNonVisible: {
   7372      return "ApproximatelyNonVisible";
   7373    }
   7374    case Visibility::ApproximatelyVisible: {
   7375      return "ApproximatelyVisible";
   7376    }
   7377  }
   7378 
   7379  return "NAN";
   7380 }
   7381 
   7382 void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility) {
   7383  LOG(LogLevel::Debug,
   7384      ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility)));
   7385 
   7386  mVisibilityState = aNewVisibility;
   7387  if (StaticPrefs::media_test_video_suspend()) {
   7388    QueueEvent(u"visibilitychanged"_ns);
   7389  }
   7390 
   7391  if (!mDecoder) {
   7392    return;
   7393  }
   7394  NotifyDecoderActivityChanges();
   7395 }
   7396 
   7397 MediaKeys* HTMLMediaElement::GetMediaKeys() const { return mMediaKeys; }
   7398 
   7399 bool HTMLMediaElement::ContainsRestrictedContent() const {
   7400  return GetMediaKeys() != nullptr;
   7401 }
   7402 
   7403 void HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult) {
   7404  LOG(LogLevel::Debug, ("%s", __func__));
   7405  MOZ_ASSERT(mSetMediaKeysDOMPromise);
   7406 
   7407  ResetSetMediaKeysTempVariables();
   7408 
   7409  mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
   7410 }
   7411 
   7412 void HTMLMediaElement::RemoveMediaKeys() {
   7413  LOG(LogLevel::Debug, ("%s", __func__));
   7414  // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
   7415  // to decrypt media data and remove the association with the media element.
   7416  if (mMediaKeys) {
   7417    mMediaKeys->Unbind();
   7418  }
   7419  mMediaKeys = nullptr;
   7420 }
   7421 
   7422 bool HTMLMediaElement::TryRemoveMediaKeysAssociation() {
   7423  MOZ_ASSERT(mMediaKeys);
   7424  LOG(LogLevel::Debug, ("%s", __func__));
   7425  // 5.2.1 If the user agent or CDM do not support removing the association,
   7426  // let this object's attaching media keys value be false and reject promise
   7427  // with a new DOMException whose name is NotSupportedError.
   7428  // 5.2.2 If the association cannot currently be removed, let this object's
   7429  // attaching media keys value be false and reject promise with a new
   7430  // DOMException whose name is InvalidStateError.
   7431  if (mDecoder) {
   7432    RefPtr<HTMLMediaElement> self = this;
   7433    mDecoder->SetCDMProxy(nullptr)
   7434        ->Then(
   7435            AbstractMainThread(), __func__,
   7436            [self]() {
   7437              self->mSetCDMRequest.Complete();
   7438 
   7439              self->RemoveMediaKeys();
   7440              if (self->AttachNewMediaKeys()) {
   7441                // No incoming MediaKeys object or MediaDecoder is not
   7442                // created yet.
   7443                self->MakeAssociationWithCDMResolved();
   7444              }
   7445            },
   7446            [self](const MediaResult& aResult) {
   7447              self->mSetCDMRequest.Complete();
   7448              // 5.2.4 If the preceding step failed, let this object's
   7449              // attaching media keys value be false and reject promise with
   7450              // a new DOMException whose name is the appropriate error name.
   7451              self->SetCDMProxyFailure(aResult);
   7452            })
   7453        ->Track(mSetCDMRequest);
   7454    return false;
   7455  }
   7456 
   7457  RemoveMediaKeys();
   7458  return true;
   7459 }
   7460 
   7461 bool HTMLMediaElement::DetachExistingMediaKeys() {
   7462  LOG(LogLevel::Debug, ("%s", __func__));
   7463  MOZ_ASSERT(mSetMediaKeysDOMPromise);
   7464  // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
   7465  // already in use by another media element, and the user agent is unable
   7466  // to use it with this element, let this object's attaching media keys
   7467  // value be false and reject promise with a new DOMException whose name
   7468  // is QuotaExceededError.
   7469  if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
   7470    SetCDMProxyFailure(MediaResult(
   7471        NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR,
   7472        "MediaKeys object is already bound to another HTMLMediaElement"));
   7473    return false;
   7474  }
   7475 
   7476  // 5.2 If the mediaKeys attribute is not null, run the following steps:
   7477  if (mMediaKeys) {
   7478    return TryRemoveMediaKeysAssociation();
   7479  }
   7480  return true;
   7481 }
   7482 
   7483 void HTMLMediaElement::MakeAssociationWithCDMResolved() {
   7484  LOG(LogLevel::Debug, ("%s", __func__));
   7485  MOZ_ASSERT(mSetMediaKeysDOMPromise);
   7486 
   7487  // 5.4 Set the mediaKeys attribute to mediaKeys.
   7488  mMediaKeys = mIncomingMediaKeys;
   7489 #ifdef MOZ_WMF_CDM
   7490  if (mMediaKeys && mMediaKeys->GetCDMProxy()) {
   7491    mIsUsingWMFCDM = !!mMediaKeys->GetCDMProxy()->AsWMFCDMProxy();
   7492  }
   7493 #endif
   7494  // 5.5 Let this object's attaching media keys value be false.
   7495  ResetSetMediaKeysTempVariables();
   7496  // 5.6 Resolve promise.
   7497  mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
   7498  mSetMediaKeysDOMPromise = nullptr;
   7499 
   7500  if (profiler_is_collecting_markers()) {
   7501    if (mMediaKeys) {
   7502      nsString keySystem;
   7503      mMediaKeys->GetKeySystem(keySystem);
   7504      profiler_add_marker("cdmresolved",
   7505                          geckoprofiler::category::MEDIA_PLAYBACK, {},
   7506                          CDMResolvedMarker{}, keySystem,
   7507                          mMediaKeys->GetMediaKeySystemConfigurationString(),
   7508                          Flow::FromPointer(this));
   7509    } else {
   7510      PROFILER_MARKER("removemediakey", MEDIA_PLAYBACK, {}, FlowMarker,
   7511                      Flow::FromPointer(this));
   7512    }
   7513  }
   7514 }
   7515 
   7516 bool HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy) {
   7517  LOG(LogLevel::Debug, ("%s", __func__));
   7518  MOZ_ASSERT(aProxy);
   7519 
   7520  // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
   7521  // algorithm on the media element.
   7522  // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
   7523  if (mDecoder) {
   7524    // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
   7525    // HTMLMediaElement should resolve or reject the DOM promise.
   7526    RefPtr<HTMLMediaElement> self = this;
   7527    mDecoder->SetCDMProxy(aProxy)
   7528        ->Then(
   7529            AbstractMainThread(), __func__,
   7530            [self]() {
   7531              self->mSetCDMRequest.Complete();
   7532              self->MakeAssociationWithCDMResolved();
   7533            },
   7534            [self](const MediaResult& aResult) {
   7535              self->mSetCDMRequest.Complete();
   7536              self->SetCDMProxyFailure(aResult);
   7537            })
   7538        ->Track(mSetCDMRequest);
   7539    return false;
   7540  }
   7541  return true;
   7542 }
   7543 
   7544 bool HTMLMediaElement::AttachNewMediaKeys() {
   7545  LOG(LogLevel::Debug,
   7546      ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
   7547  MOZ_ASSERT(mSetMediaKeysDOMPromise);
   7548 
   7549  // 5.3. If mediaKeys is not null, run the following steps:
   7550  if (mIncomingMediaKeys) {
   7551    auto* cdmProxy = mIncomingMediaKeys->GetCDMProxy();
   7552    if (!cdmProxy) {
   7553      SetCDMProxyFailure(MediaResult(
   7554          NS_ERROR_DOM_INVALID_STATE_ERR,
   7555          "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
   7556      return false;
   7557    }
   7558 
   7559    // 5.3.1 Associate the CDM instance represented by mediaKeys with the
   7560    // media element for decrypting media data.
   7561    if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
   7562      // 5.3.2 If the preceding step failed, run the following steps:
   7563 
   7564      // 5.3.2.1 Set the mediaKeys attribute to null.
   7565      mMediaKeys = nullptr;
   7566      // 5.3.2.2 Let this object's attaching media keys value be false.
   7567      // 5.3.2.3 Reject promise with a new DOMException whose name is
   7568      // the appropriate error name.
   7569      SetCDMProxyFailure(
   7570          MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
   7571                      "Failed to bind MediaKeys object to HTMLMediaElement"));
   7572      return false;
   7573    }
   7574    return TryMakeAssociationWithCDM(cdmProxy);
   7575  }
   7576  return true;
   7577 }
   7578 
   7579 void HTMLMediaElement::ResetSetMediaKeysTempVariables() {
   7580  mAttachingMediaKey = false;
   7581  mIncomingMediaKeys = nullptr;
   7582 }
   7583 
   7584 already_AddRefed<Promise> HTMLMediaElement::SetMediaKeys(
   7585    mozilla::dom::MediaKeys* aMediaKeys, ErrorResult& aRv) {
   7586  LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p", this,
   7587                        aMediaKeys, mMediaKeys.get(), mDecoder.get()));
   7588 
   7589  if (MozAudioCaptured()) {
   7590    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   7591    return nullptr;
   7592  }
   7593 
   7594  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
   7595  if (!win) {
   7596    aRv.Throw(NS_ERROR_UNEXPECTED);
   7597    return nullptr;
   7598  }
   7599  RefPtr<DetailedPromise> promise = DetailedPromise::Create(
   7600      win->AsGlobal(), aRv, "HTMLMediaElement.setMediaKeys"_ns);
   7601  if (aRv.Failed()) {
   7602    return nullptr;
   7603  }
   7604 
   7605  // 1. If mediaKeys and the mediaKeys attribute are the same object,
   7606  // return a resolved promise.
   7607  if (mMediaKeys == aMediaKeys) {
   7608    promise->MaybeResolveWithUndefined();
   7609    return promise.forget();
   7610  }
   7611 
   7612  // 2. If this object's attaching media keys value is true, return a
   7613  // promise rejected with a new DOMException whose name is InvalidStateError.
   7614  if (mAttachingMediaKey) {
   7615    promise->MaybeRejectWithInvalidStateError(
   7616        "A MediaKeys object is in attaching operation.");
   7617    return promise.forget();
   7618  }
   7619 
   7620  // 3. Let this object's attaching media keys value be true.
   7621  mAttachingMediaKey = true;
   7622  mIncomingMediaKeys = aMediaKeys;
   7623 
   7624  // 4. Let promise be a new promise.
   7625  mSetMediaKeysDOMPromise = promise;
   7626 
   7627  // 5. Run the following steps in parallel:
   7628 
   7629  // 5.1 & 5.2 & 5.3
   7630  if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
   7631    return promise.forget();
   7632  }
   7633 
   7634  // 5.4, 5.5, 5.6
   7635  MakeAssociationWithCDMResolved();
   7636 
   7637  // 6. Return promise.
   7638  return promise.forget();
   7639 }
   7640 
   7641 EventHandlerNonNull* HTMLMediaElement::GetOnencrypted() {
   7642  return EventTarget::GetEventHandler(nsGkAtoms::onencrypted);
   7643 }
   7644 
   7645 void HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback) {
   7646  EventTarget::SetEventHandler(nsGkAtoms::onencrypted, aCallback);
   7647 }
   7648 
   7649 EventHandlerNonNull* HTMLMediaElement::GetOnwaitingforkey() {
   7650  return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey);
   7651 }
   7652 
   7653 void HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback) {
   7654  EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, aCallback);
   7655 }
   7656 
   7657 void HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
   7658                                         const nsAString& aInitDataType) {
   7659  LOG(LogLevel::Debug, ("%p DispatchEncrypted initDataType='%s'", this,
   7660                        NS_ConvertUTF16toUTF8(aInitDataType).get()));
   7661 
   7662  if (mReadyState == HAVE_NOTHING) {
   7663    // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
   7664    // Queueing for later dispatch in MetadataLoaded.
   7665    mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
   7666    return;
   7667  }
   7668 
   7669  RefPtr<MediaEncryptedEvent> event;
   7670  if (IsCORSSameOrigin()) {
   7671    event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
   7672  } else {
   7673    event = MediaEncryptedEvent::Constructor(this);
   7674  }
   7675 
   7676  RefPtr<AsyncEventDispatcher> asyncDispatcher =
   7677      new AsyncEventDispatcher(this, event.forget());
   7678  asyncDispatcher->PostDOMEvent();
   7679  if (profiler_is_collecting_markers()) {
   7680    nsPrintfCString markerName{"%p:encrypted", this};
   7681    PROFILER_MARKER_UNTYPED(markerName, MEDIA_PLAYBACK);
   7682  }
   7683 }
   7684 
   7685 bool HTMLMediaElement::IsEventAttributeNameInternal(nsAtom* aName) {
   7686  return nsContentUtils::IsEventAttributeName(
   7687      aName, EventNameType_HTML | EventNameType_HTMLMedia);
   7688 }
   7689 
   7690 void HTMLMediaElement::NotifyWaitingForKey() {
   7691  LOG(LogLevel::Debug, ("%p, NotifyWaitingForKey()", this));
   7692 
   7693  // http://w3c.github.io/encrypted-media/#wait-for-key
   7694  // 7.3.4 Queue a "waitingforkey" Event
   7695  // 1. Let the media element be the specified HTMLMediaElement object.
   7696  // 2. If the media element's waiting for key value is true, abort these steps.
   7697  if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
   7698    // 3. Set the media element's waiting for key value to true.
   7699    // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
   7700    // data enqueued in the MDSM is consumed.
   7701    mWaitingForKey = WAITING_FOR_KEY;
   7702    // mWaitingForKey changed outside of UpdateReadyStateInternal. This may
   7703    // affect mReadyState.
   7704    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   7705  }
   7706 }
   7707 
   7708 AudioTrackList* HTMLMediaElement::AudioTracks() { return mAudioTrackList; }
   7709 
   7710 VideoTrackList* HTMLMediaElement::VideoTracks() { return mVideoTrackList; }
   7711 
   7712 TextTrackList* HTMLMediaElement::GetTextTracks() {
   7713  return GetOrCreateTextTrackManager()->GetTextTracks();
   7714 }
   7715 
   7716 already_AddRefed<TextTrack> HTMLMediaElement::AddTextTrack(
   7717    TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) {
   7718  return GetOrCreateTextTrackManager()->AddTextTrack(
   7719      aKind, aLabel, aLanguage, TextTrackMode::Hidden,
   7720      TextTrackReadyState::Loaded, TextTrackSource::AddTextTrack);
   7721 }
   7722 
   7723 void HTMLMediaElement::PopulatePendingTextTrackList() {
   7724  if (mTextTrackManager) {
   7725    mTextTrackManager->PopulatePendingList();
   7726  }
   7727 }
   7728 
   7729 TextTrackManager* HTMLMediaElement::GetOrCreateTextTrackManager() {
   7730  if (!mTextTrackManager) {
   7731    mTextTrackManager = new TextTrackManager(this);
   7732    mTextTrackManager->AddListeners();
   7733  }
   7734  return mTextTrackManager;
   7735 }
   7736 
   7737 MediaDecoderOwner::NextFrameStatus HTMLMediaElement::NextFrameStatus() {
   7738  if (mDecoder) {
   7739    return mDecoder->NextFrameStatus();
   7740  }
   7741  if (mSrcStream) {
   7742    AutoTArray<RefPtr<MediaTrack>, 4> tracks;
   7743    GetAllEnabledMediaTracks(tracks);
   7744    if (!tracks.IsEmpty() && !mSrcStreamPlaybackEnded) {
   7745      return NEXT_FRAME_AVAILABLE;
   7746    }
   7747    return NEXT_FRAME_UNAVAILABLE;
   7748  }
   7749  return NEXT_FRAME_UNINITIALIZED;
   7750 }
   7751 
   7752 void HTMLMediaElement::SetDecoder(MediaDecoder* aDecoder) {
   7753  MOZ_ASSERT(aDecoder);  // Use ShutdownDecoder() to clear.
   7754  if (mDecoder) {
   7755    ShutdownDecoder();
   7756  }
   7757  mDecoder = aDecoder;
   7758  DDLINKCHILD("decoder", mDecoder.get());
   7759  if (mDecoder && mForcedHidden) {
   7760    mDecoder->SetForcedHidden(mForcedHidden);
   7761  }
   7762 }
   7763 
   7764 float HTMLMediaElement::ComputedVolume() const {
   7765  return mMuted                 ? 0.0f
   7766         : mAudioChannelWrapper ? mAudioChannelWrapper->GetEffectiveVolume()
   7767                                : static_cast<float>(mVolume);
   7768 }
   7769 
   7770 bool HTMLMediaElement::ComputedMuted() const {
   7771  return (mMuted & MUTED_BY_AUDIO_CHANNEL);
   7772 }
   7773 
   7774 bool HTMLMediaElement::IsSuspendedByInactiveDocOrDocShell() const {
   7775  return mSuspendedByInactiveDocOrDocshell;
   7776 }
   7777 
   7778 bool HTMLMediaElement::IsCurrentlyPlaying() const {
   7779  // We have playable data, but we still need to check whether data is "real"
   7780  // current data.
   7781  return mReadyState >= HAVE_CURRENT_DATA && !IsPlaybackEnded();
   7782 }
   7783 
   7784 void HTMLMediaElement::SetAudibleState(bool aAudible) {
   7785  if (mIsAudioTrackAudible != aAudible) {
   7786    mIsAudioTrackAudible = aAudible;
   7787    NotifyAudioPlaybackChanged(
   7788        AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
   7789  }
   7790 }
   7791 
   7792 void HTMLMediaElement::NotifyAudioPlaybackChanged(
   7793    AudibleChangedReasons aReason) {
   7794  if (mAudioChannelWrapper) {
   7795    mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason);
   7796  }
   7797  // We would start the listener after media becomes audible.
   7798  const bool isAudible = IsAudible();
   7799  if (isAudible && !mMediaControlKeyListener->IsStarted()) {
   7800    StartMediaControlKeyListenerIfNeeded();
   7801  }
   7802  mMediaControlKeyListener->UpdateMediaAudibleState(isAudible);
   7803  // only request wake lock for audible media.
   7804  UpdateWakeLock();
   7805 }
   7806 
   7807 void HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo) {
   7808  const bool oldHasAudio = mMediaInfo.HasAudio();
   7809  mMediaInfo = aInfo;
   7810  if ((aInfo.HasAudio() != oldHasAudio) && mResumeDelayedPlaybackAgent) {
   7811    mResumeDelayedPlaybackAgent->UpdateAudibleState(this, IsAudible());
   7812  }
   7813  nsILoadContext* loadContext = OwnerDoc()->GetLoadContext();
   7814  if (HasAudio() && loadContext && !loadContext->UsePrivateBrowsing()) {
   7815    mTitleChangeObserver->Subscribe();
   7816    UpdateStreamName();
   7817  } else {
   7818    mTitleChangeObserver->Unsubscribe();
   7819  }
   7820  if (mAudioChannelWrapper) {
   7821    mAudioChannelWrapper->AudioCaptureTrackChangeIfNeeded();
   7822  }
   7823  UpdateWakeLock();
   7824 }
   7825 
   7826 MediaInfo HTMLMediaElement::GetMediaInfo() const { return mMediaInfo; }
   7827 
   7828 FrameStatistics* HTMLMediaElement::GetFrameStatistics() const {
   7829  return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr;
   7830 }
   7831 
   7832 void HTMLMediaElement::DispatchAsyncTestingEvent(const nsAString& aName) {
   7833  if (!StaticPrefs::media_testing_only_events()) {
   7834    return;
   7835  }
   7836  QueueEvent(aName);
   7837 }
   7838 
   7839 void HTMLMediaElement::AudioCaptureTrackChange(bool aCapture) {
   7840  // No need to capture a silent media element.
   7841  if (!HasAudio()) {
   7842    return;
   7843  }
   7844 
   7845  if (aCapture && !mStreamWindowCapturer) {
   7846    nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
   7847    if (!window) {
   7848      return;
   7849    }
   7850 
   7851    MediaTrackGraph* mtg = MediaTrackGraph::GetInstance(
   7852        MediaTrackGraph::AUDIO_THREAD_DRIVER, window,
   7853        MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
   7854        MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
   7855    RefPtr<DOMMediaStream> stream =
   7856        CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
   7857                              StreamCaptureType::CAPTURE_AUDIO, mtg);
   7858    mStreamWindowCapturer =
   7859        new MediaStreamWindowCapturer(stream, window->WindowID());
   7860    mStreamWindowCapturer->mStream->RegisterTrackListener(
   7861        mStreamWindowCapturer);
   7862  } else if (!aCapture && mStreamWindowCapturer) {
   7863    for (size_t i = 0; i < mOutputStreams.Length(); i++) {
   7864      if (mOutputStreams[i].mStream == mStreamWindowCapturer->mStream) {
   7865        // We own this MediaStream, it is not exposed to JS.
   7866        AutoTArray<RefPtr<MediaStreamTrack>, 2> tracks;
   7867        mStreamWindowCapturer->mStream->GetTracks(tracks);
   7868        for (auto& track : tracks) {
   7869          track->Stop();
   7870        }
   7871        mOutputStreams.RemoveElementAt(i);
   7872        break;
   7873      }
   7874    }
   7875 
   7876    mStreamWindowCapturer->mStream->UnregisterTrackListener(
   7877        mStreamWindowCapturer);
   7878    mStreamWindowCapturer = nullptr;
   7879    if (mOutputStreams.IsEmpty()) {
   7880      mTracksCaptured = nullptr;
   7881    }
   7882  }
   7883 }
   7884 
   7885 void HTMLMediaElement::NotifyCueDisplayStatesChanged() {
   7886  if (!mTextTrackManager) {
   7887    return;
   7888  }
   7889 
   7890  mTextTrackManager->DispatchUpdateCueDisplay();
   7891 }
   7892 
   7893 void HTMLMediaElement::LogVisibility(CallerAPI aAPI) {
   7894  const bool isVisible = mVisibilityState == Visibility::ApproximatelyVisible;
   7895 
   7896  LOG(LogLevel::Debug, ("%p visibility = %u, API: '%d' and 'All'", this,
   7897                        isVisible, static_cast<int>(aAPI)));
   7898 
   7899  if (!isVisible) {
   7900    LOG(LogLevel::Debug, ("%p inTree = %u, API: '%d' and 'All'", this,
   7901                          IsInComposedDoc(), static_cast<int>(aAPI)));
   7902  }
   7903 }
   7904 
   7905 void HTMLMediaElement::UpdateCustomPolicyAfterPlayed() {
   7906  if (mAudioChannelWrapper) {
   7907    mAudioChannelWrapper->NotifyPlayStateChanged();
   7908  }
   7909 }
   7910 
   7911 nsTArray<RefPtr<PlayPromise>> HTMLMediaElement::TakePendingPlayPromises() {
   7912  return std::move(mPendingPlayPromises);
   7913 }
   7914 
   7915 void HTMLMediaElement::NotifyAboutPlaying() {
   7916  // Stick to the QueueEvent() call path for now because we want to
   7917  // trigger some telemetry-related codes in the QueueEvent() method.
   7918  QueueEvent(u"playing"_ns);
   7919 }
   7920 
   7921 already_AddRefed<PlayPromise> HTMLMediaElement::CreatePlayPromise(
   7922    ErrorResult& aRv) const {
   7923  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
   7924 
   7925  if (!win) {
   7926    aRv.Throw(NS_ERROR_UNEXPECTED);
   7927    return nullptr;
   7928  }
   7929 
   7930  RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv);
   7931  LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get()));
   7932 
   7933  return promise.forget();
   7934 }
   7935 
   7936 already_AddRefed<Promise> HTMLMediaElement::CreateDOMPromise(
   7937    ErrorResult& aRv) const {
   7938  nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
   7939 
   7940  if (!win) {
   7941    aRv.Throw(NS_ERROR_UNEXPECTED);
   7942    return nullptr;
   7943  }
   7944 
   7945  return Promise::Create(win->AsGlobal(), aRv);
   7946 }
   7947 
   7948 void HTMLMediaElement::AsyncResolvePendingPlayPromises() {
   7949  if (mShuttingDown) {
   7950    return;
   7951  }
   7952 
   7953  nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
   7954      this, TakePendingPlayPromises());
   7955 
   7956  GetMainThreadSerialEventTarget()->Dispatch(event.forget());
   7957 }
   7958 
   7959 void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError) {
   7960  if (!mPaused) {
   7961    mPaused = true;
   7962    QueueEvent(u"pause"_ns);
   7963  }
   7964 
   7965  if (mShuttingDown) {
   7966    return;
   7967  }
   7968 
   7969  if (aError == NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR) {
   7970    DispatchEventsWhenPlayWasNotAllowed();
   7971  }
   7972 
   7973  nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
   7974      this, TakePendingPlayPromises(), aError);
   7975 
   7976  GetMainThreadSerialEventTarget()->Dispatch(event.forget());
   7977 }
   7978 
   7979 void HTMLMediaElement::GetEMEInfo(dom::EMEDebugInfo& aInfo) {
   7980  MOZ_ASSERT(NS_IsMainThread(),
   7981             "MediaKeys expects to be interacted with on main thread!");
   7982  if (!mMediaKeys) {
   7983    return;
   7984  }
   7985  mMediaKeys->GetKeySystem(aInfo.mKeySystem);
   7986  mMediaKeys->GetSessionsInfo(aInfo.mSessionsInfo);
   7987 }
   7988 
   7989 void HTMLMediaElement::NotifyDecoderActivityChanges() const {
   7990  if (mDecoder) {
   7991    mDecoder->NotifyOwnerActivityChanged(
   7992        IsActuallyInvisible(), IsInComposedDoc(),
   7993        OwnerDoc()->IsInBackgroundWindow(), HasPendingCallbacks());
   7994  }
   7995 }
   7996 
   7997 Document* HTMLMediaElement::GetDocument() const { return OwnerDoc(); }
   7998 
   7999 bool HTMLMediaElement::IsAudible() const {
   8000  // No audio track.
   8001  if (!HasAudio()) {
   8002    return false;
   8003  }
   8004 
   8005  // Muted or the volume should not be ~0
   8006  if (mMuted || (std::fabs(Volume()) <= 1e-7)) {
   8007    return false;
   8008  }
   8009 
   8010  return mIsAudioTrackAudible;
   8011 }
   8012 
   8013 Maybe<nsAutoString> HTMLMediaElement::GetKeySystem() const {
   8014  if (!mMediaKeys) {
   8015    return Nothing();
   8016  }
   8017  nsAutoString keySystem;
   8018  mMediaKeys->GetKeySystem(keySystem);
   8019  return Some(keySystem);
   8020 }
   8021 
   8022 void HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo) {
   8023  if (!aInfo) {
   8024    return;
   8025  }
   8026 
   8027  AudioTrackList* audioList = AudioTracks();
   8028  if (audioList && aInfo->HasAudio()) {
   8029    const TrackInfo& info = aInfo->mAudio;
   8030    RefPtr<AudioTrack> track = MediaTrackList::CreateAudioTrack(
   8031        audioList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel,
   8032        info.mLanguage, info.mEnabled);
   8033 
   8034    audioList->AddTrack(track);
   8035  }
   8036 
   8037  VideoTrackList* videoList = VideoTracks();
   8038  if (videoList && aInfo->HasVideo()) {
   8039    const TrackInfo& info = aInfo->mVideo;
   8040    RefPtr<VideoTrack> track = MediaTrackList::CreateVideoTrack(
   8041        videoList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel,
   8042        info.mLanguage);
   8043 
   8044    videoList->AddTrack(track);
   8045    track->SetEnabledInternal(info.mEnabled, MediaTrack::FIRE_NO_EVENTS);
   8046  }
   8047 }
   8048 
   8049 void HTMLMediaElement::RemoveMediaTracks() {
   8050  if (mAudioTrackList) {
   8051    mAudioTrackList->RemoveTracks();
   8052  }
   8053  if (mVideoTrackList) {
   8054    mVideoTrackList->RemoveTracks();
   8055  }
   8056 }
   8057 
   8058 class MediaElementGMPCrashHelper : public GMPCrashHelper {
   8059 public:
   8060  explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
   8061      : mElement(aElement) {
   8062    MOZ_ASSERT(NS_IsMainThread());  // WeakPtr isn't thread safe.
   8063  }
   8064  already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override {
   8065    MOZ_ASSERT(NS_IsMainThread());  // WeakPtr isn't thread safe.
   8066    if (!mElement) {
   8067      return nullptr;
   8068    }
   8069    return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
   8070  }
   8071 
   8072 private:
   8073  WeakPtr<HTMLMediaElement> mElement;
   8074 };
   8075 
   8076 already_AddRefed<GMPCrashHelper> HTMLMediaElement::CreateGMPCrashHelper() {
   8077  return MakeAndAddRef<MediaElementGMPCrashHelper>(this);
   8078 }
   8079 
   8080 void HTMLMediaElement::MarkAsTainted() {
   8081  mHasSuspendTaint = true;
   8082 
   8083  if (mDecoder) {
   8084    mDecoder->SetSuspendTaint(true);
   8085  }
   8086 }
   8087 
   8088 bool HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj) {
   8089  return nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::debugger) ||
   8090         nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::tabs);
   8091 }
   8092 
   8093 already_AddRefed<Promise> HTMLMediaElement::SetSinkId(const nsAString& aSinkId,
   8094                                                      ErrorResult& aRv) {
   8095  LOG(LogLevel::Info,
   8096      ("%p, setSinkId(%s)", this, NS_ConvertUTF16toUTF8(aSinkId).get()));
   8097  nsCOMPtr<nsPIDOMWindowInner> win = OwnerDoc()->GetInnerWindow();
   8098  if (!win) {
   8099    aRv.Throw(NS_ERROR_UNEXPECTED);
   8100    return nullptr;
   8101  }
   8102 
   8103  RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv);
   8104  if (aRv.Failed()) {
   8105    return nullptr;
   8106  }
   8107 
   8108  if (!FeaturePolicyUtils::IsFeatureAllowed(win->GetExtantDoc(),
   8109                                            u"speaker-selection"_ns)) {
   8110    promise->MaybeRejectWithNotAllowedError(
   8111        "Document's Permissions Policy does not allow setSinkId()");
   8112  }
   8113 
   8114  if (mSink.first.Equals(aSinkId)) {
   8115    promise->MaybeResolveWithUndefined();
   8116    return promise.forget();
   8117  }
   8118 
   8119  RefPtr<MediaDevices> mediaDevices = win->Navigator()->GetMediaDevices(aRv);
   8120  if (aRv.Failed()) {
   8121    return nullptr;
   8122  }
   8123 
   8124  nsString sinkId(aSinkId);
   8125  mediaDevices->GetSinkDevice(sinkId)
   8126      ->Then(
   8127          AbstractMainThread(), __func__,
   8128          [self = RefPtr<HTMLMediaElement>(this),
   8129           this](RefPtr<AudioDeviceInfo>&& aInfo) {
   8130            // Sink found switch output device.
   8131            MOZ_ASSERT(aInfo);
   8132            if (mDecoder) {
   8133              RefPtr<SinkInfoPromise> p = mDecoder->SetSink(aInfo)->Then(
   8134                  AbstractMainThread(), __func__,
   8135                  [aInfo](const GenericPromise::ResolveOrRejectValue& aValue) {
   8136                    if (aValue.IsResolve()) {
   8137                      return SinkInfoPromise::CreateAndResolve(aInfo, __func__);
   8138                    }
   8139                    return SinkInfoPromise::CreateAndReject(
   8140                        aValue.RejectValue(), __func__);
   8141                  });
   8142              return p;
   8143            }
   8144            if (mSrcStream) {
   8145              MOZ_ASSERT(mMediaStreamRenderer);
   8146              RefPtr<SinkInfoPromise> p =
   8147                  mMediaStreamRenderer->SetAudioOutputDevice(aInfo)->Then(
   8148                      AbstractMainThread(), __func__,
   8149                      [aInfo](
   8150                          const GenericPromise::ResolveOrRejectValue& aValue) {
   8151                        if (aValue.IsResolve()) {
   8152                          return SinkInfoPromise::CreateAndResolve(aInfo,
   8153                                                                   __func__);
   8154                        }
   8155                        return SinkInfoPromise::CreateAndReject(
   8156                            aValue.RejectValue(), __func__);
   8157                      });
   8158              return p;
   8159            }
   8160            // No media attached to the element save it for later.
   8161            return SinkInfoPromise::CreateAndResolve(aInfo, __func__);
   8162          },
   8163          [](nsresult res) {
   8164            // Promise is rejected, sink not found.
   8165            return SinkInfoPromise::CreateAndReject(res, __func__);
   8166          })
   8167      ->Then(AbstractMainThread(), __func__,
   8168             [promise, self = RefPtr<HTMLMediaElement>(this), this,
   8169              sinkId](const SinkInfoPromise::ResolveOrRejectValue& aValue) {
   8170               if (aValue.IsResolve()) {
   8171                 LOG(LogLevel::Info, ("%p, set sinkid=%s", this,
   8172                                      NS_ConvertUTF16toUTF8(sinkId).get()));
   8173                 mSink = std::pair(sinkId, aValue.ResolveValue());
   8174                 promise->MaybeResolveWithUndefined();
   8175               } else {
   8176                 switch (aValue.RejectValue()) {
   8177                   case NS_ERROR_ABORT:
   8178                     promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   8179                     break;
   8180                   case NS_ERROR_NOT_AVAILABLE: {
   8181                     promise->MaybeRejectWithNotFoundError(
   8182                         "The object can not be found here.");
   8183                     break;
   8184                   }
   8185                   default:
   8186                     MOZ_ASSERT_UNREACHABLE("Invalid error.");
   8187                 }
   8188               }
   8189             });
   8190 
   8191  aRv = NS_OK;
   8192  return promise.forget();
   8193 }
   8194 
   8195 void HTMLMediaElement::NotifyTextTrackModeChanged() {
   8196  if (mPendingTextTrackChanged) {
   8197    return;
   8198  }
   8199  mPendingTextTrackChanged = true;
   8200  AbstractMainThread()->Dispatch(
   8201      NS_NewRunnableFunction("HTMLMediaElement::NotifyTextTrackModeChanged",
   8202                             [this, self = RefPtr<HTMLMediaElement>(this)]() {
   8203                               mPendingTextTrackChanged = false;
   8204                               if (!mTextTrackManager) {
   8205                                 return;
   8206                               }
   8207                               GetTextTracks()->CreateAndDispatchChangeEvent();
   8208                               // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag
   8209                               if (!mShowPoster) {
   8210                                 mTextTrackManager->TimeMarchesOn();
   8211                               }
   8212                             }));
   8213 }
   8214 
   8215 void HTMLMediaElement::CreateResumeDelayedMediaPlaybackAgentIfNeeded() {
   8216  if (mResumeDelayedPlaybackAgent) {
   8217    return;
   8218  }
   8219  mResumeDelayedPlaybackAgent =
   8220      MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(this,
   8221                                                                 IsAudible());
   8222  if (!mResumeDelayedPlaybackAgent) {
   8223    LOG(LogLevel::Debug,
   8224        ("%p Failed to create a delayed playback agant", this));
   8225    return;
   8226  }
   8227  mResumeDelayedPlaybackAgent->GetResumePromise()
   8228      ->Then(
   8229          AbstractMainThread(), __func__,
   8230          [self = RefPtr<HTMLMediaElement>(this)]() {
   8231            LOG(LogLevel::Debug, ("%p Resume delayed Play() call", self.get()));
   8232            self->mResumePlaybackRequest.Complete();
   8233            self->mResumeDelayedPlaybackAgent = nullptr;
   8234            IgnoredErrorResult dummy;
   8235            RefPtr<Promise> toBeIgnored = self->Play(dummy);
   8236          },
   8237          [self = RefPtr<HTMLMediaElement>(this)]() {
   8238            LOG(LogLevel::Debug,
   8239                ("%p Can not resume delayed Play() call", self.get()));
   8240            self->mResumePlaybackRequest.Complete();
   8241            self->mResumeDelayedPlaybackAgent = nullptr;
   8242          })
   8243      ->Track(mResumePlaybackRequest);
   8244 }
   8245 
   8246 void HTMLMediaElement::ClearResumeDelayedMediaPlaybackAgentIfNeeded() {
   8247  if (mResumeDelayedPlaybackAgent) {
   8248    mResumePlaybackRequest.DisconnectIfExists();
   8249    mResumeDelayedPlaybackAgent = nullptr;
   8250  }
   8251 }
   8252 
   8253 void HTMLMediaElement::NotifyMediaControlPlaybackStateChanged() {
   8254  if (!mMediaControlKeyListener->IsStarted()) {
   8255    return;
   8256  }
   8257  if (mPaused) {
   8258    mMediaControlKeyListener->NotifyMediaStoppedPlaying();
   8259  } else {
   8260    mMediaControlKeyListener->NotifyMediaStartedPlaying();
   8261  }
   8262 }
   8263 
   8264 bool HTMLMediaElement::IsInFullScreen() const {
   8265  return State().HasState(ElementState::FULLSCREEN);
   8266 }
   8267 
   8268 bool HTMLMediaElement::IsPlayable() const {
   8269  return (mDecoder || mSrcStream) && !HasError();
   8270 }
   8271 
   8272 bool HTMLMediaElement::ShouldStartMediaControlKeyListener() const {
   8273  if (!IsPlayable()) {
   8274    MEDIACONTROL_LOG("Not start listener because media is not playable");
   8275    return false;
   8276  }
   8277 
   8278  if (mSrcStream) {
   8279    MEDIACONTROL_LOG("Not listening because media is real-time");
   8280    return false;
   8281  }
   8282 
   8283  if (IsBeingUsedInPictureInPictureMode()) {
   8284    MEDIACONTROL_LOG("Start listener because of being used in PiP mode");
   8285    return true;
   8286  }
   8287 
   8288  if (IsInFullScreen()) {
   8289    MEDIACONTROL_LOG("Start listener because of being used in fullscreen");
   8290    return true;
   8291  }
   8292 
   8293  // In order to filter out notification-ish sound, we use this pref to set the
   8294  // eligible media duration to prevent showing media control for those short
   8295  // sound.
   8296  if (Duration() <
   8297      StaticPrefs::media_mediacontrol_eligible_media_duration_s()) {
   8298    MEDIACONTROL_LOG("Not listening because media's duration %f is too short.",
   8299                     Duration());
   8300    return false;
   8301  }
   8302 
   8303  // This includes cases such like `video is muted`, `video has zero volume`,
   8304  // `video's audio track is still inaudible` and `tab is muted by audio channel
   8305  // (tab sound indicator)`, all these cases would make media inaudible.
   8306  // `ComputedVolume()` would return the final volume applied the affection made
   8307  // by audio channel, which is used to detect if the tab is muted by audio
   8308  // channel.
   8309  if (!IsAudible() || ComputedVolume() == 0.0f) {
   8310    MEDIACONTROL_LOG("Not listening because media is inaudible");
   8311    return false;
   8312  }
   8313  return true;
   8314 }
   8315 
   8316 void HTMLMediaElement::StartMediaControlKeyListenerIfNeeded() {
   8317  if (!ShouldStartMediaControlKeyListener()) {
   8318    return;
   8319  }
   8320  mMediaControlKeyListener->Start();
   8321 }
   8322 
   8323 void HTMLMediaElement::UpdateStreamName() {
   8324  MOZ_ASSERT(NS_IsMainThread());
   8325 
   8326  nsAutoString aTitle;
   8327  OwnerDoc()->GetTitle(aTitle);
   8328 
   8329  if (mDecoder) {
   8330    mDecoder->SetStreamName(aTitle);
   8331  }
   8332 }
   8333 
   8334 void HTMLMediaElement::SetSecondaryMediaStreamRenderer(
   8335    VideoFrameContainer* aContainer,
   8336    FirstFrameVideoOutput* aFirstFrameOutput /* = nullptr */) {
   8337  MOZ_ASSERT(mSrcStream);
   8338  MOZ_ASSERT(mMediaStreamRenderer);
   8339  if (mSecondaryMediaStreamRenderer) {
   8340    mSecondaryMediaStreamRenderer->Shutdown();
   8341    mSecondaryMediaStreamRenderer = nullptr;
   8342  }
   8343  if (aContainer) {
   8344    mSecondaryMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>(
   8345        AbstractMainThread(), aContainer, aFirstFrameOutput, this);
   8346    if (mSrcStreamIsPlaying) {
   8347      mSecondaryMediaStreamRenderer->Start();
   8348    }
   8349    if (mSelectedVideoStreamTrack) {
   8350      mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
   8351    }
   8352  }
   8353 }
   8354 
   8355 void HTMLMediaElement::UpdateMediaControlAfterPictureInPictureModeChanged() {
   8356  if (IsBeingUsedInPictureInPictureMode()) {
   8357    // When media enters PIP mode, we should ensure that the listener has been
   8358    // started because we always want to control PIP video.
   8359    StartMediaControlKeyListenerIfNeeded();
   8360    if (!mMediaControlKeyListener->IsStarted()) {
   8361      MEDIACONTROL_LOG("Failed to start listener when entering PIP mode");
   8362    }
   8363    // Updating controller PIP state no matter the listener starts or not.
   8364    mMediaControlKeyListener->SetPictureInPictureModeEnabled(true);
   8365  } else {
   8366    mMediaControlKeyListener->SetPictureInPictureModeEnabled(false);
   8367  }
   8368 }
   8369 
   8370 bool HTMLMediaElement::IsBeingUsedInPictureInPictureMode() const {
   8371  if (!IsVideo()) {
   8372    return false;
   8373  }
   8374  return static_cast<const HTMLVideoElement*>(this)->IsCloningElementVisually();
   8375 }
   8376 
   8377 void HTMLMediaElement::NodeInfoChanged(Document* aOldDoc) {
   8378  if (mMediaSource) {
   8379    OwnerDoc()->AddMediaElementWithMSE();
   8380    aOldDoc->RemoveMediaElementWithMSE();
   8381  }
   8382 
   8383  nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
   8384 }
   8385 
   8386 void HTMLMediaElement::MaybeMarkSHEntryAsUserInteracted() {
   8387  if (media::AutoplayPolicy::GetAutoplayPolicy(*this) ==
   8388      dom::AutoplayPolicy::Allowed) {
   8389    // Only mark entries when autoplay is allowed for both audio and video,
   8390    // i.e. when AutoplayPolicy is not Blocked or Allowed_muted.
   8391    OwnerDoc()->SetSHEntryHasUserInteraction(true);
   8392  }
   8393 }
   8394 
   8395 #ifdef MOZ_WMF_CDM
   8396 bool HTMLMediaElement::IsUsingWMFCDM() const { return mIsUsingWMFCDM; };
   8397 
   8398 CDMProxy* HTMLMediaElement::GetCDMProxy() const {
   8399  return mMediaKeys ? mMediaKeys->GetCDMProxy() : nullptr;
   8400 };
   8401 #endif
   8402 
   8403 }  // namespace mozilla::dom
   8404 
   8405 #undef LOG
   8406 #undef LOG_EVENT