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/. */
7 #include "mozilla/dom/HTMLVideoElement.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/dom/HTMLVideoElementBinding.h"
11 #include "nsGenericHTMLElement.h"
12 #include "nsGkAtoms.h"
15 #include "nsIHttpChannel.h"
16 #include "nsNodeInfoManager.h"
19 #include "nsRFPService.h"
20 #include "nsThreadUtils.h"
21 #include "ImageContainer.h"
22 #include "VideoFrameContainer.h"
23 #include "VideoOutput.h"
25 #include "FrameStatistics.h"
26 #include "MediaError.h"
27 #include "MediaDecoder.h"
28 #include "MediaDecoderStateMachine.h"
29 #include "mozilla/Preferences.h"
30 #include "mozilla/dom/WakeLock.h"
31 #include "mozilla/dom/power/PowerManagerService.h"
32 #include "mozilla/dom/Performance.h"
33 #include "mozilla/dom/TimeRanges.h"
34 #include "mozilla/dom/VideoPlaybackQuality.h"
35 #include "mozilla/dom/VideoStreamTrack.h"
36 #include "mozilla/StaticPrefs_media.h"
37 #include "mozilla/Unused.h"
42 extern mozilla::LazyLogModule gMediaElementLog
;
43 #define LOG(msg, ...) \
44 MOZ_LOG(gMediaElementLog, LogLevel::Debug, \
45 ("HTMLVideoElement=%p, " msg, this, ##__VA_ARGS__))
47 nsGenericHTMLElement
* NS_NewHTMLVideoElement(
48 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
49 mozilla::dom::FromParser aFromParser
) {
50 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
51 auto* nim
= nodeInfo
->NodeInfoManager();
52 mozilla::dom::HTMLVideoElement
* element
=
53 new (nim
) mozilla::dom::HTMLVideoElement(nodeInfo
.forget());
58 namespace mozilla::dom
{
60 nsresult
HTMLVideoElement::Clone(mozilla::dom::NodeInfo
* aNodeInfo
,
61 nsINode
** aResult
) const {
63 RefPtr
<mozilla::dom::NodeInfo
> ni(aNodeInfo
);
64 auto* nim
= ni
->NodeInfoManager();
65 HTMLVideoElement
* it
= new (nim
) HTMLVideoElement(ni
.forget());
67 nsCOMPtr
<nsINode
> kungFuDeathGrip
= it
;
68 nsresult rv
= const_cast<HTMLVideoElement
*>(this)->CopyInnerTo(it
);
69 if (NS_SUCCEEDED(rv
)) {
70 kungFuDeathGrip
.swap(*aResult
);
75 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement
,
78 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLVideoElement
)
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLVideoElement
)
81 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTarget
)
82 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTargetPromise
)
83 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneSource
)
84 tmp
->mSecondaryVideoOutput
= nullptr;
85 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(HTMLMediaElement
)
87 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLVideoElement
,
89 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTarget
)
90 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTargetPromise
)
91 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneSource
)
92 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
94 HTMLVideoElement::HTMLVideoElement(already_AddRefed
<NodeInfo
>&& aNodeInfo
)
95 : HTMLMediaElement(std::move(aNodeInfo
)),
96 mVideoWatchManager(this, AbstractThread::MainThread()) {
97 DecoderDoctorLogger::LogConstruction(this);
100 HTMLVideoElement::~HTMLVideoElement() {
101 mVideoWatchManager
.Shutdown();
102 DecoderDoctorLogger::LogDestruction(this);
105 void HTMLVideoElement::UpdateMediaSize(const nsIntSize
& aSize
) {
106 HTMLMediaElement::UpdateMediaSize(aSize
);
107 // If we have a clone target, we should update its size as well.
108 if (mVisualCloneTarget
) {
109 Maybe
<nsIntSize
> newSize
= Some(aSize
);
110 mVisualCloneTarget
->Invalidate(ImageSizeChanged::Yes
, newSize
,
111 ForceInvalidate::Yes
);
115 Maybe
<CSSIntSize
> HTMLVideoElement::GetVideoSize() const {
116 if (!mMediaInfo
.HasVideo()) {
125 switch (mMediaInfo
.mVideo
.mRotation
) {
126 case VideoRotation::kDegree_90
:
127 case VideoRotation::kDegree_270
: {
128 size
.width
= mMediaInfo
.mVideo
.mDisplay
.height
;
129 size
.height
= mMediaInfo
.mVideo
.mDisplay
.width
;
132 case VideoRotation::kDegree_0
:
133 case VideoRotation::kDegree_180
:
135 size
.height
= mMediaInfo
.mVideo
.mDisplay
.height
;
136 size
.width
= mMediaInfo
.mVideo
.mDisplay
.width
;
143 void HTMLVideoElement::Invalidate(ImageSizeChanged aImageSizeChanged
,
144 const Maybe
<nsIntSize
>& aNewIntrinsicSize
,
145 ForceInvalidate aForceInvalidate
) {
146 HTMLMediaElement::Invalidate(aImageSizeChanged
, aNewIntrinsicSize
,
148 if (mVisualCloneTarget
) {
149 VideoFrameContainer
* container
=
150 mVisualCloneTarget
->GetVideoFrameContainer();
152 container
->Invalidate();
157 bool HTMLVideoElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
158 const nsAString
& aValue
,
159 nsIPrincipal
* aMaybeScriptedPrincipal
,
160 nsAttrValue
& aResult
) {
161 if (aAttribute
== nsGkAtoms::width
|| aAttribute
== nsGkAtoms::height
) {
162 return aResult
.ParseHTMLDimension(aValue
);
165 return HTMLMediaElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
166 aMaybeScriptedPrincipal
, aResult
);
169 void HTMLVideoElement::MapAttributesIntoRule(
170 MappedDeclarationsBuilder
& aBuilder
) {
171 MapImageSizeAttributesInto(aBuilder
, MapAspectRatio::Yes
);
172 MapCommonAttributesInto(aBuilder
);
176 HTMLVideoElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
177 static const MappedAttributeEntry attributes
[] = {
178 {nsGkAtoms::width
}, {nsGkAtoms::height
}, {nullptr}};
180 static const MappedAttributeEntry
* const map
[] = {attributes
,
181 sCommonAttributeMap
};
183 return FindAttributeDependence(aAttribute
, map
);
186 nsMapRuleToAttributesFunc
HTMLVideoElement::GetAttributeMappingFunction()
188 return &MapAttributesIntoRule
;
191 void HTMLVideoElement::UnbindFromTree(UnbindContext
& aContext
) {
192 if (mVisualCloneSource
) {
193 mVisualCloneSource
->EndCloningVisually();
194 } else if (mVisualCloneTarget
) {
195 AsyncEventDispatcher::RunDOMEventWhenSafe(
196 *this, u
"MozStopPictureInPicture"_ns
, CanBubble::eNo
,
197 ChromeOnlyDispatch::eYes
);
198 EndCloningVisually();
201 HTMLMediaElement::UnbindFromTree(aContext
);
204 nsresult
HTMLVideoElement::SetAcceptHeader(nsIHttpChannel
* aChannel
) {
209 "application/ogg;q=0.7,"
210 "audio/*;q=0.6,*/*;q=0.5");
212 return aChannel
->SetRequestHeader("Accept"_ns
, value
, false);
215 bool HTMLVideoElement::IsInteractiveHTMLContent() const {
216 return HasAttr(nsGkAtoms::controls
) ||
217 HTMLMediaElement::IsInteractiveHTMLContent();
220 gfx::IntSize
HTMLVideoElement::GetVideoIntrinsicDimensions() {
221 const auto& sz
= mMediaInfo
.mVideo
.mDisplay
;
223 // Prefer the size of the container as it's more up to date.
224 return ToMaybeRef(mVideoFrameContainer
.get())
225 .map([&](auto& aVFC
) { return aVFC
.CurrentIntrinsicSize().valueOr(sz
); })
229 uint32_t HTMLVideoElement::VideoWidth() {
233 gfx::IntSize size
= GetVideoIntrinsicDimensions();
234 if (mMediaInfo
.mVideo
.mRotation
== VideoRotation::kDegree_90
||
235 mMediaInfo
.mVideo
.mRotation
== VideoRotation::kDegree_270
) {
241 uint32_t HTMLVideoElement::VideoHeight() {
245 gfx::IntSize size
= GetVideoIntrinsicDimensions();
246 if (mMediaInfo
.mVideo
.mRotation
== VideoRotation::kDegree_90
||
247 mMediaInfo
.mVideo
.mRotation
== VideoRotation::kDegree_270
) {
253 uint32_t HTMLVideoElement::MozParsedFrames() const {
254 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
255 if (!IsVideoStatsEnabled()) {
259 if (OwnerDoc()->ShouldResistFingerprinting(
260 RFPTarget::VideoElementMozFrames
)) {
261 return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
264 return mDecoder
? mDecoder
->GetFrameStatistics().GetParsedFrames() : 0;
267 uint32_t HTMLVideoElement::MozDecodedFrames() const {
268 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
269 if (!IsVideoStatsEnabled()) {
273 if (OwnerDoc()->ShouldResistFingerprinting(
274 RFPTarget::VideoElementMozFrames
)) {
275 return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
278 return mDecoder
? mDecoder
->GetFrameStatistics().GetDecodedFrames() : 0;
281 uint32_t HTMLVideoElement::MozPresentedFrames() {
282 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
283 if (!IsVideoStatsEnabled()) {
287 if (OwnerDoc()->ShouldResistFingerprinting(
288 RFPTarget::VideoElementMozFrames
)) {
289 return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
290 VideoWidth(), VideoHeight());
293 return mDecoder
? mDecoder
->GetFrameStatistics().GetPresentedFrames() : 0;
296 uint32_t HTMLVideoElement::MozPaintedFrames() {
297 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
298 if (!IsVideoStatsEnabled()) {
302 if (OwnerDoc()->ShouldResistFingerprinting(
303 RFPTarget::VideoElementMozFrames
)) {
304 return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
305 VideoWidth(), VideoHeight());
308 layers::ImageContainer
* container
= GetImageContainer();
309 return container
? container
->GetPaintCount() : 0;
312 double HTMLVideoElement::MozFrameDelay() {
313 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
315 if (!IsVideoStatsEnabled() || OwnerDoc()->ShouldResistFingerprinting(
316 RFPTarget::VideoElementMozFrameDelay
)) {
320 VideoFrameContainer
* container
= GetVideoFrameContainer();
321 // Hide negative delays. Frame timing tweaks in the compositor (e.g.
322 // adding a bias value to prevent multiple dropped/duped frames when
323 // frame times are aligned with composition times) may produce apparent
324 // negative delay, but we shouldn't report that.
325 return container
? std::max(0.0, container
->GetFrameDelay()) : 0.0;
328 bool HTMLVideoElement::MozHasAudio() const {
329 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
333 JSObject
* HTMLVideoElement::WrapNode(JSContext
* aCx
,
334 JS::Handle
<JSObject
*> aGivenProto
) {
335 return HTMLVideoElement_Binding::Wrap(aCx
, this, aGivenProto
);
338 already_AddRefed
<VideoPlaybackQuality
>
339 HTMLVideoElement::GetVideoPlaybackQuality() {
340 DOMHighResTimeStamp creationTime
= 0;
341 uint32_t totalFrames
= 0;
342 uint32_t droppedFrames
= 0;
344 if (IsVideoStatsEnabled()) {
345 if (nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow()) {
346 Performance
* perf
= window
->GetPerformance();
348 creationTime
= perf
->Now();
353 if (OwnerDoc()->ShouldResistFingerprinting(
354 RFPTarget::VideoElementPlaybackQuality
)) {
355 totalFrames
= nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
356 droppedFrames
= nsRFPService::GetSpoofedDroppedFrames(
357 TotalPlayTime(), VideoWidth(), VideoHeight());
359 FrameStatistics
* stats
= &mDecoder
->GetFrameStatistics();
360 if (sizeof(totalFrames
) >= sizeof(stats
->GetParsedFrames())) {
361 totalFrames
= stats
->GetTotalFrames();
362 droppedFrames
= stats
->GetDroppedFrames();
364 uint64_t total
= stats
->GetTotalFrames();
365 const auto maxNumber
= std::numeric_limits
<uint32_t>::max();
366 if (total
<= maxNumber
) {
367 totalFrames
= uint32_t(total
);
368 droppedFrames
= uint32_t(stats
->GetDroppedFrames());
370 // Too big number(s) -> Resize everything to fit in 32 bits.
371 double ratio
= double(maxNumber
) / double(total
);
372 totalFrames
= maxNumber
; // === total * ratio
373 droppedFrames
= uint32_t(double(stats
->GetDroppedFrames()) * ratio
);
377 if (!StaticPrefs::media_video_dropped_frame_stats_enabled()) {
383 RefPtr
<VideoPlaybackQuality
> playbackQuality
=
384 new VideoPlaybackQuality(this, creationTime
, totalFrames
, droppedFrames
);
385 return playbackQuality
.forget();
388 void HTMLVideoElement::WakeLockRelease() {
389 HTMLMediaElement::WakeLockRelease();
390 ReleaseVideoWakeLockIfExists();
393 void HTMLVideoElement::UpdateWakeLock() {
394 HTMLMediaElement::UpdateWakeLock();
396 CreateVideoWakeLockIfNeeded();
398 ReleaseVideoWakeLockIfExists();
402 bool HTMLVideoElement::ShouldCreateVideoWakeLock() const {
403 if (!StaticPrefs::media_video_wakelock()) {
406 // Only request wake lock for video with audio or video from media
407 // stream, because non-stream video without audio is often used as a
410 // Some web conferencing sites route audio outside the video element,
411 // and would not be detected unless we check for media stream, so do
414 // Media streams generally aren't used as background images, though if
415 // they were we'd get false positives. If this is an issue, we could
416 // check for media stream AND document has audio playing (but that was
418 return HasVideo() && (mSrcStream
|| HasAudio());
421 void HTMLVideoElement::CreateVideoWakeLockIfNeeded() {
422 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed
)) {
425 if (!mScreenWakeLock
&& ShouldCreateVideoWakeLock()) {
426 RefPtr
<power::PowerManagerService
> pmService
=
427 power::PowerManagerService::GetInstance();
428 NS_ENSURE_TRUE_VOID(pmService
);
431 mScreenWakeLock
= pmService
->NewWakeLock(u
"video-playing"_ns
,
432 OwnerDoc()->GetInnerWindow(), rv
);
436 void HTMLVideoElement::ReleaseVideoWakeLockIfExists() {
437 if (mScreenWakeLock
) {
439 mScreenWakeLock
->Unlock(rv
);
440 rv
.SuppressException();
441 mScreenWakeLock
= nullptr;
446 bool HTMLVideoElement::SetVisualCloneTarget(
447 RefPtr
<HTMLVideoElement
> aVisualCloneTarget
,
448 RefPtr
<Promise
> aVisualCloneTargetPromise
) {
449 MOZ_DIAGNOSTIC_ASSERT(
450 !aVisualCloneTarget
|| aVisualCloneTarget
->IsInComposedDoc(),
451 "Can't set the clone target to a disconnected video "
453 MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource
,
454 "Can't clone a video element that is already a clone.");
455 if (!aVisualCloneTarget
||
456 (aVisualCloneTarget
->IsInComposedDoc() && !mVisualCloneSource
)) {
457 mVisualCloneTarget
= std::move(aVisualCloneTarget
);
458 mVisualCloneTargetPromise
= std::move(aVisualCloneTargetPromise
);
464 bool HTMLVideoElement::SetVisualCloneSource(
465 RefPtr
<HTMLVideoElement
> aVisualCloneSource
) {
466 MOZ_DIAGNOSTIC_ASSERT(
467 !aVisualCloneSource
|| aVisualCloneSource
->IsInComposedDoc(),
468 "Can't set the clone source to a disconnected video "
470 MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget
,
471 "Can't clone a video element that is already a "
473 if (!aVisualCloneSource
||
474 (aVisualCloneSource
->IsInComposedDoc() && !mVisualCloneTarget
)) {
475 mVisualCloneSource
= std::move(aVisualCloneSource
);
482 bool HTMLVideoElement::IsVideoStatsEnabled() {
483 return StaticPrefs::media_video_stats_enabled();
486 double HTMLVideoElement::TotalPlayTime() const {
490 uint32_t timeRangeCount
= mPlayed
->Length();
492 for (uint32_t i
= 0; i
< timeRangeCount
; i
++) {
493 double begin
= mPlayed
->Start(i
);
494 double end
= mPlayed
->End(i
);
495 total
+= end
- begin
;
498 if (mCurrentPlayRangeStart
!= -1.0) {
499 double now
= CurrentTime();
500 if (mCurrentPlayRangeStart
!= now
) {
501 total
+= now
- mCurrentPlayRangeStart
;
509 already_AddRefed
<Promise
> HTMLVideoElement::CloneElementVisually(
510 HTMLVideoElement
& aTargetVideo
, ErrorResult
& aRv
) {
511 MOZ_ASSERT(IsInComposedDoc(),
512 "Can't clone a video that's not bound to a DOM tree.");
513 MOZ_ASSERT(aTargetVideo
.IsInComposedDoc(),
514 "Can't clone to a video that's not bound to a DOM tree.");
515 if (!IsInComposedDoc() || !aTargetVideo
.IsInComposedDoc()) {
516 aRv
.Throw(NS_ERROR_UNEXPECTED
);
520 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
522 aRv
.Throw(NS_ERROR_UNEXPECTED
);
526 RefPtr
<Promise
> promise
= Promise::Create(win
->AsGlobal(), aRv
);
531 // Do we already have a visual clone target? If so, shut it down.
532 if (mVisualCloneTarget
) {
533 EndCloningVisually();
536 // If there's a poster set on the target video, clear it, otherwise
537 // it'll display over top of the cloned frames.
538 aTargetVideo
.UnsetHTMLAttr(nsGkAtoms::poster
, aRv
);
543 if (!SetVisualCloneTarget(&aTargetVideo
, promise
)) {
544 aRv
.Throw(NS_ERROR_FAILURE
);
548 if (!aTargetVideo
.SetVisualCloneSource(this)) {
549 mVisualCloneTarget
= nullptr;
550 aRv
.Throw(NS_ERROR_FAILURE
);
554 aTargetVideo
.SetMediaInfo(mMediaInfo
);
556 if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
557 NotifyUAWidgetSetupOrChange();
560 MaybeBeginCloningVisually();
562 return promise
.forget();
565 void HTMLVideoElement::StopCloningElementVisually() {
566 if (mVisualCloneTarget
) {
567 EndCloningVisually();
571 void HTMLVideoElement::MaybeBeginCloningVisually() {
572 if (!mVisualCloneTarget
) {
577 mDecoder
->SetSecondaryVideoContainer(
578 mVisualCloneTarget
->GetVideoFrameContainer());
579 NotifyDecoderActivityChanges();
580 UpdateMediaControlAfterPictureInPictureModeChanged();
581 } else if (mSrcStream
) {
582 VideoFrameContainer
* container
=
583 mVisualCloneTarget
->GetVideoFrameContainer();
585 mSecondaryVideoOutput
= MakeRefPtr
<FirstFrameVideoOutput
>(
586 container
, AbstractThread::MainThread());
587 mVideoWatchManager
.Watch(
588 mSecondaryVideoOutput
->mFirstFrameRendered
,
589 &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered
);
590 SetSecondaryMediaStreamRenderer(container
, mSecondaryVideoOutput
);
592 UpdateMediaControlAfterPictureInPictureModeChanged();
596 void HTMLVideoElement::EndCloningVisually() {
597 MOZ_ASSERT(mVisualCloneTarget
);
600 mDecoder
->SetSecondaryVideoContainer(nullptr);
601 NotifyDecoderActivityChanges();
602 } else if (mSrcStream
) {
603 if (mSecondaryVideoOutput
) {
604 mVideoWatchManager
.Unwatch(
605 mSecondaryVideoOutput
->mFirstFrameRendered
,
606 &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered
);
607 mSecondaryVideoOutput
= nullptr;
609 SetSecondaryMediaStreamRenderer(nullptr);
612 Unused
<< mVisualCloneTarget
->SetVisualCloneSource(nullptr);
613 Unused
<< SetVisualCloneTarget(nullptr);
615 UpdateMediaControlAfterPictureInPictureModeChanged();
617 if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
618 NotifyUAWidgetSetupOrChange();
622 void HTMLVideoElement::OnSecondaryVideoContainerInstalled(
623 const RefPtr
<VideoFrameContainer
>& aSecondaryContainer
) {
624 MOZ_ASSERT(NS_IsMainThread());
625 MOZ_DIAGNOSTIC_ASSERT_IF(mVisualCloneTargetPromise
, mVisualCloneTarget
);
626 if (!mVisualCloneTargetPromise
) {
627 // Clone target was unset.
631 VideoFrameContainer
* container
= mVisualCloneTarget
->GetVideoFrameContainer();
632 if (NS_WARN_IF(container
!= aSecondaryContainer
)) {
633 // Not the right container.
637 NS_DispatchToCurrentThread(NewRunnableMethod(
638 "Promise::MaybeResolveWithUndefined", mVisualCloneTargetPromise
,
639 &Promise::MaybeResolveWithUndefined
));
640 mVisualCloneTargetPromise
= nullptr;
643 void HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered() {
644 OnSecondaryVideoContainerInstalled(
645 mVisualCloneTarget
->GetVideoFrameContainer());
648 void HTMLVideoElement::OnVisibilityChange(Visibility aNewVisibility
) {
649 HTMLMediaElement::OnVisibilityChange(aNewVisibility
);
651 // See the alternative part after step 4, but we only pause/resume invisible
652 // autoplay for non-audible video, which is different from the spec. This
653 // behavior seems aiming to reduce the power consumption without interering
654 // users, and Chrome and Safari also chose to do that only for non-audible
655 // video, so we want to match them in order to reduce webcompat issue.
656 // https://html.spec.whatwg.org/multipage/media.html#ready-states:eligible-for-autoplay-2
657 if (!HasAttr(nsGkAtoms::autoplay
) || IsAudible()) {
661 if (aNewVisibility
== Visibility::ApproximatelyVisible
&& mPaused
&&
662 IsEligibleForAutoplay() && AllowedToPlay()) {
663 LOG("resume invisible paused autoplay video");
667 // We need to consider the Pip window as well, which won't reflect in the
669 if ((aNewVisibility
== Visibility::ApproximatelyNonVisible
&&
670 !IsCloningElementVisually()) &&
672 LOG("pause non-audible autoplay video when it's invisible");
674 mCanAutoplayFlag
= true;
679 } // namespace mozilla::dom