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