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::DoGetParentSVGTransforms(
174 gfx::Matrix
* aFromParentTransform
) const {
175 return SVGUtils::GetParentSVGTransforms(this, 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
193 if (aModType
== dom::MutationEvent_Binding::REMOVAL
&&
194 (aNameSpaceID
== kNameSpaceID_None
||
195 aNameSpaceID
== kNameSpaceID_XLink
) &&
196 aAttribute
== nsGkAtoms::href
) {
197 auto* element
= static_cast<SVGImageElement
*>(GetContent());
198 if (aNameSpaceID
== kNameSpaceID_None
||
199 !element
->mStringAttributes
[SVGImageElement::HREF
].IsExplicitlySet()) {
200 mImageContainer
= nullptr;
208 void SVGImageFrame::OnVisibilityChange(
209 Visibility aNewVisibility
, const Maybe
<OnNonvisible
>& aNonvisibleAction
) {
210 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
211 do_QueryInterface(GetContent());
213 imageLoader
->OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
216 nsIFrame::OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
219 gfx::Matrix
SVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth
,
220 int32_t aNativeHeight
) {
221 float x
, y
, width
, height
;
222 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
223 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
224 element
, &x
, &y
, &width
, &height
);
226 Matrix viewBoxTM
= SVGContentUtils::GetViewBoxTransform(
227 width
, height
, 0, 0, aNativeWidth
, aNativeHeight
,
228 element
->mPreserveAspectRatio
);
230 return viewBoxTM
* gfx::Matrix::Translation(x
, y
);
233 gfx::Matrix
SVGImageFrame::GetVectorImageTransform() {
235 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
236 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
>(element
, &x
, &y
);
238 // No viewBoxTM needed here -- our height/width overrides any concept of
239 // "native size" that the SVG image has, and it will handle viewBox and
240 // preserveAspectRatio on its own once we give it a region to draw into.
242 return gfx::Matrix::Translation(x
, y
);
245 bool SVGImageFrame::GetIntrinsicImageDimensions(
246 mozilla::gfx::Size
& aSize
, mozilla::AspectRatio
& aAspectRatio
) const {
247 if (!mImageContainer
) {
251 ImageResolution resolution
= mImageContainer
->GetResolution();
252 int32_t width
, height
;
253 if (NS_FAILED(mImageContainer
->GetWidth(&width
))) {
257 resolution
.ApplyXTo(aSize
.width
);
260 if (NS_FAILED(mImageContainer
->GetHeight(&height
))) {
263 aSize
.height
= height
;
264 resolution
.ApplyYTo(aSize
.height
);
267 aAspectRatio
= mImageContainer
->GetIntrinsicRatio();
271 bool SVGImageFrame::TransformContextForPainting(gfxContext
* aGfxContext
,
272 const gfxMatrix
& aTransform
) {
273 gfx::Matrix imageTransform
;
274 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
275 imageTransform
= GetVectorImageTransform() * ToMatrix(aTransform
);
277 int32_t nativeWidth
, nativeHeight
;
278 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
279 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
280 nativeWidth
== 0 || nativeHeight
== 0) {
283 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
284 imageTransform
= GetRasterImageTransform(nativeWidth
, nativeHeight
) *
285 ToMatrix(aTransform
);
287 // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
288 // it'll get applied an extra time by DrawSingleUnscaledImage.
289 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
290 gfxFloat pageZoomFactor
=
291 nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx
);
292 imageTransform
.PreScale(pageZoomFactor
, pageZoomFactor
);
295 if (imageTransform
.IsSingular()) {
299 aGfxContext
->Multiply(ThebesMatrix(imageTransform
));
303 //----------------------------------------------------------------------
304 // ISVGDisplayableFrame methods
306 void SVGImageFrame::PaintSVG(gfxContext
& aContext
, const gfxMatrix
& aTransform
,
307 imgDrawingParams
& aImgParams
) {
308 if (!StyleVisibility()->IsVisible()) {
312 float x
, y
, width
, height
;
313 SVGImageElement
* imgElem
= static_cast<SVGImageElement
*>(GetContent());
314 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
315 imgElem
, &x
, &y
, &width
, &height
);
316 NS_ASSERTION(width
> 0 && height
> 0,
317 "Should only be painting things with valid width/height");
319 if (!mImageContainer
) {
320 nsCOMPtr
<imgIRequest
> currentRequest
;
321 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
322 do_QueryInterface(GetContent());
324 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
325 getter_AddRefs(currentRequest
));
328 currentRequest
->GetImage(getter_AddRefs(mImageContainer
));
331 if (mImageContainer
) {
332 gfxClipAutoSaveRestore
autoSaveClip(&aContext
);
334 if (StyleDisplay()->IsScrollableOverflow()) {
336 SVGUtils::GetClipRectForFrame(this, x
, y
, width
, height
);
337 autoSaveClip
.TransformedClip(aTransform
, clipRect
);
340 gfxContextMatrixAutoSaveRestore
autoSaveMatrix(&aContext
);
342 if (!TransformContextForPainting(&aContext
, aTransform
)) {
346 // fill-opacity doesn't affect <image>, so if we're allowed to
347 // optimize group opacity, the opacity used for compositing the
348 // image into the current canvas is just the group opacity.
349 float opacity
= 1.0f
;
350 if (SVGUtils::CanOptimizeOpacity(this)) {
351 opacity
= StyleEffects()->mOpacity
;
354 gfxGroupForBlendAutoSaveRestore
autoGroupForBlend(&aContext
);
355 if (opacity
!= 1.0f
|| StyleEffects()->HasMixBlendMode()) {
356 autoGroupForBlend
.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA
,
360 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
361 uint32_t flags
= aImgParams
.imageFlags
;
362 if (mForceSyncDecoding
) {
363 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
366 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
367 // Package up the attributes of this image element which can override the
368 // attributes of mImageContainer's internal SVG document. The 'width' &
369 // 'height' values we're passing in here are in CSS units (though they
370 // come from width/height *attributes* in SVG). They influence the region
371 // of the SVG image's internal document that is visible, in combination
372 // with preserveAspectRatio and viewBox.
373 const SVGImageContext
context(
374 Some(CSSIntSize::Ceil(width
, height
)),
375 Some(imgElem
->mPreserveAspectRatio
.GetAnimValue()));
377 // For the actual draw operation to draw crisply (and at the right size),
378 // our destination rect needs to be |width|x|height|, *in dev pixels*.
379 LayoutDeviceSize
devPxSize(width
, height
);
380 nsRect
destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(
381 devPxSize
, appUnitsPerDevPx
));
382 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
383 if (currentRequest
) {
384 LCPHelpers::FinalizeLCPEntryForImage(
385 GetContent()->AsElement(),
386 static_cast<imgRequestProxy
*>(currentRequest
.get()), destRect
);
389 // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
390 // That method needs our image to have a fixed native width & height,
391 // and that's not always true for TYPE_VECTOR images.
392 aImgParams
.result
&= nsLayoutUtils::DrawSingleImage(
393 aContext
, PresContext(), mImageContainer
,
394 nsLayoutUtils::GetSamplingFilterForFrame(this), destRect
, destRect
,
396 } else { // mImageContainer->GetType() == TYPE_RASTER
397 aImgParams
.result
&= nsLayoutUtils::DrawSingleUnscaledImage(
398 aContext
, PresContext(), mImageContainer
,
399 nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0),
400 nullptr, SVGImageContext(), flags
);
405 void SVGImageFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
406 const nsDisplayListSet
& aLists
) {
407 if (!static_cast<const SVGElement
*>(GetContent())->HasValidDimensions()) {
411 if (aBuilder
->IsForPainting()) {
412 if (!IsVisibleForPainting()) {
415 if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) {
418 aBuilder
->BuildCompositorHitTestInfoIfNeeded(this,
419 aLists
.BorderBackground());
422 DisplayOutline(aBuilder
, aLists
);
423 aLists
.Content()->AppendNewToTop
<DisplaySVGImage
>(aBuilder
, this);
426 bool SVGImageFrame::IsInvisible() const {
427 if (!StyleVisibility()->IsVisible()) {
431 // Anything below will round to zero later down the pipeline.
432 constexpr float opacity_threshold
= 1.0 / 128.0;
434 return StyleEffects()->mOpacity
<= opacity_threshold
&&
435 SVGUtils::CanOptimizeOpacity(this);
438 bool SVGImageFrame::CreateWebRenderCommands(
439 mozilla::wr::DisplayListBuilder
& aBuilder
,
440 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
441 const mozilla::layers::StackingContextHelper
& aSc
,
442 mozilla::layers::RenderRootStateManager
* aManager
,
443 nsDisplayListBuilder
* aDisplayListBuilder
, DisplaySVGImage
* aItem
,
445 if (!StyleVisibility()->IsVisible()) {
449 float opacity
= 1.0f
;
450 if (SVGUtils::CanOptimizeOpacity(this)) {
451 opacity
= StyleEffects()->mOpacity
;
454 if (opacity
!= 1.0f
) {
455 // FIXME: not implemented, might be trivial
458 if (StyleEffects()->HasMixBlendMode()) {
459 // FIXME: not implemented
463 // try to setup the image
464 if (!mImageContainer
) {
465 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
466 if (currentRequest
) {
467 currentRequest
->GetImage(getter_AddRefs(mImageContainer
));
471 if (!mImageContainer
) {
472 // nothing to draw (yet)
476 uint32_t flags
= aDisplayListBuilder
->GetImageDecodeFlags();
477 if (mForceSyncDecoding
) {
478 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
481 // Compute bounds of the image
482 nscoord appUnitsPerDevPx
= PresContext()->AppUnitsPerDevPixel();
483 int32_t appUnitsPerCSSPixel
= AppUnitsPerCSSPixel();
485 float x
, y
, width
, height
;
486 SVGImageElement
* imgElem
= static_cast<SVGImageElement
*>(GetContent());
487 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
488 imgElem
, &x
, &y
, &width
, &height
);
489 NS_ASSERTION(width
> 0 && height
> 0,
490 "Should only be painting things with valid width/height");
492 auto toReferenceFrame
= aItem
->ToReferenceFrame();
493 auto appRect
= nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width
, height
),
494 appUnitsPerCSSPixel
);
495 appRect
+= toReferenceFrame
;
496 auto destRect
= LayoutDeviceRect::FromAppUnits(appRect
, appUnitsPerDevPx
);
497 auto clipRect
= destRect
;
499 if (StyleDisplay()->IsScrollableOverflow()) {
500 // Apply potential non-trivial clip
501 auto cssClip
= SVGUtils::GetClipRectForFrame(this, 0, 0, width
, height
);
503 nsLayoutUtils::RoundGfxRectToAppRect(cssClip
, appUnitsPerCSSPixel
);
504 appClip
+= toReferenceFrame
;
505 clipRect
= LayoutDeviceRect::FromAppUnits(appClip
, appUnitsPerDevPx
);
507 // Apply preserveAspectRatio
508 if (mImageContainer
->GetType() == imgIContainer::TYPE_RASTER
) {
509 int32_t nativeWidth
, nativeHeight
;
510 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
511 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
512 nativeWidth
== 0 || nativeHeight
== 0) {
513 // Image has no size; nothing to draw
517 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
519 auto preserveAspectRatio
= imgElem
->mPreserveAspectRatio
.GetAnimValue();
520 uint16_t align
= preserveAspectRatio
.GetAlign();
521 uint16_t meetOrSlice
= preserveAspectRatio
.GetMeetOrSlice();
523 // default to the defaults
524 if (align
== SVG_PRESERVEASPECTRATIO_UNKNOWN
) {
525 align
= SVG_PRESERVEASPECTRATIO_XMIDYMID
;
527 if (meetOrSlice
== SVG_MEETORSLICE_UNKNOWN
) {
528 meetOrSlice
= SVG_MEETORSLICE_MEET
;
531 // aspect > 1 is horizontal
532 // aspect < 1 is vertical
533 float nativeAspect
= ((float)nativeWidth
) / ((float)nativeHeight
);
534 float viewAspect
= width
/ height
;
536 // "Meet" is "fit image to view"; "Slice" is "cover view with image".
538 // Whether we meet or slice, one side of the destRect will always be
539 // perfectly spanned by our image. The only questions to answer are
540 // "which side won't span perfectly" and "should that side be grown
543 // Because we fit our image to the destRect, this all just reduces to:
544 // "if meet, shrink to fit. if slice, grow to fit."
545 if (align
!= SVG_PRESERVEASPECTRATIO_NONE
&& nativeAspect
!= viewAspect
) {
546 // Slightly redundant bools, but they make the conditions clearer
547 bool tooTall
= nativeAspect
> viewAspect
;
548 bool tooWide
= nativeAspect
< viewAspect
;
549 if ((meetOrSlice
== SVG_MEETORSLICE_MEET
&& tooTall
) ||
550 (meetOrSlice
== SVG_MEETORSLICE_SLICE
&& tooWide
)) {
551 // Adjust height and realign y
552 auto oldHeight
= destRect
.height
;
553 destRect
.height
= destRect
.width
/ nativeAspect
;
554 auto heightChange
= oldHeight
- destRect
.height
;
556 case SVG_PRESERVEASPECTRATIO_XMINYMIN
:
557 case SVG_PRESERVEASPECTRATIO_XMIDYMIN
:
558 case SVG_PRESERVEASPECTRATIO_XMAXYMIN
:
559 // align to top (no-op)
561 case SVG_PRESERVEASPECTRATIO_XMINYMID
:
562 case SVG_PRESERVEASPECTRATIO_XMIDYMID
:
563 case SVG_PRESERVEASPECTRATIO_XMAXYMID
:
565 destRect
.y
+= heightChange
/ 2.0f
;
567 case SVG_PRESERVEASPECTRATIO_XMINYMAX
:
568 case SVG_PRESERVEASPECTRATIO_XMIDYMAX
:
569 case SVG_PRESERVEASPECTRATIO_XMAXYMAX
:
571 destRect
.y
+= heightChange
;
574 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
576 } else if ((meetOrSlice
== SVG_MEETORSLICE_MEET
&& tooWide
) ||
577 (meetOrSlice
== SVG_MEETORSLICE_SLICE
&& tooTall
)) {
578 // Adjust width and realign x
579 auto oldWidth
= destRect
.width
;
580 destRect
.width
= destRect
.height
* nativeAspect
;
581 auto widthChange
= oldWidth
- destRect
.width
;
583 case SVG_PRESERVEASPECTRATIO_XMINYMIN
:
584 case SVG_PRESERVEASPECTRATIO_XMINYMID
:
585 case SVG_PRESERVEASPECTRATIO_XMINYMAX
:
586 // align to left (no-op)
588 case SVG_PRESERVEASPECTRATIO_XMIDYMIN
:
589 case SVG_PRESERVEASPECTRATIO_XMIDYMID
:
590 case SVG_PRESERVEASPECTRATIO_XMIDYMAX
:
592 destRect
.x
+= widthChange
/ 2.0f
;
594 case SVG_PRESERVEASPECTRATIO_XMAXYMIN
:
595 case SVG_PRESERVEASPECTRATIO_XMAXYMID
:
596 case SVG_PRESERVEASPECTRATIO_XMAXYMAX
:
598 destRect
.x
+= widthChange
;
601 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
608 SVGImageContext svgContext
;
609 if (mImageContainer
->GetType() == imgIContainer::TYPE_VECTOR
) {
610 if (StaticPrefs::image_svg_blob_image()) {
611 flags
|= imgIContainer::FLAG_RECORD_BLOB
;
613 // Forward preserveAspectRatio to inner SVGs
614 svgContext
.SetViewportSize(Some(CSSIntSize::Ceil(width
, height
)));
615 svgContext
.SetPreserveAspectRatio(
616 Some(imgElem
->mPreserveAspectRatio
.GetAnimValue()));
619 Maybe
<ImageIntRegion
> region
;
620 IntSize decodeSize
= nsLayoutUtils::ComputeImageContainerDrawingParameters(
621 mImageContainer
, this, destRect
, clipRect
, aSc
, flags
, svgContext
,
624 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
625 LCPHelpers::FinalizeLCPEntryForImage(
626 GetContent()->AsElement(),
627 static_cast<imgRequestProxy
*>(currentRequest
.get()),
628 LayoutDeviceRect::ToAppUnits(destRect
, appUnitsPerDevPx
) -
632 RefPtr
<image::WebRenderImageProvider
> provider
;
633 ImgDrawResult drawResult
= mImageContainer
->GetImageProvider(
634 aManager
->LayerManager(), decodeSize
, svgContext
, region
, flags
,
635 getter_AddRefs(provider
));
637 // While we got a container, it may not contain a fully decoded surface. If
638 // that is the case, and we have an image we were previously displaying which
639 // has a fully decoded surface, then we should prefer the previous image.
640 switch (drawResult
) {
641 case ImgDrawResult::NOT_READY
:
642 case ImgDrawResult::TEMPORARY_ERROR
:
643 // nothing to draw (yet)
645 case ImgDrawResult::NOT_SUPPORTED
:
646 // things we haven't implemented for WR yet
649 // image is ready to draw
653 // Don't do any actual mutations to state if we're doing a dry run
654 // (used to decide if we're making this into an active layer)
656 // If the image container is empty, we don't want to fallback. Any other
657 // failure will be due to resource constraints and fallback is unlikely to
658 // help us. Hence we can ignore the return value from PushImage.
660 aManager
->CommandBuilder().PushImageProvider(aItem
, provider
, drawResult
,
661 aBuilder
, aResources
,
669 nsIFrame
* SVGImageFrame::GetFrameForPoint(const gfxPoint
& aPoint
) {
670 if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD
) && IgnoreHitTest()) {
675 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
676 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
677 element
, &rect
.x
, &rect
.y
, &rect
.width
, &rect
.height
);
679 if (!rect
.Contains(ToPoint(aPoint
))) {
683 // Special case for raster images -- we only want to accept points that fall
684 // in the underlying image's (scaled to fit) native bounds. That region
685 // doesn't necessarily map to our <image> element's [x,y,width,height] if the
686 // raster image's aspect ratio is being preserved. We have to look up the
687 // native image size & our viewBox transform in order to filter out points
688 // that fall outside that area. (This special case doesn't apply to vector
689 // images because they don't limit their drawing to explicit "native
690 // bounds" -- they have an infinite canvas on which to place content.)
691 if (StyleDisplay()->IsScrollableOverflow() && mImageContainer
) {
692 if (mImageContainer
->GetType() == imgIContainer::TYPE_RASTER
) {
693 int32_t nativeWidth
, nativeHeight
;
694 if (NS_FAILED(mImageContainer
->GetWidth(&nativeWidth
)) ||
695 NS_FAILED(mImageContainer
->GetHeight(&nativeHeight
)) ||
696 nativeWidth
== 0 || nativeHeight
== 0) {
699 mImageContainer
->GetResolution().ApplyTo(nativeWidth
, nativeHeight
);
700 Matrix viewBoxTM
= SVGContentUtils::GetViewBoxTransform(
701 rect
.width
, rect
.height
, 0, 0, nativeWidth
, nativeHeight
,
702 element
->mPreserveAspectRatio
);
703 if (!SVGUtils::HitTestRect(viewBoxTM
, 0, 0, nativeWidth
, nativeHeight
,
704 aPoint
.x
- rect
.x
, aPoint
.y
- rect
.y
)) {
713 void SVGImageFrame::ReflowSVG() {
714 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
715 "This call is probably a wasteful mistake");
717 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
),
718 "ReflowSVG mechanism not designed for this");
720 if (!SVGUtils::NeedsReflowSVG(this)) {
724 float x
, y
, width
, height
;
725 SVGImageElement
* element
= static_cast<SVGImageElement
*>(GetContent());
726 SVGGeometryProperty::ResolveAll
<SVGT::X
, SVGT::Y
, SVGT::Width
, SVGT::Height
>(
727 element
, &x
, &y
, &width
, &height
);
729 Rect
extent(x
, y
, width
, height
);
731 if (!extent
.IsEmpty()) {
732 mRect
= nsLayoutUtils::RoundGfxRectToAppRect(extent
, AppUnitsPerCSSPixel());
737 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
738 // Make sure we have our filter property (if any) before calling
739 // FinishAndStoreOverflow (subsequent filter changes are handled off
740 // nsChangeHint_UpdateEffects):
741 SVGObserverUtils::UpdateEffects(this);
743 if (!mReflowCallbackPosted
) {
744 mReflowCallbackPosted
= true;
745 PresShell()->PostReflowCallback(this);
749 nsRect overflow
= nsRect(nsPoint(0, 0), mRect
.Size());
750 OverflowAreas
overflowAreas(overflow
, overflow
);
751 FinishAndStoreOverflow(overflowAreas
, mRect
.Size());
753 RemoveStateBits(NS_FRAME_FIRST_REFLOW
| NS_FRAME_IS_DIRTY
|
754 NS_FRAME_HAS_DIRTY_CHILDREN
);
756 // Invalidate, but only if this is not our first reflow (since if it is our
757 // first reflow then we haven't had our first paint yet).
758 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
763 bool SVGImageFrame::ReflowFinished() {
764 mReflowCallbackPosted
= false;
766 // XXX(seth): We don't need this. The purpose of updating visibility
767 // synchronously is to ensure that animated images start animating
768 // immediately. In the short term, however,
769 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
770 // animations start as soon as the image is painted for the first time, and in
771 // the long term we want to update visibility information from the display
772 // list whenever we paint, so we don't actually need to do this. However, to
773 // avoid behavior changes during the transition from the old image visibility
774 // code, we'll leave it in for now.
775 UpdateVisibilitySynchronously();
780 void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted
= false; }
782 already_AddRefed
<imgIRequest
> SVGImageFrame::GetCurrentRequest() const {
783 nsCOMPtr
<imgIRequest
> request
;
784 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
=
785 do_QueryInterface(GetContent());
787 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
788 getter_AddRefs(request
));
790 return request
.forget();
793 bool SVGImageFrame::IgnoreHitTest() const {
794 switch (Style()->PointerEvents()) {
795 case StylePointerEvents::None
:
797 case StylePointerEvents::Visiblepainted
:
798 case StylePointerEvents::Auto
:
799 if (StyleVisibility()->IsVisible()) {
800 /* XXX: should check pixel transparency */
804 case StylePointerEvents::Visiblefill
:
805 case StylePointerEvents::Visiblestroke
:
806 case StylePointerEvents::Visible
:
807 if (StyleVisibility()->IsVisible()) {
811 case StylePointerEvents::Painted
:
812 /* XXX: should check pixel transparency */
814 case StylePointerEvents::Fill
:
815 case StylePointerEvents::Stroke
:
816 case StylePointerEvents::All
:
819 NS_ERROR("not reached");
826 void SVGImageFrame::NotifySVGChanged(uint32_t aFlags
) {
827 MOZ_ASSERT(aFlags
& (TRANSFORM_CHANGED
| COORD_CONTEXT_CHANGED
),
828 "Invalidation logic may need adjusting");
831 SVGBBox
SVGImageFrame::GetBBoxContribution(const Matrix
& aToBBoxUserspace
,
833 if (aToBBoxUserspace
.IsSingular()) {
834 // XXX ReportToConsole
838 if ((aFlags
& SVGUtils::eForGetClientRects
) &&
839 aToBBoxUserspace
.PreservesAxisAlignedRectangles()) {
840 Rect rect
= NSRectToRect(mRect
, AppUnitsPerCSSPixel());
841 return aToBBoxUserspace
.TransformBounds(rect
);
844 auto* element
= static_cast<SVGImageElement
*>(GetContent());
846 return element
->GeometryBounds(aToBBoxUserspace
);
849 //----------------------------------------------------------------------
850 // SVGImageListener implementation
852 NS_IMPL_ISUPPORTS(SVGImageListener
, imgINotificationObserver
)
854 SVGImageListener::SVGImageListener(SVGImageFrame
* aFrame
) : mFrame(aFrame
) {}
856 void SVGImageListener::Notify(imgIRequest
* aRequest
, int32_t aType
,
857 const nsIntRect
* aData
) {
862 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
863 mFrame
->InvalidateFrame();
864 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
866 nsChangeHint_InvalidateRenderingObservers
);
867 SVGUtils::ScheduleReflowSVG(mFrame
);
870 if (aType
== imgINotificationObserver::FRAME_UPDATE
) {
871 // No new dimensions, so we don't need to call
872 // SVGUtils::InvalidateAndScheduleBoundsUpdate.
873 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
875 nsChangeHint_InvalidateRenderingObservers
);
876 mFrame
->InvalidateFrame();
879 if (aType
== imgINotificationObserver::SIZE_AVAILABLE
) {
880 // Called once the resource's dimensions have been obtained.
881 nsCOMPtr
<imgIContainer
> image
;
882 aRequest
->GetImage(getter_AddRefs(image
));
884 StyleImageOrientation orientation
=
885 mFrame
->StyleVisibility()->UsedImageOrientation(aRequest
);
886 image
= nsLayoutUtils::OrientImage(image
, orientation
);
887 image
->SetAnimationMode(mFrame
->PresContext()->ImageAnimationMode());
888 mFrame
->mImageContainer
= std::move(image
);
890 mFrame
->InvalidateFrame();
891 nsLayoutUtils::PostRestyleEvent(mFrame
->GetContent()->AsElement(),
893 nsChangeHint_InvalidateRenderingObservers
);
894 SVGUtils::ScheduleReflowSVG(mFrame
);
898 } // namespace mozilla