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"
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"
41 #include "nsFontMetrics.h"
42 #include "nsIFrameInlines.h"
43 #include "nsIImageLoadingContent.h"
44 #include "nsImageLoadingContent.h"
45 #include "nsImageRenderer.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"
60 #include "nsCSSRendering.h"
61 #include "nsNameSpaceManager.h"
64 # include "nsAccessibilityService.h"
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"
80 #include "nsBidiUtils.h"
81 #include "nsBidiPresUtils.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
{
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 {
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
,
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();
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();
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
) {
181 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result
);
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
);
214 case nsIFrame::ExtremumLength::MinContent
:
215 case nsIFrame::ExtremumLength::MaxContent
:
216 case nsIFrame::ExtremumLength::FitContent
:
217 case nsIFrame::ExtremumLength::FitContentFunction
:
219 case nsIFrame::ExtremumLength::MozAvailable
:
222 MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
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
275 if (mKind
!= Kind::ImageElement
) {
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()) {
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();
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
),
316 mIntrinsicSize(0, 0),
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
)
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.
341 return a11y::eHTMLImageMapType
;
344 return a11y::eImageType
;
348 void nsImageFrame::DisconnectMap() {
353 mImageMap
->Destroy();
357 if (nsAccessibilityService
* accService
= GetAccService()) {
358 accService
->RecreateAccessible(PresShell(), mContent
);
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
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);
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
) {
408 const auto& content
= *StyleContent();
409 if (content
.ContentCount() != 1) {
412 const auto& item
= content
.ContentAt(0);
413 if (!item
.IsImage()) {
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
=
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
) {
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");
468 if (mKind
== Kind::ListStyleImage
) {
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
) {
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);
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);
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
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
) {
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
);
561 nsresult rv
= mContentURLRequest
->GetImageStatus(&status
);
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
) {
585 aResolution
.ApplyXTo(aSize
.width
.ref());
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())
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);
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
);
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
678 bool nsImageFrame::ShouldUseMappedAspectRatio() const {
679 if (mKind
!= Kind::ImageElement
) {
682 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
683 if (currentRequest
) {
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
;
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();
708 if (Maybe
<AspectRatio
> fromImage
= aImage
->GetIntrinsicRatio()) {
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
;
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(),
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
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()) {
760 aTransform
.SetScale(float(destRect
.width
) / float(intrinsicSize
.width
),
761 float(destRect
.height
) / float(intrinsicSize
.height
));
765 // This function checks whether the given request is the current request for our
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
);
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
;
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
)) {
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()) {
838 const auto& content
= aStyle
.StyleContent()->mContent
;
839 if (!content
.IsItems()) {
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.
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.
856 if (ImageOk(aElement
.State())) {
857 // Image is fine or loading; do the image frame thing
861 if (aStyle
.StyleUIReset()->mMozForceBrokenImageIcon
) {
865 // if our "do not show placeholders" pref is set, skip the icon
866 if (gIconLoad
&& gIconLoad
->mPrefForceInlineAltText
) {
870 if (!HasAltText(aElement
)) {
874 if (aElement
.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks
) {
875 // FIXME(emilio): We definitely don't reframe when this changes...
876 return HaveSpecifiedSize(aStyle
.StylePosition());
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
) {
906 aRequest
->GetImageStatus(&imgStatus
);
908 imgStatus
& imgIRequest::STATUS_ERROR
? NS_ERROR_FAILURE
: NS_OK
;
909 return OnLoadComplete(aRequest
, status
);
913 void nsImageFrame::OnSizeAvailable(imgIRequest
* aRequest
,
914 imgIContainer
* aImage
) {
919 /* Get requested animation policy from the pres context:
924 aImage
->SetAnimationMode(PresContext()->ImageAnimationMode());
926 if (IsPendingLoad(aRequest
)) {
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
);
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
953 bool intrinsicSizeOrRatioChanged
= [&] {
954 // NOTE(emilio): We intentionally want to call both functions and avoid
956 bool intrinsicSizeChanged
= UpdateIntrinsicSize();
957 bool intrinsicRatioChanged
= UpdateIntrinsicRatio();
958 return intrinsicSizeChanged
|| intrinsicRatioChanged
;
960 if (!GotInitialReflow()) {
964 // We're going to need to repaint now either way.
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
,
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
)) {
987 if (!GotInitialReflow()) {
988 // Don't bother to do anything; we have a reflow coming up!
992 if (mFirstFrameComplete
&& !StyleVisibility()->IsVisible()) {
996 if (IsPendingLoad(aRequest
)) {
1001 nsIntRect layerInvalidRect
=
1002 mImage
? mImage
->GetImageSpaceInvalidationRect(*aRect
) : *aRect
;
1004 if (layerInvalidRect
.IsEqualInterior(GetMaxSizedIntRect())) {
1005 // Invalidate our entire area.
1006 InvalidateSelf(nullptr, nullptr);
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
)) {
1024 InvalidateLayer(type
, aLayerInvalidRect
, aFrameInvalidRect
);
1026 if (!mFirstFrameComplete
) {
1027 InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK
, aLayerInvalidRect
,
1032 void nsImageFrame::OnLoadComplete(imgIRequest
* aRequest
, nsresult aStatus
) {
1033 NotifyNewCurrentRequest(aRequest
, aStatus
);
1036 void nsImageFrame::ResponsiveContentDensityChanged() {
1037 if (!GotInitialReflow()) {
1045 if (!UpdateIntrinsicSize() && !UpdateIntrinsicRatio()) {
1049 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
1053 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest
* aRequest
,
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.
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()) {
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
,
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
) {
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();
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()) {
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())
1178 // get the offset into the content area of the image where aImg starts if it is
1180 nscoord
nsImageFrame::GetContinuationOffset() const {
1182 for (nsIFrame
* f
= GetPrevInFlow(); f
; f
= f
->GetPrevInFlow()) {
1183 offset
+= f
->GetContentRect().height
;
1185 NS_ASSERTION(offset
>= 0, "bogus GetContentRect");
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.
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
,
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
) {
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!");
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
);
1255 RemoveStateBits(IMAGE_SIZECONSTRAINED
);
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();
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
) {
1305 if (!imageOK
|| !haveSize
) {
1306 nsRect
altFeedbackSize(
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();
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();
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;
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
1389 if (fits
|| (0 == totalWidth
)) {
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
;
1398 // Space won't fit. Leave it at the end but don't include it in
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)
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) {
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()))) {
1468 // Determine how much of the text to display on this line
1469 uint32_t maxFit
; // number of characters that fit
1471 MeasureString(str
, strLen
, iSize
, maxFit
, aRenderingContext
, *fm
);
1474 nsresult rv
= NS_ERROR_FAILURE
;
1476 if (aPresContext
->BidiEnabled()) {
1477 mozilla::intl::BidiEmbeddingLevel level
;
1481 x
= pt
.x
+ maxDescent
;
1482 if (wm
.IsBidiLTR()) {
1484 level
= mozilla::intl::BidiEmbeddingLevel::LTR();
1486 y
= aRect
.YMost() - strWidth
;
1487 level
= mozilla::intl::BidiEmbeddingLevel::RTL();
1490 y
= pt
.y
+ maxAscent
;
1491 if (wm
.IsBidiLTR()) {
1493 level
= mozilla::intl::BidiEmbeddingLevel::LTR();
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
,
1507 ? nsPoint(pt
.x
+ maxDescent
, pt
.y
)
1508 : nsPoint(pt
.x
, pt
.y
+ maxAscent
),
1509 *fm
, aRenderingContext
);
1512 // Move to the next line
1515 if (wm
.IsVerticalRL()) {
1517 } else if (wm
.IsVerticalLR()) {
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
1535 SetBorderStyle(side
, StyleBorderStyle::Inset
);
1540 class nsDisplayAltFeedback final
: public nsPaintedDisplayItem
{
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
{
1554 static_cast<const nsDisplayItemGenericImageGeometry
*>(aGeometry
);
1556 if (aBuilder
->ShouldSyncDecodeImages() &&
1557 geometry
->ShouldInvalidateToSyncDecodeImages()) {
1559 aInvalidRegion
->Or(*aInvalidRegion
, GetBounds(aBuilder
, &snap
));
1562 nsDisplayItem::ComputeInvalidationRegion(aBuilder
, aGeometry
,
1566 nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) const final
{
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.
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
1628 if ((inner
.width
< 2 * borderEdgeWidth
) ||
1629 (inner
.height
< 2 * borderEdgeWidth
)) {
1630 return ImgDrawResult::SUCCESS
;
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
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
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
;
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
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
,
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
;
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
,
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
;
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();
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.
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
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
1792 bool textDrawResult
= true;
1793 class AutoSaveRestore
{
1795 explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder
& aBuilder
,
1796 bool& aTextDrawResult
)
1797 : mBuilder(aBuilder
), mTextDrawResult(aTextDrawResult
) {
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();
1811 mozilla::wr::DisplayListBuilder
& mBuilder
;
1812 bool& mTextDrawResult
;
1815 AutoSaveRestore
autoSaveRestore(aBuilder
, textDrawResult
);
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
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
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
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
,
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
,
1885 RefPtr
<image::WebRenderImageProvider
> provider
;
1886 result
= imgCon
->GetImageProvider(aManager
->LayerManager(), decodeSize
,
1887 svgContext
, region
, aFlags
,
1888 getter_AddRefs(provider
));
1890 bool wrResult
= aManager
->CommandBuilder().PushImageProvider(
1891 aItem
, provider
, result
, aBuilder
, aResources
, destRect
, bounds
);
1892 result
&= wrResult
? ImgDrawResult::SUCCESS
: ImgDrawResult::NOT_READY
;
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
;
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
,
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
;
1940 inner
.x
+= paddedIconSize
;
1942 inner
.width
-= paddedIconSize
;
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
1964 return textDrawResult
? ImgDrawResult::SUCCESS
: ImgDrawResult::NOT_READY
;
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())));
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.
1987 static bool OldImageHasDifferentRatio(const nsImageFrame
& aFrame
,
1988 imgIContainer
& aImage
,
1989 imgIContainer
* aPrevImage
) {
1990 if (!aPrevImage
|| aPrevImage
== &aImage
) {
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
)) {
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;
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.
2020 (!currentRatioRecomputed
&& aImage
.GetIntrinsicRatio() == Nothing()) ||
2021 currentRatio
== currentRatioRecomputed
,
2022 "aspect-ratio got out of sync during paint? How?");
2024 auto oldRatio
= ComputeIntrinsicRatio(aPrevImage
, hasRequest
, aFrame
);
2025 return oldRatio
!= currentRatio
;
2028 void nsDisplayImage::Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) {
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.
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 {
2071 static_cast<const nsDisplayItemGenericImageGeometry
*>(aGeometry
);
2073 if (aBuilder
->ShouldSyncDecodeImages() &&
2074 geometry
->ShouldInvalidateToSyncDecodeImages()) {
2076 aInvalidRegion
->Or(*aInvalidRegion
, GetBounds(aBuilder
, &snap
));
2079 nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder
, aGeometry
,
2083 nsRect
nsDisplayImage::GetDestRect() const {
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 {
2094 if (mImage
&& mImage
->WillDrawOpaqueNow()) {
2095 const nsRect frameContentBox
= GetBounds(aSnap
);
2096 return GetDestRect().Intersect(frameContentBox
);
2101 bool nsDisplayImage::CreateWebRenderCommands(
2102 mozilla::wr::DisplayListBuilder
& aBuilder
,
2103 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
2104 const StackingContextHelper
& aSc
, RenderRootStateManager
* aManager
,
2105 nsDisplayListBuilder
* aDisplayListBuilder
) {
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()) {
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
;
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
2177 drawResult
= ImgDrawResult::WRONG_SIZE
;
2178 provider
= std::move(prevProvider
);
2183 // Previous image was unusable; we can forget about it.
2184 updatePrevImage
= true;
2187 case ImgDrawResult::NOT_SUPPORTED
:
2190 updatePrevImage
= mPrevImage
!= mImage
;
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
);
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
,
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(),
2228 constraintRect
.y
-= GetContinuationOffset();
2230 nsPoint anchorPoint
;
2231 nsRect dest
= nsLayoutUtils::ComputeObjectDestRect(
2232 constraintRect
, mIntrinsicSize
, mIntrinsicRatio
, StylePosition(),
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;
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) {
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
);
2331 aLists
.Content()->AppendNewToTop
<nsDisplayImage
>(aBuilder
, this, mImage
,
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;
2344 if (GetShowFrameBorders() && GetImageMap()) {
2345 aLists
.Outlines()->AppendNewToTop
<nsDisplayGeneric
>(
2346 aBuilder
, this, PaintDebugImageMap
, "DebugImageMap",
2347 DisplayItemType::TYPE_DEBUG_IMAGE_MAP
);
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.
2368 if (displaySelection
!= nsISelectionDisplay::DISPLAY_ALL
) {
2372 // If the image is currently resize target of the editor, don't draw the
2373 // selection overlay.
2374 HTMLEditor
* htmlEditor
= nsContentUtils::GetHTMLEditor(PresContext());
2379 return htmlEditor
->GetResizerTarget() != mContent
;
2382 nsImageMap
* nsImageFrame::GetImageMap() {
2384 if (nsIContent
* map
= GetMapElement()) {
2385 mImageMap
= new nsImageMap();
2386 mImageMap
->Init(this, map
);
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;
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
));
2416 nsCOMPtr
<nsIURI
> href
= content
->GetHrefURI();
2420 status
= (*aHref
!= nullptr);
2422 RefPtr
<HTMLAnchorElement
> anchor
= HTMLAnchorElement::FromNode(content
);
2424 anchor
->GetTarget(aTarget
);
2426 NS_ADDREF(*aNode
= content
);
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.
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());
2446 nsresult
nsImageFrame::GetContentForEvent(WidgetEvent
* aEvent
,
2447 nsIContent
** aContent
) {
2448 NS_ENSURE_ARG_POINTER(aContent
);
2450 nsIFrame
* f
= nsLayoutUtils::GetNonGeneratedAncestor(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()
2460 if (capturingContent
&& capturingContent
->GetPrimaryFrame() == this) {
2461 *aContent
= capturingContent
;
2462 NS_IF_ADDREF(*aContent
);
2466 if (nsImageMap
* map
= GetImageMap()) {
2467 const CSSIntPoint p
= TranslateEventCoords(
2468 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent
, RelativeTo
{this}));
2469 nsCOMPtr
<nsIContent
> area
= map
->GetArea(p
);
2471 area
.forget(aContent
);
2476 *aContent
= GetContent();
2477 NS_IF_ADDREF(*aContent
);
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
) {
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;
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
;
2532 nsContentUtils::TriggerLink(anchorNode
, uri
, target
, clicked
,
2533 /* isTrusted */ true);
2539 return nsAtomicContainerFrame::HandleEvent(aPresContext
, aEvent
,
2543 Maybe
<nsIFrame::Cursor
> nsImageFrame::GetCursor(const nsPoint
& aPoint
) {
2544 nsImageMap
* map
= GetImageMap();
2546 return nsIFrame::GetCursor(aPoint
);
2548 const CSSIntPoint p
= TranslateEventCoords(aPoint
);
2549 HTMLAreaElement
* area
= map
->GetArea(p
);
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
)) {
2576 if (nsGkAtoms::alt
== aAttribute
) {
2577 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
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 {
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());
2621 LogicalSides
nsImageFrame::GetLogicalSkipSides() const {
2622 LogicalSides
skip(mWritingMode
);
2623 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak
==
2624 StyleBoxDecorationBreak::Clone
)) {
2627 if (GetPrevInFlow()) {
2628 skip
|= eLogicalSideBitsBStart
;
2630 if (GetNextInFlow()) {
2631 skip
|= eLogicalSideBitsBEnd
;
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
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 */
2669 void nsImageFrame::GetDocumentCharacterSet(nsACString
& aCharset
) const {
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;
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();
2698 Document
* doc
= presShell
->GetDocument();
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();
2715 // create a loader and load the images
2716 rv
= LoadIcon(loadingSrc
, aPresContext
,
2717 getter_AddRefs(gIconLoad
->mLoadingImage
));
2718 if (NS_FAILED(rv
)) {
2722 rv
= LoadIcon(brokenSrc
, aPresContext
,
2723 getter_AddRefs(gIconLoad
->mBrokenImage
));
2724 if (NS_FAILED(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
);
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;
2752 mBrokenImage
->CancelAndForgetObserver(NS_ERROR_FAILURE
);
2753 mBrokenImage
= nullptr;
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
),
2763 // assert |aData| is one of our prefs.
2765 for (; i
< ArrayLength(kIconLoadPrefs
); ++i
) {
2766 if (NS_ConvertASCIItoUTF16(kIconLoadPrefs
[i
]) == nsDependentString(aData
))
2769 MOZ_ASSERT(i
< ArrayLength(kIconLoadPrefs
));
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
,
2798 nsresult
nsImageFrame::StopAnimation() {
2799 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
2801 if (currentRequest
) {
2802 bool deregister
= true;
2803 nsLayoutUtils::DeregisterImageRequest(PresContext(), currentRequest
,
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
) {
2818 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
2819 nsCOMPtr
<imgIContainer
> image
;
2820 aRequest
->GetImage(getter_AddRefs(image
));
2825 // Retrieve the image's intrinsic size.
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
) {
2854 return mFrame
->Notify(aRequest
, aType
, aData
);
2857 static bool IsInAutoWidthTableCellForQuirk(nsIFrame
* aFrame
) {
2858 if (eCompatibility_NavQuirks
!= aFrame
->PresContext()->CompatibilityMode())
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();
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
);