Bug 1547792 - AspectRatio should be a single ratio, not a size. r=dholbert
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 02 May 2019 23:28:21 +0000
changeset 531253 451701e88d92935b77385235832865dabe8c2942
parent 531252 020e530d49b3b177902dad1048c90d7d9b290354
child 531254 de1a60c3fbf2078321aa69300a3ee0c277ea59bc
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1547792
milestone68.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 1547792 - AspectRatio should be a single ratio, not a size. r=dholbert Differential Revision: https://phabricator.services.mozilla.com/D29244
image/ClippedImage.cpp
image/ClippedImage.h
image/DynamicImage.cpp
image/ImageWrapper.cpp
image/OrientedImage.cpp
image/OrientedImage.h
image/RasterImage.cpp
image/VectorImage.cpp
image/imgIContainer.idl
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/AspectRatio.h
layout/generic/moz.build
layout/generic/nsFlexContainerFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsFrame.h
layout/generic/nsHTMLCanvasFrame.cpp
layout/generic/nsHTMLCanvasFrame.h
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/generic/nsImageFrame.h
layout/generic/nsLineLayout.cpp
layout/generic/nsSubDocumentFrame.cpp
layout/generic/nsSubDocumentFrame.h
layout/generic/nsVideoFrame.cpp
layout/generic/nsVideoFrame.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsImageRenderer.cpp
layout/painting/nsImageRenderer.h
layout/style/nsStyleStruct.cpp
layout/svg/nsSVGOuterSVGFrame.cpp
layout/svg/nsSVGOuterSVGFrame.h
layout/xul/nsImageBoxFrame.cpp
--- a/image/ClippedImage.cpp
+++ b/image/ClippedImage.cpp
@@ -209,24 +209,22 @@ ClippedImage::GetIntrinsicSize(nsSize* a
   if (!ShouldClip()) {
     return InnerImage()->GetIntrinsicSize(aSize);
   }
 
   *aSize = nsSize(mClip.Width(), mClip.Height());
   return NS_OK;
 }
 
