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 /* rendering object for the HTML <video> element */
9 #include "nsVideoFrame.h"
12 #include "nsGkAtoms.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/dom/HTMLImageElement.h"
16 #include "mozilla/dom/HTMLVideoElement.h"
17 #include "mozilla/dom/ShadowRoot.h"
18 #include "mozilla/layers/RenderRootStateManager.h"
19 #include "nsDisplayList.h"
20 #include "nsGenericHTMLElement.h"
21 #include "nsPresContext.h"
22 #include "nsContentCreatorFunctions.h"
23 #include "nsIContentInlines.h"
24 #include "nsImageFrame.h"
25 #include "nsIImageLoadingContent.h"
26 #include "nsContentUtils.h"
27 #include "nsLayoutUtils.h"
28 #include "ImageContainer.h"
29 #include "nsStyleUtil.h"
32 using namespace mozilla
;
33 using namespace mozilla::layers
;
34 using namespace mozilla::dom
;
35 using namespace mozilla::gfx
;
37 nsIFrame
* NS_NewHTMLVideoFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
38 return new (aPresShell
) nsVideoFrame(aStyle
, aPresShell
->GetPresContext());
41 NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame
)
43 // A matrix to obtain a correct-rotated video frame.
44 static Matrix
ComputeRotationMatrix(gfxFloat aRotatedWidth
,
45 gfxFloat aRotatedHeight
,
46 VideoInfo::Rotation aDegrees
) {
47 Matrix shiftVideoCenterToOrigin
;
48 if (aDegrees
== VideoInfo::Rotation::kDegree_90
||
49 aDegrees
== VideoInfo::Rotation::kDegree_270
) {
50 shiftVideoCenterToOrigin
=
51 Matrix::Translation(-aRotatedHeight
/ 2.0, -aRotatedWidth
/ 2.0);
53 shiftVideoCenterToOrigin
=
54 Matrix::Translation(-aRotatedWidth
/ 2.0, -aRotatedHeight
/ 2.0);
57 auto angle
= static_cast<double>(aDegrees
) / 180.0 * M_PI
;
58 Matrix rotation
= Matrix::Rotation(static_cast<gfx::Float
>(angle
));
59 Matrix shiftLeftTopToOrigin
=
60 Matrix::Translation(aRotatedWidth
/ 2.0, aRotatedHeight
/ 2.0);
61 return shiftVideoCenterToOrigin
* rotation
* shiftLeftTopToOrigin
;
64 static void SwapScaleWidthHeightForRotation(IntSize
& aSize
,
65 VideoInfo::Rotation aDegrees
) {
66 if (aDegrees
== VideoInfo::Rotation::kDegree_90
||
67 aDegrees
== VideoInfo::Rotation::kDegree_270
) {
68 int32_t tmpWidth
= aSize
.width
;
69 aSize
.width
= aSize
.height
;
70 aSize
.height
= tmpWidth
;
74 nsVideoFrame::nsVideoFrame(ComputedStyle
* aStyle
, nsPresContext
* aPresContext
)
75 : nsContainerFrame(aStyle
, aPresContext
, kClassID
) {
76 EnableVisibilityTracking();
79 nsVideoFrame::~nsVideoFrame() = default;
81 NS_QUERYFRAME_HEAD(nsVideoFrame
)
82 NS_QUERYFRAME_ENTRY(nsVideoFrame
)
83 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
84 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame
)
86 nsresult
nsVideoFrame::CreateAnonymousContent(
87 nsTArray
<ContentInfo
>& aElements
) {
88 nsNodeInfoManager
* nodeInfoManager
=
89 GetContent()->GetComposedDoc()->NodeInfoManager();
90 RefPtr
<NodeInfo
> nodeInfo
;
92 if (HasVideoElement()) {
93 // Create an anonymous image element as a child to hold the poster
94 // image. We may not have a poster image now, but one could be added
95 // before we load, or on a subsequent load.
96 nodeInfo
= nodeInfoManager
->GetNodeInfo(
97 nsGkAtoms::img
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
98 NS_ENSURE_TRUE(nodeInfo
, NS_ERROR_OUT_OF_MEMORY
);
99 mPosterImage
= NS_NewHTMLImageElement(nodeInfo
.forget());
100 NS_ENSURE_TRUE(mPosterImage
, NS_ERROR_OUT_OF_MEMORY
);
101 UpdatePosterSource(false);
103 // XXX(Bug 1631371) Check if this should use a fallible operation as it
104 // pretended earlier.
105 aElements
.AppendElement(mPosterImage
);
107 // Set up the caption overlay div for showing any TextTrack data
108 nodeInfo
= nodeInfoManager
->GetNodeInfo(
109 nsGkAtoms::div
, nullptr, kNameSpaceID_XHTML
, nsINode::ELEMENT_NODE
);
110 NS_ENSURE_TRUE(nodeInfo
, NS_ERROR_OUT_OF_MEMORY
);
111 mCaptionDiv
= NS_NewHTMLDivElement(nodeInfo
.forget());
112 NS_ENSURE_TRUE(mCaptionDiv
, NS_ERROR_OUT_OF_MEMORY
);
113 nsGenericHTMLElement
* div
=
114 static_cast<nsGenericHTMLElement
*>(mCaptionDiv
.get());
115 div
->SetClassName(u
"caption-box"_ns
);
117 // XXX(Bug 1631371) Check if this should use a fallible operation as it
118 // pretended earlier.
119 aElements
.AppendElement(mCaptionDiv
);
126 void nsVideoFrame::AppendAnonymousContentTo(nsTArray
<nsIContent
*>& aElements
,
129 aElements
.AppendElement(mPosterImage
);
133 aElements
.AppendElement(mCaptionDiv
);
137 nsIContent
* nsVideoFrame::GetVideoControls() const {
138 if (!mContent
->GetShadowRoot()) {
142 // The video controls <div> is the only child of the UA Widget Shadow Root
143 // if it is present. It is only lazily inserted into the DOM when
144 // the controls attribute is set.
145 MOZ_ASSERT(mContent
->GetShadowRoot()->IsUAWidget());
146 MOZ_ASSERT(1 >= mContent
->GetShadowRoot()->GetChildCount());
147 return mContent
->GetShadowRoot()->GetFirstChild();
150 void nsVideoFrame::Destroy(DestroyContext
& aContext
) {
151 if (mReflowCallbackPosted
) {
152 PresShell()->CancelReflowCallback(this);
154 aContext
.AddAnonymousContent(mCaptionDiv
.forget());
155 aContext
.AddAnonymousContent(mPosterImage
.forget());
156 nsContainerFrame::Destroy(aContext
);
159 class DispatchResizeEvent
: public Runnable
{
161 explicit DispatchResizeEvent(nsIContent
* aContent
,
162 const nsLiteralString
& aName
)
163 : Runnable("DispatchResizeEvent"), mContent(aContent
), mName(aName
) {}
164 NS_IMETHOD
Run() override
{
165 nsContentUtils::DispatchTrustedEvent(mContent
->OwnerDoc(), mContent
, mName
,
166 CanBubble::eNo
, Cancelable::eNo
);
169 nsCOMPtr
<nsIContent
> mContent
;
170 const nsLiteralString mName
;
173 bool nsVideoFrame::ReflowFinished() {
174 mReflowCallbackPosted
= false;
175 auto GetSize
= [&](nsIContent
* aContent
) -> Maybe
<nsSize
> {
179 nsIFrame
* f
= aContent
->GetPrimaryFrame();
183 return Some(f
->GetSize());
186 AutoTArray
<nsCOMPtr
<nsIRunnable
>, 2> events
;
188 if (auto size
= GetSize(mCaptionDiv
)) {
189 if (*size
!= mCaptionTrackedSize
) {
190 mCaptionTrackedSize
= *size
;
191 events
.AppendElement(
192 new DispatchResizeEvent(mCaptionDiv
, u
"resizecaption"_ns
));
195 nsIContent
* controls
= GetVideoControls();
196 if (auto size
= GetSize(controls
)) {
197 if (*size
!= mControlsTrackedSize
) {
198 mControlsTrackedSize
= *size
;
199 events
.AppendElement(
200 new DispatchResizeEvent(controls
, u
"resizevideocontrols"_ns
));
203 for (auto& event
: events
) {
204 nsContentUtils::AddScriptRunner(event
.forget());
209 void nsVideoFrame::Reflow(nsPresContext
* aPresContext
, ReflowOutput
& aMetrics
,
210 const ReflowInput
& aReflowInput
,
211 nsReflowStatus
& aStatus
) {
213 DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
214 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aMetrics
, aStatus
);
215 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
217 NS_FRAME_TRACE_CALLS
,
218 ("enter nsVideoFrame::Reflow: availSize=%d,%d",
219 aReflowInput
.AvailableISize(), aReflowInput
.AvailableBSize()));
221 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW
), "frame is not in reflow");
223 const WritingMode myWM
= aReflowInput
.GetWritingMode();
224 nscoord contentBoxBSize
= aReflowInput
.ComputedBSize();
225 const auto logicalBP
= aReflowInput
.ComputedLogicalBorderPadding(myWM
);
226 const nscoord borderBoxISize
=
227 aReflowInput
.ComputedISize() + logicalBP
.IStartEnd(myWM
);
228 const bool isBSizeShrinkWrapping
= (contentBoxBSize
== NS_UNCONSTRAINEDSIZE
);
230 nscoord borderBoxBSize
;
231 if (!isBSizeShrinkWrapping
) {
232 borderBoxBSize
= contentBoxBSize
+ logicalBP
.BStartEnd(myWM
);
235 nsIContent
* videoControlsDiv
= GetVideoControls();
237 // Reflow the child frames. We may have up to three: an image
238 // frame (for the poster image), a container frame for the controls,
239 // and a container frame for the caption.
240 for (nsIFrame
* child
: mFrames
) {
241 nsSize oldChildSize
= child
->GetSize();
242 nsReflowStatus childStatus
;
243 const WritingMode childWM
= child
->GetWritingMode();
244 LogicalSize availableSize
= aReflowInput
.ComputedSize(childWM
);
245 availableSize
.BSize(childWM
) = NS_UNCONSTRAINEDSIZE
;
246 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, child
,
248 ReflowOutput
kidDesiredSize(myWM
);
249 const nsSize containerSize
=
250 aReflowInput
.ComputedSizeAsContainerIfConstrained();
252 if (child
->GetContent() == mPosterImage
) {
253 // Reflow the poster frame.
254 const LogicalPoint childOrigin
= logicalBP
.StartOffset(myWM
);
255 const LogicalSize posterRenderSize
= aReflowInput
.ComputedSize(childWM
);
256 kidReflowInput
.SetComputedISize(posterRenderSize
.ISize(childWM
));
257 kidReflowInput
.SetComputedBSize(posterRenderSize
.BSize(childWM
));
259 ReflowChild(child
, aPresContext
, kidDesiredSize
, kidReflowInput
, myWM
,
260 childOrigin
, containerSize
, ReflowChildFlags::Default
,
262 MOZ_ASSERT(childStatus
.IsFullyComplete(),
263 "We gave our child unconstrained available block-size, "
264 "so it should be complete!");
266 FinishReflowChild(child
, aPresContext
, kidDesiredSize
, &kidReflowInput
,
267 myWM
, childOrigin
, containerSize
,
268 ReflowChildFlags::Default
);
270 } else if (child
->GetContent() == mCaptionDiv
||
271 child
->GetContent() == videoControlsDiv
) {
272 // Reflow the caption and control bar frames.
273 const LogicalPoint childOrigin
= logicalBP
.StartOffset(myWM
);
274 ReflowChild(child
, aPresContext
, kidDesiredSize
, kidReflowInput
, myWM
,
275 childOrigin
, containerSize
, ReflowChildFlags::Default
,
277 MOZ_ASSERT(childStatus
.IsFullyComplete(),
278 "We gave our child unconstrained available block-size, "
279 "so it should be complete!");
281 if (child
->GetContent() == videoControlsDiv
&& isBSizeShrinkWrapping
) {
282 // Resolve our own BSize based on the controls' size in the
283 // same axis. Unless we're size-contained, in which case we
284 // have to behave as if we have an intrinsic size of 0.
285 if (GetContainSizeAxes().mBContained
) {
288 contentBoxBSize
= kidDesiredSize
.BSize(myWM
);
292 FinishReflowChild(child
, aPresContext
, kidDesiredSize
, &kidReflowInput
,
293 myWM
, childOrigin
, containerSize
,
294 ReflowChildFlags::Default
);
296 if (child
->GetSize() != oldChildSize
) {
297 // We might find non-primary frames in printing due to
298 // ReplicateFixedFrames, but we don't care about that.
299 MOZ_ASSERT(child
->IsPrimaryFrame() ||
300 PresContext()->IsPrintingOrPrintPreview(),
301 "We only look at the primary frame in ReflowFinished");
302 if (!mReflowCallbackPosted
) {
303 mReflowCallbackPosted
= true;
304 PresShell()->PostReflowCallback(this);
308 NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
312 if (isBSizeShrinkWrapping
) {
313 if (contentBoxBSize
== NS_UNCONSTRAINEDSIZE
) {
314 // We didn't get a BSize from our intrinsic size/ratio, nor did we
315 // get one from our controls. Just use BSize of 0.
318 contentBoxBSize
= aReflowInput
.ApplyMinMaxBSize(contentBoxBSize
);
319 borderBoxBSize
= contentBoxBSize
+ logicalBP
.BStartEnd(myWM
);
322 LogicalSize
logicalDesiredSize(myWM
, borderBoxISize
, borderBoxBSize
);
323 aMetrics
.SetSize(myWM
, logicalDesiredSize
);
325 aMetrics
.SetOverflowAreasToDesiredBounds();
327 FinishAndStoreOverflow(&aMetrics
);
329 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS
, ("exit nsVideoFrame::Reflow: size=%d,%d",
330 aMetrics
.Width(), aMetrics
.Height()));
332 MOZ_ASSERT(aStatus
.IsEmpty(), "This type of frame can't be split.");
336 a11y::AccType
nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType
; }
339 #ifdef DEBUG_FRAME_DUMP
340 nsresult
nsVideoFrame::GetFrameName(nsAString
& aResult
) const {
341 return MakeFrameName(u
"HTMLVideo"_ns
, aResult
);
345 nsIFrame::SizeComputationResult
nsVideoFrame::ComputeSize(
346 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
347 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
348 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
349 ComputeSizeFlags aFlags
) {
350 if (!HasVideoElement()) {
351 return nsContainerFrame::ComputeSize(
352 aRenderingContext
, aWM
, aCBSize
, aAvailableISize
, aMargin
,
353 aBorderPadding
, aSizeOverrides
, aFlags
);
356 return {ComputeSizeWithIntrinsicDimensions(
357 aRenderingContext
, aWM
, GetIntrinsicSize(), GetAspectRatio(),
358 aCBSize
, aMargin
, aBorderPadding
, aSizeOverrides
, aFlags
),
359 AspectRatioUsage::None
};
362 nscoord
nsVideoFrame::GetMinISize(gfxContext
* aRenderingContext
) {
364 // Bind the result variable to a RAII-based debug object - the variable
365 // therefore must match the function's return value.
366 DISPLAY_MIN_INLINE_SIZE(this, result
);
367 // This call handles size-containment
368 nsSize size
= GetIntrinsicSize().ToSize().valueOr(nsSize());
369 result
= GetWritingMode().IsVertical() ? size
.height
: size
.width
;
373 nscoord
nsVideoFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
374 // <audio> / <video> has the same min / pref ISize.
375 return GetMinISize(aRenderingContext
);
378 Maybe
<nsSize
> nsVideoFrame::PosterImageSize() const {
379 // Use the poster image frame's size.
380 nsIFrame
* child
= GetPosterImage()->GetPrimaryFrame();
381 return child
->GetIntrinsicSize().ToSize();
384 AspectRatio
nsVideoFrame::GetIntrinsicRatio() const {
385 if (!HasVideoElement()) {
386 // Audio elements have no intrinsic ratio.
387 return AspectRatio();
390 // 'contain:[inline-]size' replaced elements have no intrinsic ratio.
391 if (GetContainSizeAxes().IsAny()) {
392 return AspectRatio();
395 auto* element
= static_cast<HTMLVideoElement
*>(GetContent());
396 if (Maybe
<CSSIntSize
> size
= element
->GetVideoSize()) {
397 return AspectRatio::FromSize(*size
);
400 if (ShouldDisplayPoster()) {
401 if (Maybe
<nsSize
> imgSize
= PosterImageSize()) {
402 return AspectRatio::FromSize(*imgSize
);
406 if (StylePosition()->mAspectRatio
.HasRatio()) {
407 return AspectRatio();
410 return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels
);
413 bool nsVideoFrame::ShouldDisplayPoster() const {
414 if (!HasVideoElement()) {
418 auto* element
= static_cast<HTMLVideoElement
*>(GetContent());
419 if (element
->GetPlayedOrSeeked() && HasVideoData()) {
423 nsCOMPtr
<nsIImageLoadingContent
> imgContent
= do_QueryInterface(mPosterImage
);
424 NS_ENSURE_TRUE(imgContent
, false);
426 nsCOMPtr
<imgIRequest
> request
;
427 nsresult res
= imgContent
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
428 getter_AddRefs(request
));
429 if (NS_FAILED(res
) || !request
) {
434 res
= request
->GetImageStatus(&status
);
435 if (NS_FAILED(res
) || (status
& imgIRequest::STATUS_ERROR
)) return false;
440 IntrinsicSize
nsVideoFrame::GetIntrinsicSize() {
441 const auto containAxes
= GetContainSizeAxes();
442 const auto isVideo
= HasVideoElement();
443 // Intrinsic size will be given by contain-intrinsic-size if the element is
444 // size-contained. If both axes have containment, ContainIntrinsicSize() will
445 // ignore the fallback size argument, so we can just pass no intrinsic size,
447 if (containAxes
.IsBoth()) {
448 return containAxes
.ContainIntrinsicSize({}, *this);
452 // An audio element with no "controls" attribute, distinguished by the last
453 // and only child being the control, falls back to no intrinsic size.
454 if (!mFrames
.LastChild()) {
455 return containAxes
.ContainIntrinsicSize({}, *this);
458 return containAxes
.ContainIntrinsicSize(
459 IntrinsicSize(kFallbackIntrinsicSize
), *this);
462 auto* element
= static_cast<HTMLVideoElement
*>(GetContent());
463 if (Maybe
<CSSIntSize
> size
= element
->GetVideoSize()) {
464 return containAxes
.ContainIntrinsicSize(
465 IntrinsicSize(CSSPixel::ToAppUnits(*size
)), *this);
468 if (ShouldDisplayPoster()) {
469 if (Maybe
<nsSize
> imgSize
= PosterImageSize()) {
470 return containAxes
.ContainIntrinsicSize(IntrinsicSize(*imgSize
), *this);
474 if (StylePosition()->mAspectRatio
.HasRatio()) {
478 return containAxes
.ContainIntrinsicSize(IntrinsicSize(kFallbackIntrinsicSize
),
482 void nsVideoFrame::UpdatePosterSource(bool aNotify
) {
483 NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
484 HTMLVideoElement
* element
= static_cast<HTMLVideoElement
*>(GetContent());
486 if (element
->HasAttr(nsGkAtoms::poster
) &&
487 !element
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::poster
,
488 nsGkAtoms::_empty
, eIgnoreCase
)) {
489 nsAutoString posterStr
;
490 element
->GetPoster(posterStr
);
491 mPosterImage
->SetAttr(kNameSpaceID_None
, nsGkAtoms::src
, posterStr
,
494 mPosterImage
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::src
, aNotify
);
498 nsresult
nsVideoFrame::AttributeChanged(int32_t aNameSpaceID
,
499 nsAtom
* aAttribute
, int32_t aModType
) {
500 if (aAttribute
== nsGkAtoms::poster
&& HasVideoElement()) {
501 UpdatePosterSource(true);
503 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
506 void nsVideoFrame::OnVisibilityChange(
507 Visibility aNewVisibility
, const Maybe
<OnNonvisible
>& aNonvisibleAction
) {
508 if (HasVideoElement()) {
509 static_cast<HTMLMediaElement
*>(GetContent())
510 ->OnVisibilityChange(aNewVisibility
);
513 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
514 do_QueryInterface(mPosterImage
);
516 imageLoader
->OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
519 nsContainerFrame::OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
522 bool nsVideoFrame::HasVideoElement() const {
523 return static_cast<HTMLMediaElement
*>(GetContent())->IsVideo();
526 bool nsVideoFrame::HasVideoData() const {
527 if (!HasVideoElement()) {
530 auto* element
= static_cast<HTMLVideoElement
*>(GetContent());
531 return element
->GetVideoSize().isSome();
534 void nsVideoFrame::UpdateTextTrack() {
535 static_cast<HTMLMediaElement
*>(GetContent())->NotifyCueDisplayStatesChanged();
540 class nsDisplayVideo
: public nsPaintedDisplayItem
{
542 nsDisplayVideo(nsDisplayListBuilder
* aBuilder
, nsVideoFrame
* aFrame
)
543 : nsPaintedDisplayItem(aBuilder
, aFrame
) {
544 MOZ_COUNT_CTOR(nsDisplayVideo
);
547 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayVideo
)
549 NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO
)
551 already_AddRefed
<ImageContainer
> GetImageContainer(gfxRect
& aDestGFXRect
) {
552 nsRect area
= Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
553 HTMLVideoElement
* element
=
554 static_cast<HTMLVideoElement
*>(Frame()->GetContent());
556 Maybe
<CSSIntSize
> videoSizeInPx
= element
->GetVideoSize();
557 if (videoSizeInPx
.isNothing() || area
.IsEmpty()) {
561 RefPtr
<ImageContainer
> container
= element
->GetImageContainer();
566 // Retrieve the size of the decoded video frame, before being scaled
567 // by pixel aspect ratio.
568 mozilla::gfx::IntSize frameSize
= container
->GetCurrentSize();
569 if (frameSize
.width
== 0 || frameSize
.height
== 0) {
570 // No image, or zero-sized image. Don't render.
574 const auto aspectRatio
= AspectRatio::FromSize(*videoSizeInPx
);
575 const IntrinsicSize
intrinsicSize(CSSPixel::ToAppUnits(*videoSizeInPx
));
576 nsRect dest
= nsLayoutUtils::ComputeObjectDestRect(
577 area
, intrinsicSize
, aspectRatio
, Frame()->StylePosition());
579 aDestGFXRect
= Frame()->PresContext()->AppUnitsToGfxUnits(dest
);
580 aDestGFXRect
.Round();
581 if (aDestGFXRect
.IsEmpty()) {
585 return container
.forget();
588 virtual bool CreateWebRenderCommands(
589 mozilla::wr::DisplayListBuilder
& aBuilder
,
590 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
591 const mozilla::layers::StackingContextHelper
& aSc
,
592 mozilla::layers::RenderRootStateManager
* aManager
,
593 nsDisplayListBuilder
* aDisplayListBuilder
) override
{
594 HTMLVideoElement
* element
=
595 static_cast<HTMLVideoElement
*>(Frame()->GetContent());
597 RefPtr
<ImageContainer
> container
= GetImageContainer(destGFXRect
);
602 container
->SetRotation(element
->RotationDegrees());
604 // If the image container is empty, we don't want to fallback. Any other
605 // failure will be due to resource constraints and fallback is unlikely to
606 // help us. Hence we can ignore the return value from PushImage.
607 LayoutDeviceRect
rect(destGFXRect
.x
, destGFXRect
.y
, destGFXRect
.width
,
609 aManager
->CommandBuilder().PushImage(this, container
, aBuilder
, aResources
,
614 // For opaque videos, we will want to override GetOpaqueRegion here.
615 // This is tracked by bug 1545498.
617 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
,
618 bool* aSnap
) const override
{
620 nsIFrame
* f
= Frame();
621 return f
->GetContentRectRelativeToSelf() + ToReferenceFrame();
624 // Only report FirstContentfulPaint when the video is set
625 bool IsContentful() const override
{
626 nsVideoFrame
* f
= static_cast<nsVideoFrame
*>(Frame());
627 HTMLVideoElement
* video
= HTMLVideoElement::FromNode(f
->GetContent());
628 return video
->VideoWidth() > 0;
631 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
632 gfxContext
* aCtx
) override
{
633 HTMLVideoElement
* element
=
634 static_cast<HTMLVideoElement
*>(Frame()->GetContent());
636 RefPtr
<ImageContainer
> container
= GetImageContainer(destGFXRect
);
641 VideoInfo::Rotation rotationDeg
= element
->RotationDegrees();
642 Matrix preTransform
= ComputeRotationMatrix(
643 destGFXRect
.Width(), destGFXRect
.Height(), rotationDeg
);
645 preTransform
* Matrix::Translation(destGFXRect
.x
, destGFXRect
.y
);
647 AutoLockImage
autoLock(container
);
648 Image
* image
= autoLock
.GetImage(TimeStamp::Now());
652 RefPtr
<gfx::SourceSurface
> surface
= image
->GetAsSourceSurface();
653 if (!surface
|| !surface
->IsValid()) {
656 gfx::IntSize size
= surface
->GetSize();
658 IntSize
scaleToSize(static_cast<int32_t>(destGFXRect
.Width()),
659 static_cast<int32_t>(destGFXRect
.Height()));
660 // scaleHint is set regardless of rotation, so swap w/h if needed.
661 SwapScaleWidthHeightForRotation(scaleToSize
, rotationDeg
);
662 transform
.PreScale(scaleToSize
.width
/ double(size
.Width()),
663 scaleToSize
.height
/ double(size
.Height()));
665 gfxContextMatrixAutoSaveRestore
saveMatrix(aCtx
);
667 gfxUtils::SnapTransformTranslation(aCtx
->CurrentMatrix(), nullptr));
669 transform
= gfxUtils::SnapTransform(
670 transform
, gfxRect(0, 0, size
.width
, size
.height
), nullptr);
671 aCtx
->Multiply(ThebesMatrix(transform
));
673 aCtx
->GetDrawTarget()->FillRect(
674 Rect(0, 0, size
.width
, size
.height
),
675 SurfacePattern(surface
, ExtendMode::CLAMP
, Matrix(),
676 nsLayoutUtils::GetSamplingFilterForFrame(Frame())),
681 } // namespace mozilla
683 void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
684 const nsDisplayListSet
& aLists
) {
685 if (!IsVisibleForPainting()) return;
687 DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
689 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
691 if (HidesContent()) {
695 const bool shouldDisplayPoster
= ShouldDisplayPoster();
697 // NOTE: If we're displaying a poster image (instead of video data), we can
698 // trust the nsImageFrame to constrain its drawing to its content rect
699 // (which happens to be the same as our content rect).
701 if (shouldDisplayPoster
||
702 !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
703 clipFlags
= DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT
;
708 DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
clip(
709 aBuilder
, this, clipFlags
);
711 if (HasVideoElement() && !shouldDisplayPoster
) {
712 aLists
.Content()->AppendNewToTop
<nsDisplayVideo
>(aBuilder
, this);
715 // Add child frames to display list. We expect various children,
716 // but only want to draw mPosterImage conditionally. Others we
717 // always add to the display list.
718 for (nsIFrame
* child
: mFrames
) {
719 if (child
->GetContent() != mPosterImage
|| shouldDisplayPoster
) {
720 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
722 aBuilder
->GetVisibleRect() - child
->GetOffsetTo(this),
723 aBuilder
->GetDirtyRect() - child
->GetOffsetTo(this));
725 child
->BuildDisplayListForStackingContext(aBuilder
, aLists
.Content());