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/HTMLVideoElement.h"
8 
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/dom/HTMLVideoElementBinding.h"
11 #include "nsGenericHTMLElement.h"
12 #include "nsGkAtoms.h"
13 #include "nsSize.h"
14 #include "nsError.h"
15 #include "nsIHttpChannel.h"
16 #include "nsNodeInfoManager.h"
17 #include "plbase64.h"
18 #include "prlock.h"
19 #include "nsRFPService.h"
20 #include "nsThreadUtils.h"
21 #include "ImageContainer.h"
22 #include "VideoFrameContainer.h"
23 #include "VideoOutput.h"
24 
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"
38 
39 #include <algorithm>
40 #include <limits>
41 
NS_NewHTMLVideoElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,mozilla::dom::FromParser aFromParser)42 nsGenericHTMLElement* NS_NewHTMLVideoElement(
43     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
44     mozilla::dom::FromParser aFromParser) {
45   RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
46   auto* nim = nodeInfo->NodeInfoManager();
47   mozilla::dom::HTMLVideoElement* element =
48       new (nim) mozilla::dom::HTMLVideoElement(nodeInfo.forget());
49   element->Init();
50   return element;
51 }
52 
53 namespace mozilla::dom {
54 
Clone(mozilla::dom::NodeInfo * aNodeInfo,nsINode ** aResult) const55 nsresult HTMLVideoElement::Clone(mozilla::dom::NodeInfo* aNodeInfo,
56                                  nsINode** aResult) const {
57   *aResult = nullptr;
58   RefPtr<mozilla::dom::NodeInfo> ni(aNodeInfo);
59   auto* nim = ni->NodeInfoManager();
60   HTMLVideoElement* it = new (nim) HTMLVideoElement(ni.forget());
61   it->Init();
62   nsCOMPtr<nsINode> kungFuDeathGrip = it;
63   nsresult rv = const_cast<HTMLVideoElement*>(this)->CopyInnerTo(it);
64   if (NS_SUCCEEDED(rv)) {
65     kungFuDeathGrip.swap(*aResult);
66   }
67   return rv;
68 }
69 
70 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement,
71                                                HTMLMediaElement)
72 
73 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLVideoElement)
74 
75 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLVideoElement)
76   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTarget)
77   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneTargetPromise)
78   NS_IMPL_CYCLE_COLLECTION_UNLINK(mVisualCloneSource)
79   tmp->mSecondaryVideoOutput = nullptr;
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(HTMLMediaElement)
81 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLVideoElement,HTMLMediaElement)82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLVideoElement,
83                                                   HTMLMediaElement)
84   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTarget)
85   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneTargetPromise)
86   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVisualCloneSource)
87 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
88 
89 HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo>&& aNodeInfo)
90     : HTMLMediaElement(std::move(aNodeInfo)),
91       mIsOrientationLocked(false),
92       mVideoWatchManager(this, mAbstractMainThread) {
93   DecoderDoctorLogger::LogConstruction(this);
94 }
95 
~HTMLVideoElement()96 HTMLVideoElement::~HTMLVideoElement() {
97   mVideoWatchManager.Shutdown();
98   DecoderDoctorLogger::LogDestruction(this);
99 }
100 
UpdateMediaSize(const nsIntSize & aSize)101 void HTMLVideoElement::UpdateMediaSize(const nsIntSize& aSize) {
102   HTMLMediaElement::UpdateMediaSize(aSize);
103   // If we have a clone target, we should update its size as well.
104   if (mVisualCloneTarget) {
105     Maybe<nsIntSize> newSize = Some(aSize);
106     mVisualCloneTarget->Invalidate(true, newSize, true);
107   }
108 }
109 
GetVideoSize() const110 Maybe<CSSIntSize> HTMLVideoElement::GetVideoSize() const {
111   if (!mMediaInfo.HasVideo()) {
112     return Nothing();
113   }
114 
115   if (mDisableVideo) {
116     return Nothing();
117   }
118 
119   CSSIntSize size;
120   switch (mMediaInfo.mVideo.mRotation) {
121     case VideoInfo::Rotation::kDegree_90:
122     case VideoInfo::Rotation::kDegree_270: {
123       size.width = mMediaInfo.mVideo.mDisplay.height;
124       size.height = mMediaInfo.mVideo.mDisplay.width;
125       break;
126     }
127     case VideoInfo::Rotation::kDegree_0:
128     case VideoInfo::Rotation::kDegree_180:
129     default: {
130       size.height = mMediaInfo.mVideo.mDisplay.height;
131       size.width = mMediaInfo.mVideo.mDisplay.width;
132       break;
133     }
134   }
135   return Some(size);
136 }
137 
Invalidate(bool aImageSizeChanged,Maybe<nsIntSize> & aNewIntrinsicSize,bool aForceInvalidate)138 void HTMLVideoElement::Invalidate(bool aImageSizeChanged,
139                                   Maybe<nsIntSize>& aNewIntrinsicSize,
140                                   bool aForceInvalidate) {
141   HTMLMediaElement::Invalidate(aImageSizeChanged, aNewIntrinsicSize,
142                                aForceInvalidate);
143   if (mVisualCloneTarget) {
144     VideoFrameContainer* container =
145         mVisualCloneTarget->GetVideoFrameContainer();
146     if (container) {
147       container->Invalidate();
148     }
149   }
150 }
151 
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)152 bool HTMLVideoElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
153                                       const nsAString& aValue,
154                                       nsIPrincipal* aMaybeScriptedPrincipal,
155                                       nsAttrValue& aResult) {
156   if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height) {
157     return aResult.ParseHTMLDimension(aValue);
158   }
159 
160   return HTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
161                                           aMaybeScriptedPrincipal, aResult);
162 }
163 
MapAttributesIntoRule(const nsMappedAttributes * aAttributes,MappedDeclarations & aDecls)164 void HTMLVideoElement::MapAttributesIntoRule(
165     const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
166   nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aDecls,
167                                                    MapAspectRatio::Yes);
168   nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls);
169 }
170 
NS_IMETHODIMP_(bool)171 NS_IMETHODIMP_(bool)
172 HTMLVideoElement::IsAttributeMapped(const nsAtom* aAttribute) const {
173   static const MappedAttributeEntry attributes[] = {
174       {nsGkAtoms::width}, {nsGkAtoms::height}, {nullptr}};
175 
176   static const MappedAttributeEntry* const map[] = {attributes,
177                                                     sCommonAttributeMap};
178 
179   return FindAttributeDependence(aAttribute, map);
180 }
181 
GetAttributeMappingFunction() const182 nsMapRuleToAttributesFunc HTMLVideoElement::GetAttributeMappingFunction()
183     const {
184   return &MapAttributesIntoRule;
185 }
186 
UnbindFromTree(bool aNullParent)187 void HTMLVideoElement::UnbindFromTree(bool aNullParent) {
188   if (mVisualCloneSource) {
189     mVisualCloneSource->EndCloningVisually();
190   } else if (mVisualCloneTarget) {
191     RefPtr<AsyncEventDispatcher> asyncDispatcher =
192         new AsyncEventDispatcher(this, u"MozStopPictureInPicture"_ns,
193                                  CanBubble::eNo, ChromeOnlyDispatch::eYes);
194     asyncDispatcher->RunDOMEventWhenSafe();
195 
196     EndCloningVisually();
197   }
198 
199   HTMLMediaElement::UnbindFromTree(aNullParent);
200 }
201 
SetAcceptHeader(nsIHttpChannel * aChannel)202 nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel) {
203   nsAutoCString value(
204       "video/webm,"
205       "video/ogg,"
206       "video/*;q=0.9,"
207       "application/ogg;q=0.7,"
208       "audio/*;q=0.6,*/*;q=0.5");
209 
210   return aChannel->SetRequestHeader("Accept"_ns, value, false);
211 }
212 
IsInteractiveHTMLContent() const213 bool HTMLVideoElement::IsInteractiveHTMLContent() const {
214   return HasAttr(kNameSpaceID_None, nsGkAtoms::controls) ||
215          HTMLMediaElement::IsInteractiveHTMLContent();
216 }
217 
MozParsedFrames() const218 uint32_t HTMLVideoElement::MozParsedFrames() const {
219   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
220   if (!IsVideoStatsEnabled()) {
221     return 0;
222   }
223 
224   if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
225     return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
226   }
227 
228   return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
229 }
230 
MozDecodedFrames() const231 uint32_t HTMLVideoElement::MozDecodedFrames() const {
232   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
233   if (!IsVideoStatsEnabled()) {
234     return 0;
235   }
236 
237   if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
238     return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
239   }
240 
241   return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
242 }
243 
MozPresentedFrames() const244 uint32_t HTMLVideoElement::MozPresentedFrames() const {
245   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
246   if (!IsVideoStatsEnabled()) {
247     return 0;
248   }
249 
250   if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
251     return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
252                                                    VideoWidth(), VideoHeight());
253   }
254 
255   return mDecoder ? mDecoder->GetFrameStatistics().GetPresentedFrames() : 0;
256 }
257 
MozPaintedFrames()258 uint32_t HTMLVideoElement::MozPaintedFrames() {
259   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
260   if (!IsVideoStatsEnabled()) {
261     return 0;
262   }
263 
264   if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
265     return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
266                                                    VideoWidth(), VideoHeight());
267   }
268 
269   layers::ImageContainer* container = GetImageContainer();
270   return container ? container->GetPaintCount() : 0;
271 }
272 
MozFrameDelay()273 double HTMLVideoElement::MozFrameDelay() {
274   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
275 
276   if (!IsVideoStatsEnabled() ||
277       nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
278     return 0.0;
279   }
280 
281   VideoFrameContainer* container = GetVideoFrameContainer();
282   // Hide negative delays. Frame timing tweaks in the compositor (e.g.
283   // adding a bias value to prevent multiple dropped/duped frames when
284   // frame times are aligned with composition times) may produce apparent
285   // negative delay, but we shouldn't report that.
286   return container ? std::max(0.0, container->GetFrameDelay()) : 0.0;
287 }
288 
MozHasAudio() const289 bool HTMLVideoElement::MozHasAudio() const {
290   MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
291   return HasAudio();
292 }
293 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)294 JSObject* HTMLVideoElement::WrapNode(JSContext* aCx,
295                                      JS::Handle<JSObject*> aGivenProto) {
296   return HTMLVideoElement_Binding::Wrap(aCx, this, aGivenProto);
297 }
298 
299 already_AddRefed<VideoPlaybackQuality>
GetVideoPlaybackQuality()300 HTMLVideoElement::GetVideoPlaybackQuality() {
301   DOMHighResTimeStamp creationTime = 0;
302   uint32_t totalFrames = 0;
303   uint32_t droppedFrames = 0;
304 
305   if (IsVideoStatsEnabled()) {
306     if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
307       Performance* perf = window->GetPerformance();
308       if (perf) {
309         creationTime = perf->Now();
310       }
311     }
312 
313     if (mDecoder) {
314       if (nsContentUtils::ShouldResistFingerprinting(OwnerDoc())) {
315         totalFrames = nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
316         droppedFrames = nsRFPService::GetSpoofedDroppedFrames(
317             TotalPlayTime(), VideoWidth(), VideoHeight());
318       } else {
319         FrameStatistics* stats = &mDecoder->GetFrameStatistics();
320         if (sizeof(totalFrames) >= sizeof(stats->GetParsedFrames())) {
321           totalFrames = stats->GetTotalFrames();
322           droppedFrames = stats->GetDroppedFrames();
323         } else {
324           uint64_t total = stats->GetTotalFrames();
325           const auto maxNumber = std::numeric_limits<uint32_t>::max();
326           if (total <= maxNumber) {
327             totalFrames = uint32_t(total);
328             droppedFrames = uint32_t(stats->GetDroppedFrames());
329           } else {
330             // Too big number(s) -> Resize everything to fit in 32 bits.
331             double ratio = double(maxNumber) / double(total);
332             totalFrames = maxNumber;  // === total * ratio
333             droppedFrames = uint32_t(double(stats->GetDroppedFrames()) * ratio);
334           }
335         }
336       }
337     }
338   }
339 
340   RefPtr<VideoPlaybackQuality> playbackQuality =
341       new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames);
342   return playbackQuality.forget();
343 }
344 
WakeLockRelease()345 void HTMLVideoElement::WakeLockRelease() {
346   HTMLMediaElement::WakeLockRelease();
347   ReleaseVideoWakeLockIfExists();
348 }
349 
UpdateWakeLock()350 void HTMLVideoElement::UpdateWakeLock() {
351   HTMLMediaElement::UpdateWakeLock();
352   if (!mPaused) {
353     CreateVideoWakeLockIfNeeded();
354   } else {
355     ReleaseVideoWakeLockIfExists();
356   }
357 }
358 
ShouldCreateVideoWakeLock() const359 bool HTMLVideoElement::ShouldCreateVideoWakeLock() const {
360   // Only request wake lock for video with audio or video from media stream,
361   // because non-stream video without audio is often used as a background image.
362   //
363   // Some web conferencing sites route audio outside the video element, and
364   // would not be detected unless we check for media stream, so do that below.
365   //
366   // Media streams generally aren't used as background images, though if they
367   // were we'd get false positives. If this is an issue, we could check for
368   // media stream AND document has audio playing (but that was tricky to do).
369   return HasVideo() && (mSrcStream || HasAudio());
370 }
371 
CreateVideoWakeLockIfNeeded()372 void HTMLVideoElement::CreateVideoWakeLockIfNeeded() {
373   if (!mScreenWakeLock && ShouldCreateVideoWakeLock()) {
374     RefPtr<power::PowerManagerService> pmService =
375         power::PowerManagerService::GetInstance();
376     NS_ENSURE_TRUE_VOID(pmService);
377 
378     ErrorResult rv;
379     mScreenWakeLock = pmService->NewWakeLock(u"video-playing"_ns,
380                                              OwnerDoc()->GetInnerWindow(), rv);
381   }
382 }
383 
ReleaseVideoWakeLockIfExists()384 void HTMLVideoElement::ReleaseVideoWakeLockIfExists() {
385   if (mScreenWakeLock) {
386     ErrorResult rv;
387     mScreenWakeLock->Unlock(rv);
388     rv.SuppressException();
389     mScreenWakeLock = nullptr;
390     return;
391   }
392 }
393 
SetVisualCloneTarget(RefPtr<HTMLVideoElement> aVisualCloneTarget,RefPtr<Promise> aVisualCloneTargetPromise)394 bool HTMLVideoElement::SetVisualCloneTarget(
395     RefPtr<HTMLVideoElement> aVisualCloneTarget,
396     RefPtr<Promise> aVisualCloneTargetPromise) {
397   MOZ_DIAGNOSTIC_ASSERT(
398       !aVisualCloneTarget || aVisualCloneTarget->IsInComposedDoc(),
399       "Can't set the clone target to a disconnected video "
400       "element.");
401   MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource,
402                         "Can't clone a video element that is already a clone.");
403   if (!aVisualCloneTarget ||
404       (aVisualCloneTarget->IsInComposedDoc() && !mVisualCloneSource)) {
405     mVisualCloneTarget = std::move(aVisualCloneTarget);
406     mVisualCloneTargetPromise = std::move(aVisualCloneTargetPromise);
407     return true;
408   }
409   return false;
410 }
411 
SetVisualCloneSource(RefPtr<HTMLVideoElement> aVisualCloneSource)412 bool HTMLVideoElement::SetVisualCloneSource(
413     RefPtr<HTMLVideoElement> aVisualCloneSource) {
414   MOZ_DIAGNOSTIC_ASSERT(
415       !aVisualCloneSource || aVisualCloneSource->IsInComposedDoc(),
416       "Can't set the clone source to a disconnected video "
417       "element.");
418   MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget,
419                         "Can't clone a video element that is already a "
420                         "clone.");
421   if (!aVisualCloneSource ||
422       (aVisualCloneSource->IsInComposedDoc() && !mVisualCloneTarget)) {
423     mVisualCloneSource = std::move(aVisualCloneSource);
424     return true;
425   }
426   return false;
427 }
428 
429 /* static */
IsVideoStatsEnabled()430 bool HTMLVideoElement::IsVideoStatsEnabled() {
431   return StaticPrefs::media_video_stats_enabled();
432 }
433 
TotalPlayTime() const434 double HTMLVideoElement::TotalPlayTime() const {
435   double total = 0.0;
436 
437   if (mPlayed) {
438     uint32_t timeRangeCount = mPlayed->Length();
439 
440     for (uint32_t i = 0; i < timeRangeCount; i++) {
441       double begin = mPlayed->Start(i);
442       double end = mPlayed->End(i);
443       total += end - begin;
444     }
445 
446     if (mCurrentPlayRangeStart != -1.0) {
447       double now = CurrentTime();
448       if (mCurrentPlayRangeStart != now) {
449         total += now - mCurrentPlayRangeStart;
450       }
451     }
452   }
453 
454   return total;
455 }
456 
CloneElementVisually(HTMLVideoElement & aTargetVideo,ErrorResult & aRv)457 already_AddRefed<Promise> HTMLVideoElement::CloneElementVisually(
458     HTMLVideoElement& aTargetVideo, ErrorResult& aRv) {
459   MOZ_ASSERT(IsInComposedDoc(),
460              "Can't clone a video that's not bound to a DOM tree.");
461   MOZ_ASSERT(aTargetVideo.IsInComposedDoc(),
462              "Can't clone to a video that's not bound to a DOM tree.");
463   if (!IsInComposedDoc() || !aTargetVideo.IsInComposedDoc()) {
464     aRv.Throw(NS_ERROR_UNEXPECTED);
465     return nullptr;
466   }
467 
468   nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
469   if (!win) {
470     aRv.Throw(NS_ERROR_UNEXPECTED);
471     return nullptr;
472   }
473 
474   RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv);
475   if (aRv.Failed()) {
476     return nullptr;
477   }
478 
479   // Do we already have a visual clone target? If so, shut it down.
480   if (mVisualCloneTarget) {
481     EndCloningVisually();
482   }
483 
484   // If there's a poster set on the target video, clear it, otherwise
485   // it'll display over top of the cloned frames.
486   aTargetVideo.UnsetHTMLAttr(nsGkAtoms::poster, aRv);
487   if (aRv.Failed()) {
488     return nullptr;
489   }
490 
491   if (!SetVisualCloneTarget(&aTargetVideo, promise)) {
492     aRv.Throw(NS_ERROR_FAILURE);
493     return nullptr;
494   }
495 
496   if (!aTargetVideo.SetVisualCloneSource(this)) {
497     mVisualCloneTarget = nullptr;
498     aRv.Throw(NS_ERROR_FAILURE);
499     return nullptr;
500   }
501 
502   aTargetVideo.SetMediaInfo(mMediaInfo);
503 
504   if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
505     NotifyUAWidgetSetupOrChange();
506   }
507 
508   MaybeBeginCloningVisually();
509 
510   return promise.forget();
511 }
512 
StopCloningElementVisually()513 void HTMLVideoElement::StopCloningElementVisually() {
514   if (mVisualCloneTarget) {
515     EndCloningVisually();
516   }
517 }
518 
MaybeBeginCloningVisually()519 void HTMLVideoElement::MaybeBeginCloningVisually() {
520   if (!mVisualCloneTarget) {
521     return;
522   }
523 
524   if (mDecoder) {
525     mDecoder->SetSecondaryVideoContainer(
526         mVisualCloneTarget->GetVideoFrameContainer());
527     NotifyDecoderActivityChanges();
528     UpdateMediaControlAfterPictureInPictureModeChanged();
529     OwnerDoc()->EnableChildElementInPictureInPictureMode();
530   } else if (mSrcStream) {
531     VideoFrameContainer* container =
532         mVisualCloneTarget->GetVideoFrameContainer();
533     if (container) {
534       mSecondaryVideoOutput =
535           MakeRefPtr<FirstFrameVideoOutput>(container, mAbstractMainThread);
536       mVideoWatchManager.Watch(
537           mSecondaryVideoOutput->mFirstFrameRendered,
538           &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
539       SetSecondaryMediaStreamRenderer(container, mSecondaryVideoOutput);
540     }
541     UpdateMediaControlAfterPictureInPictureModeChanged();
542     OwnerDoc()->EnableChildElementInPictureInPictureMode();
543   }
544 }
545 
EndCloningVisually()546 void HTMLVideoElement::EndCloningVisually() {
547   MOZ_ASSERT(mVisualCloneTarget);
548 
549   if (mDecoder) {
550     mDecoder->SetSecondaryVideoContainer(nullptr);
551     NotifyDecoderActivityChanges();
552     OwnerDoc()->DisableChildElementInPictureInPictureMode();
553   } else if (mSrcStream) {
554     if (mSecondaryVideoOutput) {
555       mVideoWatchManager.Unwatch(
556           mSecondaryVideoOutput->mFirstFrameRendered,
557           &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
558       mSecondaryVideoOutput = nullptr;
559     }
560     SetSecondaryMediaStreamRenderer(nullptr);
561     OwnerDoc()->DisableChildElementInPictureInPictureMode();
562   }
563 
564   Unused << mVisualCloneTarget->SetVisualCloneSource(nullptr);
565   Unused << SetVisualCloneTarget(nullptr);
566 
567   UpdateMediaControlAfterPictureInPictureModeChanged();
568 
569   if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
570     NotifyUAWidgetSetupOrChange();
571   }
572 }
573 
OnSecondaryVideoContainerInstalled(const RefPtr<VideoFrameContainer> & aSecondaryContainer)574 void HTMLVideoElement::OnSecondaryVideoContainerInstalled(
575     const RefPtr<VideoFrameContainer>& aSecondaryContainer) {
576   MOZ_ASSERT(NS_IsMainThread());
577   MOZ_DIAGNOSTIC_ASSERT_IF(mVisualCloneTargetPromise, mVisualCloneTarget);
578   if (!mVisualCloneTargetPromise) {
579     // Clone target was unset.
580     return;
581   }
582 
583   VideoFrameContainer* container = mVisualCloneTarget->GetVideoFrameContainer();
584   if (NS_WARN_IF(container != aSecondaryContainer)) {
585     // Not the right container.
586     return;
587   }
588 
589   mMainThreadEventTarget->Dispatch(NewRunnableMethod(
590       "Promise::MaybeResolveWithUndefined", mVisualCloneTargetPromise,
591       &Promise::MaybeResolveWithUndefined));
592   mVisualCloneTargetPromise = nullptr;
593 }
594 
OnSecondaryVideoOutputFirstFrameRendered()595 void HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered() {
596   OnSecondaryVideoContainerInstalled(
597       mVisualCloneTarget->GetVideoFrameContainer());
598 }
599 
600 }  // namespace mozilla::dom
601