Backed out 2 changesets (bug 1733498) for causing perma Localised repacks build busta...
[gecko.git] / layout / svg / SVGImageFrame.cpp
blobdf9fb2478bfde593024e4b14b4bd7ab455ce3bb8
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;
40 namespace mozilla {
42 class SVGImageListener final : public imgINotificationObserver {
43 public:
44 explicit SVGImageListener(SVGImageFrame* aFrame);
46 NS_DECL_ISUPPORTS
47 NS_DECL_IMGINOTIFICATIONOBSERVER
49 void SetFrame(SVGImageFrame* frame) { mFrame = frame; }
51 private:
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());
73 namespace mozilla {
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.
79 if (mListener) {
80 nsCOMPtr<nsIImageLoadingContent> imageLoader =
81 do_QueryInterface(GetContent());
82 if (imageLoader) {
83 imageLoader->RemoveNativeObserver(mListener);
85 reinterpret_cast<SVGImageListener*>(mListener.get())->SetFrame(nullptr);
87 mListener = 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());
113 if (!imageLoader) {
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);
124 /* virtual */
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);
137 if (imageLoader) {
138 imageLoader->FrameDestroyed(this);
141 nsIFrame::Destroy(aContext);
144 /* virtual */
145 void SVGImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
146 nsIFrame::DidSetComputedStyle(aOldStyle);
148 if (!mImageContainer || !aOldStyle) {
149 return;
152 nsCOMPtr<imgIRequest> currentRequest;
153 nsCOMPtr<nsIImageLoadingContent> imageLoader =
154 do_QueryInterface(GetContent());
155 if (imageLoader) {
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 //----------------------------------------------------------------------
179 // nsIFrame methods:
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
188 // recomposite.
189 InvalidateFrame();
190 return NS_OK;
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;
201 InvalidateFrame();
205 return NS_OK;
208 void SVGImageFrame::OnVisibilityChange(
209 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
210 nsCOMPtr<nsIImageLoadingContent> imageLoader =
211 do_QueryInterface(GetContent());
212 if (imageLoader) {
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() {
234 float x, y;
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) {
248 return false;
251 ImageResolution resolution = mImageContainer->GetResolution();
252 int32_t width, height;
253 if (NS_FAILED(mImageContainer->GetWidth(&width))) {
254 aSize.width = -1;
255 } else {
256 aSize.width = width;
257 resolution.ApplyXTo(aSize.width);
260 if (NS_FAILED(mImageContainer->GetHeight(&height))) {
261 aSize.height = -1;
262 } else {
263 aSize.height = height;
264 resolution.ApplyYTo(aSize.height);
267 aAspectRatio = mImageContainer->GetIntrinsicRatio();
268 return true;
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);
276 } else {
277 int32_t nativeWidth, nativeHeight;
278 if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
279 NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
280 nativeWidth == 0 || nativeHeight == 0) {
281 return false;
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()) {
296 return false;
299 aGfxContext->Multiply(ThebesMatrix(imageTransform));
300 return true;
303 //----------------------------------------------------------------------
304 // ISVGDisplayableFrame methods
306 void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
307 imgDrawingParams& aImgParams) {
308 if (!StyleVisibility()->IsVisible()) {
309 return;
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());
323 if (imageLoader)
324 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
325 getter_AddRefs(currentRequest));
327 if (currentRequest)
328 currentRequest->GetImage(getter_AddRefs(mImageContainer));
331 if (mImageContainer) {
332 gfxClipAutoSaveRestore autoSaveClip(&aContext);
334 if (StyleDisplay()->IsScrollableOverflow()) {
335 gfxRect clipRect =
336 SVGUtils::GetClipRectForFrame(this, x, y, width, height);
337 autoSaveClip.TransformedClip(aTransform, clipRect);
340 gfxContextMatrixAutoSaveRestore autoSaveMatrix(&aContext);
342 if (!TransformContextForPainting(&aContext, aTransform)) {
343 return;
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,
357 opacity);
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,
395 context, flags);
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()) {
408 return;
411 if (aBuilder->IsForPainting()) {
412 if (!IsVisibleForPainting()) {
413 return;
415 if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) {
416 return;
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()) {
428 return true;
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,
444 bool aDryRun) {
445 if (!StyleVisibility()->IsVisible()) {
446 return true;
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
456 return false;
458 if (StyleEffects()->HasMixBlendMode()) {
459 // FIXME: not implemented
460 return false;
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)
473 return true;
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);
502 auto appClip =
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
514 return true;
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
541 // or shrunk".
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;
555 switch (align) {
556 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
557 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
558 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
559 // align to top (no-op)
560 break;
561 case SVG_PRESERVEASPECTRATIO_XMINYMID:
562 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
563 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
564 // align to center
565 destRect.y += heightChange / 2.0f;
566 break;
567 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
568 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
569 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
570 // align to bottom
571 destRect.y += heightChange;
572 break;
573 default:
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;
582 switch (align) {
583 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
584 case SVG_PRESERVEASPECTRATIO_XMINYMID:
585 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
586 // align to left (no-op)
587 break;
588 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
589 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
590 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
591 // align to center
592 destRect.x += widthChange / 2.0f;
593 break;
594 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
595 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
596 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
597 // align to right
598 destRect.x += widthChange;
599 break;
600 default:
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,
622 region);
624 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
625 LCPHelpers::FinalizeLCPEntryForImage(
626 GetContent()->AsElement(),
627 static_cast<imgRequestProxy*>(currentRequest.get()),
628 LayoutDeviceRect::ToAppUnits(destRect, appUnitsPerDevPx) -
629 toReferenceFrame);
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)
644 return true;
645 case ImgDrawResult::NOT_SUPPORTED:
646 // things we haven't implemented for WR yet
647 return false;
648 default:
649 // image is ready to draw
650 break;
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)
655 if (!aDryRun) {
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.
659 if (provider) {
660 aManager->CommandBuilder().PushImageProvider(aItem, provider, drawResult,
661 aBuilder, aResources,
662 destRect, clipRect);
666 return true;
669 nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) {
670 if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && IgnoreHitTest()) {
671 return nullptr;
674 Rect rect;
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))) {
680 return nullptr;
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) {
697 return nullptr;
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)) {
705 return nullptr;
710 return this;
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)) {
721 return;
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());
733 } else {
734 mRect.SetEmpty();
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)) {
759 InvalidateFrame();
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();
777 return false;
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());
786 if (imageLoader) {
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:
796 break;
797 case StylePointerEvents::Visiblepainted:
798 case StylePointerEvents::Auto:
799 if (StyleVisibility()->IsVisible()) {
800 /* XXX: should check pixel transparency */
801 return false;
803 break;
804 case StylePointerEvents::Visiblefill:
805 case StylePointerEvents::Visiblestroke:
806 case StylePointerEvents::Visible:
807 if (StyleVisibility()->IsVisible()) {
808 return false;
810 break;
811 case StylePointerEvents::Painted:
812 /* XXX: should check pixel transparency */
813 return false;
814 case StylePointerEvents::Fill:
815 case StylePointerEvents::Stroke:
816 case StylePointerEvents::All:
817 return false;
818 default:
819 NS_ERROR("not reached");
820 break;
823 return true;
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,
832 uint32_t aFlags) {
833 if (aToBBoxUserspace.IsSingular()) {
834 // XXX ReportToConsole
835 return {};
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) {
858 if (!mFrame) {
859 return;
862 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
863 mFrame->InvalidateFrame();
864 nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
865 RestyleHint{0},
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(),
874 RestyleHint{0},
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));
883 if (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(),
892 RestyleHint{0},
893 nsChangeHint_InvalidateRenderingObservers);
894 SVGUtils::ScheduleReflowSVG(mFrame);
898 } // namespace mozilla