diff options
author | Andy <webmaster@RealityRipple.com> | 2020-08-04 13:54:01 -0700 |
---|---|---|
committer | Andy <webmaster@RealityRipple.com> | 2020-08-04 13:56:45 -0700 |
commit | 3ed884a6adff46cb5871508612832ab8691752ac (patch) | |
tree | 8325a71298a455591729769b2c6d015e2af77748 | |
parent | 267d32f4f3360bb583486391d3d9cb620458c5f0 (diff) | |
download | UXP-3ed884a6adff46cb5871508612832ab8691752ac.tar UXP-3ed884a6adff46cb5871508612832ab8691752ac.tar.gz UXP-3ed884a6adff46cb5871508612832ab8691752ac.tar.lz UXP-3ed884a6adff46cb5871508612832ab8691752ac.tar.xz UXP-3ed884a6adff46cb5871508612832ab8691752ac.zip |
Issue #1620 - Use Intrinsic Aspect Ratio for Images
https://bugzilla.mozilla.org/show_bug.cgi?id=1547231
https://bugzilla.mozilla.org/show_bug.cgi?id=1559094
https://bugzilla.mozilla.org/show_bug.cgi?id=1633434
https://bugzilla.mozilla.org/show_bug.cgi?id=1565690
https://bugzilla.mozilla.org/show_bug.cgi?id=1602047
Make use of Aspect Ratios in Image frames before Images are loaded.
- Check for width and height HTML properties and create a ratio with them.
- Overwrite HTML size values with actual image dimensions on load.
- Collapse any frames with srcless images.
Comments:
dom/html/nsGenericHTMLElement.cpp:1483
layout/generic/nsImageFrame.cpp:289
-rw-r--r-- | dom/html/HTMLImageElement.cpp | 2 | ||||
-rw-r--r-- | dom/html/HTMLImageElement.h | 5 | ||||
-rw-r--r-- | dom/html/nsGenericHTMLElement.cpp | 60 | ||||
-rw-r--r-- | dom/html/nsGenericHTMLElement.h | 4 | ||||
-rw-r--r-- | layout/generic/crashtests/1633434.html | 15 | ||||
-rw-r--r-- | layout/generic/crashtests/crashtests.list | 1 | ||||
-rw-r--r-- | layout/generic/nsImageFrame.cpp | 266 | ||||
-rw-r--r-- | layout/generic/nsImageFrame.h | 20 | ||||
-rw-r--r-- | layout/style/nsCSSPropList.h | 11 | ||||
-rw-r--r-- | layout/style/nsRuleNode.cpp | 6 | ||||
-rw-r--r-- | layout/style/nsStyleStruct.cpp | 7 | ||||
-rw-r--r-- | layout/style/nsStyleStruct.h | 1 | ||||
-rw-r--r-- | layout/style/test/ListCSSProperties.cpp | 1 | ||||
-rw-r--r-- | modules/libpref/init/all.js | 6 | ||||
-rw-r--r-- | testing/web-platform/tests/css-backgrounds/background-size-cover-003-ref.html | 21 | ||||
-rw-r--r-- | testing/web-platform/tests/css-backgrounds/background-size-cover-003.html | 38 | ||||
-rw-r--r-- | testing/web-platform/tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html | 60 |
17 files changed, 381 insertions, 143 deletions
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index 444c352e2..d042a9fe6 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -325,7 +325,7 @@ HTMLImageElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes, nsGenericHTMLElement::MapImageAlignAttributeInto(aAttributes, aData); nsGenericHTMLElement::MapImageBorderAttributeInto(aAttributes, aData); nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData); - nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData); + nsGenericHTMLElement::MapImageSizeAttributesInto(aAttributes, aData, true); nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData); } diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h index bb4a09882..23cac4afb 100644 --- a/dom/html/HTMLImageElement.h +++ b/dom/html/HTMLImageElement.h @@ -196,6 +196,11 @@ public: return GetReferrerPolicyAsEnum(); } + bool IsAwaitingLoad() const + { + return !!mPendingImageLoadTask; + } + int32_t X(); int32_t Y(); // Uses XPCOM GetLowsrc. diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index b52e61ce6..afc76ee31 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -1449,29 +1449,59 @@ nsGenericHTMLElement::MapImageMarginAttributeInto(const nsMappedAttributes* aAtt void nsGenericHTMLElement::MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes, - nsRuleData* aData) + nsRuleData* aData, + bool aMapAspectRatio) { if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Position))) return; + auto* aWidth = aAttributes->GetAttr(nsGkAtoms::width); + auto* aHeight = aAttributes->GetAttr(nsGkAtoms::height); + // width: value - nsCSSValue* width = aData->ValueForWidth(); - if (width->GetUnit() == eCSSUnit_Null) { - const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width); - if (value && value->Type() == nsAttrValue::eInteger) - width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); - else if (value && value->Type() == nsAttrValue::ePercent) - width->SetPercentValue(value->GetPercentValue()); + if (aWidth) { + nsCSSValue* cWidth = aData->ValueForWidth(); + if (cWidth->GetUnit() == eCSSUnit_Null) { + if (aWidth->Type() == nsAttrValue::eInteger) + cWidth->SetFloatValue((float)aWidth->GetIntegerValue(), eCSSUnit_Pixel); + else if (aWidth->Type() == nsAttrValue::ePercent) + cWidth->SetPercentValue(aWidth->GetPercentValue()); + } } // height: value - nsCSSValue* height = aData->ValueForHeight(); - if (height->GetUnit() == eCSSUnit_Null) { - const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height); - if (value && value->Type() == nsAttrValue::eInteger) - height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); - else if (value && value->Type() == nsAttrValue::ePercent) - height->SetPercentValue(value->GetPercentValue()); + if (aHeight) { + nsCSSValue* cHeight = aData->ValueForHeight(); + if (cHeight->GetUnit() == eCSSUnit_Null) { + if (aHeight->Type() == nsAttrValue::eInteger) + cHeight->SetFloatValue((float)aHeight->GetIntegerValue(), eCSSUnit_Pixel); + else if (aHeight->Type() == nsAttrValue::ePercent) + cHeight->SetPercentValue(aHeight->GetPercentValue()); + } + } + + // 2020-07-15 (RealityRipple) Much of this is a guess based on a few sources. + // Please go over this with a fine-tooth comb before production. + if (Preferences::GetBool("layout.css.width-and-height-map-to-aspect-ratio.enabled") && + aMapAspectRatio && aWidth && aHeight) { + Maybe<double> w; + if (aWidth->Type() == nsAttrValue::eInteger) { + w.emplace(aWidth->GetIntegerValue()); + } else if (aWidth->Type() == nsAttrValue::eDoubleValue) { + w.emplace(aWidth->GetDoubleValue()); + } + + Maybe<double> h; + if (aHeight->Type() == nsAttrValue::eInteger) { + h.emplace(aHeight->GetIntegerValue()); + } else if (aHeight->Type() == nsAttrValue::eDoubleValue) { + h.emplace(aHeight->GetDoubleValue()); + } + + if (w && h && *w != 0 && *h != 0) { + nsCSSValue* aspect_ratio = aData->ValueForAspectRatio(); + aspect_ratio->SetFloatValue((float(*w) / float(*h)), eCSSUnit_Number); + } } } diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index c9169df11..8412ea0dc 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -702,10 +702,12 @@ public: * * @param aAttributes the list of attributes to map * @param aData the returned rule data [INOUT] + * @param aMapAspectRatio map width and height attributes on aspect-ratio * @see GetAttributeMappingFunction */ static void MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes, - nsRuleData* aData); + nsRuleData* aData, + bool = false); /** * Helper to map the background attribute * into a style struct. diff --git a/layout/generic/crashtests/1633434.html b/layout/generic/crashtests/1633434.html new file mode 100644 index 000000000..8a60b2072 --- /dev/null +++ b/layout/generic/crashtests/1633434.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> + <script> + document.addEventListener('DOMContentLoaded', () => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svg.setAttribute('height', '6') + svg.setAttribute('width', '1pc') + document.documentElement.appendChild(svg) + svg.style.setProperty('height', '5%', undefined) + svg.width.baseVal.valueInSpecifiedUnits = 1.988164037240853e+38 + }) + </script> +</head> +</html> diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list index 1a597e51c..72872bfde 100644 --- a/layout/generic/crashtests/crashtests.list +++ b/layout/generic/crashtests/crashtests.list @@ -644,3 +644,4 @@ load 1304441.html load 1316649.html load 1381134.html load 1381134-2.html +load 1633434.html diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index 365b7810b..3208a3233 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -14,6 +14,7 @@ #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/PathHelpers.h" +#include "mozilla/dom/HTMLImageElement.h" #include "mozilla/MouseEvents.h" #include "mozilla/Unused.h" @@ -229,26 +230,26 @@ nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { nsAtomicContainerFrame::DidSetStyleContext(aOldStyleContext); - if (!mImage) { - // We'll pick this change up whenever we do get an image. - return; - } - nsStyleImageOrientation newOrientation = StyleVisibility()->mImageOrientation; // We need to update our orientation either if we had no style context before // because this is the first time it's been set, or if the image-orientation // property changed from its previous value. bool shouldUpdateOrientation = - !aOldStyleContext || - aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation; + mImage && + (!aOldStyleContext || + aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation); if (shouldUpdateOrientation) { nsCOMPtr<imgIContainer> image(mImage->Unwrap()); mImage = nsLayoutUtils::OrientImage(image, newOrientation); - UpdateIntrinsicSize(mImage); - UpdateIntrinsicRatio(mImage); + UpdateIntrinsicSize(); + UpdateIntrinsicRatio(); + } else if (!aOldStyleContext || + aOldStyleContext->StylePosition()->mAspectRatio != + StylePosition()->mAspectRatio) { + UpdateIntrinsicRatio(); } } @@ -286,50 +287,114 @@ nsImageFrame::Init(nsIContent* aContent, p->AdjustPriority(-1); } -bool -nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage) +// 2020-07-14 (RealityRipple) Firefox is doing this completely differently +// because of loading="lazy" support and the StyleDisplay()->IsContainSize() +// property. Double-check all of this for problems. + +static IntrinsicSize +ComputeIntrinsicSize(imgIContainer* aImage, + bool aUseMappedRatio, + const nsImageFrame& aFrame) { - NS_PRECONDITION(aImage, "null image"); - if (!aImage) - return false; + // When 'contain: size' is implemented, make sure to check for it. +/* + const ComputedStyle& style = *aFrame.Style(); + if (style.StyleDisplay()->IsContainSize()) { + return AspectRatio(); + } + */ + nsSize size; + IntrinsicSize intrinsicSize; + if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) { + if (size.width != -1) + intrinsicSize.width.SetCoordValue(size.width); + if (size.height != -1) + intrinsicSize.height.SetCoordValue(size.height); + return intrinsicSize; + } - IntrinsicSize oldIntrinsicSize = mIntrinsicSize; - mIntrinsicSize = IntrinsicSize(); - - // Set intrinsic size to match aImage's reported intrinsic width & height. - nsSize intrinsicSize; - if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) { - // If the image has no intrinsic width, intrinsicSize.width will be -1, and - // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None. - // Otherwise we use intrinsicSize.width. Height works the same way. - if (intrinsicSize.width != -1) - mIntrinsicSize.width.SetCoordValue(intrinsicSize.width); - if (intrinsicSize.height != -1) - mIntrinsicSize.height.SetCoordValue(intrinsicSize.height); - } else { - // Failure means that the image hasn't loaded enough to report a result. We - // treat this case as if the image's intrinsic size was 0x0. - mIntrinsicSize.width.SetCoordValue(0); - mIntrinsicSize.height.SetCoordValue(0); + // If broken images should ever lose their size + /* + if (aFrame.ShouldShowBrokenImageIcon()) { + nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( + ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); + intrinsicSize.width.SetCoordValue(edgeLengthToUse); + intrinsicSize.height.SetCoordValue(edgeLengthToUse); + return intrinsicSize; } + */ - return mIntrinsicSize != oldIntrinsicSize; + if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio != 0.0f) { + return IntrinsicSize(); + } + + intrinsicSize.width.SetCoordValue(0); + intrinsicSize.height.SetCoordValue(0); + return intrinsicSize; +} + +// For compat reasons, see bug 1602047, we don't use the intrinsic ratio from +// width="" and height="" for images with no src attribute (no request). +// +// If <img loading=lazy> ever gets implemented, this will need to check for it. +bool nsImageFrame::ShouldUseMappedAspectRatio() const { + nsCOMPtr<imgIRequest> currentRequest; + nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); + if (imageLoader) { + imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(currentRequest)); + } + if (!!currentRequest) { + return true; + } + // TODO(emilio): Investigate the compat situation of the above check, maybe we + // can just check for empty src attribute or something... + auto* image = static_cast<HTMLImageElement*>(mContent); + return image && image->IsAwaitingLoad(); } bool -nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) +nsImageFrame::UpdateIntrinsicSize() { - NS_PRECONDITION(aImage, "null image"); - - if (!aImage) - return false; + IntrinsicSize oldIntrinsicSize = mIntrinsicSize; + mIntrinsicSize = ComputeIntrinsicSize(mImage, ShouldUseMappedAspectRatio(), *this); + return mIntrinsicSize != oldIntrinsicSize; +} - AspectRatio oldIntrinsicRatio = mIntrinsicRatio; +static AspectRatio +ComputeAspectRatio(imgIContainer* aImage, + bool aUseMappedRatio, + const nsImageFrame& aFrame) +{ + // When 'contain: size' is implemented, make sure to check for it. +/* + const ComputedStyle& style = *aFrame.Style(); + if (style.StyleDisplay()->IsContainSize()) { + return AspectRatio(); + } + */ + if (aImage) { + AspectRatio fromImage; + if (NS_SUCCEEDED(aImage->GetIntrinsicRatio(&fromImage))) { + return fromImage; + } + } + if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio != 0.0f) { + return AspectRatio(aFrame.StylePosition()->mAspectRatio); + } + if (aFrame.ShouldShowBrokenImageIcon()) { + return AspectRatio(1.0f); + } + return AspectRatio(); +} - // Set intrinsic ratio to match aImage's reported intrinsic ratio. - if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio))) - mIntrinsicRatio = AspectRatio(); +bool +nsImageFrame::UpdateIntrinsicRatio() +{ + AspectRatio oldIntrinsicRatio = mIntrinsicRatio; + mIntrinsicRatio = + ComputeAspectRatio(mImage, ShouldUseMappedAspectRatio(), *this); return mIntrinsicRatio != oldIntrinsicRatio; } @@ -541,30 +606,38 @@ nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) return NS_OK; } - bool intrinsicSizeChanged = false; + UpdateImage(aRequest, aImage); + return NS_OK; +} + +void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { + MOZ_ASSERT(aRequest); if (SizeIsAvailable(aRequest)) { // This is valid and for the current request, so update our stored image // container, orienting according to our style. - mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation); - - intrinsicSizeChanged = UpdateIntrinsicSize(mImage); - intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged; + mImage = nsLayoutUtils::OrientImage(aImage, + StyleVisibility()->mImageOrientation); + MOZ_ASSERT(mImage); } 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.width.SetCoordValue(0); - mIntrinsicSize.height.SetCoordValue(0); - mIntrinsicRatio = AspectRatio(); - intrinsicSizeChanged = true; } + // NOTE(emilio): Intentionally using `|` instead of `||` to avoid + // short-circuiting. + bool intrinsicSizeChanged = + UpdateIntrinsicSize() | UpdateIntrinsicRatio(); + if (!(mState & IMAGE_GOTINITIALREFLOW)) { + return; + } + + // We're going to need to repaint now either way. + InvalidateFrame(); - if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) { + if (intrinsicSizeChanged) { // Now we need to reflow if we have an unconstrained size and have - // already gotten the initial reflow + // already gotten the initial reflow. if (!(mState & IMAGE_SIZECONSTRAINED)) { - nsIPresShell *presShell = presContext->GetPresShell(); + nsIPresShell *presShell = PresContext()->GetPresShell(); NS_ASSERTION(presShell, "No PresShell."); if (presShell) { presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, @@ -578,8 +651,6 @@ nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) mPrevImage = nullptr; } - - return NS_OK; } nsresult @@ -654,45 +725,9 @@ nsImageFrame::NotifyNewCurrentRequest(imgIRequest *aRequest, { nsCOMPtr<imgIContainer> image; aRequest->GetImage(getter_AddRefs(image)); - NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?"); - - // May have to switch sizes here! - bool intrinsicSizeChanged = true; - if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) { - // Update our stored image container, orienting according to our style. - mImage = nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation); - - 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.width.SetCoordValue(0); - mIntrinsicSize.height.SetCoordValue(0); - mIntrinsicRatio = AspectRatio(); - } - - if (mState & IMAGE_GOTINITIALREFLOW) { // do nothing if we haven't gotten the initial reflow yet - if (intrinsicSizeChanged) { - if (!(mState & IMAGE_SIZECONSTRAINED)) { - nsIPresShell *presShell = PresContext()->GetPresShell(); - if (presShell) { - presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, - NS_FRAME_IS_DIRTY); - } - } else { - // We've already gotten the initial reflow, and our size hasn't changed, - // so we're ready to request a decode. - MaybeDecodeForPredictedSize(); - } - - mPrevImage = nullptr; - } - // Update border+content to account for image change - InvalidateFrame(); - } + NS_ASSERTION(image || NS_FAILED(aStatus), + "Successful load with no container?"); + UpdateImage(aRequest, image); } void @@ -786,32 +821,27 @@ bool nsImageFrame::ShouldShowBrokenImageIcon() const void nsImageFrame::EnsureIntrinsicSizeAndRatio() { + // When 'contain: size' is implemented, make sure to check for it. +/* + 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 = AspectRatio(); + return; + } + */ + // If mIntrinsicSize.width and height are 0, then we need to update from the // image container. - if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && + if (!(mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.width.GetCoordValue() == 0 && mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && - mIntrinsicSize.height.GetCoordValue() == 0) { - - if (mImage) { - UpdateIntrinsicSize(mImage); - UpdateIntrinsicRatio(mImage); - } else { - // Image request is null or image size not known. - if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { - // Likely an invalid image. Check if we should display it as broken. - if (ShouldShowBrokenImageIcon()) { - // Invalid image specified. make the image big enough for the "broken" icon - nscoord edgeLengthToUse = - nsPresContext::CSSPixelsToAppUnits( - ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); - mIntrinsicSize.width.SetCoordValue(edgeLengthToUse); - mIntrinsicSize.height.SetCoordValue(edgeLengthToUse); - mIntrinsicRatio = AspectRatio(1.0f); - } - } - } + mIntrinsicSize.height.GetCoordValue() == 0)) { + return; } + UpdateIntrinsicSize(); + UpdateIntrinsicRatio(); } /* virtual */ diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h index 2414d89df..5e9b67274 100644 --- a/layout/generic/nsImageFrame.h +++ b/layout/generic/nsImageFrame.h @@ -273,21 +273,19 @@ private: void GetDocumentCharacterSet(nsACString& aCharset) const; bool ShouldDisplaySelection(); + // Whether the image frame should use the mapped aspect ratio from width="" + // and height="". + bool ShouldUseMappedAspectRatio() const; + /** * Recalculate mIntrinsicSize from the image. - * - * @return whether aImage's size did _not_ - * match our previous intrinsic size. */ - bool UpdateIntrinsicSize(imgIContainer* aImage); + bool UpdateIntrinsicSize(); /** * Recalculate mIntrinsicRatio from the image. - * - * @return whether aImage's ratio did _not_ - * match our previous intrinsic ratio. */ - bool UpdateIntrinsicRatio(imgIContainer* aImage); + bool UpdateIntrinsicRatio(); /** * This function calculates the transform for converting between @@ -308,6 +306,12 @@ private: bool IsPendingLoad(imgIRequest* aRequest) const; /** + * Updates mImage based on the current image request (cannot be null), and the + * image passed in (can be null), and invalidate layout and paint as needed. + */ + void UpdateImage(imgIRequest* aRequest, imgIContainer* aImage); + + /** * Function to convert a dirty rect in the source image to a dirty * rect for the image frame. */ diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index 411f982a4..44bd44cef 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -470,6 +470,17 @@ CSS_PROP_DISPLAY( kAppearanceKTable, CSS_PROP_NO_OFFSET, eStyleAnimType_Discrete) +CSS_PROP_POSITION( + aspect-ratio, + aspect_ratio, + AspectRatio, + CSS_PROPERTY_INTERNAL | + CSS_PROPERTY_PARSE_INACCESSIBLE, + "", + VARIANT_NUMBER, + nullptr, + offsetof(nsStylePosition, mAspectRatio), + eStyleAnimType_None) CSS_PROP_DISPLAY( backface-visibility, backface_visibility, diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index a0f65c069..1a451a2ef 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -8544,6 +8544,12 @@ nsRuleNode::ComputePositionData(void* aStartStruct, SETCOORD_UNSET_INITIAL, aContext, mPresContext, conditions); + // aspect-ratio: float, initial + SetFactor(*aRuleData->ValueForAspectRatio(), + pos->mAspectRatio, conditions, + parentPos->mAspectRatio, 0.0f, + SETFCT_UNSET_INITIAL | SETFCT_POSITIVE | SETFCT_NONE); + // box-sizing: enum, inherit, initial SetValue(*aRuleData->ValueForBoxSizing(), pos->mBoxSizing, conditions, diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index 9270f2960..3b19a4418 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -1408,6 +1408,7 @@ nsStylePosition::nsStylePosition(StyleStructContext aContext) , mGridAutoColumnsMax(eStyleUnit_Auto) , mGridAutoRowsMin(eStyleUnit_Auto) , mGridAutoRowsMax(eStyleUnit_Auto) + , mAspectRatio(0.0f) , mGridAutoFlow(NS_STYLE_GRID_AUTO_FLOW_ROW) , mBoxSizing(StyleBoxSizing::Content) , mAlignContent(NS_STYLE_ALIGN_NORMAL) @@ -1466,6 +1467,7 @@ nsStylePosition::nsStylePosition(const nsStylePosition& aSource) , mGridAutoColumnsMax(aSource.mGridAutoColumnsMax) , mGridAutoRowsMin(aSource.mGridAutoRowsMin) , mGridAutoRowsMax(aSource.mGridAutoRowsMax) + , mAspectRatio(aSource.mAspectRatio) , mGridAutoFlow(aSource.mGridAutoFlow) , mBoxSizing(aSource.mBoxSizing) , mAlignContent(aSource.mAlignContent) @@ -1636,6 +1638,11 @@ nsStylePosition::CalcDifference(const nsStylePosition& aNewData, if (isVertical ? heightChanged : widthChanged) { hint |= nsChangeHint_ReflowHintsForISizeChange; } + + if (mAspectRatio != aNewData.mAspectRatio) { + hint |= nsChangeHint_ReflowHintsForISizeChange | + nsChangeHint_ReflowHintsForBSizeChange; + } } else { if (widthChanged || heightChanged) { hint |= nsChangeHint_NeutralChange; diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h index b257c6bb5..4bda817dd 100644 --- a/layout/style/nsStyleStruct.h +++ b/layout/style/nsStyleStruct.h @@ -1815,6 +1815,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePosition nsStyleCoord mGridAutoColumnsMax; // [reset] coord, percent, enum, calc, flex nsStyleCoord mGridAutoRowsMin; // [reset] coord, percent, enum, calc, flex nsStyleCoord mGridAutoRowsMax; // [reset] coord, percent, enum, calc, flex + float mAspectRatio; // [reset] float uint8_t mGridAutoFlow; // [reset] enumerated. See nsStyleConsts.h mozilla::StyleBoxSizing mBoxSizing; // [reset] see nsStyleConsts.h diff --git a/layout/style/test/ListCSSProperties.cpp b/layout/style/test/ListCSSProperties.cpp index 718032f61..9f727104b 100644 --- a/layout/style/test/ListCSSProperties.cpp +++ b/layout/style/test/ListCSSProperties.cpp @@ -106,6 +106,7 @@ const char *gInaccessibleProperties[] = { "-x-span", "-x-system-font", "-x-text-zoom", + "aspect-ratio", // for now. "-moz-control-character-visibility", "-moz-script-level", // parsed by UA sheets only "-moz-script-size-multiplier", diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 97070c23a..25e487206 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4809,6 +4809,12 @@ pref("media.ondevicechange.fakeDeviceChangeEvent.enabled", false); // those platforms we don't handle touch events anyway so it's conceptually // a no-op. pref("layout.css.touch_action.enabled", true); + +// WHATWG computed intrinsic aspect ratio for an img element +// https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images +// Are the width and height attributes on image-like elements mapped to the +// internal-for-now aspect-ratio property? +pref("layout.css.width-and-height-map-to-aspect-ratio.enabled", false); // Enables some assertions in nsStyleContext that are too expensive // for general use, but might be useful to enable for specific tests. diff --git a/testing/web-platform/tests/css-backgrounds/background-size-cover-003-ref.html b/testing/web-platform/tests/css-backgrounds/background-size-cover-003-ref.html new file mode 100644 index 000000000..bd965cfec --- /dev/null +++ b/testing/web-platform/tests/css-backgrounds/background-size-cover-003-ref.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>CSS Test Reference</title> +<style> +body { margin: 0 } +.first { + width: 100px; + height: 50px; + background: lime; +} +.space { + height: 50px; +} +.second { + width: 100px; + height: 100px; + background: lime; +} +</style> +<div class="first"></div> +<div class="space"></div> +<div class="second"></div> diff --git a/testing/web-platform/tests/css-backgrounds/background-size-cover-003.html b/testing/web-platform/tests/css-backgrounds/background-size-cover-003.html new file mode 100644 index 000000000..4d2b6b125 --- /dev/null +++ b/testing/web-platform/tests/css-backgrounds/background-size-cover-003.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>CSS Test: background-size: cover with zero-sized background positioning area.</title> +<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#valdef-background-size-cover"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/4049"> +<link rel="help" href=" https://bugzilla.mozilla.org/show_bug.cgi?id=1559094"> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<link rel="author" href="https://mozilla.org" title="Mozilla"> +<link rel="match" href="background-size-cover-003-ref.html"> +<style> +body { margin: 0 } +div { + background-size: cover; + background-repeat: no-repeat; + background-position: top left; + background-origin: content-box; + background-image: url(/images/green-100x50.png); +} +#test1 { + height: 0; + width: 100px; + padding-bottom: 100px; +} + +#test2 { + height: 100px; + width: 0; + padding-right: 100px; +} +#test3 { + height: 0; + width: 0; + padding-right: 100px; + padding-bottom: 100px; +} +</style> +<div id="test1"></div> +<div id="test2"></div> +<div id="test3"></div> diff --git a/testing/web-platform/tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html b/testing/web-platform/tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html new file mode 100644 index 000000000..9ae87be9c --- /dev/null +++ b/testing/web-platform/tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html @@ -0,0 +1,60 @@ +<!doctype html> +<title>Image width and height attributes are used to infer aspect-ratio</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + img { + width: 100%; + max-width: 100px; + height: auto; + } +</style> +<img src="/images/green.png"> +<img src="/images/green.png" width=100 height=125> +<img src="" width=100 height=125> +<img src="error.png" width=100 height=125> +<img src="error.png"> +<script> +let t = async_test("Image width and height attributes are used to infer aspect-ratio"); +function assert_ratio(img, expected) { + let epsilon = 0.001; + assert_approx_equals(parseFloat(getComputedStyle(img).width, 10) / parseFloat(getComputedStyle(img).height, 10), expected, epsilon); +} +// Create and append a new image and immediately check the ratio. +// This is not racy because the spec requires the user agent to queue a task: +// https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data +t.step(function() { + var img = new Image(); + img.width = 250; + img.height = 100; + img.src = "/images/blue.png"; + document.body.appendChild(img); + assert_ratio(img, 2.5); + + img = new Image(); + img.setAttribute("width", "0.8"); + img.setAttribute("height", "0.2"); + img.src = "/images/blue.png"; + document.body.appendChild(img); + // Decimals are apparently ignored? + assert_equals(getComputedStyle(img).height, "0px"); + + img = new Image(); + img.setAttribute("width", "50%"); + img.setAttribute("height", "25%"); + img.src = "/images/blue.png"; + document.body.appendChild(img); + // Percentages should be ignored. + assert_equals(getComputedStyle(img).height, "0px"); +}); + +onload = t.step_func_done(function() { + let images = document.querySelectorAll("img"); + assert_ratio(images[0], 2.0); // Loaded image's aspect ratio, at least by default, overrides width / height ratio. + assert_ratio(images[1], 2.0); // 2.0 is the original aspect ratio of green.png + assert_equals(getComputedStyle(images[2]).height, "0px"); // aspect-ratio doesn't override intrinsic size of images that don't have any src. + assert_equals(getComputedStyle(images[3]).height, "125px"); // what intrinsic size? + assert_equals(getComputedStyle(images[4]).height, "100px"); // what aspect ratio? + assert_ratio(images[5], 133/106); // The original aspect ratio of blue.png +}); +</script> |