Bug 1708422: part 13) Factor code out to `mozInlineSpellChecker::SpellCheckerTimeSlic...
[gecko.git] / layout / generic / nsImageFrame.cpp
blob36f898c82128c3e00588bd27b2c3bba93778ce18
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* rendering object for replaced elements with image data */
9 #include "nsImageFrame.h"
11 #include "TextDrawTarget.h"
12 #include "gfx2DGlue.h"
13 #include "gfxContext.h"
14 #include "gfxUtils.h"
15 #include "mozilla/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"
38 #include "nsCOMPtr.h"
39 #include "nsFontMetrics.h"
40 #include "nsIImageLoadingContent.h"
41 #include "nsImageLoadingContent.h"
42 #include "nsString.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"
55 #include "nsNetCID.h"
56 #include "nsCSSRendering.h"
57 #include "nsNameSpaceManager.h"
58 #include <algorithm>
59 #ifdef ACCESSIBILITY
60 # include "nsAccessibilityService.h"
61 #endif
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"
74 #include "nsRange.h"
76 #include "nsError.h"
77 #include "nsBidiUtils.h"
78 #include "nsBidiPresUtils.h"
80 #include "gfxRect.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);
126 if (!length) {
127 return false;
129 switch (*length) {
130 case nsIFrame::ExtremumLength::MinContent:
131 case nsIFrame::ExtremumLength::MaxContent:
132 case nsIFrame::ExtremumLength::MozFitContent:
133 return true;
134 case nsIFrame::ExtremumLength::MozAvailable:
135 return false;
137 MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
138 return false;
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
183 // Blink's behavior.
184 if (mKind != Kind::ImageElement) {
185 return false;
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()) {
193 return false;
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();
210 return !!currentURI;
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),
224 mComputedSize(0, 0),
225 mIntrinsicSize(0, 0),
226 mKind(aKind),
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)
241 #ifdef ACCESSIBILITY
242 a11y::AccType nsImageFrame::AccessibleType() {
243 // Don't use GetImageMap() to avoid reentrancy into accessibility.
244 if (HasImageMap()) {
245 return a11y::eHTMLImageMapType;
248 return a11y::eImageType;
250 #endif
252 void nsImageFrame::DisconnectMap() {
253 if (!mImageMap) {
254 return;
257 mImageMap->Destroy();
258 mImageMap = nullptr;
260 #ifdef ACCESSIBILITY
261 if (nsAccessibilityService* accService = GetAccService()) {
262 accService->RecreateAccessible(PresShell(), mContent);
264 #endif
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
276 // a DOM listener.
277 DisconnectMap();
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);
300 mListener = 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) {
310 return;
312 const auto& content = *StyleContent();
313 if (content.ContentCount() != 1) {
314 return;
316 const auto& item = content.ContentAt(0);
317 if (!item.IsImage()) {
318 return;
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 =
335 mImage &&
336 (!aOldStyle ||
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) {
352 if (!aRequest) {
353 return false;
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) {
363 return nullptr;
365 uint32_t contentIndex = 0;
366 const nsStyleContent* styleContent = StyleContent();
367 if (mKind == Kind::ContentPropertyAtIndex) {
368 MOZ_RELEASE_ASSERT(
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);
403 if (!gIconLoad) {
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);
414 } else {
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
430 // layout.
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) {
442 return;
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);
449 uint32_t status = 0;
450 nsresult rv = mContentURLRequest->GetImageStatus(&status);
451 if (NS_FAILED(rv)) {
452 return;
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) {
473 if (aSize.width) {
474 aResolution.ApplyXTo(aSize.width.ref());
476 if (aSize.height) {
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);
502 nsSize size;
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);
509 } else {
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
533 // ratio then...
534 bool nsImageFrame::ShouldUseMappedAspectRatio() const {
535 if (mKind != Kind::ImageElement) {
536 return true;
538 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
539 if (currentRequest) {
540 return true;
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;
550 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();
563 if (aImage) {
564 if (Maybe<AspectRatio> fromImage = aImage->GetIntrinsicRatio()) {
565 return *fromImage;
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;
584 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
603 // instead?
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()) {
612 return false;
615 aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width),
616 float(destRect.height) / float(intrinsicSize.height));
617 return true;
620 // This function checks whether the given request is the current request for our
621 // mContent.
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);
626 return false;
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;
670 return r;
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)) {
681 return true;
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.
690 /* static */
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
695 return true;
698 if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) {
699 return true;
702 // if our "do not show placeholders" pref is set, skip the icon
703 if (gIconLoad && gIconLoad->mPrefForceInlineAltText) {
704 return false;
707 if (!HasAltText(aElement)) {
708 return true;
711 if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks) {
712 // FIXME(emilio): We definitely don't reframe when this changes...
713 return HaveSpecifiedSize(aStyle.StylePosition());
716 return false;
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) {
742 uint32_t imgStatus;
743 aRequest->GetImageStatus(&imgStatus);
744 nsresult status =
745 imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
746 return OnLoadComplete(aRequest, status);
750 void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest,
751 imgIContainer* aImage) {
752 if (!aImage) {
753 return;
756 /* Get requested animation policy from the pres context:
757 * normal = 0
758 * one frame = 1
759 * one loop = 2
761 aImage->SetAnimationMode(PresContext()->ImageAnimationMode());
763 if (IsPendingLoad(aRequest)) {
764 // We don't care
765 return;
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);
778 MOZ_ASSERT(mImage);
779 } else {
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
784 // short-circuiting.
785 bool intrinsicSizeChanged = UpdateIntrinsicSize() | UpdateIntrinsicRatio();
786 if (!GotInitialReflow()) {
787 return;
790 // We're going to need to repaint now either way.
791 InvalidateFrame();
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)) {
797 #ifdef ACCESSIBILITY
798 if (nsAccessibilityService* accService = GetAccService()) {
799 accService->NotifyOfImageSizeAvailable(PresShell(), mContent);
801 #endif
802 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
803 NS_FRAME_IS_DIRTY);
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)) {
815 return;
818 if (!GotInitialReflow()) {
819 // Don't bother to do anything; we have a reflow coming up!
820 return;
823 if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
824 return;
827 if (IsPendingLoad(aRequest)) {
828 // We don't care
829 return;
832 nsIntRect layerInvalidRect =
833 mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect;
835 if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
836 // Invalidate our entire area.
837 InvalidateSelf(nullptr, nullptr);
838 return;
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)) {
853 return;
856 InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect);
858 if (!mFirstFrameComplete) {
859 InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
860 aFrameInvalidRect);
864 void nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
865 NotifyNewCurrentRequest(aRequest, aStatus);
868 void nsImageFrame::ResponsiveContentDensityChanged() {
869 if (!GotInitialReflow()) {
870 return;
873 if (!mImage) {
874 return;
877 if (!UpdateIntrinsicSize() && !UpdateIntrinsicRatio()) {
878 return;
881 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
882 NS_FRAME_IS_DIRTY);
885 void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest,
886 nsresult aStatus) {
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.
896 if (!mImage) {
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()) {
929 return;
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,
941 flags);
943 // Request a decode.
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();
963 return;
966 // If mIntrinsicSize.width and height are 0, then we need to update from the
967 // image container.
968 if (mIntrinsicSize != IntrinsicSize(0, 0)) {
969 return;
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())
997 ->FindImageMap()
998 : nullptr;
1001 // get the offset into the content area of the image where aImg starts if it is
1002 // a continuation.
1003 nscoord nsImageFrame::GetContinuationOffset() const {
1004 nscoord offset = 0;
1005 for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
1006 offset += f->GetContentRect().height;
1008 NS_ASSERTION(offset >= 0, "bogus GetContentRect");
1009 return offset;
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) {
1038 MarkInReflow();
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!");
1042 NS_FRAME_TRACE(
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);
1052 } else {
1053 RemoveStateBits(IMAGE_SIZECONSTRAINED);
1056 mComputedSize =
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();
1094 bool imageOK =
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) {
1100 haveSize = true;
1103 if (!imageOK || !haveSize) {
1104 nsRect altFeedbackSize(
1105 0, 0,
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();
1146 return false;
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();
1164 aMaxFit = 0;
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;
1173 break;
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
1183 // include it
1184 if (fits || (0 == totalWidth)) {
1185 // New piece fits
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;
1192 } else {
1193 // Space won't fit. Leave it at the end but don't include it in
1194 // the width
1195 fits = false;
1198 len++;
1201 aMaxFit += len;
1202 aString += len;
1203 aLength -= len;
1206 if (!fits) {
1207 break;
1210 return totalWidth;
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)
1244 : aRect.TopLeft();
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) {
1254 if (!firstLine) {
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()))) {
1259 break;
1263 // Determine how much of the text to display on this line
1264 uint32_t maxFit; // number of characters that fit
1265 nscoord strWidth =
1266 MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
1268 // Display the text
1269 nsresult rv = NS_ERROR_FAILURE;
1271 if (aPresContext->BidiEnabled()) {
1272 nsBidiDirection dir;
1273 nscoord x, y;
1275 if (isVertical) {
1276 x = pt.x + maxDescent;
1277 if (wm.IsBidiLTR()) {
1278 y = aRect.y;
1279 dir = NSBIDI_LTR;
1280 } else {
1281 y = aRect.YMost() - strWidth;
1282 dir = NSBIDI_RTL;
1284 } else {
1285 y = pt.y + maxAscent;
1286 if (wm.IsBidiLTR()) {
1287 x = aRect.x;
1288 dir = NSBIDI_LTR;
1289 } else {
1290 x = aRect.XMost() - strWidth;
1291 dir = NSBIDI_RTL;
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,
1301 isVertical
1302 ? nsPoint(pt.x + maxDescent, pt.y)
1303 : nsPoint(pt.x, pt.y + maxAscent),
1304 *fm, aRenderingContext);
1307 // Move to the next line
1308 str += maxFit;
1309 strLen -= maxFit;
1310 if (wm.IsVerticalRL()) {
1311 pt.x -= lineHeight;
1312 } else if (wm.IsVerticalLR()) {
1313 pt.x += lineHeight;
1314 } else {
1315 pt.y += lineHeight;
1318 firstLine = false;
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
1329 // mComputedBorder
1330 SetBorderStyle(side, StyleBorderStyle::Inset);
1335 class nsDisplayAltFeedback final : public nsPaintedDisplayItem {
1336 public:
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 {
1348 auto geometry =
1349 static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1351 if (aBuilder->ShouldSyncDecodeImages() &&
1352 geometry->ShouldInvalidateToSyncDecodeImages()) {
1353 bool snap;
1354 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1357 nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
1358 aInvalidRegion);
1361 nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
1362 *aSnap = false;
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.
1403 bool isLoading =
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
1422 // our frame bounds
1423 if ((inner.width < 2 * borderEdgeWidth) ||
1424 (inner.height < 2 * borderEdgeWidth)) {
1425 return ImgDrawResult::SUCCESS;
1428 // Paint the border
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
1434 // returns.
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
1444 inner.Deflate(
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;
1464 } else {
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
1472 // doing so now.
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,
1490 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;
1503 // stroked rect:
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,
1512 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;
1528 } else {
1529 if (!flushRight) {
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();
1546 return result;
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.
1559 bool isLoading =
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
1578 // our frame bounds
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
1586 // end.
1587 bool textDrawResult = true;
1588 class AutoSaveRestore {
1589 public:
1590 explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder& aBuilder,
1591 bool& aTextDrawResult)
1592 : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) {
1593 mBuilder.Save();
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();
1600 } else {
1601 mBuilder.Restore();
1605 private:
1606 mozilla::wr::DisplayListBuilder& mBuilder;
1607 bool& mTextDrawResult;
1610 AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult);
1612 // Paint the border
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
1617 // returns.
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
1628 inner.Deflate(
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
1650 // doing so now.
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,
1669 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,
1679 region);
1680 RefPtr<ImageContainer> container;
1681 result = imgCon->GetImageContainerAtSize(
1682 aManager->LayerManager(), decodeSize, svgContext, region, aFlags,
1683 getter_AddRefs(container));
1684 if (container) {
1685 bool wrResult = aManager->CommandBuilder().PushImage(
1686 aItem, container, aBuilder, aResources, aSc, destRect, bounds);
1687 result &= wrResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
1688 } else {
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;
1702 // stroked rect:
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,
1718 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;
1733 } else {
1734 if (!flushRight) {
1735 inner.x += paddedIconSize;
1737 inner.width -= paddedIconSize;
1741 // Draw text
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
1758 // already.
1759 return textDrawResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
1762 #ifdef DEBUG
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())));
1775 #endif
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.
1781 // See bug 1589955.
1782 static bool OldImageHasDifferentRatio(const nsImageFrame& aFrame,
1783 imgIContainer& aImage,
1784 imgIContainer* aPrevImage) {
1785 if (!aPrevImage || aPrevImage == &aImage) {
1786 return false;
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)) {
1796 return false;
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;
1803 #ifdef DEBUG
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.
1814 MOZ_ASSERT(
1815 (!currentRatioRecomputed && aImage.GetIntrinsicRatio() == Nothing()) ||
1816 currentRatio == currentRatioRecomputed,
1817 "aspect-ratio got out of sync during paint? How?");
1818 #endif
1819 auto oldRatio = ComputeIntrinsicRatio(aPrevImage, hasRequest, aFrame);
1820 return oldRatio != currentRatio;
1823 void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
1824 MOZ_ASSERT(mImage);
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.
1846 if (mPrevImage) {
1847 result = frame->PaintImage(*aCtx, ToReferenceFrame(), GetPaintRect(),
1848 mPrevImage, flags);
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 {
1863 auto geometry =
1864 static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
1866 if (aBuilder->ShouldSyncDecodeImages() &&
1867 geometry->ShouldInvalidateToSyncDecodeImages()) {
1868 bool snap;
1869 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
1872 nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry,
1873 aInvalidRegion);
1876 already_AddRefed<imgIContainer> nsDisplayImage::GetImage() {
1877 nsCOMPtr<imgIContainer> image = mImage;
1878 return image.forget();
1881 nsRect nsDisplayImage::GetDestRect() const {
1882 bool snap = true;
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;
1903 if (!animated) {
1904 int32_t imageWidth;
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 {
1947 *aSnap = false;
1948 if (mImage && mImage->WillDrawOpaqueNow()) {
1949 const nsRect frameContentBox = GetBounds(aSnap);
1950 return GetDestRect().Intersect(frameContentBox);
1952 return nsRegion();
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()) {
1965 return nullptr;
1968 RefPtr<ImageLayer> layer = static_cast<ImageLayer*>(
1969 aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
1970 if (!layer) {
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) {
1984 if (!mImage) {
1985 return false;
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()) {
1992 return false;
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;
2040 } else {
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);
2051 flags = prevFlags;
2052 break;
2055 // Previous image was unusable; we can forget about it.
2056 updatePrevImage = true;
2058 break;
2059 case ImgDrawResult::NOT_SUPPORTED:
2060 return false;
2061 default:
2062 updatePrevImage = mPrevImage != mImage;
2063 break;
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.
2077 if (container) {
2078 if (flags & imgIContainer::FLAG_RECORD_BLOB) {
2079 aManager->CommandBuilder().PushBlobImage(this, container, aBuilder,
2080 aResources, destRect, destRect);
2081 } else {
2082 aManager->CommandBuilder().PushImage(this, container, aBuilder,
2083 aResources, aSc, destRect, destRect);
2087 nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, drawResult);
2088 return true;
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(),
2110 &anchorPoint);
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;
2149 return result;
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) {
2182 bool imageOK =
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);
2209 } else {
2210 aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage,
2211 mPrevImage);
2213 // If we were previously displaying an icon, we're not anymore
2214 if (mDisplayingIcon) {
2215 gIconLoad->RemoveIconObserver(this);
2216 mDisplayingIcon = false;
2219 #ifdef DEBUG
2220 if (GetShowFrameBorders() && GetImageMap()) {
2221 aLists.Outlines()->AppendNewToTop<nsDisplayGeneric>(
2222 aBuilder, this, PaintDebugImageMap, "DebugImageMap",
2223 DisplayItemType::TYPE_DEBUG_IMAGE_MAP);
2225 #endif
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.
2239 return false;
2242 if (displaySelection != nsISelectionDisplay::DISPLAY_ALL) {
2243 return true;
2246 // If the image is currently resize target of the editor, don't draw the
2247 // selection overlay.
2248 HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(PresContext());
2249 if (!htmlEditor) {
2250 return true;
2253 return htmlEditor->GetResizerTarget() != mContent;
2256 nsImageMap* nsImageFrame::GetImageMap() {
2257 if (!mImageMap) {
2258 if (nsIContent* map = GetMapElement()) {
2259 mImageMap = new nsImageMap();
2260 mImageMap->Init(this, map);
2264 return mImageMap;
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();
2282 x -= inner.x;
2283 y -= inner.y;
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;
2292 aTarget.Truncate();
2293 *aHref = nullptr;
2294 *aNode = nullptr;
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));
2300 if (link) {
2301 nsCOMPtr<nsIURI> href = content->GetHrefURI();
2302 if (href) {
2303 href.forget(aHref);
2305 status = (*aHref != nullptr);
2307 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(content);
2308 if (anchor) {
2309 anchor->GetTarget(aTarget);
2311 NS_ADDREF(*aNode = content);
2312 break;
2315 return status;
2318 nsresult nsImageFrame::GetContentForEvent(WidgetEvent* aEvent,
2319 nsIContent** aContent) {
2320 NS_ENSURE_ARG_POINTER(aContent);
2322 nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
2323 if (f != 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()
2331 : nullptr;
2332 if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
2333 *aContent = capturingContent;
2334 NS_IF_ADDREF(*aContent);
2335 return NS_OK;
2338 if (nsImageMap* map = GetImageMap()) {
2339 nsIntPoint p;
2340 TranslateEventCoords(
2341 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this}),
2343 nsCOMPtr<nsIContent> area = map->GetArea(p.x, p.y);
2344 if (area) {
2345 area.forget(aContent);
2346 return NS_OK;
2350 *aContent = GetContent();
2351 NS_IF_ADDREF(*aContent);
2352 return NS_OK;
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) {
2367 nsIntPoint p;
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;
2396 nsAutoCString spec;
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;
2407 clicked = true;
2409 nsContentUtils::TriggerLink(anchorNode, uri, target, clicked,
2410 /* isTrusted */ true);
2416 return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
2417 aEventStatus);
2420 Maybe<nsIFrame::Cursor> nsImageFrame::GetCursor(const nsPoint& aPoint) {
2421 nsImageMap* map = GetImageMap();
2422 if (!map) {
2423 return nsIFrame::GetCursor(aPoint);
2425 nsIntPoint p;
2426 TranslateEventCoords(aPoint, p);
2427 HTMLAreaElement* area = map->GetArea(p.x, p.y);
2428 if (!area) {
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)) {
2452 return rv;
2454 if (nsGkAtoms::alt == aAttribute) {
2455 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
2456 NS_FRAME_IS_DIRTY);
2459 return NS_OK;
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 {
2484 nsCString str;
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());
2497 #endif
2499 LogicalSides nsImageFrame::GetLogicalSkipSides() const {
2500 LogicalSides skip(mWritingMode);
2501 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
2502 StyleBoxDecorationBreak::Clone)) {
2503 return skip;
2505 if (GetPrevInFlow()) {
2506 skip |= eLogicalSideBitsBStart;
2508 if (GetNextInFlow()) {
2509 skip |= eLogicalSideBitsBEnd;
2511 return skip;
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
2536 apply to icons. */
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 */
2544 aRequest);
2547 void nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const {
2548 if (mContent) {
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;
2557 if (mContent) {
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();
2572 if (!presShell) {
2573 return;
2576 Document* doc = presShell->GetDocument();
2577 if (!doc) {
2578 return;
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();
2592 nsresult rv;
2593 // create a loader and load the images
2594 rv = LoadIcon(loadingSrc, aPresContext,
2595 getter_AddRefs(gIconLoad->mLoadingImage));
2596 if (NS_FAILED(rv)) {
2597 return rv;
2600 rv = LoadIcon(brokenSrc, aPresContext,
2601 getter_AddRefs(gIconLoad->mBrokenImage));
2602 if (NS_FAILED(rv)) {
2603 return rv;
2606 return 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);
2619 GetPrefs();
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;
2629 if (mBrokenImage) {
2630 mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
2631 mBrokenImage = nullptr;
2635 NS_IMETHODIMP
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),
2639 "wrong topic");
2640 #ifdef DEBUG
2641 // assert |aData| is one of our prefs.
2642 uint32_t i = 0;
2643 for (; i < ArrayLength(kIconLoadPrefs); ++i) {
2644 if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData))
2645 break;
2647 MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs));
2648 #endif
2650 GetPrefs();
2651 return NS_OK;
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,
2671 &deregister);
2673 return NS_OK;
2676 nsresult nsImageFrame::StopAnimation() {
2677 nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
2679 if (currentRequest) {
2680 bool deregister = true;
2681 nsLayoutUtils::DeregisterImageRequest(PresContext(), currentRequest,
2682 &deregister);
2684 return NS_OK;
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) {
2693 return;
2696 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
2697 nsCOMPtr<imgIContainer> image;
2698 aRequest->GetImage(getter_AddRefs(image));
2699 if (!image) {
2700 return;
2703 // Retrieve the image's intrinsic size.
2704 int32_t width = 0;
2705 int32_t height = 0;
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) {
2728 if (!mFrame) {
2729 return;
2732 return mFrame->Notify(aRequest, aType, aData);
2735 static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) {
2736 if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
2737 return false;
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();
2745 return false;
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);