From da820542cf5e73ef4be6a3b7b82f586e6ca84d71 Mon Sep 17 00:00:00 2001 From: Daniel Holbert Date: Fri, 14 Nov 2014 16:45:24 -0800 Subject: [PATCH] Bug 624647 part 2: Honor "object-fit" & "object-position" in nsImageFrame, nsSubDocumentFrame, & nsVideoFrame. r=roc --- layout/generic/nsImageFrame.cpp | 46 ++++++++---- layout/generic/nsSubDocumentFrame.cpp | 37 +++++++-- layout/generic/nsVideoFrame.cpp | 103 +++++++++++--------------- layout/style/html.css | 7 ++ 4 files changed, 113 insertions(+), 80 deletions(-) diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index ca5e7ce1e15..3b9f70b62ef 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -347,25 +347,36 @@ nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) { - // Set the translation components. + // First, figure out destRect (the rect we're rendering into). + // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here, + // because GetInnerArea() might be smaller if we're fragmented, whereas + // mComputedSize has our full content-box size (which we need for + // ComputeObjectDestRect to work correctly). + nsRect constraintRect(GetInnerArea().TopLeft(), mComputedSize); + constraintRect.y -= GetContinuationOffset(); + + nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(constraintRect, + mIntrinsicSize, + mIntrinsicRatio, + StylePosition()); + // Set the translation components, based on destRect // XXXbz does this introduce rounding errors because of the cast to // float? Should we just manually add that stuff in every time // instead? - nsRect innerArea = GetInnerArea(); - aTransform.SetToTranslate(float(innerArea.x), - float(innerArea.y - GetContinuationOffset())); + aTransform.SetToTranslate(float(destRect.x), + float(destRect.y)); - // Set the scale factors. + // Set the scale factors, based on destRect and intrinsic size. if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.width.GetCoordValue() != 0 && mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.height.GetCoordValue() != 0 && - mIntrinsicSize.width.GetCoordValue() != mComputedSize.width && - mIntrinsicSize.height.GetCoordValue() != mComputedSize.height) { + mIntrinsicSize.width.GetCoordValue() != destRect.width && + mIntrinsicSize.height.GetCoordValue() != destRect.height) { - aTransform.SetScale(float(mComputedSize.width) / + aTransform.SetScale(float(destRect.width) / float(mIntrinsicSize.width.GetCoordValue()), - float(mComputedSize.height) / + float(destRect.height) / float(mIntrinsicSize.height.GetCoordValue())); return true; } @@ -1489,9 +1500,18 @@ nsImageFrame::PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt, // Render the image into our content area (the area inside // the borders and padding) NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width"); - nsRect inner = GetInnerArea() + aPt; - nsRect dest(inner.TopLeft(), mComputedSize); - dest.y -= GetContinuationOffset(); + + // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here, + // because GetInnerArea() might be smaller if we're fragmented, whereas + // mComputedSize has our full content-box size (which we need for + // ComputeObjectDestRect to work correctly). + nsRect constraintRect(aPt + GetInnerArea().TopLeft(), mComputedSize); + constraintRect.y -= GetContinuationOffset(); + + nsRect dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect, + mIntrinsicSize, + mIntrinsicRatio, + StylePosition()); nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(), PresContext(), aImage, @@ -1501,7 +1521,7 @@ nsImageFrame::PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt, nsImageMap* map = GetImageMap(); if (map) { gfxPoint devPixelOffset = - nsLayoutUtils::PointToGfxPoint(inner.TopLeft(), + nsLayoutUtils::PointToGfxPoint(dest.TopLeft(), PresContext()->AppUnitsPerDevPixel()); AutoRestoreTransform autoRestoreTransform(drawTarget); drawTarget->SetTransform( diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp index 2d242fe95bc..55d14688cca 100644 --- a/layout/generic/nsSubDocumentFrame.cpp +++ b/layout/generic/nsSubDocumentFrame.cpp @@ -273,6 +273,18 @@ nsSubDocumentFrame::GetSubdocumentSize() } else { docSizeAppUnits = GetContentRect().Size(); } + // Adjust subdocument size, according to 'object-fit' and the + // subdocument's intrinsic size and ratio. + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + if (subDocRoot) { + nsRect destRect = + nsLayoutUtils::ComputeObjectDestRect(nsRect(nsPoint(), docSizeAppUnits), + subDocRoot->GetIntrinsicSize(), + subDocRoot->GetIntrinsicRatio(), + StylePosition()); + docSizeAppUnits = destRect.Size(); + } + return nsIntSize(presContext->AppUnitsToDevPixels(docSizeAppUnits.width), presContext->AppUnitsToDevPixels(docSizeAppUnits.height)); } @@ -756,14 +768,27 @@ nsSubDocumentFrame::Reflow(nsPresContext* aPresContext, nsPoint offset = nsPoint(aReflowState.ComputedPhysicalBorderPadding().left, aReflowState.ComputedPhysicalBorderPadding().top); - nsSize innerSize(aDesiredSize.Width(), aDesiredSize.Height()); - innerSize.width -= aReflowState.ComputedPhysicalBorderPadding().LeftRight(); - innerSize.height -= aReflowState.ComputedPhysicalBorderPadding().TopBottom(); - if (mInnerView) { + const nsMargin& bp = aReflowState.ComputedPhysicalBorderPadding(); + nsSize innerSize(aDesiredSize.Width() - bp.LeftRight(), + aDesiredSize.Height() - bp.TopBottom()); + + // Size & position the view according to 'object-fit' & 'object-position'. + nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame(); + IntrinsicSize intrinsSize; + nsSize intrinsRatio; + if (subDocRoot) { + intrinsSize = subDocRoot->GetIntrinsicSize(); + intrinsRatio = subDocRoot->GetIntrinsicRatio(); + } + nsRect destRect = + nsLayoutUtils::ComputeObjectDestRect(nsRect(offset, innerSize), + intrinsSize, intrinsRatio, + StylePosition()); + nsViewManager* vm = mInnerView->GetViewManager(); - vm->MoveViewTo(mInnerView, offset.x, offset.y); - vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), innerSize), true); + vm->MoveViewTo(mInnerView, destRect.x, destRect.y); + vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), destRect.Size()), true); } aDesiredSize.SetOverflowAreasToDesiredBounds(); diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp index 20bd0cb4685..f2a8a5496f9 100644 --- a/layout/generic/nsVideoFrame.cpp +++ b/layout/generic/nsVideoFrame.cpp @@ -153,32 +153,18 @@ nsVideoFrame::IsLeaf() const return true; } -// Return the largest rectangle that fits in aRect and has the -// same aspect ratio as aRatio, centered at the center of aRect -static gfxRect -CorrectForAspectRatio(const gfxRect& aRect, const nsIntSize& aRatio) -{ - NS_ASSERTION(aRatio.width > 0 && aRatio.height > 0 && !aRect.IsEmpty(), - "Nothing to draw"); - // Choose scale factor that scales aRatio to just fit into aRect - gfxFloat scale = - std::min(aRect.Width()/aRatio.width, aRect.Height()/aRatio.height); - gfxSize scaledRatio(scale*aRatio.width, scale*aRatio.height); - gfxPoint topLeft((aRect.Width() - scaledRatio.width)/2, - (aRect.Height() - scaledRatio.height)/2); - return gfxRect(aRect.TopLeft() + topLeft, scaledRatio); -} - already_AddRefed nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsDisplayItem* aItem, const ContainerLayerParameters& aContainerParameters) { - nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame(); + nsSize contentBoxSize = GetContentRectRelativeToSelf().Size(); HTMLVideoElement* element = static_cast(GetContent()); - nsIntSize videoSize; - if (NS_FAILED(element->GetVideoSize(&videoSize)) || area.IsEmpty()) { + + nsIntSize videoSizeInPx; + if (NS_FAILED(element->GetVideoSize(&videoSizeInPx)) || + contentBoxSize.IsEmpty()) { return nullptr; } @@ -194,21 +180,31 @@ nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder, return nullptr; } - // Compute the rectangle in which to paint the video. We need to use - // the largest rectangle that fills our content-box and has the - // correct aspect ratio. - nsPresContext* presContext = PresContext(); - gfxRect r = gfxRect(presContext->AppUnitsToGfxUnits(area.x), - presContext->AppUnitsToGfxUnits(area.y), - presContext->AppUnitsToGfxUnits(area.width), - presContext->AppUnitsToGfxUnits(area.height)); - r = CorrectForAspectRatio(r, videoSize); - r.Round(); - if (r.IsEmpty()) { + // Convert video size from pixel units into app units, to get an aspect-ratio + // (which has to be represented as a nsSize) and an IntrinsicSize that we + // can pass to ComputeObjectRenderRect. + nsSize aspectRatio(nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width), + nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height)); + IntrinsicSize intrinsicSize; + intrinsicSize.width.SetCoordValue(aspectRatio.width); + intrinsicSize.height.SetCoordValue(aspectRatio.height); + + nsRect contentBoxRect( + GetContentRectRelativeToSelf().TopLeft() + aItem->ToReferenceFrame(), + contentBoxSize); + + nsRect dest = nsLayoutUtils::ComputeObjectDestRect(contentBoxRect, + intrinsicSize, + aspectRatio, + StylePosition()); + + gfxRect destGFXRect = PresContext()->AppUnitsToGfxUnits(dest); + destGFXRect.Round(); + if (destGFXRect.IsEmpty()) { return nullptr; } - IntSize scaleHint(static_cast(r.Width()), - static_cast(r.Height())); + IntSize scaleHint(static_cast(destGFXRect.Width()), + static_cast(destGFXRect.Height())); container->SetScaleHint(scaleHint); nsRefPtr layer = static_cast @@ -222,10 +218,10 @@ nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder, layer->SetContainer(container); layer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(this)); // Set a transform on the layer to draw the video in the right place - gfxPoint p = r.TopLeft() + aContainerParameters.mOffset; + gfxPoint p = destGFXRect.TopLeft() + aContainerParameters.mOffset; Matrix transform = Matrix::Translation(p.x, p.y); layer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); - layer->SetScaleToSize(IntSize(r.width, r.height), ScaleMode::STRETCH); + layer->SetScaleToSize(scaleHint, ScaleMode::STRETCH); nsRefPtr result = layer.forget(); return result.forget(); } @@ -287,35 +283,20 @@ nsVideoFrame::Reflow(nsPresContext* aPresContext, aMetrics.Width(), aMetrics.Height()); - uint32_t posterHeight, posterWidth; - nsSize scaledPosterSize(0, 0); - nsSize computedArea(aReflowState.ComputedWidth(), aReflowState.ComputedHeight()); - nsPoint posterTopLeft(0, 0); - - nsCOMPtr posterImage = do_QueryInterface(mPosterImage); - if (!posterImage) { - return; + nsRect posterRenderRect; + if (ShouldDisplayPoster()) { + posterRenderRect = + nsRect(nsPoint(mBorderPadding.left, mBorderPadding.top), + nsSize(aReflowState.ComputedWidth(), + aReflowState.ComputedHeight())); } - posterImage->GetNaturalHeight(&posterHeight); - posterImage->GetNaturalWidth(&posterWidth); - - if (ShouldDisplayPoster() && posterHeight && posterWidth) { - gfxFloat scale = - std::min(static_cast(computedArea.width)/nsPresContext::CSSPixelsToAppUnits(static_cast(posterWidth)), - static_cast(computedArea.height)/nsPresContext::CSSPixelsToAppUnits(static_cast(posterHeight))); - gfxSize scaledRatio = gfxSize(scale*posterWidth, scale*posterHeight); - scaledPosterSize.width = nsPresContext::CSSPixelsToAppUnits(static_cast(scaledRatio.width)); - scaledPosterSize.height = nsPresContext::CSSPixelsToAppUnits(static_cast(scaledRatio.height)); - } - kidReflowState.SetComputedWidth(scaledPosterSize.width); - kidReflowState.SetComputedHeight(scaledPosterSize.height); - posterTopLeft.x = ((computedArea.width - scaledPosterSize.width) / 2) + mBorderPadding.left; - posterTopLeft.y = ((computedArea.height - scaledPosterSize.height) / 2) + mBorderPadding.top; - + kidReflowState.SetComputedWidth(posterRenderRect.width); + kidReflowState.SetComputedHeight(posterRenderRect.height); ReflowChild(imageFrame, aPresContext, kidDesiredSize, kidReflowState, - posterTopLeft.x, posterTopLeft.y, 0, aStatus); - FinishReflowChild(imageFrame, aPresContext, kidDesiredSize, &kidReflowState, - posterTopLeft.x, posterTopLeft.y, 0); + posterRenderRect.x, posterRenderRect.y, 0, aStatus); + FinishReflowChild(imageFrame, aPresContext, + kidDesiredSize, &kidReflowState, + posterRenderRect.x, posterRenderRect.y, 0); } else if (child->GetContent() == mVideoControls) { // Reflow the video controls frame. nsBoxLayoutState boxState(PresContext(), aReflowState.rendContext); diff --git a/layout/style/html.css b/layout/style/html.css index 8de87b3b509..bd4e7e950c2 100644 --- a/layout/style/html.css +++ b/layout/style/html.css @@ -720,6 +720,13 @@ video { object-fit: contain; } +video > img:-moz-native-anonymous { + /* Video poster images should render with the video element's "object-fit" & + "object-position" properties */ + object-fit: inherit !important; + object-position: inherit !important; +} + audio:not([controls]) { display: none; }