nsImageFrame.cpp (111338B)
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 /* rendering object for replaced elements with image data */ 8 9 #include "nsImageFrame.h" 10 11 #include <algorithm> 12 13 #include "TextDrawTarget.h" 14 #include "gfx2DGlue.h" 15 #include "gfxContext.h" 16 #include "gfxUtils.h" 17 #include "mozilla/ComputedStyle.h" 18 #include "mozilla/DebugOnly.h" 19 #include "mozilla/Encoding.h" 20 #include "mozilla/HTMLEditor.h" 21 #include "mozilla/MouseEvents.h" 22 #include "mozilla/PresShell.h" 23 #include "mozilla/PresShellInlines.h" 24 #include "mozilla/SVGImageContext.h" 25 #include "mozilla/StaticPrefs_browser.h" 26 #include "mozilla/StaticPrefs_image.h" 27 #include "mozilla/StaticPrefs_layout.h" 28 #include "mozilla/dom/Document.h" 29 #include "mozilla/dom/FetchPriority.h" 30 #include "mozilla/dom/GeneratedImageContent.h" 31 #include "mozilla/dom/HTMLAreaElement.h" 32 #include "mozilla/dom/HTMLImageElement.h" 33 #include "mozilla/dom/LargestContentfulPaint.h" 34 #include "mozilla/dom/NameSpaceConstants.h" 35 #include "mozilla/dom/ReferrerInfo.h" 36 #include "mozilla/dom/ResponsiveImageSelector.h" 37 #include "mozilla/dom/ViewTransition.h" 38 #include "mozilla/gfx/2D.h" 39 #include "mozilla/gfx/Helpers.h" 40 #include "mozilla/gfx/PathHelpers.h" 41 #include "mozilla/image/WebRenderImageProvider.h" 42 #include "mozilla/intl/BidiEmbeddingLevel.h" 43 #include "mozilla/layers/RenderRootStateManager.h" 44 #include "mozilla/layers/WebRenderLayerManager.h" 45 #include "nsCOMPtr.h" 46 #include "nsCSSAnonBoxes.h" 47 #include "nsCSSRendering.h" 48 #include "nsContentUtils.h" 49 #include "nsFontMetrics.h" 50 #include "nsGkAtoms.h" 51 #include "nsIFrameInlines.h" 52 #include "nsIImageLoadingContent.h" 53 #include "nsILoadGroup.h" 54 #include "nsImageLoadingContent.h" 55 #include "nsImageMap.h" 56 #include "nsImageRenderer.h" 57 #include "nsNameSpaceManager.h" 58 #include "nsNetCID.h" 59 #include "nsNetUtil.h" 60 #include "nsObjectLoadingContent.h" 61 #include "nsPresContext.h" 62 #include "nsPrintfCString.h" 63 #include "nsString.h" 64 #include "nsStyleConsts.h" 65 #include "nsStyleUtil.h" 66 #include "nsTransform2D.h" 67 #ifdef ACCESSIBILITY 68 # include "nsAccessibilityService.h" 69 #endif 70 #include "DisplayListClipState.h" 71 #include "ImageContainer.h" 72 #include "ImageRegion.h" 73 #include "gfxRect.h" 74 #include "imgIContainer.h" 75 #include "imgLoader.h" 76 #include "imgRequestProxy.h" 77 #include "mozilla/ServoStyleSet.h" 78 #include "mozilla/dom/BrowserChild.h" 79 #include "mozilla/dom/HTMLAnchorElement.h" 80 #include "mozilla/dom/Link.h" 81 #include "mozilla/dom/Selection.h" 82 #include "nsBidiPresUtils.h" 83 #include "nsBidiUtils.h" 84 #include "nsBlockFrame.h" 85 #include "nsCSSFrameConstructor.h" 86 #include "nsDisplayList.h" 87 #include "nsError.h" 88 #include "nsIContent.h" 89 #include "nsIURIMutator.h" 90 #include "nsLayoutUtils.h" 91 #include "nsRange.h" 92 #include "nsStyleStructInlines.h" 93 94 using namespace mozilla; 95 using namespace mozilla::dom; 96 using namespace mozilla::gfx; 97 using namespace mozilla::image; 98 using namespace mozilla::layers; 99 100 using mozilla::layout::TextDrawTarget; 101 102 static constexpr wr::ImageKey kNoKey{{0}, 0}; 103 104 class nsDisplayGradient final : public nsPaintedDisplayItem { 105 public: 106 nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame) 107 : nsPaintedDisplayItem(aBuilder, aFrame) { 108 MOZ_COUNT_CTOR(nsDisplayGradient); 109 } 110 111 MOZ_COUNTED_DTOR_FINAL(nsDisplayGradient) 112 113 nsRect GetBounds(bool* aSnap) const { 114 *aSnap = true; 115 return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame(); 116 } 117 118 nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final { 119 return GetBounds(aSnap); 120 } 121 122 void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final; 123 124 bool CreateWebRenderCommands(wr::DisplayListBuilder&, 125 wr::IpcResourceUpdateQueue&, 126 const StackingContextHelper&, 127 layers::RenderRootStateManager*, 128 nsDisplayListBuilder*) final; 129 130 NS_DISPLAY_DECL_NAME("Gradient", TYPE_GRADIENT) 131 }; 132 133 void nsDisplayGradient::Paint(nsDisplayListBuilder* aBuilder, 134 gfxContext* aCtx) { 135 auto* frame = static_cast<nsImageFrame*>(Frame()); 136 nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(), 137 aBuilder->GetImageRendererFlags()); 138 nsSize size = frame->GetSize(); 139 imageRenderer.SetPreferredSize({}, size); 140 141 ImgDrawResult result; 142 if (!imageRenderer.PrepareImage()) { 143 result = imageRenderer.PrepareResult(); 144 } else { 145 nsRect dest(ToReferenceFrame(), size); 146 result = imageRenderer.DrawLayer( 147 frame->PresContext(), *aCtx, dest, dest, dest.TopLeft(), 148 GetPaintRect(aBuilder, aCtx), dest.Size(), /* aOpacity = */ 1.0f); 149 } 150 (void)result; 151 } 152 153 bool nsDisplayGradient::CreateWebRenderCommands( 154 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 155 const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager, 156 nsDisplayListBuilder* aDisplayListBuilder) { 157 auto* frame = static_cast<nsImageFrame*>(Frame()); 158 nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(), 159 aDisplayListBuilder->GetImageRendererFlags()); 160 nsSize size = frame->GetSize(); 161 imageRenderer.SetPreferredSize({}, size); 162 163 ImgDrawResult result; 164 if (!imageRenderer.PrepareImage()) { 165 result = imageRenderer.PrepareResult(); 166 } else { 167 nsRect dest(ToReferenceFrame(), size); 168 result = imageRenderer.BuildWebRenderDisplayItemsForLayer( 169 frame->PresContext(), aBuilder, aResources, aSc, aManager, this, dest, 170 dest, dest.TopLeft(), dest, dest.Size(), 171 /* aOpacity = */ 1.0f); 172 if (result == ImgDrawResult::NOT_SUPPORTED) { 173 return false; 174 } 175 } 176 return true; 177 } 178 179 // sizes (pixels) for image icon, padding and border frame 180 #define ICON_SIZE (16) 181 #define ICON_PADDING (3) 182 #define ALT_BORDER_WIDTH (1) 183 184 // Default alignment value (so we can tell an unset value from a set value) 185 #define ALIGN_UNSET uint8_t(-1) 186 187 class BrokenImageIcon final : public imgINotificationObserver { 188 // private class that wraps the data and logic needed for 189 // broken image and loading image icons 190 public: 191 explicit BrokenImageIcon(const nsImageFrame& aFrame); 192 static void Shutdown(); 193 194 NS_DECL_ISUPPORTS 195 NS_DECL_IMGINOTIFICATIONOBSERVER 196 197 static imgRequestProxy* GetImage(nsImageFrame* aFrame) { 198 return Get(*aFrame).mImage.get(); 199 } 200 201 static void AddObserver(nsImageFrame* aFrame) { 202 auto& instance = Get(*aFrame); 203 MOZ_ASSERT(!instance.mObservers.Contains(aFrame), 204 "Observer shouldn't aleady be in array"); 205 instance.mObservers.AppendElement(aFrame); 206 } 207 208 static void RemoveObserver(nsImageFrame* aFrame) { 209 auto& instance = Get(*aFrame); 210 DebugOnly<bool> didRemove = instance.mObservers.RemoveElement(aFrame); 211 MOZ_ASSERT(didRemove, "Observer not in array"); 212 } 213 214 private: 215 static BrokenImageIcon& Get(const nsImageFrame& aFrame) { 216 if (!gSingleton) { 217 gSingleton = new BrokenImageIcon(aFrame); 218 } 219 return *gSingleton; 220 } 221 222 ~BrokenImageIcon() = default; 223 224 nsTObserverArray<nsImageFrame*> mObservers; 225 RefPtr<imgRequestProxy> mImage; 226 227 static StaticRefPtr<BrokenImageIcon> gSingleton; 228 }; 229 230 StaticRefPtr<BrokenImageIcon> BrokenImageIcon::gSingleton; 231 232 NS_IMPL_ISUPPORTS(BrokenImageIcon, imgINotificationObserver) 233 234 BrokenImageIcon::BrokenImageIcon(const nsImageFrame& aFrame) { 235 constexpr auto brokenSrc = u"resource://gre-resources/broken-image.png"_ns; 236 nsCOMPtr<nsIURI> realURI; 237 NS_NewURI(getter_AddRefs(realURI), brokenSrc); 238 239 MOZ_ASSERT(realURI, "how?"); 240 if (NS_WARN_IF(!realURI)) { 241 return; 242 } 243 244 nsPresContext* pc = aFrame.PresContext(); 245 Document* doc = pc->Document(); 246 RefPtr<imgLoader> il = nsContentUtils::GetImgLoaderForDocument(doc); 247 248 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup(); 249 250 // For icon loads, we don't need to merge with the loadgroup flags 251 const nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; 252 const nsContentPolicyType contentPolicyType = 253 nsIContentPolicy::TYPE_INTERNAL_IMAGE; 254 255 nsresult rv = 256 il->LoadImage(realURI, /* icon URI */ 257 nullptr, /* initial document URI; this is only 258 relevant for cookies, so does not 259 apply to icons. */ 260 nullptr, /* referrer (not relevant for icons) */ 261 nullptr, /* principal (not relevant for icons) */ 262 0, loadGroup, this, nullptr, /* No context */ 263 nullptr, /* Not associated with any particular document */ 264 loadFlags, nullptr, contentPolicyType, u""_ns, 265 false, /* aUseUrgentStartForChannel */ 266 false, /* aLinkPreload */ 267 0, FetchPriority::Auto, getter_AddRefs(mImage)); 268 (void)NS_WARN_IF(NS_FAILED(rv)); 269 } 270 271 void BrokenImageIcon::Shutdown() { 272 if (!gSingleton) { 273 return; 274 } 275 if (gSingleton->mImage) { 276 gSingleton->mImage->CancelAndForgetObserver(NS_ERROR_FAILURE); 277 gSingleton->mImage = nullptr; 278 } 279 gSingleton = nullptr; 280 } 281 282 void BrokenImageIcon::Notify(imgIRequest* aRequest, int32_t aType, 283 const nsIntRect* aData) { 284 MOZ_ASSERT(aRequest); 285 286 if (aType != imgINotificationObserver::LOAD_COMPLETE && 287 aType != imgINotificationObserver::FRAME_UPDATE) { 288 return; 289 } 290 291 if (aType == imgINotificationObserver::LOAD_COMPLETE) { 292 nsCOMPtr<imgIContainer> image; 293 aRequest->GetImage(getter_AddRefs(image)); 294 if (!image) { 295 return; 296 } 297 298 // Retrieve the image's intrinsic size. 299 int32_t width = 0; 300 int32_t height = 0; 301 image->GetWidth(&width); 302 image->GetHeight(&height); 303 304 // Request a decode at that size. 305 image->RequestDecodeForSize(IntSize(width, height), 306 imgIContainer::DECODE_FLAGS_DEFAULT | 307 imgIContainer::FLAG_HIGH_QUALITY_SCALING); 308 } 309 310 for (nsImageFrame* frame : mObservers.ForwardRange()) { 311 frame->InvalidateFrame(); 312 } 313 } 314 315 // test if the width and height are fixed, looking at the style data 316 // This is used by nsImageFrame::ImageFrameTypeFor and should not be used for 317 // layout decisions. 318 static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition, 319 const AnchorPosResolutionParams& aParams) { 320 // check the width and height values in the reflow input's style struct 321 // - if width and height are specified as either coord or percentage, then 322 // the size of the image frame is constrained 323 return aStylePosition->GetWidth(aParams)->IsLengthPercentage() && 324 aStylePosition->GetHeight(aParams)->IsLengthPercentage(); 325 } 326 327 template <typename SizeOrMaxSize> 328 static bool DependsOnIntrinsicSize(const SizeOrMaxSize& aMinOrMaxSize) { 329 auto length = nsIFrame::ToExtremumLength(aMinOrMaxSize); 330 if (!length) { 331 return false; 332 } 333 switch (*length) { 334 case nsIFrame::ExtremumLength::MinContent: 335 case nsIFrame::ExtremumLength::MaxContent: 336 case nsIFrame::ExtremumLength::FitContent: 337 case nsIFrame::ExtremumLength::FitContentFunction: 338 return true; 339 case nsIFrame::ExtremumLength::MozAvailable: 340 case nsIFrame::ExtremumLength::Stretch: 341 return false; 342 } 343 MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?"); 344 return false; 345 } 346 347 // Decide whether we can optimize away reflows that result from the 348 // image's intrinsic size changing. 349 static bool SizeDependsOnIntrinsicSize(const ReflowInput& aReflowInput) { 350 const auto& position = *aReflowInput.mStylePosition; 351 const auto anchorResolutionParams = 352 AnchorPosResolutionParams::From(&aReflowInput); 353 WritingMode wm = aReflowInput.GetWritingMode(); 354 // Don't try to make this optimization when an image has percentages 355 // in its 'width' or 'height'. The percentages might be treated like 356 // auto (especially for intrinsic width calculations and for heights). 357 // 358 // min-width: min-content and such can also affect our intrinsic size. 359 // but note that those keywords on the block axis behave like auto, so we 360 // don't need to check them. 361 // 362 // Flex item's min-[width|height]:auto resolution depends on intrinsic size. 363 return !position.GetHeight(anchorResolutionParams)->ConvertsToLength() || 364 !position.GetWidth(anchorResolutionParams)->ConvertsToLength() || 365 DependsOnIntrinsicSize( 366 *position.MinISize(wm, anchorResolutionParams)) || 367 DependsOnIntrinsicSize( 368 *position.MaxISize(wm, anchorResolutionParams)) || 369 aReflowInput.mFrame->IsFlexItem(); 370 } 371 372 nsIFrame* NS_NewImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 373 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), 374 nsImageFrame::Kind::ImageLoadingContent); 375 } 376 377 static bool ShouldCreateImageFrameForContentProperty( 378 const ComputedStyle& aStyle) { 379 Span<const StyleContentItem> items = 380 aStyle.StyleContent()->NonAltContentItems(); 381 return items.Length() == 1 && items[0].IsImage(); 382 } 383 384 nsIFrame* NS_NewXULImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 385 auto kind = ShouldCreateImageFrameForContentProperty(*aStyle) 386 ? nsImageFrame::Kind::ContentProperty 387 : nsImageFrame::Kind::XULImage; 388 return new (aPresShell) 389 nsImageFrame(aStyle, aPresShell->GetPresContext(), kind); 390 } 391 392 nsIFrame* NS_NewImageFrameForContentProperty(PresShell* aPresShell, 393 ComputedStyle* aStyle) { 394 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), 395 nsImageFrame::Kind::ContentProperty); 396 } 397 398 nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell, 399 ComputedStyle* aStyle) { 400 return new (aPresShell) 401 nsImageFrame(aStyle, aPresShell->GetPresContext(), 402 nsImageFrame::Kind::ContentPropertyAtIndex); 403 } 404 405 nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell, 406 ComputedStyle* aStyle) { 407 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), 408 nsImageFrame::Kind::ListStyleImage); 409 } 410 411 nsIFrame* NS_NewImageFrameForViewTransition(PresShell* aPresShell, 412 ComputedStyle* aStyle) { 413 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), 414 nsImageFrame::Kind::ViewTransition); 415 } 416 417 bool nsImageFrame::ShouldShowBrokenImageIcon() const { 418 // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and 419 // Blink behave differently here for content: url(..), for now adapt to 420 // Blink's behavior. 421 if (mKind != Kind::ImageLoadingContent) { 422 return false; 423 } 424 425 if (!StaticPrefs::browser_display_show_image_placeholders()) { 426 return false; 427 } 428 429 // <img alt=""> is special, and it shouldn't draw the broken image icon, 430 // unlike the no-alt attribute or non-empty-alt-attribute case. 431 if (auto* image = HTMLImageElement::FromNode(mContent)) { 432 const nsAttrValue* alt = image->GetParsedAttr(nsGkAtoms::alt); 433 if (alt && alt->IsEmptyString()) { 434 return false; 435 } 436 } 437 438 // check for broken images. valid null images (eg. img src="") are 439 // not considered broken because they have no image requests 440 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { 441 uint32_t imageStatus; 442 return NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) && 443 (imageStatus & imgIRequest::STATUS_ERROR); 444 } 445 446 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); 447 MOZ_ASSERT(imageLoader); 448 // Show the broken image icon only if we've tried to perform a load at all 449 // (that is, if we have a current uri). 450 nsCOMPtr<nsIURI> currentURI = imageLoader->GetCurrentURI(); 451 return !!currentURI; 452 } 453 454 nsImageFrame* nsImageFrame::CreateContinuingFrame( 455 mozilla::PresShell* aPresShell, ComputedStyle* aStyle) const { 456 return new (aPresShell) 457 nsImageFrame(aStyle, aPresShell->GetPresContext(), mKind); 458 } 459 460 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame) 461 462 nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, 463 ClassID aID, Kind aKind) 464 : nsAtomicContainerFrame(aStyle, aPresContext, aID), 465 mIntrinsicSize(0, 0), 466 mKind(aKind) { 467 EnableVisibilityTracking(); 468 } 469 470 nsImageFrame::~nsImageFrame() = default; 471 472 NS_QUERYFRAME_HEAD(nsImageFrame) 473 NS_QUERYFRAME_ENTRY(nsImageFrame) 474 NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) 475 476 #ifdef ACCESSIBILITY 477 a11y::AccType nsImageFrame::AccessibleType() { 478 if (mKind == Kind::ListStyleImage) { 479 // This is an HTMLListBulletAccessible. 480 return a11y::eNoType; 481 } 482 483 if (mKind == Kind::ViewTransition) { 484 // View transitions don't show up in the a11y tree. 485 return a11y::eNoType; 486 } 487 488 // Don't use GetImageMap() to avoid reentrancy into accessibility. 489 if (HasImageMap()) { 490 return a11y::eHTMLImageMapType; 491 } 492 493 return a11y::eImageType; 494 } 495 #endif 496 497 void nsImageFrame::DisconnectMap() { 498 if (!mImageMap) { 499 return; 500 } 501 502 mImageMap->Destroy(); 503 mImageMap = nullptr; 504 505 #ifdef ACCESSIBILITY 506 if (nsAccessibilityService* accService = GetAccService()) { 507 accService->RecreateAccessible(PresShell(), mContent); 508 } 509 #endif 510 } 511 512 void nsImageFrame::Destroy(DestroyContext& aContext) { 513 MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing()); 514 515 if (mReflowCallbackPosted) { 516 PresShell()->CancelReflowCallback(this); 517 mReflowCallbackPosted = false; 518 } 519 520 // Tell our image map, if there is one, to clean up 521 // This causes the nsImageMap to unregister itself as 522 // a DOM listener. 523 DisconnectMap(); 524 525 MOZ_ASSERT(mListener); 526 527 if (mKind == Kind::ImageLoadingContent) { 528 MOZ_ASSERT(!mOwnedRequest); 529 MOZ_ASSERT(!mOwnedRequestRegistered); 530 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); 531 MOZ_ASSERT(imageLoader); 532 533 // Notify our image loading content that we are going away so it can 534 // deregister with our refresh driver. 535 imageLoader->FrameDestroyed(this); 536 imageLoader->RemoveNativeObserver(mListener); 537 } else { 538 DeinitOwnedRequest(); 539 } 540 541 // set the frame to null so we don't send messages to a dead object. 542 mListener->SetFrame(nullptr); 543 mListener = nullptr; 544 545 // If we were displaying an icon, take ourselves off the list 546 if (mDisplayingIcon) { 547 BrokenImageIcon::RemoveObserver(this); 548 } 549 550 nsAtomicContainerFrame::Destroy(aContext); 551 } 552 553 void nsImageFrame::DeinitOwnedRequest() { 554 MOZ_ASSERT(mKind != Kind::ImageLoadingContent); 555 if (!mOwnedRequest) { 556 return; 557 } 558 PresContext()->Document()->UntrackImage(mOwnedRequest); 559 nsLayoutUtils::DeregisterImageRequest(PresContext(), mOwnedRequest, 560 &mOwnedRequestRegistered); 561 mOwnedRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 562 mOwnedRequest = nullptr; 563 } 564 565 void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 566 nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle); 567 568 // A list-style-image ::marker default size is calculated from the font's 569 // em-size, which might have changed here. 570 if (mKind == Kind::ListStyleImage) { 571 UpdateIntrinsicSize(); 572 } 573 574 // Normal "owned" images reframe when `content` or `list-style-image` change, 575 // but XUL images don't (and we don't really need to). So deal with the 576 // dynamic list-style-image change in that case. 577 // 578 // TODO(emilio): We might want to do the same for regular list-style-image or 579 // even simple content: url() changes. 580 if (mKind == Kind::XULImage && aOldStyle) { 581 if (!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src) && 582 aOldStyle->StyleList()->mListStyleImage != 583 StyleList()->mListStyleImage) { 584 UpdateXULImage(); 585 } 586 // If we have no image our intrinsic size might be themed. We need to 587 // update the size even if the effective appearance hasn't changed to 588 // deal correctly with theme changes. 589 if (!mOwnedRequest) { 590 UpdateIntrinsicSize(); 591 } 592 } 593 594 // We need to update our orientation either if we had no ComputedStyle before 595 // because this is the first time it's been set, or if the image-orientation 596 // property changed from its previous value. 597 bool shouldUpdateOrientation = false; 598 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest(); 599 const auto newOrientation = 600 StyleVisibility()->UsedImageOrientation(currentRequest); 601 if (mImage) { 602 if (aOldStyle) { 603 auto oldOrientation = 604 aOldStyle->StyleVisibility()->UsedImageOrientation(currentRequest); 605 shouldUpdateOrientation = oldOrientation != newOrientation; 606 } else { 607 shouldUpdateOrientation = true; 608 } 609 } 610 611 if (shouldUpdateOrientation) { 612 nsCOMPtr<imgIContainer> image(mImage->Unwrap()); 613 mImage = nsLayoutUtils::OrientImage(image, newOrientation); 614 615 UpdateIntrinsicSize(); 616 UpdateIntrinsicRatio(); 617 } else if (!aOldStyle || aOldStyle->StylePosition()->mAspectRatio != 618 StylePosition()->mAspectRatio) { 619 UpdateIntrinsicRatio(); 620 } 621 } 622 623 static bool SizeIsAvailable(imgIRequest* aRequest) { 624 if (!aRequest) { 625 return false; 626 } 627 628 uint32_t imageStatus = 0; 629 nsresult rv = aRequest->GetImageStatus(&imageStatus); 630 return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE); 631 } 632 633 const StyleImage* nsImageFrame::GetImageFromStyle() const { 634 switch (mKind) { 635 case Kind::ViewTransition: 636 break; 637 case Kind::ImageLoadingContent: 638 break; 639 case Kind::ListStyleImage: 640 MOZ_ASSERT( 641 GetParent()->GetContent()->IsGeneratedContentContainerForMarker()); 642 MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)); 643 return &StyleList()->mListStyleImage; 644 case Kind::XULImage: 645 MOZ_ASSERT(!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src)); 646 return &StyleList()->mListStyleImage; 647 case Kind::ContentProperty: 648 case Kind::ContentPropertyAtIndex: { 649 uint32_t contentIndex = 0; 650 const nsStyleContent* styleContent = StyleContent(); 651 if (mKind == Kind::ContentPropertyAtIndex) { 652 MOZ_RELEASE_ASSERT( 653 mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)); 654 contentIndex = 655 static_cast<GeneratedImageContent*>(mContent.get())->Index(); 656 657 // TODO(emilio): Consider inheriting the `content` property instead of 658 // doing this parent traversal? 659 nsIFrame* parent = GetParent(); 660 MOZ_DIAGNOSTIC_ASSERT( 661 parent->GetContent()->IsGeneratedContentContainerForMarker() || 662 parent->GetContent()->IsGeneratedContentContainerForAfter() || 663 parent->GetContent()->IsGeneratedContentContainerForBefore()); 664 nsIFrame* nonAnonymousParent = parent; 665 while (nonAnonymousParent->Style()->IsAnonBox()) { 666 nonAnonymousParent = nonAnonymousParent->GetParent(); 667 } 668 MOZ_DIAGNOSTIC_ASSERT(parent->GetContent() == 669 nonAnonymousParent->GetContent()); 670 styleContent = nonAnonymousParent->StyleContent(); 671 } 672 auto items = styleContent->NonAltContentItems(); 673 MOZ_RELEASE_ASSERT(contentIndex < items.Length()); 674 const auto& contentItem = items[contentIndex]; 675 MOZ_RELEASE_ASSERT(contentItem.IsImage()); 676 return &contentItem.AsImage(); 677 } 678 } 679 MOZ_ASSERT_UNREACHABLE("Don't call me"); 680 return nullptr; 681 } 682 683 void nsImageFrame::UpdateXULImage() { 684 MOZ_ASSERT(mKind == Kind::XULImage); 685 DeinitOwnedRequest(); 686 687 nsAutoString src; 688 nsPresContext* pc = PresContext(); 689 if (mContent->AsElement()->GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) { 690 nsContentPolicyType contentPolicyType; 691 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 692 uint64_t requestContextID = 0; 693 nsContentUtils::GetContentPolicyTypeForUIImageLoading( 694 mContent, getter_AddRefs(triggeringPrincipal), contentPolicyType, 695 &requestContextID); 696 nsCOMPtr<nsIURI> uri; 697 nsContentUtils::NewURIWithDocumentCharset( 698 getter_AddRefs(uri), src, pc->Document(), mContent->GetBaseURI()); 699 if (uri) { 700 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mContent->AsElement()); 701 nsContentUtils::LoadImage( 702 uri, mContent, pc->Document(), triggeringPrincipal, requestContextID, 703 referrerInfo, mListener, nsIRequest::LOAD_NORMAL, u""_ns, 704 getter_AddRefs(mOwnedRequest), contentPolicyType); 705 SetupOwnedRequest(); 706 } 707 } else { 708 const auto* image = GetImageFromStyle(); 709 if (image->IsImageRequestType()) { 710 if (imgRequestProxy* proxy = image->GetImageRequest()) { 711 proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest)); 712 SetupOwnedRequest(); 713 } 714 } 715 } 716 717 if (!mOwnedRequest) { 718 UpdateImage(nullptr, nullptr); 719 } 720 } 721 722 void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 723 nsIFrame* aPrevInFlow) { 724 MOZ_ASSERT_IF(aPrevInFlow, 725 aPrevInFlow->Type() == Type() && 726 static_cast<nsImageFrame*>(aPrevInFlow)->mKind == mKind); 727 728 nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); 729 730 mListener = new nsImageListener(this); 731 732 GetImageMap(); // Ensure to init the image map asap. This is important to 733 // make <area> elements focusable. 734 735 if (StaticPrefs::layout_image_eager_broken_image_icon()) { 736 (void)BrokenImageIcon::GetImage(this); 737 } 738 739 nsPresContext* pc = PresContext(); 740 if (mKind == Kind::ImageLoadingContent) { 741 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent); 742 MOZ_ASSERT(imageLoader); 743 imageLoader->AddNativeObserver(mListener); 744 // We have a PresContext now, so we need to notify the image content node 745 // that it can register images. 746 imageLoader->FrameCreated(this); 747 AssertSyncDecodingHintIsInSync(); 748 if (nsIDocShell* docShell = pc->GetDocShell()) { 749 RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext(); 750 mIsInObjectOrEmbed = bc->IsEmbedderTypeObjectOrEmbed() && 751 pc->Document()->IsImageDocument(); 752 } 753 } else if (mKind == Kind::XULImage) { 754 UpdateXULImage(); 755 } else if (mKind == Kind::ViewTransition) { 756 // View transitions have a surface directly. 757 } else { 758 const StyleImage* image = GetImageFromStyle(); 759 if (image->IsImageRequestType()) { 760 if (imgRequestProxy* proxy = image->GetImageRequest()) { 761 proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest)); 762 SetupOwnedRequest(); 763 } 764 } 765 } 766 767 // Give image loads associated with an image frame a small priority boost. 768 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { 769 uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT; 770 771 // Increase load priority further if intrinsic size might be important for 772 // layout. 773 if (!HaveSpecifiedSize(StylePosition(), 774 AnchorPosResolutionParams::From(this))) { 775 categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY; 776 } 777 778 currentRequest->BoostPriority(categoryToBoostPriority); 779 } 780 781 MaybeSendIntrinsicSizeAndRatioToEmbedder(); 782 } 783 784 void nsImageFrame::SetupOwnedRequest() { 785 MOZ_ASSERT(mKind != Kind::ImageLoadingContent); 786 if (!mOwnedRequest) { 787 return; 788 } 789 790 // We're not using AssociateRequestToFrame for the content property, so we 791 // need to add it to the image tracker manually. 792 PresContext()->Document()->TrackImage(mOwnedRequest); 793 794 uint32_t status = 0; 795 nsresult rv = mOwnedRequest->GetImageStatus(&status); 796 if (NS_FAILED(rv)) { 797 return; 798 } 799 800 if (status & imgIRequest::STATUS_SIZE_AVAILABLE) { 801 nsCOMPtr<imgIContainer> image; 802 mOwnedRequest->GetImage(getter_AddRefs(image)); 803 OnSizeAvailable(mOwnedRequest, image); 804 } 805 806 if (status & imgIRequest::STATUS_FRAME_COMPLETE) { 807 mFirstFrameComplete = true; 808 } 809 810 if (status & imgIRequest::STATUS_IS_ANIMATED) { 811 nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest, 812 &mOwnedRequestRegistered); 813 } 814 } 815 816 static void ScaleIntrinsicSizeForDensity(IntrinsicSize& aSize, 817 const ImageResolution& aResolution) { 818 if (aSize.width) { 819 aResolution.ApplyXTo(aSize.width.ref()); 820 } 821 if (aSize.height) { 822 aResolution.ApplyYTo(aSize.height.ref()); 823 } 824 } 825 826 static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage, 827 nsIContent& aContent, 828 IntrinsicSize& aSize) { 829 ImageResolution resolution = aImage->GetResolution(); 830 if (auto* image = HTMLImageElement::FromNode(aContent)) { 831 if (auto* selector = image->GetResponsiveImageSelector()) { 832 resolution.ScaleBy(selector->GetSelectedImageDensity()); 833 } 834 } 835 ScaleIntrinsicSizeForDensity(aSize, resolution); 836 } 837 838 static nscoord ListImageDefaultLength(const nsImageFrame& aFrame) { 839 // https://drafts.csswg.org/css-lists-3/#image-markers 840 // The spec says we should use 1em x 1em, but that seems too large. 841 // See disussion in https://github.com/w3c/csswg-drafts/issues/4207 842 auto* pc = aFrame.PresContext(); 843 RefPtr<nsFontMetrics> fm = 844 nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc); 845 RefPtr<gfxFont> font = fm->GetThebesFontGroup()->GetFirstValidFont(); 846 auto emAU = 847 font->GetMetrics(fm->Orientation()).emHeight * pc->AppUnitsPerDevPixel(); 848 return std::max(NSToCoordRound(0.4f * emAU), 849 nsPresContext::CSSPixelsToAppUnits(1)); 850 } 851 852 IntrinsicSize nsImageFrame::ComputeIntrinsicSize( 853 bool aIgnoreContainment) const { 854 const auto containAxes = 855 aIgnoreContainment ? ContainSizeAxes(false, false) : GetContainSizeAxes(); 856 if (containAxes.IsBoth()) { 857 return FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); 858 } 859 860 nsSize size; 861 if (mImage && NS_SUCCEEDED(mImage->GetIntrinsicSizeInAppUnits(&size))) { 862 IntrinsicSize intrinsicSize; 863 intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width); 864 intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height); 865 if (mKind == nsImageFrame::Kind::ListStyleImage) { 866 if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) { 867 nscoord defaultLength = ListImageDefaultLength(*this); 868 if (intrinsicSize.width.isNothing()) { 869 intrinsicSize.width = Some(defaultLength); 870 } 871 if (intrinsicSize.height.isNothing()) { 872 intrinsicSize.height = Some(defaultLength); 873 } 874 } 875 } 876 if (mKind == nsImageFrame::Kind::ImageLoadingContent || 877 (mKind == nsImageFrame::Kind::XULImage && 878 GetContent()->AsElement()->HasNonEmptyAttr(nsGkAtoms::src))) { 879 ScaleIntrinsicSizeForDensity(mImage, *GetContent(), intrinsicSize); 880 } else { 881 // We don't include zoom here because FinishIntrinsicSize already does it 882 // for us. 883 ScaleIntrinsicSizeForDensity( 884 intrinsicSize, 885 GetImageFromStyle()->GetResolution(/* aStyleForZoom = */ nullptr)); 886 } 887 return FinishIntrinsicSize(containAxes, intrinsicSize); 888 } 889 890 if (auto size = GetViewTransitionBorderBoxSize()) { 891 IntrinsicSize intrinsicSize; 892 intrinsicSize.width.emplace(size->width); 893 intrinsicSize.height.emplace(size->height); 894 return FinishIntrinsicSize(containAxes, intrinsicSize); 895 } 896 897 if (mKind == nsImageFrame::Kind::ListStyleImage) { 898 // Note: images are handled above, this handles gradients etc. 899 const nscoord defaultLength = ListImageDefaultLength(*this); 900 return FinishIntrinsicSize(containAxes, 901 IntrinsicSize(defaultLength, defaultLength)); 902 } 903 904 if (mKind == nsImageFrame::Kind::XULImage && IsThemed()) { 905 nsPresContext* pc = PresContext(); 906 // FIXME: const_cast here is a bit evil but IsThemed and so does the same. 907 const auto widgetSize = pc->Theme()->GetMinimumWidgetSize( 908 pc, const_cast<nsImageFrame*>(this), 909 StyleDisplay()->EffectiveAppearance()); 910 const IntrinsicSize intrinsicSize( 911 LayoutDeviceIntSize::ToAppUnits(widgetSize, pc->AppUnitsPerDevPixel())); 912 return FinishIntrinsicSize(containAxes, intrinsicSize); 913 } 914 915 if (ShouldShowBrokenImageIcon()) { 916 nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( 917 ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); 918 return FinishIntrinsicSize(containAxes, 919 IntrinsicSize(edgeLengthToUse, edgeLengthToUse)); 920 } 921 922 if (ShouldUseMappedAspectRatio() && 923 StylePosition()->mAspectRatio.HasRatio()) { 924 return IntrinsicSize(); 925 } 926 927 // XXX: No FinishIntrinsicSize? 928 return IntrinsicSize(0, 0); 929 } 930 931 // For compat reasons, see bug 1602047, we don't use the intrinsic ratio from 932 // width="" and height="" for images with no src attribute (no request). 933 // 934 // But we shouldn't get fooled by <img loading=lazy>. We do want to apply the 935 // ratio then... 936 bool nsImageFrame::ShouldUseMappedAspectRatio() const { 937 if (mKind != Kind::ImageLoadingContent) { 938 return true; 939 } 940 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest(); 941 if (currentRequest) { 942 return true; 943 } 944 // TODO(emilio): Investigate the compat situation of the above check, maybe we 945 // can just check for empty src attribute or something... 946 auto* image = HTMLImageElement::FromNode(mContent); 947 return image && image->IsAwaitingLoadOrLazyLoading(); 948 } 949 950 bool nsImageFrame::UpdateIntrinsicSize() { 951 IntrinsicSize oldIntrinsicSize = mIntrinsicSize; 952 mIntrinsicSize = ComputeIntrinsicSize(); 953 return mIntrinsicSize != oldIntrinsicSize; 954 } 955 956 nsAtom* nsImageFrame::GetViewTransitionName() const { 957 if (mKind != Kind::ViewTransition) { 958 return nullptr; 959 } 960 MOZ_ASSERT(GetContent()->AsElement()->HasName()); 961 return GetContent() 962 ->AsElement() 963 ->GetParsedAttr(nsGkAtoms::name) 964 ->GetAtomValue(); 965 } 966 967 Maybe<nsSize> nsImageFrame::GetViewTransitionBorderBoxSize() const { 968 auto* name = GetViewTransitionName(); 969 if (!name) { 970 return {}; 971 } 972 auto* vt = PresContext()->Document()->GetActiveViewTransition(); 973 if (NS_WARN_IF(!vt)) { 974 return {}; 975 } 976 return Style()->GetPseudoType() == PseudoStyleType::viewTransitionOld 977 ? vt->GetOldBorderBoxSize(name) 978 : vt->GetNewBorderBoxSize(name); 979 } 980 981 wr::ImageKey nsImageFrame::GetViewTransitionImageKey( 982 layers::RenderRootStateManager* aManager, 983 wr::IpcResourceUpdateQueue& aResources) const { 984 auto* name = GetViewTransitionName(); 985 if (!name) { 986 return kNoKey; 987 } 988 auto* vt = PresContext()->Document()->GetActiveViewTransition(); 989 if (NS_WARN_IF(!vt)) { 990 return kNoKey; 991 } 992 const auto* key = 993 Style()->GetPseudoType() == PseudoStyleType::viewTransitionOld 994 ? vt->ReadOldImageKey(name, aManager, aResources) 995 : vt->GetNewImageKey(name); 996 return key ? *key : kNoKey; 997 } 998 999 AspectRatio nsImageFrame::ComputeIntrinsicRatioForImage( 1000 imgIContainer* aImage, bool aIgnoreContainment) const { 1001 if (!aIgnoreContainment && GetContainSizeAxes().IsAny()) { 1002 return AspectRatio(); 1003 } 1004 1005 if (aImage) { 1006 if (AspectRatio fromImage = aImage->GetIntrinsicRatio()) { 1007 return fromImage; 1008 } 1009 } 1010 1011 if (auto size = GetViewTransitionBorderBoxSize()) { 1012 return AspectRatio::FromSize(*size); 1013 } 1014 1015 if (ShouldUseMappedAspectRatio()) { 1016 const StyleAspectRatio& ratio = StylePosition()->mAspectRatio; 1017 if (ratio.auto_ && ratio.HasRatio()) { 1018 // Return the mapped intrinsic aspect ratio stored in 1019 // nsStylePosition::mAspectRatio. 1020 return ratio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes); 1021 } 1022 } 1023 if (ShouldShowBrokenImageIcon()) { 1024 return AspectRatio(1.0f); 1025 } 1026 return AspectRatio(); 1027 } 1028 1029 bool nsImageFrame::UpdateIntrinsicRatio() { 1030 AspectRatio oldIntrinsicRatio = mIntrinsicRatio; 1031 mIntrinsicRatio = ComputeIntrinsicRatioForImage(mImage); 1032 return mIntrinsicRatio != oldIntrinsicRatio; 1033 } 1034 1035 bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) { 1036 nsRect destRect = GetDestRect(GetContentRectRelativeToSelf()); 1037 // Set the translation components, based on destRect 1038 // XXXbz does this introduce rounding errors because of the cast to 1039 // float? Should we just manually add that stuff in every time 1040 // instead? 1041 aTransform.SetToTranslate(float(destRect.x), float(destRect.y)); 1042 1043 // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic 1044 // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector, 1045 // see ScaleIntrinsicSizeForDensity. 1046 nsSize intrinsicSize; 1047 if (!mImage || 1048 !NS_SUCCEEDED(mImage->GetIntrinsicSizeInAppUnits(&intrinsicSize)) || 1049 intrinsicSize.IsEmpty()) { 1050 return false; 1051 } 1052 1053 aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width), 1054 float(destRect.height) / float(intrinsicSize.height)); 1055 return true; 1056 } 1057 1058 // This function checks whether the given request is the current request for our 1059 // mContent. 1060 bool nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const { 1061 // Default to pending load in case of errors 1062 if (mKind != Kind::ImageLoadingContent) { 1063 MOZ_ASSERT(aRequest == mOwnedRequest); 1064 return false; 1065 } 1066 1067 nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent)); 1068 MOZ_ASSERT(imageLoader); 1069 1070 int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST; 1071 imageLoader->GetRequestType(aRequest, &requestType); 1072 1073 return requestType != nsIImageLoadingContent::CURRENT_REQUEST; 1074 } 1075 1076 nsRect nsImageFrame::SourceRectToDest(const nsIntRect& aRect) { 1077 // When scaling the image, row N of the source image may (depending on 1078 // the scaling function) be used to draw any row in the destination image 1079 // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the 1080 // floating-point scaling factor. The same holds true for columns. 1081 // So, we start by computing that bound without the floor and ceiling. 1082 1083 nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1), 1084 nsPresContext::CSSPixelsToAppUnits(aRect.y - 1), 1085 nsPresContext::CSSPixelsToAppUnits(aRect.width + 2), 1086 nsPresContext::CSSPixelsToAppUnits(aRect.height + 2)); 1087 1088 nsTransform2D sourceToDest; 1089 if (!GetSourceToDestTransform(sourceToDest)) { 1090 // Failed to generate transform matrix. Return our whole content area, 1091 // to be on the safe side (since this method is used for generating 1092 // invalidation rects). 1093 return GetContentRectRelativeToSelf(); 1094 } 1095 1096 sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height); 1097 1098 // Now, round the edges out to the pixel boundary. 1099 nscoord scale = nsPresContext::CSSPixelsToAppUnits(1); 1100 nscoord right = r.x + r.width; 1101 nscoord bottom = r.y + r.height; 1102 1103 r.x -= (scale + (r.x % scale)) % scale; 1104 r.y -= (scale + (r.y % scale)) % scale; 1105 r.width = right + ((scale - (right % scale)) % scale) - r.x; 1106 r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y; 1107 1108 return r; 1109 } 1110 1111 static bool ImageOk(ElementState aState) { 1112 return !aState.HasState(ElementState::BROKEN); 1113 } 1114 1115 static bool HasAltText(const Element& aElement) { 1116 // We always return some alternate text for <input>, see 1117 // nsCSSFrameConstructor::GetAlternateTextFor. 1118 if (aElement.IsHTMLElement(nsGkAtoms::input)) { 1119 return true; 1120 } 1121 1122 MOZ_ASSERT(aElement.IsHTMLElement(nsGkAtoms::img)); 1123 return aElement.HasNonEmptyAttr(nsGkAtoms::alt); 1124 } 1125 1126 bool nsImageFrame::ShouldCreateImageFrameForContentProperty( 1127 const Element& aElement, const ComputedStyle& aStyle) { 1128 if (aElement.IsRootOfNativeAnonymousSubtree()) { 1129 return false; 1130 } 1131 return ::ShouldCreateImageFrameForContentProperty(aStyle); 1132 } 1133 1134 // Check if we want to use an image frame or just let the frame constructor make 1135 // us into an inline, and if so, which kind of image frame should we create. 1136 /* static */ 1137 auto nsImageFrame::ImageFrameTypeFor(const Element& aElement, 1138 const ComputedStyle& aStyle) 1139 -> ImageFrameType { 1140 if (ShouldCreateImageFrameForContentProperty(aElement, aStyle)) { 1141 // Prefer the content property, for compat reasons, see bug 1484928. 1142 return ImageFrameType::ForContentProperty; 1143 } 1144 1145 if (ImageOk(aElement.State())) { 1146 // Image is fine or loading; do the image frame thing 1147 return ImageFrameType::ForElementRequest; 1148 } 1149 1150 if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) { 1151 return ImageFrameType::ForElementRequest; 1152 } 1153 1154 if (!HasAltText(aElement)) { 1155 return ImageFrameType::ForElementRequest; 1156 } 1157 1158 // FIXME(emilio, bug 1788767): We definitely don't reframe when 1159 // HaveSpecifiedSize changes... 1160 if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks && 1161 HaveSpecifiedSize(aStyle.StylePosition(), 1162 {nullptr, aStyle.StyleDisplay()->mPosition})) { 1163 return ImageFrameType::ForElementRequest; 1164 } 1165 1166 return ImageFrameType::None; 1167 } 1168 1169 void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType, 1170 const nsIntRect* aRect) { 1171 if (aType == imgINotificationObserver::SIZE_AVAILABLE) { 1172 nsCOMPtr<imgIContainer> image; 1173 aRequest->GetImage(getter_AddRefs(image)); 1174 return OnSizeAvailable(aRequest, image); 1175 } 1176 1177 if (aType == imgINotificationObserver::FRAME_UPDATE) { 1178 return OnFrameUpdate(aRequest, aRect); 1179 } 1180 1181 if (aType == imgINotificationObserver::FRAME_COMPLETE) { 1182 mFirstFrameComplete = true; 1183 } 1184 1185 if (aType == imgINotificationObserver::IS_ANIMATED && 1186 mKind != Kind::ImageLoadingContent) { 1187 nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest, 1188 &mOwnedRequestRegistered); 1189 } 1190 1191 if (aType == imgINotificationObserver::LOAD_COMPLETE) { 1192 LargestContentfulPaint::MaybeProcessImageForElementTiming( 1193 static_cast<imgRequestProxy*>(aRequest), GetContent()->AsElement()); 1194 return OnLoadComplete(aRequest); 1195 } 1196 } 1197 1198 void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, 1199 imgIContainer* aImage) { 1200 if (!aImage) { 1201 return; 1202 } 1203 1204 /* Get requested animation policy from the pres context: 1205 * normal = 0 1206 * one frame = 1 1207 * one loop = 2 1208 */ 1209 aImage->SetAnimationMode(PresContext()->ImageAnimationMode()); 1210 1211 if (IsPendingLoad(aRequest)) { 1212 // We don't care 1213 return; 1214 } 1215 1216 UpdateImage(aRequest, aImage); 1217 } 1218 1219 void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { 1220 if (SizeIsAvailable(aRequest)) { 1221 StyleImageOrientation orientation = 1222 StyleVisibility()->UsedImageOrientation(aRequest); 1223 // This is valid and for the current request, so update our stored image 1224 // container, orienting according to our style. 1225 mImage = nsLayoutUtils::OrientImage(aImage, orientation); 1226 MOZ_ASSERT(mImage); 1227 } else { 1228 // We no longer have a valid image, so release our stored image container. 1229 mImage = mPrevImage = nullptr; 1230 if (mKind == Kind::ListStyleImage) { 1231 auto* genContent = static_cast<GeneratedImageContent*>(GetContent()); 1232 genContent->NotifyLoadFailed(); 1233 // No need to continue below since the above state change will destroy 1234 // this frame. 1235 return; 1236 } 1237 } 1238 1239 UpdateIntrinsicSizeAndRatio(); 1240 1241 if (!GotInitialReflow()) { 1242 return; 1243 } 1244 1245 // We're going to need to repaint now either way. 1246 InvalidateFrame(); 1247 } 1248 1249 void nsImageFrame::OnFrameUpdate(imgIRequest* aRequest, 1250 const nsIntRect* aRect) { 1251 if (NS_WARN_IF(!aRect)) { 1252 return; 1253 } 1254 1255 if (!GotInitialReflow()) { 1256 // Don't bother to do anything; we have a reflow coming up! 1257 return; 1258 } 1259 1260 if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) { 1261 return; 1262 } 1263 1264 if (IsPendingLoad(aRequest)) { 1265 // We don't care 1266 return; 1267 } 1268 1269 nsIntRect layerInvalidRect = 1270 mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect; 1271 1272 if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) { 1273 // Invalidate our entire area. 1274 InvalidateSelf(nullptr, nullptr); 1275 return; 1276 } 1277 1278 nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect); 1279 InvalidateSelf(&layerInvalidRect, &frameInvalidRect); 1280 } 1281 1282 void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect, 1283 const nsRect* aFrameInvalidRect) { 1284 // Check if WebRender has interacted with this frame. If it has 1285 // we need to let it know that things have changed. 1286 const auto type = DisplayItemType::TYPE_IMAGE; 1287 const auto providerId = mImage ? mImage->GetProviderId() : 0; 1288 if (WebRenderUserData::ProcessInvalidateForImage(this, type, providerId)) { 1289 return; 1290 } 1291 1292 InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect); 1293 1294 if (!mFirstFrameComplete) { 1295 InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect, 1296 aFrameInvalidRect); 1297 } 1298 } 1299 1300 void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() { 1301 MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()), 1302 Some(GetAspectRatio())); 1303 } 1304 1305 void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder( 1306 Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) { 1307 if (!mIsInObjectOrEmbed || !mImage) { 1308 return; 1309 } 1310 1311 nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell(); 1312 if (!docShell) { 1313 return; 1314 } 1315 1316 BrowsingContext* bc = docShell->GetBrowsingContext(); 1317 if (!bc) { 1318 return; 1319 } 1320 MOZ_ASSERT(bc->IsContentSubframe()); 1321 1322 if (bc->GetParent()->IsInProcess()) { 1323 if (Element* embedder = bc->GetEmbedderElement()) { 1324 if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) { 1325 static_cast<nsObjectLoadingContent*>(olc.get()) 1326 ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize, 1327 aIntrinsicRatio); 1328 } else { 1329 MOZ_ASSERT_UNREACHABLE("Got out of sync?"); 1330 } 1331 return; 1332 } 1333 } 1334 1335 if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { 1336 (void)browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize, 1337 aIntrinsicRatio); 1338 } 1339 } 1340 1341 void nsImageFrame::OnLoadComplete(imgIRequest* aRequest) { 1342 NotifyNewCurrentRequest(aRequest); 1343 } 1344 1345 void nsImageFrame::ElementStateChanged(ElementState aStates) { 1346 if (!(aStates & ElementState::BROKEN)) { 1347 return; 1348 } 1349 if (mKind != Kind::ImageLoadingContent) { 1350 return; 1351 } 1352 if (!ImageOk(mContent->AsElement()->State())) { 1353 UpdateImage(nullptr, nullptr); 1354 } 1355 } 1356 1357 void nsImageFrame::ResponsiveContentDensityChanged() { 1358 UpdateIntrinsicSizeAndRatio(); 1359 } 1360 1361 void nsImageFrame::UpdateIntrinsicSizeAndRatio() { 1362 bool intrinsicSizeOrRatioChanged = [&] { 1363 // NOTE(emilio): We intentionally want to call both functions and avoid 1364 // short-circuiting. 1365 bool intrinsicSizeChanged = UpdateIntrinsicSize(); 1366 bool intrinsicRatioChanged = UpdateIntrinsicRatio(); 1367 return intrinsicSizeChanged || intrinsicRatioChanged; 1368 }(); 1369 1370 if (!intrinsicSizeOrRatioChanged) { 1371 return; 1372 } 1373 1374 // Our aspect-ratio property value changed, and an embedding <object> or 1375 // <embed> might care about that. 1376 MaybeSendIntrinsicSizeAndRatioToEmbedder(); 1377 1378 if (!GotInitialReflow()) { 1379 return; 1380 } 1381 1382 // Now we need to reflow if we have an unconstrained size and have 1383 // already gotten the initial reflow. 1384 if (!HasAnyStateBits(IMAGE_SIZECONSTRAINED)) { 1385 PresShell()->FrameNeedsReflow( 1386 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); 1387 } else if (PresShell()->IsActive()) { 1388 // We've already gotten the initial reflow, and our size hasn't changed, 1389 // so we're ready to request a decode. 1390 MaybeDecodeForPredictedSize(); 1391 } 1392 } 1393 1394 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest) { 1395 nsCOMPtr<imgIContainer> image; 1396 aRequest->GetImage(getter_AddRefs(image)); 1397 #ifdef DEBUG 1398 uint32_t imgStatus; 1399 aRequest->GetImageStatus(&imgStatus); 1400 NS_ASSERTION(image || (imgStatus & imgIRequest::STATUS_ERROR), 1401 "Successful load with no container?"); 1402 #endif 1403 UpdateImage(aRequest, image); 1404 } 1405 1406 void nsImageFrame::MaybeDecodeForPredictedSize() { 1407 // Check that we're ready to decode. 1408 if (!mImage) { 1409 return; // Nothing to do yet. 1410 } 1411 1412 if (mComputedSize.IsEmpty()) { 1413 return; // We won't draw anything, so no point in decoding. 1414 } 1415 1416 if (GetVisibility() != Visibility::ApproximatelyVisible) { 1417 return; // We're not visible, so don't decode. 1418 } 1419 1420 // OK, we're ready to decode. Compute the scale to the screen... 1421 mozilla::PresShell* presShell = PresShell(); 1422 MatrixScales scale = 1423 ScaleFactor<UnknownUnits, UnknownUnits>( 1424 presShell->GetCumulativeResolution()) * 1425 nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this); 1426 auto resolutionToScreen = ViewAs<LayoutDeviceToScreenScale2D>(scale); 1427 1428 // If we are in a remote browser, then apply scaling from ancestor browsers 1429 if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { 1430 resolutionToScreen = 1431 resolutionToScreen * ViewAs<ScreenToScreenScale2D>( 1432 browserChild->GetEffectsInfo().mRasterScale); 1433 } 1434 1435 // ...and this frame's content box... 1436 const nsPoint offset = 1437 GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this)); 1438 const nsRect frameContentBox = GetContentRectRelativeToSelf() + offset; 1439 1440 // ...and our predicted dest rect... 1441 const int32_t factor = PresContext()->AppUnitsPerDevPixel(); 1442 const LayoutDeviceRect destRect = 1443 LayoutDeviceRect::FromAppUnits(GetDestRect(frameContentBox), factor); 1444 1445 // ...and use them to compute our predicted size in screen pixels. 1446 const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen; 1447 const ScreenIntSize predictedScreenIntSize = 1448 RoundedToInt(predictedScreenSize); 1449 if (predictedScreenIntSize.IsEmpty()) { 1450 return; 1451 } 1452 1453 // Determine the optimal image size to use. 1454 uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING | 1455 imgIContainer::FLAG_ASYNC_NOTIFY; 1456 SamplingFilter samplingFilter = 1457 nsLayoutUtils::GetSamplingFilterForFrame(this); 1458 gfxSize gfxPredictedScreenSize = 1459 gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height); 1460 nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest( 1461 gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter, 1462 flags); 1463 1464 // Request a decode. 1465 mImage->RequestDecodeForSize(predictedImageSize, flags); 1466 } 1467 1468 nsRect nsImageFrame::GetDestRect(const nsRect& aFrameContentBox, 1469 nsPoint* aAnchorPoint) { 1470 // Note: To get the "dest rect", we have to provide the "constraint rect" 1471 // (which is the content-box, with the effects of fragmentation undone). 1472 nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize); 1473 constraintRect.y -= GetContinuationOffset(); 1474 1475 auto intrinsicSize = mIntrinsicSize; 1476 auto intrinsicRatio = mIntrinsicRatio; 1477 if (GetContainSizeAxes().IsAny()) { 1478 // Ignore containment for object-fit computations. 1479 const bool ignoreContainment = true; 1480 intrinsicSize = ComputeIntrinsicSize(ignoreContainment); 1481 intrinsicRatio = ComputeIntrinsicRatioForImage(mImage, ignoreContainment); 1482 } 1483 return nsLayoutUtils::ComputeObjectDestRect(constraintRect, intrinsicSize, 1484 intrinsicRatio, StylePosition(), 1485 aAnchorPoint); 1486 } 1487 1488 void nsImageFrame::EnsureIntrinsicSizeAndRatio(bool aConsiderIntrinsicsDirty) { 1489 const auto containAxes = GetContainSizeAxes(); 1490 if (containAxes.IsBoth()) { 1491 // If we have 'contain:size', then we have no intrinsic aspect ratio, 1492 // and the intrinsic size is determined by contain-intrinsic-size, 1493 // regardless of what our underlying image may think. 1494 mIntrinsicSize = FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); 1495 mIntrinsicRatio = AspectRatio(); 1496 return; 1497 } 1498 1499 // If mIntrinsicSize is set (i.e. anything besides (0,0)), then we assume that 1500 // our intrinsic size/ratio have been already computed and don't need 1501 // recomputing, *unless* the aConsiderIntrinsicsDirty param is set to true 1502 // (in which case our intrinsic size/ratio might be invalid). 1503 // 1504 // The fallback list-style-image marker size might have been set in 1505 // DidSetComputedStyle, and it might have changed since then. 1506 // TODO(emilio): We should remove that special case and add missing 1507 // invalidation if/where needed. 1508 if (!aConsiderIntrinsicsDirty && mIntrinsicSize != IntrinsicSize(0, 0) && 1509 mKind != Kind::ListStyleImage) { 1510 return; 1511 } 1512 1513 bool intrinsicSizeOrRatioChanged = UpdateIntrinsicSize(); 1514 intrinsicSizeOrRatioChanged = 1515 UpdateIntrinsicRatio() || intrinsicSizeOrRatioChanged; 1516 1517 if (intrinsicSizeOrRatioChanged) { 1518 // Our aspect-ratio property value changed, and an embedding <object> or 1519 // <embed> might care about that. 1520 MaybeSendIntrinsicSizeAndRatioToEmbedder(); 1521 } 1522 } 1523 1524 nsIFrame::SizeComputationResult nsImageFrame::ComputeSize( 1525 const SizeComputationInput& aSizingInput, WritingMode aWM, 1526 const LogicalSize& aCBSize, nscoord aAvailableISize, 1527 const LogicalSize& aMargin, const LogicalSize& aBorderPadding, 1528 const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { 1529 EnsureIntrinsicSizeAndRatio(); 1530 return { 1531 ComputeSizeWithIntrinsicDimensions( 1532 aSizingInput.mRenderingContext, aWM, mIntrinsicSize, GetAspectRatio(), 1533 aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags), 1534 AspectRatioUsage::None}; 1535 } 1536 1537 Element* nsImageFrame::GetMapElement() const { 1538 return IsForImageLoadingContent() 1539 ? nsImageLoadingContent::FindImageMap(mContent->AsElement()) 1540 : nullptr; 1541 } 1542 1543 // get the offset into the content area of the image where aImg starts if it is 1544 // a continuation. 1545 nscoord nsImageFrame::GetContinuationOffset() const { 1546 nscoord offset = 0; 1547 for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) { 1548 offset += f->GetContentRect().height; 1549 } 1550 NS_ASSERTION(offset >= 0, "bogus GetContentRect"); 1551 return offset; 1552 } 1553 1554 nscoord nsImageFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 1555 IntrinsicISizeType aType) { 1556 EnsureIntrinsicSizeAndRatio(); 1557 return mIntrinsicSize.ISize(GetWritingMode()).valueOr(0); 1558 } 1559 1560 void nsImageFrame::ReflowChildren(nsPresContext* aPresContext, 1561 const ReflowInput& aReflowInput, 1562 const LogicalSize& aImageSize) { 1563 for (nsIFrame* child : mFrames) { 1564 ReflowOutput childDesiredSize(aReflowInput); 1565 WritingMode wm = GetWritingMode(); 1566 // Shouldn't be hard to support if we want, but why bother. 1567 MOZ_ASSERT( 1568 wm == child->GetWritingMode(), 1569 "We don't expect mismatched writing-modes in content we control"); 1570 nsReflowStatus childStatus; 1571 1572 LogicalPoint childOffset(wm); 1573 ReflowInput childReflowInput(aPresContext, aReflowInput, child, aImageSize); 1574 const nsSize containerSize = aImageSize.GetPhysicalSize(wm); 1575 ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, wm, 1576 childOffset, containerSize, ReflowChildFlags::Default, 1577 childStatus); 1578 1579 FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput, 1580 wm, childOffset, containerSize, 1581 ReflowChildFlags::Default); 1582 } 1583 } 1584 1585 void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, 1586 const ReflowInput& aReflowInput, 1587 nsReflowStatus& aStatus) { 1588 MarkInReflow(); 1589 DO_GLOBAL_REFLOW_COUNT("nsImageFrame"); 1590 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1591 NS_FRAME_TRACE( 1592 NS_FRAME_TRACE_CALLS, 1593 ("enter nsImageFrame::Reflow: availSize=%d,%d", 1594 aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); 1595 1596 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow"); 1597 1598 // see if we have a frozen size (i.e. a fixed width and height) 1599 if (!SizeDependsOnIntrinsicSize(aReflowInput)) { 1600 AddStateBits(IMAGE_SIZECONSTRAINED); 1601 } else { 1602 RemoveStateBits(IMAGE_SIZECONSTRAINED); 1603 } 1604 1605 mComputedSize = aReflowInput.ComputedPhysicalSize(); 1606 1607 const auto wm = GetWritingMode(); 1608 aMetrics.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm)); 1609 1610 if (GetPrevInFlow()) { 1611 aMetrics.Width() = GetPrevInFlow()->GetSize().width; 1612 nscoord y = GetContinuationOffset(); 1613 aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top; 1614 aMetrics.Height() = std::max(0, aMetrics.Height()); 1615 } 1616 1617 // we have to split images if we are: 1618 // in Paginated mode, we need to have a constrained height, and have a height 1619 // larger than our available height 1620 uint32_t loadStatus = imgIRequest::STATUS_NONE; 1621 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { 1622 currentRequest->GetImageStatus(&loadStatus); 1623 } 1624 1625 if (aPresContext->IsPaginated() && 1626 ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || 1627 HasAnyStateBits(IMAGE_SIZECONSTRAINED)) && 1628 NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() && 1629 aMetrics.Height() > aReflowInput.AvailableHeight()) { 1630 // our desired height was greater than 0, so to avoid infinite 1631 // splitting, use 1 pixel as the min 1632 aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), 1633 aReflowInput.AvailableHeight()); 1634 aStatus.SetIncomplete(); 1635 } 1636 1637 aMetrics.SetOverflowAreasToDesiredBounds(); 1638 const bool imageOK = mKind != Kind::ImageLoadingContent || 1639 ImageOk(mContent->AsElement()->State()); 1640 1641 // Determine if the size is available 1642 const bool haveSize = loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE; 1643 if (!imageOK || !haveSize) { 1644 nsRect altFeedbackSize( 1645 0, 0, 1646 nsPresContext::CSSPixelsToAppUnits( 1647 ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)), 1648 nsPresContext::CSSPixelsToAppUnits( 1649 ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH))); 1650 // We include the altFeedbackSize in our ink overflow, but not in our 1651 // scrollable overflow, since it doesn't really need to be scrolled to 1652 // outside the image. 1653 nsRect& inkOverflow = aMetrics.InkOverflow(); 1654 inkOverflow.UnionRect(inkOverflow, altFeedbackSize); 1655 } else { 1656 // Union with our dest rect (note that it will most likely get clipped in 1657 // FinishAndStoreOverflow). Only do this if we're not fragmented, since in 1658 // that case overflow goes into our continuation. 1659 if (aStatus.IsComplete()) { 1660 aMetrics.mOverflowAreas.UnionAllWith( 1661 GetDestRect(aReflowInput.ComputedPhysicalContentBoxRelativeToSelf())); 1662 } 1663 if (PresShell()->IsActive()) { 1664 // We've just reflowed and we should have an accurate size, so we're ready 1665 // to request a decode. 1666 MaybeDecodeForPredictedSize(); 1667 } 1668 } 1669 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay); 1670 1671 // Reflow the child frames. Our children can't affect our size in any way. 1672 ReflowChildren(aPresContext, aReflowInput, aMetrics.Size(GetWritingMode())); 1673 1674 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) { 1675 mReflowCallbackPosted = true; 1676 PresShell()->PostReflowCallback(this); 1677 } 1678 1679 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d", 1680 aMetrics.Width(), aMetrics.Height())); 1681 } 1682 1683 bool nsImageFrame::ReflowFinished() { 1684 mReflowCallbackPosted = false; 1685 1686 // XXX(seth): We don't need this. The purpose of updating visibility 1687 // synchronously is to ensure that animated images start animating 1688 // immediately. In the short term, however, 1689 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that 1690 // animations start as soon as the image is painted for the first time, and in 1691 // the long term we want to update visibility information from the display 1692 // list whenever we paint, so we don't actually need to do this. However, to 1693 // avoid behavior changes during the transition from the old image visibility 1694 // code, we'll leave it in for now. 1695 UpdateVisibilitySynchronously(); 1696 1697 return false; 1698 } 1699 1700 void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; } 1701 1702 // Computes the width of the specified string. aMaxWidth specifies the maximum 1703 // width available. Once this limit is reached no more characters are measured. 1704 // The number of characters that fit within the maximum width are returned in 1705 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected 1706 // into the rendering context before this is called (for performance). MMP 1707 nscoord nsImageFrame::MeasureString(const char16_t* aString, int32_t aLength, 1708 nscoord aMaxWidth, uint32_t& aMaxFit, 1709 gfxContext& aContext, 1710 nsFontMetrics& aFontMetrics) { 1711 nscoord totalWidth = 0; 1712 aFontMetrics.SetTextRunRTL(false); 1713 nscoord spaceWidth = aFontMetrics.SpaceWidth(); 1714 1715 aMaxFit = 0; 1716 while (aLength > 0) { 1717 // Find the next place we can line break 1718 uint32_t len = aLength; 1719 bool trailingSpace = false; 1720 for (int32_t i = 0; i < aLength; i++) { 1721 if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) { 1722 len = i; // don't include the space when measuring 1723 trailingSpace = true; 1724 break; 1725 } 1726 } 1727 1728 // Measure this chunk of text, and see if it fits 1729 nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi( 1730 aString, len, this, aFontMetrics, aContext); 1731 bool fits = (totalWidth + width) <= aMaxWidth; 1732 1733 // If it fits on the line, or it's the first word we've processed then 1734 // include it 1735 if (fits || (0 == totalWidth)) { 1736 // New piece fits 1737 totalWidth += width; 1738 1739 // If there's a trailing space then see if it fits as well 1740 if (trailingSpace) { 1741 if ((totalWidth + spaceWidth) <= aMaxWidth) { 1742 totalWidth += spaceWidth; 1743 } else { 1744 // Space won't fit. Leave it at the end but don't include it in 1745 // the width 1746 fits = false; 1747 } 1748 1749 len++; 1750 } 1751 1752 aMaxFit += len; 1753 aString += len; 1754 aLength -= len; 1755 } 1756 1757 if (!fits) { 1758 break; 1759 } 1760 } 1761 return totalWidth; 1762 } 1763 1764 // Formats the alt-text to fit within the specified rectangle. Breaks lines 1765 // between words if a word would extend past the edge of the rectangle 1766 void nsImageFrame::DisplayAltText(nsPresContext* aPresContext, 1767 gfxContext& aRenderingContext, 1768 const nsString& aAltText, 1769 const nsRect& aRect) { 1770 // Set font and color 1771 aRenderingContext.SetColor( 1772 sRGBColor::FromABGR(StyleText()->mColor.ToColor())); 1773 RefPtr<nsFontMetrics> fm = 1774 nsLayoutUtils::GetInflatedFontMetricsForFrame(this); 1775 1776 // Format the text to display within the formatting rect 1777 1778 nscoord maxAscent = fm->MaxAscent(); 1779 nscoord maxDescent = fm->MaxDescent(); 1780 nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate 1781 // length if writing mode is vertical 1782 1783 WritingMode wm = GetWritingMode(); 1784 bool isVertical = wm.IsVertical(); 1785 1786 fm->SetVertical(isVertical); 1787 fm->SetTextOrientation(StyleVisibility()->mTextOrientation); 1788 1789 // XXX It would be nice if there was a way to have the font metrics tell 1790 // use where to break the text given a maximum width. At a minimum we need 1791 // to be able to get the break character... 1792 const char16_t* str = aAltText.get(); 1793 int32_t strLen = aAltText.Length(); 1794 nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0) 1795 : aRect.TopLeft(); 1796 nscoord iSize = isVertical ? aRect.height : aRect.width; 1797 1798 if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) { 1799 aPresContext->SetBidiEnabled(); 1800 } 1801 1802 // Always show the first line, even if we have to clip it below 1803 bool firstLine = true; 1804 while (strLen > 0) { 1805 if (!firstLine) { 1806 // If we've run out of space, break out of the loop 1807 if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) || 1808 (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) || 1809 (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) { 1810 break; 1811 } 1812 } 1813 1814 // Determine how much of the text to display on this line 1815 uint32_t maxFit; // number of characters that fit 1816 nscoord strWidth = 1817 MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm); 1818 1819 // Display the text 1820 nsresult rv = NS_ERROR_FAILURE; 1821 1822 if (aPresContext->BidiEnabled()) { 1823 mozilla::intl::BidiEmbeddingLevel level; 1824 nscoord x, y; 1825 1826 if (isVertical) { 1827 x = pt.x + maxDescent; 1828 if (wm.IsBidiLTR()) { 1829 y = aRect.y; 1830 level = mozilla::intl::BidiEmbeddingLevel::LTR(); 1831 } else { 1832 y = aRect.YMost() - strWidth; 1833 level = mozilla::intl::BidiEmbeddingLevel::RTL(); 1834 } 1835 } else { 1836 y = pt.y + maxAscent; 1837 if (wm.IsBidiLTR()) { 1838 x = aRect.x; 1839 level = mozilla::intl::BidiEmbeddingLevel::LTR(); 1840 } else { 1841 x = aRect.XMost() - strWidth; 1842 level = mozilla::intl::BidiEmbeddingLevel::RTL(); 1843 } 1844 } 1845 1846 rv = nsBidiPresUtils::RenderText( 1847 str, maxFit, level, aPresContext, aRenderingContext, 1848 aRenderingContext.GetDrawTarget(), *fm, x, y); 1849 } 1850 if (NS_FAILED(rv)) { 1851 nsLayoutUtils::DrawUniDirString(str, maxFit, 1852 isVertical 1853 ? nsPoint(pt.x + maxDescent, pt.y) 1854 : nsPoint(pt.x, pt.y + maxAscent), 1855 *fm, aRenderingContext); 1856 } 1857 1858 // Move to the next line 1859 str += maxFit; 1860 strLen -= maxFit; 1861 if (wm.IsVerticalRL()) { 1862 pt.x -= lineHeight; 1863 } else if (wm.IsVerticalLR()) { 1864 pt.x += lineHeight; 1865 } else { 1866 pt.y += lineHeight; 1867 } 1868 1869 firstLine = false; 1870 } 1871 } 1872 1873 struct nsRecessedBorder : public nsStyleBorder { 1874 explicit nsRecessedBorder(nscoord aBorderWidth) { 1875 for (const auto side : AllPhysicalSides()) { 1876 BorderColorFor(side) = StyleColor::Black(); 1877 mBorder.Get(side) = aBorderWidth; 1878 mBorderStyle.Get(side) = StyleBorderStyle::Inset; 1879 } 1880 } 1881 }; 1882 1883 class nsDisplayAltFeedback final : public nsPaintedDisplayItem { 1884 public: 1885 nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) 1886 : nsPaintedDisplayItem(aBuilder, aFrame) {} 1887 1888 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final { 1889 *aSnap = false; 1890 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); 1891 } 1892 1893 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final { 1894 // Always sync decode, because these icons are UI, and since they're not 1895 // discardable we'll pay the price of sync decoding at most once. 1896 uint32_t flags = imgIContainer::FLAG_SYNC_DECODE; 1897 1898 nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); 1899 (void)f->DisplayAltFeedback(*aCtx, GetPaintRect(aBuilder, aCtx), 1900 ToReferenceFrame(), flags); 1901 } 1902 1903 bool CreateWebRenderCommands( 1904 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 1905 const StackingContextHelper& aSc, 1906 layers::RenderRootStateManager* aManager, 1907 nsDisplayListBuilder* aDisplayListBuilder) final { 1908 // Always sync decode, because these icons are UI, and since they're not 1909 // discardable we'll pay the price of sync decoding at most once. 1910 uint32_t flags = 1911 imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY; 1912 nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); 1913 ImgDrawResult result = f->DisplayAltFeedbackWithoutLayer( 1914 this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder, 1915 ToReferenceFrame(), flags); 1916 1917 return result == ImgDrawResult::SUCCESS; 1918 } 1919 1920 NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK) 1921 }; 1922 1923 ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext, 1924 const nsRect& aDirtyRect, 1925 nsPoint aPt, uint32_t aFlags) { 1926 // Whether we draw the broken or loading icon. 1927 bool isLoading = mKind != Kind::ImageLoadingContent || 1928 ImageOk(mContent->AsElement()->State()); 1929 1930 // Calculate the content area. 1931 nsRect inner = GetContentRectRelativeToSelf() + aPt; 1932 1933 // Display a recessed one pixel border 1934 nscoord borderEdgeWidth = 1935 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); 1936 1937 // if inner area is empty, then make it big enough for at least the icon 1938 if (inner.IsEmpty()) { 1939 inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits( 1940 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)), 1941 2 * (nsPresContext::CSSPixelsToAppUnits( 1942 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH))); 1943 } 1944 1945 // Make sure we have enough room to actually render the border within 1946 // our frame bounds 1947 if ((inner.width < 2 * borderEdgeWidth) || 1948 (inner.height < 2 * borderEdgeWidth)) { 1949 return ImgDrawResult::SUCCESS; 1950 } 1951 1952 // Paint the border 1953 if (!isLoading) { 1954 nsRecessedBorder recessedBorder(borderEdgeWidth); 1955 1956 // Assert that we're not drawing a border-image here; if we were, we 1957 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder 1958 // returns. 1959 MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone()); 1960 1961 (void)nsCSSRendering::PaintBorderWithStyleBorder( 1962 PresContext(), aRenderingContext, this, inner, inner, recessedBorder, 1963 mComputedStyle, PaintBorderFlags::SyncDecodeImages); 1964 } 1965 1966 // Adjust the inner rect to account for the one pixel recessed border, 1967 // and a six pixel padding on each edge 1968 inner.Deflate( 1969 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH), 1970 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH)); 1971 if (inner.IsEmpty()) { 1972 return ImgDrawResult::SUCCESS; 1973 } 1974 1975 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); 1976 1977 // Clip so we don't render outside the inner rect 1978 aRenderingContext.Save(); 1979 aRenderingContext.Clip(NSRectToSnappedRect( 1980 inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); 1981 1982 ImgDrawResult result = ImgDrawResult::SUCCESS; 1983 1984 // Check if we should display image placeholders 1985 if (ShouldShowBrokenImageIcon()) { 1986 result = ImgDrawResult::NOT_READY; 1987 nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); 1988 imgIRequest* request = BrokenImageIcon::GetImage(this); 1989 1990 // If we weren't previously displaying an icon, register ourselves 1991 // as an observer for load and animation updates and flag that we're 1992 // doing so now. 1993 if (request && !mDisplayingIcon) { 1994 BrokenImageIcon::AddObserver(this); 1995 mDisplayingIcon = true; 1996 } 1997 1998 WritingMode wm = GetWritingMode(); 1999 bool flushRight = wm.IsPhysicalRTL(); 2000 2001 // If the icon in question is loaded, draw it. 2002 uint32_t imageStatus = 0; 2003 if (request) { 2004 request->GetImageStatus(&imageStatus); 2005 } 2006 if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE && 2007 !(imageStatus & imgIRequest::STATUS_ERROR)) { 2008 nsCOMPtr<imgIContainer> imgCon; 2009 request->GetImage(getter_AddRefs(imgCon)); 2010 MOZ_ASSERT(imgCon, "Load complete, but no image container?"); 2011 nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size, 2012 size); 2013 result = nsLayoutUtils::DrawSingleImage( 2014 aRenderingContext, PresContext(), imgCon, 2015 nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, 2016 SVGImageContext(), aFlags); 2017 } 2018 2019 // If we could not draw the icon, just draw some graffiti in the mean time. 2020 if (result == ImgDrawResult::NOT_READY) { 2021 ColorPattern color(ToDeviceColor(sRGBColor(1.f, 0.f, 0.f, 1.f))); 2022 2023 nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x; 2024 2025 // stroked rect: 2026 nsRect rect(iconXPos, inner.y, size, size); 2027 Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect( 2028 rect, PresContext()->AppUnitsPerDevPixel())); 2029 drawTarget->StrokeRect(devPxRect, color); 2030 2031 // filled circle in bottom right quadrant of stroked rect: 2032 nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2); 2033 rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX, 2034 size / 2 - twoPX); 2035 devPxRect = ToRect(nsLayoutUtils::RectToGfxRect( 2036 rect, PresContext()->AppUnitsPerDevPixel())); 2037 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(); 2038 AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); 2039 RefPtr<Path> ellipse = builder->Finish(); 2040 drawTarget->Fill(ellipse, color); 2041 } 2042 2043 // Reduce the inner rect by the width of the icon, and leave an 2044 // additional ICON_PADDING pixels for padding 2045 int32_t paddedIconSize = 2046 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING); 2047 if (wm.IsVertical()) { 2048 inner.y += paddedIconSize; 2049 inner.height -= paddedIconSize; 2050 } else { 2051 if (!flushRight) { 2052 inner.x += paddedIconSize; 2053 } 2054 inner.width -= paddedIconSize; 2055 } 2056 } 2057 2058 // If there's still room, display the alt-text 2059 if (!inner.IsEmpty()) { 2060 nsAutoString altText; 2061 nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(), altText); 2062 DisplayAltText(PresContext(), aRenderingContext, altText, inner); 2063 } 2064 2065 aRenderingContext.Restore(); 2066 2067 return result; 2068 } 2069 2070 ImgDrawResult nsImageFrame::DisplayAltFeedbackWithoutLayer( 2071 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, 2072 wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, 2073 layers::RenderRootStateManager* aManager, 2074 nsDisplayListBuilder* aDisplayListBuilder, nsPoint aPt, uint32_t aFlags) { 2075 // Whether we draw the broken or loading icon. 2076 bool isLoading = mKind != Kind::ImageLoadingContent || 2077 ImageOk(mContent->AsElement()->State()); 2078 2079 // Calculate the content area. 2080 nsRect inner = GetContentRectRelativeToSelf() + aPt; 2081 2082 // Display a recessed one pixel border 2083 nscoord borderEdgeWidth = 2084 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); 2085 2086 // if inner area is empty, then make it big enough for at least the icon 2087 if (inner.IsEmpty()) { 2088 inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits( 2089 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)), 2090 2 * (nsPresContext::CSSPixelsToAppUnits( 2091 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH))); 2092 } 2093 2094 // Make sure we have enough room to actually render the border within 2095 // our frame bounds 2096 if ((inner.width < 2 * borderEdgeWidth) || 2097 (inner.height < 2 * borderEdgeWidth)) { 2098 return ImgDrawResult::SUCCESS; 2099 } 2100 2101 // If the TextDrawTarget requires fallback we need to rollback everything we 2102 // may have added to the display list, but we don't find that out until the 2103 // end. 2104 bool textDrawResult = true; 2105 class AutoSaveRestore { 2106 public: 2107 explicit AutoSaveRestore(wr::DisplayListBuilder& aBuilder, 2108 bool& aTextDrawResult) 2109 : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) { 2110 mBuilder.Save(); 2111 } 2112 ~AutoSaveRestore() { 2113 // If we have to use fallback for the text restore the builder and remove 2114 // anything else we added to the display list, we need to use fallback. 2115 if (mTextDrawResult) { 2116 mBuilder.ClearSave(); 2117 } else { 2118 mBuilder.Restore(); 2119 } 2120 } 2121 2122 private: 2123 wr::DisplayListBuilder& mBuilder; 2124 bool& mTextDrawResult; 2125 }; 2126 2127 AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult); 2128 2129 // Paint the border 2130 if (!isLoading) { 2131 nsRecessedBorder recessedBorder(borderEdgeWidth); 2132 // Assert that we're not drawing a border-image here; if we were, we 2133 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder 2134 // returns. 2135 MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone()); 2136 2137 nsRect rect = nsRect(aPt, GetSize()); 2138 (void)nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( 2139 aItem, this, rect, aBuilder, aResources, aSc, aManager, 2140 aDisplayListBuilder, recessedBorder); 2141 } 2142 2143 // Adjust the inner rect to account for the one pixel recessed border, 2144 // and a six pixel padding on each edge 2145 inner.Deflate( 2146 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH), 2147 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH)); 2148 if (inner.IsEmpty()) { 2149 return ImgDrawResult::SUCCESS; 2150 } 2151 2152 // Clip to this rect so we don't render outside the inner rect 2153 const auto bounds = LayoutDeviceRect::FromAppUnits( 2154 inner, PresContext()->AppUnitsPerDevPixel()); 2155 auto wrBounds = wr::ToLayoutRect(bounds); 2156 2157 // Check if we should display image placeholders 2158 if (ShouldShowBrokenImageIcon()) { 2159 ImgDrawResult result = ImgDrawResult::NOT_READY; 2160 nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); 2161 imgIRequest* request = BrokenImageIcon::GetImage(this); 2162 2163 // If we weren't previously displaying an icon, register ourselves 2164 // as an observer for load and animation updates and flag that we're 2165 // doing so now. 2166 if (request && !mDisplayingIcon) { 2167 BrokenImageIcon::AddObserver(this); 2168 mDisplayingIcon = true; 2169 } 2170 2171 WritingMode wm = GetWritingMode(); 2172 const bool flushRight = wm.IsPhysicalRTL(); 2173 2174 // If the icon in question is loaded, draw it. 2175 uint32_t imageStatus = 0; 2176 if (request) { 2177 request->GetImageStatus(&imageStatus); 2178 } 2179 if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE && 2180 !(imageStatus & imgIRequest::STATUS_ERROR)) { 2181 nsCOMPtr<imgIContainer> imgCon; 2182 request->GetImage(getter_AddRefs(imgCon)); 2183 MOZ_ASSERT(imgCon, "Load complete, but no image container?"); 2184 2185 nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size, 2186 size); 2187 2188 const int32_t factor = PresContext()->AppUnitsPerDevPixel(); 2189 const auto destRect = LayoutDeviceRect::FromAppUnits(dest, factor); 2190 2191 SVGImageContext svgContext; 2192 Maybe<ImageIntRegion> region; 2193 IntSize decodeSize = 2194 nsLayoutUtils::ComputeImageContainerDrawingParameters( 2195 imgCon, this, destRect, destRect, aSc, aFlags, svgContext, 2196 region); 2197 RefPtr<image::WebRenderImageProvider> provider; 2198 result = imgCon->GetImageProvider(aManager->LayerManager(), decodeSize, 2199 svgContext, region, aFlags, 2200 getter_AddRefs(provider)); 2201 if (provider) { 2202 bool wrResult = aManager->CommandBuilder().PushImageProvider( 2203 aItem, provider, result, aBuilder, aResources, destRect, bounds); 2204 result &= wrResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY; 2205 } else { 2206 // We don't use &= here because we want the result to be NOT_READY so 2207 // the next block executes. 2208 result = ImgDrawResult::NOT_READY; 2209 } 2210 } 2211 2212 // If we could not draw the icon, just draw some graffiti in the mean time. 2213 if (result == ImgDrawResult::NOT_READY) { 2214 auto color = wr::ColorF{1.0f, 0.0f, 0.0f, 1.0f}; 2215 bool isBackfaceVisible = !aItem->BackfaceIsHidden(); 2216 2217 nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x; 2218 2219 // stroked rect: 2220 nsRect rect(iconXPos, inner.y, size, size); 2221 auto devPxRect = LayoutDeviceRect::FromAppUnits( 2222 rect, PresContext()->AppUnitsPerDevPixel()); 2223 auto dest = wr::ToLayoutRect(devPxRect); 2224 2225 auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0); 2226 wr::BorderSide side = {color, wr::BorderStyle::Solid}; 2227 wr::BorderSide sides[4] = {side, side, side, side}; 2228 Range<const wr::BorderSide> sidesRange(sides, 4); 2229 aBuilder.PushBorder(dest, wrBounds, isBackfaceVisible, borderWidths, 2230 sidesRange, wr::EmptyBorderRadius()); 2231 2232 // filled circle in bottom right quadrant of stroked rect: 2233 nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2); 2234 rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX, 2235 size / 2 - twoPX); 2236 devPxRect = LayoutDeviceRect::FromAppUnits( 2237 rect, PresContext()->AppUnitsPerDevPixel()); 2238 dest = wr::ToLayoutRect(devPxRect); 2239 2240 aBuilder.PushRoundedRect(dest, wrBounds, isBackfaceVisible, color); 2241 } 2242 2243 // Reduce the inner rect by the width of the icon, and leave an 2244 // additional ICON_PADDING pixels for padding 2245 int32_t paddedIconSize = 2246 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING); 2247 if (wm.IsVertical()) { 2248 inner.y += paddedIconSize; 2249 inner.height -= paddedIconSize; 2250 } else { 2251 if (!flushRight) { 2252 inner.x += paddedIconSize; 2253 } 2254 inner.width -= paddedIconSize; 2255 } 2256 } 2257 2258 // Draw text 2259 if (!inner.IsEmpty()) { 2260 RefPtr<TextDrawTarget> textDrawer = 2261 new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, inner, 2262 /* aCallerDoesSaveRestore = */ true); 2263 MOZ_ASSERT(textDrawer->IsValid()); 2264 if (textDrawer->IsValid()) { 2265 gfxContext captureCtx(textDrawer); 2266 2267 nsAutoString altText; 2268 nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(), 2269 altText); 2270 DisplayAltText(PresContext(), captureCtx, altText, inner); 2271 2272 textDrawer->TerminateShadows(); 2273 textDrawResult = !textDrawer->CheckHasUnsupportedFeatures(); 2274 } 2275 } 2276 2277 // Purposely ignore local DrawResult because we handled it not being success 2278 // already. 2279 return textDrawResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY; 2280 } 2281 2282 // We want to sync-decode in this case, as otherwise we either need to flash 2283 // white while waiting to decode the new image, or paint the old image with a 2284 // different aspect-ratio, which would be bad as it'd be stretched. 2285 // 2286 // See bug 1589955. 2287 static bool OldImageHasDifferentRatio(const nsImageFrame& aFrame, 2288 imgIContainer& aImage, 2289 imgIContainer* aPrevImage) { 2290 if (!aPrevImage || aPrevImage == &aImage) { 2291 return false; 2292 } 2293 2294 // If we don't depend on our intrinsic image size / ratio, we're good. 2295 // 2296 // FIXME(emilio): There's the case of the old image being painted 2297 // intrinsically, and src and styles changing at the same time... Maybe we 2298 // should keep track of the old GetPaintRect()'s ratio and the image's ratio, 2299 // instead of checking this bit? 2300 if (aFrame.HasAnyStateBits(IMAGE_SIZECONSTRAINED)) { 2301 return false; 2302 } 2303 2304 auto currentRatio = aFrame.GetIntrinsicRatio(); 2305 auto oldRatio = aFrame.ComputeIntrinsicRatioForImage(aPrevImage); 2306 return oldRatio != currentRatio; 2307 } 2308 2309 #ifdef DEBUG 2310 void nsImageFrame::AssertSyncDecodingHintIsInSync() const { 2311 if (!IsForImageLoadingContent()) { 2312 return; 2313 } 2314 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); 2315 MOZ_ASSERT(imageLoader); 2316 2317 // The opposite is not true, we might have some other heuristics which force 2318 // sync-decoding of images. 2319 MOZ_ASSERT_IF(imageLoader->GetSyncDecodingHint(), mForceSyncDecoding); 2320 } 2321 #endif 2322 2323 void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { 2324 auto* frame = Frame(); 2325 frame->AssertSyncDecodingHintIsInSync(); 2326 2327 auto* image = frame->mImage.get(); 2328 auto* prevImage = frame->mPrevImage.get(); 2329 if (!image) { 2330 return; 2331 } 2332 2333 const bool oldImageIsDifferent = 2334 OldImageHasDifferentRatio(*frame, *image, prevImage); 2335 2336 uint32_t flags = aBuilder->GetImageDecodeFlags(); 2337 if (aBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent || 2338 frame->mForceSyncDecoding) { 2339 flags |= imgIContainer::FLAG_SYNC_DECODE; 2340 } 2341 2342 ImgDrawResult result = frame->PaintImage( 2343 *aCtx, ToReferenceFrame(), GetPaintRect(aBuilder, aCtx), image, flags); 2344 2345 if (result == ImgDrawResult::NOT_READY || 2346 result == ImgDrawResult::INCOMPLETE || 2347 result == ImgDrawResult::TEMPORARY_ERROR) { 2348 // If the current image failed to paint because it's still loading or 2349 // decoding, try painting the previous image. 2350 if (prevImage) { 2351 result = 2352 frame->PaintImage(*aCtx, ToReferenceFrame(), 2353 GetPaintRect(aBuilder, aCtx), prevImage, flags); 2354 } 2355 } 2356 } 2357 2358 nsRect nsDisplayImage::GetDestRect() const { 2359 auto* f = static_cast<nsImageFrame*>(mFrame); 2360 return f->GetDestRect(f->GetContentRectRelativeToSelf() + ToReferenceFrame()); 2361 } 2362 2363 nsRect nsDisplayImage::GetDestRectViewTransition() const { 2364 nsRect destRect = GetDestRect(); 2365 auto* image = static_cast<nsImageFrame*>(mFrame); 2366 2367 auto* name = image->GetViewTransitionName(); 2368 auto* vt = image->PresContext()->Document()->GetActiveViewTransition(); 2369 2370 if (!name || !vt) { 2371 return destRect; 2372 } 2373 2374 // In view transitions, the snapshot images natural dimension is the captured 2375 // elements principal border box. In order to render the captured overflow to 2376 // its appropiate position and scale, we must internally map and scale the 2377 // destRect with respect to the captured element's inkOverflowRect. 2378 nsRect inkOverflowRect; 2379 nsSize borderBoxSize; 2380 Maybe<nsRect> activeRect; 2381 2382 if (image->Style()->GetPseudoType() == PseudoStyleType::viewTransitionOld) { 2383 inkOverflowRect = vt->GetOldInkOverflowRect(name).value(); 2384 borderBoxSize = vt->GetOldBorderBoxSize(name).value(); 2385 activeRect = vt->GetOldActiveRect(name); 2386 } else { 2387 inkOverflowRect = vt->GetNewInkOverflowRect(name).value(); 2388 borderBoxSize = vt->GetNewBorderBoxSize(name).value(); 2389 activeRect = vt->GetNewActiveRect(name); 2390 } 2391 2392 if (borderBoxSize.IsEmpty()) { 2393 return destRect; 2394 } 2395 2396 // Scale the ink overflow offset to maintain its position relative to 2397 // the destination border box, as if the offset scaled with the element. 2398 auto xRatio = static_cast<float>(inkOverflowRect.X()) / borderBoxSize.Width(); 2399 auto yRatio = 2400 static_cast<float>(inkOverflowRect.Y()) / borderBoxSize.Height(); 2401 auto scaledX = std::round(xRatio * destRect.Width()); 2402 auto scaledY = std::round(yRatio * destRect.Height()); 2403 2404 const nsPoint scaledInkOverflowOffset(scaledX, scaledY); 2405 2406 // Scale destRect’s size to match the captured element’s relative ink overflow 2407 // size. 2408 auto widthRatio = 2409 static_cast<float>(inkOverflowRect.Width()) / borderBoxSize.Width(); 2410 auto heightRatio = 2411 static_cast<float>(inkOverflowRect.Height()) / borderBoxSize.Height(); 2412 const nsSize scaledInkOverflowSize( 2413 std::round(widthRatio * destRect.Width()), 2414 std::round(heightRatio * destRect.Height())); 2415 2416 destRect = nsRect(destRect.TopLeft() + scaledInkOverflowOffset, 2417 scaledInkOverflowSize); 2418 2419 if (activeRect) { 2420 destRect = destRect.Intersect(activeRect.value()); 2421 } 2422 2423 return destRect; 2424 } 2425 2426 nsRegion nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, 2427 bool* aSnap) const { 2428 *aSnap = false; 2429 auto* image = Frame()->mImage.get(); 2430 if (image && image->WillDrawOpaqueNow()) { 2431 const nsRect frameContentBox = GetBounds(aSnap); 2432 return GetDestRect().Intersect(frameContentBox); 2433 } 2434 return nsRegion(); 2435 } 2436 2437 void nsDisplayImage::MaybeCreateWebRenderCommandsForViewTransition( 2438 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 2439 const StackingContextHelper& aSc, RenderRootStateManager* aManager, 2440 nsDisplayListBuilder* aDisplayListBuilder) { 2441 auto* frame = Frame(); 2442 MOZ_ASSERT(!frame->mImage); 2443 auto key = frame->GetViewTransitionImageKey(aManager, aResources); 2444 if (NS_WARN_IF(key == kNoKey)) { 2445 return; 2446 } 2447 VT_LOG_DEBUG("GetViewTransitionImageKey(%s) = %s", frame->ListTag().get(), 2448 ToString(key).c_str()); 2449 nsRect destAppUnits = GetDestRectViewTransition(); 2450 const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); 2451 const auto destRect = 2452 wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(destAppUnits, factor)); 2453 auto rendering = wr::ToImageRendering(frame->UsedImageRendering()); 2454 aBuilder.PushDebug(1); 2455 aBuilder.PushImage(destRect, destRect, !BackfaceIsHidden(), 2456 /* aForceAntiAliasing = */ false, rendering, key); 2457 } 2458 2459 bool nsDisplayImage::CreateWebRenderCommands( 2460 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, 2461 const StackingContextHelper& aSc, RenderRootStateManager* aManager, 2462 nsDisplayListBuilder* aDisplayListBuilder) { 2463 auto* frame = Frame(); 2464 auto* image = frame->mImage.get(); 2465 if (!image) { 2466 MaybeCreateWebRenderCommandsForViewTransition( 2467 aBuilder, aResources, aSc, aManager, aDisplayListBuilder); 2468 return true; 2469 } 2470 2471 if (nsImageMap* map = frame->GetImageMap(); map && map->HasFocus()) { 2472 // We can't draw some of the focus areas (in particular, PolyArea would be 2473 // somewhat hard to do). 2474 return false; 2475 } 2476 2477 auto* prevImage = frame->mPrevImage.get(); 2478 2479 frame->AssertSyncDecodingHintIsInSync(); 2480 const bool oldImageIsDifferent = 2481 OldImageHasDifferentRatio(*frame, *image, prevImage); 2482 2483 uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags(); 2484 if (aDisplayListBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent || 2485 frame->mForceSyncDecoding) { 2486 flags |= imgIContainer::FLAG_SYNC_DECODE; 2487 } 2488 if (StaticPrefs::image_svg_blob_image() && 2489 image->GetType() == imgIContainer::TYPE_VECTOR) { 2490 flags |= imgIContainer::FLAG_RECORD_BLOB; 2491 } 2492 2493 const nsRect destAppUnits = GetDestRect(); 2494 const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); 2495 const auto destRect = LayoutDeviceRect::FromAppUnits(destAppUnits, factor); 2496 2497 SVGImageContext svgContext; 2498 Maybe<ImageIntRegion> region; 2499 IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters( 2500 image, mFrame, destRect, destRect, aSc, flags, svgContext, region); 2501 2502 RefPtr<image::WebRenderImageProvider> provider; 2503 ImgDrawResult drawResult = 2504 image->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext, 2505 region, flags, getter_AddRefs(provider)); 2506 2507 if (nsCOMPtr<imgIRequest> currentRequest = frame->GetCurrentRequest()) { 2508 LCPHelpers::FinalizeLCPEntryForImage( 2509 frame->GetContent()->AsElement(), 2510 static_cast<imgRequestProxy*>(currentRequest.get()), 2511 destAppUnits - ToReferenceFrame()); 2512 } 2513 2514 // While we got a container, it may not contain a fully decoded surface. If 2515 // that is the case, and we have an image we were previously displaying which 2516 // has a fully decoded surface, then we should prefer the previous image. 2517 bool updatePrevImage = false; 2518 switch (drawResult) { 2519 case ImgDrawResult::NOT_READY: 2520 case ImgDrawResult::INCOMPLETE: 2521 case ImgDrawResult::TEMPORARY_ERROR: 2522 if (prevImage && prevImage != image) { 2523 // The current image and the previous image might be switching between 2524 // rasterized surfaces and blob recordings, so we need to update the 2525 // flags appropriately. 2526 uint32_t prevFlags = flags; 2527 if (StaticPrefs::image_svg_blob_image() && 2528 prevImage->GetType() == imgIContainer::TYPE_VECTOR) { 2529 prevFlags |= imgIContainer::FLAG_RECORD_BLOB; 2530 } else { 2531 prevFlags &= ~imgIContainer::FLAG_RECORD_BLOB; 2532 } 2533 2534 RefPtr<image::WebRenderImageProvider> prevProvider; 2535 ImgDrawResult prevDrawResult = prevImage->GetImageProvider( 2536 aManager->LayerManager(), decodeSize, svgContext, region, prevFlags, 2537 getter_AddRefs(prevProvider)); 2538 if (prevProvider && (prevDrawResult == ImgDrawResult::SUCCESS || 2539 prevDrawResult == ImgDrawResult::WRONG_SIZE)) { 2540 // We use WRONG_SIZE here to ensure that when the frame next tries to 2541 // invalidate due to a frame update from the current image, we don't 2542 // consider the result from the previous image to be a valid result to 2543 // avoid redrawing. 2544 drawResult = ImgDrawResult::WRONG_SIZE; 2545 provider = std::move(prevProvider); 2546 flags = prevFlags; 2547 break; 2548 } 2549 2550 // Previous image was unusable; we can forget about it. 2551 updatePrevImage = true; 2552 } 2553 break; 2554 case ImgDrawResult::NOT_SUPPORTED: 2555 return false; 2556 default: 2557 updatePrevImage = prevImage != image; 2558 break; 2559 } 2560 2561 // The previous image was not used, and is different from the current image. 2562 // We should forget about it. We need to update the frame as well because the 2563 // display item may get recreated. 2564 if (updatePrevImage) { 2565 frame->mPrevImage = frame->mImage; 2566 } 2567 2568 // If the image provider is null, we don't want to fallback. Any other 2569 // failure will be due to resource constraints and fallback is unlikely to 2570 // help us. Hence we can ignore the return value from PushImage. 2571 aManager->CommandBuilder().PushImageProvider( 2572 this, provider, drawResult, aBuilder, aResources, destRect, destRect); 2573 return true; 2574 } 2575 2576 ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext, 2577 nsPoint aPt, const nsRect& aDirtyRect, 2578 imgIContainer* aImage, uint32_t aFlags) { 2579 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); 2580 2581 // Render the image into our content area (the area inside 2582 // the borders and padding) 2583 NS_ASSERTION(GetContentRectRelativeToSelf().width == mComputedSize.width, 2584 "bad width"); 2585 2586 nsPoint anchorPoint; 2587 const nsRect dest = 2588 GetDestRect(GetContentRectRelativeToSelf() + aPt, &anchorPoint); 2589 2590 SVGImageContext svgContext; 2591 SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage); 2592 2593 ImgDrawResult result = nsLayoutUtils::DrawSingleImage( 2594 aRenderingContext, PresContext(), aImage, 2595 nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, 2596 svgContext, aFlags, &anchorPoint); 2597 2598 if (nsImageMap* map = GetImageMap(); map && map->HasFocus()) { 2599 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint( 2600 dest.TopLeft(), PresContext()->AppUnitsPerDevPixel()); 2601 AutoRestoreTransform autoRestoreTransform(drawTarget); 2602 drawTarget->SetTransform( 2603 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); 2604 2605 // solid white stroke: 2606 ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite())); 2607 map->DrawFocus(this, *drawTarget, white); 2608 2609 // then dashed black stroke over the top: 2610 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack())); 2611 StrokeOptions strokeOptions; 2612 nsLayoutUtils::InitDashPattern(strokeOptions, StyleBorderStyle::Dotted); 2613 map->DrawFocus(this, *drawTarget, black, strokeOptions); 2614 } 2615 2616 if (result == ImgDrawResult::SUCCESS) { 2617 mPrevImage = aImage; 2618 } else if (result == ImgDrawResult::BAD_IMAGE) { 2619 mPrevImage = nullptr; 2620 } 2621 2622 return result; 2623 } 2624 2625 already_AddRefed<imgIRequest> nsImageFrame::GetCurrentRequest() const { 2626 if (mKind != Kind::ImageLoadingContent) { 2627 return do_AddRef(mOwnedRequest); 2628 } 2629 2630 MOZ_ASSERT(!mOwnedRequest); 2631 2632 nsCOMPtr<imgIRequest> request; 2633 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); 2634 MOZ_ASSERT(imageLoader); 2635 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, 2636 getter_AddRefs(request)); 2637 return request.forget(); 2638 } 2639 2640 void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 2641 const nsDisplayListSet& aLists) { 2642 if (!IsVisibleForPainting()) { 2643 return; 2644 } 2645 2646 DisplayBorderBackgroundOutline(aBuilder, aLists); 2647 2648 if (HidesContent()) { 2649 DisplaySelectionOverlay(aBuilder, aLists.Content(), 2650 nsISelectionDisplay::DISPLAY_IMAGES); 2651 return; 2652 } 2653 2654 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 2655 auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay()); 2656 if (!clipAxes.isEmpty()) { 2657 nsRect clipRect; 2658 nsRectCornerRadii radii; 2659 bool haveRadii = 2660 ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii); 2661 clipState.ClipContainingBlockDescendants( 2662 clipRect + aBuilder->ToReferenceFrame(this), 2663 haveRadii ? &radii : nullptr); 2664 } 2665 2666 if (!mComputedSize.IsEmpty()) { 2667 const bool imageOK = mKind != Kind::ImageLoadingContent || 2668 ImageOk(mContent->AsElement()->State()); 2669 2670 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest(); 2671 2672 const bool isViewTransition = mKind == Kind::ViewTransition; 2673 const bool isImageFromStyle = mKind != Kind::ImageLoadingContent && 2674 mKind != Kind::XULImage && !isViewTransition; 2675 const bool drawAltFeedback = [&] { 2676 if (!imageOK) { 2677 return true; 2678 } 2679 if (isImageFromStyle && !GetImageFromStyle()->IsImageRequestType()) { 2680 // If we're a gradient, we don't need to draw alt feedback. 2681 return false; 2682 } 2683 if (isViewTransition) { 2684 // Same for view transitions. 2685 return false; 2686 } 2687 // XXX(seth): The SizeIsAvailable check here should not be necessary - the 2688 // intention is that a non-null mImage means we have a size, but there is 2689 // currently some code that violates this invariant. 2690 return !mImage || !SizeIsAvailable(currentRequest); 2691 }(); 2692 2693 if (drawAltFeedback) { 2694 // No image yet, or image load failed. Draw the alt-text and an icon 2695 // indicating the status 2696 aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this); 2697 2698 // This image is visible (we are being asked to paint it) but it's not 2699 // decoded yet. And we are not going to ask the image to draw, so this 2700 // may be the only chance to tell it that it should decode. 2701 if (currentRequest) { 2702 uint32_t status = 0; 2703 currentRequest->GetImageStatus(&status); 2704 if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) { 2705 MaybeDecodeForPredictedSize(); 2706 } 2707 // Increase loading priority if the image is ready to be displayed. 2708 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) { 2709 currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY); 2710 } 2711 } 2712 } else { 2713 if (mImage || isViewTransition) { 2714 aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this); 2715 } else if (isImageFromStyle) { 2716 aLists.Content()->AppendNewToTop<nsDisplayGradient>(aBuilder, this); 2717 } 2718 2719 // If we were previously displaying an icon, we're not anymore 2720 if (mDisplayingIcon) { 2721 BrokenImageIcon::RemoveObserver(this); 2722 mDisplayingIcon = false; 2723 } 2724 } 2725 } 2726 2727 if (ShouldDisplaySelection()) { 2728 DisplaySelectionOverlay(aBuilder, aLists.Content(), 2729 nsISelectionDisplay::DISPLAY_IMAGES); 2730 } 2731 2732 BuildDisplayListForNonBlockChildren(aBuilder, aLists); 2733 } 2734 2735 bool nsImageFrame::ShouldDisplaySelection() { 2736 int16_t displaySelection = PresShell()->GetSelectionFlags(); 2737 if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) { 2738 // no need to check the blue border, we cannot be drawn selected. 2739 return false; 2740 } 2741 2742 if (displaySelection != nsISelectionDisplay::DISPLAY_ALL) { 2743 return true; 2744 } 2745 2746 // If the image is currently resize target of the editor, don't draw the 2747 // selection overlay. 2748 HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(PresContext()); 2749 if (!htmlEditor) { 2750 return true; 2751 } 2752 2753 return htmlEditor->GetResizerTarget() != mContent; 2754 } 2755 2756 nsImageMap* nsImageFrame::GetImageMap() { 2757 if (!mImageMap) { 2758 if (nsIContent* map = GetMapElement()) { 2759 mImageMap = new nsImageMap(); 2760 mImageMap->Init(this, map); 2761 } 2762 } 2763 2764 return mImageMap; 2765 } 2766 2767 bool nsImageFrame::IsServerImageMap() { 2768 return mContent->AsElement()->HasAttr(nsGkAtoms::ismap); 2769 } 2770 2771 CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) const { 2772 const nsRect contentRect = GetContentRectRelativeToSelf(); 2773 // Subtract out border and padding here so that the coordinates are 2774 // now relative to the content area of this frame. 2775 return CSSPixel::FromAppUnitsRounded(aPoint - contentRect.TopLeft()); 2776 } 2777 2778 bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, 2779 nsIContent** aNode) { 2780 aTarget.Truncate(); 2781 *aHref = nullptr; 2782 *aNode = nullptr; 2783 2784 // Walk up the content tree, looking for an nsIDOMAnchorElement 2785 for (nsIContent* content = mContent->GetParent(); content; 2786 content = content->GetParent()) { 2787 nsCOMPtr<dom::Link> link = do_QueryInterface(content); 2788 if (!link) { 2789 continue; 2790 } 2791 if (nsCOMPtr<nsIURI> href = link->GetURI()) { 2792 href.forget(aHref); 2793 } 2794 2795 if (auto* anchor = HTMLAnchorElement::FromNode(content)) { 2796 anchor->GetLinkTarget(aTarget); 2797 } 2798 NS_ADDREF(*aNode = content); 2799 return *aHref != nullptr; 2800 } 2801 return false; 2802 } 2803 2804 bool nsImageFrame::IsLeafDynamic() const { 2805 if (mKind != Kind::ImageLoadingContent) { 2806 // Image frames created for "content: url()" could have an author-controlled 2807 // shadow root, we want to be a regular leaf for those. 2808 return true; 2809 } 2810 // For elements that create image frames, calling attachShadow() will throw, 2811 // so the only ShadowRoot we could ever have is a UA widget. 2812 const auto* shadow = mContent->AsElement()->GetShadowRoot(); 2813 MOZ_ASSERT_IF(shadow, shadow->IsUAWidget()); 2814 return !shadow; 2815 } 2816 2817 nsIContent* nsImageFrame::GetContentForEvent(const WidgetEvent* aEvent) const { 2818 if (mImageMap) { 2819 // XXX We need to make this special check for area element's capturing the 2820 // mouse due to bug 135040. Remove it once that's fixed. 2821 nsIContent* capturingContent = aEvent->HasMouseEventMessage() 2822 ? PresShell::GetCapturingContent() 2823 : nullptr; 2824 if (capturingContent && capturingContent->GetPrimaryFrame() == this) { 2825 return capturingContent; 2826 } 2827 const CSSIntPoint p = TranslateEventCoords( 2828 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this})); 2829 if (auto* area = mImageMap->GetArea(p)) { 2830 return area; 2831 } 2832 } 2833 return nsIFrame::GetContentForEvent(aEvent); 2834 } 2835 2836 // XXX what should clicks on transparent pixels do? 2837 nsresult nsImageFrame::HandleEvent(nsPresContext* aPresContext, 2838 WidgetGUIEvent* aEvent, 2839 nsEventStatus* aEventStatus) { 2840 NS_ENSURE_ARG_POINTER(aEventStatus); 2841 2842 if ((aEvent->mMessage == ePointerClick && 2843 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) || 2844 aEvent->mMessage == eMouseMove) { 2845 nsImageMap* map = GetImageMap(); 2846 bool isServerMap = IsServerImageMap(); 2847 if (map || isServerMap) { 2848 CSSIntPoint p = 2849 TranslateEventCoords(nsLayoutUtils::GetEventCoordinatesRelativeTo( 2850 aEvent, RelativeTo{this})); 2851 2852 // Even though client-side image map triggering happens 2853 // through content, we need to make sure we're not inside 2854 // (in case we deal with a case of both client-side and 2855 // sever-side on the same image - it happens!) 2856 const bool inside = map && map->GetArea(p); 2857 2858 if (!inside && isServerMap) { 2859 // Server side image maps use the href in a containing anchor 2860 // element to provide the basis for the destination url. 2861 nsCOMPtr<nsIURI> uri; 2862 nsAutoString target; 2863 nsCOMPtr<nsIContent> anchorNode; 2864 if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target, 2865 getter_AddRefs(anchorNode))) { 2866 // XXX if the mouse is over/clicked in the border/padding area 2867 // we should probably just pretend nothing happened. Nav4 2868 // keeps the x,y coordinates positive as we do; IE doesn't 2869 // bother. Both of them send the click through even when the 2870 // mouse is over the border. 2871 if (p.x < 0) { 2872 p.x = 0; 2873 } 2874 if (p.y < 0) { 2875 p.y = 0; 2876 } 2877 2878 nsAutoCString spec; 2879 nsresult rv = uri->GetSpec(spec); 2880 NS_ENSURE_SUCCESS(rv, rv); 2881 2882 spec += nsPrintfCString("?%d,%d", p.x.value, p.y.value); 2883 rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri); 2884 NS_ENSURE_SUCCESS(rv, rv); 2885 2886 if (aEvent->mMessage == ePointerClick && 2887 !aEvent->DefaultPrevented()) { 2888 *aEventStatus = nsEventStatus_eConsumeDoDefault; 2889 nsContentUtils::TriggerLinkClick( 2890 anchorNode, uri, target, 2891 aEvent->IsTrusted() ? UserNavigationInvolvement::Activation 2892 : UserNavigationInvolvement::None); 2893 } else { 2894 nsContentUtils::TriggerLinkMouseOver(anchorNode, uri, target); 2895 } 2896 } 2897 } 2898 } 2899 } 2900 2901 return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent, 2902 aEventStatus); 2903 } 2904 2905 nsIFrame::Cursor nsImageFrame::GetCursor(const nsPoint& aPoint) { 2906 nsImageMap* map = GetImageMap(); 2907 if (!map) { 2908 return nsIFrame::GetCursor(aPoint); 2909 } 2910 const CSSIntPoint p = TranslateEventCoords(aPoint); 2911 HTMLAreaElement* area = map->GetArea(p); 2912 if (!area) { 2913 return nsIFrame::GetCursor(aPoint); 2914 } 2915 2916 // Use the cursor from the style of the *area* element. 2917 RefPtr<ComputedStyle> areaStyle = 2918 PresShell()->StyleSet()->ResolveStyleLazily(*area); 2919 2920 // This is one of the cases, like the <xul:tree> pseudo-style stuff, where we 2921 // get styles out of the blue and expect to trigger image loads for those. 2922 areaStyle->StartImageLoads(*PresContext()->Document()); 2923 2924 StyleCursorKind kind = areaStyle->StyleUI()->Cursor().keyword; 2925 if (kind == StyleCursorKind::Auto) { 2926 kind = StyleCursorKind::Default; 2927 } 2928 return Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)}; 2929 } 2930 2931 nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID, 2932 nsAtom* aAttribute, 2933 AttrModType aModType) { 2934 nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID, 2935 aAttribute, aModType); 2936 if (NS_FAILED(rv)) { 2937 return rv; 2938 } 2939 if (nsGkAtoms::alt == aAttribute) { 2940 PresShell()->FrameNeedsReflow( 2941 this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); 2942 } 2943 if (mKind == Kind::XULImage && aAttribute == nsGkAtoms::src && 2944 aNameSpaceID == kNameSpaceID_None) { 2945 UpdateXULImage(); 2946 } 2947 return NS_OK; 2948 } 2949 2950 void nsImageFrame::OnVisibilityChange( 2951 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) { 2952 if (mKind == Kind::ImageLoadingContent) { 2953 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent); 2954 imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); 2955 } 2956 2957 if (aNewVisibility == Visibility::ApproximatelyVisible && 2958 PresShell()->IsActive()) { 2959 MaybeDecodeForPredictedSize(); 2960 } 2961 2962 nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); 2963 } 2964 2965 void nsImageFrame::MarkIntrinsicISizesDirty() { 2966 // Recompute our intrinsic size and ratio. It's important that we pass 'true' 2967 // here -- that makes us recompute the intrinsic size *and* ratio, regardless 2968 // of their current value. Without that, EnsureIntrinsicSizeAndRatio assumes 2969 // the intrinsic ratio can't have changed in some cases; but if we're a 2970 // "content-visibility:auto" image that's being scrolled back into view, our 2971 // ratio may have changed from not-existing to existing, per spec text[1] 2972 // about suppressing the natural aspect ratio when size-contained. 2973 // [1] https://drafts.csswg.org/css-contain-2/#containment-size 2974 EnsureIntrinsicSizeAndRatio(true); 2975 2976 // Call superclass method: 2977 nsAtomicContainerFrame::MarkIntrinsicISizesDirty(); 2978 } 2979 2980 #ifdef DEBUG_FRAME_DUMP 2981 nsresult nsImageFrame::GetFrameName(nsAString& aResult) const { 2982 return MakeFrameName(u"ImageFrame"_ns, aResult); 2983 } 2984 2985 void nsImageFrame::List(FILE* out, const char* aPrefix, 2986 ListFlags aFlags) const { 2987 nsCString str; 2988 ListGeneric(str, aPrefix, aFlags); 2989 2990 // output the img src url 2991 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) { 2992 nsCOMPtr<nsIURI> uri = currentRequest->GetURI(); 2993 nsAutoCString uristr; 2994 uri->GetAsciiSpec(uristr); 2995 str += nsPrintfCString(" [src=%s]", uristr.get()); 2996 } 2997 fprintf_stderr(out, "%s\n", str.get()); 2998 } 2999 #endif 3000 3001 LogicalSides nsImageFrame::GetLogicalSkipSides() const { 3002 LogicalSides skip(mWritingMode); 3003 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 3004 StyleBoxDecorationBreak::Clone)) { 3005 return skip; 3006 } 3007 if (GetPrevInFlow()) { 3008 skip += LogicalSide::BStart; 3009 } 3010 if (GetNextInFlow()) { 3011 skip += LogicalSide::BEnd; 3012 } 3013 return skip; 3014 } 3015 NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver) 3016 3017 nsImageListener::nsImageListener(nsImageFrame* aFrame) : mFrame(aFrame) {} 3018 3019 nsImageListener::~nsImageListener() = default; 3020 3021 void nsImageListener::Notify(imgIRequest* aRequest, int32_t aType, 3022 const nsIntRect* aData) { 3023 if (!mFrame) { 3024 return; 3025 } 3026 3027 return mFrame->Notify(aRequest, aType, aData); 3028 } 3029 3030 static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) { 3031 if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode()) { 3032 return false; 3033 } 3034 // Check if the parent of the closest nsBlockFrame has auto width. 3035 nsBlockFrame* ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame); 3036 if (ancestor->Style()->GetPseudoType() == PseudoStyleType::cellContent) { 3037 // Assume direct parent is a table cell frame. 3038 nsIFrame* grandAncestor = static_cast<nsIFrame*>(ancestor->GetParent()); 3039 return grandAncestor && 3040 grandAncestor->StylePosition() 3041 ->GetWidth(AnchorPosResolutionParams::From(grandAncestor)) 3042 ->IsAuto(); 3043 } 3044 return false; 3045 } 3046 3047 void nsImageFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput, 3048 InlineMinISizeData* aData) { 3049 // Note: we are one of the children that mPercentageBasisForChildren was 3050 // prepared for (i.e. our parent frame prepares the percentage basis for us, 3051 // not for our own children). Hence it's fine that we're resolving our 3052 // percentages sizes against this basis in IntrinsicForContainer(). 3053 nscoord isize = nsLayoutUtils::IntrinsicForContainer( 3054 aInput.mContext, this, IntrinsicISizeType::MinISize, 3055 aInput.mPercentageBasisForChildren); 3056 bool canBreak = !IsInAutoWidthTableCellForQuirk(this); 3057 aData->DefaultAddInlineMinISize(this, isize, canBreak); 3058 } 3059 3060 void nsImageFrame::ReleaseGlobals() { BrokenImageIcon::Shutdown(); }