Bug 1795723 - Unified extensions UI should support High Contrast Mode. r=ayeddi,deskt...
[gecko.git] / dom / html / HTMLVideoElement.cpp
blobc8f26393df2c3198d208cc1e4fc0e1dec2899d0f
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"
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"
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"
39 #include <algorithm>
40 #include <limits>
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;
53 namespace mozilla::dom {
55 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);
67 return rv;
70 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLVideoElement,
71 HTMLMediaElement)
73 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLVideoElement)
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)
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
89 HTMLVideoElement::HTMLVideoElement(already_AddRefed<NodeInfo>&& aNodeInfo)
90 : HTMLMediaElement(std::move(aNodeInfo)),
91 mIsOrientationLocked(false),
92 mVideoWatchManager(this, mAbstractMainThread) {
93 DecoderDoctorLogger::LogConstruction(this);
96 HTMLVideoElement::~HTMLVideoElement() {
97 mVideoWatchManager.Shutdown();
98 DecoderDoctorLogger::LogDestruction(this);
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);
110 Maybe<CSSIntSize> HTMLVideoElement::GetVideoSize() const {
111 if (!mMediaInfo.HasVideo()) {
112 return Nothing();
115 if (mDisableVideo) {
116 return Nothing();
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;
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;
135 return Some(size);
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();
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);
160 return HTMLMediaElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
161 aMaybeScriptedPrincipal, aResult);
164 void HTMLVideoElement::MapAttributesIntoRule(
165 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
166 nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aDecls,
167 MapAspectRatio::Yes);
168 nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls);
171 NS_IMETHODIMP_(bool)
172 HTMLVideoElement::IsAttributeMapped(const nsAtom* aAttribute) const {
173 static const MappedAttributeEntry attributes[] = {
174 {nsGkAtoms::width}, {nsGkAtoms::height}, {nullptr}};
176 static const MappedAttributeEntry* const map[] = {attributes,
177 sCommonAttributeMap};
179 return FindAttributeDependence(aAttribute, map);
182 nsMapRuleToAttributesFunc HTMLVideoElement::GetAttributeMappingFunction()
183 const {
184 return &MapAttributesIntoRule;
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();
196 EndCloningVisually();
199 HTMLMediaElement::UnbindFromTree(aNullParent);
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");
210 return aChannel->SetRequestHeader("Accept"_ns, value, false);
213 bool HTMLVideoElement::IsInteractiveHTMLContent() const {
214 return HasAttr(kNameSpaceID_None, nsGkAtoms::controls) ||
215 HTMLMediaElement::IsInteractiveHTMLContent();
218 gfx::IntSize HTMLVideoElement::GetVideoIntrinsicDimensions() {
219 layers::ImageContainer* container = GetImageContainer();
220 // Prefer the size of the container as it's more up to date.
221 if (container && container->GetCurrentSize().width != 0) {
222 // But adjust to the aspect ratio of the container.
223 float dar = static_cast<float>(mMediaInfo.mVideo.mDisplay.width) /
224 mMediaInfo.mVideo.mDisplay.height;
225 gfx::IntSize size = container->GetCurrentSize();
226 float imageDar = static_cast<float>(size.width) / size.height;
227 return gfx::IntSize(int(size.width * (dar / imageDar)), size.height);
229 return mMediaInfo.mVideo.mDisplay;
232 uint32_t HTMLVideoElement::VideoWidth() {
233 if (!mMediaInfo.HasVideo()) {
234 return 0;
236 gfx::IntSize size = GetVideoIntrinsicDimensions();
237 if (mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_90 ||
238 mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_270) {
239 return size.height;
241 return size.width;
244 uint32_t HTMLVideoElement::VideoHeight() {
245 if (!mMediaInfo.HasVideo()) {
246 return 0;
248 gfx::IntSize size = GetVideoIntrinsicDimensions();
249 if (mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_90 ||
250 mMediaInfo.mVideo.mRotation == VideoInfo::Rotation::kDegree_270) {
251 return size.width;
253 return size.height;
256 uint32_t HTMLVideoElement::MozParsedFrames() const {
257 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
258 if (!IsVideoStatsEnabled()) {
259 return 0;
262 if (OwnerDoc()->ShouldResistFingerprinting()) {
263 return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
266 return mDecoder ? mDecoder->GetFrameStatistics().GetParsedFrames() : 0;
269 uint32_t HTMLVideoElement::MozDecodedFrames() const {
270 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
271 if (!IsVideoStatsEnabled()) {
272 return 0;
275 if (OwnerDoc()->ShouldResistFingerprinting()) {
276 return nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
279 return mDecoder ? mDecoder->GetFrameStatistics().GetDecodedFrames() : 0;
282 uint32_t HTMLVideoElement::MozPresentedFrames() {
283 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
284 if (!IsVideoStatsEnabled()) {
285 return 0;
288 if (OwnerDoc()->ShouldResistFingerprinting()) {
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()) {
299 return 0;
302 if (OwnerDoc()->ShouldResistFingerprinting()) {
303 return nsRFPService::GetSpoofedPresentedFrames(TotalPlayTime(),
304 VideoWidth(), VideoHeight());
307 layers::ImageContainer* container = GetImageContainer();
308 return container ? container->GetPaintCount() : 0;
311 double HTMLVideoElement::MozFrameDelay() {
312 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
314 if (!IsVideoStatsEnabled() || OwnerDoc()->ShouldResistFingerprinting()) {
315 return 0.0;
318 VideoFrameContainer* container = GetVideoFrameContainer();
319 // Hide negative delays. Frame timing tweaks in the compositor (e.g.
320 // adding a bias value to prevent multiple dropped/duped frames when
321 // frame times are aligned with composition times) may produce apparent
322 // negative delay, but we shouldn't report that.
323 return container ? std::max(0.0, container->GetFrameDelay()) : 0.0;
326 bool HTMLVideoElement::MozHasAudio() const {
327 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");
328 return HasAudio();
331 JSObject* HTMLVideoElement::WrapNode(JSContext* aCx,
332 JS::Handle<JSObject*> aGivenProto) {
333 return HTMLVideoElement_Binding::Wrap(aCx, this, aGivenProto);
336 already_AddRefed<VideoPlaybackQuality>
337 HTMLVideoElement::GetVideoPlaybackQuality() {
338 DOMHighResTimeStamp creationTime = 0;
339 uint32_t totalFrames = 0;
340 uint32_t droppedFrames = 0;
342 if (IsVideoStatsEnabled()) {
343 if (nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow()) {
344 Performance* perf = window->GetPerformance();
345 if (perf) {
346 creationTime = perf->Now();
350 if (mDecoder) {
351 if (OwnerDoc()->ShouldResistFingerprinting()) {
352 totalFrames = nsRFPService::GetSpoofedTotalFrames(TotalPlayTime());
353 droppedFrames = nsRFPService::GetSpoofedDroppedFrames(
354 TotalPlayTime(), VideoWidth(), VideoHeight());
355 } else {
356 FrameStatistics* stats = &mDecoder->GetFrameStatistics();
357 if (sizeof(totalFrames) >= sizeof(stats->GetParsedFrames())) {
358 totalFrames = stats->GetTotalFrames();
359 droppedFrames = stats->GetDroppedFrames();
360 } else {
361 uint64_t total = stats->GetTotalFrames();
362 const auto maxNumber = std::numeric_limits<uint32_t>::max();
363 if (total <= maxNumber) {
364 totalFrames = uint32_t(total);
365 droppedFrames = uint32_t(stats->GetDroppedFrames());
366 } else {
367 // Too big number(s) -> Resize everything to fit in 32 bits.
368 double ratio = double(maxNumber) / double(total);
369 totalFrames = maxNumber; // === total * ratio
370 droppedFrames = uint32_t(double(stats->GetDroppedFrames()) * ratio);
374 if (!StaticPrefs::media_video_dropped_frame_stats_enabled()) {
375 droppedFrames = 0;
380 RefPtr<VideoPlaybackQuality> playbackQuality =
381 new VideoPlaybackQuality(this, creationTime, totalFrames, droppedFrames);
382 return playbackQuality.forget();
385 void HTMLVideoElement::WakeLockRelease() {
386 HTMLMediaElement::WakeLockRelease();
387 ReleaseVideoWakeLockIfExists();
390 void HTMLVideoElement::UpdateWakeLock() {
391 HTMLMediaElement::UpdateWakeLock();
392 if (!mPaused) {
393 CreateVideoWakeLockIfNeeded();
394 } else {
395 ReleaseVideoWakeLockIfExists();
399 bool HTMLVideoElement::ShouldCreateVideoWakeLock() const {
400 // Only request wake lock for video with audio or video from media stream,
401 // because non-stream video without audio is often used as a background image.
403 // Some web conferencing sites route audio outside the video element, and
404 // would not be detected unless we check for media stream, so do that below.
406 // Media streams generally aren't used as background images, though if they
407 // were we'd get false positives. If this is an issue, we could check for
408 // media stream AND document has audio playing (but that was tricky to do).
409 return HasVideo() && (mSrcStream || HasAudio());
412 void HTMLVideoElement::CreateVideoWakeLockIfNeeded() {
413 if (!mScreenWakeLock && ShouldCreateVideoWakeLock()) {
414 RefPtr<power::PowerManagerService> pmService =
415 power::PowerManagerService::GetInstance();
416 NS_ENSURE_TRUE_VOID(pmService);
418 ErrorResult rv;
419 mScreenWakeLock = pmService->NewWakeLock(u"video-playing"_ns,
420 OwnerDoc()->GetInnerWindow(), rv);
424 void HTMLVideoElement::ReleaseVideoWakeLockIfExists() {
425 if (mScreenWakeLock) {
426 ErrorResult rv;
427 mScreenWakeLock->Unlock(rv);
428 rv.SuppressException();
429 mScreenWakeLock = nullptr;
430 return;
434 bool HTMLVideoElement::SetVisualCloneTarget(
435 RefPtr<HTMLVideoElement> aVisualCloneTarget,
436 RefPtr<Promise> aVisualCloneTargetPromise) {
437 MOZ_DIAGNOSTIC_ASSERT(
438 !aVisualCloneTarget || aVisualCloneTarget->IsInComposedDoc(),
439 "Can't set the clone target to a disconnected video "
440 "element.");
441 MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource,
442 "Can't clone a video element that is already a clone.");
443 if (!aVisualCloneTarget ||
444 (aVisualCloneTarget->IsInComposedDoc() && !mVisualCloneSource)) {
445 mVisualCloneTarget = std::move(aVisualCloneTarget);
446 mVisualCloneTargetPromise = std::move(aVisualCloneTargetPromise);
447 return true;
449 return false;
452 bool HTMLVideoElement::SetVisualCloneSource(
453 RefPtr<HTMLVideoElement> aVisualCloneSource) {
454 MOZ_DIAGNOSTIC_ASSERT(
455 !aVisualCloneSource || aVisualCloneSource->IsInComposedDoc(),
456 "Can't set the clone source to a disconnected video "
457 "element.");
458 MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget,
459 "Can't clone a video element that is already a "
460 "clone.");
461 if (!aVisualCloneSource ||
462 (aVisualCloneSource->IsInComposedDoc() && !mVisualCloneTarget)) {
463 mVisualCloneSource = std::move(aVisualCloneSource);
464 return true;
466 return false;
469 /* static */
470 bool HTMLVideoElement::IsVideoStatsEnabled() {
471 return StaticPrefs::media_video_stats_enabled();
474 double HTMLVideoElement::TotalPlayTime() const {
475 double total = 0.0;
477 if (mPlayed) {
478 uint32_t timeRangeCount = mPlayed->Length();
480 for (uint32_t i = 0; i < timeRangeCount; i++) {
481 double begin = mPlayed->Start(i);
482 double end = mPlayed->End(i);
483 total += end - begin;
486 if (mCurrentPlayRangeStart != -1.0) {
487 double now = CurrentTime();
488 if (mCurrentPlayRangeStart != now) {
489 total += now - mCurrentPlayRangeStart;
494 return total;
497 already_AddRefed<Promise> HTMLVideoElement::CloneElementVisually(
498 HTMLVideoElement& aTargetVideo, ErrorResult& aRv) {
499 MOZ_ASSERT(IsInComposedDoc(),
500 "Can't clone a video that's not bound to a DOM tree.");
501 MOZ_ASSERT(aTargetVideo.IsInComposedDoc(),
502 "Can't clone to a video that's not bound to a DOM tree.");
503 if (!IsInComposedDoc() || !aTargetVideo.IsInComposedDoc()) {
504 aRv.Throw(NS_ERROR_UNEXPECTED);
505 return nullptr;
508 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
509 if (!win) {
510 aRv.Throw(NS_ERROR_UNEXPECTED);
511 return nullptr;
514 RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv);
515 if (aRv.Failed()) {
516 return nullptr;
519 // Do we already have a visual clone target? If so, shut it down.
520 if (mVisualCloneTarget) {
521 EndCloningVisually();
524 // If there's a poster set on the target video, clear it, otherwise
525 // it'll display over top of the cloned frames.
526 aTargetVideo.UnsetHTMLAttr(nsGkAtoms::poster, aRv);
527 if (aRv.Failed()) {
528 return nullptr;
531 if (!SetVisualCloneTarget(&aTargetVideo, promise)) {
532 aRv.Throw(NS_ERROR_FAILURE);
533 return nullptr;
536 if (!aTargetVideo.SetVisualCloneSource(this)) {
537 mVisualCloneTarget = nullptr;
538 aRv.Throw(NS_ERROR_FAILURE);
539 return nullptr;
542 aTargetVideo.SetMediaInfo(mMediaInfo);
544 if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
545 NotifyUAWidgetSetupOrChange();
548 MaybeBeginCloningVisually();
550 return promise.forget();
553 void HTMLVideoElement::StopCloningElementVisually() {
554 if (mVisualCloneTarget) {
555 EndCloningVisually();
559 void HTMLVideoElement::MaybeBeginCloningVisually() {
560 if (!mVisualCloneTarget) {
561 return;
564 if (mDecoder) {
565 mDecoder->SetSecondaryVideoContainer(
566 mVisualCloneTarget->GetVideoFrameContainer());
567 NotifyDecoderActivityChanges();
568 UpdateMediaControlAfterPictureInPictureModeChanged();
569 OwnerDoc()->EnableChildElementInPictureInPictureMode();
570 } else if (mSrcStream) {
571 VideoFrameContainer* container =
572 mVisualCloneTarget->GetVideoFrameContainer();
573 if (container) {
574 mSecondaryVideoOutput =
575 MakeRefPtr<FirstFrameVideoOutput>(container, mAbstractMainThread);
576 mVideoWatchManager.Watch(
577 mSecondaryVideoOutput->mFirstFrameRendered,
578 &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
579 SetSecondaryMediaStreamRenderer(container, mSecondaryVideoOutput);
581 UpdateMediaControlAfterPictureInPictureModeChanged();
582 OwnerDoc()->EnableChildElementInPictureInPictureMode();
586 void HTMLVideoElement::EndCloningVisually() {
587 MOZ_ASSERT(mVisualCloneTarget);
589 if (mDecoder) {
590 mDecoder->SetSecondaryVideoContainer(nullptr);
591 NotifyDecoderActivityChanges();
592 OwnerDoc()->DisableChildElementInPictureInPictureMode();
593 } else if (mSrcStream) {
594 if (mSecondaryVideoOutput) {
595 mVideoWatchManager.Unwatch(
596 mSecondaryVideoOutput->mFirstFrameRendered,
597 &HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered);
598 mSecondaryVideoOutput = nullptr;
600 SetSecondaryMediaStreamRenderer(nullptr);
601 OwnerDoc()->DisableChildElementInPictureInPictureMode();
604 Unused << mVisualCloneTarget->SetVisualCloneSource(nullptr);
605 Unused << SetVisualCloneTarget(nullptr);
607 UpdateMediaControlAfterPictureInPictureModeChanged();
609 if (IsInComposedDoc() && !StaticPrefs::media_cloneElementVisually_testing()) {
610 NotifyUAWidgetSetupOrChange();
614 void HTMLVideoElement::OnSecondaryVideoContainerInstalled(
615 const RefPtr<VideoFrameContainer>& aSecondaryContainer) {
616 MOZ_ASSERT(NS_IsMainThread());
617 MOZ_DIAGNOSTIC_ASSERT_IF(mVisualCloneTargetPromise, mVisualCloneTarget);
618 if (!mVisualCloneTargetPromise) {
619 // Clone target was unset.
620 return;
623 VideoFrameContainer* container = mVisualCloneTarget->GetVideoFrameContainer();
624 if (NS_WARN_IF(container != aSecondaryContainer)) {
625 // Not the right container.
626 return;
629 mMainThreadEventTarget->Dispatch(NewRunnableMethod(
630 "Promise::MaybeResolveWithUndefined", mVisualCloneTargetPromise,
631 &Promise::MaybeResolveWithUndefined));
632 mVisualCloneTargetPromise = nullptr;
635 void HTMLVideoElement::OnSecondaryVideoOutputFirstFrameRendered() {
636 OnSecondaryVideoContainerInstalled(
637 mVisualCloneTarget->GetVideoFrameContainer());
640 } // namespace mozilla::dom