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/. */
6 
7 #include "SVGImageFrame.h"
8 
9 // Keep in (case-insensitive) order:
10 #include "gfxContext.h"
11 #include "gfxPlatform.h"
12 #include "mozilla/gfx/2D.h"
13 #include "mozilla/layers/RenderRootStateManager.h"
14 #include "mozilla/layers/WebRenderLayerManager.h"
15 #include "imgIContainer.h"
16 #include "ImageRegion.h"
17 #include "nsContainerFrame.h"
18 #include "nsIImageLoadingContent.h"
19 #include "nsLayoutUtils.h"
20 #include "imgINotificationObserver.h"
21 #include "SVGGeometryProperty.h"
22 #include "SVGGeometryFrame.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"
32 #include "mozilla/Unused.h"
33 
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;
39 
40 namespace mozilla {
41 
42 class SVGImageListener final : public imgINotificationObserver {
43  public:
44   explicit SVGImageListener(SVGImageFrame* aFrame);
45 
46   NS_DECL_ISUPPORTS
47   NS_DECL_IMGINOTIFICATIONOBSERVER
48 
SetFrame(SVGImageFrame * frame)49   void SetFrame(SVGImageFrame* frame) { mFrame = frame; }
50 
51  private:
52   ~SVGImageListener() = default;
53 
54   SVGImageFrame* mFrame;
55 };
56 
57 // ---------------------------------------------------------------------
58 // nsQueryFrame methods
59 NS_QUERYFRAME_HEAD(SVGImageFrame)
60   NS_QUERYFRAME_ENTRY(SVGImageFrame)
61 NS_QUERYFRAME_TAIL_INHERITING(SVGGeometryFrame)
62 
63 }  // namespace mozilla
64 
NS_NewSVGImageFrame(mozilla::PresShell * aPresShell,mozilla::ComputedStyle * aStyle)65 nsIFrame* NS_NewSVGImageFrame(mozilla::PresShell* aPresShell,
66                               mozilla::ComputedStyle* aStyle) {
67   return new (aPresShell)
68       mozilla::SVGImageFrame(aStyle, aPresShell->GetPresContext());
69 }
70 
71 namespace mozilla {
72 
NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame)73 NS_IMPL_FRAMEARENA_HELPERS(SVGImageFrame)
74 
75 SVGImageFrame::~SVGImageFrame() {
76   // set the frame to null so we don't send messages to a dead object.
77   if (mListener) {
78     nsCOMPtr<nsIImageLoadingContent> imageLoader =
79         do_QueryInterface(GetContent());
80     if (imageLoader) {
81       imageLoader->RemoveNativeObserver(mListener);
82     }
83     reinterpret_cast<SVGImageListener*>(mListener.get())->SetFrame(nullptr);
84   }
85   mListener = nullptr;
86 }
87 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)88 void SVGImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
89                          nsIFrame* aPrevInFlow) {
90   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
91                "Content is not an SVG image!");
92 
93   SVGGeometryFrame::Init(aContent, aParent, aPrevInFlow);
94 
95   if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
96     // Non-display frames are likely to be patterns, masks or the like.
97     // Treat them as always visible.
98     // This call must happen before the FrameCreated. This is because the
99     // primary frame pointer on our content node isn't set until after this
100     // function ends, so there is no way for the resulting OnVisibilityChange
101     // notification to get a frame. FrameCreated has a workaround for this in
102     // that it passes our frame around so it can be accessed. OnVisibilityChange
103     // doesn't have that workaround.
104     IncApproximateVisibleCount();
105   }
106 
107   mListener = new SVGImageListener(this);
108   nsCOMPtr<nsIImageLoadingContent> imageLoader =
109       do_QueryInterface(GetContent());
110   if (!imageLoader) {
111     MOZ_CRASH("Why is this not an image loading content?");
112   }
113 
114   // We should have a PresContext now, so let's notify our image loader that
115   // we need to register any image animations with the refresh driver.
116   imageLoader->FrameCreated(this);
117 
118   imageLoader->AddNativeObserver(mListener);
119 }
120 
121 /* virtual */
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)122 void SVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot,
123                                 PostDestroyData& aPostDestroyData) {
124   if (HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
125     DecApproximateVisibleCount();
126   }
127 
128   if (mReflowCallbackPosted) {
129     PresShell()->CancelReflowCallback(this);
130     mReflowCallbackPosted = false;
131   }
132 
133   nsCOMPtr<nsIImageLoadingContent> imageLoader =
134       do_QueryInterface(nsIFrame::mContent);
135 
136   if (imageLoader) {
137     imageLoader->FrameDestroyed(this);
138   }
139 
140   nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
141 }
142 
143 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldStyle)144 void SVGImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
145   SVGGeometryFrame::DidSetComputedStyle(aOldStyle);
146 
147   if (!mImageContainer || !aOldStyle) {
148     return;
149   }
150 
151   auto newOrientation = StyleVisibility()->mImageOrientation;
152 
153   if (aOldStyle->StyleVisibility()->mImageOrientation != newOrientation) {
154     nsCOMPtr<imgIContainer> image(mImageContainer->Unwrap());
155     mImageContainer = nsLayoutUtils::OrientImage(image, newOrientation);
156   }
157 
158   // TODO(heycam): We should handle aspect-ratio, like nsImageFrame does.
159 }
160 
161 //----------------------------------------------------------------------
162 // nsIFrame methods:
163 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)164 nsresult SVGImageFrame::AttributeChanged(int32_t aNameSpaceID,
165                                          nsAtom* aAttribute, int32_t aModType) {
166   if (aNameSpaceID == kNameSpaceID_None) {
167     if (aAttribute == nsGkAtoms::preserveAspectRatio) {
168       // We don't paint the content of the image using display lists, therefore
169       // we have to invalidate for this children-only transform changes since
170       // there is no layer tree to notice that the transform changed and
171       // recomposite.
172       InvalidateFrame();
173       return NS_OK;
174     }
175   }
176 
177   // Currently our SMIL implementation does not modify the DOM attributes. Once
178   // we implement the SVG 2 SMIL behaviour this can be removed
179   // SVGImageElement::AfterSetAttr's implementation will be sufficient.
180   if (aModType == MutationEvent_Binding::SMIL &&
181       aAttribute == nsGkAtoms::href &&
182       (aNameSpaceID == kNameSpaceID_XLink ||
183        aNameSpaceID == kNameSpaceID_None)) {
184     SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
185 
186     bool hrefIsSet =
187         element->mStringAttributes[SVGImageElement::HREF].IsExplicitlySet() ||
188         element->mStringAttributes[SVGImageElement::XLINK_HREF]
189             .IsExplicitlySet();
190     if (hrefIsSet) {
191       element->LoadSVGImage(true, true);
192     } else {
193       element->CancelImageRequests(true);
194     }
195   }
196 
197   return SVGGeometryFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
198 }
199 
OnVisibilityChange(Visibility aNewVisibility,const Maybe<OnNonvisible> & aNonvisibleAction)200 void SVGImageFrame::OnVisibilityChange(
201     Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
202   nsCOMPtr<nsIImageLoadingContent> imageLoader =
203       do_QueryInterface(GetContent());
204   if (!imageLoader) {
205     SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
206     return;
207   }
208 
209   imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
210 
211   SVGGeometryFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
212 }
213 
GetRasterImageTransform(int32_t aNativeWidth,int32_t aNativeHeight)214 gfx::Matrix SVGImageFrame::GetRasterImageTransform(int32_t aNativeWidth,
215                                                    int32_t aNativeHeight) {
216   float x, y, width, height;
217   SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
218   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
219       element, &x, &y, &width, &height);
220 
221   Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform(
222       width, height, 0, 0, aNativeWidth, aNativeHeight,
223       element->mPreserveAspectRatio);
224 
225   return viewBoxTM * gfx::Matrix::Translation(x, y);
226 }
227 
GetVectorImageTransform()228 gfx::Matrix SVGImageFrame::GetVectorImageTransform() {
229   float x, y;
230   SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
231   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y>(element, &x, &y);
232 
233   // No viewBoxTM needed here -- our height/width overrides any concept of
234   // "native size" that the SVG image has, and it will handle viewBox and
235   // preserveAspectRatio on its own once we give it a region to draw into.
236 
237   return gfx::Matrix::Translation(x, y);
238 }
239 
GetIntrinsicImageDimensions(mozilla::gfx::Size & aSize,mozilla::AspectRatio & aAspectRatio) const240 bool SVGImageFrame::GetIntrinsicImageDimensions(
241     mozilla::gfx::Size& aSize, mozilla::AspectRatio& aAspectRatio) const {
242   if (!mImageContainer) {
243     return false;
244   }
245 
246   ImageResolution resolution = mImageContainer->GetResolution();
247 
248   int32_t width, height;
249   if (NS_FAILED(mImageContainer->GetWidth(&width))) {
250     aSize.width = -1;
251   } else {
252     aSize.width = width;
253     resolution.ApplyXTo(aSize.width);
254   }
255 
256   if (NS_FAILED(mImageContainer->GetHeight(&height))) {
257     aSize.height = -1;
258   } else {
259     aSize.height = height;
260     resolution.ApplyYTo(aSize.height);
261   }
262 
263   Maybe<AspectRatio> asp = mImageContainer->GetIntrinsicRatio();
264   aAspectRatio = asp.valueOr(AspectRatio{});
265 
266   return true;
267 }
268 
TransformContextForPainting(gfxContext * aGfxContext,const gfxMatrix & aTransform)269 bool SVGImageFrame::TransformContextForPainting(gfxContext* aGfxContext,
270                                                 const gfxMatrix& aTransform) {
271   gfx::Matrix imageTransform;
272   if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
273     imageTransform = GetVectorImageTransform() * ToMatrix(aTransform);
274   } else {
275     int32_t nativeWidth, nativeHeight;
276     if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
277         NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
278         nativeWidth == 0 || nativeHeight == 0) {
279       return false;
280     }
281     mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
282     imageTransform = GetRasterImageTransform(nativeWidth, nativeHeight) *
283                      ToMatrix(aTransform);
284 
285     // NOTE: We need to cancel out the effects of Full-Page-Zoom, or else
286     // it'll get applied an extra time by DrawSingleUnscaledImage.
287     nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
288     gfxFloat pageZoomFactor =
289         nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPx);
290     imageTransform.PreScale(pageZoomFactor, pageZoomFactor);
291   }
292 
293   if (imageTransform.IsSingular()) {
294     return false;
295   }
296 
297   aGfxContext->Multiply(ThebesMatrix(imageTransform));
298   return true;
299 }
300 
301 //----------------------------------------------------------------------
302 // ISVGDisplayableFrame methods:
PaintSVG(gfxContext & aContext,const gfxMatrix & aTransform,imgDrawingParams & aImgParams,const nsIntRect * aDirtyRect)303 void SVGImageFrame::PaintSVG(gfxContext& aContext, const gfxMatrix& aTransform,
304                              imgDrawingParams& aImgParams,
305                              const nsIntRect* aDirtyRect) {
306   if (!StyleVisibility()->IsVisible()) {
307     return;
308   }
309 
310   float x, y, width, height;
311   SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent());
312   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
313       imgElem, &x, &y, &width, &height);
314   NS_ASSERTION(width > 0 && height > 0,
315                "Should only be painting things with valid width/height");
316 
317   if (!mImageContainer) {
318     nsCOMPtr<imgIRequest> currentRequest;
319     nsCOMPtr<nsIImageLoadingContent> imageLoader =
320         do_QueryInterface(GetContent());
321     if (imageLoader)
322       imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
323                               getter_AddRefs(currentRequest));
324 
325     if (currentRequest)
326       currentRequest->GetImage(getter_AddRefs(mImageContainer));
327   }
328 
329   if (mImageContainer) {
330     gfxContextAutoSaveRestore autoRestorer(&aContext);
331 
332     if (StyleDisplay()->IsScrollableOverflow()) {
333       gfxRect clipRect =
334           SVGUtils::GetClipRectForFrame(this, x, y, width, height);
335       SVGUtils::SetClipRect(&aContext, aTransform, clipRect);
336     }
337 
338     if (!TransformContextForPainting(&aContext, aTransform)) {
339       return;
340     }
341 
342     // fill-opacity doesn't affect <image>, so if we're allowed to
343     // optimize group opacity, the opacity used for compositing the
344     // image into the current canvas is just the group opacity.
345     float opacity = 1.0f;
346     if (SVGUtils::CanOptimizeOpacity(this)) {
347       opacity = StyleEffects()->mOpacity;
348     }
349 
350     if (opacity != 1.0f ||
351         StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
352       aContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
353     }
354 
355     nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
356     nsRect dirtyRect;  // only used if aDirtyRect is non-null
357     if (aDirtyRect) {
358       NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
359                        (mState & NS_FRAME_IS_NONDISPLAY),
360                    "Display lists handle dirty rect intersection test");
361       dirtyRect = ToAppUnits(*aDirtyRect, appUnitsPerDevPx);
362 
363       // dirtyRect is relative to the outer <svg>, we should transform it
364       // down to <image>.
365       Rect dir(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height);
366       dir.Scale(1.f / AppUnitsPerCSSPixel());
367 
368       // FIXME: This isn't correct if there is an inner <svg> enclosing
369       // the <image>. But that seems to be a quite obscure usecase, we can
370       // add a dedicated utility for that purpose to replace the GetCTM
371       // here if necessary.
372       auto mat = SVGContentUtils::GetCTM(
373           static_cast<SVGImageElement*>(GetContent()), false);
374       if (mat.IsSingular()) {
375         return;
376       }
377 
378       mat.Invert();
379       dir = mat.TransformRect(dir);
380 
381       // x, y offset of <image> is not included in CTM.
382       dir.MoveBy(-x, -y);
383 
384       dir.Scale(AppUnitsPerCSSPixel());
385       dir.Round();
386       dirtyRect = nsRect(dir.x, dir.y, dir.width, dir.height);
387     }
388 
389     uint32_t flags = aImgParams.imageFlags;
390     if (mForceSyncDecoding) {
391       flags |= imgIContainer::FLAG_SYNC_DECODE;
392     }
393 
394     if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
395       // Package up the attributes of this image element which can override the
396       // attributes of mImageContainer's internal SVG document.  The 'width' &
397       // 'height' values we're passing in here are in CSS units (though they
398       // come from width/height *attributes* in SVG). They influence the region
399       // of the SVG image's internal document that is visible, in combination
400       // with preserveAspectRatio and viewBox.
401       const Maybe<SVGImageContext> context(Some(
402           SVGImageContext(Some(CSSIntSize::Truncate(width, height)),
403                           Some(imgElem->mPreserveAspectRatio.GetAnimValue()))));
404 
405       // For the actual draw operation to draw crisply (and at the right size),
406       // our destination rect needs to be |width|x|height|, *in dev pixels*.
407       LayoutDeviceSize devPxSize(width, height);
408       nsRect destRect(nsPoint(), LayoutDevicePixel::ToAppUnits(
409                                      devPxSize, appUnitsPerDevPx));
410 
411       // Note: Can't use DrawSingleUnscaledImage for the TYPE_VECTOR case.
412       // That method needs our image to have a fixed native width & height,
413       // and that's not always true for TYPE_VECTOR images.
414       aImgParams.result &= nsLayoutUtils::DrawSingleImage(
415           aContext, PresContext(), mImageContainer,
416           nsLayoutUtils::GetSamplingFilterForFrame(this), destRect,
417           aDirtyRect ? dirtyRect : destRect, context, flags);
418     } else {  // mImageContainer->GetType() == TYPE_RASTER
419       aImgParams.result &= nsLayoutUtils::DrawSingleUnscaledImage(
420           aContext, PresContext(), mImageContainer,
421           nsLayoutUtils::GetSamplingFilterForFrame(this), nsPoint(0, 0),
422           aDirtyRect ? &dirtyRect : nullptr, Nothing(), flags);
423     }
424 
425     if (opacity != 1.0f ||
426         StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
427       aContext.PopGroupAndBlend();
428     }
429     // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext
430   }
431 }
432 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const mozilla::layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder,DisplaySVGGeometry * aItem,bool aDryRun)433 bool SVGImageFrame::CreateWebRenderCommands(
434     mozilla::wr::DisplayListBuilder& aBuilder,
435     mozilla::wr::IpcResourceUpdateQueue& aResources,
436     const mozilla::layers::StackingContextHelper& aSc,
437     mozilla::layers::RenderRootStateManager* aManager,
438     nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem,
439     bool aDryRun) {
440   if (!StyleVisibility()->IsVisible()) {
441     return true;
442   }
443 
444   float opacity = 1.0f;
445   if (SVGUtils::CanOptimizeOpacity(this)) {
446     opacity = StyleEffects()->mOpacity;
447   }
448 
449   if (opacity != 1.0f) {
450     // FIXME: not implemented, might be trivial
451     return false;
452   }
453   if (StyleEffects()->mMixBlendMode != StyleBlend::Normal) {
454     // FIXME: not implemented
455     return false;
456   }
457 
458   // try to setup the image
459   if (!mImageContainer) {
460     nsCOMPtr<imgIRequest> currentRequest;
461     nsCOMPtr<nsIImageLoadingContent> imageLoader =
462         do_QueryInterface(GetContent());
463     if (imageLoader) {
464       imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
465                               getter_AddRefs(currentRequest));
466     }
467 
468     if (currentRequest) {
469       currentRequest->GetImage(getter_AddRefs(mImageContainer));
470     }
471   }
472 
473   if (!mImageContainer) {
474     // nothing to draw (yet)
475     return true;
476   }
477 
478   uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
479 
480   // Compute bounds of the image
481   nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
482   int32_t appUnitsPerCSSPixel = AppUnitsPerCSSPixel();
483 
484   float x, y, width, height;
485   SVGImageElement* imgElem = static_cast<SVGImageElement*>(GetContent());
486   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
487       imgElem, &x, &y, &width, &height);
488   NS_ASSERTION(width > 0 && height > 0,
489                "Should only be painting things with valid width/height");
490 
491   auto toReferenceFrame = aItem->ToReferenceFrame();
492   auto appRect = nsLayoutUtils::RoundGfxRectToAppRect(Rect(0, 0, width, height),
493                                                       appUnitsPerCSSPixel);
494   appRect += toReferenceFrame;
495   auto destRect = LayoutDeviceRect::FromAppUnits(appRect, appUnitsPerDevPx);
496   auto clipRect = destRect;
497 
498   if (StyleDisplay()->IsScrollableOverflow()) {
499     // Apply potential non-trivial clip
500     auto cssClip = SVGUtils::GetClipRectForFrame(this, 0, 0, width, height);
501     auto appClip =
502         nsLayoutUtils::RoundGfxRectToAppRect(cssClip, appUnitsPerCSSPixel);
503     appClip += toReferenceFrame;
504     clipRect = LayoutDeviceRect::FromAppUnits(appClip, appUnitsPerDevPx);
505 
506     // Apply preserveAspectRatio
507     if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
508       int32_t nativeWidth, nativeHeight;
509       if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
510           NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
511           nativeWidth == 0 || nativeHeight == 0) {
512         // Image has no size; nothing to draw
513         return true;
514       }
515 
516       auto preserveAspectRatio = imgElem->mPreserveAspectRatio.GetAnimValue();
517       uint16_t align = preserveAspectRatio.GetAlign();
518       uint16_t meetOrSlice = preserveAspectRatio.GetMeetOrSlice();
519 
520       // default to the defaults
521       if (align == SVG_PRESERVEASPECTRATIO_UNKNOWN) {
522         align = SVG_PRESERVEASPECTRATIO_XMIDYMID;
523       }
524       if (meetOrSlice == SVG_MEETORSLICE_UNKNOWN) {
525         meetOrSlice = SVG_MEETORSLICE_MEET;
526       }
527 
528       // aspect > 1 is horizontal
529       // aspect < 1 is vertical
530       float nativeAspect = ((float)nativeWidth) / ((float)nativeHeight);
531       float viewAspect = width / height;
532 
533       // "Meet" is "fit image to view"; "Slice" is "cover view with image".
534       //
535       // Whether we meet or slice, one side of the destRect will always be
536       // perfectly spanned by our image. The only questions to answer are
537       // "which side won't span perfectly" and "should that side be grown
538       // or shrunk".
539       //
540       // Because we fit our image to the destRect, this all just reduces to:
541       // "if meet, shrink to fit. if slice, grow to fit."
542       if (align != SVG_PRESERVEASPECTRATIO_NONE && nativeAspect != viewAspect) {
543         // Slightly redundant bools, but they make the conditions clearer
544         bool tooTall = nativeAspect > viewAspect;
545         bool tooWide = nativeAspect < viewAspect;
546         if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooTall) ||
547             (meetOrSlice == SVG_MEETORSLICE_SLICE && tooWide)) {
548           // Adjust height and realign y
549           auto oldHeight = destRect.height;
550           destRect.height = destRect.width / nativeAspect;
551           auto heightChange = oldHeight - destRect.height;
552           switch (align) {
553             case SVG_PRESERVEASPECTRATIO_XMINYMIN:
554             case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
555             case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
556               // align to top (no-op)
557               break;
558             case SVG_PRESERVEASPECTRATIO_XMINYMID:
559             case SVG_PRESERVEASPECTRATIO_XMIDYMID:
560             case SVG_PRESERVEASPECTRATIO_XMAXYMID:
561               // align to center
562               destRect.y += heightChange / 2.0f;
563               break;
564             case SVG_PRESERVEASPECTRATIO_XMINYMAX:
565             case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
566             case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
567               // align to bottom
568               destRect.y += heightChange;
569               break;
570             default:
571               MOZ_ASSERT_UNREACHABLE("Unknown value for align");
572           }
573         } else if ((meetOrSlice == SVG_MEETORSLICE_MEET && tooWide) ||
574                    (meetOrSlice == SVG_MEETORSLICE_SLICE && tooTall)) {
575           // Adjust width and realign x
576           auto oldWidth = destRect.width;
577           destRect.width = destRect.height * nativeAspect;
578           auto widthChange = oldWidth - destRect.width;
579           switch (align) {
580             case SVG_PRESERVEASPECTRATIO_XMINYMIN:
581             case SVG_PRESERVEASPECTRATIO_XMINYMID:
582             case SVG_PRESERVEASPECTRATIO_XMINYMAX:
583               // align to left (no-op)
584               break;
585             case SVG_PRESERVEASPECTRATIO_XMIDYMIN:
586             case SVG_PRESERVEASPECTRATIO_XMIDYMID:
587             case SVG_PRESERVEASPECTRATIO_XMIDYMAX:
588               // align to center
589               destRect.x += widthChange / 2.0f;
590               break;
591             case SVG_PRESERVEASPECTRATIO_XMAXYMIN:
592             case SVG_PRESERVEASPECTRATIO_XMAXYMID:
593             case SVG_PRESERVEASPECTRATIO_XMAXYMAX:
594               // align to right
595               destRect.x += widthChange;
596               break;
597             default:
598               MOZ_ASSERT_UNREACHABLE("Unknown value for align");
599           }
600         }
601       }
602     }
603   }
604 
605   Maybe<SVGImageContext> svgContext;
606   if (mImageContainer->GetType() == imgIContainer::TYPE_VECTOR) {
607     if (StaticPrefs::image_svg_blob_image()) {
608       flags |= imgIContainer::FLAG_RECORD_BLOB;
609     }
610     // Forward preserveAspectRatio to inner SVGs
611     svgContext.emplace(Some(CSSIntSize::Truncate(width, height)),
612                        Some(imgElem->mPreserveAspectRatio.GetAnimValue()));
613   }
614 
615   Maybe<ImageIntRegion> region;
616   IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
617       mImageContainer, this, destRect, clipRect, aSc, flags, svgContext,
618       region);
619 
620   RefPtr<layers::ImageContainer> container;
621   ImgDrawResult drawResult = mImageContainer->GetImageContainerAtSize(
622       aManager->LayerManager(), decodeSize, svgContext, region, flags,
623       getter_AddRefs(container));
624 
625   // While we got a container, it may not contain a fully decoded surface. If
626   // that is the case, and we have an image we were previously displaying which
627   // has a fully decoded surface, then we should prefer the previous image.
628   switch (drawResult) {
629     case ImgDrawResult::NOT_READY:
630     case ImgDrawResult::INCOMPLETE:
631     case ImgDrawResult::TEMPORARY_ERROR:
632       // nothing to draw (yet)
633       return true;
634     case ImgDrawResult::NOT_SUPPORTED:
635       // things we haven't implemented for WR yet
636       return false;
637     default:
638       // image is ready to draw
639       break;
640   }
641 
642   // Don't do any actual mutations to state if we're doing a dry run
643   // (used to decide if we're making this into an active layer)
644   if (!aDryRun) {
645     // If the image container is empty, we don't want to fallback. Any other
646     // failure will be due to resource constraints and fallback is unlikely to
647     // help us. Hence we can ignore the return value from PushImage.
648     if (container) {
649       if (flags & imgIContainer::FLAG_RECORD_BLOB) {
650         aManager->CommandBuilder().PushBlobImage(
651             aItem, container, aBuilder, aResources, destRect, clipRect);
652       } else {
653         aManager->CommandBuilder().PushImage(
654             aItem, container, aBuilder, aResources, aSc, destRect, clipRect);
655       }
656     }
657 
658     nsDisplayItemGenericImageGeometry::UpdateDrawResult(aItem, drawResult);
659   }
660 
661   return true;
662 }
663 
GetFrameForPoint(const gfxPoint & aPoint)664 nsIFrame* SVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) {
665   if (!HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) && !GetHitTestFlags()) {
666     return nullptr;
667   }
668 
669   Rect rect;
670   SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
671   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
672       element, &rect.x, &rect.y, &rect.width, &rect.height);
673 
674   if (!rect.Contains(ToPoint(aPoint))) {
675     return nullptr;
676   }
677 
678   // Special case for raster images -- we only want to accept points that fall
679   // in the underlying image's (scaled to fit) native bounds.  That region
680   // doesn't necessarily map to our <image> element's [x,y,width,height] if the
681   // raster image's aspect ratio is being preserved.  We have to look up the
682   // native image size & our viewBox transform in order to filter out points
683   // that fall outside that area.  (This special case doesn't apply to vector
684   // images because they don't limit their drawing to explicit "native
685   // bounds" -- they have an infinite canvas on which to place content.)
686   if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) {
687     if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) {
688       int32_t nativeWidth, nativeHeight;
689       if (NS_FAILED(mImageContainer->GetWidth(&nativeWidth)) ||
690           NS_FAILED(mImageContainer->GetHeight(&nativeHeight)) ||
691           nativeWidth == 0 || nativeHeight == 0) {
692         return nullptr;
693       }
694       mImageContainer->GetResolution().ApplyTo(nativeWidth, nativeHeight);
695       Matrix viewBoxTM = SVGContentUtils::GetViewBoxTransform(
696           rect.width, rect.height, 0, 0, nativeWidth, nativeHeight,
697           element->mPreserveAspectRatio);
698       if (!SVGUtils::HitTestRect(viewBoxTM, 0, 0, nativeWidth, nativeHeight,
699                                  aPoint.x - rect.x, aPoint.y - rect.y)) {
700         return nullptr;
701       }
702     }
703   }
704 
705   return this;
706 }
707 
708 //----------------------------------------------------------------------
709 // SVGGeometryFrame methods:
710 
711 // Lie about our fill/stroke so that covered region and hit detection work
712 // properly
713 
ReflowSVG()714 void SVGImageFrame::ReflowSVG() {
715   NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
716                "This call is probably a wasteful mistake");
717 
718   MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
719              "ReflowSVG mechanism not designed for this");
720 
721   if (!SVGUtils::NeedsReflowSVG(this)) {
722     return;
723   }
724 
725   float x, y, width, height;
726   SVGImageElement* element = static_cast<SVGImageElement*>(GetContent());
727   SVGGeometryProperty::ResolveAll<SVGT::X, SVGT::Y, SVGT::Width, SVGT::Height>(
728       element, &x, &y, &width, &height);
729 
730   Rect extent(x, y, width, height);
731 
732   if (!extent.IsEmpty()) {
733     mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, AppUnitsPerCSSPixel());
734   } else {
735     mRect.SetEmpty();
736   }
737 
738   if (mState & NS_FRAME_FIRST_REFLOW) {
739     // Make sure we have our filter property (if any) before calling
740     // FinishAndStoreOverflow (subsequent filter changes are handled off
741     // nsChangeHint_UpdateEffects):
742     SVGObserverUtils::UpdateEffects(this);
743 
744     if (!mReflowCallbackPosted) {
745       mReflowCallbackPosted = true;
746       PresShell()->PostReflowCallback(this);
747     }
748   }
749 
750   nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
751   OverflowAreas overflowAreas(overflow, overflow);
752   FinishAndStoreOverflow(overflowAreas, mRect.Size());
753 
754   RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
755                   NS_FRAME_HAS_DIRTY_CHILDREN);
756 
757   // Invalidate, but only if this is not our first reflow (since if it is our
758   // first reflow then we haven't had our first paint yet).
759   if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
760     InvalidateFrame();
761   }
762 }
763 
ReflowFinished()764 bool SVGImageFrame::ReflowFinished() {
765   mReflowCallbackPosted = false;
766 
767   // XXX(seth): We don't need this. The purpose of updating visibility
768   // synchronously is to ensure that animated images start animating
769   // immediately. In the short term, however,
770   // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
771   // animations start as soon as the image is painted for the first time, and in
772   // the long term we want to update visibility information from the display
773   // list whenever we paint, so we don't actually need to do this. However, to
774   // avoid behavior changes during the transition from the old image visibility
775   // code, we'll leave it in for now.
776   UpdateVisibilitySynchronously();
777 
778   return false;
779 }
780 
ReflowCallbackCanceled()781 void SVGImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
782 
GetHitTestFlags()783 uint16_t SVGImageFrame::GetHitTestFlags() {
784   uint16_t flags = 0;
785 
786   switch (StyleUI()->mPointerEvents) {
787     case StylePointerEvents::None:
788       break;
789     case StylePointerEvents::Visiblepainted:
790     case StylePointerEvents::Auto:
791       if (StyleVisibility()->IsVisible()) {
792         /* XXX: should check pixel transparency */
793         flags |= SVG_HIT_TEST_FILL;
794       }
795       break;
796     case StylePointerEvents::Visiblefill:
797     case StylePointerEvents::Visiblestroke:
798     case StylePointerEvents::Visible:
799       if (StyleVisibility()->IsVisible()) {
800         flags |= SVG_HIT_TEST_FILL;
801       }
802       break;
803     case StylePointerEvents::Painted:
804       /* XXX: should check pixel transparency */
805       flags |= SVG_HIT_TEST_FILL;
806       break;
807     case StylePointerEvents::Fill:
808     case StylePointerEvents::Stroke:
809     case StylePointerEvents::All:
810       flags |= SVG_HIT_TEST_FILL;
811       break;
812     default:
813       NS_ERROR("not reached");
814       break;
815   }
816 
817   return flags;
818 }
819 
820 //----------------------------------------------------------------------
821 // SVGImageListener implementation
822 
NS_IMPL_ISUPPORTS(SVGImageListener,imgINotificationObserver)823 NS_IMPL_ISUPPORTS(SVGImageListener, imgINotificationObserver)
824 
825 SVGImageListener::SVGImageListener(SVGImageFrame* aFrame) : mFrame(aFrame) {}
826 
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)827 void SVGImageListener::Notify(imgIRequest* aRequest, int32_t aType,
828                               const nsIntRect* aData) {
829   if (!mFrame) {
830     return;
831   }
832 
833   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
834     mFrame->InvalidateFrame();
835     nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
836                                     RestyleHint{0},
837                                     nsChangeHint_InvalidateRenderingObservers);
838     SVGUtils::ScheduleReflowSVG(mFrame);
839   }
840 
841   if (aType == imgINotificationObserver::FRAME_UPDATE) {
842     // No new dimensions, so we don't need to call
843     // SVGUtils::InvalidateAndScheduleBoundsUpdate.
844     nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
845                                     RestyleHint{0},
846                                     nsChangeHint_InvalidateRenderingObservers);
847     mFrame->InvalidateFrame();
848   }
849 
850   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
851     // Called once the resource's dimensions have been obtained.
852     nsCOMPtr<imgIContainer> image;
853     aRequest->GetImage(getter_AddRefs(image));
854     if (image) {
855       image = nsLayoutUtils::OrientImage(
856           image, mFrame->StyleVisibility()->mImageOrientation);
857       image->SetAnimationMode(mFrame->PresContext()->ImageAnimationMode());
858       mFrame->mImageContainer = std::move(image);
859     }
860     mFrame->InvalidateFrame();
861     nsLayoutUtils::PostRestyleEvent(mFrame->GetContent()->AsElement(),
862                                     RestyleHint{0},
863                                     nsChangeHint_InvalidateRenderingObservers);
864     SVGUtils::ScheduleReflowSVG(mFrame);
865   }
866 }
867 
868 }  // namespace mozilla
869