1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SVGImageFrame.h"
9 // Keep in (case-insensitive) order:
10 #include "gfxContext.h"
11 #include "gfxPlatform.h"
12 #include "mozilla/ComputedStyleInlines.h"
13 #include "mozilla/image/WebRenderImageProvider.h"
14 #include "mozilla/layers/RenderRootStateManager.h"
15 #include "mozilla/layers/WebRenderLayerManager.h"
16 #include "imgIContainer.h"
17 #include "ImageRegion.h"
18 #include "nsContainerFrame.h"
19 #include "nsIImageLoadingContent.h"
20 #include "nsLayoutUtils.h"
21 #include "imgINotificationObserver.h"
22 #include "SVGGeometryProperty.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/StaticPrefs_image.h"
25 #include "mozilla/SVGContentUtils.h"
26 #include "mozilla/SVGImageContext.h"
27 #include "mozilla/SVGObserverUtils.h"
28 #include "mozilla/SVGUtils.h"
29 #include "mozilla/dom/MutationEventBinding.h"
30 #include "mozilla/dom/SVGImageElement.h"
31 #include "mozilla/dom/LargestContentfulPaint.h"
32 #include "nsIReflowCallback.h"
34 using namespace mozilla::dom
;
35 using namespace mozilla::gfx
;
36 using namespace mozilla::image
;
37 using namespace mozilla::dom::SVGPreserveAspectRatio_Binding
;
38 namespace SVGT
= SVGGeometryProperty::Tags
;
42 class SVGImageListener final
: public imgINotificationObserver
{
44 explicit SVGImageListener(SVGImageFrame
* aFrame
);
47 NS_DECL_IMGINOTIFICATIONOBSERVER
49 void SetFrame(SVGImageFrame
* frame
) { mFrame
= frame
; }
52 ~SVGImageListener() = default;
54 SVGImageFrame
* mFrame
;
57 // ---------------------------------------------------------------------
58 // nsQueryFrame methods
60 NS_QUERYFRAME_HEAD(SVGImageFrame
)
61 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame
)
62 NS_QUERYFRAME_ENTRY(SVGImageFrame
)
63 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame
)
65 } // namespace mozilla
67 nsIFrame
* NS_NewSVGImageFrame(mozilla::PresShell
* aPresShell
,
68 mozilla::ComputedStyle
* aStyle
) {
69 return new (aPresShell
)
70 mozilla::SVGImageFrame(aStyle
, aPresShell
->GetPresContext());
75 NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame
)
77 SVGImageFrame::~SVGImageFrame() {
78 // set the frame to null so we don't send messages to a dead object.
80 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
81 do_QueryInterface(GetContent());
83 imageLoader
->RemoveNativeObserver(mListener
);
85 reinterpret_cast<SVGImageListener
*>(mListener
.get())->SetFrame(nullptr);
90 void SVGImageFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
91 nsIFrame
* aPrevInFlow
) {
92 NS_ASSERTION(aContent
->IsSVGElement(nsGkAtoms::image
),
93 "Content is not an SVG image!");
95 AddStateBits(aParent
->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD
);
96 nsIFrame::Init(aContent
, aParent
, aPrevInFlow
);
98 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
99 // Non-display frames are likely to be patterns, masks or the like.
100 // Treat them as always visible.
101 // This call must happen before the FrameCreated. This is because the
102 // primary frame pointer on our content node isn't set until after this
103 // function ends, so there is no way for the resulting OnVisibilityChange
104 // notification to get a frame. FrameCreated has a workaround for this in
105 // that it passes our frame around so it can be accessed. OnVisibilityChange
106 // doesn't have that workaround.
107 IncApproximateVisibleCount();
110 mListener
= new SVGImageListener(this);
111 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
112 do_QueryInterface(GetContent());
114 MOZ_CRASH("Why is this not an image loading content?");
117 // We should have a PresContext now, so let's notify our image loader that
118 // we need to register any image animations with the refresh driver.
119 imageLoader
->FrameCreated(this);
121 imageLoader
->AddNativeObserver(mListener
);
125 void SVGImageFrame::Destroy(DestroyContext
& aContext
) {
126 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
127 DecApproximateVisibleCount();
130 if (mReflowCallbackPosted
) {
131 PresShell()->CancelReflowCallback(this);
132 mReflowCallbackPosted
= false;
135 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
138 imageLoader
->FrameDestroyed(this);
141 nsIFrame::Destroy(aContext
);
145 void SVGImageFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
146 nsIFrame::DidSetComputedStyle(aOldStyle
);
148 if (!mImageContainer
|| !aOldStyle
) {
152 nsCOMPtr
<imgIRequest
> currentRequest
;
153 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
154 do_QueryInterface(GetContent());
156 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
157 getter_AddRefs(currentRequest
));
160 StyleImageOrientation newOrientation
=
161 StyleVisibility()->UsedImageOrientation(currentRequest
);
162 StyleImageOrientation oldOrientation
=
163 aOldStyle
->StyleVisibility()->UsedImageOrientation(currentRequest
);
165 if (oldOrientation
!= newOrientation
) {
166 nsCOMPtr
<imgIContainer
> image(mImageContainer
->Unwrap());
167 mImageContainer
= nsLayoutUtils::OrientImage(image
, newOrientation
);
170 // TODO(heycam): We should handle aspect-ratio, like nsImageFrame does.
173 bool SVGImageFrame::IsSVGTransformed(gfx::Matrix
* aOwnTransform
,
174 gfx::Matrix
* aFromParentTransform
) const {
175 return SVGUtils::IsSVGTransformed(this, aOwnTransform
, aFromParentTransform
);
178 //----------------------------------------------------------------------
181 nsresult
SVGImageFrame::AttributeChanged(int32_t aNameSpaceID
,
182 nsAtom
* aAttribute
, int32_t aModType
) {
183 if (aNameSpaceID
== kNameSpaceID_None
) {
184 if (aAttribute
== nsGkAtoms::preserveAspectRatio
) {
185 // We don't paint the content of the image using display lists, therefore
186 // we have to invalidate for this children-only transform changes since
187 // there is no layer tree to notice that the transform changed and
194 // Currently our SMIL implementation does not modify the DOM attributes. Once
195 // we implement the SVG 2 SMIL behaviour this can be removed
196 // SVGImageElement::AfterSetAttr's implementation will be sufficient.
197 if (aModType
== MutationEvent_Binding::SMIL
&&
198 aAttribute
== nsGkAtoms::href
&&
199 (aNameSpaceID
== kNameSpaceID_XLink
||
200 aNameSpaceID
== kNameSpaceID_None
)) {
201 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
204 element
->mStringAttributes
[SVGImageElement::HREF
].IsExplicitlySet() ||
205 element
->mStringAttributes
[SVGImageElement::XLINK_HREF
]
208 element
->LoadSVGImage(true, true);
210 element
->CancelImageRequests(true);
217 void SVGImageFrame::OnVisibilityChange(
218 Visibility aNewVisibility
, const Maybe
<OnNonvisible
>& aNonvisibleAction
) {
219 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
220 do_QueryInterface(GetContent());
222 imageLoader
->OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
225 nsIFrame::OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
228 gfx::Matrix
SVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth
,
229 int32_t aNativeHeight
) {
230 float x
, y
, width
, height
;
231 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
232 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
233 element
, &x
, &y
, &width
, &height
);
235 Matrix viewBoxTM
= SVGContentUtils::GetViewBoxTransform(
236 width
, height
, 0, 0, aNativeWidth
, aNativeHeight
,
237 element
->mPreserveAspectRatio
);
239 return viewBoxTM
* gfx::Matrix::Translation(x
, y
);
242 gfx::Matrix
SVGImageFrame::GetVectorImageTransform() {
244 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
245 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
>(element
, &x
, &y
);
247 // No viewBoxTM needed here -- our height/width overrides any concept of
248 // "native size" that the SVG image has, and it will handle viewBox and
249 // preserveAspectRatio on its own once we give it a region to draw into.
251 return gfx::Matrix::Translation(x
, y
);
254 bool SVGImageFrame::GetIntrinsicImageDimensions(
255 mozilla::gfx::Size
& aSize
, mozilla::AspectRatio
& aAspectRatio
) const {
256 if (!mImageContainer
) {
260 ImageResolution resolution
= mImageContainer
->GetResolution();
262 int32_t width
, height
;
263 if (NS_FAILED(mImageContainer
->GetWidth(&width
))) {
267 resolution
.ApplyXTo(aSize
.width
);
270 if (NS_FAILED(mImageContainer
->GetHeight(&height
))) {
273 aSize
.height
= height
;
274 resolution
.ApplyYTo(aSize
.height
);
277 Maybe
<AspectRatio
> asp
= mImageContainer
->GetIntrinsicRatio();
278 aAspectRatio
= asp
.valueOr(AspectRatio
{});
283 bool SVGImageFrame::TransformContextForPainting(gfxContext
* aGfxContext
,
284 const gfxMatrix
& aTransform
) {
285 gfx::Matrix imageTransform
;
286 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
287 imageTransform
= GetVectorImageTransform() * ToMatrix(aTransform
);
289 int32_t nativeWidth
, nativeHeight
;
290 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
291 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
292 nativeWidth
== 0 || nativeHeight
== 0) {
295 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
296 imageTransform
= GetRasterImageTransform(nativeWidth
, nativeHeight
) *
297 ToMatrix(aTransform
);
299 // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
300 // it'll get applied an extra time by DrawSingleUnscaledImage.
301 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
302 gfxFloat pageZoomFactor
=
303 nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx
);
304 imageTransform
.PreScale(pageZoomFactor
, pageZoomFactor
);
307 if (imageTransform
.IsSingular()) {
311 aGfxContext
->Multiply(ThebesMatrix(imageTransform
));
315 //----------------------------------------------------------------------
316 // ISVGDisplayableFrame methods
318 void SVGImageFrame::PaintSVG(gfxContext
& aContext
, const gfxMatrix
& aTransform
,
319 imgDrawingParams
& aImgParams
) {
320 if (!StyleVisibility()->IsVisible()) {
324 float x
, y
, width
, height
;
325 SVGImageElement
* imgElem
= static_cast<SVGImageElement
*>(GetContent());
326 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
327 imgElem
, &x
, &y
, &width
, &height
);
328 NS_ASSERTION(width
> 0 && height
> 0,
329 "Should only be painting things with valid width/height");
331 if (!mImageContainer
) {
332 nsCOMPtr
<imgIRequest
> currentRequest
;
333 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
334 do_QueryInterface(GetContent());
336 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
337 getter_AddRefs(currentRequest
));
340 currentRequest
->GetImage(getter_AddRefs(mImageContainer
));
343 if (mImageContainer
) {
344 gfxClipAutoSaveRestore
autoSaveClip(&aContext
);
346 if (StyleDisplay()->IsScrollableOverflow()) {
348 SVGUtils::GetClipRectForFrame(this, x
, y
, width
, height
);
349 autoSaveClip
.TransformedClip(aTransform
, clipRect
);
352 gfxContextMatrixAutoSaveRestore
autoSaveMatrix(&aContext
);
354 if (!TransformContextForPainting(&aContext
, aTransform
)) {
358 // fill-opacity doesn't affect <image>, so if we're allowed to
359 // optimize group opacity, the opacity used for compositing the
360 // image into the current canvas is just the group opacity.
361 float opacity
= 1.0f
;
362 if (SVGUtils::CanOptimizeOpacity(this)) {
363 opacity
= StyleEffects()->mOpacity
;
366 gfxGroupForBlendAutoSaveRestore
autoGroupForBlend(&aContext
);
367 if (opacity
!= 1.0f
|| StyleEffects()->HasMixBlendMode()) {
368 autoGroupForBlend
.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA
,
372 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
373 uint32_t flags
= aImgParams
.imageFlags
;
374 if (mForceSyncDecoding
) {
375 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
378 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
379 // Package up the attributes of this image element which can override the
380 // attributes of mImageContainer's internal SVG document. The 'width' &
381 // 'height' values we're passing in here are in CSS units (though they
382 // come from width/height *attributes* in SVG). They influence the region
383 // of the SVG image's internal document that is visible, in combination
384 // with preserveAspectRatio and viewBox.
385 const SVGImageContext
context(
386 Some(CSSIntSize::Ceil(width
, height
)),
387 Some(imgElem
->mPreserveAspectRatio
.GetAnimValue()));
389 // For the actual draw operation to draw crisply (and at the right size),
390 // our destination rect needs to be |width|x|height|, *in dev pixels*.
391 LayoutDeviceSize
devPxSize(width
, height
);
392 nsRect
destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(
393 devPxSize
, appUnitsPerDevPx
));
394 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
395 if (currentRequest
) {
396 LCPHelpers::FinalizeLCPEntryForImage(
397 GetContent()->AsElement(),
398 static_cast<imgRequestProxy
*>(currentRequest
.get()), destRect
);
401 // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
402 // That method needs our image to have a fixed native width & height,
403 // and that's not always true for TYPE_VECTOR images.
404 aImgParams
.result
&= nsLayoutUtils::DrawSingleImage(
405 aContext
, PresContext(), mImageContainer
,
406 nsLayoutUtils::GetSamplingFilterForFrame(this), destRect
, destRect
,
408 } else { // mImageContainer->GetType() == TYPE_RASTER
409 aImgParams
.result
&= nsLayoutUtils::DrawSingleUnscaledImage(
410 aContext
, PresContext(), mImageContainer
,
411 nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0),
412 nullptr, SVGImageContext(), flags
);
417 void SVGImageFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
418 const nsDisplayListSet
& aLists
) {
419 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
423 if (aBuilder
->IsForPainting()) {
424 if (!IsVisibleForPainting()) {
427 if (StyleEffects()->IsTransparent()) {
430 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
431 aLists
.BorderBackground());
434 DisplayOutline(aBuilder
, aLists
);
435 aLists
.Content()->AppendNewToTop
<DisplaySVGImage
>(aBuilder
, this);
438 bool SVGImageFrame::IsInvisible() const {
439 if (!StyleVisibility()->IsVisible()) {
443 // Anything below will round to zero later down the pipeline.
444 constexpr float opacity_threshold
= 1.0 / 128.0;
446 return StyleEffects()->mOpacity
<= opacity_threshold
;
449 bool SVGImageFrame::CreateWebRenderCommands(
450 mozilla::wr::DisplayListBuilder
& aBuilder
,
451 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
452 const mozilla::layers::StackingContextHelper
& aSc
,
453 mozilla::layers::RenderRootStateManager
* aManager
,
454 nsDisplayListBuilder
* aDisplayListBuilder
, DisplaySVGImage
* aItem
,
456 if (!StyleVisibility()->IsVisible()) {
460 float opacity
= 1.0f
;
461 if (SVGUtils::CanOptimizeOpacity(this)) {
462 opacity
= StyleEffects()->mOpacity
;
465 if (opacity
!= 1.0f
) {
466 // FIXME: not implemented, might be trivial
469 if (StyleEffects()->HasMixBlendMode()) {
470 // FIXME: not implemented
474 // try to setup the image
475 if (!mImageContainer
) {
476 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
477 if (currentRequest
) {
478 currentRequest
->GetImage(getter_AddRefs(mImageContainer
));
482 if (!mImageContainer
) {
483 // nothing to draw (yet)
487 uint32_t flags
= aDisplayListBuilder
->GetImageDecodeFlags();
488 if (mForceSyncDecoding
) {
489 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
492 // Compute bounds of the image
493 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
494 int32_t appUnitsPerCSSPixel
= AppUnitsPerCSSPixel();
496 float x
, y
, width
, height
;
497 SVGImageElement
* imgElem
= static_cast<SVGImageElement
*>(GetContent());
498 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
499 imgElem
, &x
, &y
, &width
, &height
);
500 NS_ASSERTION(width
> 0 && height
> 0,
501 "Should only be painting things with valid width/height");
503 auto toReferenceFrame
= aItem
->ToReferenceFrame();
504 auto appRect
= nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width
, height
),
505 appUnitsPerCSSPixel
);
506 appRect
+= toReferenceFrame
;
507 auto destRect
= LayoutDeviceRect::FromAppUnits(appRect
, appUnitsPerDevPx
);
508 auto clipRect
= destRect
;
510 if (StyleDisplay()->IsScrollableOverflow()) {
511 // Apply potential non-trivial clip
512 auto cssClip
= SVGUtils::GetClipRectForFrame(this, 0, 0, width
, height
);
514 nsLayoutUtils::RoundGfxRectToAppRect(cssClip
, appUnitsPerCSSPixel
);
515 appClip
+= toReferenceFrame
;
516 clipRect
= LayoutDeviceRect::FromAppUnits(appClip
, appUnitsPerDevPx
);
518 // Apply preserveAspectRatio
519 if (mImageContainer
->GetType() == imgIContainer::TYPE_RASTER
) {
520 int32_t nativeWidth
, nativeHeight
;
521 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
522 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
523 nativeWidth
== 0 || nativeHeight
== 0) {
524 // Image has no size; nothing to draw
528 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
530 auto preserveAspectRatio
= imgElem
->mPreserveAspectRatio
.GetAnimValue();
531 uint16_t align
= preserveAspectRatio
.GetAlign();
532 uint16_t meetOrSlice
= preserveAspectRatio
.GetMeetOrSlice();
534 // default to the defaults
535 if (align
== SVG_PRESERVEASPECTRATIO_UNKNOWN
) {
536 align
= SVG_PRESERVEASPECTRATIO_XMIDYMID
;
538 if (meetOrSlice
== SVG_MEETORSLICE_UNKNOWN
) {
539 meetOrSlice
= SVG_MEETORSLICE_MEET
;
542 // aspect > 1 is horizontal
543 // aspect < 1 is vertical
544 float nativeAspect
= ((float)nativeWidth
) / ((float)nativeHeight
);
545 float viewAspect
= width
/ height
;
547 // "Meet" is "fit image to view"; "Slice" is "cover view with image".
549 // Whether we meet or slice, one side of the destRect will always be
550 // perfectly spanned by our image. The only questions to answer are
551 // "which side won't span perfectly" and "should that side be grown
554 // Because we fit our image to the destRect, this all just reduces to:
555 // "if meet, shrink to fit. if slice, grow to fit."
556 if (align
!= SVG_PRESERVEASPECTRATIO_NONE
&& nativeAspect
!= viewAspect
) {
557 // Slightly redundant bools, but they make the conditions clearer
558 bool tooTall
= nativeAspect
> viewAspect
;
559 bool tooWide
= nativeAspect
< viewAspect
;
560 if ((meetOrSlice
== SVG_MEETORSLICE_MEET
&& tooTall
) ||
561 (meetOrSlice
== SVG_MEETORSLICE_SLICE
&& tooWide
)) {
562 // Adjust height and realign y
563 auto oldHeight
= destRect
.height
;
564 destRect
.height
= destRect
.width
/ nativeAspect
;
565 auto heightChange
= oldHeight
- destRect
.height
;
567 case SVG_PRESERVEASPECTRATIO_XMINYMIN
:
568 case SVG_PRESERVEASPECTRATIO_XMIDYMIN
:
569 case SVG_PRESERVEASPECTRATIO_XMAXYMIN
:
570 // align to top (no-op)
572 case SVG_PRESERVEASPECTRATIO_XMINYMID
:
573 case SVG_PRESERVEASPECTRATIO_XMIDYMID
:
574 case SVG_PRESERVEASPECTRATIO_XMAXYMID
:
576 destRect
.y
+= heightChange
/ 2.0f
;
578 case SVG_PRESERVEASPECTRATIO_XMINYMAX
:
579 case SVG_PRESERVEASPECTRATIO_XMIDYMAX
:
580 case SVG_PRESERVEASPECTRATIO_XMAXYMAX
:
582 destRect
.y
+= heightChange
;
585 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
587 } else if ((meetOrSlice
== SVG_MEETORSLICE_MEET
&& tooWide
) ||
588 (meetOrSlice
== SVG_MEETORSLICE_SLICE
&& tooTall
)) {
589 // Adjust width and realign x
590 auto oldWidth
= destRect
.width
;
591 destRect
.width
= destRect
.height
* nativeAspect
;
592 auto widthChange
= oldWidth
- destRect
.width
;
594 case SVG_PRESERVEASPECTRATIO_XMINYMIN
:
595 case SVG_PRESERVEASPECTRATIO_XMINYMID
:
596 case SVG_PRESERVEASPECTRATIO_XMINYMAX
:
597 // align to left (no-op)
599 case SVG_PRESERVEASPECTRATIO_XMIDYMIN
:
600 case SVG_PRESERVEASPECTRATIO_XMIDYMID
:
601 case SVG_PRESERVEASPECTRATIO_XMIDYMAX
:
603 destRect
.x
+= widthChange
/ 2.0f
;
605 case SVG_PRESERVEASPECTRATIO_XMAXYMIN
:
606 case SVG_PRESERVEASPECTRATIO_XMAXYMID
:
607 case SVG_PRESERVEASPECTRATIO_XMAXYMAX
:
609 destRect
.x
+= widthChange
;
612 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
619 SVGImageContext svgContext
;
620 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
621 if (StaticPrefs::image_svg_blob_image()) {
622 flags
|= imgIContainer::FLAG_RECORD_BLOB
;
624 // Forward preserveAspectRatio to inner SVGs
625 svgContext
.SetViewportSize(Some(CSSIntSize::Ceil(width
, height
)));
626 svgContext
.SetPreserveAspectRatio(
627 Some(imgElem
->mPreserveAspectRatio
.GetAnimValue()));
630 Maybe
<ImageIntRegion
> region
;
631 IntSize decodeSize
= nsLayoutUtils::ComputeImageContainerDrawingParameters(
632 mImageContainer
, this, destRect
, clipRect
, aSc
, flags
, svgContext
,
635 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
636 LCPHelpers::FinalizeLCPEntryForImage(
637 GetContent()->AsElement(),
638 static_cast<imgRequestProxy
*>(currentRequest
.get()),
639 LayoutDeviceRect::ToAppUnits(destRect
, appUnitsPerDevPx
) -
643 RefPtr
<image::WebRenderImageProvider
> provider
;
644 ImgDrawResult drawResult
= mImageContainer
->GetImageProvider(
645 aManager
->LayerManager(), decodeSize
, svgContext
, region
, flags
,
646 getter_AddRefs(provider
));
648 // While we got a container, it may not contain a fully decoded surface. If
649 // that is the case, and we have an image we were previously displaying which
650 // has a fully decoded surface, then we should prefer the previous image.
651 switch (drawResult
) {
652 case ImgDrawResult::NOT_READY
:
653 case ImgDrawResult::TEMPORARY_ERROR
:
654 // nothing to draw (yet)
656 case ImgDrawResult::NOT_SUPPORTED
:
657 // things we haven't implemented for WR yet
660 // image is ready to draw
664 // Don't do any actual mutations to state if we're doing a dry run
665 // (used to decide if we're making this into an active layer)
667 // If the image container is empty, we don't want to fallback. Any other
668 // failure will be due to resource constraints and fallback is unlikely to
669 // help us. Hence we can ignore the return value from PushImage.
671 aManager
->CommandBuilder().PushImageProvider(aItem
, provider
, drawResult
,
672 aBuilder
, aResources
,
680 nsIFrame
* SVGImageFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
681 if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) && IgnoreHitTest()) {
686 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
687 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
688 element
, &rect
.x
, &rect
.y
, &rect
.width
, &rect
.height
);
690 if (!rect
.Contains(ToPoint(aPoint
))) {
694 // Special case for raster images -- we only want to accept points that fall
695 // in the underlying image's (scaled to fit) native bounds. That region
696 // doesn't necessarily map to our <image> element's [x,y,width,height] if the
697 // raster image's aspect ratio is being preserved. We have to look up the
698 // native image size & our viewBox transform in order to filter out points
699 // that fall outside that area. (This special case doesn't apply to vector
700 // images because they don't limit their drawing to explicit "native
701 // bounds" -- they have an infinite canvas on which to place content.)
702 if (StyleDisplay()->IsScrollableOverflow() && mImageContainer
) {
703 if (mImageContainer
->GetType() == imgIContainer::TYPE_RASTER
) {
704 int32_t nativeWidth
, nativeHeight
;
705 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
706 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
707 nativeWidth
== 0 || nativeHeight
== 0) {
710 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
711 Matrix viewBoxTM
= SVGContentUtils::GetViewBoxTransform(
712 rect
.width
, rect
.height
, 0, 0, nativeWidth
, nativeHeight
,
713 element
->mPreserveAspectRatio
);
714 if (!SVGUtils::HitTestRect(viewBoxTM
, 0, 0, nativeWidth
, nativeHeight
,
715 aPoint
.x
- rect
.x
, aPoint
.y
- rect
.y
)) {
724 void SVGImageFrame::ReflowSVG() {
725 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
726 "This call is probably a wasteful mistake");
728 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
729 "ReflowSVG mechanism not designed for this");
731 if (!SVGUtils::NeedsReflowSVG(this)) {
735 float x
, y
, width
, height
;
736 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
737 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
738 element
, &x
, &y
, &width
, &height
);
740 Rect
extent(x
, y
, width
, height
);
742 if (!extent
.IsEmpty()) {
743 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
, AppUnitsPerCSSPixel());
748 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
749 // Make sure we have our filter property (if any) before calling
750 // FinishAndStoreOverflow (subsequent filter changes are handled off
751 // nsChangeHint_UpdateEffects):
752 SVGObserverUtils::UpdateEffects(this);
754 if (!mReflowCallbackPosted
) {
755 mReflowCallbackPosted
= true;
756 PresShell()->PostReflowCallback(this);
760 nsRect overflow
= nsRect(nsPoint(0, 0), mRect
.Size());
761 OverflowAreas
overflowAreas(overflow
, overflow
);
762 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
764 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
765 NS_FRAME_HAS_DIRTY_CHILDREN
);
767 // Invalidate, but only if this is not our first reflow (since if it is our
768 // first reflow then we haven't had our first paint yet).
769 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
774 bool SVGImageFrame::ReflowFinished() {
775 mReflowCallbackPosted
= false;
777 // XXX(seth): We don't need this. The purpose of updating visibility
778 // synchronously is to ensure that animated images start animating
779 // immediately. In the short term, however,
780 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
781 // animations start as soon as the image is painted for the first time, and in
782 // the long term we want to update visibility information from the display
783 // list whenever we paint, so we don't actually need to do this. However, to
784 // avoid behavior changes during the transition from the old image visibility
785 // code, we'll leave it in for now.
786 UpdateVisibilitySynchronously();
791 void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted
= false; }
793 already_AddRefed
<imgIRequest
> SVGImageFrame::GetCurrentRequest() const {
794 nsCOMPtr
<imgIRequest
> request
;
795 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
796 do_QueryInterface(GetContent());
798 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
799 getter_AddRefs(request
));
801 return request
.forget();
804 bool SVGImageFrame::IgnoreHitTest() const {
805 switch (Style()->PointerEvents()) {
806 case StylePointerEvents::None
:
808 case StylePointerEvents::Visiblepainted
:
809 case StylePointerEvents::Auto
:
810 if (StyleVisibility()->IsVisible()) {
811 /* XXX: should check pixel transparency */
815 case StylePointerEvents::Visiblefill
:
816 case StylePointerEvents::Visiblestroke
:
817 case StylePointerEvents::Visible
:
818 if (StyleVisibility()->IsVisible()) {
822 case StylePointerEvents::Painted
:
823 /* XXX: should check pixel transparency */
825 case StylePointerEvents::Fill
:
826 case StylePointerEvents::Stroke
:
827 case StylePointerEvents::All
:
830 NS_ERROR("not reached");
837 void SVGImageFrame::NotifySVGChanged(uint32_t aFlags
) {
838 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
839 "Invalidation logic may need adjusting");
842 SVGBBox
SVGImageFrame::GetBBoxContribution(const Matrix
& aToBBoxUserspace
,
844 if (aToBBoxUserspace
.IsSingular()) {
845 // XXX ReportToConsole
849 if ((aFlags
& SVGUtils::eForGetClientRects
) &&
850 aToBBoxUserspace
.PreservesAxisAlignedRectangles()) {
851 Rect rect
= NSRectToRect(mRect
, AppUnitsPerCSSPixel());
852 return aToBBoxUserspace
.TransformBounds(rect
);
855 auto* element
= static_cast<SVGImageElement
*>(GetContent());
857 return element
->GeometryBounds(aToBBoxUserspace
);
860 //----------------------------------------------------------------------
861 // SVGImageListener implementation
863 NS_IMPL_ISUPPORTS(SVGImageListener
, imgINotificationObserver
)
865 SVGImageListener::SVGImageListener(SVGImageFrame
* aFrame
) : mFrame(aFrame
) {}
867 void SVGImageListener::Notify(imgIRequest
* aRequest
, int32_t aType
,
868 const nsIntRect
* aData
) {
873 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
874 mFrame
->InvalidateFrame();
875 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
877 nsChangeHint_InvalidateRenderingObservers
);
878 SVGUtils::ScheduleReflowSVG(mFrame
);
881 if (aType
== imgINotificationObserver::FRAME_UPDATE
) {
882 // No new dimensions, so we don't need to call
883 // SVGUtils::InvalidateAndScheduleBoundsUpdate.
884 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
886 nsChangeHint_InvalidateRenderingObservers
);
887 mFrame
->InvalidateFrame();
890 if (aType
== imgINotificationObserver::SIZE_AVAILABLE
) {
891 // Called once the resource's dimensions have been obtained.
892 nsCOMPtr
<imgIContainer
> image
;
893 aRequest
->GetImage(getter_AddRefs(image
));
895 StyleImageOrientation orientation
=
896 mFrame
->StyleVisibility()->UsedImageOrientation(aRequest
);
897 image
= nsLayoutUtils::OrientImage(image
, orientation
);
898 image
->SetAnimationMode(mFrame
->PresContext()->ImageAnimationMode());
899 mFrame
->mImageContainer
= std::move(image
);
901 mFrame
->InvalidateFrame();
902 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
904 nsChangeHint_InvalidateRenderingObservers
);
905 SVGUtils::ScheduleReflowSVG(mFrame
);
909 } // namespace mozilla