-NS_IMETHODIMP
-ClippedImage::GetIntrinsicRatio(nsSize* aRatio) {
+Maybe<AspectRatio> ClippedImage::GetIntrinsicRatio() {
   if (!ShouldClip()) {
-    return InnerImage()->GetIntrinsicRatio(aRatio);
+    return InnerImage()->GetIntrinsicRatio();
   }
 
-  *aRatio = nsSize(mClip.Width(), mClip.Height());
-  return NS_OK;
+  return Some(AspectRatio::FromSize(mClip.Width(), mClip.Height()));
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 ClippedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
   ImgDrawResult result;
   RefPtr<SourceSurface> surface;
   Tie(result, surface) =
       GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags, 1.0);
--- a/image/ClippedImage.h
+++ b/image/ClippedImage.h
@@ -29,17 +29,17 @@ class ClippedImage : public ImageWrapper
   typedef gfx::SourceSurface SourceSurface;
 
  public:
   NS_INLINE_DECL_REFCOUNTING_INHERITED(ClippedImage, ImageWrapper)
 
   NS_IMETHOD GetWidth(int32_t* aWidth) override;
   NS_IMETHOD GetHeight(int32_t* aHeight) override;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
-  NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
+  Maybe<AspectRatio> GetIntrinsicRatio() override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
   GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
   GetFrameAtSize(const gfx::IntSize& aSize, uint32_t aWhichFrame,
                  uint32_t aFlags) override;
   NS_IMETHOD_(bool)
   IsImageContainerAvailable(layers::LayerManager* aManager,
                             uint32_t aFlags) override;
--- a/image/DynamicImage.cpp
+++ b/image/DynamicImage.cpp
@@ -99,21 +99,19 @@ size_t DynamicImage::GetNativeSizesLengt
 
 NS_IMETHODIMP
 DynamicImage::GetIntrinsicSize(nsSize* aSize) {
   IntSize intSize(mDrawable->Size());
   *aSize = nsSize(intSize.width, intSize.height);
   return NS_OK;
 }
 
-NS_IMETHODIMP
-DynamicImage::GetIntrinsicRatio(nsSize* aSize) {
-  IntSize intSize(mDrawable->Size());
-  *aSize = nsSize(intSize.width, intSize.height);
-  return NS_OK;
+Maybe<AspectRatio> DynamicImage::GetIntrinsicRatio() {
+  auto size = mDrawable->Size();
+  return Some(AspectRatio::FromSize(size.width, size.height));
 }
 
 NS_IMETHODIMP_(Orientation)
 DynamicImage::GetOrientation() { return Orientation(); }
 
 NS_IMETHODIMP
 DynamicImage::GetType(uint16_t* aType) {
   *aType = imgIContainer::TYPE_RASTER;
--- a/image/ImageWrapper.cpp
+++ b/image/ImageWrapper.cpp
@@ -115,19 +115,18 @@ size_t ImageWrapper::GetNativeSizesLengt
   return mInnerImage->GetNativeSizesLength();
 }
 
 NS_IMETHODIMP
 ImageWrapper::GetIntrinsicSize(nsSize* aSize) {
   return mInnerImage->GetIntrinsicSize(aSize);
 }
 
-NS_IMETHODIMP
-ImageWrapper::GetIntrinsicRatio(nsSize* aSize) {
-  return mInnerImage->GetIntrinsicRatio(aSize);
+Maybe<AspectRatio> ImageWrapper::GetIntrinsicRatio() {
+  return mInnerImage->GetIntrinsicRatio();
 }
 
 NS_IMETHODIMP_(Orientation)
 ImageWrapper::GetOrientation() { return mInnerImage->GetOrientation(); }
 
 NS_IMETHODIMP
 ImageWrapper::GetType(uint16_t* aType) { return mInnerImage->GetType(aType); }
 
--- a/image/OrientedImage.cpp
+++ b/image/OrientedImage.cpp
@@ -62,25 +62,22 @@ OrientedImage::GetIntrinsicSize(nsSize* 
 
   if (mOrientation.SwapsWidthAndHeight()) {
     swap(aSize->width, aSize->height);
   }
 
   return rv;
 }
 
-NS_IMETHODIMP
-OrientedImage::GetIntrinsicRatio(nsSize* aRatio) {
-  nsresult rv = InnerImage()->GetIntrinsicRatio(aRatio);
-
-  if (mOrientation.SwapsWidthAndHeight()) {
-    swap(aRatio->width, aRatio->height);
+Maybe<AspectRatio> OrientedImage::GetIntrinsicRatio() {
+  Maybe<AspectRatio> ratio = InnerImage()->GetIntrinsicRatio();
+  if (ratio && mOrientation.SwapsWidthAndHeight()) {
+    ratio = Some(ratio->Inverted());
   }
-
-  return rv;
+  return ratio;
 }
 
 NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
 OrientedImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags) {
   nsresult rv;
 
   if (mOrientation.IsIdentity()) {
     return InnerImage()->GetFrame(aWhichFrame, aFlags);
--- a/image/OrientedImage.h
+++ b/image/OrientedImage.h
@@ -26,17 +26,17 @@ class OrientedImage : public ImageWrappe
 
  public:
   NS_INLINE_DECL_REFCOUNTING_INHERITED(OrientedImage, ImageWrapper)
 
   NS_IMETHOD GetWidth(int32_t* aWidth) override;
   NS_IMETHOD GetHeight(int32_t* aHeight) override;
   nsresult GetNativeSizes(nsTArray<gfx::IntSize>& aNativeSizes) const override;
   NS_IMETHOD GetIntrinsicSize(nsSize* aSize) override;
-  NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
+  Maybe<AspectRatio> GetIntrinsicRatio() override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
   GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
   NS_IMETHOD_(already_AddRefed<SourceSurface>)
   GetFrameAtSize(const gfx::IntSize& aSize, uint32_t aWhichFrame,
                  uint32_t aFlags) override;
   NS_IMETHOD_(bool)
   IsImageContainerAvailable(layers::LayerManager* aManager,
                             uint32_t aFlags) override;
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -249,24 +249,22 @@ RasterImage::GetIntrinsicSize(nsSize* aS
   }
 
   *aSize = nsSize(nsPresContext::CSSPixelsToAppUnits(mSize.width),
                   nsPresContext::CSSPixelsToAppUnits(mSize.height));
   return NS_OK;
 }
 
 //******************************************************************************
-NS_IMETHODIMP
-RasterImage::GetIntrinsicRatio(nsSize* aRatio) {
+Maybe<AspectRatio> RasterImage::GetIntrinsicRatio() {
   if (mError) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
-  *aRatio = nsSize(mSize.width, mSize.height);
-  return NS_OK;
+  return Some(AspectRatio::FromSize(mSize.width, mSize.height));
 }
 
 NS_IMETHODIMP_(Orientation)
 RasterImage::GetOrientation() { return mOrientation; }
 
 //******************************************************************************
 NS_IMETHODIMP
 RasterImage::GetType(uint16_t* aType) {
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -630,29 +630,27 @@ VectorImage::GetIntrinsicSize(nsSize* aS
   }
   if (rfSize.height) {
     aSize->height = *rfSize.height;
   }
   return NS_OK;
 }
 
 //******************************************************************************
-NS_IMETHODIMP
-VectorImage::GetIntrinsicRatio(nsSize* aRatio) {
+Maybe<AspectRatio> VectorImage::GetIntrinsicRatio() {
   if (mError || !mIsFullyLoaded) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
   nsIFrame* rootFrame = mSVGDocumentWrapper->GetRootLayoutFrame();
   if (!rootFrame) {
-    return NS_ERROR_FAILURE;
+    return Nothing();
   }
 
-  *aRatio = rootFrame->GetIntrinsicRatio();
-  return NS_OK;
+  return Some(rootFrame->GetIntrinsicRatio());
 }
 
 NS_IMETHODIMP_(Orientation)
 VectorImage::GetOrientation() { return Orientation(); }
 
 //******************************************************************************
 NS_IMETHODIMP
 VectorImage::GetType(uint16_t* aType) {
--- a/image/imgIContainer.idl
+++ b/image/imgIContainer.idl
@@ -9,16 +9,17 @@
 webidl Document;
 
 %{C++
 #include "ImgDrawResult.h"
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxRect.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/AspectRatio.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "nsRect.h"
 #include "nsSize.h"
 #include "limits.h"
 
 namespace mozilla {
 namespace layers {
@@ -40,16 +41,17 @@ namespace image {
 class ImageRegion;
 struct Orientation;
 
 }
 }
 
 %}
 
+native MaybeAspectRatio(mozilla::Maybe<mozilla::AspectRatio>);
 native ImgDrawResult(mozilla::image::ImgDrawResult);
 [ptr] native gfxContext(gfxContext);
 [ref] native gfxMatrix(gfxMatrix);
 [ref] native gfxRect(gfxRect);
 [ref] native gfxSize(gfxSize);
 native SamplingFilter(mozilla::gfx::SamplingFilter);
 [ref] native nsIntRect(nsIntRect);
 native nsIntRectByVal(nsIntRect);
@@ -94,19 +96,19 @@ interface imgIContainer : nsISupports
    * The intrinsic size of this image in appunits. If the image has no intrinsic
    * size in a dimension, -1 will be returned for that dimension. In the case of
    * any error, an exception will be thrown.
    */
   [noscript] readonly attribute nsSize intrinsicSize;
 
   /**
    * The (dimensionless) intrinsic ratio of this image. In the case of any
-   * error, an exception will be thrown.
+   * error, Nothing() will be returned.
    */
-  [noscript] readonly attribute nsSize intrinsicRatio;
+  [notxpcom, nostdcall] readonly attribute MaybeAspectRatio intrinsicRatio;
 
   /**
    * Given a size at which this image will be displayed, and the drawing
    * parameters affecting how it will be drawn, returns the image size which
    * should be used to draw to produce the highest quality result. This is the
    * appropriate size, for example, to use as an input to the pixel snapping
    * algorithm.
    *
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -4392,38 +4392,32 @@ nsRect nsLayoutUtils::GetTextShadowRects
 
     resultRect.UnionRect(resultRect, tmpRect);
   }
   return resultRect;
 }
 
 enum ObjectDimensionType { eWidth, eHeight };
 static nscoord ComputeMissingDimension(
-    const nsSize& aDefaultObjectSize, const nsSize& aIntrinsicRatio,
+    const nsSize& aDefaultObjectSize, const AspectRatio& aIntrinsicRatio,
     const Maybe<nscoord>& aSpecifiedWidth,
     const Maybe<nscoord>& aSpecifiedHeight,
     ObjectDimensionType aDimensionToCompute) {
   // The "default sizing algorithm" computes the missing dimension as follows:
   // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
 
   // 1. "If the object has an intrinsic aspect ratio, the missing dimension of
   //     the concrete object size is calculated using the intrinsic aspect
   //     ratio and the present dimension."
-  if (aIntrinsicRatio.width > 0 && aIntrinsicRatio.height > 0) {
+  if (aIntrinsicRatio) {
     // Fill in the missing dimension using the intrinsic aspect ratio.
-    nscoord knownDimensionSize;
-    float ratio;
     if (aDimensionToCompute == eWidth) {
-      knownDimensionSize = *aSpecifiedHeight;
-      ratio = aIntrinsicRatio.width / aIntrinsicRatio.height;
-    } else {
-      knownDimensionSize = *aSpecifiedWidth;
-      ratio = aIntrinsicRatio.height / aIntrinsicRatio.width;
-    }
-    return NSCoordSaturatingNonnegativeMultiply(knownDimensionSize, ratio);
+      return aIntrinsicRatio.ApplyTo(*aSpecifiedHeight);
+    }
+    return aIntrinsicRatio.Inverted().ApplyTo(*aSpecifiedWidth);
   }
 
   // 2. "Otherwise, if the missing dimension is present in the object's
   //     intrinsic dimensions, [...]"
   // NOTE: *Skipping* this case, because we already know it's not true -- we're
   // in this function because the missing dimension is *not* present in
   // the object's intrinsic dimensions.
 
@@ -4453,17 +4447,17 @@ static nscoord ComputeMissingDimension(
  * we run the default sizing algorithm using the object's intrinsic size in
  * place of the specified size. But if the object has neither an intrinsic
  * height nor an intrinsic width, then we instead return without populating our
  * outparam, and we let the caller figure out the size (using a contain
  * constraint).
  */
 static Maybe<nsSize> MaybeComputeObjectFitNoneSize(
     const nsSize& aDefaultObjectSize, const IntrinsicSize& aIntrinsicSize,
-    const nsSize& aIntrinsicRatio) {
+    const AspectRatio& aIntrinsicRatio) {
   // "If the object has an intrinsic height or width, its size is resolved as
   // if its intrinsic dimensions were given as the specified size."
   //
   // So, first we check if we have an intrinsic height and/or width:
   const Maybe<nscoord>& specifiedWidth = aIntrinsicSize.width;
   const Maybe<nscoord>& specifiedHeight = aIntrinsicSize.height;
 
   Maybe<nsSize> noneSize;  // (the value we'll return)
@@ -4491,23 +4485,22 @@ static Maybe<nsSize> MaybeComputeObjectF
   // computations; so, we return w/out populating noneSize.
   return noneSize;
 }
 
 // Computes the concrete object size to render into, as described at
 // http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
 static nsSize ComputeConcreteObjectSize(const nsSize& aConstraintSize,
                                         const IntrinsicSize& aIntrinsicSize,
-                                        const nsSize& aIntrinsicRatio,
+                                        const AspectRatio& aIntrinsicRatio,
                                         uint8_t aObjectFit) {
   // Handle default behavior (filling the container) w/ fast early return.
   // (Also: if there's no valid intrinsic ratio, then we have the "fill"
   // behavior & just use the constraint size.)
-  if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) ||
-      aIntrinsicRatio.width == 0 || aIntrinsicRatio.height == 0) {
+  if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) || !aIntrinsicRatio) {
     return aConstraintSize;
   }
 
   // The type of constraint to compute (cover/contain), if needed:
   Maybe<nsImageRenderer::FitType> fitType;
 
   Maybe<nsSize> noneSize;
   if (aObjectFit == NS_STYLE_OBJECT_FIT_NONE ||
@@ -4575,17 +4568,17 @@ static bool HasInitialObjectFitAndPositi
 
   return aStylePos->mObjectFit == NS_STYLE_OBJECT_FIT_FILL &&
          IsCoord50Pct(objectPos.horizontal) && IsCoord50Pct(objectPos.vertical);
 }
 
 /* static */
 nsRect nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
                                             const IntrinsicSize& aIntrinsicSize,
-                                            const nsSize& aIntrinsicRatio,
+                                            const AspectRatio& aIntrinsicRatio,
                                             const nsStylePosition* aStylePos,
                                             nsPoint* aAnchorPoint) {
   // Step 1: Figure out our "concrete object size"
   // (the size of the region we'll actually draw our image's pixels into).
   nsSize concreteObjectSize =
       ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
                                 aIntrinsicRatio, aStylePos->mObjectFit);
 
@@ -5434,59 +5427,60 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
     }
 
     // FIXME(emilio): Why the minBsize == 0 special-case? Also, shouldn't this
     // use BehavesLikeInitialValueOnBlockAxis instead?
     if (!styleBSize.IsAuto() ||
         !(styleMinBSize.IsAuto() || (styleMinBSize.ConvertsToLength() &&
                                      styleMinBSize.ToLength() == 0)) ||
         !styleMaxBSize.IsNone()) {
-      nsSize ratio(aFrame->GetIntrinsicRatio());
-      nscoord ratioISize = (horizontalAxis ? ratio.width : ratio.height);
-      nscoord ratioBSize = (horizontalAxis ? ratio.height : ratio.width);
-      if (ratioBSize != 0) {
+      if (AspectRatio ratio = aFrame->GetIntrinsicRatio()) {
+        // Convert 'ratio' if necessary, so that it's storing ISize/BSize:
+        if (!horizontalAxis) {
+          ratio = ratio.Inverted();
+        }
         AddStateBitToAncestors(
             aFrame, NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
 
         nscoord bSizeTakenByBoxSizing = GetDefiniteSizeTakenByBoxSizing(
             boxSizing, aFrame, !isInlineAxis, aFlags & IGNORE_PADDING,
             aPercentageBasis);
         // NOTE: This is only the minContentSize if we've been passed
         // MIN_INTRINSIC_ISIZE (which is fine, because this should only be used
         // inside a check for that flag).
         nscoord minContentSize = result;
         nscoord h;
         if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
                             &h) ||
             (aPercentageBasis.isNothing() &&
              GetPercentBSize(styleBSize, aFrame, horizontalAxis, h))) {
           h = std::max(0, h - bSizeTakenByBoxSizing);
-          result = NSCoordMulDiv(h, ratioISize, ratioBSize);
+          result = ratio.ApplyTo(h);
         }
 
         if (GetDefiniteSize(styleMaxBSize, aFrame, !isInlineAxis,
                             aPercentageBasis, &h) ||
             (aPercentageBasis.isNothing() &&
              GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) {
           h = std::max(0, h - bSizeTakenByBoxSizing);
-          nscoord maxISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
+          nscoord maxISize = ratio.ApplyTo(h);
           if (maxISize < result) {
             result = maxISize;
           }
           if (maxISize < minContentSize) {
             minContentSize = maxISize;
           }
         }
 
         if (GetDefiniteSize(styleMinBSize, aFrame, !isInlineAxis,
                             aPercentageBasis, &h) ||
             (aPercentageBasis.isNothing() &&
              GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) {
           h = std::max(0, h - bSizeTakenByBoxSizing);
-          nscoord minISize = NSCoordMulDiv(h, ratioISize, ratioBSize);
+          nscoord minISize = ratio.ApplyTo(h);
           if (minISize > result) {
             result = minISize;
           }
           if (minISize > minContentSize) {
             minContentSize = minISize;
           }
         }
         if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE)) {
@@ -6862,55 +6856,51 @@ ImgDrawResult nsLayoutUtils::DrawSingleI
   return DrawImageInternal(aContext, aPresContext, image, aSamplingFilter, dest,
                            fill, aAnchorPoint ? *aAnchorPoint : fill.TopLeft(),
                            aDirty, aSVGContext, aImageFlags);
 }
 
 /* static */
 void nsLayoutUtils::ComputeSizeForDrawing(
     imgIContainer* aImage, /* outparam */ CSSIntSize& aImageSize,
-    /* outparam */ nsSize& aIntrinsicRatio,
+    /* outparam */ AspectRatio& aIntrinsicRatio,
     /* outparam */ bool& aGotWidth,
     /* outparam */ bool& aGotHeight) {
   aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width));
   aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height));
-  bool gotRatio = NS_SUCCEEDED(aImage->GetIntrinsicRatio(&aIntrinsicRatio));
-
-  if (!(aGotWidth && aGotHeight) && !gotRatio) {
+  Maybe<AspectRatio> intrinsicRatio = aImage->GetIntrinsicRatio();
+  aIntrinsicRatio = intrinsicRatio.valueOr(AspectRatio());
+
+  if (!(aGotWidth && aGotHeight) && intrinsicRatio.isNothing()) {
     // We hit an error (say, because the image failed to load or couldn't be
     // decoded) and should return zero size.
     aGotWidth = aGotHeight = true;
     aImageSize = CSSIntSize(0, 0);
-    aIntrinsicRatio = nsSize(0, 0);
   }
 }
 
 /* static */
 CSSIntSize nsLayoutUtils::ComputeSizeForDrawingWithFallback(
     imgIContainer* aImage, const nsSize& aFallbackSize) {
   CSSIntSize imageSize;
-  nsSize imageRatio;
+  AspectRatio imageRatio;
   bool gotHeight, gotWidth;
   ComputeSizeForDrawing(aImage, imageSize, imageRatio, gotWidth, gotHeight);
 
   // If we didn't get both width and height, try to compute them using the
   // intrinsic ratio of the image.
   if (gotWidth != gotHeight) {
     if (!gotWidth) {
-      if (imageRatio.height != 0) {
-        imageSize.width = NSCoordSaturatingNonnegativeMultiply(
-            imageSize.height,
-            float(imageRatio.width) / float(imageRatio.height));
+      if (imageRatio) {
+        imageSize.width = imageRatio.ApplyTo(imageSize.height);
         gotWidth = true;
       }
     } else {
-      if (imageRatio.width != 0) {
-        imageSize.height = NSCoordSaturatingNonnegativeMultiply(
-            imageSize.width,
-            float(imageRatio.height) / float(imageRatio.width));
+      if (imageRatio) {
+        imageSize.height = imageRatio.Inverted().ApplyTo(imageSize.width);
         gotHeight = true;
       }
     }
   }
 
   // If we still don't have a width or height, just use the fallback size the
   // caller provided.
   if (!gotWidth) {
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -57,16 +57,17 @@ class nsView;
 class nsIFrame;
 class nsStyleCoord;
 class nsPIDOMWindowOuter;
 class imgIRequest;
 struct nsStyleFont;
 struct nsOverflowAreas;
 
 namespace mozilla {
+struct AspectRatio;
 class ComputedStyle;
 class PresShell;
 enum class PseudoStyleType : uint8_t;
 class EventListenerManager;
 enum class LayoutFrameType : uint8_t;
 struct IntrinsicSize;
 struct ContainerLayerParameters;
 class WritingMode;
@@ -141,16 +142,17 @@ enum class ReparentingDirection {
 };
 
 /**
  * nsLayoutUtils is a namespace class used for various helper
  * functions that are useful in multiple places in layout.  The goal
  * is not to define multiple copies of the same static helper.
  */
 class nsLayoutUtils {
+  typedef mozilla::AspectRatio AspectRatio;
   typedef mozilla::ComputedStyle ComputedStyle;
   typedef mozilla::LengthPercentage LengthPercentage;
   typedef mozilla::LengthPercentageOrAuto LengthPercentageOrAuto;
   typedef mozilla::dom::DOMRectList DOMRectList;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::StackingContextHelper StackingContextHelper;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
   typedef mozilla::IntrinsicSize IntrinsicSize;
@@ -1321,17 +1323,17 @@ class nsLayoutUtils {
    * @param aAnchorPoint [out] A point that should be pixel-aligned by functions
    *                           like nsLayoutUtils::DrawImage. See documentation
    *                           in nsCSSRendering.h for ComputeObjectAnchorPoint.
    * @return The nsRect into which we should render the replaced content (using
    *         the same coordinate space as the passed-in aConstraintRect).
    */
   static nsRect ComputeObjectDestRect(const nsRect& aConstraintRect,
                                       const IntrinsicSize& aIntrinsicSize,
-                                      const nsSize& aIntrinsicRatio,
+                                      const AspectRatio& aIntrinsicRatio,
                                       const nsStylePosition* aStylePos,
                                       nsPoint* aAnchorPoint = nullptr);
 
   /**
    * Get the font metrics corresponding to the frame's style data.
    * @param aFrame the frame
    * @param aSizeInflation number to multiply font size by
    */
@@ -1921,18 +1923,18 @@ class nsLayoutUtils {
    * dimension.
    *
    * NOTE: This method is similar to ComputeSizeWithIntrinsicDimensions.  The
    * difference is that this one is simpler and is suited to places where we
    * have less information about the frame tree.
    */
   static void ComputeSizeForDrawing(imgIContainer* aImage,
                                     CSSIntSize& aImageSize,
-                                    nsSize& aIntrinsicRatio, bool& aGotWidth,
-                                    bool& aGotHeight);
+                                    AspectRatio& aIntrinsicRatio,
+                                    bool& aGotWidth, bool& aGotHeight);
 
   /**
    * Given an imgIContainer, this method attempts to obtain an intrinsic
    * px-valued height & width for it. If the imgIContainer has a non-pixel
    * value for either height or width, this method tries to generate a pixel
    * value for that dimension using the intrinsic ratio (if available). If,
    * after trying all these methods, no value is available for one or both
    * dimensions, the corresponding dimension of aFallbackSize is used instead.
new file mode 100644
--- /dev/null
+++ b/layout/generic/AspectRatio.h
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+#ifndef mozilla_AspectRatio_h
+#define mozilla_AspectRatio_h
+
+/* The aspect ratio of a box, in a "width / height" format. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsCoord.h"
+
+namespace mozilla {
+
+struct AspectRatio {
+  AspectRatio() : mRatio(0.0f) {}
+  explicit AspectRatio(float aRatio) : mRatio(std::max(aRatio, 0.0f)) {}
+
+  static AspectRatio FromSize(float aWidth, float aHeight) {
+    if (aWidth == 0.0f || aHeight == 0.0f) {
+      return AspectRatio();
+    }
+    return AspectRatio(aWidth / aHeight);
+  }
+
+  explicit operator bool() const { return mRatio != 0.0f; }
+
+  nscoord ApplyTo(nscoord aCoord) const {
+    MOZ_DIAGNOSTIC_ASSERT(*this);
+    return NSCoordSaturatingNonnegativeMultiply(aCoord, mRatio);
+  }
+
+  // Inverts the ratio, in order to get the height / width ratio.
+  MOZ_MUST_USE AspectRatio Inverted() const {
+    return *this ? AspectRatio(1.0f / mRatio) : *this;
+  }
+
+  bool operator==(const AspectRatio& aOther) const {
+    return mRatio == aOther.mRatio;
+  }
+
+  bool operator!=(const AspectRatio& aOther) const {
+    return !(*this == aOther);
+  }
+
+  bool operator<(const AspectRatio& aOther) const {
+    return mRatio < aOther.mRatio;
+  }
+
+ private:
+  // 0.0f represents no aspect ratio.
+  float mRatio;
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_AspectRatio_h
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -128,16 +128,17 @@ EXPORTS += [
     'ScrollAnimationPhysics.h',
     'ScrollbarActivity.h',
     'ScrollSnap.h',
     'TextDrawTarget.h',
     'Visibility.h',
 ]
 
 EXPORTS.mozilla += [
+    'AspectRatio.h',
     'AutoCopyListener.h',
     'CSSAlignUtils.h',
     'CSSOrderAwareFrameIterator.h',
     'FrameTypeList.h',
     'ReflowInput.h',
     'ReflowOutput.h',
     'ViewportFrame.h',
     'WritingModes.h',
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -387,31 +387,29 @@ class MOZ_STACK_CLASS nsFlexContainerFra
                           : LogicalSize(mWM, aCrossSize, aMainSize);
   }
 
   // Are my axes reversed with respect to what the author asked for?
   // (We may reverse the axes in the FlexboxAxisTracker constructor and set
   // this flag, to avoid reflowing our children in bottom-to-top order.)
   bool AreAxesInternallyReversed() const { return mAreAxesInternallyReversed; }
 
+  bool IsMainAxisHorizontal() const {
+    // If we're row-oriented, and our writing mode is NOT vertical,
+    // or we're column-oriented and our writing mode IS vertical,
+    // then our main axis is horizontal. This handles all cases:
+    return mIsRowOriented != mWM.IsVertical();
+  }
+
  private:
   // Delete copy-constructor & reassignment operator, to prevent accidental
   // (unnecessary) copying.
   FlexboxAxisTracker(const FlexboxAxisTracker&) = delete;
   FlexboxAxisTracker& operator=(const FlexboxAxisTracker&) = delete;
 
-  // Private because callers shouldn't need to care about physical axes
-  // (but we do internally, to provide one API).
-  bool IsMainAxisHorizontal() const {
-    // If we're row-oriented, and our writing mode is NOT vertical,
-    // or we're column-oriented and our writing mode IS vertical,
-    // then our main axis is horizontal. This handles all cases:
-    return mIsRowOriented != mWM.IsVertical();
-  }
-
   // Helpers for constructor which determine the orientation of our axes, based
   // on legacy box properties (-webkit-box-orient, -webkit-box-direction) or
   // modern flexbox properties (flex-direction, flex-wrap) depending on whether
   // the flex container is a "legacy box" (as determined by IsLegacyBox).
   void InitAxesFromLegacyProps(const nsFlexContainerFrame* aFlexContainer);
   void InitAxesFromModernProps(const nsFlexContainerFrame* aFlexContainer);
 
   // XXXdholbert [BEGIN DEPRECATED]
@@ -620,20 +618,18 @@ class nsFlexContainerFrame::FlexItem : p
       // regardless of mFlexShrink, we should just return 0.
       // (This is really a special-case for when mFlexShrink is infinity, to
       // avoid performing mFlexShrink * mFlexBaseSize = inf * 0 = undefined.)
       return 0.0f;
     }
     return mFlexShrink * mFlexBaseSize;
   }
 
-  // Returns a LogicalSize representing the flex item's logical intrinsic ratio
-  // (ISize:BSize), as expressed in the *flex container's* writing mode.
-  const LogicalSize& IntrinsicRatio() const { return mIntrinsicRatio; }
-  bool HasIntrinsicRatio() const { return !mIntrinsicRatio.IsAllZero(); }
+  const AspectRatio& IntrinsicRatio() const { return mIntrinsicRatio; }
+  bool HasIntrinsicRatio() const { return !!mIntrinsicRatio; }
 
   // Getters for margin:
   // ===================
   const nsMargin& GetMargin() const { return mMargin; }
 
   // Returns the margin component for a given mozilla::Side
   nscoord GetMarginComponentForSide(mozilla::Side aSide) const {
     return mMargin.Side(aSide);
@@ -841,17 +837,17 @@ class nsFlexContainerFrame::FlexItem : p
   // Helper called by the constructor, to set mNeedsMinSizeAutoResolution:
   void CheckForMinSizeAuto(const ReflowInput& aFlexItemReflowInput,
                            const FlexboxAxisTracker& aAxisTracker);
 
   // Values that we already know in constructor (and are hence mostly 'const'):
   nsIFrame* const mFrame;  // The flex item's frame.
   const float mFlexGrow;
   const float mFlexShrink;
-  const LogicalSize mIntrinsicRatio;
+  const AspectRatio mIntrinsicRatio;
   const nsMargin mBorderPadding;
   nsMargin mMargin;  // non-const because we need to resolve auto margins
 
   // These are non-const so that we can lazily update them with the item's
   // intrinsic size (obtained via a "measuring" reflow), when necessary.
   // (e.g. for "flex-basis:auto;height:auto" & "min-height:auto")
   nscoord mFlexBaseSize;
   nscoord mMainMinSize;
@@ -1447,26 +1443,27 @@ static nscoord CrossSizeToUseWithRatio(c
   }
 
   // Indefinite cross-size.
   return NS_AUTOHEIGHT;
 }
 
 // Convenience function; returns a main-size, given a cross-size and an
 // intrinsic ratio. The caller is responsible for ensuring that the passed-in
-// intrinsic ratio must not have 0 in its cross-axis component (or else we'll
-// divide by 0).
+// intrinsic ratio is not zero.
 static nscoord MainSizeFromAspectRatio(nscoord aCrossSize,
-                                       const LogicalSize& aIntrinsicRatio,
+                                       const AspectRatio& aIntrinsicRatio,
                                        const FlexboxAxisTracker& aAxisTracker) {
-  MOZ_ASSERT(aAxisTracker.GetCrossComponent(aIntrinsicRatio) != 0,
+  MOZ_ASSERT(aIntrinsicRatio,
              "Invalid ratio; will divide by 0! Caller should've checked...");
-  return NSCoordMulDiv(aCrossSize,
-                       aAxisTracker.GetMainComponent(aIntrinsicRatio),
-                       aAxisTracker.GetCrossComponent(aIntrinsicRatio));
+  AspectRatio ratio = aAxisTracker.IsMainAxisHorizontal()
+                          ? aIntrinsicRatio
+                          : aIntrinsicRatio.Inverted();
+
+  return ratio.ApplyTo(aCrossSize);
 }
 
 // Partially resolves "min-[width|height]:auto" and returns the resulting value.
 // By "partially", I mean we don't consider the min-content size (but we do
 // consider flex-basis, main max-size, and the intrinsic aspect ratio).
 // The caller is responsible for computing & considering the min-content size
 // in combination with the partially-resolved value that this function returns.
 //
@@ -1501,17 +1498,17 @@ static nscoord PartiallyResolveAutoMinSi
   }
 
   // * if the item has no intrinsic aspect ratio, its min-content size:
   //  --- SKIPPING THIS IN THIS FUNCTION --- caller's responsibility.
 
   // * if the item has an intrinsic aspect ratio, the width (height) calculated
   //   from the aspect ratio and any definite size constraints in the opposite
   //   dimension.
-  if (aAxisTracker.GetCrossComponent(aFlexItem.IntrinsicRatio()) != 0) {
+  if (aFlexItem.IntrinsicRatio()) {
     // We have a usable aspect ratio. (not going to divide by 0)
     const bool useMinSizeIfCrossSizeIsIndefinite = true;
     nscoord crossSizeToUseWithRatio = CrossSizeToUseWithRatio(
         aFlexItem, aItemReflowInput, useMinSizeIfCrossSizeIsIndefinite,
         aAxisTracker);
     nscoord minMainSizeFromRatio = MainSizeFromAspectRatio(
         crossSizeToUseWithRatio, aFlexItem.IntrinsicRatio(), aAxisTracker);
     minMainSize = std::min(minMainSize, minMainSizeFromRatio);
@@ -1531,17 +1528,17 @@ static bool ResolveAutoFlexBasisFromRati
              "Should only be called to resolve an 'auto' flex-basis");
   // If the flex item has ...
   //  - an intrinsic aspect ratio,
   //  - a [used] flex-basis of 'main-size' [auto?]
   //    [We have this, if we're here.]
   //  - a definite cross size
   // then the flex base size is calculated from its inner cross size and the
   // flex item’s intrinsic aspect ratio.
-  if (aAxisTracker.GetCrossComponent(aFlexItem.IntrinsicRatio()) != 0) {
+  if (aFlexItem.IntrinsicRatio()) {
     // We have a usable aspect ratio. (not going to divide by 0)
     const bool useMinSizeIfCrossSizeIsIndefinite = false;
     nscoord crossSizeToUseWithRatio = CrossSizeToUseWithRatio(
         aFlexItem, aItemReflowInput, useMinSizeIfCrossSizeIsIndefinite,
         aAxisTracker);
     if (crossSizeToUseWithRatio != NS_AUTOHEIGHT) {
       // We have a definite cross-size
       nscoord mainSizeFromRatio = MainSizeFromAspectRatio(
@@ -1606,18 +1603,17 @@ void nsFlexContainerFrame::ResolveAutoFl
 
   nscoord resolvedMinSize;  // (only set/used if isMainMinSizeAuto==true)
   bool minSizeNeedsToMeasureContent = false;  // assume the best
   if (isMainMinSizeAuto) {
     // Resolve the min-size, except for considering the min-content size.
     // (We'll consider that later, if we need to.)
     resolvedMinSize =
         PartiallyResolveAutoMinSize(aFlexItem, aItemReflowInput, aAxisTracker);
-    if (resolvedMinSize > 0 &&
-        aAxisTracker.GetCrossComponent(aFlexItem.IntrinsicRatio()) == 0) {
+    if (resolvedMinSize > 0 && !aFlexItem.IntrinsicRatio()) {
       // We don't have a usable aspect ratio, so we need to consider our
       // min-content size as another candidate min-size, which we'll have to
       // min() with the current resolvedMinSize.
       // (If resolvedMinSize were already at 0, we could skip this measurement
       // because it can't go any lower. But it's not 0, so we need it.)
       minSizeNeedsToMeasureContent = true;
     }
   }
@@ -1867,19 +1863,17 @@ FlexItem::FlexItem(ReflowInput& aFlexIte
                    float aFlexShrink, nscoord aFlexBaseSize,
                    nscoord aMainMinSize, nscoord aMainMaxSize,
                    nscoord aTentativeCrossSize, nscoord aCrossMinSize,
                    nscoord aCrossMaxSize,
                    const FlexboxAxisTracker& aAxisTracker)
     : mFrame(aFlexItemReflowInput.mFrame),
       mFlexGrow(aFlexGrow),
       mFlexShrink(aFlexShrink),
-      // We store the intrinsic ratio in the *flex container's* WM:
-      mIntrinsicRatio(aAxisTracker.GetWritingMode(),
-                      mFrame->GetIntrinsicRatio()),
+      mIntrinsicRatio(mFrame->GetIntrinsicRatio()),
       mBorderPadding(aFlexItemReflowInput.ComputedPhysicalBorderPadding()),
       mMargin(aFlexItemReflowInput.ComputedPhysicalMargin()),
       mMainMinSize(aMainMinSize),
       mMainMaxSize(aMainMaxSize),
       mCrossMinSize(aCrossMinSize),
       mCrossMaxSize(aCrossMaxSize),
       mMainPosn(0),
       mCrossSize(aTentativeCrossSize),
@@ -1971,17 +1965,16 @@ FlexItem::FlexItem(ReflowInput& aFlexIte
 // Simplified constructor for creating a special "strut" FlexItem, for a child
 // with visibility:collapse. The strut has 0 main-size, and it only exists to
 // impose a minimum cross size on whichever FlexLine it ends up in.
 FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize,
                    WritingMode aContainerWM)
     : mFrame(aChildFrame),
       mFlexGrow(0.0f),
       mFlexShrink(0.0f),
-      mIntrinsicRatio(aContainerWM),
       // mBorderPadding uses default constructor,
       // mMargin uses default constructor,
       mFlexBaseSize(0),
       mMainMinSize(0),
       mMainMaxSize(0),
       mCrossMinSize(0),
       mCrossMaxSize(0),
       mMainSize(0),
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5516,27 +5516,27 @@ nsIFrame::IntrinsicISizeOffsetData nsIFr
 }
 
 /* virtual */
 IntrinsicSize nsFrame::GetIntrinsicSize() {
   return IntrinsicSize();  // default is width/height set to eStyleUnit_None
 }
 
 /* virtual */
-nsSize nsFrame::GetIntrinsicRatio() { return nsSize(0, 0); }
+AspectRatio nsFrame::GetIntrinsicRatio() { return AspectRatio(); }
 
 /* virtual */
 LogicalSize nsFrame::ComputeSize(gfxContext* aRenderingContext, WritingMode aWM,
                                  const LogicalSize& aCBSize,
                                  nscoord aAvailableISize,
                                  const LogicalSize& aMargin,
                                  const LogicalSize& aBorder,
                                  const LogicalSize& aPadding,
                                  ComputeSizeFlags aFlags) {
-  MOZ_ASSERT(GetIntrinsicRatio() == nsSize(0, 0),
+  MOZ_ASSERT(!GetIntrinsicRatio(),
              "Please override this method and call "
              "nsFrame::ComputeSizeWithIntrinsicDimensions instead.");
   LogicalSize result =
       ComputeAutoSize(aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
                       aBorder, aPadding, aFlags);
   const nsStylePosition* stylePos = StylePosition();
 
   LogicalSize boxSizingAdjust(aWM);
@@ -5778,20 +5778,22 @@ LogicalSize nsFrame::ComputeSize(gfxCont
   result.ISize(aWM) = std::max(0, result.ISize(aWM));
   result.BSize(aWM) = std::max(0, result.BSize(aWM));
 
   return result;
 }
 
 LogicalSize nsFrame::ComputeSizeWithIntrinsicDimensions(
     gfxContext* aRenderingContext, WritingMode aWM,
-    const IntrinsicSize& aIntrinsicSize, nsSize aIntrinsicRatio,
+    const IntrinsicSize& aIntrinsicSize, const AspectRatio& aIntrinsicRatio,
     const LogicalSize& aCBSize, const LogicalSize& aMargin,
     const LogicalSize& aBorder, const LogicalSize& aPadding,
     ComputeSizeFlags aFlags) {
+  auto logicalRatio =
+      aWM.IsVertical() ? aIntrinsicRatio.Inverted() : aIntrinsicRatio;
   const nsStylePosition* stylePos = StylePosition();
   const auto* inlineStyleCoord = &stylePos->ISize(aWM);
   const auto* blockStyleCoord = &stylePos->BSize(aWM);
   auto* parentFrame = GetParent();
   const bool isGridItem = parentFrame && parentFrame->IsGridContainerFrame() &&
                           !HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
   const bool isFlexItem =
       parentFrame && parentFrame->IsFlexContainerFrame() &&
@@ -5910,20 +5912,16 @@ LogicalSize nsFrame::ComputeSizeWithIntr
   const bool hasIntrinsicISize = isizeCoord.isSome();
   nscoord intrinsicISize = std::max(0, isizeCoord.valueOr(0));
 
   const auto& bsizeCoord =
       isVertical ? aIntrinsicSize.width : aIntrinsicSize.height;
   const bool hasIntrinsicBSize = bsizeCoord.isSome();
   nscoord intrinsicBSize = std::max(0, bsizeCoord.valueOr(0));
 
-  NS_ASSERTION(aIntrinsicRatio.width >= 0 && aIntrinsicRatio.height >= 0,
-               "Intrinsic ratio has a negative component!");
-  LogicalSize logicalRatio(aWM, aIntrinsicRatio);
-
   if (!isAutoISize) {
     iSize = ComputeISizeValue(
         aRenderingContext, aCBSize.ISize(aWM), boxSizingAdjust.ISize(aWM),
         boxSizingToMarginEdgeISize, *inlineStyleCoord, aFlags);
   } else if (MOZ_UNLIKELY(isGridItem)) {
     MOZ_ASSERT(!IS_TRUE_OVERFLOW_CONTAINER(this));
     // 'auto' inline-size for grid-level box - apply 'stretch' as needed:
     auto cbSize = aCBSize.ISize(aWM);
@@ -5932,17 +5930,17 @@ LogicalSize nsFrame::ComputeSizeWithIntr
         auto inlineAxisAlignment =
             aWM.IsOrthogonalTo(GetParent()->GetWritingMode())
                 ? stylePos->UsedAlignSelf(GetParent()->Style())
                 : stylePos->UsedJustifySelf(GetParent()->Style());
         // Note: 'normal' means 'start' for elements with an intrinsic size
         // or ratio in the relevant dimension, otherwise 'stretch'.
         // https://drafts.csswg.org/css-grid/#grid-item-sizing
         if ((inlineAxisAlignment == NS_STYLE_ALIGN_NORMAL &&
-             !hasIntrinsicISize && !(logicalRatio.ISize(aWM) > 0)) ||
+             !hasIntrinsicISize && !logicalRatio) ||
             inlineAxisAlignment == NS_STYLE_ALIGN_STRETCH) {
           stretchI = eStretch;
         }
       }
       if (stretchI != eNoStretch ||
           (aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize)) {
         iSize =
             std::max(nscoord(0), cbSize - aPadding.ISize(aWM) -
@@ -5999,17 +5997,17 @@ LogicalSize nsFrame::ComputeSizeWithIntr
         auto blockAxisAlignment =
             !aWM.IsOrthogonalTo(GetParent()->GetWritingMode())
                 ? stylePos->UsedAlignSelf(GetParent()->Style())
                 : stylePos->UsedJustifySelf(GetParent()->Style());
         // Note: 'normal' means 'start' for elements with an intrinsic size
         // or ratio in the relevant dimension, otherwise 'stretch'.
         // https://drafts.csswg.org/css-grid/#grid-item-sizing
         if ((blockAxisAlignment == NS_STYLE_ALIGN_NORMAL &&
-             !hasIntrinsicBSize && !(logicalRatio.BSize(aWM) > 0)) ||
+             !hasIntrinsicBSize && !logicalRatio) ||
             blockAxisAlignment == NS_STYLE_ALIGN_STRETCH) {
           stretchB = eStretch;
         }
       }
       if (stretchB != eNoStretch ||
           (aFlags & ComputeSizeFlags::eBClampMarginBoxMinSize)) {
         bSize =
             std::max(nscoord(0), cbSize - aPadding.BSize(aWM) -
@@ -6054,20 +6052,19 @@ LogicalSize nsFrame::ComputeSizeWithIntr
       // 'auto' iSize, 'auto' bSize
 
       // Get tentative values - CSS 2.1 sections 10.3.2 and 10.6.2:
 
       nscoord tentISize, tentBSize;
 
       if (hasIntrinsicISize) {
         tentISize = intrinsicISize;
-      } else if (hasIntrinsicBSize && logicalRatio.BSize(aWM) > 0) {
-        tentISize = NSCoordMulDiv(intrinsicBSize, logicalRatio.ISize(aWM),
-                                  logicalRatio.BSize(aWM));
-      } else if (logicalRatio.ISize(aWM) > 0) {
+      } else if (hasIntrinsicBSize && logicalRatio) {
+        tentISize = logicalRatio.ApplyTo(intrinsicBSize);
+      } else if (logicalRatio) {
         tentISize =
             aCBSize.ISize(aWM) - boxSizingToMarginEdgeISize;  // XXX scrollbar?
         if (tentISize < 0) tentISize = 0;
       } else {
         tentISize = nsPresContext::CSSPixelsToAppUnits(300);
       }
 
       // If we need to clamp the inline size to fit the CB, we use the 'stretch'
@@ -6075,77 +6072,62 @@ LogicalSize nsFrame::ComputeSizeWithIntr
       // unless we have 'stretch' in the other axis.
       if ((aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize) &&
           stretchI != eStretch && tentISize > iSize) {
         stretchI = (stretchB == eStretch ? eStretch : eStretchPreservingRatio);
       }
 
       if (hasIntrinsicBSize) {
         tentBSize = intrinsicBSize;
-      } else if (logicalRatio.ISize(aWM) > 0) {
-        tentBSize = NSCoordMulDiv(tentISize, logicalRatio.BSize(aWM),
-                                  logicalRatio.ISize(aWM));
+      } else if (logicalRatio) {
+        tentBSize = logicalRatio.Inverted().ApplyTo(tentISize);
       } else {
         tentBSize = nsPresContext::CSSPixelsToAppUnits(150);
       }
 
       // (ditto the comment about clamping the inline size above)
       if ((aFlags & ComputeSizeFlags::eBClampMarginBoxMinSize) &&
           stretchB != eStretch && tentBSize > bSize) {
         stretchB = (stretchI == eStretch ? eStretch : eStretchPreservingRatio);
       }
 
-      if (aIntrinsicRatio != nsSize(0, 0)) {
+      if (logicalRatio) {
         if (stretchI == eStretch) {
           tentISize = iSize;  // * / 'stretch'
           if (stretchB == eStretch) {
             tentBSize = bSize;  // 'stretch' / 'stretch'
-          } else if (stretchB == eStretchPreservingRatio &&
-                     logicalRatio.ISize(aWM) > 0) {
+          } else if (stretchB == eStretchPreservingRatio) {
             // 'normal' / 'stretch'
-            tentBSize = NSCoordMulDiv(iSize, logicalRatio.BSize(aWM),
-                                      logicalRatio.ISize(aWM));
+            tentBSize = logicalRatio.Inverted().ApplyTo(iSize);
           }
         } else if (stretchB == eStretch) {
           tentBSize = bSize;  // 'stretch' / * (except 'stretch')
-          if (stretchI == eStretchPreservingRatio &&
-              logicalRatio.BSize(aWM) > 0) {
+          if (stretchI == eStretchPreservingRatio) {
             // 'stretch' / 'normal'
-            tentISize = NSCoordMulDiv(bSize, logicalRatio.ISize(aWM),
-                                      logicalRatio.BSize(aWM));
+            tentISize = logicalRatio.ApplyTo(bSize);
           }
         } else if (stretchI == eStretchPreservingRatio) {
           tentISize = iSize;  // * (except 'stretch') / 'normal'
-          if (logicalRatio.ISize(aWM) > 0) {
-            tentBSize = NSCoordMulDiv(iSize, logicalRatio.BSize(aWM),
-                                      logicalRatio.ISize(aWM));
-          }
+          tentBSize = logicalRatio.Inverted().ApplyTo(iSize);
           if (stretchB == eStretchPreservingRatio && tentBSize > bSize) {
             // Stretch within the CB size with preserved intrinsic ratio.
             tentBSize = bSize;  // 'normal' / 'normal'
-            if (logicalRatio.BSize(aWM) > 0) {
-              tentISize = NSCoordMulDiv(bSize, logicalRatio.ISize(aWM),
-                                        logicalRatio.BSize(aWM));
-            }
+            tentISize = logicalRatio.ApplyTo(bSize);
           }
         } else if (stretchB == eStretchPreservingRatio) {
           tentBSize = bSize;  // 'normal' / * (except 'normal' and 'stretch')
-          if (logicalRatio.BSize(aWM) > 0) {
-            tentISize = NSCoordMulDiv(bSize, logicalRatio.ISize(aWM),
-                                      logicalRatio.BSize(aWM));
-          }
+          tentISize = logicalRatio.ApplyTo(bSize);
         }
       }
 
       // ComputeAutoSizeWithIntrinsicDimensions preserves the ratio when
       // applying the min/max-size.  We don't want that when we have 'stretch'
       // in either axis because tentISize/tentBSize is likely not according to
       // ratio now.
-      if (aIntrinsicRatio != nsSize(0, 0) && stretchI != eStretch &&
-          stretchB != eStretch) {
+      if (logicalRatio && stretchI != eStretch && stretchB != eStretch) {
         nsSize autoSize = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(
             minISize, minBSize, maxISize, maxBSize, tentISize, tentBSize);
         // The nsSize that ComputeAutoSizeWithIntrinsicDimensions returns will
         // actually contain logical values if the parameters passed to it were
         // logical coordinates, so we do NOT perform a physical-to-logical
         // conversion here, but just assign the fields directly to our result.
         iSize = autoSize.width;
         bSize = autoSize.height;
@@ -6153,38 +6135,36 @@ LogicalSize nsFrame::ComputeSizeWithIntr
         // Not honoring an intrinsic ratio: clamp the dimensions independently.
         iSize = NS_CSS_MINMAX(tentISize, minISize, maxISize);
         bSize = NS_CSS_MINMAX(tentBSize, minBSize, maxBSize);
       }
     } else {
       // 'auto' iSize, non-'auto' bSize
       bSize = NS_CSS_MINMAX(bSize, minBSize, maxBSize);
       if (stretchI != eStretch) {
-        if (logicalRatio.BSize(aWM) > 0) {
-          iSize = NSCoordMulDiv(bSize, logicalRatio.ISize(aWM),
-                                logicalRatio.BSize(aWM));
+        if (logicalRatio) {
+          iSize = logicalRatio.ApplyTo(bSize);
         } else if (hasIntrinsicISize) {
           if (!((aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize) &&
                 intrinsicISize > iSize)) {
             iSize = intrinsicISize;
           }  // else - leave iSize as is to fill the CB
         } else {
           iSize = nsPresContext::CSSPixelsToAppUnits(300);
         }
       }  // else - leave iSize as is to fill the CB
       iSize = NS_CSS_MINMAX(iSize, minISize, maxISize);
     }
   } else {
     if (isAutoBSize) {
       // non-'auto' iSize, 'auto' bSize
       iSize = NS_CSS_MINMAX(iSize, minISize, maxISize);
       if (stretchB != eStretch) {
-        if (logicalRatio.ISize(aWM) > 0) {
-          bSize = NSCoordMulDiv(iSize, logicalRatio.BSize(aWM),
-                                logicalRatio.ISize(aWM));
+        if (logicalRatio) {
+          bSize = logicalRatio.Inverted().ApplyTo(iSize);
         } else if (hasIntrinsicBSize) {
           if (!((aFlags & ComputeSizeFlags::eBClampMarginBoxMinSize) &&
                 intrinsicBSize > bSize)) {
             bSize = intrinsicBSize;
           }  // else - leave bSize as is to fill the CB
         } else {
           bSize = nsPresContext::CSSPixelsToAppUnits(150);
         }
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -268,31 +268,32 @@ class nsFrame : public nsBox {
   nscoord GetPrefISize(gfxContext* aRenderingContext) override;
   void AddInlineMinISize(gfxContext* aRenderingContext,
                          InlineMinISizeData* aData) override;
   void AddInlinePrefISize(gfxContext* aRenderingContext,
                           InlinePrefISizeData* aData) override;
   IntrinsicISizeOffsetData IntrinsicISizeOffsets(
       nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE) override;
   mozilla::IntrinsicSize GetIntrinsicSize() override;
-  nsSize GetIntrinsicRatio() override;
+  mozilla::AspectRatio GetIntrinsicRatio() override;
 
   mozilla::LogicalSize ComputeSize(
       gfxContext* aRenderingContext, mozilla::WritingMode aWM,
       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
       const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
       const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
 
   /**
    * Calculate the used values for 'width' and 'height' for a replaced element.
    *   http://www.w3.org/TR/CSS21/visudet.html#min-max-widths
    */
   mozilla::LogicalSize ComputeSizeWithIntrinsicDimensions(
       gfxContext* aRenderingContext, mozilla::WritingMode aWM,
-      const mozilla::IntrinsicSize& aIntrinsicSize, nsSize aIntrinsicRatio,
+      const mozilla::IntrinsicSize& aIntrinsicSize,
+      const mozilla::AspectRatio& aIntrinsicRatio,
       const mozilla::LogicalSize& aCBSize, const mozilla::LogicalSize& aMargin,
       const mozilla::LogicalSize& aBorder, const mozilla::LogicalSize& aPadding,
       ComputeSizeFlags aFlags);
 
   // Compute tight bounds assuming this frame honours its border, background
   // and outline, its children's tight bounds, and nothing else.
   nsRect ComputeSimpleTightBounds(mozilla::gfx::DrawTarget* aDrawTarget) const;
 
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -47,19 +47,19 @@ static IntrinsicSize IntrinsicSizeFromCa
 /* Helper for our nsIFrame::GetIntrinsicRatio() impl. Takes the result of
  * "GetCanvasSize()" as a parameter, which may help avoid redundant
  * indirect calls to GetCanvasSize().
  *
  * @param aCanvasSizeInPx The canvas's size in CSS pixels, as returned
  *                        by GetCanvasSize().
  * @return The canvas's intrinsic ratio, as a nsSize.
  */
-static nsSize IntrinsicRatioFromCanvasSize(const nsIntSize& aCanvasSizeInPx) {
-  return nsSize(nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.width),
-                nsPresContext::CSSPixelsToAppUnits(aCanvasSizeInPx.height));
+static AspectRatio IntrinsicRatioFromCanvasSize(
+    const nsIntSize& aCanvasSizeInPx) {
+  return AspectRatio::FromSize(aCanvasSizeInPx.width, aCanvasSizeInPx.height);
 }
 
 class nsDisplayCanvas final : public nsDisplayItem {
  public:
   nsDisplayCanvas(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
       : nsDisplayItem(aBuilder, aFrame) {
     MOZ_COUNT_CTOR(nsDisplayCanvas);
   }
@@ -81,17 +81,17 @@ class nsDisplayCanvas final : public nsD
       // object-fit/object-position CSS properties), clipped to the container's
       // content box (which is what GetBounds() returns). So, we grab those
       // rects and intersect them.
       nsRect constraintRect = GetBounds(aBuilder, aSnap);
 
       // Need intrinsic size & ratio, for ComputeObjectDestRect:
       nsIntSize canvasSize = f->GetCanvasSize();
       IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSize);
-      nsSize intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSize);
+      AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSize);
 
       const nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
           constraintRect, intrinsicSize, intrinsicRatio, f->StylePosition());
       return nsRegion(destRect.Intersect(constraintRect));
     }
     return result;
   }
 
@@ -140,17 +140,18 @@ class nsDisplayCanvas final : public nsD
         data->UpdateCompositableClient(aBuilder.GetRenderRoot());
 
         // Push IFrame for async image pipeline.
         // XXX Remove this once partial display list update is supported.
 
         nsIntSize canvasSizeInPx = data->GetSize();
         IntrinsicSize intrinsicSize =
             IntrinsicSizeFromCanvasSize(canvasSizeInPx);
-        nsSize intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+        AspectRatio intrinsicRatio =
+            IntrinsicRatioFromCanvasSize(canvasSizeInPx);
 
         nsRect area =
             mFrame->GetContentRectRelativeToSelf() + ToReferenceFrame();
         nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
             area, intrinsicSize, intrinsicRatio, mFrame->StylePosition());
 
         LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
             dest, mFrame->PresContext()->AppUnitsPerDevPixel());
@@ -293,34 +294,34 @@ nscoord nsHTMLCanvasFrame::GetPrefISize(
 IntrinsicSize nsHTMLCanvasFrame::GetIntrinsicSize() {
   if (StyleDisplay()->IsContainSize()) {
     return IntrinsicSize(0, 0);
   }
   return IntrinsicSizeFromCanvasSize(GetCanvasSize());
 }
 
 /* virtual */
-nsSize nsHTMLCanvasFrame::GetIntrinsicRatio() {
+AspectRatio nsHTMLCanvasFrame::GetIntrinsicRatio() {
   if (StyleDisplay()->IsContainSize()) {
-    return nsSize(0, 0);
+    return AspectRatio();
   }
   return IntrinsicRatioFromCanvasSize(GetCanvasSize());
 }
 
 /* virtual */
 LogicalSize nsHTMLCanvasFrame::ComputeSize(
     gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
     nscoord aAvailableISize, const LogicalSize& aMargin,
     const LogicalSize& aBorder, const LogicalSize& aPadding,
     ComputeSizeFlags aFlags) {
   IntrinsicSize intrinsicSize;
-  nsSize intrinsicRatio;
+  AspectRatio intrinsicRatio;
   if (StyleDisplay()->IsContainSize()) {
     intrinsicSize = IntrinsicSize(0, 0);
-    // intrinsicRatio is already implicitly 0,0 via default ctor.
+    // intrinsicRatio is already implicitly zero via default ctor.
   } else {
     nsIntSize canvasSizeInPx = GetCanvasSize();
     intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
     intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
   }
 
   return ComputeSizeWithIntrinsicDimensions(
       aRenderingContext, aWM, intrinsicSize, intrinsicRatio, aCBSize, aMargin,
@@ -411,17 +412,17 @@ already_AddRefed<Layer> nsHTMLCanvasFram
     return nullptr;
 
   CanvasLayer* oldLayer = static_cast<CanvasLayer*>(
       aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, aItem));
   RefPtr<Layer> layer = element->GetCanvasLayer(aBuilder, oldLayer, aManager);
   if (!layer) return nullptr;
 
   IntrinsicSize intrinsicSize = IntrinsicSizeFromCanvasSize(canvasSizeInPx);
-  nsSize intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
+  AspectRatio intrinsicRatio = IntrinsicRatioFromCanvasSize(canvasSizeInPx);
 
   nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
       area, intrinsicSize, intrinsicRatio, StylePosition());
 
   gfxRect destGFXRect = presContext->AppUnitsToGfxUnits(dest);
 
   // Transform the canvas into the right place
   gfxPoint p = destGFXRect.TopLeft() + aContainerParameters.mOffset;
--- a/layout/generic/nsHTMLCanvasFrame.h
+++ b/layout/generic/nsHTMLCanvasFrame.h
@@ -59,17 +59,17 @@ class nsHTMLCanvasFrame final : public n
                                  WebRenderCanvasData* aCanvasData);
 
   /* get the size of the canvas's image */
   nsIntSize GetCanvasSize();
 
   virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
   virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
   virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
-  virtual nsSize GetIntrinsicRatio() override;
+  virtual mozilla::AspectRatio GetIntrinsicRatio() override;
 
   virtual mozilla::LogicalSize ComputeSize(
       gfxContext* aRenderingContext, mozilla::WritingMode aWritingMode,
       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
       const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
       const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
 
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -48,16 +48,17 @@
 
 #include <algorithm>
 #include <stdio.h>
 
 #include "CaretAssociationHint.h"
 #include "FrameProperties.h"
 #include "LayoutConstants.h"
 #include "mozilla/layout/FrameChildList.h"
+#include "mozilla/AspectRatio.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/SmallPointerArray.h"
 #include "mozilla/WritingModes.h"
 #include "nsDirection.h"
 #include "nsFrameList.h"
 #include "nsFrameState.h"
 #include "mozilla/ReflowOutput.h"
 #include "nsITheme.h"
@@ -2366,25 +2367,24 @@ class nsIFrame : public nsQueryFrame {
    *   i.e. the Containing Block's inline-size
    */
   IntrinsicISizeOffsetData IntrinsicBSizeOffsets(
       nscoord aPercentageBasis = NS_UNCONSTRAINEDSIZE);
 
   virtual mozilla::IntrinsicSize GetIntrinsicSize() = 0;
 
   /**
-   * Get the intrinsic ratio of this element, or nsSize(0,0) if it has
-   * no intrinsic ratio.  The intrinsic ratio is the ratio of the
-   * height/width of a box with an intrinsic size or the intrinsic
-   * aspect ratio of a scalable vector image without an intrinsic size.
+   * Get the intrinsic ratio of this element, or a default-constructed
+   * AspectRatio if it has no intrinsic ratio.
    *
-   * Either one of the sides may be zero, indicating a zero or infinite
-   * ratio.
-   */
-  virtual nsSize GetIntrinsicRatio() = 0;
+   * The intrinsic ratio is the ratio of the width/height of a box with an
+   * intrinsic size or the intrinsic aspect ratio of a scalable vector image
+   * without an intrinsic size.
+   */
+  virtual mozilla::AspectRatio GetIntrinsicRatio() = 0;
 
   /**
    * Bit-flags to pass to ComputeSize in |aFlags| parameter.
    */
   enum ComputeSizeFlags {
     eDefault = 0,
     /**
      * Set if the frame is in a context where non-replaced blocks should
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -204,17 +204,16 @@ nsImageFrame* nsImageFrame::CreateContin
 
 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
 
 nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
                            ClassID aID, Kind aKind)
     : nsAtomicContainerFrame(aStyle, aPresContext, aID),
       mComputedSize(0, 0),
       mIntrinsicSize(0, 0),
-      mIntrinsicRatio(0, 0),
       mKind(aKind),
       mContentURLRequestRegistered(false),
       mDisplayingIcon(false),
       mFirstFrameComplete(false),
       mReflowCallbackPosted(false),
       mForceSyncDecoding(false) {
   EnableVisibilityTracking();
 }
@@ -478,25 +477,23 @@ bool nsImageFrame::UpdateIntrinsicSize(i
   return mIntrinsicSize != oldIntrinsicSize;
 }
 
 bool nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) {
   MOZ_ASSERT(aImage, "null image");
 
   if (!aImage) return false;
 
-  nsSize oldIntrinsicRatio = mIntrinsicRatio;
-
   // Set intrinsic ratio to match aImage's reported intrinsic ratio.
   // But if we have 'contain:size', or aImage hasn't loaded enough to report
   // useful ratio, we fall back to 0,0.
-  if (StyleDisplay()->IsContainSize() ||
-      NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio))) {
-    mIntrinsicRatio.SizeTo(0, 0);
-  }
+  AspectRatio oldIntrinsicRatio = mIntrinsicRatio;
+  mIntrinsicRatio = StyleDisplay()->IsContainSize()
+                        ? AspectRatio()
+                        : aImage->GetIntrinsicRatio().valueOr(AspectRatio());
 
   return mIntrinsicRatio != oldIntrinsicRatio;
 }
 
 bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) {
   // 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
@@ -694,17 +691,17 @@ nsresult nsImageFrame::OnSizeAvailable(i
     intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
     intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
   } else {
     // We no longer have a valid image, so release our stored image container.
     mImage = mPrevImage = nullptr;
 
     // Have to size to 0,0 so that GetDesiredSize recalculates the size.
     mIntrinsicSize = IntrinsicSize(0, 0);
-    mIntrinsicRatio.SizeTo(0, 0);
+    mIntrinsicRatio = AspectRatio();
     intrinsicSizeChanged = true;
   }
 
   if (!GotInitialReflow()) {
     return NS_OK;
   }
 
   MarkNeedsDisplayItemRebuild();
@@ -815,17 +812,17 @@ void nsImageFrame::NotifyNewCurrentReque
     intrinsicSizeChanged = UpdateIntrinsicSize(mImage);
     intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged;
   } else {
     // We no longer have a valid image, so release our stored image container.
     mImage = mPrevImage = nullptr;
 
     // Have to size to 0,0 so that GetDesiredSize recalculates the size
     mIntrinsicSize = IntrinsicSize(0, 0);
-    mIntrinsicRatio.SizeTo(0, 0);
+    mIntrinsicRatio = AspectRatio();
   }
 
   if (GotInitialReflow()) {
     if (intrinsicSizeChanged) {
       if (!(mState & IMAGE_SIZECONSTRAINED)) {
         PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
                                       NS_FRAME_IS_DIRTY);
       } else {
@@ -902,17 +899,17 @@ nsRect nsImageFrame::PredictedDestRect(c
                                               mIntrinsicRatio, StylePosition());
 }
 
 void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
   if (StyleDisplay()->IsContainSize()) {
     // If we have 'contain:size', then our intrinsic size and ratio are 0,0
     // regardless of what our underlying image may think.
     mIntrinsicSize = IntrinsicSize(0, 0);
-    mIntrinsicRatio.SizeTo(0, 0);
+    mIntrinsicRatio = AspectRatio();
     return;
   }
 
   // If mIntrinsicSize.width and height are 0, then we need to update from the
   // image container.
   if (mIntrinsicSize != IntrinsicSize(0, 0)) {
     return;
   }
@@ -923,17 +920,17 @@ void nsImageFrame::EnsureIntrinsicSizeAn
     return;
   }
 
   // invalid image specified. make the image big enough for the "broken" icon
   if (ShouldShowBrokenImageIcon()) {
     nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
         ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
     mIntrinsicSize = IntrinsicSize(edgeLengthToUse, edgeLengthToUse);
-    mIntrinsicRatio.SizeTo(1, 1);
+    mIntrinsicRatio = AspectRatio(1.0f);
   }
 }
 
 /* virtual */
 LogicalSize nsImageFrame::ComputeSize(
     gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
     nscoord aAvailableISize, const LogicalSize& aMargin,
     const LogicalSize& aBorder, const LogicalSize& aPadding,
@@ -992,17 +989,17 @@ nscoord nsImageFrame::GetPrefISize(gfxCo
   // convert from normal twips to scaled twips (printing...)
   return iSize.valueOr(0);
 }
 
 /* virtual */
 IntrinsicSize nsImageFrame::GetIntrinsicSize() { return mIntrinsicSize; }
 
 /* virtual */
-nsSize nsImageFrame::GetIntrinsicRatio() { return mIntrinsicRatio; }
+AspectRatio nsImageFrame::GetIntrinsicRatio() { return mIntrinsicRatio; }
 
 void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
                           const ReflowInput& aReflowInput,
                           nsReflowStatus& aStatus) {
   MarkInReflow();
   DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -79,17 +79,17 @@ class nsImageFrame : public nsAtomicCont
 
   virtual void Init(nsIContent* aContent, nsContainerFrame* aParent,
                     nsIFrame* aPrevInFlow) override;
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override;
   virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
   virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
   virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
-  virtual nsSize GetIntrinsicRatio() override;
+  virtual mozilla::AspectRatio GetIntrinsicRatio() override;
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
                       const ReflowInput& aReflowInput,
                       nsReflowStatus& aStatus) override;
 
   virtual nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
                                       nsIContent** aContent) override;
   virtual nsresult HandleEvent(nsPresContext* aPresContext,
                                mozilla::WidgetGUIEvent* aEvent,
@@ -357,17 +357,17 @@ class nsImageFrame : public nsAtomicCont
 
   // An image request created for content: url(..).
   RefPtr<imgRequestProxy> mContentURLRequest;
 
   nsCOMPtr<imgIContainer> mImage;
   nsCOMPtr<imgIContainer> mPrevImage;
   nsSize mComputedSize;
   mozilla::IntrinsicSize mIntrinsicSize;
-  nsSize mIntrinsicRatio;
+  mozilla::AspectRatio mIntrinsicRatio;
 
   const Kind mKind;
   bool mContentURLRequestRegistered;
   bool mDisplayingIcon;
   bool mFirstFrameComplete;
   bool mReflowCallbackPosted;
   bool mForceSyncDecoding;
 
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -722,17 +722,17 @@ static bool IsPercentageAware(const nsIF
     // Per CSS 2.1, section 10.3.2:
     //   If 'height' and 'width' both have computed values of 'auto' and
     //   the element has an intrinsic ratio but no intrinsic height or
     //   width and the containing block's width does not itself depend
     //   on the replaced element's width, then the used value of 'width'
     //   is calculated from the constraint equation used for
     //   block-level, non-replaced elements in normal flow.
     nsIFrame* f = const_cast<nsIFrame*>(aFrame);
-    if (f->GetIntrinsicRatio() != nsSize(0, 0) &&
+    if (f->GetIntrinsicRatio() &&
         // Some percents are treated like 'auto', so check != coord
         !pos->BSize(aWM).ConvertsToLength()) {
       const IntrinsicSize& intrinsicSize = f->GetIntrinsicSize();
       if (!intrinsicSize.width && !intrinsicSize.height) {
         return true;
       }
     }
   }
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -718,17 +718,17 @@ IntrinsicSize nsSubDocumentFrame::GetInt
   nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame();
   if (subDocRoot) {
     return subDocRoot->GetIntrinsicSize();
   }
   return nsAtomicContainerFrame::GetIntrinsicSize();
 }
 
 /* virtual */
-nsSize nsSubDocumentFrame::GetIntrinsicRatio() {
+AspectRatio nsSubDocumentFrame::GetIntrinsicRatio() {
   nsIFrame* subDocRoot = ObtainIntrinsicSizeFrame();
   if (subDocRoot) {
     return subDocRoot->GetIntrinsicRatio();
   }
   return nsAtomicContainerFrame::GetIntrinsicRatio();
 }
 
 /* virtual */
@@ -800,17 +800,17 @@ void nsSubDocumentFrame::Reflow(nsPresCo
   if (mInnerView) {
     const nsMargin& bp = aReflowInput.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;
+    AspectRatio intrinsRatio;
     if (subDocRoot) {
       intrinsSize = subDocRoot->GetIntrinsicSize();
       intrinsRatio = subDocRoot->GetIntrinsicRatio();
     }
     nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
         nsRect(offset, innerSize), intrinsSize, intrinsRatio, StylePosition());
 
     nsViewManager* vm = mInnerView->GetViewManager();
--- a/layout/generic/nsSubDocumentFrame.h
+++ b/layout/generic/nsSubDocumentFrame.h
@@ -50,17 +50,17 @@ class nsSubDocumentFrame final : public 
 
   void DestroyFrom(nsIFrame* aDestructRoot,
                    PostDestroyData& aPostDestroyData) override;
 
   nscoord GetMinISize(gfxContext* aRenderingContext) override;
   nscoord GetPrefISize(gfxContext* aRenderingContext) override;
 
   mozilla::IntrinsicSize GetIntrinsicSize() override;
-  nsSize GetIntrinsicRatio() override;
+  mozilla::AspectRatio GetIntrinsicRatio() override;
 
   mozilla::LogicalSize ComputeAutoSize(
       gfxContext* aRenderingContext, mozilla::WritingMode aWritingMode,
       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
       const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
       const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
 
   mozilla::LogicalSize ComputeSize(
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -184,19 +184,21 @@ already_AddRefed<Layer> nsVideoFrame::Bu
   if (frameSize.width == 0 || frameSize.height == 0) {
     // No image, or zero-sized image. No point creating a layer.
     return nullptr;
   }
 
   // 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(aspectRatio.width, aspectRatio.height);
+  auto aspectRatio =
+      AspectRatio::FromSize(videoSizeInPx.width, videoSizeInPx.height);
+  IntrinsicSize intrinsicSize(
+      nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width),
+      nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height));
 
   nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
       area, intrinsicSize, aspectRatio, StylePosition());
 
   gfxRect destGFXRect = PresContext()->AppUnitsToGfxUnits(dest);
   destGFXRect.Round();
   if (destGFXRect.IsEmpty()) {
     return nullptr;
@@ -429,20 +431,21 @@ class nsDisplayVideo : public nsDisplayI
     if (frameSize.width == 0 || frameSize.height == 0) {
       // No image, or zero-sized image. Don't render.
       return true;
     }
 
     // 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(
+    IntrinsicSize intrinsicSize(
         nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.width),
         nsPresContext::CSSPixelsToAppUnits(videoSizeInPx.height));
-    IntrinsicSize intrinsicSize(aspectRatio.width, aspectRatio.height);
+    auto aspectRatio =
+        AspectRatio::FromSize(videoSizeInPx.width, videoSizeInPx.height);
 
     nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
         area, intrinsicSize, aspectRatio, Frame()->StylePosition());
 
     gfxRect destGFXRect = Frame()->PresContext()->AppUnitsToGfxUnits(dest);
     destGFXRect.Round();
     if (destGFXRect.IsEmpty()) {
       return true;
@@ -571,17 +574,19 @@ LogicalSize nsVideoFrame::ComputeSize(
                                          aAvailableISize, aMargin, aBorder,
                                          aPadding, aFlags);
   }
 
   nsSize size = GetVideoIntrinsicSize(aRenderingContext);
   IntrinsicSize intrinsicSize(size.width, size.height);
 
   // Only video elements have an intrinsic ratio.
-  nsSize intrinsicRatio = HasVideoElement() ? size : nsSize(0, 0);
+  auto intrinsicRatio = HasVideoElement()
+                            ? AspectRatio::FromSize(size.width, size.height)
+                            : AspectRatio();
 
   return ComputeSizeWithIntrinsicDimensions(
       aRenderingContext, aWM, intrinsicSize, intrinsicRatio, aCBSize, aMargin,
       aBorder, aPadding, aFlags);
 }
 
 nscoord nsVideoFrame::GetMinISize(gfxContext* aRenderingContext) {
   nscoord result;
@@ -622,23 +627,24 @@ nscoord nsVideoFrame::GetPrefISize(gfxCo
     } else {
       result = 0;
     }
   }
 
   return result;
 }
 
-nsSize nsVideoFrame::GetIntrinsicRatio() {
+AspectRatio nsVideoFrame::GetIntrinsicRatio() {
   if (!HasVideoElement()) {
     // Audio elements have no intrinsic ratio.
-    return nsSize(0, 0);
+    return AspectRatio();
   }
 
-  return GetVideoIntrinsicSize(nullptr);
+  nsSize size = GetVideoIntrinsicSize(nullptr);
+  return AspectRatio::FromSize(size.width, size.height);
 }
 
 bool nsVideoFrame::ShouldDisplayPoster() {
   if (!HasVideoElement()) return false;
 
   HTMLVideoElement* element = static_cast<HTMLVideoElement*>(GetContent());
   if (element->GetPlayedOrSeeked() && HasVideoData()) return false;
 
--- a/layout/generic/nsVideoFrame.h
+++ b/layout/generic/nsVideoFrame.h
@@ -50,17 +50,17 @@ class nsVideoFrame final : public nsCont
                             int32_t aModType) override;
 
   void OnVisibilityChange(
       Visibility aNewVisibility,
       const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()) override;
 
   /* get the size of the video's display */
   nsSize GetVideoIntrinsicSize(gfxContext* aRenderingContext);
-  nsSize GetIntrinsicRatio() override;
+  mozilla::AspectRatio GetIntrinsicRatio() override;
   mozilla::LogicalSize ComputeSize(
       gfxContext* aRenderingContext, mozilla::WritingMode aWritingMode,
       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
       const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
       const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
   nscoord GetMinISize(gfxContext* aRenderingContext) override;
   nscoord GetPrefISize(gfxContext* aRenderingContext) override;
   void DestroyFrom(nsIFrame* aDestructRoot,
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -2883,39 +2883,34 @@ static nsSize ComputeDrawnSizeForBackgro
 
   // Calculate the rounded size only if the background-size computation
   // returned a correct size for the image.
   if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
     imageSize.width = nsCSSRendering::ComputeRoundedSize(
         imageSize.width, aBgPositioningArea.width);
     if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
         aLayerSize.explicit_size.height.IsAuto()) {
-      // Restore intrinsic rato
-      if (aIntrinsicSize.mRatio.width) {
-        float scale =
-            float(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width;
+      // Restore intrinsic ratio
+      if (aIntrinsicSize.mRatio) {
         imageSize.height =
-            NSCoordSaturatingNonnegativeMultiply(imageSize.width, scale);
+            aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
       }
     }
   }
 
   // Calculate the rounded size only if the background-size computation
   // returned a correct size for the image.
   if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
     imageSize.height = nsCSSRendering::ComputeRoundedSize(
         imageSize.height, aBgPositioningArea.height);
     if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
         aLayerSize.explicit_size.width.IsAuto()) {
-      // Restore intrinsic rato
-      if (aIntrinsicSize.mRatio.height) {
-        float scale =
-            float(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height;
-        imageSize.width =
-            NSCoordSaturatingNonnegativeMultiply(imageSize.height, scale);
+      // Restore intrinsic ratio
+      if (aIntrinsicSize.mRatio) {
+        imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
       }
     }
   }
 
   return imageSize;
 }
 
 /* ComputeSpacedRepeatSize
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -33,25 +33,21 @@ using namespace mozilla::image;
 using namespace mozilla::layers;
 
 nsSize CSSSizeOrRatio::ComputeConcreteSize() const {
   NS_ASSERTION(CanComputeConcreteSize(), "Cannot compute");
   if (mHasWidth && mHasHeight) {
     return nsSize(mWidth, mHeight);
   }
   if (mHasWidth) {
-    nscoord height = NSCoordSaturatingNonnegativeMultiply(
-        mWidth, double(mRatio.height) / mRatio.width);
-    return nsSize(mWidth, height);
+    return nsSize(mWidth, mRatio.Inverted().ApplyTo(mWidth));
   }
 
   MOZ_ASSERT(mHasHeight);
-  nscoord width = NSCoordSaturatingNonnegativeMultiply(
-      mHeight, double(mRatio.width) / mRatio.height);
-  return nsSize(width, mHeight);
+  return nsSize(mRatio.ApplyTo(mHeight), mHeight);
 }
 
 nsImageRenderer::nsImageRenderer(nsIFrame* aForFrame,
                                  const nsStyleImage* aImage, uint32_t aFlags)
     : mForFrame(aForFrame),
       mImage(aImage),
       mType(aImage->GetType()),
       mImageContainer(nullptr),
@@ -223,25 +219,22 @@ CSSSizeOrRatio nsImageRenderer::ComputeI
       }
       if (haveHeight) {
         result.SetHeight(
             nsPresContext::CSSPixelsToAppUnits(imageIntSize.height));
       }
 
       // If we know the aspect ratio and one of the dimensions,
       // we can compute the other missing width or height.
-      if (!haveHeight && haveWidth && result.mRatio.width != 0) {
-        nscoord intrinsicHeight = NSCoordSaturatingNonnegativeMultiply(
-            imageIntSize.width,
-            float(result.mRatio.height) / float(result.mRatio.width));
+      if (!haveHeight && haveWidth && result.mRatio) {
+        nscoord intrinsicHeight =
+            result.mRatio.Inverted().ApplyTo(imageIntSize.width);
         result.SetHeight(nsPresContext::CSSPixelsToAppUnits(intrinsicHeight));
-      } else if (haveHeight && !haveWidth && result.mRatio.height != 0) {
-        nscoord intrinsicWidth = NSCoordSaturatingNonnegativeMultiply(
-            imageIntSize.height,
-            float(result.mRatio.width) / float(result.mRatio.height));
+      } else if (haveHeight && !haveWidth && result.mRatio) {
+        nscoord intrinsicWidth = result.mRatio.ApplyTo(imageIntSize.height);
         result.SetWidth(nsPresContext::CSSPixelsToAppUnits(intrinsicWidth));
       }
 
       break;
     }
     case eStyleImageType_Element: {
       // XXX element() should have the width/height of the referenced element,
       //     and that element's ratio, if it matches.  If it doesn't match, it
@@ -312,70 +305,64 @@ nsSize nsImageRenderer::ComputeConcreteS
   }
 
   MOZ_ASSERT(aSpecifiedSize.mHasWidth || aSpecifiedSize.mHasHeight);
 
   // The specified height is partial, try to compute the missing part.
   if (aSpecifiedSize.mHasWidth) {
     nscoord height;
     if (aIntrinsicSize.HasRatio()) {
-      height = NSCoordSaturatingNonnegativeMultiply(
-          aSpecifiedSize.mWidth,
-          double(aIntrinsicSize.mRatio.height) / aIntrinsicSize.mRatio.width);
+      height = aIntrinsicSize.mRatio.Inverted().ApplyTo(aSpecifiedSize.mWidth);
     } else if (aIntrinsicSize.mHasHeight) {
       height = aIntrinsicSize.mHeight;
     } else {
       height = aDefaultSize.height;
     }
     return nsSize(aSpecifiedSize.mWidth, height);
   }
 
   MOZ_ASSERT(aSpecifiedSize.mHasHeight);
   nscoord width;
   if (aIntrinsicSize.HasRatio()) {
-    width = NSCoordSaturatingNonnegativeMultiply(
-        aSpecifiedSize.mHeight,
-        double(aIntrinsicSize.mRatio.width) / aIntrinsicSize.mRatio.height);
+    width = aIntrinsicSize.mRatio.ApplyTo(aSpecifiedSize.mHeight);
   } else if (aIntrinsicSize.mHasWidth) {
     width = aIntrinsicSize.mWidth;
   } else {
     width = aDefaultSize.width;
   }
   return nsSize(width, aSpecifiedSize.mHeight);
 }
 
 /* static */
-nsSize nsImageRenderer::ComputeConstrainedSize(const nsSize& aConstrainingSize,
-                                               const nsSize& aIntrinsicRatio,
-                                               FitType aFitType) {
-  if (aIntrinsicRatio.width <= 0 && aIntrinsicRatio.height <= 0) {
+nsSize nsImageRenderer::ComputeConstrainedSize(
+    const nsSize& aConstrainingSize, const AspectRatio& aIntrinsicRatio,
+    FitType aFitType) {
+  if (!aIntrinsicRatio) {
     return aConstrainingSize;
   }
 
-  float scaleX = double(aConstrainingSize.width) / aIntrinsicRatio.width;
-  float scaleY = double(aConstrainingSize.height) / aIntrinsicRatio.height;
+  auto constrainingRatio =
+      AspectRatio::FromSize(aConstrainingSize.width, aConstrainingSize.height);
   nsSize size;
-  if ((aFitType == CONTAIN) == (scaleX < scaleY)) {
+  if ((aFitType == CONTAIN) == (constrainingRatio < aIntrinsicRatio)) {
     size.width = aConstrainingSize.width;
-    size.height =
-        NSCoordSaturatingNonnegativeMultiply(aIntrinsicRatio.height, scaleX);
+    size.height = aIntrinsicRatio.Inverted().ApplyTo(aConstrainingSize.width);
     // If we're reducing the size by less than one css pixel, then just use the
     // constraining size.
     if (aFitType == CONTAIN &&
         aConstrainingSize.height - size.height < AppUnitsPerCSSPixel()) {
       size.height = aConstrainingSize.height;
     }
   } else {
-    size.width =
-        NSCoordSaturatingNonnegativeMultiply(aIntrinsicRatio.width, scaleY);
+    size.height = aConstrainingSize.height;
+    size.width = aIntrinsicRatio.ApplyTo(aConstrainingSize.height);
     if (aFitType == CONTAIN &&
         aConstrainingSize.width - size.width < AppUnitsPerCSSPixel()) {
       size.width = aConstrainingSize.width;
     }
-    size.height = aConstrainingSize.height;
   }
   return size;
 }
 
 /**
  * mSize is the image's "preferred" size for this particular rendering, while
  * the drawn (aka concrete) size is the actual rendered size after accounting
  * for background-size etc..  The preferred size is most often the image's
--- a/layout/painting/nsImageRenderer.h
+++ b/layout/painting/nsImageRenderer.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsImageRenderer_h__
 #define nsImageRenderer_h__
 
 #include "nsLayoutUtils.h"
 #include "nsStyleStruct.h"
 #include "Units.h"
+#include "mozilla/AspectRatio.h"
 
 class gfxDrawable;
 namespace mozilla {
 
 namespace layers {
 class StackingContextHelper;
 class WebRenderParentCommand;
 class RenderRootStateManager;
@@ -26,65 +27,61 @@ class IpcResourceUpdateQueue;
 }  // namespace wr
 
 // A CSSSizeOrRatio represents a (possibly partially specified) size for use
 // in computing image sizes. Either or both of the width and height might be
 // given. A ratio of width to height may also be given. If we at least two
 // of these then we can compute a concrete size, that is a width and height.
 struct CSSSizeOrRatio {
   CSSSizeOrRatio()
-      : mRatio(0, 0),
-        mWidth(0),
-        mHeight(0),
-        mHasWidth(false),
-        mHasHeight(false) {}
+      : mWidth(0), mHeight(0), mHasWidth(false), mHasHeight(false) {}
 
   bool CanComputeConcreteSize() const {
     return mHasWidth + mHasHeight + HasRatio() >= 2;
   }
   bool IsConcrete() const { return mHasWidth && mHasHeight; }
-  bool HasRatio() const { return mRatio.width > 0 && mRatio.height > 0; }
+  bool HasRatio() const { return !!mRatio; }
   bool IsEmpty() const {
     return (mHasWidth && mWidth <= 0) || (mHasHeight && mHeight <= 0) ||
-           mRatio.width <= 0 || mRatio.height <= 0;
+           !mRatio;
   }
 
   // CanComputeConcreteSize must return true when ComputeConcreteSize is
   // called.
   nsSize ComputeConcreteSize() const;
 
   void SetWidth(nscoord aWidth) {
     mWidth = aWidth;
     mHasWidth = true;
     if (mHasHeight) {
-      mRatio = nsSize(mWidth, mHeight);
+      mRatio = AspectRatio::FromSize(mWidth, mHeight);
     }
   }
   void SetHeight(nscoord aHeight) {
     mHeight = aHeight;
     mHasHeight = true;
     if (mHasWidth) {
-      mRatio = nsSize(mWidth, mHeight);
+      mRatio = AspectRatio::FromSize(mWidth, mHeight);
     }
   }
   void SetSize(const nsSize& aSize) {
     mWidth = aSize.width;
     mHeight = aSize.height;
     mHasWidth = true;
     mHasHeight = true;
-    mRatio = aSize;
+    mRatio = AspectRatio::FromSize(mWidth, mHeight);
   }
-  void SetRatio(const nsSize& aRatio) {
+  void SetRatio(const AspectRatio& aRatio) {
     MOZ_ASSERT(
         !mHasWidth || !mHasHeight,
         "Probably shouldn't be setting a ratio if we have a concrete size");
     mRatio = aRatio;
   }
 
-  nsSize mRatio;
+  AspectRatio mRatio;
   nscoord mWidth;
   nscoord mHeight;
   bool mHasWidth;
   bool mHasHeight;
 };
 
 /**
  * This is a small wrapper class to encapsulate image drawing that can draw an
@@ -151,22 +148,20 @@ class nsImageRenderer {
                                        const nsSize& aOriginBounds,
                                        const nsSize& aImageSize,
                                        nsPoint* aTopLeft,
                                        nsPoint* aAnchorPoint);
 
   /**
    * Compute the size of the rendered image using either the 'cover' or
    * 'contain' constraints (aFitType).
-   * aIntrinsicRatio may be an invalid ratio, that is one or both of its
-   * dimensions can be less than or equal to zero.
    */
-  static nsSize ComputeConstrainedSize(const nsSize& aConstrainingSize,
-                                       const nsSize& aIntrinsicRatio,
-                                       FitType aFitType);
+  static nsSize ComputeConstrainedSize(
+      const nsSize& aConstrainingSize,
+      const mozilla::AspectRatio& aIntrinsicRatio, FitType aFitType);
   /**
    * Compute the size of the rendered image (the concrete size) where no cover/
    * contain constraints are given. The 'default algorithm' from the CSS Image
    * Values spec.
    */
   static nsSize ComputeConcreteSize(
       const mozilla::CSSSizeOrRatio& aSpecifiedSize,
       const mozilla::CSSSizeOrRatio& aIntrinsicSize,
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2657,30 +2657,30 @@ static bool SizeDependsOnPositioningArea
 
   if (type == eStyleImageType_Image) {
     nsCOMPtr<imgIContainer> imgContainer;
     if (imgRequestProxy* req = aImage.GetImageData()) {
       req->GetImage(getter_AddRefs(imgContainer));
     }
     if (imgContainer) {
       CSSIntSize imageSize;
-      nsSize imageRatio;
+      AspectRatio imageRatio;
       bool hasWidth, hasHeight;
       nsLayoutUtils::ComputeSizeForDrawing(imgContainer, imageSize, imageRatio,
                                            hasWidth, hasHeight);
 
       // If the image has a fixed width and height, rendering never depends on
       // the frame size.
       if (hasWidth && hasHeight) {
         return false;
       }
 
       // If the image has an intrinsic ratio, rendering will depend on frame
       // size when background-size is all auto.
-      if (imageRatio != nsSize(0, 0)) {
+      if (imageRatio) {
         return size.width.IsAuto() == size.height.IsAuto();
       }
 
       // Otherwise, rendering depends on frame size when the image dimensions
       // and background-size don't complement each other.
       return !(hasWidth && size.width.IsLengthPercentage()) &&
              !(hasHeight && size.height.IsLengthPercentage());
     }
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -229,69 +229,51 @@ IntrinsicSize nsSVGOuterSVGFrame::GetInt
         nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content));
     intrinsicSize.height.emplace(std::max(val, 0));
   }
 
   return intrinsicSize;
 }
 
 /* virtual */
-nsSize nsSVGOuterSVGFrame::GetIntrinsicRatio() {
+AspectRatio nsSVGOuterSVGFrame::GetIntrinsicRatio() {
   if (StyleDisplay()->IsContainSize()) {
-    return nsSize(0, 0);
+    return AspectRatio();
   }
 
   // We only have an intrinsic size/ratio if our width and height attributes
   // are both specified and set to non-percentage values, or we have a viewBox
   // rect: http://www.w3.org/TR/SVGMobile12/coords.html#IntrinsicSizing
   // Unfortunately we have to return the ratio as two nscoords whereas what
   // we have are two floats. Using app units allows for some floating point
   // values to work but really small or large numbers will fail.
 
   SVGSVGElement* content = static_cast<SVGSVGElement*>(GetContent());
   const SVGAnimatedLength& width =
       content->mLengthAttributes[SVGSVGElement::ATTR_WIDTH];
   const SVGAnimatedLength& height =
       content->mLengthAttributes[SVGSVGElement::ATTR_HEIGHT];
 
   if (!width.IsPercentage() && !height.IsPercentage()) {
-    nsSize ratio(
-        nsPresContext::CSSPixelsToAppUnits(width.GetAnimValue(content)),
-        nsPresContext::CSSPixelsToAppUnits(height.GetAnimValue(content)));
-    if (ratio.width < 0) {
-      ratio.width = 0;
-    }
-    if (ratio.height < 0) {
-      ratio.height = 0;
-    }
-    return ratio;
+    return AspectRatio::FromSize(width.GetAnimValue(content),
+                                 height.GetAnimValue(content));
   }
 
   SVGViewElement* viewElement = content->GetCurrentViewElement();
   const SVGViewBox* viewbox = nullptr;
 
   // The logic here should match HasViewBox().
   if (viewElement && viewElement->mViewBox.HasRect()) {
     viewbox = &viewElement->mViewBox.GetAnimValue();
   } else if (content->mViewBox.HasRect()) {
     viewbox = &content->mViewBox.GetAnimValue();
   }
 
   if (viewbox) {
-    float viewBoxWidth = viewbox->width;
-    float viewBoxHeight = viewbox->height;
-
-    if (viewBoxWidth < 0.0f) {
-      viewBoxWidth = 0.0f;
-    }
-    if (viewBoxHeight < 0.0f) {
-      viewBoxHeight = 0.0f;
-    }
-    return nsSize(nsPresContext::CSSPixelsToAppUnits(viewBoxWidth),
-                  nsPresContext::CSSPixelsToAppUnits(viewBoxHeight));
+    return AspectRatio::FromSize(viewbox->width, viewbox->height);
   }
 
   return nsSVGDisplayContainerFrame::GetIntrinsicRatio();
 }
 
 /* virtual */
 LogicalSize nsSVGOuterSVGFrame::ComputeSize(
     gfxContext* aRenderingContext, WritingMode aWritingMode,
--- a/layout/svg/nsSVGOuterSVGFrame.h
+++ b/layout/svg/nsSVGOuterSVGFrame.h
@@ -45,17 +45,17 @@ class nsSVGOuterSVGFrame final : public 
   }
 #endif
 
   // nsIFrame:
   virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
   virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
 
   virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
-  virtual nsSize GetIntrinsicRatio() override;
+  virtual mozilla::AspectRatio GetIntrinsicRatio() override;
 
   virtual mozilla::LogicalSize ComputeSize(
       gfxContext* aRenderingContext, mozilla::WritingMode aWritingMode,
       const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
       const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
       const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
 
   virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -465,26 +465,27 @@ nsRect nsImageBoxFrame::GetDestRect(cons
     // XXXdholbert Should we even honor these properties in this case? They only
     // apply to replaced elements, and I'm not sure we count as a replaced
     // element when our image data is determined by CSS.
     dest = clientRect;
   } else {
     // Determine dest rect based on intrinsic size & ratio, along with
     // 'object-fit' & 'object-position' properties:
     IntrinsicSize intrinsicSize;
-    nsSize intrinsicRatio;
+    AspectRatio intrinsicRatio;
     if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) {
       // Image has a valid size; use it as intrinsic size & ratio.
       intrinsicSize =
           IntrinsicSize(mIntrinsicSize.width, mIntrinsicSize.height);
-      intrinsicRatio = mIntrinsicSize;
+      intrinsicRatio =
+          AspectRatio::FromSize(mIntrinsicSize.width, mIntrinsicSize.height);
     } else {
       // Image doesn't have a (valid) intrinsic size.
       // Try to look up intrinsic ratio and use that at least.
-      imgCon->GetIntrinsicRatio(&intrinsicRatio);
+      intrinsicRatio = imgCon->GetIntrinsicRatio().valueOr(AspectRatio());
     }
     aAnchorPoint.emplace();
     dest = nsLayoutUtils::ComputeObjectDestRect(clientRect, intrinsicSize,
                                                 intrinsicRatio, StylePosition(),
                                                 aAnchorPoint.ptr());
   }
 
   return dest;