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