Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsVideoFrame.cpp
blob10775f6b3a4a58f218bd46427cd6f8238b2b8367
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"
11 #include "nsCOMPtr.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"
30 #include <algorithm>
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);
52 } else {
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);
120 UpdateTextTrack();
123 return NS_OK;
126 void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
127 uint32_t aFliter) {
128 if (mPosterImage) {
129 aElements.AppendElement(mPosterImage);
132 if (mCaptionDiv) {
133 aElements.AppendElement(mCaptionDiv);
137 nsIContent* nsVideoFrame::GetVideoControls() const {
138 if (!mContent->GetShadowRoot()) {
139 return nullptr;
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 {
160 public:
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);
167 return NS_OK;
169 nsCOMPtr<nsIContent> mContent;
170 const nsLiteralString mName;
173 bool nsVideoFrame::ReflowFinished() {
174 mReflowCallbackPosted = false;
175 auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> {
176 if (!aContent) {
177 return Nothing();
179 nsIFrame* f = aContent->GetPrimaryFrame();
180 if (!f) {
181 return Nothing();
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());
206 return false;
209 void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
210 const ReflowInput& aReflowInput,
211 nsReflowStatus& aStatus) {
212 MarkInReflow();
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!");
216 NS_FRAME_TRACE(
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,
247 availableSize);
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,
261 childStatus);
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,
276 childStatus);
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) {
286 contentBoxBSize = 0;
287 } else {
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);
307 } else {
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.
316 contentBoxBSize = 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.");
335 #ifdef ACCESSIBILITY
336 a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
337 #endif
339 #ifdef DEBUG_FRAME_DUMP
340 nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
341 return MakeFrameName(u"HTMLVideo"_ns, aResult);
343 #endif
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) {
363 nscoord result;
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;
370 return result;
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()) {
415 return false;
418 auto* element = static_cast<HTMLVideoElement*>(GetContent());
419 if (element->GetPlayedOrSeeked() && HasVideoData()) {
420 return false;
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) {
430 return false;
433 uint32_t status = 0;
434 res = request->GetImageStatus(&status);
435 if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) return false;
437 return true;
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,
446 // or whatever.
447 if (containAxes.IsBoth()) {
448 return containAxes.ContainIntrinsicSize({}, *this);
451 if (!isVideo) {
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()) {
475 return {};
478 return containAxes.ContainIntrinsicSize(IntrinsicSize(kFallbackIntrinsicSize),
479 *this);
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,
492 aNotify);
493 } else {
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);
515 if (imageLoader) {
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()) {
528 return false;
530 auto* element = static_cast<HTMLVideoElement*>(GetContent());
531 return element->GetVideoSize().isSome();
534 void nsVideoFrame::UpdateTextTrack() {
535 static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
538 namespace mozilla {
540 class nsDisplayVideo : public nsPaintedDisplayItem {
541 public:
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()) {
558 return nullptr;
561 RefPtr<ImageContainer> container = element->GetImageContainer();
562 if (!container) {
563 return nullptr;
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.
571 return nullptr;
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()) {
582 return nullptr;
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());
596 gfxRect destGFXRect;
597 RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
598 if (!container) {
599 return true;
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,
608 destGFXRect.height);
609 aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
610 aSc, rect, rect);
611 return true;
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 {
619 *aSnap = true;
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());
635 gfxRect destGFXRect;
636 RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
637 if (!container) {
638 return;
641 VideoInfo::Rotation rotationDeg = element->RotationDegrees();
642 Matrix preTransform = ComputeRotationMatrix(
643 destGFXRect.Width(), destGFXRect.Height(), rotationDeg);
644 Matrix transform =
645 preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y);
647 AutoLockImage autoLock(container);
648 Image* image = autoLock.GetImage(TimeStamp::Now());
649 if (!image) {
650 return;
652 RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
653 if (!surface || !surface->IsValid()) {
654 return;
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);
666 aCtx->SetMatrix(
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())),
677 DrawOptions());
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()) {
692 return;
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).
700 uint32_t clipFlags;
701 if (shouldDisplayPoster ||
702 !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
703 clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
704 } else {
705 clipFlags = 0;
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(
721 aBuilder, child,
722 aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
723 aBuilder->GetDirtyRect() - child->GetOffsetTo(this));
725 child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());