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