no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / layout / svg / SVGImageFrame.cpp
blob242ee6e25e94e0cebb143556f614bb7802d67e7c
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::IsSVGTransformed(gfx::Matrix* aOwnTransform,
174 gfx::Matrix* aFromParentTransform) const {
175 return SVGUtils::IsSVGTransformed(this, aOwnTransform, 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;
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());
203 bool hrefIsSet =
204 element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() ||
205 element->mStringAttributes[SVGImageElement::XLINK_HREF]
206 .IsExplicitlySet();
207 if (hrefIsSet) {
208 element->LoadSVGImage(true, true);
209 } else {
210 element->CancelImageRequests(true);
214 return NS_OK;
217 void SVGImageFrame::OnVisibilityChange(
218 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
219 nsCOMPtr<nsIImageLoadingContent> imageLoader =
220 do_QueryInterface(GetContent());
221 if (imageLoader) {
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() {
243 float x, y;
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) {
257 return false;
260 ImageResolution resolution = mImageContainer->GetResolution();
262 int32_t width, height;
263 if (NS_FAILED(mImageContainer->GetWidth(&width))) {
264 aSize.width = -1;
265 } else {
266 aSize.width = width;
267 resolution.ApplyXTo(aSize.width);
270 if (NS_FAILED(mImageContainer->GetHeight(&height))) {
271 aSize.height = -1;
272 } else {
273 aSize.height = height;
274 resolution.ApplyYTo(aSize.height);
277 Maybe<AspectRatio> asp = mImageContainer->GetIntrinsicRatio();
278 aAspectRatio = asp.valueOr(AspectRatio{});
280 return true;
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);
288 } else {
289 int32_t nativeWidth, nativeHeight;
290 if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
291 NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
292 nativeWidth == 0 || nativeHeight == 0) {
293 return false;
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()) {
308 return false;
311 aGfxContext->Multiply(ThebesMatrix(imageTransform));
312 return true;
315 //----------------------------------------------------------------------
316 // ISVGDisplayableFrame methods
318 void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
319 imgDrawingParams& aImgParams) {
320 if (!StyleVisibility()->IsVisible()) {
321 return;
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());
335 if (imageLoader)
336 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
337 getter_AddRefs(currentRequest));
339 if (currentRequest)
340 currentRequest->GetImage(getter_AddRefs(mImageContainer));
343 if (mImageContainer) {
344 gfxClipAutoSaveRestore autoSaveClip(&aContext);
346 if (StyleDisplay()->IsScrollableOverflow()) {
347 gfxRect clipRect =
348 SVGUtils::GetClipRectForFrame(this, x, y, width, height);
349 autoSaveClip.TransformedClip(aTransform, clipRect);
352 gfxContextMatrixAutoSaveRestore autoSaveMatrix(&aContext);
354 if (!TransformContextForPainting(&aContext, aTransform)) {
355 return;
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,
369 opacity);
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,
407 context, flags);
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()) {
420 return;
423 if (aBuilder->IsForPainting()) {
424 if (!IsVisibleForPainting()) {
425 return;
427 if (StyleEffects()->IsTransparent()) {
428 return;
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()) {
440 return true;
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,
455 bool aDryRun) {
456 if (!StyleVisibility()->IsVisible()) {
457 return true;
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
467 return false;
469 if (StyleEffects()->HasMixBlendMode()) {
470 // FIXME: not implemented
471 return false;
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)
484 return true;
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);
513 auto appClip =
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
525 return true;
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
552 // or shrunk".
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;
566 switch (align) {
567 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
568 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
569 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
570 // align to top (no-op)
571 break;
572 case SVG_PRESERVEASPECTRATIO_XMINYMID:
573 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
574 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
575 // align to center
576 destRect.y += heightChange / 2.0f;
577 break;
578 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
579 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
580 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
581 // align to bottom
582 destRect.y += heightChange;
583 break;
584 default:
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;
593 switch (align) {
594 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
595 case SVG_PRESERVEASPECTRATIO_XMINYMID:
596 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
597 // align to left (no-op)
598 break;
599 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
600 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
601 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
602 // align to center
603 destRect.x += widthChange / 2.0f;
604 break;
605 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
606 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
607 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
608 // align to right
609 destRect.x += widthChange;
610 break;
611 default:
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,
633 region);
635 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
636 LCPHelpers::FinalizeLCPEntryForImage(
637 GetContent()->AsElement(),
638 static_cast<imgRequestProxy*>(currentRequest.get()),
639 LayoutDeviceRect::ToAppUnits(destRect, appUnitsPerDevPx) -
640 toReferenceFrame);
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)
655 return true;
656 case ImgDrawResult::NOT_SUPPORTED:
657 // things we haven't implemented for WR yet
658 return false;
659 default:
660 // image is ready to draw
661 break;
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)
666 if (!aDryRun) {
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.
670 if (provider) {
671 aManager->CommandBuilder().PushImageProvider(aItem, provider, drawResult,
672 aBuilder, aResources,
673 destRect, clipRect);
677 return true;
680 nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) {
681 if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && IgnoreHitTest()) {
682 return nullptr;
685 Rect rect;
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))) {
691 return nullptr;
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) {
708 return nullptr;
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)) {
716 return nullptr;
721 return this;
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)) {
732 return;
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());
744 } else {
745 mRect.SetEmpty();
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)) {
770 InvalidateFrame();
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();
788 return false;
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());
797 if (imageLoader) {
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:
807 break;
808 case StylePointerEvents::Visiblepainted:
809 case StylePointerEvents::Auto:
810 if (StyleVisibility()->IsVisible()) {
811 /* XXX: should check pixel transparency */
812 return false;
814 break;
815 case StylePointerEvents::Visiblefill:
816 case StylePointerEvents::Visiblestroke:
817 case StylePointerEvents::Visible:
818 if (StyleVisibility()->IsVisible()) {
819 return false;
821 break;
822 case StylePointerEvents::Painted:
823 /* XXX: should check pixel transparency */
824 return false;
825 case StylePointerEvents::Fill:
826 case StylePointerEvents::Stroke:
827 case StylePointerEvents::All:
828 return false;
829 default:
830 NS_ERROR("not reached");
831 break;
834 return true;
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,
843 uint32_t aFlags) {
844 if (aToBBoxUserspace.IsSingular()) {
845 // XXX ReportToConsole
846 return {};
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) {
869 if (!mFrame) {
870 return;
873 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
874 mFrame->InvalidateFrame();
875 nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
876 RestyleHint{0},
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(),
885 RestyleHint{0},
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));
894 if (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(),
903 RestyleHint{0},
904 nsChangeHint_InvalidateRenderingObservers);
905 SVGUtils::ScheduleReflowSVG(mFrame);
909 } // namespace mozilla