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