Bug 1760439 [wpt PR 33220] - Implement FedCM permission delegates in content_shell...
[gecko.git] / layout / generic / nsImageFrame.cpp
blobda860cbc156dbb997d3d50b8d6ed78cd9e1fde1e
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object for replaced elements with image data */
9 #include "nsImageFrame.h"
11 #include "TextDrawTarget.h"
12 #include "gfx2DGlue.h"
13 #include "gfxContext.h"
14 #include "gfxUtils.h"
15 #include "mozilla/intl/BidiEmbeddingLevel.h"
16 #include "mozilla/ComputedStyle.h"
17 #include "mozilla/DebugOnly.h"
18 #include "mozilla/Encoding.h"
19 #include "mozilla/EventStates.h"
20 #include "mozilla/HTMLEditor.h"
21 #include "mozilla/dom/ImageTracker.h"
22 #include "mozilla/gfx/2D.h"
23 #include "mozilla/gfx/Helpers.h"
24 #include "mozilla/gfx/PathHelpers.h"
25 #include "mozilla/dom/GeneratedImageContent.h"
26 #include "mozilla/dom/HTMLAreaElement.h"
27 #include "mozilla/dom/HTMLImageElement.h"
28 #include "mozilla/dom/ResponsiveImageSelector.h"
29 #include "mozilla/image/WebRenderImageProvider.h"
30 #include "mozilla/layers/RenderRootStateManager.h"
31 #include "mozilla/layers/WebRenderLayerManager.h"
32 #include "mozilla/MouseEvents.h"
33 #include "mozilla/PresShell.h"
34 #include "mozilla/PresShellInlines.h"
35 #include "mozilla/StaticPrefs_image.h"
36 #include "mozilla/StaticPrefs_layout.h"
37 #include "mozilla/SVGImageContext.h"
38 #include "mozilla/Unused.h"
40 #include "nsCOMPtr.h"
41 #include "nsFontMetrics.h"
42 #include "nsIFrameInlines.h"
43 #include "nsIImageLoadingContent.h"
44 #include "nsImageLoadingContent.h"
45 #include "nsImageRenderer.h"
46 #include "nsString.h"
47 #include "nsPrintfCString.h"
48 #include "nsPresContext.h"
49 #include "nsGkAtoms.h"
50 #include "mozilla/dom/Document.h"
51 #include "nsContentUtils.h"
52 #include "nsCSSAnonBoxes.h"
53 #include "nsStyleConsts.h"
54 #include "nsStyleUtil.h"
55 #include "nsTransform2D.h"
56 #include "nsImageMap.h"
57 #include "nsILoadGroup.h"
58 #include "nsNetUtil.h"
59 #include "nsNetCID.h"
60 #include "nsCSSRendering.h"
61 #include "nsNameSpaceManager.h"
62 #include <algorithm>
63 #ifdef ACCESSIBILITY
64 # include "nsAccessibilityService.h"
65 #endif
66 #include "nsLayoutUtils.h"
67 #include "nsDisplayList.h"
68 #include "nsIContent.h"
69 #include "mozilla/dom/Selection.h"
70 #include "nsIURIMutator.h"
72 #include "imgIContainer.h"
73 #include "imgLoader.h"
74 #include "imgRequestProxy.h"
76 #include "nsCSSFrameConstructor.h"
77 #include "nsRange.h"
79 #include "nsError.h"
80 #include "nsBidiUtils.h"
81 #include "nsBidiPresUtils.h"
83 #include "gfxRect.h"
84 #include "ImageRegion.h"
85 #include "ImageContainer.h"
86 #include "mozilla/ServoStyleSet.h"
87 #include "nsBlockFrame.h"
88 #include "nsStyleStructInlines.h"
90 #include "mozilla/Preferences.h"
92 #include "mozilla/dom/Link.h"
93 #include "mozilla/dom/HTMLAnchorElement.h"
94 #include "mozilla/dom/BrowserChild.h"
96 using namespace mozilla;
97 using namespace mozilla::dom;
98 using namespace mozilla::gfx;
99 using namespace mozilla::image;
100 using namespace mozilla::layers;
102 using mozilla::layout::TextDrawTarget;
104 class nsDisplayGradient final : public nsPaintedDisplayItem {
105 public:
106 nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame)
107 : nsPaintedDisplayItem(aBuilder, aFrame) {
108 MOZ_COUNT_CTOR(nsDisplayGradient);
110 ~nsDisplayGradient() final { MOZ_COUNT_DTOR(nsDisplayGradient); }
112 nsDisplayItemGeometry* AllocateGeometry(
113 nsDisplayListBuilder* aBuilder) final {
114 return new nsDisplayItemGenericImageGeometry(this, aBuilder);
117 nsRect GetBounds(bool* aSnap) const {
118 *aSnap = true;
119 return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
122 nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
123 return GetBounds(aSnap);
126 void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
128 bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
129 mozilla::wr::IpcResourceUpdateQueue&,
130 const StackingContextHelper&,
131 mozilla::layers::RenderRootStateManager*,
132 nsDisplayListBuilder*) final;
134 NS_DISPLAY_DECL_NAME("Gradient", TYPE_GRADIENT)
137 void nsDisplayGradient::Paint(nsDisplayListBuilder* aBuilder,
138 gfxContext* aCtx) {
139 auto* frame = static_cast<nsImageFrame*>(Frame());
140 nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
141 aBuilder->GetImageRendererFlags());
142 nsSize size = frame->GetSize();
143 imageRenderer.SetPreferredSize({}, size);
145 ImgDrawResult result;
146 if (!imageRenderer.PrepareImage()) {
147 result = imageRenderer.PrepareResult();
148 } else {
149 nsRect dest(ToReferenceFrame(), size);
150 result = imageRenderer.DrawLayer(
151 frame->PresContext(), *aCtx, dest, dest, dest.TopLeft(),
152 GetPaintRect(aBuilder, aCtx), dest.Size(), /* aOpacity = */ 1.0f);
154 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
157 bool nsDisplayGradient::CreateWebRenderCommands(
158 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
159 const StackingContextHelper& aSc,
160 mozilla::layers::RenderRootStateManager* aManager,
161 nsDisplayListBuilder* aDisplayListBuilder) {
162 auto* frame = static_cast<nsImageFrame*>(Frame());
163 nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
164 aDisplayListBuilder->GetImageRendererFlags());
165 nsSize size = frame->GetSize();
166 imageRenderer.SetPreferredSize({}, size);
168 ImgDrawResult result;
169 if (!imageRenderer.PrepareImage()) {
170 result = imageRenderer.PrepareResult();
171 } else {
172 nsRect dest(ToReferenceFrame(), size);
173 result = imageRenderer.BuildWebRenderDisplayItemsForLayer(
174 frame->PresContext(), aBuilder, aResources, aSc, aManager, this, dest,
175 dest, dest.TopLeft(), dest, dest.Size(),
176 /* aOpacity = */ 1.0f);
177 if (result == ImgDrawResult::NOT_SUPPORTED) {
178 return false;
181 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
182 return true;
185 // sizes (pixels) for image icon, padding and border frame
186 #define ICON_SIZE (16)
187 #define ICON_PADDING (3)
188 #define ALT_BORDER_WIDTH (1)
190 // Default alignment value (so we can tell an unset value from a set value)
191 #define ALIGN_UNSET uint8_t(-1)
193 // static icon information
194 StaticRefPtr<nsImageFrame::IconLoad> nsImageFrame::gIconLoad;
196 // test if the width and height are fixed, looking at the style data
197 // This is used by nsImageFrame::ShouldCreateImageFrameFor and should
198 // not be used for layout decisions.
199 static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) {
200 // check the width and height values in the reflow input's style struct
201 // - if width and height are specified as either coord or percentage, then
202 // the size of the image frame is constrained
203 return aStylePosition->mWidth.IsLengthPercentage() &&
204 aStylePosition->mHeight.IsLengthPercentage();
207 template <typename SizeOrMaxSize>
208 static bool DependsOnIntrinsicSize(const SizeOrMaxSize& aMinOrMaxSize) {
209 auto length = nsIFrame::ToExtremumLength(aMinOrMaxSize);
210 if (!length) {
211 return false;
213 switch (*length) {
214 case nsIFrame::ExtremumLength::MinContent:
215 case nsIFrame::ExtremumLength::MaxContent:
216 case nsIFrame::ExtremumLength::FitContent:
217 case nsIFrame::ExtremumLength::FitContentFunction:
218 return true;
219 case nsIFrame::ExtremumLength::MozAvailable:
220 return false;
222 MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
223 return false;
226 // Decide whether we can optimize away reflows that result from the
227 // image's intrinsic size changing.
228 static bool SizeDependsOnIntrinsicSize(const ReflowInput& aReflowInput) {
229 const auto& position = *aReflowInput.mStylePosition;
230 WritingMode wm = aReflowInput.GetWritingMode();
231 // Don't try to make this optimization when an image has percentages
232 // in its 'width' or 'height'. The percentages might be treated like
233 // auto (especially for intrinsic width calculations and for heights).
235 // min-width: min-content and such can also affect our intrinsic size.
236 // but note that those keywords on the block axis behave like auto, so we
237 // don't need to check them.
239 // Flex item's min-[width|height]:auto resolution depends on intrinsic size.
240 return !position.mHeight.ConvertsToLength() ||
241 !position.mWidth.ConvertsToLength() ||
242 DependsOnIntrinsicSize(position.MinISize(wm)) ||
243 DependsOnIntrinsicSize(position.MaxISize(wm)) ||
244 aReflowInput.mFrame->IsFlexItem();
247 nsIFrame* NS_NewImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
248 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
249 nsImageFrame::Kind::ImageElement);
252 nsIFrame* NS_NewImageFrameForContentProperty(PresShell* aPresShell,
253 ComputedStyle* aStyle) {
254 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
255 nsImageFrame::Kind::ContentProperty);
258 nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell,
259 ComputedStyle* aStyle) {
260 return new (aPresShell)
261 nsImageFrame(aStyle, aPresShell->GetPresContext(),
262 nsImageFrame::Kind::ContentPropertyAtIndex);
265 nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell,
266 ComputedStyle* aStyle) {
267 return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
268 nsImageFrame::Kind::ListStyleImage);
271 bool nsImageFrame::ShouldShowBrokenImageIcon() const {
272 // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and
273 // Blink behave differently here for content: url(..), for now adapt to
274 // Blink's behavior.
275 if (mKind != Kind::ImageElement) {
276 return false;
279 // <img alt=""> is special, and it shouldn't draw the broken image icon,
280 // unlike the no-alt attribute or non-empty-alt-attribute case.
281 if (auto* image = HTMLImageElement::FromNode(mContent)) {
282 const nsAttrValue* alt = image->GetParsedAttr(nsGkAtoms::alt);
283 if (alt && alt->IsEmptyString()) {
284 return false;
288 // check for broken images. valid null images (eg. img src="") are
289 // not considered broken because they have no image requests
290 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
291 uint32_t imageStatus;
292 return NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
293 (imageStatus & imgIRequest::STATUS_ERROR);
296 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
297 MOZ_ASSERT(imageLoader);
298 // Show the broken image icon only if we've tried to perform a load at all
299 // (that is, if we have a current uri).
300 nsCOMPtr<nsIURI> currentURI = imageLoader->GetCurrentURI();
301 return !!currentURI;
304 nsImageFrame* nsImageFrame::CreateContinuingFrame(
305 mozilla::PresShell* aPresShell, ComputedStyle* aStyle) const {
306 return new (aPresShell)
307 nsImageFrame(aStyle, aPresShell->GetPresContext(), mKind);
310 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
312 nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
313 ClassID aID, Kind aKind)
314 : nsAtomicContainerFrame(aStyle, aPresContext, aID),
315 mComputedSize(0, 0),
316 mIntrinsicSize(0, 0),
317 mKind(aKind),
318 mContentURLRequestRegistered(false),
319 mDisplayingIcon(false),
320 mFirstFrameComplete(false),
321 mReflowCallbackPosted(false),
322 mForceSyncDecoding(false) {
323 EnableVisibilityTracking();
326 nsImageFrame::~nsImageFrame() = default;
328 NS_QUERYFRAME_HEAD(nsImageFrame)
329 NS_QUERYFRAME_ENTRY(nsImageFrame)
330 NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
332 #ifdef ACCESSIBILITY
333 a11y::AccType nsImageFrame::AccessibleType() {
334 if (mKind == Kind::ListStyleImage) {
335 // This is an HTMLListBulletAccessible.
336 return a11y::eNoType;
339 // Don't use GetImageMap() to avoid reentrancy into accessibility.
340 if (HasImageMap()) {
341 return a11y::eHTMLImageMapType;
344 return a11y::eImageType;
346 #endif
348 void nsImageFrame::DisconnectMap() {
349 if (!mImageMap) {
350 return;
353 mImageMap->Destroy();
354 mImageMap = nullptr;
356 #ifdef ACCESSIBILITY
357 if (nsAccessibilityService* accService = GetAccService()) {
358 accService->RecreateAccessible(PresShell(), mContent);
360 #endif
363 void nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot,
364 PostDestroyData& aPostDestroyData) {
365 if (mReflowCallbackPosted) {
366 PresShell()->CancelReflowCallback(this);
367 mReflowCallbackPosted = false;
370 // Tell our image map, if there is one, to clean up
371 // This causes the nsImageMap to unregister itself as
372 // a DOM listener.
373 DisconnectMap();
375 MOZ_ASSERT(mListener);
377 if (mKind == Kind::ImageElement) {
378 MOZ_ASSERT(!mContentURLRequest);
379 MOZ_ASSERT(!mContentURLRequestRegistered);
380 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
381 MOZ_ASSERT(imageLoader);
383 // Notify our image loading content that we are going away so it can
384 // deregister with our refresh driver.
385 imageLoader->FrameDestroyed(this);
386 imageLoader->RemoveNativeObserver(mListener);
387 } else if (mContentURLRequest) {
388 PresContext()->Document()->ImageTracker()->Remove(mContentURLRequest);
389 nsLayoutUtils::DeregisterImageRequest(PresContext(), mContentURLRequest,
390 &mContentURLRequestRegistered);
391 mContentURLRequest->Cancel(NS_BINDING_ABORTED);
394 // set the frame to null so we don't send messages to a dead object.
395 mListener->SetFrame(nullptr);
396 mListener = nullptr;
398 // If we were displaying an icon, take ourselves off the list
399 if (mDisplayingIcon) gIconLoad->RemoveIconObserver(this);
401 nsAtomicContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
404 void nsImageFrame::MaybeRecordContentUrlOnImageTelemetry() {
405 if (mKind != Kind::ImageElement) {
406 return;
408 const auto& content = *StyleContent();
409 if (content.ContentCount() != 1) {
410 return;
412 const auto& item = content.ContentAt(0);
413 if (!item.IsImage()) {
414 return;
416 PresContext()->Document()->SetUseCounter(
417 eUseCounter_custom_ContentUrlOnImageContent);
420 void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
421 nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle);
423 MaybeRecordContentUrlOnImageTelemetry();
425 // A ::marker's default size is calculated from the font's em-size.
426 if (IsForMarkerPseudo()) {
427 mIntrinsicSize = IntrinsicSize(0, 0);
428 UpdateIntrinsicSize();
431 auto newOrientation = StyleVisibility()->mImageOrientation;
433 // We need to update our orientation either if we had no ComputedStyle before
434 // because this is the first time it's been set, or if the image-orientation
435 // property changed from its previous value.
436 bool shouldUpdateOrientation =
437 mImage &&
438 (!aOldStyle ||
439 aOldStyle->StyleVisibility()->mImageOrientation != newOrientation);
441 if (shouldUpdateOrientation) {
442 nsCOMPtr<imgIContainer> image(mImage->Unwrap());
443 mImage = nsLayoutUtils::OrientImage(image, newOrientation);
445 UpdateIntrinsicSize();
446 UpdateIntrinsicRatio();
447 } else if (!aOldStyle || aOldStyle->StylePosition()->mAspectRatio !=
448 StylePosition()->mAspectRatio) {
449 UpdateIntrinsicRatio();
453 static bool SizeIsAvailable(imgIRequest* aRequest) {
454 if (!aRequest) {
455 return false;
458 uint32_t imageStatus = 0;
459 nsresult rv = aRequest->GetImageStatus(&imageStatus);
460 return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
463 const StyleImage* nsImageFrame::GetImageFromStyle() const {
464 if (mKind == Kind::ImageElement) {
465 MOZ_ASSERT_UNREACHABLE("Don't call me");
466 return nullptr;
468 if (mKind == Kind::ListStyleImage) {
469 MOZ_ASSERT(
470 GetParent()->GetContent()->IsGeneratedContentContainerForMarker());
471 MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
472 return &StyleList()->mListStyleImage;
474 uint32_t contentIndex = 0;
475 const nsStyleContent* styleContent = StyleContent();
476 if (mKind == Kind::ContentPropertyAtIndex) {
477 MOZ_RELEASE_ASSERT(
478 mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
479 contentIndex = static_cast<GeneratedImageContent*>(mContent.get())->Index();
481 // TODO(emilio): Consider inheriting the `content` property instead of doing
482 // this parent traversal?
483 nsIFrame* parent = GetParent();
484 MOZ_DIAGNOSTIC_ASSERT(
485 parent->GetContent()->IsGeneratedContentContainerForMarker() ||
486 parent->GetContent()->IsGeneratedContentContainerForAfter() ||
487 parent->GetContent()->IsGeneratedContentContainerForBefore());
488 nsIFrame* nonAnonymousParent = parent;
489 while (nonAnonymousParent->Style()->IsAnonBox()) {
490 nonAnonymousParent = nonAnonymousParent->GetParent();
492 MOZ_DIAGNOSTIC_ASSERT(parent->GetContent() ==
493 nonAnonymousParent->GetContent());
494 styleContent = nonAnonymousParent->StyleContent();
496 MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount());
497 auto& contentItem = styleContent->ContentAt(contentIndex);
498 MOZ_RELEASE_ASSERT(contentItem.IsImage());
499 return &contentItem.AsImage();
502 void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
503 nsIFrame* aPrevInFlow) {
504 MOZ_ASSERT_IF(aPrevInFlow,
505 aPrevInFlow->Type() == Type() &&
506 static_cast<nsImageFrame*>(aPrevInFlow)->mKind == mKind);
508 nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
510 mListener = new nsImageListener(this);
512 if (!gIconLoad) {
513 LoadIcons(PresContext());
516 if (mKind == Kind::ImageElement) {
517 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
518 MOZ_ASSERT(imageLoader);
519 imageLoader->AddNativeObserver(mListener);
520 // We have a PresContext now, so we need to notify the image content node
521 // that it can register images.
522 imageLoader->FrameCreated(this);
523 } else {
524 const StyleImage* image = GetImageFromStyle();
525 MOZ_ASSERT(mKind == Kind::ListStyleImage || image->IsImageRequestType(),
526 "Content image should only parse url() type");
527 if (image->IsImageRequestType()) {
528 if (imgRequestProxy* proxy = image->GetImageRequest()) {
529 proxy->Clone(mListener, PresContext()->Document(),
530 getter_AddRefs(mContentURLRequest));
531 SetupForContentURLRequest();
536 // Give image loads associated with an image frame a small priority boost.
537 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
538 uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
540 // Increase load priority further if intrinsic size might be important for
541 // layout.
542 if (!HaveSpecifiedSize(StylePosition())) {
543 categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
546 currentRequest->BoostPriority(categoryToBoostPriority);
550 void nsImageFrame::SetupForContentURLRequest() {
551 MOZ_ASSERT(mKind != Kind::ImageElement);
552 if (!mContentURLRequest) {
553 return;
556 // We're not using AssociateRequestToFrame for the content property, so we
557 // need to add it to the image tracker manually.
558 PresContext()->Document()->ImageTracker()->Add(mContentURLRequest);
560 uint32_t status = 0;
561 nsresult rv = mContentURLRequest->GetImageStatus(&status);
562 if (NS_FAILED(rv)) {
563 return;
566 if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
567 nsCOMPtr<imgIContainer> image;
568 mContentURLRequest->GetImage(getter_AddRefs(image));
569 OnSizeAvailable(mContentURLRequest, image);
572 if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
573 mFirstFrameComplete = true;
576 if (status & imgIRequest::STATUS_IS_ANIMATED) {
577 nsLayoutUtils::RegisterImageRequest(PresContext(), mContentURLRequest,
578 &mContentURLRequestRegistered);
582 static void ScaleIntrinsicSizeForDensity(IntrinsicSize& aSize,
583 const ImageResolution& aResolution) {
584 if (aSize.width) {
585 aResolution.ApplyXTo(aSize.width.ref());
587 if (aSize.height) {
588 aResolution.ApplyYTo(aSize.height.ref());
592 static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage,
593 nsIContent& aContent,
594 IntrinsicSize& aSize) {
595 ImageResolution resolution = aImage->GetResolution();
596 if (auto* image = HTMLImageElement::FromNode(aContent)) {
597 if (auto* selector = image->GetResponsiveImageSelector()) {
598 resolution.ScaleBy(selector->GetSelectedImageDensity());
601 ScaleIntrinsicSizeForDensity(aSize, resolution);
604 static nscoord ListImageDefaultLength(const nsImageFrame& aFrame) {
605 // https://drafts.csswg.org/css-lists-3/#image-markers
606 // The spec says we should use 1em x 1em, but that seems too large.
607 // See disussion in https://github.com/w3c/csswg-drafts/issues/4207
608 auto* pc = aFrame.PresContext();
609 RefPtr<nsFontMetrics> fm =
610 nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc);
611 auto emAU = fm->GetThebesFontGroup()
612 ->GetFirstValidFont()
613 ->GetMetrics(fm->Orientation())
614 .emHeight *
615 pc->AppUnitsPerDevPixel();
616 return std::max(NSToCoordRound(0.4f * emAU),
617 nsPresContext::CSSPixelsToAppUnits(1));
620 static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
621 bool aUseMappedRatio,
622 nsImageFrame::Kind aKind,
623 const nsImageFrame& aFrame) {
624 const ComputedStyle& style = *aFrame.Style();
625 if (style.StyleDisplay()->IsContainSize()) {
626 return IntrinsicSize(0, 0);
629 nsSize size;
630 if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) {
631 IntrinsicSize intrinsicSize;
632 intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width);
633 intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height);
634 if (aKind == nsImageFrame::Kind::ListStyleImage) {
635 if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) {
636 nscoord defaultLength = ListImageDefaultLength(aFrame);
637 if (intrinsicSize.width.isNothing()) {
638 intrinsicSize.width = Some(defaultLength);
640 if (intrinsicSize.height.isNothing()) {
641 intrinsicSize.height = Some(defaultLength);
645 if (aKind == nsImageFrame::Kind::ImageElement) {
646 ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize);
647 } else {
648 ScaleIntrinsicSizeForDensity(intrinsicSize,
649 aFrame.GetImageFromStyle()->GetResolution());
651 return intrinsicSize;
654 if (aKind == nsImageFrame::Kind::ListStyleImage) {
655 // Note: images are handled above, this handles gradients etc.
656 nscoord defaultLength = ListImageDefaultLength(aFrame);
657 return IntrinsicSize(defaultLength, defaultLength);
660 if (aFrame.ShouldShowBrokenImageIcon()) {
661 nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
662 ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
663 return IntrinsicSize(edgeLengthToUse, edgeLengthToUse);
666 if (aUseMappedRatio && style.StylePosition()->mAspectRatio.HasRatio()) {
667 return IntrinsicSize();
670 return IntrinsicSize(0, 0);
673 // For compat reasons, see bug 1602047, we don't use the intrinsic ratio from
674 // width="" and height="" for images with no src attribute (no request).
676 // But we shouldn't get fooled by <img loading=lazy>. We do want to apply the
677 // ratio then...
678 bool nsImageFrame::ShouldUseMappedAspectRatio() const {
679 if (mKind != Kind::ImageElement) {
680 return true;
682 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
683 if (currentRequest) {
684 return true;
686 // TODO(emilio): Investigate the compat situation of the above check, maybe we
687 // can just check for empty src attribute or something...
688 auto* image = HTMLImageElement::FromNode(mContent);
689 return image && image->IsAwaitingLoadOrLazyLoading();
692 bool nsImageFrame::UpdateIntrinsicSize() {
693 IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
694 mIntrinsicSize =
695 ComputeIntrinsicSize(mImage, ShouldUseMappedAspectRatio(), mKind, *this);
696 return mIntrinsicSize != oldIntrinsicSize;
699 static AspectRatio ComputeIntrinsicRatio(imgIContainer* aImage,
700 bool aUseMappedRatio,
701 const nsImageFrame& aFrame) {
702 const ComputedStyle& style = *aFrame.Style();
703 if (style.StyleDisplay()->IsContainSize()) {
704 return AspectRatio();
707 if (aImage) {
708 if (Maybe<AspectRatio> fromImage = aImage->GetIntrinsicRatio()) {
709 return *fromImage;
712 if (aUseMappedRatio) {
713 const StyleAspectRatio& ratio = style.StylePosition()->mAspectRatio;
714 if (ratio.auto_ && ratio.HasRatio()) {
715 // Return the mapped intrinsic aspect ratio stored in
716 // nsStylePosition::mAspectRatio.
717 return ratio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
720 if (aFrame.ShouldShowBrokenImageIcon()) {
721 return AspectRatio(1.0f);
723 return AspectRatio();
726 bool nsImageFrame::UpdateIntrinsicRatio() {
727 AspectRatio oldIntrinsicRatio = mIntrinsicRatio;
728 mIntrinsicRatio =
729 ComputeIntrinsicRatio(mImage, ShouldUseMappedAspectRatio(), *this);
730 return mIntrinsicRatio != oldIntrinsicRatio;
733 bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) {
734 // First, figure out destRect (the rect we're rendering into).
735 // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
736 // own size here, because GetContentRectRelativeToSelf() might be smaller if
737 // we're fragmented, whereas mComputedSize has our full content-box size
738 // (which we need for ComputeObjectDestRect to work correctly).
739 nsRect constraintRect(GetContentRectRelativeToSelf().TopLeft(),
740 mComputedSize);
741 constraintRect.y -= GetContinuationOffset();
743 nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
744 constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition());
745 // Set the translation components, based on destRect
746 // XXXbz does this introduce rounding errors because of the cast to
747 // float? Should we just manually add that stuff in every time
748 // instead?
749 aTransform.SetToTranslate(float(destRect.x), float(destRect.y));
751 // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic
752 // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector,
753 // see ScaleIntrinsicSizeForDensity.
754 nsSize intrinsicSize;
755 if (!mImage || !NS_SUCCEEDED(mImage->GetIntrinsicSize(&intrinsicSize)) ||
756 intrinsicSize.IsEmpty()) {
757 return false;
760 aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width),
761 float(destRect.height) / float(intrinsicSize.height));
762 return true;
765 // This function checks whether the given request is the current request for our
766 // mContent.
767 bool nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const {
768 // Default to pending load in case of errors
769 if (mKind != Kind::ImageElement) {
770 MOZ_ASSERT(aRequest == mContentURLRequest);
771 return false;
774 nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
775 MOZ_ASSERT(imageLoader);
777 int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
778 imageLoader->GetRequestType(aRequest, &requestType);
780 return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
783 nsRect nsImageFrame::SourceRectToDest(const nsIntRect& aRect) {
784 // When scaling the image, row N of the source image may (depending on
785 // the scaling function) be used to draw any row in the destination image
786 // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
787 // floating-point scaling factor. The same holds true for columns.
788 // So, we start by computing that bound without the floor and ceiling.
790 nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
791 nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
792 nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
793 nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
795 nsTransform2D sourceToDest;
796 if (!GetSourceToDestTransform(sourceToDest)) {
797 // Failed to generate transform matrix. Return our whole content area,
798 // to be on the safe side (since this method is used for generating
799 // invalidation rects).
800 return GetContentRectRelativeToSelf();
803 sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
805 // Now, round the edges out to the pixel boundary.
806 nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
807 nscoord right = r.x + r.width;
808 nscoord bottom = r.y + r.height;
810 r.x -= (scale + (r.x % scale)) % scale;
811 r.y -= (scale + (r.y % scale)) % scale;
812 r.width = right + ((scale - (right % scale)) % scale) - r.x;
813 r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
815 return r;
818 static bool ImageOk(EventStates aState) {
819 return !aState.HasState(NS_EVENT_STATE_BROKEN);
822 static bool HasAltText(const Element& aElement) {
823 // We always return some alternate text for <input>, see
824 // nsCSSFrameConstructor::GetAlternateTextFor.
825 if (aElement.IsHTMLElement(nsGkAtoms::input)) {
826 return true;
829 MOZ_ASSERT(aElement.IsHTMLElement(nsGkAtoms::img));
830 return aElement.HasNonEmptyAttr(nsGkAtoms::alt);
833 bool nsImageFrame::ShouldCreateImageFrameForContent(
834 const Element& aElement, const ComputedStyle& aStyle) {
835 if (aElement.IsRootOfNativeAnonymousSubtree()) {
836 return false;
838 const auto& content = aStyle.StyleContent()->mContent;
839 if (!content.IsItems()) {
840 return false;
842 Span<const StyleContentItem> items = content.AsItems().AsSpan();
843 return items.Length() == 1 && items[0].IsImage();
846 // Check if we want to use an image frame or just let the frame constructor make
847 // us into an inline.
848 /* static */
849 bool nsImageFrame::ShouldCreateImageFrameFor(const Element& aElement,
850 const ComputedStyle& aStyle) {
851 if (ShouldCreateImageFrameForContent(aElement, aStyle)) {
852 // Prefer the content property, for compat reasons, see bug 1484928.
853 return false;
856 if (ImageOk(aElement.State())) {
857 // Image is fine or loading; do the image frame thing
858 return true;
861 if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) {
862 return true;
865 // if our "do not show placeholders" pref is set, skip the icon
866 if (gIconLoad && gIconLoad->mPrefForceInlineAltText) {
867 return false;
870 if (!HasAltText(aElement)) {
871 return true;
874 if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks) {
875 // FIXME(emilio): We definitely don't reframe when this changes...
876 return HaveSpecifiedSize(aStyle.StylePosition());
879 return false;
882 void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType,
883 const nsIntRect* aRect) {
884 if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
885 nsCOMPtr<imgIContainer> image;
886 aRequest->GetImage(getter_AddRefs(image));
887 return OnSizeAvailable(aRequest, image);
890 if (aType == imgINotificationObserver::FRAME_UPDATE) {
891 return OnFrameUpdate(aRequest, aRect);
894 if (aType == imgINotificationObserver::FRAME_COMPLETE) {
895 mFirstFrameComplete = true;
898 if (aType == imgINotificationObserver::IS_ANIMATED &&
899 mKind != Kind::ImageElement) {
900 nsLayoutUtils::RegisterImageRequest(PresContext(), mContentURLRequest,
901 &mContentURLRequestRegistered);
904 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
905 uint32_t imgStatus;
906 aRequest->GetImageStatus(&imgStatus);
907 nsresult status =
908 imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
909 return OnLoadComplete(aRequest, status);
913 void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest,
914 imgIContainer* aImage) {
915 if (!aImage) {
916 return;
919 /* Get requested animation policy from the pres context:
920 * normal = 0
921 * one frame = 1
922 * one loop = 2
924 aImage->SetAnimationMode(PresContext()->ImageAnimationMode());
926 if (IsPendingLoad(aRequest)) {
927 // We don't care
928 return;
931 UpdateImage(aRequest, aImage);
934 void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) {
935 MOZ_ASSERT(aRequest);
936 if (SizeIsAvailable(aRequest)) {
937 // This is valid and for the current request, so update our stored image
938 // container, orienting according to our style.
939 mImage = nsLayoutUtils::OrientImage(aImage,
940 StyleVisibility()->mImageOrientation);
941 MOZ_ASSERT(mImage);
942 } else {
943 // We no longer have a valid image, so release our stored image container.
944 mImage = mPrevImage = nullptr;
945 if (mKind == Kind::ListStyleImage) {
946 auto* genContent = static_cast<GeneratedImageContent*>(GetContent());
947 genContent->NotifyLoadFailed();
948 // No need to continue below since the above state change will destroy
949 // this frame.
950 return;
953 bool intrinsicSizeOrRatioChanged = [&] {
954 // NOTE(emilio): We intentionally want to call both functions and avoid
955 // short-circuiting.
956 bool intrinsicSizeChanged = UpdateIntrinsicSize();
957 bool intrinsicRatioChanged = UpdateIntrinsicRatio();
958 return intrinsicSizeChanged || intrinsicRatioChanged;
959 }();
960 if (!GotInitialReflow()) {
961 return;
964 // We're going to need to repaint now either way.
965 InvalidateFrame();
967 if (intrinsicSizeOrRatioChanged) {
968 // Now we need to reflow if we have an unconstrained size and have
969 // already gotten the initial reflow.
970 if (!(mState & IMAGE_SIZECONSTRAINED)) {
971 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
972 NS_FRAME_IS_DIRTY);
973 } else if (PresShell()->IsActive()) {
974 // We've already gotten the initial reflow, and our size hasn't changed,
975 // so we're ready to request a decode.
976 MaybeDecodeForPredictedSize();
981 void nsImageFrame::OnFrameUpdate(imgIRequest* aRequest,
982 const nsIntRect* aRect) {
983 if (NS_WARN_IF(!aRect)) {
984 return;
987 if (!GotInitialReflow()) {
988 // Don't bother to do anything; we have a reflow coming up!
989 return;
992 if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
993 return;
996 if (IsPendingLoad(aRequest)) {
997 // We don't care
998 return;
1001 nsIntRect layerInvalidRect =
1002 mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect;
1004 if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
1005 // Invalidate our entire area.
1006 InvalidateSelf(nullptr, nullptr);
1007 return;
1010 nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
1011 InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
1014 void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
1015 const nsRect* aFrameInvalidRect) {
1016 // Check if WebRender has interacted with this frame. If it has
1017 // we need to let it know that things have changed.
1018 const auto type = DisplayItemType::TYPE_IMAGE;
1019 const auto providerId = mImage ? mImage->GetProviderId() : 0;
1020 if (WebRenderUserData::ProcessInvalidateForImage(this, type, providerId)) {
1021 return;
1024 InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect);
1026 if (!mFirstFrameComplete) {
1027 InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
1028 aFrameInvalidRect);
1032 void nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
1033 NotifyNewCurrentRequest(aRequest, aStatus);
1036 void nsImageFrame::ResponsiveContentDensityChanged() {
1037 if (!GotInitialReflow()) {
1038 return;
1041 if (!mImage) {
1042 return;
1045 if (!UpdateIntrinsicSize() && !UpdateIntrinsicRatio()) {
1046 return;
1049 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
1050 NS_FRAME_IS_DIRTY);
1053 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest,
1054 nsresult aStatus) {
1055 nsCOMPtr<imgIContainer> image;
1056 aRequest->GetImage(getter_AddRefs(image));
1057 NS_ASSERTION(image || NS_FAILED(aStatus),
1058 "Successful load with no container?");
1059 UpdateImage(aRequest, image);
1062 void nsImageFrame::MaybeDecodeForPredictedSize() {
1063 // Check that we're ready to decode.
1064 if (!mImage) {
1065 return; // Nothing to do yet.
1068 if (mComputedSize.IsEmpty()) {
1069 return; // We won't draw anything, so no point in decoding.
1072 if (GetVisibility() != Visibility::ApproximatelyVisible) {
1073 return; // We're not visible, so don't decode.
1076 // OK, we're ready to decode. Compute the scale to the screen...
1077 mozilla::PresShell* presShell = PresContext()->PresShell();
1078 LayoutDeviceToScreenScale2D resolutionToScreen(
1079 presShell->GetCumulativeResolution() *
1080 nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this));
1082 // If we are in a remote browser, then apply scaling from ancestor browsers
1083 if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
1084 resolutionToScreen.xScale *= browserChild->GetEffectsInfo().mScaleX;
1085 resolutionToScreen.yScale *= browserChild->GetEffectsInfo().mScaleY;
1088 // ...and this frame's content box...
1089 const nsPoint offset =
1090 GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
1091 const nsRect frameContentBox = GetContentRectRelativeToSelf() + offset;
1093 // ...and our predicted dest rect...
1094 const int32_t factor = PresContext()->AppUnitsPerDevPixel();
1095 const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
1096 PredictedDestRect(frameContentBox), factor);
1098 // ...and use them to compute our predicted size in screen pixels.
1099 const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen;
1100 const ScreenIntSize predictedScreenIntSize =
1101 RoundedToInt(predictedScreenSize);
1102 if (predictedScreenIntSize.IsEmpty()) {
1103 return;
1106 // Determine the optimal image size to use.
1107 uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
1108 imgIContainer::FLAG_ASYNC_NOTIFY;
1109 SamplingFilter samplingFilter =
1110 nsLayoutUtils::GetSamplingFilterForFrame(this);
1111 gfxSize gfxPredictedScreenSize =
1112 gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height);
1113 nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest(
1114 gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter,
1115 flags);
1117 // Request a decode.
1118 mImage->RequestDecodeForSize(predictedImageSize, flags);
1121 nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) {
1122 // Note: To get the "dest rect", we have to provide the "constraint rect"
1123 // (which is the content-box, with the effects of fragmentation undone).
1124 nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize);
1125 constraintRect.y -= GetContinuationOffset();
1127 return nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize,
1128 mIntrinsicRatio, StylePosition());
1131 bool nsImageFrame::IsForMarkerPseudo() const {
1132 if (mKind == Kind::ImageElement) {
1133 return false;
1135 auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot();
1136 return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker();
1139 void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
1140 if (StyleDisplay()->IsContainSize()) {
1141 // If we have 'contain:size', then our intrinsic size and ratio are 0,0
1142 // regardless of what our underlying image may think.
1143 mIntrinsicSize = IntrinsicSize(0, 0);
1144 mIntrinsicRatio = AspectRatio();
1145 return;
1148 // If mIntrinsicSize.width and height are 0, then we need to update from the
1149 // image container. Note that we handle ::marker intrinsic size/ratio in
1150 // DidSetComputedStyle.
1151 if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) {
1152 return;
1155 UpdateIntrinsicSize();
1156 UpdateIntrinsicRatio();
1159 nsIFrame::SizeComputationResult nsImageFrame::ComputeSize(
1160 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
1161 nscoord aAvailableISize, const LogicalSize& aMargin,
1162 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
1163 ComputeSizeFlags aFlags) {
1164 EnsureIntrinsicSizeAndRatio();
1165 return {ComputeSizeWithIntrinsicDimensions(
1166 aRenderingContext, aWM, mIntrinsicSize, GetAspectRatio(), aCBSize,
1167 aMargin, aBorderPadding, aSizeOverrides, aFlags),
1168 AspectRatioUsage::None};
1171 Element* nsImageFrame::GetMapElement() const {
1172 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
1173 return imageLoader ? static_cast<nsImageLoadingContent*>(imageLoader.get())
1174 ->FindImageMap()
1175 : nullptr;
1178 // get the offset into the content area of the image where aImg starts if it is
1179 // a continuation.
1180 nscoord nsImageFrame::GetContinuationOffset() const {
1181 nscoord offset = 0;
1182 for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
1183 offset += f->GetContentRect().height;
1185 NS_ASSERTION(offset >= 0, "bogus GetContentRect");
1186 return offset;
1189 nscoord nsImageFrame::GetMinISize(gfxContext* aRenderingContext) {
1190 // XXX The caller doesn't account for constraints of the block-size,
1191 // min-block-size, and max-block-size properties.
1192 DebugOnly<nscoord> result;
1193 DISPLAY_MIN_INLINE_SIZE(this, result);
1194 EnsureIntrinsicSizeAndRatio();
1195 const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
1196 : mIntrinsicSize.width;
1197 return iSize.valueOr(0);
1200 nscoord nsImageFrame::GetPrefISize(gfxContext* aRenderingContext) {
1201 // XXX The caller doesn't account for constraints of the block-size,
1202 // min-block-size, and max-block-size properties.
1203 DebugOnly<nscoord> result;
1204 DISPLAY_PREF_INLINE_SIZE(this, result);
1205 EnsureIntrinsicSizeAndRatio();
1206 const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
1207 : mIntrinsicSize.width;
1208 // convert from normal twips to scaled twips (printing...)
1209 return iSize.valueOr(0);
1212 void nsImageFrame::ReflowChildren(nsPresContext* aPresContext,
1213 const ReflowInput& aReflowInput,
1214 const LogicalSize& aImageSize) {
1215 for (nsIFrame* child : mFrames) {
1216 ReflowOutput childDesiredSize(aReflowInput);
1217 WritingMode wm = GetWritingMode();
1218 // Shouldn't be hard to support if we want, but why bother.
1219 MOZ_ASSERT(
1220 wm == child->GetWritingMode(),
1221 "We don't expect mismatched writing-modes in content we control");
1222 nsReflowStatus childStatus;
1224 LogicalPoint childOffset(wm);
1225 ReflowInput childReflowInput(aPresContext, aReflowInput, child, aImageSize);
1226 const nsSize containerSize = aImageSize.GetPhysicalSize(wm);
1227 ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, wm,
1228 childOffset, containerSize, ReflowChildFlags::Default,
1229 childStatus);
1231 FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput,
1232 wm, childOffset, containerSize,
1233 ReflowChildFlags::Default);
1237 void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
1238 const ReflowInput& aReflowInput,
1239 nsReflowStatus& aStatus) {
1240 MarkInReflow();
1241 DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
1242 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
1243 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
1244 NS_FRAME_TRACE(
1245 NS_FRAME_TRACE_CALLS,
1246 ("enter nsImageFrame::Reflow: availSize=%d,%d",
1247 aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
1249 MOZ_ASSERT(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow");
1251 // see if we have a frozen size (i.e. a fixed width and height)
1252 if (!SizeDependsOnIntrinsicSize(aReflowInput)) {
1253 AddStateBits(IMAGE_SIZECONSTRAINED);
1254 } else {
1255 RemoveStateBits(IMAGE_SIZECONSTRAINED);
1258 mComputedSize =
1259 nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight());
1261 aMetrics.Width() = mComputedSize.width;
1262 aMetrics.Height() = mComputedSize.height;
1264 // add borders and padding
1265 aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
1266 aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
1268 if (GetPrevInFlow()) {
1269 aMetrics.Width() = GetPrevInFlow()->GetSize().width;
1270 nscoord y = GetContinuationOffset();
1271 aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top;
1272 aMetrics.Height() = std::max(0, aMetrics.Height());
1275 // we have to split images if we are:
1276 // in Paginated mode, we need to have a constrained height, and have a height
1277 // larger than our available height
1278 uint32_t loadStatus = imgIRequest::STATUS_NONE;
1279 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
1280 currentRequest->GetImageStatus(&loadStatus);
1283 if (aPresContext->IsPaginated() &&
1284 ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) ||
1285 (mState & IMAGE_SIZECONSTRAINED)) &&
1286 NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
1287 aMetrics.Height() > aReflowInput.AvailableHeight()) {
1288 // our desired height was greater than 0, so to avoid infinite
1289 // splitting, use 1 pixel as the min
1290 aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1),
1291 aReflowInput.AvailableHeight());
1292 aStatus.SetIncomplete();
1295 aMetrics.SetOverflowAreasToDesiredBounds();
1296 bool imageOK =
1297 mKind != Kind::ImageElement || ImageOk(mContent->AsElement()->State());
1299 // Determine if the size is available
1300 bool haveSize = false;
1301 if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
1302 haveSize = true;
1305 if (!imageOK || !haveSize) {
1306 nsRect altFeedbackSize(
1307 0, 0,
1308 nsPresContext::CSSPixelsToAppUnits(
1309 ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)),
1310 nsPresContext::CSSPixelsToAppUnits(
1311 ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
1312 // We include the altFeedbackSize in our ink overflow, but not in our
1313 // scrollable overflow, since it doesn't really need to be scrolled to
1314 // outside the image.
1315 nsRect& inkOverflow = aMetrics.InkOverflow();
1316 inkOverflow.UnionRect(inkOverflow, altFeedbackSize);
1317 } else if (PresShell()->IsActive()) {
1318 // We've just reflowed and we should have an accurate size, so we're ready
1319 // to request a decode.
1320 MaybeDecodeForPredictedSize();
1322 FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
1324 // Reflow the child frames. Our children can't affect our size in any way.
1325 ReflowChildren(aPresContext, aReflowInput, aMetrics.Size(GetWritingMode()));
1327 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
1328 mReflowCallbackPosted = true;
1329 PresShell()->PostReflowCallback(this);
1332 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d",
1333 aMetrics.Width(), aMetrics.Height()));
1334 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
1337 bool nsImageFrame::ReflowFinished() {
1338 mReflowCallbackPosted = false;
1340 // XXX(seth): We don't need this. The purpose of updating visibility
1341 // synchronously is to ensure that animated images start animating
1342 // immediately. In the short term, however,
1343 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
1344 // animations start as soon as the image is painted for the first time, and in
1345 // the long term we want to update visibility information from the display
1346 // list whenever we paint, so we don't actually need to do this. However, to
1347 // avoid behavior changes during the transition from the old image visibility
1348 // code, we'll leave it in for now.
1349 UpdateVisibilitySynchronously();
1351 return false;
1354 void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
1356 // Computes the width of the specified string. aMaxWidth specifies the maximum
1357 // width available. Once this limit is reached no more characters are measured.
1358 // The number of characters that fit within the maximum width are returned in
1359 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
1360 // into the rendering context before this is called (for performance). MMP
1361 nscoord nsImageFrame::MeasureString(const char16_t* aString, int32_t aLength,
1362 nscoord aMaxWidth, uint32_t& aMaxFit,
1363 gfxContext& aContext,
1364 nsFontMetrics& aFontMetrics) {
1365 nscoord totalWidth = 0;
1366 aFontMetrics.SetTextRunRTL(false);
1367 nscoord spaceWidth = aFontMetrics.SpaceWidth();
1369 aMaxFit = 0;
1370 while (aLength > 0) {
1371 // Find the next place we can line break
1372 uint32_t len = aLength;
1373 bool trailingSpace = false;
1374 for (int32_t i = 0; i < aLength; i++) {
1375 if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
1376 len = i; // don't include the space when measuring
1377 trailingSpace = true;
1378 break;
1382 // Measure this chunk of text, and see if it fits
1383 nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
1384 aString, len, this, aFontMetrics, aContext);
1385 bool fits = (totalWidth + width) <= aMaxWidth;
1387 // If it fits on the line, or it's the first word we've processed then
1388 // include it
1389 if (fits || (0 == totalWidth)) {
1390 // New piece fits
1391 totalWidth += width;
1393 // If there's a trailing space then see if it fits as well
1394 if (trailingSpace) {
1395 if ((totalWidth + spaceWidth) <= aMaxWidth) {
1396 totalWidth += spaceWidth;
1397 } else {
1398 // Space won't fit. Leave it at the end but don't include it in
1399 // the width
1400 fits = false;
1403 len++;
1406 aMaxFit += len;
1407 aString += len;
1408 aLength -= len;
1411 if (!fits) {
1412 break;
1415 return totalWidth;
1418 // Formats the alt-text to fit within the specified rectangle. Breaks lines
1419 // between words if a word would extend past the edge of the rectangle
1420 void nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
1421 gfxContext& aRenderingContext,
1422 const nsString& aAltText,
1423 const nsRect& aRect) {
1424 // Set font and color
1425 aRenderingContext.SetColor(
1426 sRGBColor::FromABGR(StyleText()->mColor.ToColor()));
1427 RefPtr<nsFontMetrics> fm =
1428 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
1430 // Format the text to display within the formatting rect
1432 nscoord maxAscent = fm->MaxAscent();
1433 nscoord maxDescent = fm->MaxDescent();
1434 nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate
1435 // length if writing mode is vertical
1437 WritingMode wm = GetWritingMode();
1438 bool isVertical = wm.IsVertical();
1440 fm->SetVertical(isVertical);
1441 fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
1443 // XXX It would be nice if there was a way to have the font metrics tell
1444 // use where to break the text given a maximum width. At a minimum we need
1445 // to be able to get the break character...
1446 const char16_t* str = aAltText.get();
1447 int32_t strLen = aAltText.Length();
1448 nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
1449 : aRect.TopLeft();
1450 nscoord iSize = isVertical ? aRect.height : aRect.width;
1452 if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
1453 aPresContext->SetBidiEnabled();
1456 // Always show the first line, even if we have to clip it below
1457 bool firstLine = true;
1458 while (strLen > 0) {
1459 if (!firstLine) {
1460 // If we've run out of space, break out of the loop
1461 if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
1462 (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
1463 (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
1464 break;
1468 // Determine how much of the text to display on this line
1469 uint32_t maxFit; // number of characters that fit
1470 nscoord strWidth =
1471 MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
1473 // Display the text
1474 nsresult rv = NS_ERROR_FAILURE;
1476 if (aPresContext->BidiEnabled()) {
1477 mozilla::intl::BidiEmbeddingLevel level;
1478 nscoord x, y;
1480 if (isVertical) {
1481 x = pt.x + maxDescent;
1482 if (wm.IsBidiLTR()) {
1483 y = aRect.y;
1484 level = mozilla::intl::BidiEmbeddingLevel::LTR();
1485 } else {
1486 y = aRect.YMost() - strWidth;
1487 level = mozilla::intl::BidiEmbeddingLevel::RTL();
1489 } else {
1490 y = pt.y + maxAscent;
1491 if (wm.IsBidiLTR()) {
1492 x = aRect.x;
1493 level = mozilla::intl::BidiEmbeddingLevel::LTR();
1494 } else {
1495 x = aRect.XMost() - strWidth;
1496 level = mozilla::intl::BidiEmbeddingLevel::RTL();
1500 rv = nsBidiPresUtils::RenderText(
1501 str, maxFit, level, aPresContext, aRenderingContext,
1502 aRenderingContext.GetDrawTarget(), *fm, x, y);
1504 if (NS_FAILED(rv)) {
1505 nsLayoutUtils::DrawUniDirString(str, maxFit,
1506 isVertical
1507 ? nsPoint(pt.x + maxDescent, pt.y)
1508 : nsPoint(pt.x, pt.y + maxAscent),
1509 *fm, aRenderingContext);
1512 // Move to the next line
1513 str += maxFit;
1514 strLen -= maxFit;
1515 if (wm.IsVerticalRL()) {
1516 pt.x -= lineHeight;
1517 } else if (wm.IsVerticalLR()) {
1518 pt.x += lineHeight;
1519 } else {
1520 pt.y += lineHeight;
1523 firstLine = false;
1527 struct nsRecessedBorder : public nsStyleBorder {
1528 nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext)
1529 : nsStyleBorder(*aPresContext->Document()) {
1530 for (const auto side : mozilla::AllPhysicalSides()) {
1531 BorderColorFor(side) = StyleColor::Black();
1532 mBorder.Side(side) = aBorderWidth;
1533 // Note: use SetBorderStyle here because we want to affect
1534 // mComputedBorder
1535 SetBorderStyle(side, StyleBorderStyle::Inset);
1540 class nsDisplayAltFeedback final : public nsPaintedDisplayItem {
1541 public:
1542 nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
1543 : nsPaintedDisplayItem(aBuilder, aFrame) {}
1545 nsDisplayItemGeometry* AllocateGeometry(
1546 nsDisplayListBuilder* aBuilder) final {
1547 return new nsDisplayItemGenericImageGeometry(this, aBuilder);
1550 void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
1551 const nsDisplayItemGeometry* aGeometry,
1552 nsRegion* aInvalidRegion) const final {
1553 auto geometry =
1554 static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1556 if (aBuilder->ShouldSyncDecodeImages() &&
1557 geometry->ShouldInvalidateToSyncDecodeImages()) {
1558 bool snap;
1559 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1562 nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
1563 aInvalidRegion);
1566 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
1567 *aSnap = false;
1568 return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
1571 void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final {
1572 // Always sync decode, because these icons are UI, and since they're not
1573 // discardable we'll pay the price of sync decoding at most once.
1574 uint32_t flags = imgIContainer::FLAG_SYNC_DECODE;
1576 nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1577 ImgDrawResult result = f->DisplayAltFeedback(
1578 *aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame(), flags);
1580 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
1583 bool CreateWebRenderCommands(
1584 mozilla::wr::DisplayListBuilder& aBuilder,
1585 mozilla::wr::IpcResourceUpdateQueue& aResources,
1586 const StackingContextHelper& aSc,
1587 mozilla::layers::RenderRootStateManager* aManager,
1588 nsDisplayListBuilder* aDisplayListBuilder) final {
1589 uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
1590 nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
1591 ImgDrawResult result = f->DisplayAltFeedbackWithoutLayer(
1592 this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder,
1593 ToReferenceFrame(), flags);
1595 return result == ImgDrawResult::SUCCESS;
1598 NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
1601 ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext,
1602 const nsRect& aDirtyRect,
1603 nsPoint aPt, uint32_t aFlags) {
1604 // We should definitely have a gIconLoad here.
1605 MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?");
1607 // Whether we draw the broken or loading icon.
1608 bool isLoading =
1609 mKind != Kind::ImageElement || ImageOk(mContent->AsElement()->State());
1611 // Calculate the content area.
1612 nsRect inner = GetContentRectRelativeToSelf() + aPt;
1614 // Display a recessed one pixel border
1615 nscoord borderEdgeWidth =
1616 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
1618 // if inner area is empty, then make it big enough for at least the icon
1619 if (inner.IsEmpty()) {
1620 inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
1621 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
1622 2 * (nsPresContext::CSSPixelsToAppUnits(
1623 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
1626 // Make sure we have enough room to actually render the border within
1627 // our frame bounds
1628 if ((inner.width < 2 * borderEdgeWidth) ||
1629 (inner.height < 2 * borderEdgeWidth)) {
1630 return ImgDrawResult::SUCCESS;
1633 // Paint the border
1634 if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) {
1635 nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext());
1637 // Assert that we're not drawing a border-image here; if we were, we
1638 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
1639 // returns.
1640 MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
1642 Unused << nsCSSRendering::PaintBorderWithStyleBorder(
1643 PresContext(), aRenderingContext, this, inner, inner, recessedBorder,
1644 mComputedStyle, PaintBorderFlags::SyncDecodeImages);
1647 // Adjust the inner rect to account for the one pixel recessed border,
1648 // and a six pixel padding on each edge
1649 inner.Deflate(
1650 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
1651 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
1652 if (inner.IsEmpty()) {
1653 return ImgDrawResult::SUCCESS;
1656 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
1658 // Clip so we don't render outside the inner rect
1659 aRenderingContext.Save();
1660 aRenderingContext.Clip(NSRectToSnappedRect(
1661 inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
1663 ImgDrawResult result = ImgDrawResult::NOT_READY;
1665 // Check if we should display image placeholders
1666 if (!ShouldShowBrokenImageIcon() || !gIconLoad->mPrefShowPlaceholders ||
1667 (isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) {
1668 result = ImgDrawResult::SUCCESS;
1669 } else {
1670 nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
1672 imgIRequest* request = isLoading ? nsImageFrame::gIconLoad->mLoadingImage
1673 : nsImageFrame::gIconLoad->mBrokenImage;
1675 // If we weren't previously displaying an icon, register ourselves
1676 // as an observer for load and animation updates and flag that we're
1677 // doing so now.
1678 if (request && !mDisplayingIcon) {
1679 gIconLoad->AddIconObserver(this);
1680 mDisplayingIcon = true;
1683 WritingMode wm = GetWritingMode();
1684 bool flushRight = wm.IsPhysicalRTL();
1686 // If the icon in question is loaded, draw it.
1687 uint32_t imageStatus = 0;
1688 if (request) request->GetImageStatus(&imageStatus);
1689 if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
1690 !(imageStatus & imgIRequest::STATUS_ERROR)) {
1691 nsCOMPtr<imgIContainer> imgCon;
1692 request->GetImage(getter_AddRefs(imgCon));
1693 MOZ_ASSERT(imgCon, "Load complete, but no image container?");
1694 nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
1695 size);
1696 result = nsLayoutUtils::DrawSingleImage(
1697 aRenderingContext, PresContext(), imgCon,
1698 nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
1699 /* no SVGImageContext */ Nothing(), aFlags);
1702 // If we could not draw the icon, just draw some graffiti in the mean time.
1703 if (result == ImgDrawResult::NOT_READY) {
1704 ColorPattern color(ToDeviceColor(sRGBColor(1.f, 0.f, 0.f, 1.f)));
1706 nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
1708 // stroked rect:
1709 nsRect rect(iconXPos, inner.y, size, size);
1710 Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
1711 rect, PresContext()->AppUnitsPerDevPixel()));
1712 drawTarget->StrokeRect(devPxRect, color);
1714 // filled circle in bottom right quadrant of stroked rect:
1715 nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
1716 rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
1717 size / 2 - twoPX);
1718 devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
1719 rect, PresContext()->AppUnitsPerDevPixel()));
1720 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
1721 AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
1722 RefPtr<Path> ellipse = builder->Finish();
1723 drawTarget->Fill(ellipse, color);
1726 // Reduce the inner rect by the width of the icon, and leave an
1727 // additional ICON_PADDING pixels for padding
1728 int32_t paddedIconSize =
1729 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
1730 if (wm.IsVertical()) {
1731 inner.y += paddedIconSize;
1732 inner.height -= paddedIconSize;
1733 } else {
1734 if (!flushRight) {
1735 inner.x += paddedIconSize;
1737 inner.width -= paddedIconSize;
1741 // If there's still room, display the alt-text
1742 if (!inner.IsEmpty()) {
1743 nsAutoString altText;
1744 nsCSSFrameConstructor::GetAlternateTextFor(
1745 mContent->AsElement(), mContent->NodeInfo()->NameAtom(), altText);
1746 DisplayAltText(PresContext(), aRenderingContext, altText, inner);
1749 aRenderingContext.Restore();
1751 return result;
1754 ImgDrawResult nsImageFrame::DisplayAltFeedbackWithoutLayer(
1755 nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder,
1756 mozilla::wr::IpcResourceUpdateQueue& aResources,
1757 const StackingContextHelper& aSc,
1758 mozilla::layers::RenderRootStateManager* aManager,
1759 nsDisplayListBuilder* aDisplayListBuilder, nsPoint aPt, uint32_t aFlags) {
1760 // We should definitely have a gIconLoad here.
1761 MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?");
1763 // Whether we draw the broken or loading icon.
1764 bool isLoading =
1765 mKind != Kind::ImageElement || ImageOk(mContent->AsElement()->State());
1767 // Calculate the content area.
1768 nsRect inner = GetContentRectRelativeToSelf() + aPt;
1770 // Display a recessed one pixel border
1771 nscoord borderEdgeWidth =
1772 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
1774 // if inner area is empty, then make it big enough for at least the icon
1775 if (inner.IsEmpty()) {
1776 inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
1777 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
1778 2 * (nsPresContext::CSSPixelsToAppUnits(
1779 ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
1782 // Make sure we have enough room to actually render the border within
1783 // our frame bounds
1784 if ((inner.width < 2 * borderEdgeWidth) ||
1785 (inner.height < 2 * borderEdgeWidth)) {
1786 return ImgDrawResult::SUCCESS;
1789 // If the TextDrawTarget requires fallback we need to rollback everything we
1790 // may have added to the display list, but we don't find that out until the
1791 // end.
1792 bool textDrawResult = true;
1793 class AutoSaveRestore {
1794 public:
1795 explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder& aBuilder,
1796 bool& aTextDrawResult)
1797 : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) {
1798 mBuilder.Save();
1800 ~AutoSaveRestore() {
1801 // If we have to use fallback for the text restore the builder and remove
1802 // anything else we added to the display list, we need to use fallback.
1803 if (mTextDrawResult) {
1804 mBuilder.ClearSave();
1805 } else {
1806 mBuilder.Restore();
1810 private:
1811 mozilla::wr::DisplayListBuilder& mBuilder;
1812 bool& mTextDrawResult;
1815 AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult);
1817 // Paint the border
1818 if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) {
1819 nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext());
1820 // Assert that we're not drawing a border-image here; if we were, we
1821 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
1822 // returns.
1823 MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
1825 nsRect rect = nsRect(aPt, GetSize());
1826 Unused << nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
1827 aItem, this, rect, aBuilder, aResources, aSc, aManager,
1828 aDisplayListBuilder, recessedBorder);
1831 // Adjust the inner rect to account for the one pixel recessed border,
1832 // and a six pixel padding on each edge
1833 inner.Deflate(
1834 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
1835 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
1836 if (inner.IsEmpty()) {
1837 return ImgDrawResult::SUCCESS;
1840 // Clip to this rect so we don't render outside the inner rect
1841 LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
1842 inner, PresContext()->AppUnitsPerDevPixel());
1843 auto wrBounds = wr::ToLayoutRect(bounds);
1845 // Check if we should display image placeholders
1846 if (ShouldShowBrokenImageIcon() && gIconLoad->mPrefShowPlaceholders &&
1847 (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder)) {
1848 ImgDrawResult result = ImgDrawResult::NOT_READY;
1849 nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
1850 imgIRequest* request = isLoading ? nsImageFrame::gIconLoad->mLoadingImage
1851 : nsImageFrame::gIconLoad->mBrokenImage;
1853 // If we weren't previously displaying an icon, register ourselves
1854 // as an observer for load and animation updates and flag that we're
1855 // doing so now.
1856 if (request && !mDisplayingIcon) {
1857 gIconLoad->AddIconObserver(this);
1858 mDisplayingIcon = true;
1861 WritingMode wm = GetWritingMode();
1862 const bool flushRight = wm.IsPhysicalRTL();
1864 // If the icon in question is loaded, draw it.
1865 uint32_t imageStatus = 0;
1866 if (request) request->GetImageStatus(&imageStatus);
1867 if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
1868 !(imageStatus & imgIRequest::STATUS_ERROR)) {
1869 nsCOMPtr<imgIContainer> imgCon;
1870 request->GetImage(getter_AddRefs(imgCon));
1871 MOZ_ASSERT(imgCon, "Load complete, but no image container?");
1873 nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
1874 size);
1876 const int32_t factor = PresContext()->AppUnitsPerDevPixel();
1877 LayoutDeviceRect destRect(LayoutDeviceRect::FromAppUnits(dest, factor));
1879 Maybe<SVGImageContext> svgContext;
1880 Maybe<ImageIntRegion> region;
1881 IntSize decodeSize =
1882 nsLayoutUtils::ComputeImageContainerDrawingParameters(
1883 imgCon, this, destRect, destRect, aSc, aFlags, svgContext,
1884 region);
1885 RefPtr<image::WebRenderImageProvider> provider;
1886 result = imgCon->GetImageProvider(aManager->LayerManager(), decodeSize,
1887 svgContext, region, aFlags,
1888 getter_AddRefs(provider));
1889 if (provider) {
1890 bool wrResult = aManager->CommandBuilder().PushImageProvider(
1891 aItem, provider, result, aBuilder, aResources, destRect, bounds);
1892 result &= wrResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
1893 } else {
1894 // We don't use &= here because we want the result to be NOT_READY so
1895 // the next block executes.
1896 result = ImgDrawResult::NOT_READY;
1900 // If we could not draw the icon, just draw some graffiti in the mean time.
1901 if (result == ImgDrawResult::NOT_READY) {
1902 auto color = wr::ColorF{1.0f, 0.0f, 0.0f, 1.0f};
1903 bool isBackfaceVisible = !aItem->BackfaceIsHidden();
1905 nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
1907 // stroked rect:
1908 nsRect rect(iconXPos, inner.y, size, size);
1909 auto devPxRect = LayoutDeviceRect::FromAppUnits(
1910 rect, PresContext()->AppUnitsPerDevPixel());
1911 auto dest = wr::ToLayoutRect(devPxRect);
1913 auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
1914 wr::BorderSide side = {color, wr::BorderStyle::Solid};
1915 wr::BorderSide sides[4] = {side, side, side, side};
1916 Range<const wr::BorderSide> sidesRange(sides, 4);
1917 aBuilder.PushBorder(dest, wrBounds, isBackfaceVisible, borderWidths,
1918 sidesRange, wr::EmptyBorderRadius());
1920 // filled circle in bottom right quadrant of stroked rect:
1921 nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
1922 rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
1923 size / 2 - twoPX);
1924 devPxRect = LayoutDeviceRect::FromAppUnits(
1925 rect, PresContext()->AppUnitsPerDevPixel());
1926 dest = wr::ToLayoutRect(devPxRect);
1928 aBuilder.PushRoundedRect(dest, wrBounds, isBackfaceVisible, color);
1931 // Reduce the inner rect by the width of the icon, and leave an
1932 // additional ICON_PADDING pixels for padding
1933 int32_t paddedIconSize =
1934 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
1935 if (wm.IsVertical()) {
1936 inner.y += paddedIconSize;
1937 inner.height -= paddedIconSize;
1938 } else {
1939 if (!flushRight) {
1940 inner.x += paddedIconSize;
1942 inner.width -= paddedIconSize;
1946 // Draw text
1947 if (!inner.IsEmpty()) {
1948 RefPtr<TextDrawTarget> textDrawer =
1949 new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, inner,
1950 /* aCallerDoesSaveRestore = */ true);
1951 RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
1953 nsAutoString altText;
1954 nsCSSFrameConstructor::GetAlternateTextFor(
1955 mContent->AsElement(), mContent->NodeInfo()->NameAtom(), altText);
1956 DisplayAltText(PresContext(), *captureCtx.get(), altText, inner);
1958 textDrawer->TerminateShadows();
1959 textDrawResult = !textDrawer->CheckHasUnsupportedFeatures();
1962 // Purposely ignore local DrawResult because we handled it not being success
1963 // already.
1964 return textDrawResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
1967 #ifdef DEBUG
1968 static void PaintDebugImageMap(nsIFrame* aFrame, DrawTarget* aDrawTarget,
1969 const nsRect& aDirtyRect, nsPoint aPt) {
1970 nsImageFrame* f = static_cast<nsImageFrame*>(aFrame);
1971 nsRect inner = f->GetContentRectRelativeToSelf() + aPt;
1972 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
1973 inner.TopLeft(), aFrame->PresContext()->AppUnitsPerDevPixel());
1974 AutoRestoreTransform autoRestoreTransform(aDrawTarget);
1975 aDrawTarget->SetTransform(
1976 aDrawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
1977 f->GetImageMap()->Draw(aFrame, *aDrawTarget,
1978 ColorPattern(ToDeviceColor(sRGBColor::OpaqueBlack())));
1980 #endif
1982 // We want to sync-decode in this case, as otherwise we either need to flash
1983 // white while waiting to decode the new image, or paint the old image with a
1984 // different aspect-ratio, which would be bad as it'd be stretched.
1986 // See bug 1589955.
1987 static bool OldImageHasDifferentRatio(const nsImageFrame& aFrame,
1988 imgIContainer& aImage,
1989 imgIContainer* aPrevImage) {
1990 if (!aPrevImage || aPrevImage == &aImage) {
1991 return false;
1994 // If we don't depend on our intrinsic image size / ratio, we're good.
1996 // FIXME(emilio): There's the case of the old image being painted
1997 // intrinsically, and src and styles changing at the same time... Maybe we
1998 // should keep track of the old GetPaintRect()'s ratio and the image's ratio,
1999 // instead of checking this bit?
2000 if (aFrame.HasAnyStateBits(IMAGE_SIZECONSTRAINED)) {
2001 return false;
2004 auto currentRatio = aFrame.GetIntrinsicRatio();
2005 // If we have an image, we need to have a current request.
2006 // Same if we had an image.
2007 const bool hasRequest = true;
2008 #ifdef DEBUG
2009 auto currentRatioRecomputed =
2010 ComputeIntrinsicRatio(&aImage, hasRequest, aFrame);
2011 // If the image encounters an error after decoding the size (and we run
2012 // UpdateIntrinsicRatio) then the image will return the empty AspectRatio and
2013 // the aspect ratio we compute here will be different from what was computed
2014 // and stored before the image went into error state. It would be better to
2015 // check that the image has an error here but we need an imgIRequest for that,
2016 // not an imgIContainer. In lieu of that we check that
2017 // aImage.GetIntrinsicRatio() returns Nothing() as it does when the image is
2018 // in the error state and that the recomputed ratio is the zero ratio.
2019 MOZ_ASSERT(
2020 (!currentRatioRecomputed && aImage.GetIntrinsicRatio() == Nothing()) ||
2021 currentRatio == currentRatioRecomputed,
2022 "aspect-ratio got out of sync during paint? How?");
2023 #endif
2024 auto oldRatio = ComputeIntrinsicRatio(aPrevImage, hasRequest, aFrame);
2025 return oldRatio != currentRatio;
2028 void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
2029 MOZ_ASSERT(mImage);
2030 auto* frame = static_cast<nsImageFrame*>(mFrame);
2032 const bool oldImageIsDifferent =
2033 OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
2035 uint32_t flags = imgIContainer::FLAG_NONE;
2036 if (aBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
2037 frame->mForceSyncDecoding) {
2038 flags |= imgIContainer::FLAG_SYNC_DECODE;
2040 if (aBuilder->UseHighQualityScaling()) {
2041 flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
2044 ImgDrawResult result = frame->PaintImage(
2045 *aCtx, ToReferenceFrame(), GetPaintRect(aBuilder, aCtx), mImage, flags);
2047 if (result == ImgDrawResult::NOT_READY ||
2048 result == ImgDrawResult::INCOMPLETE ||
2049 result == ImgDrawResult::TEMPORARY_ERROR) {
2050 // If the current image failed to paint because it's still loading or
2051 // decoding, try painting the previous image.
2052 if (mPrevImage) {
2053 result =
2054 frame->PaintImage(*aCtx, ToReferenceFrame(),
2055 GetPaintRect(aBuilder, aCtx), mPrevImage, flags);
2059 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
2062 nsDisplayItemGeometry* nsDisplayImage::AllocateGeometry(
2063 nsDisplayListBuilder* aBuilder) {
2064 return new nsDisplayItemGenericImageGeometry(this, aBuilder);
2067 void nsDisplayImage::ComputeInvalidationRegion(
2068 nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
2069 nsRegion* aInvalidRegion) const {
2070 auto geometry =
2071 static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
2073 if (aBuilder->ShouldSyncDecodeImages() &&
2074 geometry->ShouldInvalidateToSyncDecodeImages()) {
2075 bool snap;
2076 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
2079 nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
2080 aInvalidRegion);
2083 nsRect nsDisplayImage::GetDestRect() const {
2084 bool snap = true;
2085 const nsRect frameContentBox = GetBounds(&snap);
2087 nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
2088 return imageFrame->PredictedDestRect(frameContentBox);
2091 nsRegion nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
2092 bool* aSnap) const {
2093 *aSnap = false;
2094 if (mImage && mImage->WillDrawOpaqueNow()) {
2095 const nsRect frameContentBox = GetBounds(aSnap);
2096 return GetDestRect().Intersect(frameContentBox);
2098 return nsRegion();
2101 bool nsDisplayImage::CreateWebRenderCommands(
2102 mozilla::wr::DisplayListBuilder& aBuilder,
2103 mozilla::wr::IpcResourceUpdateQueue& aResources,
2104 const StackingContextHelper& aSc, RenderRootStateManager* aManager,
2105 nsDisplayListBuilder* aDisplayListBuilder) {
2106 if (!mImage) {
2107 return false;
2110 MOZ_ASSERT(mFrame->IsImageFrame() || mFrame->IsImageControlFrame());
2111 // Image layer doesn't support draw focus ring for image map.
2112 auto* frame = static_cast<nsImageFrame*>(mFrame);
2113 if (frame->HasImageMap()) {
2114 return false;
2117 const bool oldImageIsDifferent =
2118 OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
2120 uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
2121 if (aDisplayListBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
2122 frame->mForceSyncDecoding) {
2123 flags |= imgIContainer::FLAG_SYNC_DECODE;
2125 if (aDisplayListBuilder->UseHighQualityScaling()) {
2126 flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
2128 if (StaticPrefs::image_svg_blob_image() &&
2129 mImage->GetType() == imgIContainer::TYPE_VECTOR) {
2130 flags |= imgIContainer::FLAG_RECORD_BLOB;
2133 const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
2134 LayoutDeviceRect destRect(
2135 LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
2137 Maybe<SVGImageContext> svgContext;
2138 Maybe<ImageIntRegion> region;
2139 IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
2140 mImage, mFrame, destRect, destRect, aSc, flags, svgContext, region);
2142 RefPtr<image::WebRenderImageProvider> provider;
2143 ImgDrawResult drawResult =
2144 mImage->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext,
2145 region, flags, getter_AddRefs(provider));
2147 // While we got a container, it may not contain a fully decoded surface. If
2148 // that is the case, and we have an image we were previously displaying which
2149 // has a fully decoded surface, then we should prefer the previous image.
2150 bool updatePrevImage = false;
2151 switch (drawResult) {
2152 case ImgDrawResult::NOT_READY:
2153 case ImgDrawResult::INCOMPLETE:
2154 case ImgDrawResult::TEMPORARY_ERROR:
2155 if (mPrevImage && mPrevImage != mImage) {
2156 // The current image and the previous image might be switching between
2157 // rasterized surfaces and blob recordings, so we need to update the
2158 // flags appropriately.
2159 uint32_t prevFlags = flags;
2160 if (StaticPrefs::image_svg_blob_image() &&
2161 mPrevImage->GetType() == imgIContainer::TYPE_VECTOR) {
2162 prevFlags |= imgIContainer::FLAG_RECORD_BLOB;
2163 } else {
2164 prevFlags &= ~imgIContainer::FLAG_RECORD_BLOB;
2167 RefPtr<image::WebRenderImageProvider> prevProvider;
2168 ImgDrawResult prevDrawResult = mPrevImage->GetImageProvider(
2169 aManager->LayerManager(), decodeSize, svgContext, region, prevFlags,
2170 getter_AddRefs(prevProvider));
2171 if (prevProvider && (prevDrawResult == ImgDrawResult::SUCCESS ||
2172 prevDrawResult == ImgDrawResult::WRONG_SIZE)) {
2173 // We use WRONG_SIZE here to ensure that when the frame next tries to
2174 // invalidate due to a frame update from the current image, we don't
2175 // consider the result from the previous image to be a valid result to
2176 // avoid redrawing.
2177 drawResult = ImgDrawResult::WRONG_SIZE;
2178 provider = std::move(prevProvider);
2179 flags = prevFlags;
2180 break;
2183 // Previous image was unusable; we can forget about it.
2184 updatePrevImage = true;
2186 break;
2187 case ImgDrawResult::NOT_SUPPORTED:
2188 return false;
2189 default:
2190 updatePrevImage = mPrevImage != mImage;
2191 break;
2194 // The previous image was not used, and is different from the current image.
2195 // We should forget about it. We need to update the frame as well because the
2196 // display item may get recreated.
2197 if (updatePrevImage) {
2198 mPrevImage = mImage;
2199 frame->mPrevImage = frame->mImage;
2202 // If the image provider is null, we don't want to fallback. Any other
2203 // failure will be due to resource constraints and fallback is unlikely to
2204 // help us. Hence we can ignore the return value from PushImage.
2205 aManager->CommandBuilder().PushImageProvider(
2206 this, provider, drawResult, aBuilder, aResources, destRect, destRect);
2208 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult);
2209 return true;
2212 ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext,
2213 nsPoint aPt, const nsRect& aDirtyRect,
2214 imgIContainer* aImage, uint32_t aFlags) {
2215 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
2217 // Render the image into our content area (the area inside
2218 // the borders and padding)
2219 NS_ASSERTION(GetContentRectRelativeToSelf().width == mComputedSize.width,
2220 "bad width");
2222 // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
2223 // own size here, because GetContentRectRelativeToSelf() might be smaller if
2224 // we're fragmented, whereas mComputedSize has our full content-box size
2225 // (which we need for ComputeObjectDestRect to work correctly).
2226 nsRect constraintRect(aPt + GetContentRectRelativeToSelf().TopLeft(),
2227 mComputedSize);
2228 constraintRect.y -= GetContinuationOffset();
2230 nsPoint anchorPoint;
2231 nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
2232 constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition(),
2233 &anchorPoint);
2235 Maybe<SVGImageContext> svgContext;
2236 SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);
2238 ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
2239 aRenderingContext, PresContext(), aImage,
2240 nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
2241 svgContext, aFlags, &anchorPoint);
2243 if (nsImageMap* map = GetImageMap()) {
2244 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
2245 dest.TopLeft(), PresContext()->AppUnitsPerDevPixel());
2246 AutoRestoreTransform autoRestoreTransform(drawTarget);
2247 drawTarget->SetTransform(
2248 drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
2250 // solid white stroke:
2251 ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite()));
2252 map->Draw(this, *drawTarget, white);
2254 // then dashed black stroke over the top:
2255 ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
2256 StrokeOptions strokeOptions;
2257 nsLayoutUtils::InitDashPattern(strokeOptions, StyleBorderStyle::Dotted);
2258 map->Draw(this, *drawTarget, black, strokeOptions);
2261 if (result == ImgDrawResult::SUCCESS) {
2262 mPrevImage = aImage;
2263 } else if (result == ImgDrawResult::BAD_IMAGE) {
2264 mPrevImage = nullptr;
2267 return result;
2270 already_AddRefed<imgIRequest> nsImageFrame::GetCurrentRequest() const {
2271 if (mKind != Kind::ImageElement) {
2272 return do_AddRef(mContentURLRequest);
2275 MOZ_ASSERT(!mContentURLRequest);
2277 nsCOMPtr<imgIRequest> request;
2278 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
2279 MOZ_ASSERT(imageLoader);
2280 imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
2281 getter_AddRefs(request));
2282 return request.forget();
2285 void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2286 const nsDisplayListSet& aLists) {
2287 if (!IsVisibleForPainting()) return;
2289 DisplayBorderBackgroundOutline(aBuilder, aLists);
2291 uint32_t clipFlags =
2292 nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
2294 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
2296 DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
2297 aBuilder, this, clipFlags);
2299 if (mComputedSize.width != 0 && mComputedSize.height != 0) {
2300 bool imageOK =
2301 mKind != Kind::ImageElement || ImageOk(mContent->AsElement()->State());
2303 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
2305 // XXX(seth): The SizeIsAvailable check here should not be necessary - the
2306 // intention is that a non-null mImage means we have a size, but there is
2307 // currently some code that violates this invariant.
2308 if ((mKind == Kind::ImageElement ||
2309 GetImageFromStyle()->IsImageRequestType()) &&
2310 (!imageOK || !mImage || !SizeIsAvailable(currentRequest))) {
2311 // No image yet, or image load failed. Draw the alt-text and an icon
2312 // indicating the status
2313 aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this);
2315 // This image is visible (we are being asked to paint it) but it's not
2316 // decoded yet. And we are not going to ask the image to draw, so this
2317 // may be the only chance to tell it that it should decode.
2318 if (currentRequest) {
2319 uint32_t status = 0;
2320 currentRequest->GetImageStatus(&status);
2321 if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
2322 MaybeDecodeForPredictedSize();
2324 // Increase loading priority if the image is ready to be displayed.
2325 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
2326 currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
2329 } else {
2330 if (mImage) {
2331 aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage,
2332 mPrevImage);
2333 } else if (mKind != Kind::ImageElement) {
2334 aLists.Content()->AppendNewToTop<nsDisplayGradient>(aBuilder, this);
2337 // If we were previously displaying an icon, we're not anymore
2338 if (mDisplayingIcon) {
2339 gIconLoad->RemoveIconObserver(this);
2340 mDisplayingIcon = false;
2343 #ifdef DEBUG
2344 if (GetShowFrameBorders() && GetImageMap()) {
2345 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2346 aBuilder, this, PaintDebugImageMap, "DebugImageMap",
2347 DisplayItemType::TYPE_DEBUG_IMAGE_MAP);
2349 #endif
2353 if (ShouldDisplaySelection()) {
2354 DisplaySelectionOverlay(aBuilder, aLists.Content(),
2355 nsISelectionDisplay::DISPLAY_IMAGES);
2358 BuildDisplayListForNonBlockChildren(aBuilder, aLists);
2361 bool nsImageFrame::ShouldDisplaySelection() {
2362 int16_t displaySelection = PresShell()->GetSelectionFlags();
2363 if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) {
2364 // no need to check the blue border, we cannot be drawn selected.
2365 return false;
2368 if (displaySelection != nsISelectionDisplay::DISPLAY_ALL) {
2369 return true;
2372 // If the image is currently resize target of the editor, don't draw the
2373 // selection overlay.
2374 HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(PresContext());
2375 if (!htmlEditor) {
2376 return true;
2379 return htmlEditor->GetResizerTarget() != mContent;
2382 nsImageMap* nsImageFrame::GetImageMap() {
2383 if (!mImageMap) {
2384 if (nsIContent* map = GetMapElement()) {
2385 mImageMap = new nsImageMap();
2386 mImageMap->Init(this, map);
2390 return mImageMap;
2393 bool nsImageFrame::IsServerImageMap() {
2394 return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap);
2397 CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) {
2398 const nsRect contentRect = GetContentRectRelativeToSelf();
2399 // Subtract out border and padding here so that the coordinates are
2400 // now relative to the content area of this frame.
2401 return CSSPixel::FromAppUnitsRounded(aPoint - contentRect.TopLeft());
2404 bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
2405 nsIContent** aNode) {
2406 bool status = false;
2407 aTarget.Truncate();
2408 *aHref = nullptr;
2409 *aNode = nullptr;
2411 // Walk up the content tree, looking for an nsIDOMAnchorElement
2412 for (nsIContent* content = mContent->GetParent(); content;
2413 content = content->GetParent()) {
2414 nsCOMPtr<dom::Link> link(do_QueryInterface(content));
2415 if (link) {
2416 nsCOMPtr<nsIURI> href = content->GetHrefURI();
2417 if (href) {
2418 href.forget(aHref);
2420 status = (*aHref != nullptr);
2422 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(content);
2423 if (anchor) {
2424 anchor->GetTarget(aTarget);
2426 NS_ADDREF(*aNode = content);
2427 break;
2430 return status;
2433 bool nsImageFrame::IsLeafDynamic() const {
2434 if (mKind != Kind::ImageElement) {
2435 // Image frames created for "content: url()" could have an author-controlled
2436 // shadow root, we want to be a regular leaf for those.
2437 return true;
2439 // For elements that create image frames, calling attachShadow() will throw,
2440 // so the only ShadowRoot we could ever have is a UA widget.
2441 const auto* shadow = mContent->AsElement()->GetShadowRoot();
2442 MOZ_ASSERT_IF(shadow, shadow->IsUAWidget());
2443 return !shadow;
2446 nsresult nsImageFrame::GetContentForEvent(WidgetEvent* aEvent,
2447 nsIContent** aContent) {
2448 NS_ENSURE_ARG_POINTER(aContent);
2450 nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
2451 if (f != this) {
2452 return f->GetContentForEvent(aEvent, aContent);
2455 // XXX We need to make this special check for area element's capturing the
2456 // mouse due to bug 135040. Remove it once that's fixed.
2457 nsIContent* capturingContent = aEvent->HasMouseEventMessage()
2458 ? PresShell::GetCapturingContent()
2459 : nullptr;
2460 if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
2461 *aContent = capturingContent;
2462 NS_IF_ADDREF(*aContent);
2463 return NS_OK;
2466 if (nsImageMap* map = GetImageMap()) {
2467 const CSSIntPoint p = TranslateEventCoords(
2468 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this}));
2469 nsCOMPtr<nsIContent> area = map->GetArea(p);
2470 if (area) {
2471 area.forget(aContent);
2472 return NS_OK;
2476 *aContent = GetContent();
2477 NS_IF_ADDREF(*aContent);
2478 return NS_OK;
2481 // XXX what should clicks on transparent pixels do?
2482 nsresult nsImageFrame::HandleEvent(nsPresContext* aPresContext,
2483 WidgetGUIEvent* aEvent,
2484 nsEventStatus* aEventStatus) {
2485 NS_ENSURE_ARG_POINTER(aEventStatus);
2487 if ((aEvent->mMessage == eMouseClick &&
2488 aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
2489 aEvent->mMessage == eMouseMove) {
2490 nsImageMap* map = GetImageMap();
2491 bool isServerMap = IsServerImageMap();
2492 if (map || isServerMap) {
2493 CSSIntPoint p =
2494 TranslateEventCoords(nsLayoutUtils::GetEventCoordinatesRelativeTo(
2495 aEvent, RelativeTo{this}));
2497 // Even though client-side image map triggering happens
2498 // through content, we need to make sure we're not inside
2499 // (in case we deal with a case of both client-side and
2500 // sever-side on the same image - it happens!)
2501 const bool inside = map && map->GetArea(p);
2503 if (!inside && isServerMap) {
2504 // Server side image maps use the href in a containing anchor
2505 // element to provide the basis for the destination url.
2506 nsCOMPtr<nsIURI> uri;
2507 nsAutoString target;
2508 nsCOMPtr<nsIContent> anchorNode;
2509 if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
2510 getter_AddRefs(anchorNode))) {
2511 // XXX if the mouse is over/clicked in the border/padding area
2512 // we should probably just pretend nothing happened. Nav4
2513 // keeps the x,y coordinates positive as we do; IE doesn't
2514 // bother. Both of them send the click through even when the
2515 // mouse is over the border.
2516 if (p.x < 0) p.x = 0;
2517 if (p.y < 0) p.y = 0;
2519 nsAutoCString spec;
2520 nsresult rv = uri->GetSpec(spec);
2521 NS_ENSURE_SUCCESS(rv, rv);
2523 spec += nsPrintfCString("?%d,%d", p.x, p.y);
2524 rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri);
2525 NS_ENSURE_SUCCESS(rv, rv);
2527 bool clicked = false;
2528 if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) {
2529 *aEventStatus = nsEventStatus_eConsumeDoDefault;
2530 clicked = true;
2532 nsContentUtils::TriggerLink(anchorNode, uri, target, clicked,
2533 /* isTrusted */ true);
2539 return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
2540 aEventStatus);
2543 Maybe<nsIFrame::Cursor> nsImageFrame::GetCursor(const nsPoint& aPoint) {
2544 nsImageMap* map = GetImageMap();
2545 if (!map) {
2546 return nsIFrame::GetCursor(aPoint);
2548 const CSSIntPoint p = TranslateEventCoords(aPoint);
2549 HTMLAreaElement* area = map->GetArea(p);
2550 if (!area) {
2551 return nsIFrame::GetCursor(aPoint);
2554 // Use the cursor from the style of the *area* element.
2555 RefPtr<ComputedStyle> areaStyle =
2556 PresShell()->StyleSet()->ResolveStyleLazily(*area);
2558 // This is one of the cases, like the <xul:tree> pseudo-style stuff, where we
2559 // get styles out of the blue and expect to trigger image loads for those.
2560 areaStyle->StartImageLoads(*PresContext()->Document());
2562 StyleCursorKind kind = areaStyle->StyleUI()->Cursor().keyword;
2563 if (kind == StyleCursorKind::Auto) {
2564 kind = StyleCursorKind::Default;
2566 return Some(Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)});
2569 nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
2570 nsAtom* aAttribute, int32_t aModType) {
2571 nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
2572 aAttribute, aModType);
2573 if (NS_FAILED(rv)) {
2574 return rv;
2576 if (nsGkAtoms::alt == aAttribute) {
2577 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
2578 NS_FRAME_IS_DIRTY);
2581 return NS_OK;
2584 void nsImageFrame::OnVisibilityChange(
2585 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
2586 if (mKind == Kind::ImageElement) {
2587 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
2588 imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
2591 if (aNewVisibility == Visibility::ApproximatelyVisible &&
2592 PresShell()->IsActive()) {
2593 MaybeDecodeForPredictedSize();
2596 nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
2599 #ifdef DEBUG_FRAME_DUMP
2600 nsresult nsImageFrame::GetFrameName(nsAString& aResult) const {
2601 return MakeFrameName(u"ImageFrame"_ns, aResult);
2604 void nsImageFrame::List(FILE* out, const char* aPrefix,
2605 ListFlags aFlags) const {
2606 nsCString str;
2607 ListGeneric(str, aPrefix, aFlags);
2609 // output the img src url
2610 if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
2611 nsCOMPtr<nsIURI> uri;
2612 currentRequest->GetURI(getter_AddRefs(uri));
2613 nsAutoCString uristr;
2614 uri->GetAsciiSpec(uristr);
2615 str += nsPrintfCString(" [src=%s]", uristr.get());
2617 fprintf_stderr(out, "%s\n", str.get());
2619 #endif
2621 LogicalSides nsImageFrame::GetLogicalSkipSides() const {
2622 LogicalSides skip(mWritingMode);
2623 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
2624 StyleBoxDecorationBreak::Clone)) {
2625 return skip;
2627 if (GetPrevInFlow()) {
2628 skip |= eLogicalSideBitsBStart;
2630 if (GetNextInFlow()) {
2631 skip |= eLogicalSideBitsBEnd;
2633 return skip;
2636 nsresult nsImageFrame::LoadIcon(const nsAString& aSpec,
2637 nsPresContext* aPresContext,
2638 imgRequestProxy** aRequest) {
2639 MOZ_ASSERT(!aSpec.IsEmpty(), "What happened??");
2641 nsCOMPtr<nsIURI> realURI;
2642 SpecToURI(aSpec, getter_AddRefs(realURI));
2644 RefPtr<imgLoader> il =
2645 nsContentUtils::GetImgLoaderForDocument(aPresContext->Document());
2647 nsCOMPtr<nsILoadGroup> loadGroup;
2648 GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));
2650 // For icon loads, we don't need to merge with the loadgroup flags
2651 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
2652 nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2654 return il->LoadImage(
2655 realURI, /* icon URI */
2656 nullptr, /* initial document URI; this is only
2657 relevant for cookies, so does not
2658 apply to icons. */
2659 nullptr, /* referrer (not relevant for icons) */
2660 nullptr, /* principal (not relevant for icons) */
2661 0, loadGroup, gIconLoad, nullptr, /* No context */
2662 nullptr, /* Not associated with any particular document */
2663 loadFlags, nullptr, contentPolicyType, u""_ns,
2664 false, /* aUseUrgentStartForChannel */
2665 false, /* aLinkPreload */
2666 aRequest);
2669 void nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const {
2670 if (mContent) {
2671 NS_ASSERTION(mContent->GetComposedDoc(),
2672 "Frame still alive after content removed from document!");
2673 mContent->GetComposedDoc()->GetDocumentCharacterSet()->Name(aCharset);
2677 void nsImageFrame::SpecToURI(const nsAString& aSpec, nsIURI** aURI) {
2678 nsIURI* baseURI = nullptr;
2679 if (mContent) {
2680 baseURI = mContent->GetBaseURI();
2682 nsAutoCString charset;
2683 GetDocumentCharacterSet(charset);
2684 NS_NewURI(aURI, aSpec, charset.IsEmpty() ? nullptr : charset.get(), baseURI);
2687 void nsImageFrame::GetLoadGroup(nsPresContext* aPresContext,
2688 nsILoadGroup** aLoadGroup) {
2689 if (!aPresContext) return;
2691 MOZ_ASSERT(nullptr != aLoadGroup, "null OUT parameter pointer");
2693 mozilla::PresShell* presShell = aPresContext->GetPresShell();
2694 if (!presShell) {
2695 return;
2698 Document* doc = presShell->GetDocument();
2699 if (!doc) {
2700 return;
2703 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2706 nsresult nsImageFrame::LoadIcons(nsPresContext* aPresContext) {
2707 NS_ASSERTION(!gIconLoad, "called LoadIcons twice");
2709 constexpr auto loadingSrc = u"resource://gre-resources/loading-image.png"_ns;
2710 constexpr auto brokenSrc = u"resource://gre-resources/broken-image.png"_ns;
2712 gIconLoad = new IconLoad();
2714 nsresult rv;
2715 // create a loader and load the images
2716 rv = LoadIcon(loadingSrc, aPresContext,
2717 getter_AddRefs(gIconLoad->mLoadingImage));
2718 if (NS_FAILED(rv)) {
2719 return rv;
2722 rv = LoadIcon(brokenSrc, aPresContext,
2723 getter_AddRefs(gIconLoad->mBrokenImage));
2724 if (NS_FAILED(rv)) {
2725 return rv;
2728 return rv;
2731 NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver, imgINotificationObserver)
2733 static const char* kIconLoadPrefs[] = {
2734 "browser.display.force_inline_alttext",
2735 "browser.display.show_image_placeholders",
2736 "browser.display.show_loading_image_placeholder", nullptr};
2738 nsImageFrame::IconLoad::IconLoad() {
2739 // register observers
2740 Preferences::AddStrongObservers(this, kIconLoadPrefs);
2741 GetPrefs();
2744 void nsImageFrame::IconLoad::Shutdown() {
2745 Preferences::RemoveObservers(this, kIconLoadPrefs);
2746 // in case the pref service releases us later
2747 if (mLoadingImage) {
2748 mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2749 mLoadingImage = nullptr;
2751 if (mBrokenImage) {
2752 mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2753 mBrokenImage = nullptr;
2757 NS_IMETHODIMP
2758 nsImageFrame::IconLoad::Observe(nsISupports* aSubject, const char* aTopic,
2759 const char16_t* aData) {
2760 NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
2761 "wrong topic");
2762 #ifdef DEBUG
2763 // assert |aData| is one of our prefs.
2764 uint32_t i = 0;
2765 for (; i < ArrayLength(kIconLoadPrefs); ++i) {
2766 if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData))
2767 break;
2769 MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs));
2770 #endif
2772 GetPrefs();
2773 return NS_OK;
2776 void nsImageFrame::IconLoad::GetPrefs() {
2777 mPrefForceInlineAltText =
2778 Preferences::GetBool("browser.display.force_inline_alttext");
2780 mPrefShowPlaceholders =
2781 Preferences::GetBool("browser.display.show_image_placeholders", true);
2783 mPrefShowLoadingPlaceholder = Preferences::GetBool(
2784 "browser.display.show_loading_image_placeholder", true);
2787 nsresult nsImageFrame::RestartAnimation() {
2788 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
2790 if (currentRequest) {
2791 bool deregister = false;
2792 nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(), currentRequest,
2793 &deregister);
2795 return NS_OK;
2798 nsresult nsImageFrame::StopAnimation() {
2799 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
2801 if (currentRequest) {
2802 bool deregister = true;
2803 nsLayoutUtils::DeregisterImageRequest(PresContext(), currentRequest,
2804 &deregister);
2806 return NS_OK;
2809 void nsImageFrame::IconLoad::Notify(imgIRequest* aRequest, int32_t aType,
2810 const nsIntRect* aData) {
2811 MOZ_ASSERT(aRequest);
2813 if (aType != imgINotificationObserver::LOAD_COMPLETE &&
2814 aType != imgINotificationObserver::FRAME_UPDATE) {
2815 return;
2818 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
2819 nsCOMPtr<imgIContainer> image;
2820 aRequest->GetImage(getter_AddRefs(image));
2821 if (!image) {
2822 return;
2825 // Retrieve the image's intrinsic size.
2826 int32_t width = 0;
2827 int32_t height = 0;
2828 image->GetWidth(&width);
2829 image->GetHeight(&height);
2831 // Request a decode at that size.
2832 image->RequestDecodeForSize(IntSize(width, height),
2833 imgIContainer::DECODE_FLAGS_DEFAULT |
2834 imgIContainer::FLAG_HIGH_QUALITY_SCALING);
2837 for (nsImageFrame* frame : mIconObservers.ForwardRange()) {
2838 frame->InvalidateFrame();
2842 NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
2844 nsImageListener::nsImageListener(nsImageFrame* aFrame) : mFrame(aFrame) {}
2846 nsImageListener::~nsImageListener() = default;
2848 void nsImageListener::Notify(imgIRequest* aRequest, int32_t aType,
2849 const nsIntRect* aData) {
2850 if (!mFrame) {
2851 return;
2854 return mFrame->Notify(aRequest, aType, aData);
2857 static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) {
2858 if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
2859 return false;
2860 // Check if the parent of the closest nsBlockFrame has auto width.
2861 nsBlockFrame* ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
2862 if (ancestor->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
2863 // Assume direct parent is a table cell frame.
2864 nsIFrame* grandAncestor = static_cast<nsIFrame*>(ancestor->GetParent());
2865 return grandAncestor && grandAncestor->StylePosition()->mWidth.IsAuto();
2867 return false;
2870 void nsImageFrame::AddInlineMinISize(gfxContext* aRenderingContext,
2871 nsIFrame::InlineMinISizeData* aData) {
2872 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
2873 aRenderingContext, this, IntrinsicISizeType::MinISize);
2874 bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
2875 aData->DefaultAddInlineMinISize(this, isize, canBreak);