Bug 624647 part 2: Honor "object-fit" & "object-position" in nsImageFrame, nsSubDocumentFrame, & nsVideoFrame. r=roc
authorDaniel Holbert <dholbert@cs.stanford.edu>
Fri, 14 Nov 2014 16:45:24 -0800
changeset 215874 466d3ff030e6343ff8cba81c6e6f328dcea55738
parent 215873 39a32a0978f55f6ab4f7cb38af1e6cd05a5aa74a
child 215875 91b71378545828f35bc78bb7bec7c35e6b43fefb
push id51877
push userdholbert@mozilla.com
push dateSat, 15 Nov 2014 00:45:58 +0000
treeherdermozilla-inbound@3cd01637b2ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs624647
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 624647 part 2: Honor "object-fit" & "object-position" in nsImageFrame, nsSubDocumentFrame, & nsVideoFrame. r=roc
layout/generic/nsImageFrame.cpp
layout/generic/nsSubDocumentFrame.cpp
layout/generic/nsVideoFrame.cpp
layout/style/html.css
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -342,35 +342,46 @@ nsImageFrame::UpdateIntrinsicRatio(imgIC
     mIntrinsicRatio.SizeTo(0, 0);
 
   return mIntrinsicRatio != oldIntrinsicRatio;
 }
 
 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;
   }
 
   return false;
 }
 
 /*
@@ -1484,29 +1495,38 @@ nsImageFrame::PaintImage(nsRenderingCont
                          const nsRect& aDirtyRect, imgIContainer* aImage,
                          uint32_t aFlags)
 {
   DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
 
   // 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,
     nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect,
     nullptr, aFlags);
 
   nsImageMap* map = GetImageMap();
   if (map) {
     gfxPoint devPixelOffset =
-      nsLayoutUtils::PointToGfxPoint(inner.TopLeft(),
+      nsLayoutUtils::PointToGfxPoint(dest.TopLeft(),
                                      PresContext()->AppUnitsPerDevPixel());
     AutoRestoreTransform autoRestoreTransform(drawTarget);
     drawTarget->SetTransform(
       drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
 
     // solid white stroke:
     ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f)));
     map->Draw(this, *drawTarget, white);
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -268,16 +268,28 @@ nsSubDocumentFrame::GetSubdocumentSize()
     nsPresContext* presContext = PresContext();
     nsCOMPtr<nsIDOMHTMLFrameElement> frameElem =
       do_QueryInterface(GetContent());
     if (frameElem) {
       docSizeAppUnits = GetSize();
     } 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));
   }
 }
 
 bool
 nsSubDocumentFrame::PassPointerEventsToChildren()
 {
@@ -751,24 +763,37 @@ nsSubDocumentFrame::Reflow(nsPresContext
   // XUL <iframe> or <browser>, or HTML <iframe>, <object> or <embed>
   nsLeafFrame::DoReflow(aPresContext, aDesiredSize, aReflowState, aStatus);
 
   // "offset" is the offset of our content area from our frame's
   // top-left corner.
   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());
 
-  if (mInnerView) {
+    // 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();
   if (!ShouldClipSubdocument()) {
     nsIFrame* subdocRootFrame = GetSubdocumentRootFrame();
     if (subdocRootFrame) {
       aDesiredSize.mOverflowAreas.UnionWith(subdocRootFrame->GetOverflowAreas() + offset);
     }
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -148,89 +148,85 @@ nsVideoFrame::DestroyFrom(nsIFrame* aDes
 }
 
 bool
 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<Layer>
 nsVideoFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
                          LayerManager* aManager,
                          nsDisplayItem* aItem,
                          const ContainerLayerParameters& aContainerParameters)
 {
-  nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
+  nsSize contentBoxSize = GetContentRectRelativeToSelf().Size();
   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
-  nsIntSize videoSize;
-  if (NS_FAILED(element->GetVideoSize(&videoSize)) || area.IsEmpty()) {
+
+  nsIntSize videoSizeInPx;
+  if (NS_FAILED(element->GetVideoSize(&videoSizeInPx)) ||
+      contentBoxSize.IsEmpty()) {
     return nullptr;
   }
 
   nsRefPtr<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. No point creating a layer.
     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<int32_t>(r.Width()),
-                    static_cast<int32_t>(r.Height()));
+  IntSize scaleHint(static_cast<int32_t>(destGFXRect.Width()),
+                    static_cast<int32_t>(destGFXRect.Height()));
   container->SetScaleHint(scaleHint);
 
   nsRefPtr<ImageLayer> layer = static_cast<ImageLayer*>
     (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem));
   if (!layer) {
     layer = aManager->CreateImageLayer();
     if (!layer)
       return nullptr;
   }
 
   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<Layer> result = layer.forget();
   return result.forget();
 }
 
 class DispatchResizeToControls : public nsRunnable
 {
 public:
   explicit DispatchResizeToControls(nsIContent* aContent)
@@ -282,45 +278,30 @@ nsVideoFrame::Reflow(nsPresContext*     
       LogicalSize availableSize = aReflowState.AvailableSize(wm);
       nsHTMLReflowState kidReflowState(aPresContext,
                                        aReflowState,
                                        imageFrame,
                                        availableSize,
                                        aMetrics.Width(),
                                        aMetrics.Height());
 
-      uint32_t posterHeight, posterWidth;
-      nsSize scaledPosterSize(0, 0);
-      nsSize computedArea(aReflowState.ComputedWidth(), aReflowState.ComputedHeight());
-      nsPoint posterTopLeft(0, 0);
-
-      nsCOMPtr<nsIDOMHTMLImageElement> 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<float>(computedArea.width)/nsPresContext::CSSPixelsToAppUnits(static_cast<float>(posterWidth)),
-                 static_cast<float>(computedArea.height)/nsPresContext::CSSPixelsToAppUnits(static_cast<float>(posterHeight)));
-        gfxSize scaledRatio = gfxSize(scale*posterWidth, scale*posterHeight);
-        scaledPosterSize.width = nsPresContext::CSSPixelsToAppUnits(static_cast<float>(scaledRatio.width));
-        scaledPosterSize.height = nsPresContext::CSSPixelsToAppUnits(static_cast<int32_t>(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);
       nsSize size = child->GetSize();
       nsBoxFrame::LayoutChildAt(boxState,
                                 child,
                                 nsRect(mBorderPadding.left,
                                        mBorderPadding.top,
--- a/layout/style/html.css
+++ b/layout/style/html.css
@@ -715,16 +715,23 @@ audio:not([controls]) > xul|videocontrol
   visibility: hidden;
   -moz-binding: none;
 }
 
 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;
 }
 
 *|*::-moz-html-canvas-content {
   display: block !important;
   /* we want to be an absolute and fixed container */
   -moz-transform: translate(0) !important;