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