gecko-dev/layout/generic/nsVideoFrame.cpp
Ting-Yu Lin 26a6db6fb1 Bug 1909761 Part 2 - Create a helper struct IntrinsicSizeInput to aggregate needed data when computing intrinsic inline size. r=dholbert
This patch changes the signature to `GetMinISize()`, `GetPrefISize()`,
`IntrinsicISize` by adding a helper struct as a preparation. Then we can just
add more data such as a percentage basis to the struct without altering the
signature in the future.

When passing `IntrinsicSizeInput` struct down to another helper method, we
generally just pass the original one if the method is computing the intrinsic
size of our own or our anonymous children. If the method is computing our
children's intrinsic contribution, we'll need to create a brand new
`IntrinsicSizeInput` for our children.

Differential Revision: https://phabricator.services.mozilla.com/D219521
2024-09-03 04:25:41 +00:00

731 lines
26 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* rendering object for the HTML <video> element */
#include "nsVideoFrame.h"
#include "nsCOMPtr.h"
#include "nsGkAtoms.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "nsDisplayList.h"
#include "nsGenericHTMLElement.h"
#include "nsPresContext.h"
#include "nsContentCreatorFunctions.h"
#include "nsIContentInlines.h"
#include "nsImageFrame.h"
#include "nsIImageLoadingContent.h"
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "ImageContainer.h"
#include "nsStyleUtil.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::layers;
using namespace mozilla::dom;
using namespace mozilla::gfx;
nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
return new (aPresShell) nsVideoFrame(aStyle, aPresShell->GetPresContext());
}
nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
return new (aPresShell) nsAudioFrame(aStyle, aPresShell->GetPresContext());
}
NS_IMPL_FRAMEARENA_HELPERS(nsVideoFrame)
NS_QUERYFRAME_HEAD(nsVideoFrame)
NS_QUERYFRAME_ENTRY(nsVideoFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
NS_IMPL_FRAMEARENA_HELPERS(nsAudioFrame)
NS_QUERYFRAME_HEAD(nsAudioFrame)
NS_QUERYFRAME_ENTRY(nsAudioFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsVideoFrame)
// A matrix to obtain a correct-rotated video frame.
static Matrix ComputeRotationMatrix(gfxFloat aRotatedWidth,
gfxFloat aRotatedHeight,
VideoRotation aDegrees) {
Matrix shiftVideoCenterToOrigin;
if (aDegrees == VideoRotation::kDegree_90 ||
aDegrees == VideoRotation::kDegree_270) {
shiftVideoCenterToOrigin =
Matrix::Translation(-aRotatedHeight / 2.0, -aRotatedWidth / 2.0);
} else {
shiftVideoCenterToOrigin =
Matrix::Translation(-aRotatedWidth / 2.0, -aRotatedHeight / 2.0);
}
auto angle = static_cast<double>(aDegrees) / 180.0 * M_PI;
Matrix rotation = Matrix::Rotation(static_cast<gfx::Float>(angle));
Matrix shiftLeftTopToOrigin =
Matrix::Translation(aRotatedWidth / 2.0, aRotatedHeight / 2.0);
return shiftVideoCenterToOrigin * rotation * shiftLeftTopToOrigin;
}
static void SwapScaleWidthHeightForRotation(IntSize& aSize,
VideoRotation aDegrees) {
if (aDegrees == VideoRotation::kDegree_90 ||
aDegrees == VideoRotation::kDegree_270) {
int32_t tmpWidth = aSize.width;
aSize.width = aSize.height;
aSize.height = tmpWidth;
}
}
nsVideoFrame::nsVideoFrame(ComputedStyle* aStyle, nsPresContext* aPc,
ClassID aClassID)
: nsContainerFrame(aStyle, aPc, aClassID),
mIsAudio(aClassID == nsAudioFrame::kClassID) {
EnableVisibilityTracking();
}
nsVideoFrame::~nsVideoFrame() = default;
nsAudioFrame::nsAudioFrame(ComputedStyle* aStyle, nsPresContext* aPc)
: nsVideoFrame(aStyle, aPc, kClassID) {}
nsAudioFrame::~nsAudioFrame() = default;
nsresult nsVideoFrame::CreateAnonymousContent(
nsTArray<ContentInfo>& aElements) {
nsNodeInfoManager* nodeInfoManager =
GetContent()->GetComposedDoc()->NodeInfoManager();
RefPtr<NodeInfo> nodeInfo;
if (HasVideoElement()) {
// Create an anonymous image element as a child to hold the poster
// image. We may not have a poster image now, but one could be added
// before we load, or on a subsequent load.
nodeInfo = nodeInfoManager->GetNodeInfo(
nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
mPosterImage = NS_NewHTMLImageElement(nodeInfo.forget());
NS_ENSURE_TRUE(mPosterImage, NS_ERROR_OUT_OF_MEMORY);
UpdatePosterSource(false);
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
aElements.AppendElement(mPosterImage);
// Set up the caption overlay div for showing any TextTrack data
nodeInfo = nodeInfoManager->GetNodeInfo(
nsGkAtoms::div, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
mCaptionDiv = NS_NewHTMLDivElement(nodeInfo.forget());
NS_ENSURE_TRUE(mCaptionDiv, NS_ERROR_OUT_OF_MEMORY);
nsGenericHTMLElement* div =
static_cast<nsGenericHTMLElement*>(mCaptionDiv.get());
div->SetClassName(u"caption-box"_ns);
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
aElements.AppendElement(mCaptionDiv);
UpdateTextTrack();
}
return NS_OK;
}
void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFliter) {
if (mPosterImage) {
aElements.AppendElement(mPosterImage);
}
if (mCaptionDiv) {
aElements.AppendElement(mCaptionDiv);
}
}
nsIContent* nsVideoFrame::GetVideoControls() const {
if (!mContent->GetShadowRoot()) {
return nullptr;
}
// The video controls <div> is the only child of the UA Widget Shadow Root
// if it is present. It is only lazily inserted into the DOM when
// the controls attribute is set.
MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
return mContent->GetShadowRoot()->GetFirstChild();
}
void nsVideoFrame::Destroy(DestroyContext& aContext) {
if (mReflowCallbackPosted) {
PresShell()->CancelReflowCallback(this);
}
aContext.AddAnonymousContent(mCaptionDiv.forget());
aContext.AddAnonymousContent(mPosterImage.forget());
nsContainerFrame::Destroy(aContext);
}
class DispatchResizeEvent : public Runnable {
public:
explicit DispatchResizeEvent(nsIContent* aContent,
const nsLiteralString& aName)
: Runnable("DispatchResizeEvent"), mContent(aContent), mName(aName) {}
NS_IMETHOD Run() override {
nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(), mContent, mName,
CanBubble::eNo, Cancelable::eNo);
return NS_OK;
}
nsCOMPtr<nsIContent> mContent;
const nsLiteralString mName;
};
bool nsVideoFrame::ReflowFinished() {
mReflowCallbackPosted = false;
auto GetSize = [&](nsIContent* aContent) -> Maybe<nsSize> {
if (!aContent) {
return Nothing();
}
nsIFrame* f = aContent->GetPrimaryFrame();
if (!f) {
return Nothing();
}
return Some(f->GetSize());
};
AutoTArray<nsCOMPtr<nsIRunnable>, 2> events;
if (auto size = GetSize(mCaptionDiv)) {
if (*size != mCaptionTrackedSize) {
mCaptionTrackedSize = *size;
events.AppendElement(
new DispatchResizeEvent(mCaptionDiv, u"resizecaption"_ns));
}
}
nsIContent* controls = GetVideoControls();
if (auto size = GetSize(controls)) {
if (*size != mControlsTrackedSize) {
mControlsTrackedSize = *size;
events.AppendElement(
new DispatchResizeEvent(controls, u"resizevideocontrols"_ns));
}
}
for (auto& event : events) {
nsContentUtils::AddScriptRunner(event.forget());
}
return false;
}
void nsVideoFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsVideoFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
NS_FRAME_TRACE(
NS_FRAME_TRACE_CALLS,
("enter nsVideoFrame::Reflow: availSize=%d,%d",
aReflowInput.AvailableISize(), aReflowInput.AvailableBSize()));
MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
const WritingMode myWM = aReflowInput.GetWritingMode();
nscoord contentBoxBSize = aReflowInput.ComputedBSize();
const auto logicalBP = aReflowInput.ComputedLogicalBorderPadding(myWM);
const nscoord borderBoxISize =
aReflowInput.ComputedISize() + logicalBP.IStartEnd(myWM);
const bool isBSizeShrinkWrapping = (contentBoxBSize == NS_UNCONSTRAINEDSIZE);
nscoord borderBoxBSize;
if (!isBSizeShrinkWrapping) {
borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
}
nsIContent* videoControlsDiv = GetVideoControls();
// Reflow the child frames. We may have up to three: an image
// frame (for the poster image), a container frame for the controls,
// and a container frame for the caption.
for (nsIFrame* child : mFrames) {
nsSize oldChildSize = child->GetSize();
nsReflowStatus childStatus;
const WritingMode childWM = child->GetWritingMode();
LogicalSize availableSize = aReflowInput.ComputedSize(childWM);
availableSize.BSize(childWM) = NS_UNCONSTRAINEDSIZE;
ReflowInput kidReflowInput(aPresContext, aReflowInput, child,
availableSize);
ReflowOutput kidDesiredSize(myWM);
const nsSize containerSize =
aReflowInput.ComputedSizeAsContainerIfConstrained();
if (child->GetContent() == mPosterImage) {
// Reflow the poster frame.
const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
const LogicalSize posterRenderSize = aReflowInput.ComputedSize(childWM);
kidReflowInput.SetComputedISize(posterRenderSize.ISize(childWM));
kidReflowInput.SetComputedBSize(posterRenderSize.BSize(childWM));
ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
childOrigin, containerSize, ReflowChildFlags::Default,
childStatus);
MOZ_ASSERT(childStatus.IsFullyComplete(),
"We gave our child unconstrained available block-size, "
"so it should be complete!");
FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
myWM, childOrigin, containerSize,
ReflowChildFlags::Default);
} else if (child->GetContent() == mCaptionDiv ||
child->GetContent() == videoControlsDiv) {
// Reflow the caption and control bar frames.
const LogicalPoint childOrigin = logicalBP.StartOffset(myWM);
ReflowChild(child, aPresContext, kidDesiredSize, kidReflowInput, myWM,
childOrigin, containerSize, ReflowChildFlags::Default,
childStatus);
MOZ_ASSERT(childStatus.IsFullyComplete(),
"We gave our child unconstrained available block-size, "
"so it should be complete!");
if (child->GetContent() == videoControlsDiv && isBSizeShrinkWrapping) {
// Resolve our own BSize based on the controls' size in the
// same axis. Unless we're size-contained, in which case we
// have to behave as if we have an intrinsic size of 0.
if (GetContainSizeAxes().mBContained) {
contentBoxBSize = 0;
} else {
contentBoxBSize = kidDesiredSize.BSize(myWM);
}
}
FinishReflowChild(child, aPresContext, kidDesiredSize, &kidReflowInput,
myWM, childOrigin, containerSize,
ReflowChildFlags::Default);
if (child->GetSize() != oldChildSize) {
// We might find non-primary frames in printing due to
// ReplicateFixedFrames, but we don't care about that.
MOZ_ASSERT(child->IsPrimaryFrame() ||
PresContext()->IsPrintingOrPrintPreview(),
"We only look at the primary frame in ReflowFinished");
if (!mReflowCallbackPosted) {
mReflowCallbackPosted = true;
PresShell()->PostReflowCallback(this);
}
}
} else {
NS_ERROR("Unexpected extra child frame in nsVideoFrame; skipping");
}
}
if (isBSizeShrinkWrapping) {
if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
// We didn't get a BSize from our intrinsic size/ratio, nor did we
// get one from our controls. Just use BSize of 0.
contentBoxBSize = 0;
}
contentBoxBSize = aReflowInput.ApplyMinMaxBSize(contentBoxBSize);
borderBoxBSize = contentBoxBSize + logicalBP.BStartEnd(myWM);
}
LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
aMetrics.SetSize(myWM, logicalDesiredSize);
aMetrics.SetOverflowAreasToDesiredBounds();
FinishAndStoreOverflow(&aMetrics);
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
aMetrics.Width(), aMetrics.Height()));
MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
}
#ifdef ACCESSIBILITY
a11y::AccType nsVideoFrame::AccessibleType() { return a11y::eHTMLMediaType; }
#endif
#ifdef DEBUG_FRAME_DUMP
nsresult nsVideoFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"HTMLVideo"_ns, aResult);
}
#endif
nsIFrame::SizeComputationResult nsVideoFrame::ComputeSize(
gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
nscoord aAvailableISize, const LogicalSize& aMargin,
const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
ComputeSizeFlags aFlags) {
if (!HasVideoElement()) {
return nsContainerFrame::ComputeSize(
aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
aBorderPadding, aSizeOverrides, aFlags);
}
return {ComputeSizeWithIntrinsicDimensions(
aRenderingContext, aWM, GetIntrinsicSize(), GetAspectRatio(),
aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags),
AspectRatioUsage::None};
}
nscoord nsVideoFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
IntrinsicISizeType aType) {
// <audio> / <video> has the same min / pref ISize.
return GetIntrinsicSize().ISize(GetWritingMode()).valueOr(0);
}
Maybe<nsSize> nsVideoFrame::PosterImageSize() const {
// Use the poster image frame's size.
nsIFrame* child = GetPosterImage()->GetPrimaryFrame();
return child->GetIntrinsicSize().ToSize();
}
AspectRatio nsVideoFrame::GetIntrinsicRatio() const {
if (!HasVideoElement()) {
// Audio elements have no intrinsic ratio.
return AspectRatio();
}
// 'contain:[inline-]size' replaced elements have no intrinsic ratio.
if (GetContainSizeAxes().IsAny()) {
return AspectRatio();
}
auto* element = static_cast<HTMLVideoElement*>(GetContent());
if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
return AspectRatio::FromSize(*size);
}
if (ShouldDisplayPoster()) {
if (Maybe<nsSize> imgSize = PosterImageSize()) {
return AspectRatio::FromSize(*imgSize);
}
}
if (StylePosition()->mAspectRatio.HasRatio()) {
return AspectRatio();
}
return AspectRatio::FromSize(kFallbackIntrinsicSizeInPixels);
}
bool nsVideoFrame::ShouldDisplayPoster() const {
if (!HasVideoElement()) {
return false;
}
auto* element = static_cast<HTMLVideoElement*>(GetContent());
if (element->GetPlayedOrSeeked() && HasVideoData()) {
return false;
}
nsCOMPtr<nsIImageLoadingContent> imgContent = do_QueryInterface(mPosterImage);
NS_ENSURE_TRUE(imgContent, false);
nsCOMPtr<imgIRequest> request;
nsresult res = imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
getter_AddRefs(request));
if (NS_FAILED(res) || !request) {
return false;
}
uint32_t status = 0;
res = request->GetImageStatus(&status);
if (NS_FAILED(res) || (status & imgIRequest::STATUS_ERROR)) {
return false;
}
return true;
}
IntrinsicSize nsVideoFrame::GetIntrinsicSize() {
const auto containAxes = GetContainSizeAxes();
const auto isVideo = HasVideoElement();
// Intrinsic size will be given by contain-intrinsic-size if the element is
// size-contained. If both axes have containment, FinishIntrinsicSize() will
// ignore the fallback size argument, so we can just pass no intrinsic size,
// or whatever.
if (containAxes.IsBoth()) {
return FinishIntrinsicSize(containAxes, {});
}
if (!isVideo) {
// An audio element with no "controls" attribute, distinguished by the last
// and only child being the control, falls back to no intrinsic size.
if (!mFrames.LastChild()) {
return FinishIntrinsicSize(containAxes, {});
}
return FinishIntrinsicSize(containAxes,
IntrinsicSize(kFallbackIntrinsicSize));
}
auto* element = static_cast<HTMLVideoElement*>(GetContent());
if (Maybe<CSSIntSize> size = element->GetVideoSize()) {
return FinishIntrinsicSize(containAxes,
IntrinsicSize(CSSPixel::ToAppUnits(*size)));
}
if (ShouldDisplayPoster()) {
if (Maybe<nsSize> imgSize = PosterImageSize()) {
return FinishIntrinsicSize(containAxes, IntrinsicSize(*imgSize));
}
}
if (StylePosition()->mAspectRatio.HasRatio()) {
return {};
}
return FinishIntrinsicSize(containAxes,
IntrinsicSize(kFallbackIntrinsicSize));
}
void nsVideoFrame::UpdatePosterSource(bool aNotify) {
NS_ASSERTION(HasVideoElement(), "Only call this on <video> elements.");
HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
if (element->HasAttr(nsGkAtoms::poster) &&
!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::poster,
nsGkAtoms::_empty, eIgnoreCase)) {
nsAutoString posterStr;
element->GetPoster(posterStr);
mPosterImage->SetAttr(kNameSpaceID_None, nsGkAtoms::src, posterStr,
aNotify);
} else {
mPosterImage->UnsetAttr(kNameSpaceID_None, nsGkAtoms::src, aNotify);
}
}
nsresult nsVideoFrame::AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType) {
if (aAttribute == nsGkAtoms::poster && HasVideoElement()) {
UpdatePosterSource(true);
}
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
}
void nsVideoFrame::OnVisibilityChange(
Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
if (HasVideoElement()) {
static_cast<HTMLMediaElement*>(GetContent())
->OnVisibilityChange(aNewVisibility);
}
nsCOMPtr<nsIImageLoadingContent> imageLoader =
do_QueryInterface(mPosterImage);
if (imageLoader) {
imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
}
nsContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
}
bool nsVideoFrame::HasVideoData() const {
if (!HasVideoElement()) {
return false;
}
auto* element = static_cast<HTMLVideoElement*>(GetContent());
return element->GetVideoSize().isSome();
}
void nsVideoFrame::UpdateTextTrack() {
static_cast<HTMLMediaElement*>(GetContent())->NotifyCueDisplayStatesChanged();
}
namespace mozilla {
class nsDisplayVideo : public nsPaintedDisplayItem {
public:
nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayVideo);
}
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayVideo)
NS_DISPLAY_DECL_NAME("Video", TYPE_VIDEO)
already_AddRefed<ImageContainer> GetImageContainer(gfxRect& aDestGFXRect) {
nsRect area = Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
HTMLVideoElement* element =
static_cast<HTMLVideoElement*>(Frame()->GetContent());
Maybe<CSSIntSize> videoSizeInPx = element->GetVideoSize();
if (videoSizeInPx.isNothing() || area.IsEmpty()) {
return nullptr;
}
RefPtr<ImageContainer> container = element->GetImageContainer();
if (!container) {
return nullptr;
}
// Retrieve the size of the decoded video frame, before being scaled
// by pixel aspect ratio.
mozilla::gfx::IntSize frameSize = container->GetCurrentSize();
if (frameSize.width == 0 || frameSize.height == 0) {
// No image, or zero-sized image. Don't render.
return nullptr;
}
const auto aspectRatio = AspectRatio::FromSize(*videoSizeInPx);
const IntrinsicSize intrinsicSize(CSSPixel::ToAppUnits(*videoSizeInPx));
nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
area, intrinsicSize, aspectRatio, Frame()->StylePosition());
aDestGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
aDestGFXRect.Round();
if (aDestGFXRect.IsEmpty()) {
return nullptr;
}
return container.forget();
}
virtual bool CreateWebRenderCommands(
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) override {
HTMLVideoElement* element =
static_cast<HTMLVideoElement*>(Frame()->GetContent());
gfxRect destGFXRect;
RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
if (!container) {
return true;
}
container->SetRotation(element->RotationDegrees());
// If the image container is empty, we don't want to fallback. Any other
// failure will be due to resource constraints and fallback is unlikely to
// help us. Hence we can ignore the return value from PushImage.
LayoutDeviceRect rect(destGFXRect.x, destGFXRect.y, destGFXRect.width,
destGFXRect.height);
aManager->CommandBuilder().PushImage(this, container, aBuilder, aResources,
aSc, rect, rect);
return true;
}
// For opaque videos, we will want to override GetOpaqueRegion here.
// This is tracked by bug 1545498.
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const override {
*aSnap = true;
nsIFrame* f = Frame();
return f->GetContentRectRelativeToSelf() + ToReferenceFrame();
}
// Only report FirstContentfulPaint when the video is set
bool IsContentful() const override {
nsVideoFrame* f = static_cast<nsVideoFrame*>(Frame());
HTMLVideoElement* video = HTMLVideoElement::FromNode(f->GetContent());
return video->VideoWidth() > 0;
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) override {
HTMLVideoElement* element =
static_cast<HTMLVideoElement*>(Frame()->GetContent());
gfxRect destGFXRect;
RefPtr<ImageContainer> container = GetImageContainer(destGFXRect);
if (!container) {
return;
}
VideoRotation rotationDeg = element->RotationDegrees();
Matrix preTransform = ComputeRotationMatrix(
destGFXRect.Width(), destGFXRect.Height(), rotationDeg);
Matrix transform =
preTransform * Matrix::Translation(destGFXRect.x, destGFXRect.y);
AutoLockImage autoLock(container);
Image* image = autoLock.GetImage(TimeStamp::Now());
if (!image) {
return;
}
RefPtr<gfx::SourceSurface> surface = image->GetAsSourceSurface();
if (!surface || !surface->IsValid()) {
return;
}
gfx::IntSize size = surface->GetSize();
IntSize scaleToSize(static_cast<int32_t>(destGFXRect.Width()),
static_cast<int32_t>(destGFXRect.Height()));
// scaleHint is set regardless of rotation, so swap w/h if needed.
SwapScaleWidthHeightForRotation(scaleToSize, rotationDeg);
transform.PreScale(scaleToSize.width / double(size.Width()),
scaleToSize.height / double(size.Height()));
gfxContextMatrixAutoSaveRestore saveMatrix(aCtx);
aCtx->SetMatrix(
gfxUtils::SnapTransformTranslation(aCtx->CurrentMatrix(), nullptr));
transform = gfxUtils::SnapTransform(
transform, gfxRect(0, 0, size.width, size.height), nullptr);
aCtx->Multiply(ThebesMatrix(transform));
aCtx->GetDrawTarget()->FillRect(
Rect(0, 0, size.width, size.height),
SurfacePattern(surface, ExtendMode::CLAMP, Matrix(),
nsLayoutUtils::GetSamplingFilterForFrame(Frame())),
DrawOptions());
}
};
} // namespace mozilla
void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) {
if (!IsVisibleForPainting()) return;
DO_GLOBAL_REFLOW_COUNT_DSP("nsVideoFrame");
DisplayBorderBackgroundOutline(aBuilder, aLists);
if (HidesContent()) {
return;
}
const bool shouldDisplayPoster = ShouldDisplayPoster();
// NOTE: If we're displaying a poster image (instead of video data), we can
// trust the nsImageFrame to constrain its drawing to its content rect
// (which happens to be the same as our content rect).
uint32_t clipFlags;
if (shouldDisplayPoster ||
!nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) {
clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
} else {
clipFlags = 0;
}
DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
aBuilder, this, clipFlags);
if (HasVideoElement() && !shouldDisplayPoster) {
aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this);
}
// Add child frames to display list. We expect various children,
// but only want to draw mPosterImage conditionally. Others we
// always add to the display list.
for (nsIFrame* child : mFrames) {
if (child->GetContent() != mPosterImage || shouldDisplayPoster) {
nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild(
aBuilder, child,
aBuilder->GetVisibleRect() - child->GetOffsetTo(this),
aBuilder->GetDirtyRect() - child->GetOffsetTo(this));
child->BuildDisplayListForStackingContext(aBuilder, aLists.Content());
}
}
}