Backed out changeset 36e95068e103 (bug 1848160) for bc failures on browser_preference...
[gecko.git] / layout / svg / SVGImageFrame.cpp
blobda04beee71bd543641632cf4cc9ce9df75817ead
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 "nsIReflowCallback.h"
33 using namespace mozilla::dom;
34 using namespace mozilla::gfx;
35 using namespace mozilla::image;
36 using namespace mozilla::dom::SVGPreserveAspectRatio_Binding;
37 namespace SVGT = SVGGeometryProperty::Tags;
39 namespace mozilla {
41 class SVGImageListener final : public imgINotificationObserver {
42 public:
43 explicit SVGImageListener(SVGImageFrame* aFrame);
45 NS_DECL_ISUPPORTS
46 NS_DECL_IMGINOTIFICATIONOBSERVER
48 void SetFrame(SVGImageFrame* frame) { mFrame = frame; }
50 private:
51 ~SVGImageListener() = default;
53 SVGImageFrame* mFrame;
56 // ---------------------------------------------------------------------
57 // nsQueryFrame methods
59 NS_QUERYFRAME_HEAD(SVGImageFrame)
60 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
61 NS_QUERYFRAME_ENTRY(SVGImageFrame)
62 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
64 } // namespace mozilla
66 nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell,
67 mozilla::ComputedStyle* aStyle) {
68 return new (aPresShell)
69 mozilla::SVGImageFrame(aStyle, aPresShell->GetPresContext());
72 namespace mozilla {
74 NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame)
76 SVGImageFrame::~SVGImageFrame() {
77 // set the frame to null so we don't send messages to a dead object.
78 if (mListener) {
79 nsCOMPtr<nsIImageLoadingContent> imageLoader =
80 do_QueryInterface(GetContent());
81 if (imageLoader) {
82 imageLoader->RemoveNativeObserver(mListener);
84 reinterpret_cast<SVGImageListener*>(mListener.get())->SetFrame(nullptr);
86 mListener = nullptr;
89 void SVGImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
90 nsIFrame* aPrevInFlow) {
91 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
92 "Content is not an SVG image!");
94 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
95 nsIFrame::Init(aContent, aParent, aPrevInFlow);
97 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
98 // Non-display frames are likely to be patterns, masks or the like.
99 // Treat them as always visible.
100 // This call must happen before the FrameCreated. This is because the
101 // primary frame pointer on our content node isn't set until after this
102 // function ends, so there is no way for the resulting OnVisibilityChange
103 // notification to get a frame. FrameCreated has a workaround for this in
104 // that it passes our frame around so it can be accessed. OnVisibilityChange
105 // doesn't have that workaround.
106 IncApproximateVisibleCount();
109 mListener = new SVGImageListener(this);
110 nsCOMPtr<nsIImageLoadingContent> imageLoader =
111 do_QueryInterface(GetContent());
112 if (!imageLoader) {
113 MOZ_CRASH("Why is this not an image loading content?");
116 // We should have a PresContext now, so let's notify our image loader that
117 // we need to register any image animations with the refresh driver.
118 imageLoader->FrameCreated(this);
120 imageLoader->AddNativeObserver(mListener);
123 /* virtual */
124 void SVGImageFrame::Destroy(DestroyContext& aContext) {
125 if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
126 DecApproximateVisibleCount();
129 if (mReflowCallbackPosted) {
130 PresShell()->CancelReflowCallback(this);
131 mReflowCallbackPosted = false;
134 nsCOMPtr<nsIImageLoadingContent> imageLoader =
135 do_QueryInterface(nsIFrame::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));
395 // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
396 // That method needs our image to have a fixed native width & height,
397 // and that's not always true for TYPE_VECTOR images.
398 aImgParams.result &= nsLayoutUtils::DrawSingleImage(
399 aContext, PresContext(), mImageContainer,
400 nsLayoutUtils::GetSamplingFilterForFrame(this), destRect, destRect,
401 context, flags);
402 } else { // mImageContainer->GetType() == TYPE_RASTER
403 aImgParams.result &= nsLayoutUtils::DrawSingleUnscaledImage(
404 aContext, PresContext(), mImageContainer,
405 nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0),
406 nullptr, SVGImageContext(), flags);
411 void SVGImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
412 const nsDisplayListSet& aLists) {
413 if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
414 return;
417 if (aBuilder->IsForPainting()) {
418 if (!IsVisibleForPainting()) {
419 return;
421 if (StyleEffects()->IsTransparent()) {
422 return;
424 aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
425 aLists.BorderBackground());
428 DisplayOutline(aBuilder, aLists);
429 aLists.Content()->AppendNewToTop<DisplaySVGImage>(aBuilder, this);
432 bool SVGImageFrame::IsInvisible() const {
433 if (!StyleVisibility()->IsVisible()) {
434 return true;
437 // Anything below will round to zero later down the pipeline.
438 constexpr float opacity_threshold = 1.0 / 128.0;
440 return StyleEffects()->mOpacity <= opacity_threshold;
443 bool SVGImageFrame::CreateWebRenderCommands(
444 mozilla::wr::DisplayListBuilder& aBuilder,
445 mozilla::wr::IpcResourceUpdateQueue& aResources,
446 const mozilla::layers::StackingContextHelper& aSc,
447 mozilla::layers::RenderRootStateManager* aManager,
448 nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGImage* aItem,
449 bool aDryRun) {
450 if (!StyleVisibility()->IsVisible()) {
451 return true;
454 float opacity = 1.0f;
455 if (SVGUtils::CanOptimizeOpacity(this)) {
456 opacity = StyleEffects()->mOpacity;
459 if (opacity != 1.0f) {
460 // FIXME: not implemented, might be trivial
461 return false;
463 if (StyleEffects()->HasMixBlendMode()) {
464 // FIXME: not implemented
465 return false;
468 // try to setup the image
469 if (!mImageContainer) {
470 nsCOMPtr<imgIRequest> currentRequest;
471 nsCOMPtr<nsIImageLoadingContent> imageLoader =
472 do_QueryInterface(GetContent());
473 if (imageLoader) {
474 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
475 getter_AddRefs(currentRequest));
478 if (currentRequest) {
479 currentRequest->GetImage(getter_AddRefs(mImageContainer));
483 if (!mImageContainer) {
484 // nothing to draw (yet)
485 return true;
488 uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
489 if (mForceSyncDecoding) {
490 flags |= imgIContainer::FLAG_SYNC_DECODE;
493 // Compute bounds of the image
494 nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
495 int32_t appUnitsPerCSSPixel = AppUnitsPerCSSPixel();
497 float x, y, width, height;
498 SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent());
499 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
500 imgElem, &x, &y, &width, &height);
501 NS_ASSERTION(width > 0 && height > 0,
502 "Should only be painting things with valid width/height");
504 auto toReferenceFrame = aItem->ToReferenceFrame();
505 auto appRect = nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width, height),
506 appUnitsPerCSSPixel);
507 appRect += toReferenceFrame;
508 auto destRect = LayoutDeviceRect::FromAppUnits(appRect, appUnitsPerDevPx);
509 auto clipRect = destRect;
511 if (StyleDisplay()->IsScrollableOverflow()) {
512 // Apply potential non-trivial clip
513 auto cssClip = SVGUtils::GetClipRectForFrame(this, 0, 0, width, height);
514 auto appClip =
515 nsLayoutUtils::RoundGfxRectToAppRect(cssClip, appUnitsPerCSSPixel);
516 appClip += toReferenceFrame;
517 clipRect = LayoutDeviceRect::FromAppUnits(appClip, appUnitsPerDevPx);
519 // Apply preserveAspectRatio
520 if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
521 int32_t nativeWidth, nativeHeight;
522 if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
523 NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
524 nativeWidth == 0 || nativeHeight == 0) {
525 // Image has no size; nothing to draw
526 return true;
529 mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
531 auto preserveAspectRatio = imgElem->mPreserveAspectRatio.GetAnimValue();
532 uint16_t align = preserveAspectRatio.GetAlign();
533 uint16_t meetOrSlice = preserveAspectRatio.GetMeetOrSlice();
535 // default to the defaults
536 if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) {
537 align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
539 if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) {
540 meetOrSlice = SVG_MEETORSLICE_MEET;
543 // aspect > 1 is horizontal
544 // aspect < 1 is vertical
545 float nativeAspect = ((float)nativeWidth) / ((float)nativeHeight);
546 float viewAspect = width / height;
548 // "Meet" is "fit image to view"; "Slice" is "cover view with image".
550 // Whether we meet or slice, one side of the destRect will always be
551 // perfectly spanned by our image. The only questions to answer are
552 // "which side won't span perfectly" and "should that side be grown
553 // or shrunk".
555 // Because we fit our image to the destRect, this all just reduces to:
556 // "if meet, shrink to fit. if slice, grow to fit."
557 if (align != SVG_PRESERVEASPECTRATIO_NONE && nativeAspect != viewAspect) {
558 // Slightly redundant bools, but they make the conditions clearer
559 bool tooTall = nativeAspect > viewAspect;
560 bool tooWide = nativeAspect < viewAspect;
561 if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooTall) ||
562 (meetOrSlice == SVG_MEETORSLICE_SLICE && tooWide)) {
563 // Adjust height and realign y
564 auto oldHeight = destRect.height;
565 destRect.height = destRect.width / nativeAspect;
566 auto heightChange = oldHeight - destRect.height;
567 switch (align) {
568 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
569 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
570 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
571 // align to top (no-op)
572 break;
573 case SVG_PRESERVEASPECTRATIO_XMINYMID:
574 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
575 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
576 // align to center
577 destRect.y += heightChange / 2.0f;
578 break;
579 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
580 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
581 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
582 // align to bottom
583 destRect.y += heightChange;
584 break;
585 default:
586 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
588 } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooWide) ||
589 (meetOrSlice == SVG_MEETORSLICE_SLICE && tooTall)) {
590 // Adjust width and realign x
591 auto oldWidth = destRect.width;
592 destRect.width = destRect.height * nativeAspect;
593 auto widthChange = oldWidth - destRect.width;
594 switch (align) {
595 case SVG_PRESERVEASPECTRATIO_XMINYMIN:
596 case SVG_PRESERVEASPECTRATIO_XMINYMID:
597 case SVG_PRESERVEASPECTRATIO_XMINYMAX:
598 // align to left (no-op)
599 break;
600 case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
601 case SVG_PRESERVEASPECTRATIO_XMIDYMID:
602 case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
603 // align to center
604 destRect.x += widthChange / 2.0f;
605 break;
606 case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
607 case SVG_PRESERVEASPECTRATIO_XMAXYMID:
608 case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
609 // align to right
610 destRect.x += widthChange;
611 break;
612 default:
613 MOZ_ASSERT_UNREACHABLE("Unknown value for align");
620 SVGImageContext svgContext;
621 if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
622 if (StaticPrefs::image_svg_blob_image()) {
623 flags |= imgIContainer::FLAG_RECORD_BLOB;
625 // Forward preserveAspectRatio to inner SVGs
626 svgContext.SetViewportSize(Some(CSSIntSize::Ceil(width, height)));
627 svgContext.SetPreserveAspectRatio(
628 Some(imgElem->mPreserveAspectRatio.GetAnimValue()));
631 Maybe<ImageIntRegion> region;
632 IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
633 mImageContainer, this, destRect, clipRect, aSc, flags, svgContext,
634 region);
636 RefPtr<image::WebRenderImageProvider> provider;
637 ImgDrawResult drawResult = mImageContainer->GetImageProvider(
638 aManager->LayerManager(), decodeSize, svgContext, region, flags,
639 getter_AddRefs(provider));
641 // While we got a container, it may not contain a fully decoded surface. If
642 // that is the case, and we have an image we were previously displaying which
643 // has a fully decoded surface, then we should prefer the previous image.
644 switch (drawResult) {
645 case ImgDrawResult::NOT_READY:
646 case ImgDrawResult::TEMPORARY_ERROR:
647 // nothing to draw (yet)
648 return true;
649 case ImgDrawResult::NOT_SUPPORTED:
650 // things we haven't implemented for WR yet
651 return false;
652 default:
653 // image is ready to draw
654 break;
657 // Don't do any actual mutations to state if we're doing a dry run
658 // (used to decide if we're making this into an active layer)
659 if (!aDryRun) {
660 // If the image container is empty, we don't want to fallback. Any other
661 // failure will be due to resource constraints and fallback is unlikely to
662 // help us. Hence we can ignore the return value from PushImage.
663 if (provider) {
664 aManager->CommandBuilder().PushImageProvider(aItem, provider, drawResult,
665 aBuilder, aResources,
666 destRect, clipRect);
670 return true;
673 nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) {
674 if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && IgnoreHitTest()) {
675 return nullptr;
678 Rect rect;
679 SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
680 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
681 element, &rect.x, &rect.y, &rect.width, &rect.height);
683 if (!rect.Contains(ToPoint(aPoint))) {
684 return nullptr;
687 // Special case for raster images -- we only want to accept points that fall
688 // in the underlying image's (scaled to fit) native bounds. That region
689 // doesn't necessarily map to our <image> element's [x,y,width,height] if the
690 // raster image's aspect ratio is being preserved. We have to look up the
691 // native image size & our viewBox transform in order to filter out points
692 // that fall outside that area. (This special case doesn't apply to vector
693 // images because they don't limit their drawing to explicit "native
694 // bounds" -- they have an infinite canvas on which to place content.)
695 if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) {
696 if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
697 int32_t nativeWidth, nativeHeight;
698 if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
699 NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
700 nativeWidth == 0 || nativeHeight == 0) {
701 return nullptr;
703 mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
704 Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform(
705 rect.width, rect.height, 0, 0, nativeWidth, nativeHeight,
706 element->mPreserveAspectRatio);
707 if (!SVGUtils::HitTestRect(viewBoxTM, 0, 0, nativeWidth, nativeHeight,
708 aPoint.x - rect.x, aPoint.y - rect.y)) {
709 return nullptr;
714 return this;
717 void SVGImageFrame::ReflowSVG() {
718 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
719 "This call is probably a wasteful mistake");
721 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
722 "ReflowSVG mechanism not designed for this");
724 if (!SVGUtils::NeedsReflowSVG(this)) {
725 return;
728 float x, y, width, height;
729 SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
730 SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
731 element, &x, &y, &width, &height);
733 Rect extent(x, y, width, height);
735 if (!extent.IsEmpty()) {
736 mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, AppUnitsPerCSSPixel());
737 } else {
738 mRect.SetEmpty();
741 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
742 // Make sure we have our filter property (if any) before calling
743 // FinishAndStoreOverflow (subsequent filter changes are handled off
744 // nsChangeHint_UpdateEffects):
745 SVGObserverUtils::UpdateEffects(this);
747 if (!mReflowCallbackPosted) {
748 mReflowCallbackPosted = true;
749 PresShell()->PostReflowCallback(this);
753 nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
754 OverflowAreas overflowAreas(overflow, overflow);
755 FinishAndStoreOverflow(overflowAreas, mRect.Size());
757 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
758 NS_FRAME_HAS_DIRTY_CHILDREN);
760 // Invalidate, but only if this is not our first reflow (since if it is our
761 // first reflow then we haven't had our first paint yet).
762 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
763 InvalidateFrame();
767 bool SVGImageFrame::ReflowFinished() {
768 mReflowCallbackPosted = false;
770 // XXX(seth): We don't need this. The purpose of updating visibility
771 // synchronously is to ensure that animated images start animating
772 // immediately. In the short term, however,
773 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
774 // animations start as soon as the image is painted for the first time, and in
775 // the long term we want to update visibility information from the display
776 // list whenever we paint, so we don't actually need to do this. However, to
777 // avoid behavior changes during the transition from the old image visibility
778 // code, we'll leave it in for now.
779 UpdateVisibilitySynchronously();
781 return false;
784 void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
786 bool SVGImageFrame::IgnoreHitTest() const {
787 switch (Style()->PointerEvents()) {
788 case StylePointerEvents::None:
789 break;
790 case StylePointerEvents::Visiblepainted:
791 case StylePointerEvents::Auto:
792 if (StyleVisibility()->IsVisible()) {
793 /* XXX: should check pixel transparency */
794 return false;
796 break;
797 case StylePointerEvents::Visiblefill:
798 case StylePointerEvents::Visiblestroke:
799 case StylePointerEvents::Visible:
800 if (StyleVisibility()->IsVisible()) {
801 return false;
803 break;
804 case StylePointerEvents::Painted:
805 /* XXX: should check pixel transparency */
806 return false;
807 case StylePointerEvents::Fill:
808 case StylePointerEvents::Stroke:
809 case StylePointerEvents::All:
810 return false;
811 default:
812 NS_ERROR("not reached");
813 break;
816 return true;
819 void SVGImageFrame::NotifySVGChanged(uint32_t aFlags) {
820 MOZ_ASSERT(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
821 "Invalidation logic may need adjusting");
824 SVGBBox SVGImageFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
825 uint32_t aFlags) {
826 if (aToBBoxUserspace.IsSingular()) {
827 // XXX ReportToConsole
828 return {};
831 if ((aFlags & SVGUtils::eForGetClientRects) &&
832 aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
833 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
834 return aToBBoxUserspace.TransformBounds(rect);
837 auto* element = static_cast<SVGImageElement*>(GetContent());
839 return element->GeometryBounds(aToBBoxUserspace);
842 //----------------------------------------------------------------------
843 // SVGImageListener implementation
845 NS_IMPL_ISUPPORTS(SVGImageListener, imgINotificationObserver)
847 SVGImageListener::SVGImageListener(SVGImageFrame* aFrame) : mFrame(aFrame) {}
849 void SVGImageListener::Notify(imgIRequest* aRequest, int32_t aType,
850 const nsIntRect* aData) {
851 if (!mFrame) {
852 return;
855 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
856 mFrame->InvalidateFrame();
857 nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
858 RestyleHint{0},
859 nsChangeHint_InvalidateRenderingObservers);
860 SVGUtils::ScheduleReflowSVG(mFrame);
863 if (aType == imgINotificationObserver::FRAME_UPDATE) {
864 // No new dimensions, so we don't need to call
865 // SVGUtils::InvalidateAndScheduleBoundsUpdate.
866 nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
867 RestyleHint{0},
868 nsChangeHint_InvalidateRenderingObservers);
869 mFrame->InvalidateFrame();
872 if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
873 // Called once the resource's dimensions have been obtained.
874 nsCOMPtr<imgIContainer> image;
875 aRequest->GetImage(getter_AddRefs(image));
876 if (image) {
877 StyleImageOrientation orientation =
878 mFrame->StyleVisibility()->UsedImageOrientation(aRequest);
879 image = nsLayoutUtils::OrientImage(image, orientation);
880 image->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode());
881 mFrame->mImageContainer = std::move(image);
883 mFrame->InvalidateFrame();
884 nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
885 RestyleHint{0},
886 nsChangeHint_InvalidateRenderingObservers);
887 SVGUtils::ScheduleReflowSVG(mFrame);
891 } // namespace mozilla