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/ComputedStyle.h"
16 #include "mozilla/DebugOnly.h"
17 #include "mozilla/Encoding.h"
18 #include "mozilla/EventStates.h"
19 #include "mozilla/HTMLEditor.h"
20 #include "mozilla/dom/ImageTracker.h"
21 #include "mozilla/gfx/2D.h"
22 #include "mozilla/gfx/Helpers.h"
23 #include "mozilla/gfx/PathHelpers.h"
24 #include "mozilla/dom/GeneratedImageContent.h"
25 #include "mozilla/dom/HTMLAreaElement.h"
26 #include "mozilla/dom/HTMLImageElement.h"
27 #include "mozilla/dom/ResponsiveImageSelector.h"
28 #include "mozilla/layers/RenderRootStateManager.h"
29 #include "mozilla/layers/WebRenderLayerManager.h"
30 #include "mozilla/MouseEvents.h"
31 #include "mozilla/PresShell.h"
32 #include "mozilla/PresShellInlines.h"
33 #include "mozilla/StaticPrefs_image.h"
34 #include "mozilla/StaticPrefs_layout.h"
35 #include "mozilla/SVGImageContext.h"
36 #include "mozilla/Unused.h"
39 #include "nsFontMetrics.h"
40 #include "nsIImageLoadingContent.h"
41 #include "nsImageLoadingContent.h"
43 #include "nsPrintfCString.h"
44 #include "nsPresContext.h"
45 #include "nsGkAtoms.h"
46 #include "mozilla/dom/Document.h"
47 #include "nsContentUtils.h"
48 #include "nsCSSAnonBoxes.h"
49 #include "nsStyleConsts.h"
50 #include "nsStyleUtil.h"
51 #include "nsTransform2D.h"
52 #include "nsImageMap.h"
53 #include "nsILoadGroup.h"
54 #include "nsNetUtil.h"
56 #include "nsCSSRendering.h"
57 #include "nsNameSpaceManager.h"
60 # include "nsAccessibilityService.h"
62 #include "nsLayoutUtils.h"
63 #include "nsDisplayList.h"
64 #include "nsIContent.h"
65 #include "FrameLayerBuilder.h"
66 #include "mozilla/dom/Selection.h"
67 #include "nsIURIMutator.h"
69 #include "imgIContainer.h"
70 #include "imgLoader.h"
71 #include "imgRequestProxy.h"
73 #include "nsCSSFrameConstructor.h"
77 #include "nsBidiUtils.h"
78 #include "nsBidiPresUtils.h"
81 #include "ImageLayers.h"
82 #include "ImageRegion.h"
83 #include "ImageContainer.h"
84 #include "mozilla/ServoStyleSet.h"
85 #include "nsBlockFrame.h"
86 #include "nsStyleStructInlines.h"
88 #include "mozilla/Preferences.h"
90 #include "mozilla/dom/Link.h"
91 #include "mozilla/dom/HTMLAnchorElement.h"
93 using namespace mozilla
;
94 using namespace mozilla::dom
;
95 using namespace mozilla::gfx
;
96 using namespace mozilla::image
;
97 using namespace mozilla::layers
;
99 using mozilla::layout::TextDrawTarget
;
101 // sizes (pixels) for image icon, padding and border frame
102 #define ICON_SIZE (16)
103 #define ICON_PADDING (3)
104 #define ALT_BORDER_WIDTH (1)
106 // Default alignment value (so we can tell an unset value from a set value)
107 #define ALIGN_UNSET uint8_t(-1)
109 // static icon information
110 StaticRefPtr
<nsImageFrame::IconLoad
> nsImageFrame::gIconLoad
;
112 // test if the width and height are fixed, looking at the style data
113 // This is used by nsImageFrame::ShouldCreateImageFrameFor and should
114 // not be used for layout decisions.
115 static bool HaveSpecifiedSize(const nsStylePosition
* aStylePosition
) {
116 // check the width and height values in the reflow input's style struct
117 // - if width and height are specified as either coord or percentage, then
118 // the size of the image frame is constrained
119 return aStylePosition
->mWidth
.IsLengthPercentage() &&
120 aStylePosition
->mHeight
.IsLengthPercentage();
123 template <typename SizeOrMaxSize
>
124 static bool DependsOnIntrinsicSize(const SizeOrMaxSize
& aMinOrMaxSize
) {
125 auto length
= nsIFrame::ToExtremumLength(aMinOrMaxSize
);
130 case nsIFrame::ExtremumLength::MinContent
:
131 case nsIFrame::ExtremumLength::MaxContent
:
132 case nsIFrame::ExtremumLength::MozFitContent
:
134 case nsIFrame::ExtremumLength::MozAvailable
:
137 MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
141 // Decide whether we can optimize away reflows that result from the
142 // image's intrinsic size changing.
143 static bool SizeDependsOnIntrinsicSize(const ReflowInput
& aReflowInput
) {
144 const auto& position
= *aReflowInput
.mStylePosition
;
145 WritingMode wm
= aReflowInput
.GetWritingMode();
146 // Don't try to make this optimization when an image has percentages
147 // in its 'width' or 'height'. The percentages might be treated like
148 // auto (especially for intrinsic width calculations and for heights).
150 // min-width: min-content and such can also affect our intrinsic size.
151 // but note that those keywords on the block axis behave like auto, so we
152 // don't need to check them.
154 // Flex item's min-[width|height]:auto resolution depends on intrinsic size.
155 return !position
.mHeight
.ConvertsToLength() ||
156 !position
.mWidth
.ConvertsToLength() ||
157 DependsOnIntrinsicSize(position
.MinISize(wm
)) ||
158 DependsOnIntrinsicSize(position
.MaxISize(wm
)) ||
159 aReflowInput
.mFrame
->IsFlexItem();
162 nsIFrame
* NS_NewImageFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
163 return new (aPresShell
) nsImageFrame(aStyle
, aPresShell
->GetPresContext(),
164 nsImageFrame::Kind::ImageElement
);
167 nsIFrame
* NS_NewImageFrameForContentProperty(PresShell
* aPresShell
,
168 ComputedStyle
* aStyle
) {
169 return new (aPresShell
) nsImageFrame(aStyle
, aPresShell
->GetPresContext(),
170 nsImageFrame::Kind::ContentProperty
);
173 nsIFrame
* NS_NewImageFrameForGeneratedContentIndex(PresShell
* aPresShell
,
174 ComputedStyle
* aStyle
) {
175 return new (aPresShell
)
176 nsImageFrame(aStyle
, aPresShell
->GetPresContext(),
177 nsImageFrame::Kind::ContentPropertyAtIndex
);
180 bool nsImageFrame::ShouldShowBrokenImageIcon() const {
181 // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and
182 // Blink behave differently here for content: url(..), for now adapt to
184 if (mKind
!= Kind::ImageElement
) {
188 // <img alt=""> is special, and it shouldn't draw the broken image icon,
189 // unlike the no-alt attribute or non-empty-alt-attribute case.
190 if (auto* image
= HTMLImageElement::FromNode(mContent
)) {
191 const nsAttrValue
* alt
= image
->GetParsedAttr(nsGkAtoms::alt
);
192 if (alt
&& alt
->IsEmptyString()) {
197 // check for broken images. valid null images (eg. img src="") are
198 // not considered broken because they have no image requests
199 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
200 uint32_t imageStatus
;
201 return NS_SUCCEEDED(currentRequest
->GetImageStatus(&imageStatus
)) &&
202 (imageStatus
& imgIRequest::STATUS_ERROR
);
205 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
206 MOZ_ASSERT(imageLoader
);
207 // Show the broken image icon only if we've tried to perform a load at all
208 // (that is, if we have a current uri).
209 nsCOMPtr
<nsIURI
> currentURI
= imageLoader
->GetCurrentURI();
213 nsImageFrame
* nsImageFrame::CreateContinuingFrame(
214 mozilla::PresShell
* aPresShell
, ComputedStyle
* aStyle
) const {
215 return new (aPresShell
)
216 nsImageFrame(aStyle
, aPresShell
->GetPresContext(), mKind
);
219 NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame
)
221 nsImageFrame::nsImageFrame(ComputedStyle
* aStyle
, nsPresContext
* aPresContext
,
222 ClassID aID
, Kind aKind
)
223 : nsAtomicContainerFrame(aStyle
, aPresContext
, aID
),
225 mIntrinsicSize(0, 0),
227 mContentURLRequestRegistered(false),
228 mDisplayingIcon(false),
229 mFirstFrameComplete(false),
230 mReflowCallbackPosted(false),
231 mForceSyncDecoding(false) {
232 EnableVisibilityTracking();
235 nsImageFrame::~nsImageFrame() = default;
237 NS_QUERYFRAME_HEAD(nsImageFrame
)
238 NS_QUERYFRAME_ENTRY(nsImageFrame
)
239 NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame
)
242 a11y::AccType
nsImageFrame::AccessibleType() {
243 // Don't use GetImageMap() to avoid reentrancy into accessibility.
245 return a11y::eHTMLImageMapType
;
248 return a11y::eImageType
;
252 void nsImageFrame::DisconnectMap() {
257 mImageMap
->Destroy();
261 if (nsAccessibilityService
* accService
= GetAccService()) {
262 accService
->RecreateAccessible(PresShell(), mContent
);
267 void nsImageFrame::DestroyFrom(nsIFrame
* aDestructRoot
,
268 PostDestroyData
& aPostDestroyData
) {
269 if (mReflowCallbackPosted
) {
270 PresShell()->CancelReflowCallback(this);
271 mReflowCallbackPosted
= false;
274 // Tell our image map, if there is one, to clean up
275 // This causes the nsImageMap to unregister itself as
279 MOZ_ASSERT(mListener
);
281 if (mKind
== Kind::ImageElement
) {
282 MOZ_ASSERT(!mContentURLRequest
);
283 MOZ_ASSERT(!mContentURLRequestRegistered
);
284 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
285 MOZ_ASSERT(imageLoader
);
287 // Notify our image loading content that we are going away so it can
288 // deregister with our refresh driver.
289 imageLoader
->FrameDestroyed(this);
290 imageLoader
->RemoveNativeObserver(mListener
);
291 } else if (mContentURLRequest
) {
292 PresContext()->Document()->ImageTracker()->Remove(mContentURLRequest
);
293 nsLayoutUtils::DeregisterImageRequest(PresContext(), mContentURLRequest
,
294 &mContentURLRequestRegistered
);
295 mContentURLRequest
->Cancel(NS_BINDING_ABORTED
);
298 // set the frame to null so we don't send messages to a dead object.
299 mListener
->SetFrame(nullptr);
302 // If we were displaying an icon, take ourselves off the list
303 if (mDisplayingIcon
) gIconLoad
->RemoveIconObserver(this);
305 nsAtomicContainerFrame::DestroyFrom(aDestructRoot
, aPostDestroyData
);
308 void nsImageFrame::MaybeRecordContentUrlOnImageTelemetry() {
309 if (mKind
!= Kind::ImageElement
) {
312 const auto& content
= *StyleContent();
313 if (content
.ContentCount() != 1) {
316 const auto& item
= content
.ContentAt(0);
317 if (!item
.IsImage()) {
320 PresContext()->Document()->SetUseCounter(
321 eUseCounter_custom_ContentUrlOnImageContent
);
324 void nsImageFrame::DidSetComputedStyle(ComputedStyle
* aOldStyle
) {
325 nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle
);
327 MaybeRecordContentUrlOnImageTelemetry();
329 auto newOrientation
= StyleVisibility()->mImageOrientation
;
331 // We need to update our orientation either if we had no ComputedStyle before
332 // because this is the first time it's been set, or if the image-orientation
333 // property changed from its previous value.
334 bool shouldUpdateOrientation
=
337 aOldStyle
->StyleVisibility()->mImageOrientation
!= newOrientation
);
339 if (shouldUpdateOrientation
) {
340 nsCOMPtr
<imgIContainer
> image(mImage
->Unwrap());
341 mImage
= nsLayoutUtils::OrientImage(image
, newOrientation
);
343 UpdateIntrinsicSize();
344 UpdateIntrinsicRatio();
345 } else if (!aOldStyle
|| aOldStyle
->StylePosition()->mAspectRatio
!=
346 StylePosition()->mAspectRatio
) {
347 UpdateIntrinsicRatio();
351 static bool SizeIsAvailable(imgIRequest
* aRequest
) {
356 uint32_t imageStatus
= 0;
357 nsresult rv
= aRequest
->GetImageStatus(&imageStatus
);
358 return NS_SUCCEEDED(rv
) && (imageStatus
& imgIRequest::STATUS_SIZE_AVAILABLE
);
361 const StyleImage
* nsImageFrame::GetImageFromStyle() const {
362 if (mKind
== Kind::ImageElement
) {
365 uint32_t contentIndex
= 0;
366 const nsStyleContent
* styleContent
= StyleContent();
367 if (mKind
== Kind::ContentPropertyAtIndex
) {
369 mContent
->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage
));
370 contentIndex
= static_cast<GeneratedImageContent
*>(mContent
.get())->Index();
372 // TODO(emilio): Consider inheriting the `content` property instead of doing
373 // this parent traversal?
374 nsIFrame
* parent
= GetParent();
375 MOZ_DIAGNOSTIC_ASSERT(
376 parent
->GetContent()->IsGeneratedContentContainerForMarker() ||
377 parent
->GetContent()->IsGeneratedContentContainerForAfter() ||
378 parent
->GetContent()->IsGeneratedContentContainerForBefore());
379 nsIFrame
* nonAnonymousParent
= parent
;
380 while (nonAnonymousParent
->Style()->IsAnonBox()) {
381 nonAnonymousParent
= nonAnonymousParent
->GetParent();
383 MOZ_DIAGNOSTIC_ASSERT(parent
->GetContent() ==
384 nonAnonymousParent
->GetContent());
385 styleContent
= nonAnonymousParent
->StyleContent();
387 MOZ_RELEASE_ASSERT(contentIndex
< styleContent
->ContentCount());
388 auto& contentItem
= styleContent
->ContentAt(contentIndex
);
389 MOZ_RELEASE_ASSERT(contentItem
.IsImage());
390 return &contentItem
.AsImage();
393 void nsImageFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
394 nsIFrame
* aPrevInFlow
) {
395 MOZ_ASSERT_IF(aPrevInFlow
,
396 aPrevInFlow
->Type() == Type() &&
397 static_cast<nsImageFrame
*>(aPrevInFlow
)->mKind
== mKind
);
399 nsAtomicContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
401 mListener
= new nsImageListener(this);
404 LoadIcons(PresContext());
407 if (mKind
== Kind::ImageElement
) {
408 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(aContent
);
409 MOZ_ASSERT(imageLoader
);
410 imageLoader
->AddNativeObserver(mListener
);
411 // We have a PresContext now, so we need to notify the image content node
412 // that it can register images.
413 imageLoader
->FrameCreated(this);
415 const StyleImage
* image
= GetImageFromStyle();
416 MOZ_ASSERT(image
->IsImageRequestType(),
417 "Content image should only parse url() type");
418 Document
* doc
= PresContext()->Document();
419 if (imgRequestProxy
* proxy
= image
->GetImageRequest()) {
420 proxy
->Clone(mListener
, doc
, getter_AddRefs(mContentURLRequest
));
421 SetupForContentURLRequest();
425 // Give image loads associated with an image frame a small priority boost.
426 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
427 uint32_t categoryToBoostPriority
= imgIRequest::CATEGORY_FRAME_INIT
;
429 // Increase load priority further if intrinsic size might be important for
431 if (!HaveSpecifiedSize(StylePosition())) {
432 categoryToBoostPriority
|= imgIRequest::CATEGORY_SIZE_QUERY
;
435 currentRequest
->BoostPriority(categoryToBoostPriority
);
439 void nsImageFrame::SetupForContentURLRequest() {
440 MOZ_ASSERT(mKind
!= Kind::ImageElement
);
441 if (!mContentURLRequest
) {
445 // We're not using AssociateRequestToFrame for the content property, so we
446 // need to add it to the image tracker manually.
447 PresContext()->Document()->ImageTracker()->Add(mContentURLRequest
);
450 nsresult rv
= mContentURLRequest
->GetImageStatus(&status
);
455 if (status
& imgIRequest::STATUS_SIZE_AVAILABLE
) {
456 nsCOMPtr
<imgIContainer
> image
;
457 mContentURLRequest
->GetImage(getter_AddRefs(image
));
458 OnSizeAvailable(mContentURLRequest
, image
);
461 if (status
& imgIRequest::STATUS_FRAME_COMPLETE
) {
462 mFirstFrameComplete
= true;
465 if (status
& imgIRequest::STATUS_IS_ANIMATED
) {
466 nsLayoutUtils::RegisterImageRequest(PresContext(), mContentURLRequest
,
467 &mContentURLRequestRegistered
);
471 static void ScaleIntrinsicSizeForDensity(IntrinsicSize
& aSize
,
472 const ImageResolution
& aResolution
) {
474 aResolution
.ApplyXTo(aSize
.width
.ref());
477 aResolution
.ApplyYTo(aSize
.height
.ref());
481 static void ScaleIntrinsicSizeForDensity(imgIContainer
* aImage
,
482 nsIContent
& aContent
,
483 IntrinsicSize
& aSize
) {
484 ImageResolution resolution
= aImage
->GetResolution();
485 if (auto* image
= HTMLImageElement::FromNode(aContent
)) {
486 if (auto* selector
= image
->GetResponsiveImageSelector()) {
487 resolution
.ScaleBy(selector
->GetSelectedImageDensity());
490 ScaleIntrinsicSizeForDensity(aSize
, resolution
);
493 static IntrinsicSize
ComputeIntrinsicSize(imgIContainer
* aImage
,
494 bool aUseMappedRatio
,
495 nsImageFrame::Kind aKind
,
496 const nsImageFrame
& aFrame
) {
497 const ComputedStyle
& style
= *aFrame
.Style();
498 if (style
.StyleDisplay()->IsContainSize()) {
499 return IntrinsicSize(0, 0);
503 if (aImage
&& NS_SUCCEEDED(aImage
->GetIntrinsicSize(&size
))) {
504 IntrinsicSize intrinsicSize
;
505 intrinsicSize
.width
= size
.width
== -1 ? Nothing() : Some(size
.width
);
506 intrinsicSize
.height
= size
.height
== -1 ? Nothing() : Some(size
.height
);
507 if (aKind
== nsImageFrame::Kind::ImageElement
) {
508 ScaleIntrinsicSizeForDensity(aImage
, *aFrame
.GetContent(), intrinsicSize
);
510 ScaleIntrinsicSizeForDensity(intrinsicSize
,
511 aFrame
.GetImageFromStyle()->GetResolution());
513 return intrinsicSize
;
516 if (aFrame
.ShouldShowBrokenImageIcon()) {
517 nscoord edgeLengthToUse
= nsPresContext::CSSPixelsToAppUnits(
518 ICON_SIZE
+ (2 * (ICON_PADDING
+ ALT_BORDER_WIDTH
)));
519 return IntrinsicSize(edgeLengthToUse
, edgeLengthToUse
);
522 if (aUseMappedRatio
&& style
.StylePosition()->mAspectRatio
.HasRatio()) {
523 return IntrinsicSize();
526 return IntrinsicSize(0, 0);
529 // For compat reasons, see bug 1602047, we don't use the intrinsic ratio from
530 // width="" and height="" for images with no src attribute (no request).
532 // But we shouldn't get fooled by <img loading=lazy>. We do want to apply the
534 bool nsImageFrame::ShouldUseMappedAspectRatio() const {
535 if (mKind
!= Kind::ImageElement
) {
538 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
539 if (currentRequest
) {
542 // TODO(emilio): Investigate the compat situation of the above check, maybe we
543 // can just check for empty src attribute or something...
544 auto* image
= HTMLImageElement::FromNode(mContent
);
545 return image
&& image
->IsAwaitingLoadOrLazyLoading();
548 bool nsImageFrame::UpdateIntrinsicSize() {
549 IntrinsicSize oldIntrinsicSize
= mIntrinsicSize
;
551 ComputeIntrinsicSize(mImage
, ShouldUseMappedAspectRatio(), mKind
, *this);
552 return mIntrinsicSize
!= oldIntrinsicSize
;
555 static AspectRatio
ComputeIntrinsicRatio(imgIContainer
* aImage
,
556 bool aUseMappedRatio
,
557 const nsImageFrame
& aFrame
) {
558 const ComputedStyle
& style
= *aFrame
.Style();
559 if (style
.StyleDisplay()->IsContainSize()) {
560 return AspectRatio();
564 if (Maybe
<AspectRatio
> fromImage
= aImage
->GetIntrinsicRatio()) {
568 if (aUseMappedRatio
) {
569 const StyleAspectRatio
& ratio
= style
.StylePosition()->mAspectRatio
;
570 if (ratio
.auto_
&& ratio
.HasRatio()) {
571 // Return the mapped intrinsic aspect ratio stored in
572 // nsStylePosition::mAspectRatio.
573 return ratio
.ratio
.AsRatio().ToLayoutRatio(UseBoxSizing::Yes
);
576 if (aFrame
.ShouldShowBrokenImageIcon()) {
577 return AspectRatio(1.0f
);
579 return AspectRatio();
582 bool nsImageFrame::UpdateIntrinsicRatio() {
583 AspectRatio oldIntrinsicRatio
= mIntrinsicRatio
;
585 ComputeIntrinsicRatio(mImage
, ShouldUseMappedAspectRatio(), *this);
586 return mIntrinsicRatio
!= oldIntrinsicRatio
;
589 bool nsImageFrame::GetSourceToDestTransform(nsTransform2D
& aTransform
) {
590 // First, figure out destRect (the rect we're rendering into).
591 // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
592 // because GetInnerArea() might be smaller if we're fragmented, whereas
593 // mComputedSize has our full content-box size (which we need for
594 // ComputeObjectDestRect to work correctly).
595 nsRect
constraintRect(GetInnerArea().TopLeft(), mComputedSize
);
596 constraintRect
.y
-= GetContinuationOffset();
598 nsRect destRect
= nsLayoutUtils::ComputeObjectDestRect(
599 constraintRect
, mIntrinsicSize
, mIntrinsicRatio
, StylePosition());
600 // Set the translation components, based on destRect
601 // XXXbz does this introduce rounding errors because of the cast to
602 // float? Should we just manually add that stuff in every time
604 aTransform
.SetToTranslate(float(destRect
.x
), float(destRect
.y
));
606 // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic
607 // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector,
608 // see ScaleIntrinsicSizeForDensity.
609 nsSize intrinsicSize
;
610 if (!mImage
|| !NS_SUCCEEDED(mImage
->GetIntrinsicSize(&intrinsicSize
)) ||
611 intrinsicSize
.IsEmpty()) {
615 aTransform
.SetScale(float(destRect
.width
) / float(intrinsicSize
.width
),
616 float(destRect
.height
) / float(intrinsicSize
.height
));
620 // This function checks whether the given request is the current request for our
622 bool nsImageFrame::IsPendingLoad(imgIRequest
* aRequest
) const {
623 // Default to pending load in case of errors
624 if (mKind
!= Kind::ImageElement
) {
625 MOZ_ASSERT(aRequest
== mContentURLRequest
);
629 nsCOMPtr
<nsIImageLoadingContent
> imageLoader(do_QueryInterface(mContent
));
630 MOZ_ASSERT(imageLoader
);
632 int32_t requestType
= nsIImageLoadingContent::UNKNOWN_REQUEST
;
633 imageLoader
->GetRequestType(aRequest
, &requestType
);
635 return requestType
!= nsIImageLoadingContent::CURRENT_REQUEST
;
638 nsRect
nsImageFrame::SourceRectToDest(const nsIntRect
& aRect
) {
639 // When scaling the image, row N of the source image may (depending on
640 // the scaling function) be used to draw any row in the destination image
641 // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
642 // floating-point scaling factor. The same holds true for columns.
643 // So, we start by computing that bound without the floor and ceiling.
645 nsRect
r(nsPresContext::CSSPixelsToAppUnits(aRect
.x
- 1),
646 nsPresContext::CSSPixelsToAppUnits(aRect
.y
- 1),
647 nsPresContext::CSSPixelsToAppUnits(aRect
.width
+ 2),
648 nsPresContext::CSSPixelsToAppUnits(aRect
.height
+ 2));
650 nsTransform2D sourceToDest
;
651 if (!GetSourceToDestTransform(sourceToDest
)) {
652 // Failed to generate transform matrix. Return our whole inner area,
653 // to be on the safe side (since this method is used for generating
654 // invalidation rects).
655 return GetInnerArea();
658 sourceToDest
.TransformCoord(&r
.x
, &r
.y
, &r
.width
, &r
.height
);
660 // Now, round the edges out to the pixel boundary.
661 nscoord scale
= nsPresContext::CSSPixelsToAppUnits(1);
662 nscoord right
= r
.x
+ r
.width
;
663 nscoord bottom
= r
.y
+ r
.height
;
665 r
.x
-= (scale
+ (r
.x
% scale
)) % scale
;
666 r
.y
-= (scale
+ (r
.y
% scale
)) % scale
;
667 r
.width
= right
+ ((scale
- (right
% scale
)) % scale
) - r
.x
;
668 r
.height
= bottom
+ ((scale
- (bottom
% scale
)) % scale
) - r
.y
;
673 static bool ImageOk(EventStates aState
) {
674 return !aState
.HasState(NS_EVENT_STATE_BROKEN
);
677 static bool HasAltText(const Element
& aElement
) {
678 // We always return some alternate text for <input>, see
679 // nsCSSFrameConstructor::GetAlternateTextFor.
680 if (aElement
.IsHTMLElement(nsGkAtoms::input
)) {
684 MOZ_ASSERT(aElement
.IsHTMLElement(nsGkAtoms::img
));
685 return aElement
.HasNonEmptyAttr(nsGkAtoms::alt
);
688 // Check if we want to use an image frame or just let the frame constructor make
689 // us into an inline.
691 bool nsImageFrame::ShouldCreateImageFrameFor(const Element
& aElement
,
692 ComputedStyle
& aStyle
) {
693 if (ImageOk(aElement
.State())) {
694 // Image is fine or loading; do the image frame thing
698 if (aStyle
.StyleUIReset()->mMozForceBrokenImageIcon
) {
702 // if our "do not show placeholders" pref is set, skip the icon
703 if (gIconLoad
&& gIconLoad
->mPrefForceInlineAltText
) {
707 if (!HasAltText(aElement
)) {
711 if (aElement
.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks
) {
712 // FIXME(emilio): We definitely don't reframe when this changes...
713 return HaveSpecifiedSize(aStyle
.StylePosition());
719 void nsImageFrame::Notify(imgIRequest
* aRequest
, int32_t aType
,
720 const nsIntRect
* aRect
) {
721 if (aType
== imgINotificationObserver::SIZE_AVAILABLE
) {
722 nsCOMPtr
<imgIContainer
> image
;
723 aRequest
->GetImage(getter_AddRefs(image
));
724 return OnSizeAvailable(aRequest
, image
);
727 if (aType
== imgINotificationObserver::FRAME_UPDATE
) {
728 return OnFrameUpdate(aRequest
, aRect
);
731 if (aType
== imgINotificationObserver::FRAME_COMPLETE
) {
732 mFirstFrameComplete
= true;
735 if (aType
== imgINotificationObserver::IS_ANIMATED
&&
736 mKind
!= Kind::ImageElement
) {
737 nsLayoutUtils::RegisterImageRequest(PresContext(), mContentURLRequest
,
738 &mContentURLRequestRegistered
);
741 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
743 aRequest
->GetImageStatus(&imgStatus
);
745 imgStatus
& imgIRequest::STATUS_ERROR
? NS_ERROR_FAILURE
: NS_OK
;
746 return OnLoadComplete(aRequest
, status
);
750 void nsImageFrame::OnSizeAvailable(imgIRequest
* aRequest
,
751 imgIContainer
* aImage
) {
756 /* Get requested animation policy from the pres context:
761 aImage
->SetAnimationMode(PresContext()->ImageAnimationMode());
763 if (IsPendingLoad(aRequest
)) {
768 UpdateImage(aRequest
, aImage
);
771 void nsImageFrame::UpdateImage(imgIRequest
* aRequest
, imgIContainer
* aImage
) {
772 MOZ_ASSERT(aRequest
);
773 if (SizeIsAvailable(aRequest
)) {
774 // This is valid and for the current request, so update our stored image
775 // container, orienting according to our style.
776 mImage
= nsLayoutUtils::OrientImage(aImage
,
777 StyleVisibility()->mImageOrientation
);
780 // We no longer have a valid image, so release our stored image container.
781 mImage
= mPrevImage
= nullptr;
783 // NOTE(emilio): Intentionally using `|` instead of `||` to avoid
785 bool intrinsicSizeChanged
= UpdateIntrinsicSize() | UpdateIntrinsicRatio();
786 if (!GotInitialReflow()) {
790 // We're going to need to repaint now either way.
793 if (intrinsicSizeChanged
) {
794 // Now we need to reflow if we have an unconstrained size and have
795 // already gotten the initial reflow.
796 if (!(mState
& IMAGE_SIZECONSTRAINED
)) {
798 if (nsAccessibilityService
* accService
= GetAccService()) {
799 accService
->NotifyOfImageSizeAvailable(PresShell(), mContent
);
802 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
804 } else if (PresShell()->IsActive()) {
805 // We've already gotten the initial reflow, and our size hasn't changed,
806 // so we're ready to request a decode.
807 MaybeDecodeForPredictedSize();
812 void nsImageFrame::OnFrameUpdate(imgIRequest
* aRequest
,
813 const nsIntRect
* aRect
) {
814 if (NS_WARN_IF(!aRect
)) {
818 if (!GotInitialReflow()) {
819 // Don't bother to do anything; we have a reflow coming up!
823 if (mFirstFrameComplete
&& !StyleVisibility()->IsVisible()) {
827 if (IsPendingLoad(aRequest
)) {
832 nsIntRect layerInvalidRect
=
833 mImage
? mImage
->GetImageSpaceInvalidationRect(*aRect
) : *aRect
;
835 if (layerInvalidRect
.IsEqualInterior(GetMaxSizedIntRect())) {
836 // Invalidate our entire area.
837 InvalidateSelf(nullptr, nullptr);
841 nsRect frameInvalidRect
= SourceRectToDest(layerInvalidRect
);
842 InvalidateSelf(&layerInvalidRect
, &frameInvalidRect
);
845 void nsImageFrame::InvalidateSelf(const nsIntRect
* aLayerInvalidRect
,
846 const nsRect
* aFrameInvalidRect
) {
847 // Check if WebRender has interacted with this frame. If it has
848 // we need to let it know that things have changed.
849 const auto type
= DisplayItemType::TYPE_IMAGE
;
850 const auto producerId
=
851 mImage
? mImage
->GetProducerId() : kContainerProducerID_Invalid
;
852 if (WebRenderUserData::ProcessInvalidateForImage(this, type
, producerId
)) {
856 InvalidateLayer(type
, aLayerInvalidRect
, aFrameInvalidRect
);
858 if (!mFirstFrameComplete
) {
859 InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK
, aLayerInvalidRect
,
864 void nsImageFrame::OnLoadComplete(imgIRequest
* aRequest
, nsresult aStatus
) {
865 NotifyNewCurrentRequest(aRequest
, aStatus
);
868 void nsImageFrame::ResponsiveContentDensityChanged() {
869 if (!GotInitialReflow()) {
877 if (!UpdateIntrinsicSize() && !UpdateIntrinsicRatio()) {
881 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
885 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest
* aRequest
,
887 nsCOMPtr
<imgIContainer
> image
;
888 aRequest
->GetImage(getter_AddRefs(image
));
889 NS_ASSERTION(image
|| NS_FAILED(aStatus
),
890 "Successful load with no container?");
891 UpdateImage(aRequest
, image
);
894 void nsImageFrame::MaybeDecodeForPredictedSize() {
895 // Check that we're ready to decode.
897 return; // Nothing to do yet.
900 if (mComputedSize
.IsEmpty()) {
901 return; // We won't draw anything, so no point in decoding.
904 if (GetVisibility() != Visibility::ApproximatelyVisible
) {
905 return; // We're not visible, so don't decode.
908 // OK, we're ready to decode. Compute the scale to the screen...
909 mozilla::PresShell
* presShell
= PresContext()->PresShell();
910 LayoutDeviceToScreenScale2D
resolutionToScreen(
911 presShell
->GetCumulativeResolution() *
912 nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this));
914 // ...and this frame's content box...
915 const nsPoint offset
=
916 GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
917 const nsRect frameContentBox
= GetInnerArea() + offset
;
919 // ...and our predicted dest rect...
920 const int32_t factor
= PresContext()->AppUnitsPerDevPixel();
921 const LayoutDeviceRect destRect
= LayoutDeviceRect::FromAppUnits(
922 PredictedDestRect(frameContentBox
), factor
);
924 // ...and use them to compute our predicted size in screen pixels.
925 const ScreenSize predictedScreenSize
= destRect
.Size() * resolutionToScreen
;
926 const ScreenIntSize predictedScreenIntSize
=
927 RoundedToInt(predictedScreenSize
);
928 if (predictedScreenIntSize
.IsEmpty()) {
932 // Determine the optimal image size to use.
933 uint32_t flags
= imgIContainer::FLAG_HIGH_QUALITY_SCALING
|
934 imgIContainer::FLAG_ASYNC_NOTIFY
;
935 SamplingFilter samplingFilter
=
936 nsLayoutUtils::GetSamplingFilterForFrame(this);
937 gfxSize gfxPredictedScreenSize
=
938 gfxSize(predictedScreenIntSize
.width
, predictedScreenIntSize
.height
);
939 nsIntSize predictedImageSize
= mImage
->OptimalImageSizeForDest(
940 gfxPredictedScreenSize
, imgIContainer::FRAME_CURRENT
, samplingFilter
,
944 mImage
->RequestDecodeForSize(predictedImageSize
, flags
);
947 nsRect
nsImageFrame::PredictedDestRect(const nsRect
& aFrameContentBox
) {
948 // Note: To get the "dest rect", we have to provide the "constraint rect"
949 // (which is the content-box, with the effects of fragmentation undone).
950 nsRect
constraintRect(aFrameContentBox
.TopLeft(), mComputedSize
);
951 constraintRect
.y
-= GetContinuationOffset();
953 return nsLayoutUtils::ComputeObjectDestRect(constraintRect
, mIntrinsicSize
,
954 mIntrinsicRatio
, StylePosition());
957 void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
958 if (StyleDisplay()->IsContainSize()) {
959 // If we have 'contain:size', then our intrinsic size and ratio are 0,0
960 // regardless of what our underlying image may think.
961 mIntrinsicSize
= IntrinsicSize(0, 0);
962 mIntrinsicRatio
= AspectRatio();
966 // If mIntrinsicSize.width and height are 0, then we need to update from the
968 if (mIntrinsicSize
!= IntrinsicSize(0, 0)) {
972 UpdateIntrinsicSize();
973 UpdateIntrinsicRatio();
976 nsIFrame::SizeComputationResult
nsImageFrame::ComputeSize(
977 gfxContext
* aRenderingContext
, WritingMode aWM
, const LogicalSize
& aCBSize
,
978 nscoord aAvailableISize
, const LogicalSize
& aMargin
,
979 const LogicalSize
& aBorderPadding
, const StyleSizeOverrides
& aSizeOverrides
,
980 ComputeSizeFlags aFlags
) {
981 EnsureIntrinsicSizeAndRatio();
982 return {ComputeSizeWithIntrinsicDimensions(
983 aRenderingContext
, aWM
, mIntrinsicSize
, GetAspectRatio(), aCBSize
,
984 aMargin
, aBorderPadding
, aSizeOverrides
, aFlags
),
985 AspectRatioUsage::None
};
988 // XXXdholbert This function's clients should probably just be calling
989 // GetContentRectRelativeToSelf() directly.
990 nsRect
nsImageFrame::GetInnerArea() const {
991 return GetContentRectRelativeToSelf();
994 Element
* nsImageFrame::GetMapElement() const {
995 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
996 return imageLoader
? static_cast<nsImageLoadingContent
*>(imageLoader
.get())
1001 // get the offset into the content area of the image where aImg starts if it is
1003 nscoord
nsImageFrame::GetContinuationOffset() const {
1005 for (nsIFrame
* f
= GetPrevInFlow(); f
; f
= f
->GetPrevInFlow()) {
1006 offset
+= f
->GetContentRect().height
;
1008 NS_ASSERTION(offset
>= 0, "bogus GetContentRect");
1012 nscoord
nsImageFrame::GetMinISize(gfxContext
* aRenderingContext
) {
1013 // XXX The caller doesn't account for constraints of the block-size,
1014 // min-block-size, and max-block-size properties.
1015 DebugOnly
<nscoord
> result
;
1016 DISPLAY_MIN_INLINE_SIZE(this, result
);
1017 EnsureIntrinsicSizeAndRatio();
1018 const auto& iSize
= GetWritingMode().IsVertical() ? mIntrinsicSize
.height
1019 : mIntrinsicSize
.width
;
1020 return iSize
.valueOr(0);
1023 nscoord
nsImageFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
1024 // XXX The caller doesn't account for constraints of the block-size,
1025 // min-block-size, and max-block-size properties.
1026 DebugOnly
<nscoord
> result
;
1027 DISPLAY_PREF_INLINE_SIZE(this, result
);
1028 EnsureIntrinsicSizeAndRatio();
1029 const auto& iSize
= GetWritingMode().IsVertical() ? mIntrinsicSize
.height
1030 : mIntrinsicSize
.width
;
1031 // convert from normal twips to scaled twips (printing...)
1032 return iSize
.valueOr(0);
1035 void nsImageFrame::Reflow(nsPresContext
* aPresContext
, ReflowOutput
& aMetrics
,
1036 const ReflowInput
& aReflowInput
,
1037 nsReflowStatus
& aStatus
) {
1039 DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
1040 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aMetrics
, aStatus
);
1041 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
1043 NS_FRAME_TRACE_CALLS
,
1044 ("enter nsImageFrame::Reflow: availSize=%d,%d",
1045 aReflowInput
.AvailableWidth(), aReflowInput
.AvailableHeight()));
1047 MOZ_ASSERT(mState
& NS_FRAME_IN_REFLOW
, "frame is not in reflow");
1049 // see if we have a frozen size (i.e. a fixed width and height)
1050 if (!SizeDependsOnIntrinsicSize(aReflowInput
)) {
1051 AddStateBits(IMAGE_SIZECONSTRAINED
);
1053 RemoveStateBits(IMAGE_SIZECONSTRAINED
);
1057 nsSize(aReflowInput
.ComputedWidth(), aReflowInput
.ComputedHeight());
1059 aMetrics
.Width() = mComputedSize
.width
;
1060 aMetrics
.Height() = mComputedSize
.height
;
1062 // add borders and padding
1063 aMetrics
.Width() += aReflowInput
.ComputedPhysicalBorderPadding().LeftRight();
1064 aMetrics
.Height() += aReflowInput
.ComputedPhysicalBorderPadding().TopBottom();
1066 if (GetPrevInFlow()) {
1067 aMetrics
.Width() = GetPrevInFlow()->GetSize().width
;
1068 nscoord y
= GetContinuationOffset();
1069 aMetrics
.Height() -= y
+ aReflowInput
.ComputedPhysicalBorderPadding().top
;
1070 aMetrics
.Height() = std::max(0, aMetrics
.Height());
1073 // we have to split images if we are:
1074 // in Paginated mode, we need to have a constrained height, and have a height
1075 // larger than our available height
1076 uint32_t loadStatus
= imgIRequest::STATUS_NONE
;
1077 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
1078 currentRequest
->GetImageStatus(&loadStatus
);
1081 if (aPresContext
->IsPaginated() &&
1082 ((loadStatus
& imgIRequest::STATUS_SIZE_AVAILABLE
) ||
1083 (mState
& IMAGE_SIZECONSTRAINED
)) &&
1084 NS_UNCONSTRAINEDSIZE
!= aReflowInput
.AvailableHeight() &&
1085 aMetrics
.Height() > aReflowInput
.AvailableHeight()) {
1086 // our desired height was greater than 0, so to avoid infinite
1087 // splitting, use 1 pixel as the min
1088 aMetrics
.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1),
1089 aReflowInput
.AvailableHeight());
1090 aStatus
.SetIncomplete();
1093 aMetrics
.SetOverflowAreasToDesiredBounds();
1095 mKind
!= Kind::ImageElement
|| ImageOk(mContent
->AsElement()->State());
1097 // Determine if the size is available
1098 bool haveSize
= false;
1099 if (loadStatus
& imgIRequest::STATUS_SIZE_AVAILABLE
) {
1103 if (!imageOK
|| !haveSize
) {
1104 nsRect
altFeedbackSize(
1106 nsPresContext::CSSPixelsToAppUnits(
1107 ICON_SIZE
+ 2 * (ICON_PADDING
+ ALT_BORDER_WIDTH
)),
1108 nsPresContext::CSSPixelsToAppUnits(
1109 ICON_SIZE
+ 2 * (ICON_PADDING
+ ALT_BORDER_WIDTH
)));
1110 // We include the altFeedbackSize in our ink overflow, but not in our
1111 // scrollable overflow, since it doesn't really need to be scrolled to
1112 // outside the image.
1113 nsRect
& inkOverflow
= aMetrics
.InkOverflow();
1114 inkOverflow
.UnionRect(inkOverflow
, altFeedbackSize
);
1115 } else if (PresShell()->IsActive()) {
1116 // We've just reflowed and we should have an accurate size, so we're ready
1117 // to request a decode.
1118 MaybeDecodeForPredictedSize();
1120 FinishAndStoreOverflow(&aMetrics
, aReflowInput
.mStyleDisplay
);
1122 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW
) && !mReflowCallbackPosted
) {
1123 mReflowCallbackPosted
= true;
1124 PresShell()->PostReflowCallback(this);
1127 NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS
, ("exit nsImageFrame::Reflow: size=%d,%d",
1128 aMetrics
.Width(), aMetrics
.Height()));
1129 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowInput
, aMetrics
);
1132 bool nsImageFrame::ReflowFinished() {
1133 mReflowCallbackPosted
= false;
1135 // XXX(seth): We don't need this. The purpose of updating visibility
1136 // synchronously is to ensure that animated images start animating
1137 // immediately. In the short term, however,
1138 // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
1139 // animations start as soon as the image is painted for the first time, and in
1140 // the long term we want to update visibility information from the display
1141 // list whenever we paint, so we don't actually need to do this. However, to
1142 // avoid behavior changes during the transition from the old image visibility
1143 // code, we'll leave it in for now.
1144 UpdateVisibilitySynchronously();
1149 void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted
= false; }
1151 // Computes the width of the specified string. aMaxWidth specifies the maximum
1152 // width available. Once this limit is reached no more characters are measured.
1153 // The number of characters that fit within the maximum width are returned in
1154 // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
1155 // into the rendering context before this is called (for performance). MMP
1156 nscoord
nsImageFrame::MeasureString(const char16_t
* aString
, int32_t aLength
,
1157 nscoord aMaxWidth
, uint32_t& aMaxFit
,
1158 gfxContext
& aContext
,
1159 nsFontMetrics
& aFontMetrics
) {
1160 nscoord totalWidth
= 0;
1161 aFontMetrics
.SetTextRunRTL(false);
1162 nscoord spaceWidth
= aFontMetrics
.SpaceWidth();
1165 while (aLength
> 0) {
1166 // Find the next place we can line break
1167 uint32_t len
= aLength
;
1168 bool trailingSpace
= false;
1169 for (int32_t i
= 0; i
< aLength
; i
++) {
1170 if (dom::IsSpaceCharacter(aString
[i
]) && (i
> 0)) {
1171 len
= i
; // don't include the space when measuring
1172 trailingSpace
= true;
1177 // Measure this chunk of text, and see if it fits
1178 nscoord width
= nsLayoutUtils::AppUnitWidthOfStringBidi(
1179 aString
, len
, this, aFontMetrics
, aContext
);
1180 bool fits
= (totalWidth
+ width
) <= aMaxWidth
;
1182 // If it fits on the line, or it's the first word we've processed then
1184 if (fits
|| (0 == totalWidth
)) {
1186 totalWidth
+= width
;
1188 // If there's a trailing space then see if it fits as well
1189 if (trailingSpace
) {
1190 if ((totalWidth
+ spaceWidth
) <= aMaxWidth
) {
1191 totalWidth
+= spaceWidth
;
1193 // Space won't fit. Leave it at the end but don't include it in
1213 // Formats the alt-text to fit within the specified rectangle. Breaks lines
1214 // between words if a word would extend past the edge of the rectangle
1215 void nsImageFrame::DisplayAltText(nsPresContext
* aPresContext
,
1216 gfxContext
& aRenderingContext
,
1217 const nsString
& aAltText
,
1218 const nsRect
& aRect
) {
1219 // Set font and color
1220 aRenderingContext
.SetColor(
1221 sRGBColor::FromABGR(StyleText()->mColor
.ToColor()));
1222 RefPtr
<nsFontMetrics
> fm
=
1223 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
1225 // Format the text to display within the formatting rect
1227 nscoord maxAscent
= fm
->MaxAscent();
1228 nscoord maxDescent
= fm
->MaxDescent();
1229 nscoord lineHeight
= fm
->MaxHeight(); // line-relative, so an x-coordinate
1230 // length if writing mode is vertical
1232 WritingMode wm
= GetWritingMode();
1233 bool isVertical
= wm
.IsVertical();
1235 fm
->SetVertical(isVertical
);
1236 fm
->SetTextOrientation(StyleVisibility()->mTextOrientation
);
1238 // XXX It would be nice if there was a way to have the font metrics tell
1239 // use where to break the text given a maximum width. At a minimum we need
1240 // to be able to get the break character...
1241 const char16_t
* str
= aAltText
.get();
1242 int32_t strLen
= aAltText
.Length();
1243 nsPoint pt
= wm
.IsVerticalRL() ? aRect
.TopRight() - nsPoint(lineHeight
, 0)
1245 nscoord iSize
= isVertical
? aRect
.height
: aRect
.width
;
1247 if (!aPresContext
->BidiEnabled() && HasRTLChars(aAltText
)) {
1248 aPresContext
->SetBidiEnabled();
1251 // Always show the first line, even if we have to clip it below
1252 bool firstLine
= true;
1253 while (strLen
> 0) {
1255 // If we've run out of space, break out of the loop
1256 if ((!isVertical
&& (pt
.y
+ maxDescent
) >= aRect
.YMost()) ||
1257 (wm
.IsVerticalRL() && (pt
.x
+ maxDescent
< aRect
.x
)) ||
1258 (wm
.IsVerticalLR() && (pt
.x
+ maxDescent
>= aRect
.XMost()))) {
1263 // Determine how much of the text to display on this line
1264 uint32_t maxFit
; // number of characters that fit
1266 MeasureString(str
, strLen
, iSize
, maxFit
, aRenderingContext
, *fm
);
1269 nsresult rv
= NS_ERROR_FAILURE
;
1271 if (aPresContext
->BidiEnabled()) {
1272 nsBidiDirection dir
;
1276 x
= pt
.x
+ maxDescent
;
1277 if (wm
.IsBidiLTR()) {
1281 y
= aRect
.YMost() - strWidth
;
1285 y
= pt
.y
+ maxAscent
;
1286 if (wm
.IsBidiLTR()) {
1290 x
= aRect
.XMost() - strWidth
;
1295 rv
= nsBidiPresUtils::RenderText(
1296 str
, maxFit
, dir
, aPresContext
, aRenderingContext
,
1297 aRenderingContext
.GetDrawTarget(), *fm
, x
, y
);
1299 if (NS_FAILED(rv
)) {
1300 nsLayoutUtils::DrawUniDirString(str
, maxFit
,
1302 ? nsPoint(pt
.x
+ maxDescent
, pt
.y
)
1303 : nsPoint(pt
.x
, pt
.y
+ maxAscent
),
1304 *fm
, aRenderingContext
);
1307 // Move to the next line
1310 if (wm
.IsVerticalRL()) {
1312 } else if (wm
.IsVerticalLR()) {
1322 struct nsRecessedBorder
: public nsStyleBorder
{
1323 nsRecessedBorder(nscoord aBorderWidth
, nsPresContext
* aPresContext
)
1324 : nsStyleBorder(*aPresContext
->Document()) {
1325 for (const auto side
: mozilla::AllPhysicalSides()) {
1326 BorderColorFor(side
) = StyleColor::Black();
1327 mBorder
.Side(side
) = aBorderWidth
;
1328 // Note: use SetBorderStyle here because we want to affect
1330 SetBorderStyle(side
, StyleBorderStyle::Inset
);
1335 class nsDisplayAltFeedback final
: public nsPaintedDisplayItem
{
1337 nsDisplayAltFeedback(nsDisplayListBuilder
* aBuilder
, nsIFrame
* aFrame
)
1338 : nsPaintedDisplayItem(aBuilder
, aFrame
) {}
1340 nsDisplayItemGeometry
* AllocateGeometry(
1341 nsDisplayListBuilder
* aBuilder
) final
{
1342 return new nsDisplayItemGenericImageGeometry(this, aBuilder
);
1345 void ComputeInvalidationRegion(nsDisplayListBuilder
* aBuilder
,
1346 const nsDisplayItemGeometry
* aGeometry
,
1347 nsRegion
* aInvalidRegion
) const final
{
1349 static_cast<const nsDisplayItemGenericImageGeometry
*>(aGeometry
);
1351 if (aBuilder
->ShouldSyncDecodeImages() &&
1352 geometry
->ShouldInvalidateToSyncDecodeImages()) {
1354 aInvalidRegion
->Or(*aInvalidRegion
, GetBounds(aBuilder
, &snap
));
1357 nsDisplayItem::ComputeInvalidationRegion(aBuilder
, aGeometry
,
1361 nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) const final
{
1363 return mFrame
->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
1366 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) final
{
1367 // Always sync decode, because these icons are UI, and since they're not
1368 // discardable we'll pay the price of sync decoding at most once.
1369 uint32_t flags
= imgIContainer::FLAG_SYNC_DECODE
;
1371 nsImageFrame
* f
= static_cast<nsImageFrame
*>(mFrame
);
1372 ImgDrawResult result
=
1373 f
->DisplayAltFeedback(*aCtx
, GetPaintRect(), ToReferenceFrame(), flags
);
1375 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result
);
1378 bool CreateWebRenderCommands(
1379 mozilla::wr::DisplayListBuilder
& aBuilder
,
1380 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1381 const StackingContextHelper
& aSc
,
1382 mozilla::layers::RenderRootStateManager
* aManager
,
1383 nsDisplayListBuilder
* aDisplayListBuilder
) final
{
1384 uint32_t flags
= imgIContainer::FLAG_ASYNC_NOTIFY
;
1385 nsImageFrame
* f
= static_cast<nsImageFrame
*>(mFrame
);
1386 ImgDrawResult result
= f
->DisplayAltFeedbackWithoutLayer(
1387 this, aBuilder
, aResources
, aSc
, aManager
, aDisplayListBuilder
,
1388 ToReferenceFrame(), flags
);
1390 return result
== ImgDrawResult::SUCCESS
;
1393 NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK
)
1396 ImgDrawResult
nsImageFrame::DisplayAltFeedback(gfxContext
& aRenderingContext
,
1397 const nsRect
& aDirtyRect
,
1398 nsPoint aPt
, uint32_t aFlags
) {
1399 // We should definitely have a gIconLoad here.
1400 MOZ_ASSERT(gIconLoad
, "How did we succeed in Init then?");
1402 // Whether we draw the broken or loading icon.
1404 mKind
!= Kind::ImageElement
|| ImageOk(mContent
->AsElement()->State());
1406 // Calculate the inner area
1407 nsRect inner
= GetInnerArea() + aPt
;
1409 // Display a recessed one pixel border
1410 nscoord borderEdgeWidth
=
1411 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH
);
1413 // if inner area is empty, then make it big enough for at least the icon
1414 if (inner
.IsEmpty()) {
1415 inner
.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
1416 ICON_SIZE
+ ICON_PADDING
+ ALT_BORDER_WIDTH
)),
1417 2 * (nsPresContext::CSSPixelsToAppUnits(
1418 ICON_SIZE
+ ICON_PADDING
+ ALT_BORDER_WIDTH
)));
1421 // Make sure we have enough room to actually render the border within
1423 if ((inner
.width
< 2 * borderEdgeWidth
) ||
1424 (inner
.height
< 2 * borderEdgeWidth
)) {
1425 return ImgDrawResult::SUCCESS
;
1429 if (!isLoading
|| gIconLoad
->mPrefShowLoadingPlaceholder
) {
1430 nsRecessedBorder
recessedBorder(borderEdgeWidth
, PresContext());
1432 // Assert that we're not drawing a border-image here; if we were, we
1433 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
1435 MOZ_ASSERT(recessedBorder
.mBorderImageSource
.IsNone());
1437 Unused
<< nsCSSRendering::PaintBorderWithStyleBorder(
1438 PresContext(), aRenderingContext
, this, inner
, inner
, recessedBorder
,
1439 mComputedStyle
, PaintBorderFlags::SyncDecodeImages
);
1442 // Adjust the inner rect to account for the one pixel recessed border,
1443 // and a six pixel padding on each edge
1445 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING
+ ALT_BORDER_WIDTH
),
1446 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING
+ ALT_BORDER_WIDTH
));
1447 if (inner
.IsEmpty()) {
1448 return ImgDrawResult::SUCCESS
;
1451 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
1453 // Clip so we don't render outside the inner rect
1454 aRenderingContext
.Save();
1455 aRenderingContext
.Clip(NSRectToSnappedRect(
1456 inner
, PresContext()->AppUnitsPerDevPixel(), *drawTarget
));
1458 ImgDrawResult result
= ImgDrawResult::NOT_READY
;
1460 // Check if we should display image placeholders
1461 if (!ShouldShowBrokenImageIcon() || !gIconLoad
->mPrefShowPlaceholders
||
1462 (isLoading
&& !gIconLoad
->mPrefShowLoadingPlaceholder
)) {
1463 result
= ImgDrawResult::SUCCESS
;
1465 nscoord size
= nsPresContext::CSSPixelsToAppUnits(ICON_SIZE
);
1467 imgIRequest
* request
= isLoading
? nsImageFrame::gIconLoad
->mLoadingImage
1468 : nsImageFrame::gIconLoad
->mBrokenImage
;
1470 // If we weren't previously displaying an icon, register ourselves
1471 // as an observer for load and animation updates and flag that we're
1473 if (request
&& !mDisplayingIcon
) {
1474 gIconLoad
->AddIconObserver(this);
1475 mDisplayingIcon
= true;
1478 WritingMode wm
= GetWritingMode();
1479 bool flushRight
= wm
.IsPhysicalRTL();
1481 // If the icon in question is loaded, draw it.
1482 uint32_t imageStatus
= 0;
1483 if (request
) request
->GetImageStatus(&imageStatus
);
1484 if (imageStatus
& imgIRequest::STATUS_LOAD_COMPLETE
&&
1485 !(imageStatus
& imgIRequest::STATUS_ERROR
)) {
1486 nsCOMPtr
<imgIContainer
> imgCon
;
1487 request
->GetImage(getter_AddRefs(imgCon
));
1488 MOZ_ASSERT(imgCon
, "Load complete, but no image container?");
1489 nsRect
dest(flushRight
? inner
.XMost() - size
: inner
.x
, inner
.y
, size
,
1491 result
= nsLayoutUtils::DrawSingleImage(
1492 aRenderingContext
, PresContext(), imgCon
,
1493 nsLayoutUtils::GetSamplingFilterForFrame(this), dest
, aDirtyRect
,
1494 /* no SVGImageContext */ Nothing(), aFlags
);
1497 // If we could not draw the icon, just draw some graffiti in the mean time.
1498 if (result
== ImgDrawResult::NOT_READY
) {
1499 ColorPattern
color(ToDeviceColor(sRGBColor(1.f
, 0.f
, 0.f
, 1.f
)));
1501 nscoord iconXPos
= flushRight
? inner
.XMost() - size
: inner
.x
;
1504 nsRect
rect(iconXPos
, inner
.y
, size
, size
);
1505 Rect devPxRect
= ToRect(nsLayoutUtils::RectToGfxRect(
1506 rect
, PresContext()->AppUnitsPerDevPixel()));
1507 drawTarget
->StrokeRect(devPxRect
, color
);
1509 // filled circle in bottom right quadrant of stroked rect:
1510 nscoord twoPX
= nsPresContext::CSSPixelsToAppUnits(2);
1511 rect
= nsRect(iconXPos
+ size
/ 2, inner
.y
+ size
/ 2, size
/ 2 - twoPX
,
1513 devPxRect
= ToRect(nsLayoutUtils::RectToGfxRect(
1514 rect
, PresContext()->AppUnitsPerDevPixel()));
1515 RefPtr
<PathBuilder
> builder
= drawTarget
->CreatePathBuilder();
1516 AppendEllipseToPath(builder
, devPxRect
.Center(), devPxRect
.Size());
1517 RefPtr
<Path
> ellipse
= builder
->Finish();
1518 drawTarget
->Fill(ellipse
, color
);
1521 // Reduce the inner rect by the width of the icon, and leave an
1522 // additional ICON_PADDING pixels for padding
1523 int32_t paddedIconSize
=
1524 nsPresContext::CSSPixelsToAppUnits(ICON_SIZE
+ ICON_PADDING
);
1525 if (wm
.IsVertical()) {
1526 inner
.y
+= paddedIconSize
;
1527 inner
.height
-= paddedIconSize
;
1530 inner
.x
+= paddedIconSize
;
1532 inner
.width
-= paddedIconSize
;
1536 // If there's still room, display the alt-text
1537 if (!inner
.IsEmpty()) {
1538 nsAutoString altText
;
1539 nsCSSFrameConstructor::GetAlternateTextFor(
1540 mContent
->AsElement(), mContent
->NodeInfo()->NameAtom(), altText
);
1541 DisplayAltText(PresContext(), aRenderingContext
, altText
, inner
);
1544 aRenderingContext
.Restore();
1549 ImgDrawResult
nsImageFrame::DisplayAltFeedbackWithoutLayer(
1550 nsDisplayItem
* aItem
, mozilla::wr::DisplayListBuilder
& aBuilder
,
1551 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1552 const StackingContextHelper
& aSc
,
1553 mozilla::layers::RenderRootStateManager
* aManager
,
1554 nsDisplayListBuilder
* aDisplayListBuilder
, nsPoint aPt
, uint32_t aFlags
) {
1555 // We should definitely have a gIconLoad here.
1556 MOZ_ASSERT(gIconLoad
, "How did we succeed in Init then?");
1558 // Whether we draw the broken or loading icon.
1560 mKind
!= Kind::ImageElement
|| ImageOk(mContent
->AsElement()->State());
1562 // Calculate the inner area
1563 nsRect inner
= GetInnerArea() + aPt
;
1565 // Display a recessed one pixel border
1566 nscoord borderEdgeWidth
=
1567 nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH
);
1569 // if inner area is empty, then make it big enough for at least the icon
1570 if (inner
.IsEmpty()) {
1571 inner
.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
1572 ICON_SIZE
+ ICON_PADDING
+ ALT_BORDER_WIDTH
)),
1573 2 * (nsPresContext::CSSPixelsToAppUnits(
1574 ICON_SIZE
+ ICON_PADDING
+ ALT_BORDER_WIDTH
)));
1577 // Make sure we have enough room to actually render the border within
1579 if ((inner
.width
< 2 * borderEdgeWidth
) ||
1580 (inner
.height
< 2 * borderEdgeWidth
)) {
1581 return ImgDrawResult::SUCCESS
;
1584 // If the TextDrawTarget requires fallback we need to rollback everything we
1585 // may have added to the display list, but we don't find that out until the
1587 bool textDrawResult
= true;
1588 class AutoSaveRestore
{
1590 explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder
& aBuilder
,
1591 bool& aTextDrawResult
)
1592 : mBuilder(aBuilder
), mTextDrawResult(aTextDrawResult
) {
1595 ~AutoSaveRestore() {
1596 // If we have to use fallback for the text restore the builder and remove
1597 // anything else we added to the display list, we need to use fallback.
1598 if (mTextDrawResult
) {
1599 mBuilder
.ClearSave();
1606 mozilla::wr::DisplayListBuilder
& mBuilder
;
1607 bool& mTextDrawResult
;
1610 AutoSaveRestore
autoSaveRestore(aBuilder
, textDrawResult
);
1613 if (!isLoading
|| gIconLoad
->mPrefShowLoadingPlaceholder
) {
1614 nsRecessedBorder
recessedBorder(borderEdgeWidth
, PresContext());
1615 // Assert that we're not drawing a border-image here; if we were, we
1616 // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
1618 MOZ_ASSERT(recessedBorder
.mBorderImageSource
.IsNone());
1620 nsRect rect
= nsRect(aPt
, GetSize());
1621 Unused
<< nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
1622 aItem
, this, rect
, aBuilder
, aResources
, aSc
, aManager
,
1623 aDisplayListBuilder
, recessedBorder
);
1626 // Adjust the inner rect to account for the one pixel recessed border,
1627 // and a six pixel padding on each edge
1629 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING
+ ALT_BORDER_WIDTH
),
1630 nsPresContext::CSSPixelsToAppUnits(ICON_PADDING
+ ALT_BORDER_WIDTH
));
1631 if (inner
.IsEmpty()) {
1632 return ImgDrawResult::SUCCESS
;
1635 // Clip to this rect so we don't render outside the inner rect
1636 LayoutDeviceRect bounds
= LayoutDeviceRect::FromAppUnits(
1637 inner
, PresContext()->AppUnitsPerDevPixel());
1638 auto wrBounds
= wr::ToLayoutRect(bounds
);
1640 // Check if we should display image placeholders
1641 if (ShouldShowBrokenImageIcon() && gIconLoad
->mPrefShowPlaceholders
&&
1642 (!isLoading
|| gIconLoad
->mPrefShowLoadingPlaceholder
)) {
1643 ImgDrawResult result
= ImgDrawResult::NOT_READY
;
1644 nscoord size
= nsPresContext::CSSPixelsToAppUnits(ICON_SIZE
);
1645 imgIRequest
* request
= isLoading
? nsImageFrame::gIconLoad
->mLoadingImage
1646 : nsImageFrame::gIconLoad
->mBrokenImage
;
1648 // If we weren't previously displaying an icon, register ourselves
1649 // as an observer for load and animation updates and flag that we're
1651 if (request
&& !mDisplayingIcon
) {
1652 gIconLoad
->AddIconObserver(this);
1653 mDisplayingIcon
= true;
1656 WritingMode wm
= GetWritingMode();
1657 const bool flushRight
= wm
.IsPhysicalRTL();
1659 // If the icon in question is loaded, draw it.
1660 uint32_t imageStatus
= 0;
1661 if (request
) request
->GetImageStatus(&imageStatus
);
1662 if (imageStatus
& imgIRequest::STATUS_LOAD_COMPLETE
&&
1663 !(imageStatus
& imgIRequest::STATUS_ERROR
)) {
1664 nsCOMPtr
<imgIContainer
> imgCon
;
1665 request
->GetImage(getter_AddRefs(imgCon
));
1666 MOZ_ASSERT(imgCon
, "Load complete, but no image container?");
1668 nsRect
dest(flushRight
? inner
.XMost() - size
: inner
.x
, inner
.y
, size
,
1671 const int32_t factor
= PresContext()->AppUnitsPerDevPixel();
1672 LayoutDeviceRect
destRect(LayoutDeviceRect::FromAppUnits(dest
, factor
));
1674 Maybe
<SVGImageContext
> svgContext
;
1675 Maybe
<ImageIntRegion
> region
;
1676 IntSize decodeSize
=
1677 nsLayoutUtils::ComputeImageContainerDrawingParameters(
1678 imgCon
, this, destRect
, destRect
, aSc
, aFlags
, svgContext
,
1680 RefPtr
<ImageContainer
> container
;
1681 result
= imgCon
->GetImageContainerAtSize(
1682 aManager
->LayerManager(), decodeSize
, svgContext
, region
, aFlags
,
1683 getter_AddRefs(container
));
1685 bool wrResult
= aManager
->CommandBuilder().PushImage(
1686 aItem
, container
, aBuilder
, aResources
, aSc
, destRect
, bounds
);
1687 result
&= wrResult
? ImgDrawResult::SUCCESS
: ImgDrawResult::NOT_READY
;
1689 // We don't use &= here because we want the result to be NOT_READY so
1690 // the next block executes.
1691 result
= ImgDrawResult::NOT_READY
;
1695 // If we could not draw the icon, just draw some graffiti in the mean time.
1696 if (result
== ImgDrawResult::NOT_READY
) {
1697 auto color
= wr::ColorF
{1.0f
, 0.0f
, 0.0f
, 1.0f
};
1698 bool isBackfaceVisible
= !aItem
->BackfaceIsHidden();
1700 nscoord iconXPos
= flushRight
? inner
.XMost() - size
: inner
.x
;
1703 nsRect
rect(iconXPos
, inner
.y
, size
, size
);
1704 auto devPxRect
= LayoutDeviceRect::FromAppUnits(
1705 rect
, PresContext()->AppUnitsPerDevPixel());
1706 auto dest
= wr::ToLayoutRect(devPxRect
);
1708 auto borderWidths
= wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
1709 wr::BorderSide side
= {color
, wr::BorderStyle::Solid
};
1710 wr::BorderSide sides
[4] = {side
, side
, side
, side
};
1711 Range
<const wr::BorderSide
> sidesRange(sides
, 4);
1712 aBuilder
.PushBorder(dest
, wrBounds
, isBackfaceVisible
, borderWidths
,
1713 sidesRange
, wr::EmptyBorderRadius());
1715 // filled circle in bottom right quadrant of stroked rect:
1716 nscoord twoPX
= nsPresContext::CSSPixelsToAppUnits(2);
1717 rect
= nsRect(iconXPos
+ size
/ 2, inner
.y
+ size
/ 2, size
/ 2 - twoPX
,
1719 devPxRect
= LayoutDeviceRect::FromAppUnits(
1720 rect
, PresContext()->AppUnitsPerDevPixel());
1721 dest
= wr::ToLayoutRect(devPxRect
);
1723 aBuilder
.PushRoundedRect(dest
, wrBounds
, isBackfaceVisible
, 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
;
1742 if (!inner
.IsEmpty()) {
1743 RefPtr
<TextDrawTarget
> textDrawer
=
1744 new TextDrawTarget(aBuilder
, aResources
, aSc
, aManager
, aItem
, inner
,
1745 /* aCallerDoesSaveRestore = */ true);
1746 RefPtr
<gfxContext
> captureCtx
= gfxContext::CreateOrNull(textDrawer
);
1748 nsAutoString altText
;
1749 nsCSSFrameConstructor::GetAlternateTextFor(
1750 mContent
->AsElement(), mContent
->NodeInfo()->NameAtom(), altText
);
1751 DisplayAltText(PresContext(), *captureCtx
.get(), altText
, inner
);
1753 textDrawer
->TerminateShadows();
1754 textDrawResult
= !textDrawer
->CheckHasUnsupportedFeatures();
1757 // Purposely ignore local DrawResult because we handled it not being success
1759 return textDrawResult
? ImgDrawResult::SUCCESS
: ImgDrawResult::NOT_READY
;
1763 static void PaintDebugImageMap(nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
,
1764 const nsRect
& aDirtyRect
, nsPoint aPt
) {
1765 nsImageFrame
* f
= static_cast<nsImageFrame
*>(aFrame
);
1766 nsRect inner
= f
->GetInnerArea() + aPt
;
1767 gfxPoint devPixelOffset
= nsLayoutUtils::PointToGfxPoint(
1768 inner
.TopLeft(), aFrame
->PresContext()->AppUnitsPerDevPixel());
1769 AutoRestoreTransform
autoRestoreTransform(aDrawTarget
);
1770 aDrawTarget
->SetTransform(
1771 aDrawTarget
->GetTransform().PreTranslate(ToPoint(devPixelOffset
)));
1772 f
->GetImageMap()->Draw(aFrame
, *aDrawTarget
,
1773 ColorPattern(ToDeviceColor(sRGBColor::OpaqueBlack())));
1777 // We want to sync-decode in this case, as otherwise we either need to flash
1778 // white while waiting to decode the new image, or paint the old image with a
1779 // different aspect-ratio, which would be bad as it'd be stretched.
1782 static bool OldImageHasDifferentRatio(const nsImageFrame
& aFrame
,
1783 imgIContainer
& aImage
,
1784 imgIContainer
* aPrevImage
) {
1785 if (!aPrevImage
|| aPrevImage
== &aImage
) {
1789 // If we don't depend on our intrinsic image size / ratio, we're good.
1791 // FIXME(emilio): There's the case of the old image being painted
1792 // intrinsically, and src and styles changing at the same time... Maybe we
1793 // should keep track of the old GetPaintRect()'s ratio and the image's ratio,
1794 // instead of checking this bit?
1795 if (aFrame
.HasAnyStateBits(IMAGE_SIZECONSTRAINED
)) {
1799 auto currentRatio
= aFrame
.GetIntrinsicRatio();
1800 // If we have an image, we need to have a current request.
1801 // Same if we had an image.
1802 const bool hasRequest
= true;
1804 auto currentRatioRecomputed
=
1805 ComputeIntrinsicRatio(&aImage
, hasRequest
, aFrame
);
1806 // If the image encounters an error after decoding the size (and we run
1807 // UpdateIntrinsicRatio) then the image will return the empty AspectRatio and
1808 // the aspect ratio we compute here will be different from what was computed
1809 // and stored before the image went into error state. It would be better to
1810 // check that the image has an error here but we need an imgIRequest for that,
1811 // not an imgIContainer. In lieu of that we check that
1812 // aImage.GetIntrinsicRatio() returns Nothing() as it does when the image is
1813 // in the error state and that the recomputed ratio is the zero ratio.
1815 (!currentRatioRecomputed
&& aImage
.GetIntrinsicRatio() == Nothing()) ||
1816 currentRatio
== currentRatioRecomputed
,
1817 "aspect-ratio got out of sync during paint? How?");
1819 auto oldRatio
= ComputeIntrinsicRatio(aPrevImage
, hasRequest
, aFrame
);
1820 return oldRatio
!= currentRatio
;
1823 void nsDisplayImage::Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) {
1825 auto* frame
= static_cast<nsImageFrame
*>(mFrame
);
1827 const bool oldImageIsDifferent
=
1828 OldImageHasDifferentRatio(*frame
, *mImage
, mPrevImage
);
1830 uint32_t flags
= imgIContainer::FLAG_NONE
;
1831 if (aBuilder
->ShouldSyncDecodeImages() || oldImageIsDifferent
) {
1832 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
1834 if (aBuilder
->UseHighQualityScaling()) {
1835 flags
|= imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
1838 ImgDrawResult result
= frame
->PaintImage(*aCtx
, ToReferenceFrame(),
1839 GetPaintRect(), mImage
, flags
);
1841 if (result
== ImgDrawResult::NOT_READY
||
1842 result
== ImgDrawResult::INCOMPLETE
||
1843 result
== ImgDrawResult::TEMPORARY_ERROR
) {
1844 // If the current image failed to paint because it's still loading or
1845 // decoding, try painting the previous image.
1847 result
= frame
->PaintImage(*aCtx
, ToReferenceFrame(), GetPaintRect(),
1852 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result
);
1855 nsDisplayItemGeometry
* nsDisplayImage::AllocateGeometry(
1856 nsDisplayListBuilder
* aBuilder
) {
1857 return new nsDisplayItemGenericImageGeometry(this, aBuilder
);
1860 void nsDisplayImage::ComputeInvalidationRegion(
1861 nsDisplayListBuilder
* aBuilder
, const nsDisplayItemGeometry
* aGeometry
,
1862 nsRegion
* aInvalidRegion
) const {
1864 static_cast<const nsDisplayItemGenericImageGeometry
*>(aGeometry
);
1866 if (aBuilder
->ShouldSyncDecodeImages() &&
1867 geometry
->ShouldInvalidateToSyncDecodeImages()) {
1869 aInvalidRegion
->Or(*aInvalidRegion
, GetBounds(aBuilder
, &snap
));
1872 nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder
, aGeometry
,
1876 already_AddRefed
<imgIContainer
> nsDisplayImage::GetImage() {
1877 nsCOMPtr
<imgIContainer
> image
= mImage
;
1878 return image
.forget();
1881 nsRect
nsDisplayImage::GetDestRect() const {
1883 const nsRect frameContentBox
= GetBounds(&snap
);
1885 nsImageFrame
* imageFrame
= static_cast<nsImageFrame
*>(mFrame
);
1886 return imageFrame
->PredictedDestRect(frameContentBox
);
1889 LayerState
nsDisplayImage::GetLayerState(
1890 nsDisplayListBuilder
* aBuilder
, LayerManager
* aManager
,
1891 const ContainerLayerParameters
& aParameters
) {
1892 if (!nsDisplayItem::ForceActiveLayers()) {
1893 bool animated
= false;
1894 if (!StaticPrefs::layout_animated_image_layers_enabled() ||
1895 mImage
->GetType() != imgIContainer::TYPE_RASTER
||
1896 NS_FAILED(mImage
->GetAnimated(&animated
)) || !animated
) {
1897 if (!aManager
->IsCompositingCheap() ||
1898 !nsLayoutUtils::GPUImageScalingEnabled()) {
1899 return LayerState::LAYER_NONE
;
1905 int32_t imageHeight
;
1906 mImage
->GetWidth(&imageWidth
);
1907 mImage
->GetHeight(&imageHeight
);
1909 NS_ASSERTION(imageWidth
!= 0 && imageHeight
!= 0, "Invalid image size!");
1911 const int32_t factor
= mFrame
->PresContext()->AppUnitsPerDevPixel();
1912 const LayoutDeviceRect destRect
=
1913 LayoutDeviceRect::FromAppUnits(GetDestRect(), factor
);
1914 const LayerRect destLayerRect
= destRect
* aParameters
.Scale();
1916 // Calculate the scaling factor for the frame.
1917 const gfxSize scale
= gfxSize(destLayerRect
.width
/ imageWidth
,
1918 destLayerRect
.height
/ imageHeight
);
1920 // If we are not scaling at all, no point in separating this into a layer.
1921 if (scale
.width
== 1.0f
&& scale
.height
== 1.0f
) {
1922 return LayerState::LAYER_NONE
;
1925 // If the target size is pretty small, no point in using a layer.
1926 if (destLayerRect
.width
* destLayerRect
.height
< 64 * 64) {
1927 return LayerState::LAYER_NONE
;
1932 if (!CanOptimizeToImageLayer(aManager
, aBuilder
)) {
1933 return LayerState::LAYER_NONE
;
1936 // Image layer doesn't support draw focus ring for image map.
1937 nsImageFrame
* f
= static_cast<nsImageFrame
*>(mFrame
);
1938 if (f
->HasImageMap()) {
1939 return LayerState::LAYER_NONE
;
1942 return LayerState::LAYER_ACTIVE
;
1945 nsRegion
nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder
* aBuilder
,
1946 bool* aSnap
) const {
1948 if (mImage
&& mImage
->WillDrawOpaqueNow()) {
1949 const nsRect frameContentBox
= GetBounds(aSnap
);
1950 return GetDestRect().Intersect(frameContentBox
);
1955 already_AddRefed
<Layer
> nsDisplayImage::BuildLayer(
1956 nsDisplayListBuilder
* aBuilder
, LayerManager
* aManager
,
1957 const ContainerLayerParameters
& aParameters
) {
1958 uint32_t flags
= imgIContainer::FLAG_ASYNC_NOTIFY
;
1959 if (aBuilder
->ShouldSyncDecodeImages()) {
1960 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
1963 RefPtr
<ImageContainer
> container
= mImage
->GetImageContainer(aManager
, flags
);
1964 if (!container
|| !container
->HasCurrentImage()) {
1968 RefPtr
<ImageLayer
> layer
= static_cast<ImageLayer
*>(
1969 aManager
->GetLayerBuilder()->GetLeafLayerFor(aBuilder
, this));
1971 layer
= aManager
->CreateImageLayer();
1972 if (!layer
) return nullptr;
1974 layer
->SetContainer(container
);
1975 ConfigureLayer(layer
, aParameters
);
1976 return layer
.forget();
1979 bool nsDisplayImage::CreateWebRenderCommands(
1980 mozilla::wr::DisplayListBuilder
& aBuilder
,
1981 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1982 const StackingContextHelper
& aSc
, RenderRootStateManager
* aManager
,
1983 nsDisplayListBuilder
* aDisplayListBuilder
) {
1988 MOZ_ASSERT(mFrame
->IsImageFrame() || mFrame
->IsImageControlFrame());
1989 // Image layer doesn't support draw focus ring for image map.
1990 auto* frame
= static_cast<nsImageFrame
*>(mFrame
);
1991 if (frame
->HasImageMap()) {
1995 const bool oldImageIsDifferent
=
1996 OldImageHasDifferentRatio(*frame
, *mImage
, mPrevImage
);
1998 uint32_t flags
= imgIContainer::FLAG_ASYNC_NOTIFY
;
1999 if (aDisplayListBuilder
->ShouldSyncDecodeImages() || oldImageIsDifferent
) {
2000 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
2002 if (aDisplayListBuilder
->UseHighQualityScaling()) {
2003 flags
|= imgIContainer::FLAG_HIGH_QUALITY_SCALING
;
2005 if (StaticPrefs::image_svg_blob_image() &&
2006 mImage
->GetType() == imgIContainer::TYPE_VECTOR
) {
2007 flags
|= imgIContainer::FLAG_RECORD_BLOB
;
2010 const int32_t factor
= mFrame
->PresContext()->AppUnitsPerDevPixel();
2011 LayoutDeviceRect
destRect(
2012 LayoutDeviceRect::FromAppUnits(GetDestRect(), factor
));
2014 Maybe
<SVGImageContext
> svgContext
;
2015 Maybe
<ImageIntRegion
> region
;
2016 IntSize decodeSize
= nsLayoutUtils::ComputeImageContainerDrawingParameters(
2017 mImage
, mFrame
, destRect
, destRect
, aSc
, flags
, svgContext
, region
);
2019 RefPtr
<layers::ImageContainer
> container
;
2020 ImgDrawResult drawResult
= mImage
->GetImageContainerAtSize(
2021 aManager
->LayerManager(), decodeSize
, svgContext
, region
, flags
,
2022 getter_AddRefs(container
));
2024 // While we got a container, it may not contain a fully decoded surface. If
2025 // that is the case, and we have an image we were previously displaying which
2026 // has a fully decoded surface, then we should prefer the previous image.
2027 bool updatePrevImage
= false;
2028 switch (drawResult
) {
2029 case ImgDrawResult::NOT_READY
:
2030 case ImgDrawResult::INCOMPLETE
:
2031 case ImgDrawResult::TEMPORARY_ERROR
:
2032 if (mPrevImage
&& mPrevImage
!= mImage
) {
2033 // The current image and the previous image might be switching between
2034 // rasterized surfaces and blob recordings, so we need to update the
2035 // flags appropriately.
2036 uint32_t prevFlags
= flags
;
2037 if (StaticPrefs::image_svg_blob_image() &&
2038 mPrevImage
->GetType() == imgIContainer::TYPE_VECTOR
) {
2039 prevFlags
|= imgIContainer::FLAG_RECORD_BLOB
;
2041 prevFlags
&= ~imgIContainer::FLAG_RECORD_BLOB
;
2044 RefPtr
<ImageContainer
> prevContainer
;
2045 ImgDrawResult newDrawResult
= mPrevImage
->GetImageContainerAtSize(
2046 aManager
->LayerManager(), decodeSize
, svgContext
, region
, prevFlags
,
2047 getter_AddRefs(prevContainer
));
2048 if (prevContainer
&& newDrawResult
== ImgDrawResult::SUCCESS
) {
2049 drawResult
= newDrawResult
;
2050 container
= std::move(prevContainer
);
2055 // Previous image was unusable; we can forget about it.
2056 updatePrevImage
= true;
2059 case ImgDrawResult::NOT_SUPPORTED
:
2062 updatePrevImage
= mPrevImage
!= mImage
;
2066 // The previous image was not used, and is different from the current image.
2067 // We should forget about it. We need to update the frame as well because the
2068 // display item may get recreated.
2069 if (updatePrevImage
) {
2070 mPrevImage
= mImage
;
2071 frame
->mPrevImage
= frame
->mImage
;
2074 // If the image container is empty, we don't want to fallback. Any other
2075 // failure will be due to resource constraints and fallback is unlikely to
2076 // help us. Hence we can ignore the return value from PushImage.
2078 if (flags
& imgIContainer::FLAG_RECORD_BLOB
) {
2079 aManager
->CommandBuilder().PushBlobImage(this, container
, aBuilder
,
2080 aResources
, destRect
, destRect
);
2082 aManager
->CommandBuilder().PushImage(this, container
, aBuilder
,
2083 aResources
, aSc
, destRect
, destRect
);
2087 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult
);
2091 ImgDrawResult
nsImageFrame::PaintImage(gfxContext
& aRenderingContext
,
2092 nsPoint aPt
, const nsRect
& aDirtyRect
,
2093 imgIContainer
* aImage
, uint32_t aFlags
) {
2094 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
2096 // Render the image into our content area (the area inside
2097 // the borders and padding)
2098 NS_ASSERTION(GetInnerArea().width
== mComputedSize
.width
, "bad width");
2100 // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here,
2101 // because GetInnerArea() might be smaller if we're fragmented, whereas
2102 // mComputedSize has our full content-box size (which we need for
2103 // ComputeObjectDestRect to work correctly).
2104 nsRect
constraintRect(aPt
+ GetInnerArea().TopLeft(), mComputedSize
);
2105 constraintRect
.y
-= GetContinuationOffset();
2107 nsPoint anchorPoint
;
2108 nsRect dest
= nsLayoutUtils::ComputeObjectDestRect(
2109 constraintRect
, mIntrinsicSize
, mIntrinsicRatio
, StylePosition(),
2112 uint32_t flags
= aFlags
;
2113 if (mForceSyncDecoding
) {
2114 flags
|= imgIContainer::FLAG_SYNC_DECODE
;
2117 Maybe
<SVGImageContext
> svgContext
;
2118 SVGImageContext::MaybeStoreContextPaint(svgContext
, this, aImage
);
2120 ImgDrawResult result
= nsLayoutUtils::DrawSingleImage(
2121 aRenderingContext
, PresContext(), aImage
,
2122 nsLayoutUtils::GetSamplingFilterForFrame(this), dest
, aDirtyRect
,
2123 svgContext
, flags
, &anchorPoint
);
2125 if (nsImageMap
* map
= GetImageMap()) {
2126 gfxPoint devPixelOffset
= nsLayoutUtils::PointToGfxPoint(
2127 dest
.TopLeft(), PresContext()->AppUnitsPerDevPixel());
2128 AutoRestoreTransform
autoRestoreTransform(drawTarget
);
2129 drawTarget
->SetTransform(
2130 drawTarget
->GetTransform().PreTranslate(ToPoint(devPixelOffset
)));
2132 // solid white stroke:
2133 ColorPattern
white(ToDeviceColor(sRGBColor::OpaqueWhite()));
2134 map
->Draw(this, *drawTarget
, white
);
2136 // then dashed black stroke over the top:
2137 ColorPattern
black(ToDeviceColor(sRGBColor::OpaqueBlack()));
2138 StrokeOptions strokeOptions
;
2139 nsLayoutUtils::InitDashPattern(strokeOptions
, StyleBorderStyle::Dotted
);
2140 map
->Draw(this, *drawTarget
, black
, strokeOptions
);
2143 if (result
== ImgDrawResult::SUCCESS
) {
2144 mPrevImage
= aImage
;
2145 } else if (result
== ImgDrawResult::BAD_IMAGE
) {
2146 mPrevImage
= nullptr;
2152 already_AddRefed
<imgIRequest
> nsImageFrame::GetCurrentRequest() const {
2153 if (mKind
!= Kind::ImageElement
) {
2154 return do_AddRef(mContentURLRequest
);
2157 MOZ_ASSERT(!mContentURLRequest
);
2159 nsCOMPtr
<imgIRequest
> request
;
2160 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
2161 MOZ_ASSERT(imageLoader
);
2162 imageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
2163 getter_AddRefs(request
));
2164 return request
.forget();
2167 void nsImageFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
2168 const nsDisplayListSet
& aLists
) {
2169 if (!IsVisibleForPainting()) return;
2171 DisplayBorderBackgroundOutline(aBuilder
, aLists
);
2173 uint32_t clipFlags
=
2174 nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
2176 : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT
;
2178 DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox
clip(
2179 aBuilder
, this, clipFlags
);
2181 if (mComputedSize
.width
!= 0 && mComputedSize
.height
!= 0) {
2183 mKind
!= Kind::ImageElement
|| ImageOk(mContent
->AsElement()->State());
2185 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
2187 // XXX(seth): The SizeIsAvailable check here should not be necessary - the
2188 // intention is that a non-null mImage means we have a size, but there is
2189 // currently some code that violates this invariant.
2190 if (!imageOK
|| !mImage
|| !SizeIsAvailable(currentRequest
)) {
2191 // No image yet, or image load failed. Draw the alt-text and an icon
2192 // indicating the status
2193 aLists
.Content()->AppendNewToTop
<nsDisplayAltFeedback
>(aBuilder
, this);
2195 // This image is visible (we are being asked to paint it) but it's not
2196 // decoded yet. And we are not going to ask the image to draw, so this
2197 // may be the only chance to tell it that it should decode.
2198 if (currentRequest
) {
2199 uint32_t status
= 0;
2200 currentRequest
->GetImageStatus(&status
);
2201 if (!(status
& imgIRequest::STATUS_DECODE_COMPLETE
)) {
2202 MaybeDecodeForPredictedSize();
2204 // Increase loading priority if the image is ready to be displayed.
2205 if (!(status
& imgIRequest::STATUS_LOAD_COMPLETE
)) {
2206 currentRequest
->BoostPriority(imgIRequest::CATEGORY_DISPLAY
);
2210 aLists
.Content()->AppendNewToTop
<nsDisplayImage
>(aBuilder
, this, mImage
,
2213 // If we were previously displaying an icon, we're not anymore
2214 if (mDisplayingIcon
) {
2215 gIconLoad
->RemoveIconObserver(this);
2216 mDisplayingIcon
= false;
2220 if (GetShowFrameBorders() && GetImageMap()) {
2221 aLists
.Outlines()->AppendNewToTop
<nsDisplayGeneric
>(
2222 aBuilder
, this, PaintDebugImageMap
, "DebugImageMap",
2223 DisplayItemType::TYPE_DEBUG_IMAGE_MAP
);
2229 if (ShouldDisplaySelection()) {
2230 DisplaySelectionOverlay(aBuilder
, aLists
.Content(),
2231 nsISelectionDisplay::DISPLAY_IMAGES
);
2235 bool nsImageFrame::ShouldDisplaySelection() {
2236 int16_t displaySelection
= PresShell()->GetSelectionFlags();
2237 if (!(displaySelection
& nsISelectionDisplay::DISPLAY_IMAGES
)) {
2238 // no need to check the blue border, we cannot be drawn selected.
2242 if (displaySelection
!= nsISelectionDisplay::DISPLAY_ALL
) {
2246 // If the image is currently resize target of the editor, don't draw the
2247 // selection overlay.
2248 HTMLEditor
* htmlEditor
= nsContentUtils::GetHTMLEditor(PresContext());
2253 return htmlEditor
->GetResizerTarget() != mContent
;
2256 nsImageMap
* nsImageFrame::GetImageMap() {
2258 if (nsIContent
* map
= GetMapElement()) {
2259 mImageMap
= new nsImageMap();
2260 mImageMap
->Init(this, map
);
2267 bool nsImageFrame::IsServerImageMap() {
2268 return mContent
->AsElement()->HasAttr(kNameSpaceID_None
, nsGkAtoms::ismap
);
2271 // Translate an point that is relative to our frame
2272 // into a localized pixel coordinate that is relative to the
2273 // content area of this frame (inside the border+padding).
2274 void nsImageFrame::TranslateEventCoords(const nsPoint
& aPoint
,
2275 nsIntPoint
& aResult
) {
2276 nscoord x
= aPoint
.x
;
2277 nscoord y
= aPoint
.y
;
2279 // Subtract out border and padding here so that the coordinates are
2280 // now relative to the content area of this frame.
2281 nsRect inner
= GetInnerArea();
2285 aResult
.x
= nsPresContext::AppUnitsToIntCSSPixels(x
);
2286 aResult
.y
= nsPresContext::AppUnitsToIntCSSPixels(y
);
2289 bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI
** aHref
, nsString
& aTarget
,
2290 nsIContent
** aNode
) {
2291 bool status
= false;
2296 // Walk up the content tree, looking for an nsIDOMAnchorElement
2297 for (nsIContent
* content
= mContent
->GetParent(); content
;
2298 content
= content
->GetParent()) {
2299 nsCOMPtr
<dom::Link
> link(do_QueryInterface(content
));
2301 nsCOMPtr
<nsIURI
> href
= content
->GetHrefURI();
2305 status
= (*aHref
!= nullptr);
2307 RefPtr
<HTMLAnchorElement
> anchor
= HTMLAnchorElement::FromNode(content
);
2309 anchor
->GetTarget(aTarget
);
2311 NS_ADDREF(*aNode
= content
);
2318 nsresult
nsImageFrame::GetContentForEvent(WidgetEvent
* aEvent
,
2319 nsIContent
** aContent
) {
2320 NS_ENSURE_ARG_POINTER(aContent
);
2322 nsIFrame
* f
= nsLayoutUtils::GetNonGeneratedAncestor(this);
2324 return f
->GetContentForEvent(aEvent
, aContent
);
2327 // XXX We need to make this special check for area element's capturing the
2328 // mouse due to bug 135040. Remove it once that's fixed.
2329 nsIContent
* capturingContent
= aEvent
->HasMouseEventMessage()
2330 ? PresShell::GetCapturingContent()
2332 if (capturingContent
&& capturingContent
->GetPrimaryFrame() == this) {
2333 *aContent
= capturingContent
;
2334 NS_IF_ADDREF(*aContent
);
2338 if (nsImageMap
* map
= GetImageMap()) {
2340 TranslateEventCoords(
2341 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent
, RelativeTo
{this}),
2343 nsCOMPtr
<nsIContent
> area
= map
->GetArea(p
.x
, p
.y
);
2345 area
.forget(aContent
);
2350 *aContent
= GetContent();
2351 NS_IF_ADDREF(*aContent
);
2355 // XXX what should clicks on transparent pixels do?
2356 nsresult
nsImageFrame::HandleEvent(nsPresContext
* aPresContext
,
2357 WidgetGUIEvent
* aEvent
,
2358 nsEventStatus
* aEventStatus
) {
2359 NS_ENSURE_ARG_POINTER(aEventStatus
);
2361 if ((aEvent
->mMessage
== eMouseClick
&&
2362 aEvent
->AsMouseEvent()->mButton
== MouseButton::ePrimary
) ||
2363 aEvent
->mMessage
== eMouseMove
) {
2364 nsImageMap
* map
= GetImageMap();
2365 bool isServerMap
= IsServerImageMap();
2366 if (map
|| isServerMap
) {
2368 TranslateEventCoords(nsLayoutUtils::GetEventCoordinatesRelativeTo(
2369 aEvent
, RelativeTo
{this}),
2371 bool inside
= false;
2372 // Even though client-side image map triggering happens
2373 // through content, we need to make sure we're not inside
2374 // (in case we deal with a case of both client-side and
2375 // sever-side on the same image - it happens!)
2376 if (nullptr != map
) {
2377 inside
= !!map
->GetArea(p
.x
, p
.y
);
2380 if (!inside
&& isServerMap
) {
2381 // Server side image maps use the href in a containing anchor
2382 // element to provide the basis for the destination url.
2383 nsCOMPtr
<nsIURI
> uri
;
2384 nsAutoString target
;
2385 nsCOMPtr
<nsIContent
> anchorNode
;
2386 if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri
), target
,
2387 getter_AddRefs(anchorNode
))) {
2388 // XXX if the mouse is over/clicked in the border/padding area
2389 // we should probably just pretend nothing happened. Nav4
2390 // keeps the x,y coordinates positive as we do; IE doesn't
2391 // bother. Both of them send the click through even when the
2392 // mouse is over the border.
2393 if (p
.x
< 0) p
.x
= 0;
2394 if (p
.y
< 0) p
.y
= 0;
2397 nsresult rv
= uri
->GetSpec(spec
);
2398 NS_ENSURE_SUCCESS(rv
, rv
);
2400 spec
+= nsPrintfCString("?%d,%d", p
.x
, p
.y
);
2401 rv
= NS_MutateURI(uri
).SetSpec(spec
).Finalize(uri
);
2402 NS_ENSURE_SUCCESS(rv
, rv
);
2404 bool clicked
= false;
2405 if (aEvent
->mMessage
== eMouseClick
&& !aEvent
->DefaultPrevented()) {
2406 *aEventStatus
= nsEventStatus_eConsumeDoDefault
;
2409 nsContentUtils::TriggerLink(anchorNode
, uri
, target
, clicked
,
2410 /* isTrusted */ true);
2416 return nsAtomicContainerFrame::HandleEvent(aPresContext
, aEvent
,
2420 Maybe
<nsIFrame::Cursor
> nsImageFrame::GetCursor(const nsPoint
& aPoint
) {
2421 nsImageMap
* map
= GetImageMap();
2423 return nsIFrame::GetCursor(aPoint
);
2426 TranslateEventCoords(aPoint
, p
);
2427 HTMLAreaElement
* area
= map
->GetArea(p
.x
, p
.y
);
2429 return nsIFrame::GetCursor(aPoint
);
2432 // Use the cursor from the style of the *area* element.
2433 RefPtr
<ComputedStyle
> areaStyle
=
2434 PresShell()->StyleSet()->ResolveStyleLazily(*area
);
2436 // This is one of the cases, like the <xul:tree> pseudo-style stuff, where we
2437 // get styles out of the blue and expect to trigger image loads for those.
2438 areaStyle
->StartImageLoads(*PresContext()->Document());
2440 StyleCursorKind kind
= areaStyle
->StyleUI()->mCursor
.keyword
;
2441 if (kind
== StyleCursorKind::Auto
) {
2442 kind
= StyleCursorKind::Default
;
2444 return Some(Cursor
{kind
, AllowCustomCursorImage::Yes
, std::move(areaStyle
)});
2447 nsresult
nsImageFrame::AttributeChanged(int32_t aNameSpaceID
,
2448 nsAtom
* aAttribute
, int32_t aModType
) {
2449 nsresult rv
= nsAtomicContainerFrame::AttributeChanged(aNameSpaceID
,
2450 aAttribute
, aModType
);
2451 if (NS_FAILED(rv
)) {
2454 if (nsGkAtoms::alt
== aAttribute
) {
2455 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange
,
2462 void nsImageFrame::OnVisibilityChange(
2463 Visibility aNewVisibility
, const Maybe
<OnNonvisible
>& aNonvisibleAction
) {
2464 if (mKind
== Kind::ImageElement
) {
2465 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(mContent
);
2466 imageLoader
->OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
2469 if (aNewVisibility
== Visibility::ApproximatelyVisible
&&
2470 PresShell()->IsActive()) {
2471 MaybeDecodeForPredictedSize();
2474 nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility
, aNonvisibleAction
);
2477 #ifdef DEBUG_FRAME_DUMP
2478 nsresult
nsImageFrame::GetFrameName(nsAString
& aResult
) const {
2479 return MakeFrameName(u
"ImageFrame"_ns
, aResult
);
2482 void nsImageFrame::List(FILE* out
, const char* aPrefix
,
2483 ListFlags aFlags
) const {
2485 ListGeneric(str
, aPrefix
, aFlags
);
2487 // output the img src url
2488 if (nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest()) {
2489 nsCOMPtr
<nsIURI
> uri
;
2490 currentRequest
->GetURI(getter_AddRefs(uri
));
2491 nsAutoCString uristr
;
2492 uri
->GetAsciiSpec(uristr
);
2493 str
+= nsPrintfCString(" [src=%s]", uristr
.get());
2495 fprintf_stderr(out
, "%s\n", str
.get());
2499 LogicalSides
nsImageFrame::GetLogicalSkipSides() const {
2500 LogicalSides
skip(mWritingMode
);
2501 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak
==
2502 StyleBoxDecorationBreak::Clone
)) {
2505 if (GetPrevInFlow()) {
2506 skip
|= eLogicalSideBitsBStart
;
2508 if (GetNextInFlow()) {
2509 skip
|= eLogicalSideBitsBEnd
;
2514 nsresult
nsImageFrame::LoadIcon(const nsAString
& aSpec
,
2515 nsPresContext
* aPresContext
,
2516 imgRequestProxy
** aRequest
) {
2517 MOZ_ASSERT(!aSpec
.IsEmpty(), "What happened??");
2519 nsCOMPtr
<nsIURI
> realURI
;
2520 SpecToURI(aSpec
, getter_AddRefs(realURI
));
2522 RefPtr
<imgLoader
> il
=
2523 nsContentUtils::GetImgLoaderForDocument(aPresContext
->Document());
2525 nsCOMPtr
<nsILoadGroup
> loadGroup
;
2526 GetLoadGroup(aPresContext
, getter_AddRefs(loadGroup
));
2528 // For icon loads, we don't need to merge with the loadgroup flags
2529 nsLoadFlags loadFlags
= nsIRequest::LOAD_NORMAL
;
2530 nsContentPolicyType contentPolicyType
= nsIContentPolicy::TYPE_INTERNAL_IMAGE
;
2532 return il
->LoadImage(
2533 realURI
, /* icon URI */
2534 nullptr, /* initial document URI; this is only
2535 relevant for cookies, so does not
2537 nullptr, /* referrer (not relevant for icons) */
2538 nullptr, /* principal (not relevant for icons) */
2539 0, loadGroup
, gIconLoad
, nullptr, /* No context */
2540 nullptr, /* Not associated with any particular document */
2541 loadFlags
, nullptr, contentPolicyType
, u
""_ns
,
2542 false, /* aUseUrgentStartForChannel */
2543 false, /* aLinkPreload */
2547 void nsImageFrame::GetDocumentCharacterSet(nsACString
& aCharset
) const {
2549 NS_ASSERTION(mContent
->GetComposedDoc(),
2550 "Frame still alive after content removed from document!");
2551 mContent
->GetComposedDoc()->GetDocumentCharacterSet()->Name(aCharset
);
2555 void nsImageFrame::SpecToURI(const nsAString
& aSpec
, nsIURI
** aURI
) {
2556 nsIURI
* baseURI
= nullptr;
2558 baseURI
= mContent
->GetBaseURI();
2560 nsAutoCString charset
;
2561 GetDocumentCharacterSet(charset
);
2562 NS_NewURI(aURI
, aSpec
, charset
.IsEmpty() ? nullptr : charset
.get(), baseURI
);
2565 void nsImageFrame::GetLoadGroup(nsPresContext
* aPresContext
,
2566 nsILoadGroup
** aLoadGroup
) {
2567 if (!aPresContext
) return;
2569 MOZ_ASSERT(nullptr != aLoadGroup
, "null OUT parameter pointer");
2571 mozilla::PresShell
* presShell
= aPresContext
->GetPresShell();
2576 Document
* doc
= presShell
->GetDocument();
2581 *aLoadGroup
= doc
->GetDocumentLoadGroup().take();
2584 nsresult
nsImageFrame::LoadIcons(nsPresContext
* aPresContext
) {
2585 NS_ASSERTION(!gIconLoad
, "called LoadIcons twice");
2587 constexpr auto loadingSrc
= u
"resource://gre-resources/loading-image.png"_ns
;
2588 constexpr auto brokenSrc
= u
"resource://gre-resources/broken-image.png"_ns
;
2590 gIconLoad
= new IconLoad();
2593 // create a loader and load the images
2594 rv
= LoadIcon(loadingSrc
, aPresContext
,
2595 getter_AddRefs(gIconLoad
->mLoadingImage
));
2596 if (NS_FAILED(rv
)) {
2600 rv
= LoadIcon(brokenSrc
, aPresContext
,
2601 getter_AddRefs(gIconLoad
->mBrokenImage
));
2602 if (NS_FAILED(rv
)) {
2609 NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad
, nsIObserver
, imgINotificationObserver
)
2611 static const char* kIconLoadPrefs
[] = {
2612 "browser.display.force_inline_alttext",
2613 "browser.display.show_image_placeholders",
2614 "browser.display.show_loading_image_placeholder", nullptr};
2616 nsImageFrame::IconLoad::IconLoad() {
2617 // register observers
2618 Preferences::AddStrongObservers(this, kIconLoadPrefs
);
2622 void nsImageFrame::IconLoad::Shutdown() {
2623 Preferences::RemoveObservers(this, kIconLoadPrefs
);
2624 // in case the pref service releases us later
2625 if (mLoadingImage
) {
2626 mLoadingImage
->CancelAndForgetObserver(NS_ERROR_FAILURE
);
2627 mLoadingImage
= nullptr;
2630 mBrokenImage
->CancelAndForgetObserver(NS_ERROR_FAILURE
);
2631 mBrokenImage
= nullptr;
2636 nsImageFrame::IconLoad::Observe(nsISupports
* aSubject
, const char* aTopic
,
2637 const char16_t
* aData
) {
2638 NS_ASSERTION(!nsCRT::strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
),
2641 // assert |aData| is one of our prefs.
2643 for (; i
< ArrayLength(kIconLoadPrefs
); ++i
) {
2644 if (NS_ConvertASCIItoUTF16(kIconLoadPrefs
[i
]) == nsDependentString(aData
))
2647 MOZ_ASSERT(i
< ArrayLength(kIconLoadPrefs
));
2654 void nsImageFrame::IconLoad::GetPrefs() {
2655 mPrefForceInlineAltText
=
2656 Preferences::GetBool("browser.display.force_inline_alttext");
2658 mPrefShowPlaceholders
=
2659 Preferences::GetBool("browser.display.show_image_placeholders", true);
2661 mPrefShowLoadingPlaceholder
= Preferences::GetBool(
2662 "browser.display.show_loading_image_placeholder", true);
2665 nsresult
nsImageFrame::RestartAnimation() {
2666 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
2668 if (currentRequest
) {
2669 bool deregister
= false;
2670 nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(), currentRequest
,
2676 nsresult
nsImageFrame::StopAnimation() {
2677 nsCOMPtr
<imgIRequest
> currentRequest
= GetCurrentRequest();
2679 if (currentRequest
) {
2680 bool deregister
= true;
2681 nsLayoutUtils::DeregisterImageRequest(PresContext(), currentRequest
,
2687 void nsImageFrame::IconLoad::Notify(imgIRequest
* aRequest
, int32_t aType
,
2688 const nsIntRect
* aData
) {
2689 MOZ_ASSERT(aRequest
);
2691 if (aType
!= imgINotificationObserver::LOAD_COMPLETE
&&
2692 aType
!= imgINotificationObserver::FRAME_UPDATE
) {
2696 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
2697 nsCOMPtr
<imgIContainer
> image
;
2698 aRequest
->GetImage(getter_AddRefs(image
));
2703 // Retrieve the image's intrinsic size.
2706 image
->GetWidth(&width
);
2707 image
->GetHeight(&height
);
2709 // Request a decode at that size.
2710 image
->RequestDecodeForSize(IntSize(width
, height
),
2711 imgIContainer::DECODE_FLAGS_DEFAULT
|
2712 imgIContainer::FLAG_HIGH_QUALITY_SCALING
);
2715 for (nsImageFrame
* frame
: mIconObservers
.ForwardRange()) {
2716 frame
->InvalidateFrame();
2720 NS_IMPL_ISUPPORTS(nsImageListener
, imgINotificationObserver
)
2722 nsImageListener::nsImageListener(nsImageFrame
* aFrame
) : mFrame(aFrame
) {}
2724 nsImageListener::~nsImageListener() = default;
2726 void nsImageListener::Notify(imgIRequest
* aRequest
, int32_t aType
,
2727 const nsIntRect
* aData
) {
2732 return mFrame
->Notify(aRequest
, aType
, aData
);
2735 static bool IsInAutoWidthTableCellForQuirk(nsIFrame
* aFrame
) {
2736 if (eCompatibility_NavQuirks
!= aFrame
->PresContext()->CompatibilityMode())
2738 // Check if the parent of the closest nsBlockFrame has auto width.
2739 nsBlockFrame
* ancestor
= nsLayoutUtils::FindNearestBlockAncestor(aFrame
);
2740 if (ancestor
->Style()->GetPseudoType() == PseudoStyleType::cellContent
) {
2741 // Assume direct parent is a table cell frame.
2742 nsIFrame
* grandAncestor
= static_cast<nsIFrame
*>(ancestor
->GetParent());
2743 return grandAncestor
&& grandAncestor
->StylePosition()->mWidth
.IsAuto();
2748 void nsImageFrame::AddInlineMinISize(gfxContext
* aRenderingContext
,
2749 nsIFrame::InlineMinISizeData
* aData
) {
2750 nscoord isize
= nsLayoutUtils::IntrinsicForContainer(
2751 aRenderingContext
, this, IntrinsicISizeType::MinISize
);
2752 bool canBreak
= !IsInAutoWidthTableCellForQuirk(this);
2753 aData
->DefaultAddInlineMinISize(this, isize
, canBreak
);