summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsImageFrame.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /layout/generic/nsImageFrame.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'layout/generic/nsImageFrame.cpp')
-rw-r--r--layout/generic/nsImageFrame.cpp2906
1 files changed, 2906 insertions, 0 deletions
diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp
new file mode 100644
index 0000000000..e0a6243ed2
--- /dev/null
+++ b/layout/generic/nsImageFrame.cpp
@@ -0,0 +1,2906 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* rendering object for replaced elements with image data */
+
+#include "nsImageFrame.h"
+
+#include "TextDrawTarget.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/dom/GeneratedImageContent.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/ReferrerInfo.h"
+#include "mozilla/dom/ResponsiveImageSelector.h"
+#include "mozilla/dom/LargestContentfulPaint.h"
+#include "mozilla/image/WebRenderImageProvider.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/Unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsIFrameInlines.h"
+#include "nsIImageLoadingContent.h"
+#include "nsImageLoadingContent.h"
+#include "nsImageRenderer.h"
+#include "nsObjectLoadingContent.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsStyleConsts.h"
+#include "nsStyleUtil.h"
+#include "nsTransform2D.h"
+#include "nsImageMap.h"
+#include "nsILoadGroup.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCSSRendering.h"
+#include "nsNameSpaceManager.h"
+#include <algorithm>
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+#include "nsLayoutUtils.h"
+#include "nsDisplayList.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Selection.h"
+#include "nsIURIMutator.h"
+
+#include "imgIContainer.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+
+#include "nsCSSFrameConstructor.h"
+#include "nsRange.h"
+
+#include "nsError.h"
+#include "nsBidiUtils.h"
+#include "nsBidiPresUtils.h"
+
+#include "gfxRect.h"
+#include "ImageRegion.h"
+#include "ImageContainer.h"
+#include "mozilla/ServoStyleSet.h"
+#include "nsBlockFrame.h"
+#include "nsStyleStructInlines.h"
+
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/BrowserChild.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+
+using mozilla::layout::TextDrawTarget;
+
+class nsDisplayGradient final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayGradient);
+ }
+ ~nsDisplayGradient() final { MOZ_COUNT_DTOR(nsDisplayGradient); }
+
+ nsRect GetBounds(bool* aSnap) const {
+ *aSnap = true;
+ return Frame()->GetContentRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
+ return GetBounds(aSnap);
+ }
+
+ void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
+
+ bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
+ mozilla::wr::IpcResourceUpdateQueue&,
+ const StackingContextHelper&,
+ mozilla::layers::RenderRootStateManager*,
+ nsDisplayListBuilder*) final;
+
+ NS_DISPLAY_DECL_NAME("Gradient", TYPE_GRADIENT)
+};
+
+void nsDisplayGradient::Paint(nsDisplayListBuilder* aBuilder,
+ gfxContext* aCtx) {
+ auto* frame = static_cast<nsImageFrame*>(Frame());
+ nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
+ aBuilder->GetImageRendererFlags());
+ nsSize size = frame->GetSize();
+ imageRenderer.SetPreferredSize({}, size);
+
+ ImgDrawResult result;
+ if (!imageRenderer.PrepareImage()) {
+ result = imageRenderer.PrepareResult();
+ } else {
+ nsRect dest(ToReferenceFrame(), size);
+ result = imageRenderer.DrawLayer(
+ frame->PresContext(), *aCtx, dest, dest, dest.TopLeft(),
+ GetPaintRect(aBuilder, aCtx), dest.Size(), /* aOpacity = */ 1.0f);
+ }
+ Unused << result;
+}
+
+bool nsDisplayGradient::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ auto* frame = static_cast<nsImageFrame*>(Frame());
+ nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
+ aDisplayListBuilder->GetImageRendererFlags());
+ nsSize size = frame->GetSize();
+ imageRenderer.SetPreferredSize({}, size);
+
+ ImgDrawResult result;
+ if (!imageRenderer.PrepareImage()) {
+ result = imageRenderer.PrepareResult();
+ } else {
+ nsRect dest(ToReferenceFrame(), size);
+ result = imageRenderer.BuildWebRenderDisplayItemsForLayer(
+ frame->PresContext(), aBuilder, aResources, aSc, aManager, this, dest,
+ dest, dest.TopLeft(), dest, dest.Size(),
+ /* aOpacity = */ 1.0f);
+ if (result == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// sizes (pixels) for image icon, padding and border frame
+#define ICON_SIZE (16)
+#define ICON_PADDING (3)
+#define ALT_BORDER_WIDTH (1)
+
+// Default alignment value (so we can tell an unset value from a set value)
+#define ALIGN_UNSET uint8_t(-1)
+
+class BrokenImageIcon final : public imgINotificationObserver {
+ // private class that wraps the data and logic needed for
+ // broken image and loading image icons
+ public:
+ explicit BrokenImageIcon(const nsImageFrame& aFrame);
+ static void Shutdown();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ static imgRequestProxy* GetImage(nsImageFrame* aFrame) {
+ return Get(*aFrame).mImage.get();
+ }
+
+ static void AddObserver(nsImageFrame* aFrame) {
+ auto& instance = Get(*aFrame);
+ MOZ_ASSERT(!instance.mObservers.Contains(aFrame),
+ "Observer shouldn't aleady be in array");
+ instance.mObservers.AppendElement(aFrame);
+ }
+
+ static void RemoveObserver(nsImageFrame* aFrame) {
+ auto& instance = Get(*aFrame);
+ DebugOnly<bool> didRemove = instance.mObservers.RemoveElement(aFrame);
+ MOZ_ASSERT(didRemove, "Observer not in array");
+ }
+
+ private:
+ static BrokenImageIcon& Get(const nsImageFrame& aFrame) {
+ if (!gSingleton) {
+ gSingleton = new BrokenImageIcon(aFrame);
+ }
+ return *gSingleton;
+ }
+
+ ~BrokenImageIcon() = default;
+
+ nsTObserverArray<nsImageFrame*> mObservers;
+ RefPtr<imgRequestProxy> mImage;
+
+ static StaticRefPtr<BrokenImageIcon> gSingleton;
+};
+
+StaticRefPtr<BrokenImageIcon> BrokenImageIcon::gSingleton;
+
+NS_IMPL_ISUPPORTS(BrokenImageIcon, imgINotificationObserver)
+
+BrokenImageIcon::BrokenImageIcon(const nsImageFrame& aFrame) {
+ constexpr auto brokenSrc = u"resource://gre-resources/broken-image.png"_ns;
+ nsCOMPtr<nsIURI> realURI;
+ NS_NewURI(getter_AddRefs(realURI), brokenSrc);
+
+ MOZ_ASSERT(realURI, "how?");
+ if (NS_WARN_IF(!realURI)) {
+ return;
+ }
+
+ nsPresContext* pc = aFrame.PresContext();
+ Document* doc = pc->Document();
+ RefPtr<imgLoader> il = nsContentUtils::GetImgLoaderForDocument(doc);
+
+ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
+
+ // For icon loads, we don't need to merge with the loadgroup flags
+ const nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ const nsContentPolicyType contentPolicyType =
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE;
+
+ nsresult rv =
+ il->LoadImage(realURI, /* icon URI */
+ nullptr, /* initial document URI; this is only
+ relevant for cookies, so does not
+ apply to icons. */
+ nullptr, /* referrer (not relevant for icons) */
+ nullptr, /* principal (not relevant for icons) */
+ 0, loadGroup, this, nullptr, /* No context */
+ nullptr, /* Not associated with any particular document */
+ loadFlags, nullptr, contentPolicyType, u""_ns,
+ false, /* aUseUrgentStartForChannel */
+ false, /* aLinkPreload */
+ 0, getter_AddRefs(mImage));
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+void BrokenImageIcon::Shutdown() {
+ if (!gSingleton) {
+ return;
+ }
+ if (gSingleton->mImage) {
+ gSingleton->mImage->CancelAndForgetObserver(NS_ERROR_FAILURE);
+ gSingleton->mImage = nullptr;
+ }
+ gSingleton = nullptr;
+}
+
+void BrokenImageIcon::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ MOZ_ASSERT(aRequest);
+
+ if (aType != imgINotificationObserver::LOAD_COMPLETE &&
+ aType != imgINotificationObserver::FRAME_UPDATE) {
+ return;
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ if (!image) {
+ return;
+ }
+
+ // Retrieve the image's intrinsic size.
+ int32_t width = 0;
+ int32_t height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+
+ // Request a decode at that size.
+ image->RequestDecodeForSize(IntSize(width, height),
+ imgIContainer::DECODE_FLAGS_DEFAULT |
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING);
+ }
+
+ for (nsImageFrame* frame : mObservers.ForwardRange()) {
+ frame->InvalidateFrame();
+ }
+}
+
+// test if the width and height are fixed, looking at the style data
+// This is used by nsImageFrame::ImageFrameTypeFor and should not be used for
+// layout decisions.
+static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) {
+ // check the width and height values in the reflow input's style struct
+ // - if width and height are specified as either coord or percentage, then
+ // the size of the image frame is constrained
+ return aStylePosition->mWidth.IsLengthPercentage() &&
+ aStylePosition->mHeight.IsLengthPercentage();
+}
+
+template <typename SizeOrMaxSize>
+static bool DependsOnIntrinsicSize(const SizeOrMaxSize& aMinOrMaxSize) {
+ auto length = nsIFrame::ToExtremumLength(aMinOrMaxSize);
+ if (!length) {
+ return false;
+ }
+ switch (*length) {
+ case nsIFrame::ExtremumLength::MinContent:
+ case nsIFrame::ExtremumLength::MaxContent:
+ case nsIFrame::ExtremumLength::FitContent:
+ case nsIFrame::ExtremumLength::FitContentFunction:
+ return true;
+ case nsIFrame::ExtremumLength::MozAvailable:
+ return false;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown sizing keyword?");
+ return false;
+}
+
+// Decide whether we can optimize away reflows that result from the
+// image's intrinsic size changing.
+static bool SizeDependsOnIntrinsicSize(const ReflowInput& aReflowInput) {
+ const auto& position = *aReflowInput.mStylePosition;
+ WritingMode wm = aReflowInput.GetWritingMode();
+ // Don't try to make this optimization when an image has percentages
+ // in its 'width' or 'height'. The percentages might be treated like
+ // auto (especially for intrinsic width calculations and for heights).
+ //
+ // min-width: min-content and such can also affect our intrinsic size.
+ // but note that those keywords on the block axis behave like auto, so we
+ // don't need to check them.
+ //
+ // Flex item's min-[width|height]:auto resolution depends on intrinsic size.
+ return !position.mHeight.ConvertsToLength() ||
+ !position.mWidth.ConvertsToLength() ||
+ DependsOnIntrinsicSize(position.MinISize(wm)) ||
+ DependsOnIntrinsicSize(position.MaxISize(wm)) ||
+ aReflowInput.mFrame->IsFlexItem();
+}
+
+nsIFrame* NS_NewImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ImageLoadingContent);
+}
+
+nsIFrame* NS_NewXULImageFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::XULImage);
+}
+
+nsIFrame* NS_NewImageFrameForContentProperty(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ContentProperty);
+}
+
+nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell)
+ nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ContentPropertyAtIndex);
+}
+
+nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell,
+ ComputedStyle* aStyle) {
+ return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
+ nsImageFrame::Kind::ListStyleImage);
+}
+
+bool nsImageFrame::ShouldShowBrokenImageIcon() const {
+ // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and
+ // Blink behave differently here for content: url(..), for now adapt to
+ // Blink's behavior.
+ if (mKind != Kind::ImageLoadingContent) {
+ return false;
+ }
+
+ if (!StaticPrefs::browser_display_show_image_placeholders()) {
+ return false;
+ }
+
+ // <img alt=""> is special, and it shouldn't draw the broken image icon,
+ // unlike the no-alt attribute or non-empty-alt-attribute case.
+ if (auto* image = HTMLImageElement::FromNode(mContent)) {
+ const nsAttrValue* alt = image->GetParsedAttr(nsGkAtoms::alt);
+ if (alt && alt->IsEmptyString()) {
+ return false;
+ }
+ }
+
+ // check for broken images. valid null images (eg. img src="") are
+ // not considered broken because they have no image requests
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ uint32_t imageStatus;
+ return NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) &&
+ (imageStatus & imgIRequest::STATUS_ERROR);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+ // Show the broken image icon only if we've tried to perform a load at all
+ // (that is, if we have a current uri).
+ nsCOMPtr<nsIURI> currentURI = imageLoader->GetCurrentURI();
+ return !!currentURI;
+}
+
+nsImageFrame* nsImageFrame::CreateContinuingFrame(
+ mozilla::PresShell* aPresShell, ComputedStyle* aStyle) const {
+ return new (aPresShell)
+ nsImageFrame(aStyle, aPresShell->GetPresContext(), mKind);
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame)
+
+nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID, Kind aKind)
+ : nsAtomicContainerFrame(aStyle, aPresContext, aID),
+ mIntrinsicSize(0, 0),
+ mKind(aKind) {
+ EnableVisibilityTracking();
+}
+
+nsImageFrame::~nsImageFrame() = default;
+
+NS_QUERYFRAME_HEAD(nsImageFrame)
+ NS_QUERYFRAME_ENTRY(nsImageFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
+
+#ifdef ACCESSIBILITY
+a11y::AccType nsImageFrame::AccessibleType() {
+ if (mKind == Kind::ListStyleImage) {
+ // This is an HTMLListBulletAccessible.
+ return a11y::eNoType;
+ }
+
+ // Don't use GetImageMap() to avoid reentrancy into accessibility.
+ if (HasImageMap()) {
+ return a11y::eHTMLImageMapType;
+ }
+
+ return a11y::eImageType;
+}
+#endif
+
+void nsImageFrame::DisconnectMap() {
+ if (!mImageMap) {
+ return;
+ }
+
+ mImageMap->Destroy();
+ mImageMap = nullptr;
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->RecreateAccessible(PresShell(), mContent);
+ }
+#endif
+}
+
+void nsImageFrame::Destroy(DestroyContext& aContext) {
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing());
+
+ if (mReflowCallbackPosted) {
+ PresShell()->CancelReflowCallback(this);
+ mReflowCallbackPosted = false;
+ }
+
+ // Tell our image map, if there is one, to clean up
+ // This causes the nsImageMap to unregister itself as
+ // a DOM listener.
+ DisconnectMap();
+
+ MOZ_ASSERT(mListener);
+
+ if (mKind == Kind::ImageLoadingContent) {
+ MOZ_ASSERT(!mOwnedRequest);
+ MOZ_ASSERT(!mOwnedRequestRegistered);
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+
+ // Notify our image loading content that we are going away so it can
+ // deregister with our refresh driver.
+ imageLoader->FrameDestroyed(this);
+ imageLoader->RemoveNativeObserver(mListener);
+ } else {
+ DeinitOwnedRequest();
+ }
+
+ // set the frame to null so we don't send messages to a dead object.
+ mListener->SetFrame(nullptr);
+ mListener = nullptr;
+
+ // If we were displaying an icon, take ourselves off the list
+ if (mDisplayingIcon) {
+ BrokenImageIcon::RemoveObserver(this);
+ }
+
+ nsAtomicContainerFrame::Destroy(aContext);
+}
+
+void nsImageFrame::DeinitOwnedRequest() {
+ MOZ_ASSERT(mKind != Kind::ImageLoadingContent);
+ if (!mOwnedRequest) {
+ return;
+ }
+ PresContext()->Document()->ImageTracker()->Remove(mOwnedRequest);
+ nsLayoutUtils::DeregisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ mOwnedRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mOwnedRequest = nullptr;
+}
+
+void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
+ nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle);
+
+ // A ::marker's default size is calculated from the font's em-size.
+ if (IsForMarkerPseudo()) {
+ mIntrinsicSize = IntrinsicSize(0, 0);
+ UpdateIntrinsicSize();
+ }
+
+ // Normal "owned" images reframe when `content` or `list-style-image` change,
+ // but XUL images don't (and we don't really need to). So deal with the
+ // dynamic list-style-image change in that case.
+ //
+ // TODO(emilio): We might want to do the same for regular list-style-image or
+ // even simple content: url() changes.
+ if (mKind == Kind::XULImage) {
+ if (!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src) && aOldStyle &&
+ aOldStyle->StyleList()->mListStyleImage !=
+ StyleList()->mListStyleImage) {
+ UpdateXULImage();
+ }
+ if (!mOwnedRequest && aOldStyle &&
+ aOldStyle->StyleDisplay()->EffectiveAppearance() !=
+ StyleDisplay()->EffectiveAppearance()) {
+ UpdateIntrinsicSize();
+ }
+ }
+
+ // We need to update our orientation either if we had no ComputedStyle before
+ // because this is the first time it's been set, or if the image-orientation
+ // property changed from its previous value.
+ bool shouldUpdateOrientation = false;
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+ const auto newOrientation =
+ StyleVisibility()->UsedImageOrientation(currentRequest);
+ if (mImage) {
+ if (aOldStyle) {
+ auto oldOrientation =
+ aOldStyle->StyleVisibility()->UsedImageOrientation(currentRequest);
+ shouldUpdateOrientation = oldOrientation != newOrientation;
+ } else {
+ shouldUpdateOrientation = true;
+ }
+ }
+
+ if (shouldUpdateOrientation) {
+ nsCOMPtr<imgIContainer> image(mImage->Unwrap());
+ mImage = nsLayoutUtils::OrientImage(image, newOrientation);
+
+ UpdateIntrinsicSize();
+ UpdateIntrinsicRatio();
+ } else if (!aOldStyle || aOldStyle->StylePosition()->mAspectRatio !=
+ StylePosition()->mAspectRatio) {
+ UpdateIntrinsicRatio();
+ }
+}
+
+static bool SizeIsAvailable(imgIRequest* aRequest) {
+ if (!aRequest) {
+ return false;
+ }
+
+ uint32_t imageStatus = 0;
+ nsresult rv = aRequest->GetImageStatus(&imageStatus);
+ return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE);
+}
+
+const StyleImage* nsImageFrame::GetImageFromStyle() const {
+ switch (mKind) {
+ case Kind::ImageLoadingContent:
+ break;
+ case Kind::ListStyleImage:
+ MOZ_ASSERT(
+ GetParent()->GetContent()->IsGeneratedContentContainerForMarker());
+ MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
+ return &StyleList()->mListStyleImage;
+ case Kind::XULImage:
+ MOZ_ASSERT(!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src));
+ return &StyleList()->mListStyleImage;
+ case Kind::ContentProperty:
+ case Kind::ContentPropertyAtIndex: {
+ uint32_t contentIndex = 0;
+ const nsStyleContent* styleContent = StyleContent();
+ if (mKind == Kind::ContentPropertyAtIndex) {
+ MOZ_RELEASE_ASSERT(
+ mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
+ contentIndex =
+ static_cast<GeneratedImageContent*>(mContent.get())->Index();
+
+ // TODO(emilio): Consider inheriting the `content` property instead of
+ // doing this parent traversal?
+ nsIFrame* parent = GetParent();
+ MOZ_DIAGNOSTIC_ASSERT(
+ parent->GetContent()->IsGeneratedContentContainerForMarker() ||
+ parent->GetContent()->IsGeneratedContentContainerForAfter() ||
+ parent->GetContent()->IsGeneratedContentContainerForBefore());
+ nsIFrame* nonAnonymousParent = parent;
+ while (nonAnonymousParent->Style()->IsAnonBox()) {
+ nonAnonymousParent = nonAnonymousParent->GetParent();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(parent->GetContent() ==
+ nonAnonymousParent->GetContent());
+ styleContent = nonAnonymousParent->StyleContent();
+ }
+ MOZ_RELEASE_ASSERT(contentIndex < styleContent->ContentCount());
+ auto& contentItem = styleContent->ContentAt(contentIndex);
+ MOZ_RELEASE_ASSERT(contentItem.IsImage());
+ return &contentItem.AsImage();
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Don't call me");
+ return nullptr;
+}
+
+void nsImageFrame::UpdateXULImage() {
+ MOZ_ASSERT(mKind == Kind::XULImage);
+ DeinitOwnedRequest();
+
+ nsAutoString src;
+ nsPresContext* pc = PresContext();
+ if (mContent->AsElement()->GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) {
+ nsContentPolicyType contentPolicyType;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ uint64_t requestContextID = 0;
+ nsContentUtils::GetContentPolicyTypeForUIImageLoading(
+ mContent, getter_AddRefs(triggeringPrincipal), contentPolicyType,
+ &requestContextID);
+ nsCOMPtr<nsIURI> uri;
+ nsContentUtils::NewURIWithDocumentCharset(
+ getter_AddRefs(uri), src, pc->Document(), mContent->GetBaseURI());
+ if (uri) {
+ auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mContent->AsElement());
+ nsContentUtils::LoadImage(
+ uri, mContent, pc->Document(), triggeringPrincipal, requestContextID,
+ referrerInfo, mListener, nsIRequest::LOAD_NORMAL, u""_ns,
+ getter_AddRefs(mOwnedRequest), contentPolicyType);
+ SetupOwnedRequest();
+ }
+ } else {
+ const auto* image = GetImageFromStyle();
+ if (image->IsImageRequestType()) {
+ if (imgRequestProxy* proxy = image->GetImageRequest()) {
+ proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest));
+ SetupOwnedRequest();
+ }
+ }
+ }
+
+ if (!mOwnedRequest) {
+ UpdateImage(nullptr, nullptr);
+ }
+}
+
+void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ MOZ_ASSERT_IF(aPrevInFlow,
+ aPrevInFlow->Type() == Type() &&
+ static_cast<nsImageFrame*>(aPrevInFlow)->mKind == mKind);
+
+ nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ mListener = new nsImageListener(this);
+
+ GetImageMap(); // Ensure to init the image map asap. This is important to
+ // make <area> elements focusable.
+
+ if (StaticPrefs::layout_image_eager_broken_image_icon()) {
+ Unused << BrokenImageIcon::GetImage(this);
+ }
+
+ nsPresContext* pc = PresContext();
+ if (mKind == Kind::ImageLoadingContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aContent);
+ MOZ_ASSERT(imageLoader);
+ imageLoader->AddNativeObserver(mListener);
+ // We have a PresContext now, so we need to notify the image content node
+ // that it can register images.
+ imageLoader->FrameCreated(this);
+ AssertSyncDecodingHintIsInSync();
+ if (nsIDocShell* docShell = pc->GetDocShell()) {
+ RefPtr<BrowsingContext> bc = docShell->GetBrowsingContext();
+ mIsInObjectOrEmbed = bc->IsEmbedderTypeObjectOrEmbed() &&
+ pc->Document()->IsImageDocument();
+ }
+ } else if (mKind == Kind::XULImage) {
+ UpdateXULImage();
+ } else {
+ const StyleImage* image = GetImageFromStyle();
+ if (image->IsImageRequestType()) {
+ if (imgRequestProxy* proxy = image->GetImageRequest()) {
+ proxy->Clone(mListener, pc->Document(), getter_AddRefs(mOwnedRequest));
+ SetupOwnedRequest();
+ }
+ }
+ }
+
+ // Give image loads associated with an image frame a small priority boost.
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ uint32_t categoryToBoostPriority = imgIRequest::CATEGORY_FRAME_INIT;
+
+ // Increase load priority further if intrinsic size might be important for
+ // layout.
+ if (!HaveSpecifiedSize(StylePosition())) {
+ categoryToBoostPriority |= imgIRequest::CATEGORY_SIZE_QUERY;
+ }
+
+ currentRequest->BoostPriority(categoryToBoostPriority);
+ }
+
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+}
+
+void nsImageFrame::SetupOwnedRequest() {
+ MOZ_ASSERT(mKind != Kind::ImageLoadingContent);
+ if (!mOwnedRequest) {
+ return;
+ }
+
+ // We're not using AssociateRequestToFrame for the content property, so we
+ // need to add it to the image tracker manually.
+ PresContext()->Document()->ImageTracker()->Add(mOwnedRequest);
+
+ uint32_t status = 0;
+ nsresult rv = mOwnedRequest->GetImageStatus(&status);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ mOwnedRequest->GetImage(getter_AddRefs(image));
+ OnSizeAvailable(mOwnedRequest, image);
+ }
+
+ if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
+ mFirstFrameComplete = true;
+ }
+
+ if (status & imgIRequest::STATUS_IS_ANIMATED) {
+ nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ }
+}
+
+static void ScaleIntrinsicSizeForDensity(IntrinsicSize& aSize,
+ const ImageResolution& aResolution) {
+ if (aSize.width) {
+ aResolution.ApplyXTo(aSize.width.ref());
+ }
+ if (aSize.height) {
+ aResolution.ApplyYTo(aSize.height.ref());
+ }
+}
+
+static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage,
+ nsIContent& aContent,
+ IntrinsicSize& aSize) {
+ ImageResolution resolution = aImage->GetResolution();
+ if (auto* image = HTMLImageElement::FromNode(aContent)) {
+ if (auto* selector = image->GetResponsiveImageSelector()) {
+ resolution.ScaleBy(selector->GetSelectedImageDensity());
+ }
+ }
+ ScaleIntrinsicSizeForDensity(aSize, resolution);
+}
+
+static nscoord ListImageDefaultLength(const nsImageFrame& aFrame) {
+ // https://drafts.csswg.org/css-lists-3/#image-markers
+ // The spec says we should use 1em x 1em, but that seems too large.
+ // See disussion in https://github.com/w3c/csswg-drafts/issues/4207
+ auto* pc = aFrame.PresContext();
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc);
+ RefPtr<gfxFont> font = fm->GetThebesFontGroup()->GetFirstValidFont();
+ auto emAU =
+ font->GetMetrics(fm->Orientation()).emHeight * pc->AppUnitsPerDevPixel();
+ return std::max(NSToCoordRound(0.4f * emAU),
+ nsPresContext::CSSPixelsToAppUnits(1));
+}
+
+static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
+ bool aUseMappedRatio,
+ nsImageFrame::Kind aKind,
+ const nsImageFrame& aFrame) {
+ const auto containAxes = aFrame.GetContainSizeAxes();
+ if (containAxes.IsBoth()) {
+ return aFrame.FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0));
+ }
+
+ nsSize size;
+ if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) {
+ IntrinsicSize intrinsicSize;
+ intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width);
+ intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height);
+ if (aKind == nsImageFrame::Kind::ListStyleImage) {
+ if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) {
+ nscoord defaultLength = ListImageDefaultLength(aFrame);
+ if (intrinsicSize.width.isNothing()) {
+ intrinsicSize.width = Some(defaultLength);
+ }
+ if (intrinsicSize.height.isNothing()) {
+ intrinsicSize.height = Some(defaultLength);
+ }
+ }
+ }
+ if (aKind == nsImageFrame::Kind::ImageLoadingContent ||
+ (aKind == nsImageFrame::Kind::XULImage &&
+ aFrame.GetContent()->AsElement()->HasNonEmptyAttr(nsGkAtoms::src))) {
+ ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize);
+ } else {
+ ScaleIntrinsicSizeForDensity(
+ intrinsicSize,
+ aFrame.GetImageFromStyle()->GetResolution(*aFrame.Style()));
+ }
+ return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize);
+ }
+
+ if (aKind == nsImageFrame::Kind::ListStyleImage) {
+ // Note: images are handled above, this handles gradients etc.
+ const nscoord defaultLength = ListImageDefaultLength(aFrame);
+ return aFrame.FinishIntrinsicSize(
+ containAxes, IntrinsicSize(defaultLength, defaultLength));
+ }
+
+ if (aKind == nsImageFrame::Kind::XULImage && aFrame.IsThemed()) {
+ nsPresContext* pc = aFrame.PresContext();
+ // FIXME: const_cast here is a bit evil but IsThemed and so does the same.
+ const auto widgetSize = pc->Theme()->GetMinimumWidgetSize(
+ pc, const_cast<nsImageFrame*>(&aFrame),
+ aFrame.StyleDisplay()->EffectiveAppearance());
+ const IntrinsicSize intrinsicSize(
+ LayoutDeviceIntSize::ToAppUnits(widgetSize, pc->AppUnitsPerDevPixel()));
+ return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize);
+ }
+
+ if (aFrame.ShouldShowBrokenImageIcon()) {
+ nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
+ return aFrame.FinishIntrinsicSize(
+ containAxes, IntrinsicSize(edgeLengthToUse, edgeLengthToUse));
+ }
+
+ if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio.HasRatio()) {
+ return IntrinsicSize();
+ }
+
+ // XXX: No FinishIntrinsicSize?
+ return IntrinsicSize(0, 0);
+}
+
+// For compat reasons, see bug 1602047, we don't use the intrinsic ratio from
+// width="" and height="" for images with no src attribute (no request).
+//
+// But we shouldn't get fooled by <img loading=lazy>. We do want to apply the
+// ratio then...
+bool nsImageFrame::ShouldUseMappedAspectRatio() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ return true;
+ }
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+ if (currentRequest) {
+ return true;
+ }
+ // TODO(emilio): Investigate the compat situation of the above check, maybe we
+ // can just check for empty src attribute or something...
+ auto* image = HTMLImageElement::FromNode(mContent);
+ return image && image->IsAwaitingLoadOrLazyLoading();
+}
+
+bool nsImageFrame::UpdateIntrinsicSize() {
+ IntrinsicSize oldIntrinsicSize = mIntrinsicSize;
+ mIntrinsicSize =
+ ComputeIntrinsicSize(mImage, ShouldUseMappedAspectRatio(), mKind, *this);
+ return mIntrinsicSize != oldIntrinsicSize;
+}
+
+static AspectRatio ComputeIntrinsicRatio(imgIContainer* aImage,
+ bool aUseMappedRatio,
+ const nsImageFrame& aFrame) {
+ if (aFrame.GetContainSizeAxes().IsAny()) {
+ return AspectRatio();
+ }
+
+ if (aImage) {
+ if (Maybe<AspectRatio> fromImage = aImage->GetIntrinsicRatio()) {
+ return *fromImage;
+ }
+ }
+ if (aUseMappedRatio) {
+ const StyleAspectRatio& ratio = aFrame.StylePosition()->mAspectRatio;
+ if (ratio.auto_ && ratio.HasRatio()) {
+ // Return the mapped intrinsic aspect ratio stored in
+ // nsStylePosition::mAspectRatio.
+ return ratio.ratio.AsRatio().ToLayoutRatio(UseBoxSizing::Yes);
+ }
+ }
+ if (aFrame.ShouldShowBrokenImageIcon()) {
+ return AspectRatio(1.0f);
+ }
+ return AspectRatio();
+}
+
+bool nsImageFrame::UpdateIntrinsicRatio() {
+ AspectRatio oldIntrinsicRatio = mIntrinsicRatio;
+ mIntrinsicRatio =
+ ComputeIntrinsicRatio(mImage, ShouldUseMappedAspectRatio(), *this);
+ return mIntrinsicRatio != oldIntrinsicRatio;
+}
+
+bool nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) {
+ // First, figure out destRect (the rect we're rendering into).
+ // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
+ // own size here, because GetContentRectRelativeToSelf() might be smaller if
+ // we're fragmented, whereas mComputedSize has our full content-box size
+ // (which we need for ComputeObjectDestRect to work correctly).
+ nsRect constraintRect(GetContentRectRelativeToSelf().TopLeft(),
+ mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ nsRect destRect = nsLayoutUtils::ComputeObjectDestRect(
+ constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition());
+ // Set the translation components, based on destRect
+ // XXXbz does this introduce rounding errors because of the cast to
+ // float? Should we just manually add that stuff in every time
+ // instead?
+ aTransform.SetToTranslate(float(destRect.x), float(destRect.y));
+
+ // NOTE(emilio): This intrinsicSize is not the same as the layout intrinsic
+ // size (mIntrinsicSize), which can be scaled due to ResponsiveImageSelector,
+ // see ScaleIntrinsicSizeForDensity.
+ nsSize intrinsicSize;
+ if (!mImage || !NS_SUCCEEDED(mImage->GetIntrinsicSize(&intrinsicSize)) ||
+ intrinsicSize.IsEmpty()) {
+ return false;
+ }
+
+ aTransform.SetScale(float(destRect.width) / float(intrinsicSize.width),
+ float(destRect.height) / float(intrinsicSize.height));
+ return true;
+}
+
+// This function checks whether the given request is the current request for our
+// mContent.
+bool nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const {
+ // Default to pending load in case of errors
+ if (mKind != Kind::ImageLoadingContent) {
+ MOZ_ASSERT(aRequest == mOwnedRequest);
+ return false;
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> imageLoader(do_QueryInterface(mContent));
+ MOZ_ASSERT(imageLoader);
+
+ int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST;
+ imageLoader->GetRequestType(aRequest, &requestType);
+
+ return requestType != nsIImageLoadingContent::CURRENT_REQUEST;
+}
+
+nsRect nsImageFrame::SourceRectToDest(const nsIntRect& aRect) {
+ // When scaling the image, row N of the source image may (depending on
+ // the scaling function) be used to draw any row in the destination image
+ // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the
+ // floating-point scaling factor. The same holds true for columns.
+ // So, we start by computing that bound without the floor and ceiling.
+
+ nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1),
+ nsPresContext::CSSPixelsToAppUnits(aRect.y - 1),
+ nsPresContext::CSSPixelsToAppUnits(aRect.width + 2),
+ nsPresContext::CSSPixelsToAppUnits(aRect.height + 2));
+
+ nsTransform2D sourceToDest;
+ if (!GetSourceToDestTransform(sourceToDest)) {
+ // Failed to generate transform matrix. Return our whole content area,
+ // to be on the safe side (since this method is used for generating
+ // invalidation rects).
+ return GetContentRectRelativeToSelf();
+ }
+
+ sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height);
+
+ // Now, round the edges out to the pixel boundary.
+ nscoord scale = nsPresContext::CSSPixelsToAppUnits(1);
+ nscoord right = r.x + r.width;
+ nscoord bottom = r.y + r.height;
+
+ r.x -= (scale + (r.x % scale)) % scale;
+ r.y -= (scale + (r.y % scale)) % scale;
+ r.width = right + ((scale - (right % scale)) % scale) - r.x;
+ r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y;
+
+ return r;
+}
+
+static bool ImageOk(ElementState aState) {
+ return !aState.HasState(ElementState::BROKEN);
+}
+
+static bool HasAltText(const Element& aElement) {
+ // We always return some alternate text for <input>, see
+ // nsCSSFrameConstructor::GetAlternateTextFor.
+ if (aElement.IsHTMLElement(nsGkAtoms::input)) {
+ return true;
+ }
+
+ MOZ_ASSERT(aElement.IsHTMLElement(nsGkAtoms::img));
+ return aElement.HasNonEmptyAttr(nsGkAtoms::alt);
+}
+
+bool nsImageFrame::ShouldCreateImageFrameForContentProperty(
+ const Element& aElement, const ComputedStyle& aStyle) {
+ if (aElement.IsRootOfNativeAnonymousSubtree()) {
+ return false;
+ }
+ const auto& content = aStyle.StyleContent()->mContent;
+ if (!content.IsItems()) {
+ return false;
+ }
+ Span<const StyleContentItem> items = content.AsItems().AsSpan();
+ return items.Length() == 1 && items[0].IsImage();
+}
+
+// Check if we want to use an image frame or just let the frame constructor make
+// us into an inline, and if so, which kind of image frame should we create.
+/* static */
+auto nsImageFrame::ImageFrameTypeFor(const Element& aElement,
+ const ComputedStyle& aStyle)
+ -> ImageFrameType {
+ if (ShouldCreateImageFrameForContentProperty(aElement, aStyle)) {
+ // Prefer the content property, for compat reasons, see bug 1484928.
+ return ImageFrameType::ForContentProperty;
+ }
+
+ if (ImageOk(aElement.State())) {
+ // Image is fine or loading; do the image frame thing
+ return ImageFrameType::ForElementRequest;
+ }
+
+ if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ if (!HasAltText(aElement)) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ // FIXME(emilio, bug 1788767): We definitely don't reframe when
+ // HaveSpecifiedSize changes...
+ if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks &&
+ HaveSpecifiedSize(aStyle.StylePosition())) {
+ return ImageFrameType::ForElementRequest;
+ }
+
+ return ImageFrameType::None;
+}
+
+void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aRect) {
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ return OnSizeAvailable(aRequest, image);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ return OnFrameUpdate(aRequest, aRect);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ mFirstFrameComplete = true;
+ }
+
+ if (aType == imgINotificationObserver::IS_ANIMATED &&
+ mKind != Kind::ImageLoadingContent) {
+ nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest,
+ &mOwnedRequestRegistered);
+ }
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ LargestContentfulPaint::MaybeProcessImageForElementTiming(
+ static_cast<imgRequestProxy*>(aRequest), GetContent()->AsElement());
+ uint32_t imgStatus;
+ aRequest->GetImageStatus(&imgStatus);
+ nsresult status =
+ imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+ return OnLoadComplete(aRequest, status);
+ }
+}
+
+void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest,
+ imgIContainer* aImage) {
+ if (!aImage) {
+ return;
+ }
+
+ /* Get requested animation policy from the pres context:
+ * normal = 0
+ * one frame = 1
+ * one loop = 2
+ */
+ aImage->SetAnimationMode(PresContext()->ImageAnimationMode());
+
+ if (IsPendingLoad(aRequest)) {
+ // We don't care
+ return;
+ }
+
+ UpdateImage(aRequest, aImage);
+}
+
+void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) {
+ if (SizeIsAvailable(aRequest)) {
+ StyleImageOrientation orientation =
+ StyleVisibility()->UsedImageOrientation(aRequest);
+ // This is valid and for the current request, so update our stored image
+ // container, orienting according to our style.
+ mImage = nsLayoutUtils::OrientImage(aImage, orientation);
+ MOZ_ASSERT(mImage);
+ } else {
+ // We no longer have a valid image, so release our stored image container.
+ mImage = mPrevImage = nullptr;
+ if (mKind == Kind::ListStyleImage) {
+ auto* genContent = static_cast<GeneratedImageContent*>(GetContent());
+ genContent->NotifyLoadFailed();
+ // No need to continue below since the above state change will destroy
+ // this frame.
+ return;
+ }
+ }
+
+ UpdateIntrinsicSizeAndRatio();
+
+ if (!GotInitialReflow()) {
+ return;
+ }
+
+ // We're going to need to repaint now either way.
+ InvalidateFrame();
+}
+
+void nsImageFrame::OnFrameUpdate(imgIRequest* aRequest,
+ const nsIntRect* aRect) {
+ if (NS_WARN_IF(!aRect)) {
+ return;
+ }
+
+ if (!GotInitialReflow()) {
+ // Don't bother to do anything; we have a reflow coming up!
+ return;
+ }
+
+ if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ if (IsPendingLoad(aRequest)) {
+ // We don't care
+ return;
+ }
+
+ nsIntRect layerInvalidRect =
+ mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect;
+
+ if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) {
+ // Invalidate our entire area.
+ InvalidateSelf(nullptr, nullptr);
+ return;
+ }
+
+ nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect);
+ InvalidateSelf(&layerInvalidRect, &frameInvalidRect);
+}
+
+void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect,
+ const nsRect* aFrameInvalidRect) {
+ // Check if WebRender has interacted with this frame. If it has
+ // we need to let it know that things have changed.
+ const auto type = DisplayItemType::TYPE_IMAGE;
+ const auto providerId = mImage ? mImage->GetProviderId() : 0;
+ if (WebRenderUserData::ProcessInvalidateForImage(this, type, providerId)) {
+ return;
+ }
+
+ InvalidateLayer(type, aLayerInvalidRect, aFrameInvalidRect);
+
+ if (!mFirstFrameComplete) {
+ InvalidateLayer(DisplayItemType::TYPE_ALT_FEEDBACK, aLayerInvalidRect,
+ aFrameInvalidRect);
+ }
+}
+
+void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder() {
+ MaybeSendIntrinsicSizeAndRatioToEmbedder(Some(GetIntrinsicSize()),
+ Some(GetAspectRatio()));
+}
+
+void nsImageFrame::MaybeSendIntrinsicSizeAndRatioToEmbedder(
+ Maybe<IntrinsicSize> aIntrinsicSize, Maybe<AspectRatio> aIntrinsicRatio) {
+ if (!mIsInObjectOrEmbed || !mImage) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = PresContext()->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ MOZ_ASSERT(bc->IsContentSubframe());
+
+ if (bc->GetParent()->IsInProcess()) {
+ if (Element* embedder = bc->GetEmbedderElement()) {
+ if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(embedder)) {
+ static_cast<nsObjectLoadingContent*>(olc.get())
+ ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Got out of sync?");
+ }
+ return;
+ }
+ }
+
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
+ Unused << browserChild->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize,
+ aIntrinsicRatio);
+ }
+}
+
+void nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) {
+ NotifyNewCurrentRequest(aRequest, aStatus);
+}
+
+void nsImageFrame::ElementStateChanged(ElementState aStates) {
+ if (!(aStates & ElementState::BROKEN)) {
+ return;
+ }
+ if (mKind != Kind::ImageLoadingContent) {
+ return;
+ }
+ if (!ImageOk(mContent->AsElement()->State())) {
+ UpdateImage(nullptr, nullptr);
+ }
+}
+
+void nsImageFrame::ResponsiveContentDensityChanged() {
+ UpdateIntrinsicSizeAndRatio();
+}
+
+void nsImageFrame::UpdateIntrinsicSizeAndRatio() {
+ bool intrinsicSizeOrRatioChanged = [&] {
+ // NOTE(emilio): We intentionally want to call both functions and avoid
+ // short-circuiting.
+ bool intrinsicSizeChanged = UpdateIntrinsicSize();
+ bool intrinsicRatioChanged = UpdateIntrinsicRatio();
+ return intrinsicSizeChanged || intrinsicRatioChanged;
+ }();
+
+ if (!intrinsicSizeOrRatioChanged) {
+ return;
+ }
+
+ // Our aspect-ratio property value changed, and an embedding <object> or
+ // <embed> might care about that.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+
+ if (!GotInitialReflow()) {
+ return;
+ }
+
+ // Now we need to reflow if we have an unconstrained size and have
+ // already gotten the initial reflow.
+ if (!HasAnyStateBits(IMAGE_SIZECONSTRAINED)) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ } else if (PresShell()->IsActive()) {
+ // We've already gotten the initial reflow, and our size hasn't changed,
+ // so we're ready to request a decode.
+ MaybeDecodeForPredictedSize();
+ }
+}
+
+void nsImageFrame::NotifyNewCurrentRequest(imgIRequest* aRequest,
+ nsresult aStatus) {
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ NS_ASSERTION(image || NS_FAILED(aStatus),
+ "Successful load with no container?");
+ UpdateImage(aRequest, image);
+}
+
+void nsImageFrame::MaybeDecodeForPredictedSize() {
+ // Check that we're ready to decode.
+ if (!mImage) {
+ return; // Nothing to do yet.
+ }
+
+ if (mComputedSize.IsEmpty()) {
+ return; // We won't draw anything, so no point in decoding.
+ }
+
+ if (GetVisibility() != Visibility::ApproximatelyVisible) {
+ return; // We're not visible, so don't decode.
+ }
+
+ // OK, we're ready to decode. Compute the scale to the screen...
+ mozilla::PresShell* presShell = PresShell();
+ MatrixScales scale =
+ ScaleFactor<UnknownUnits, UnknownUnits>(
+ presShell->GetCumulativeResolution()) *
+ nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this);
+ auto resolutionToScreen = ViewAs<LayoutDeviceToScreenScale2D>(scale);
+
+ // If we are in a remote browser, then apply scaling from ancestor browsers
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
+ resolutionToScreen =
+ resolutionToScreen * ViewAs<ScreenToScreenScale2D>(
+ browserChild->GetEffectsInfo().mRasterScale);
+ }
+
+ // ...and this frame's content box...
+ const nsPoint offset =
+ GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this));
+ const nsRect frameContentBox = GetContentRectRelativeToSelf() + offset;
+
+ // ...and our predicted dest rect...
+ const int32_t factor = PresContext()->AppUnitsPerDevPixel();
+ const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(
+ PredictedDestRect(frameContentBox), factor);
+
+ // ...and use them to compute our predicted size in screen pixels.
+ const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen;
+ const ScreenIntSize predictedScreenIntSize =
+ RoundedToInt(predictedScreenSize);
+ if (predictedScreenIntSize.IsEmpty()) {
+ return;
+ }
+
+ // Determine the optimal image size to use.
+ uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
+ imgIContainer::FLAG_ASYNC_NOTIFY;
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(this);
+ gfxSize gfxPredictedScreenSize =
+ gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height);
+ nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest(
+ gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter,
+ flags);
+
+ // Request a decode.
+ mImage->RequestDecodeForSize(predictedImageSize, flags);
+}
+
+nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) {
+ // Note: To get the "dest rect", we have to provide the "constraint rect"
+ // (which is the content-box, with the effects of fragmentation undone).
+ nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ return nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize,
+ mIntrinsicRatio, StylePosition());
+}
+
+bool nsImageFrame::IsForMarkerPseudo() const {
+ if (mKind == Kind::ImageLoadingContent) {
+ return false;
+ }
+ auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot();
+ return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker();
+}
+
+void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
+ const auto containAxes = GetContainSizeAxes();
+ if (containAxes.IsBoth()) {
+ // If we have 'contain:size', then we have no intrinsic aspect ratio,
+ // and the intrinsic size is determined by contain-intrinsic-size,
+ // regardless of what our underlying image may think.
+ mIntrinsicSize = FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0));
+ mIntrinsicRatio = AspectRatio();
+ return;
+ }
+
+ // If mIntrinsicSize.width and height are 0, then we need to update from the
+ // image container. Note that we handle ::marker intrinsic size/ratio in
+ // DidSetComputedStyle.
+ if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) {
+ return;
+ }
+
+ bool intrinsicSizeOrRatioChanged = UpdateIntrinsicSize();
+ intrinsicSizeOrRatioChanged =
+ UpdateIntrinsicRatio() || intrinsicSizeOrRatioChanged;
+
+ if (intrinsicSizeOrRatioChanged) {
+ // Our aspect-ratio property value changed, and an embedding <object> or
+ // <embed> might care about that.
+ MaybeSendIntrinsicSizeAndRatioToEmbedder();
+ }
+}
+
+nsIFrame::SizeComputationResult nsImageFrame::ComputeSize(
+ gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
+ nscoord aAvailableISize, const LogicalSize& aMargin,
+ const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
+ ComputeSizeFlags aFlags) {
+ EnsureIntrinsicSizeAndRatio();
+ return {ComputeSizeWithIntrinsicDimensions(
+ aRenderingContext, aWM, mIntrinsicSize, GetAspectRatio(), aCBSize,
+ aMargin, aBorderPadding, aSizeOverrides, aFlags),
+ AspectRatioUsage::None};
+}
+
+Element* nsImageFrame::GetMapElement() const {
+ return IsForImageLoadingContent()
+ ? nsImageLoadingContent::FindImageMap(mContent->AsElement())
+ : nullptr;
+}
+
+// get the offset into the content area of the image where aImg starts if it is
+// a continuation.
+nscoord nsImageFrame::GetContinuationOffset() const {
+ nscoord offset = 0;
+ for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) {
+ offset += f->GetContentRect().height;
+ }
+ NS_ASSERTION(offset >= 0, "bogus GetContentRect");
+ return offset;
+}
+
+nscoord nsImageFrame::GetMinISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the block-size,
+ // min-block-size, and max-block-size properties.
+ DebugOnly<nscoord> result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+ EnsureIntrinsicSizeAndRatio();
+ const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
+ : mIntrinsicSize.width;
+ return iSize.valueOr(0);
+}
+
+nscoord nsImageFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ // XXX The caller doesn't account for constraints of the block-size,
+ // min-block-size, and max-block-size properties.
+ DebugOnly<nscoord> result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+ EnsureIntrinsicSizeAndRatio();
+ const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height
+ : mIntrinsicSize.width;
+ // convert from normal twips to scaled twips (printing...)
+ return iSize.valueOr(0);
+}
+
+void nsImageFrame::ReflowChildren(nsPresContext* aPresContext,
+ const ReflowInput& aReflowInput,
+ const LogicalSize& aImageSize) {
+ for (nsIFrame* child : mFrames) {
+ ReflowOutput childDesiredSize(aReflowInput);
+ WritingMode wm = GetWritingMode();
+ // Shouldn't be hard to support if we want, but why bother.
+ MOZ_ASSERT(
+ wm == child->GetWritingMode(),
+ "We don't expect mismatched writing-modes in content we control");
+ nsReflowStatus childStatus;
+
+ LogicalPoint childOffset(wm);
+ ReflowInput childReflowInput(aPresContext, aReflowInput, child, aImageSize);
+ const nsSize containerSize = aImageSize.GetPhysicalSize(wm);
+ ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, wm,
+ childOffset, containerSize, ReflowChildFlags::Default,
+ childStatus);
+
+ FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput,
+ wm, childOffset, containerSize,
+ ReflowChildFlags::Default);
+ }
+}
+
+void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ DO_GLOBAL_REFLOW_COUNT("nsImageFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+ NS_FRAME_TRACE(
+ NS_FRAME_TRACE_CALLS,
+ ("enter nsImageFrame::Reflow: availSize=%d,%d",
+ aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
+
+ MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow");
+
+ // see if we have a frozen size (i.e. a fixed width and height)
+ if (!SizeDependsOnIntrinsicSize(aReflowInput)) {
+ AddStateBits(IMAGE_SIZECONSTRAINED);
+ } else {
+ RemoveStateBits(IMAGE_SIZECONSTRAINED);
+ }
+
+ mComputedSize = aReflowInput.ComputedPhysicalSize();
+
+ const auto wm = GetWritingMode();
+ aMetrics.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm));
+
+ if (GetPrevInFlow()) {
+ aMetrics.Width() = GetPrevInFlow()->GetSize().width;
+ nscoord y = GetContinuationOffset();
+ aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top;
+ aMetrics.Height() = std::max(0, aMetrics.Height());
+ }
+
+ // we have to split images if we are:
+ // in Paginated mode, we need to have a constrained height, and have a height
+ // larger than our available height
+ uint32_t loadStatus = imgIRequest::STATUS_NONE;
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ currentRequest->GetImageStatus(&loadStatus);
+ }
+
+ if (aPresContext->IsPaginated() &&
+ ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) ||
+ HasAnyStateBits(IMAGE_SIZECONSTRAINED)) &&
+ NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableHeight() &&
+ aMetrics.Height() > aReflowInput.AvailableHeight()) {
+ // our desired height was greater than 0, so to avoid infinite
+ // splitting, use 1 pixel as the min
+ aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1),
+ aReflowInput.AvailableHeight());
+ aStatus.SetIncomplete();
+ }
+
+ aMetrics.SetOverflowAreasToDesiredBounds();
+ bool imageOK = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Determine if the size is available
+ bool haveSize = false;
+ if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) {
+ haveSize = true;
+ }
+
+ if (!imageOK || !haveSize) {
+ nsRect altFeedbackSize(
+ 0, 0,
+ nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)),
+ nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + 2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
+ // We include the altFeedbackSize in our ink overflow, but not in our
+ // scrollable overflow, since it doesn't really need to be scrolled to
+ // outside the image.
+ nsRect& inkOverflow = aMetrics.InkOverflow();
+ inkOverflow.UnionRect(inkOverflow, altFeedbackSize);
+ } else if (PresShell()->IsActive()) {
+ // We've just reflowed and we should have an accurate size, so we're ready
+ // to request a decode.
+ MaybeDecodeForPredictedSize();
+ }
+ FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
+
+ // Reflow the child frames. Our children can't affect our size in any way.
+ ReflowChildren(aPresContext, aReflowInput, aMetrics.Size(GetWritingMode()));
+
+ if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) {
+ mReflowCallbackPosted = true;
+ PresShell()->PostReflowCallback(this);
+ }
+
+ NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d",
+ aMetrics.Width(), aMetrics.Height()));
+}
+
+bool nsImageFrame::ReflowFinished() {
+ mReflowCallbackPosted = false;
+
+ // XXX(seth): We don't need this. The purpose of updating visibility
+ // synchronously is to ensure that animated images start animating
+ // immediately. In the short term, however,
+ // nsImageLoadingContent::OnUnlockedDraw() is enough to ensure that
+ // animations start as soon as the image is painted for the first time, and in
+ // the long term we want to update visibility information from the display
+ // list whenever we paint, so we don't actually need to do this. However, to
+ // avoid behavior changes during the transition from the old image visibility
+ // code, we'll leave it in for now.
+ UpdateVisibilitySynchronously();
+
+ return false;
+}
+
+void nsImageFrame::ReflowCallbackCanceled() { mReflowCallbackPosted = false; }
+
+// Computes the width of the specified string. aMaxWidth specifies the maximum
+// width available. Once this limit is reached no more characters are measured.
+// The number of characters that fit within the maximum width are returned in
+// aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected
+// into the rendering context before this is called (for performance). MMP
+nscoord nsImageFrame::MeasureString(const char16_t* aString, int32_t aLength,
+ nscoord aMaxWidth, uint32_t& aMaxFit,
+ gfxContext& aContext,
+ nsFontMetrics& aFontMetrics) {
+ nscoord totalWidth = 0;
+ aFontMetrics.SetTextRunRTL(false);
+ nscoord spaceWidth = aFontMetrics.SpaceWidth();
+
+ aMaxFit = 0;
+ while (aLength > 0) {
+ // Find the next place we can line break
+ uint32_t len = aLength;
+ bool trailingSpace = false;
+ for (int32_t i = 0; i < aLength; i++) {
+ if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) {
+ len = i; // don't include the space when measuring
+ trailingSpace = true;
+ break;
+ }
+ }
+
+ // Measure this chunk of text, and see if it fits
+ nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
+ aString, len, this, aFontMetrics, aContext);
+ bool fits = (totalWidth + width) <= aMaxWidth;
+
+ // If it fits on the line, or it's the first word we've processed then
+ // include it
+ if (fits || (0 == totalWidth)) {
+ // New piece fits
+ totalWidth += width;
+
+ // If there's a trailing space then see if it fits as well
+ if (trailingSpace) {
+ if ((totalWidth + spaceWidth) <= aMaxWidth) {
+ totalWidth += spaceWidth;
+ } else {
+ // Space won't fit. Leave it at the end but don't include it in
+ // the width
+ fits = false;
+ }
+
+ len++;
+ }
+
+ aMaxFit += len;
+ aString += len;
+ aLength -= len;
+ }
+
+ if (!fits) {
+ break;
+ }
+ }
+ return totalWidth;
+}
+
+// Formats the alt-text to fit within the specified rectangle. Breaks lines
+// between words if a word would extend past the edge of the rectangle
+void nsImageFrame::DisplayAltText(nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ const nsString& aAltText,
+ const nsRect& aRect) {
+ // Set font and color
+ aRenderingContext.SetColor(
+ sRGBColor::FromABGR(StyleText()->mColor.ToColor()));
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
+
+ // Format the text to display within the formatting rect
+
+ nscoord maxAscent = fm->MaxAscent();
+ nscoord maxDescent = fm->MaxDescent();
+ nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate
+ // length if writing mode is vertical
+
+ WritingMode wm = GetWritingMode();
+ bool isVertical = wm.IsVertical();
+
+ fm->SetVertical(isVertical);
+ fm->SetTextOrientation(StyleVisibility()->mTextOrientation);
+
+ // XXX It would be nice if there was a way to have the font metrics tell
+ // use where to break the text given a maximum width. At a minimum we need
+ // to be able to get the break character...
+ const char16_t* str = aAltText.get();
+ int32_t strLen = aAltText.Length();
+ nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0)
+ : aRect.TopLeft();
+ nscoord iSize = isVertical ? aRect.height : aRect.width;
+
+ if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) {
+ aPresContext->SetBidiEnabled();
+ }
+
+ // Always show the first line, even if we have to clip it below
+ bool firstLine = true;
+ while (strLen > 0) {
+ if (!firstLine) {
+ // If we've run out of space, break out of the loop
+ if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) ||
+ (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) ||
+ (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) {
+ break;
+ }
+ }
+
+ // Determine how much of the text to display on this line
+ uint32_t maxFit; // number of characters that fit
+ nscoord strWidth =
+ MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm);
+
+ // Display the text
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aPresContext->BidiEnabled()) {
+ mozilla::intl::BidiEmbeddingLevel level;
+ nscoord x, y;
+
+ if (isVertical) {
+ x = pt.x + maxDescent;
+ if (wm.IsBidiLTR()) {
+ y = aRect.y;
+ level = mozilla::intl::BidiEmbeddingLevel::LTR();
+ } else {
+ y = aRect.YMost() - strWidth;
+ level = mozilla::intl::BidiEmbeddingLevel::RTL();
+ }
+ } else {
+ y = pt.y + maxAscent;
+ if (wm.IsBidiLTR()) {
+ x = aRect.x;
+ level = mozilla::intl::BidiEmbeddingLevel::LTR();
+ } else {
+ x = aRect.XMost() - strWidth;
+ level = mozilla::intl::BidiEmbeddingLevel::RTL();
+ }
+ }
+
+ rv = nsBidiPresUtils::RenderText(
+ str, maxFit, level, aPresContext, aRenderingContext,
+ aRenderingContext.GetDrawTarget(), *fm, x, y);
+ }
+ if (NS_FAILED(rv)) {
+ nsLayoutUtils::DrawUniDirString(str, maxFit,
+ isVertical
+ ? nsPoint(pt.x + maxDescent, pt.y)
+ : nsPoint(pt.x, pt.y + maxAscent),
+ *fm, aRenderingContext);
+ }
+
+ // Move to the next line
+ str += maxFit;
+ strLen -= maxFit;
+ if (wm.IsVerticalRL()) {
+ pt.x -= lineHeight;
+ } else if (wm.IsVerticalLR()) {
+ pt.x += lineHeight;
+ } else {
+ pt.y += lineHeight;
+ }
+
+ firstLine = false;
+ }
+}
+
+struct nsRecessedBorder : public nsStyleBorder {
+ explicit nsRecessedBorder(nscoord aBorderWidth) {
+ for (const auto side : AllPhysicalSides()) {
+ BorderColorFor(side) = StyleColor::Black();
+ mBorder.Side(side) = aBorderWidth;
+ // Note: use SetBorderStyle here because we want to affect
+ // mComputedBorder
+ SetBorderStyle(side, StyleBorderStyle::Inset);
+ }
+ }
+};
+
+class nsDisplayAltFeedback final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {}
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final {
+ // Always sync decode, because these icons are UI, and since they're not
+ // discardable we'll pay the price of sync decoding at most once.
+ uint32_t flags = imgIContainer::FLAG_SYNC_DECODE;
+
+ nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
+ Unused << f->DisplayAltFeedback(*aCtx, GetPaintRect(aBuilder, aCtx),
+ ToReferenceFrame(), flags);
+ }
+
+ bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) final {
+ // Always sync decode, because these icons are UI, and since they're not
+ // discardable we'll pay the price of sync decoding at most once.
+ uint32_t flags =
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
+ nsImageFrame* f = static_cast<nsImageFrame*>(mFrame);
+ ImgDrawResult result = f->DisplayAltFeedbackWithoutLayer(
+ this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder,
+ ToReferenceFrame(), flags);
+
+ return result == ImgDrawResult::SUCCESS;
+ }
+
+ NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK)
+};
+
+ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext,
+ const nsRect& aDirtyRect,
+ nsPoint aPt, uint32_t aFlags) {
+ // Whether we draw the broken or loading icon.
+ bool isLoading = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Calculate the content area.
+ nsRect inner = GetContentRectRelativeToSelf() + aPt;
+
+ // Display a recessed one pixel border
+ nscoord borderEdgeWidth =
+ nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
+
+ // if inner area is empty, then make it big enough for at least the icon
+ if (inner.IsEmpty()) {
+ inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
+ 2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
+ }
+
+ // Make sure we have enough room to actually render the border within
+ // our frame bounds
+ if ((inner.width < 2 * borderEdgeWidth) ||
+ (inner.height < 2 * borderEdgeWidth)) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Paint the border
+ if (!isLoading) {
+ nsRecessedBorder recessedBorder(borderEdgeWidth);
+
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
+ // returns.
+ MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
+
+ Unused << nsCSSRendering::PaintBorderWithStyleBorder(
+ PresContext(), aRenderingContext, this, inner, inner, recessedBorder,
+ mComputedStyle, PaintBorderFlags::SyncDecodeImages);
+ }
+
+ // Adjust the inner rect to account for the one pixel recessed border,
+ // and a six pixel padding on each edge
+ inner.Deflate(
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
+ if (inner.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Clip so we don't render outside the inner rect
+ aRenderingContext.Save();
+ aRenderingContext.Clip(NSRectToSnappedRect(
+ inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget));
+
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ // Check if we should display image placeholders
+ if (ShouldShowBrokenImageIcon()) {
+ result = ImgDrawResult::NOT_READY;
+ nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
+ imgIRequest* request = BrokenImageIcon::GetImage(this);
+
+ // If we weren't previously displaying an icon, register ourselves
+ // as an observer for load and animation updates and flag that we're
+ // doing so now.
+ if (request && !mDisplayingIcon) {
+ BrokenImageIcon::AddObserver(this);
+ mDisplayingIcon = true;
+ }
+
+ WritingMode wm = GetWritingMode();
+ bool flushRight = wm.IsPhysicalRTL();
+
+ // If the icon in question is loaded, draw it.
+ uint32_t imageStatus = 0;
+ if (request) request->GetImageStatus(&imageStatus);
+ if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
+ !(imageStatus & imgIRequest::STATUS_ERROR)) {
+ nsCOMPtr<imgIContainer> imgCon;
+ request->GetImage(getter_AddRefs(imgCon));
+ MOZ_ASSERT(imgCon, "Load complete, but no image container?");
+ nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
+ size);
+ result = nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, PresContext(), imgCon,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
+ SVGImageContext(), aFlags);
+ }
+
+ // If we could not draw the icon, just draw some graffiti in the mean time.
+ if (result == ImgDrawResult::NOT_READY) {
+ ColorPattern color(ToDeviceColor(sRGBColor(1.f, 0.f, 0.f, 1.f)));
+
+ nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
+
+ // stroked rect:
+ nsRect rect(iconXPos, inner.y, size, size);
+ Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
+ rect, PresContext()->AppUnitsPerDevPixel()));
+ drawTarget->StrokeRect(devPxRect, color);
+
+ // filled circle in bottom right quadrant of stroked rect:
+ nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
+ rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
+ size / 2 - twoPX);
+ devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
+ rect, PresContext()->AppUnitsPerDevPixel()));
+ RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ drawTarget->Fill(ellipse, color);
+ }
+
+ // Reduce the inner rect by the width of the icon, and leave an
+ // additional ICON_PADDING pixels for padding
+ int32_t paddedIconSize =
+ nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
+ if (wm.IsVertical()) {
+ inner.y += paddedIconSize;
+ inner.height -= paddedIconSize;
+ } else {
+ if (!flushRight) {
+ inner.x += paddedIconSize;
+ }
+ inner.width -= paddedIconSize;
+ }
+ }
+
+ // If there's still room, display the alt-text
+ if (!inner.IsEmpty()) {
+ nsAutoString altText;
+ nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(), altText);
+ DisplayAltText(PresContext(), aRenderingContext, altText, inner);
+ }
+
+ aRenderingContext.Restore();
+
+ return result;
+}
+
+ImgDrawResult nsImageFrame::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) {
+ // Whether we draw the broken or loading icon.
+ bool isLoading = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ // Calculate the content area.
+ nsRect inner = GetContentRectRelativeToSelf() + aPt;
+
+ // Display a recessed one pixel border
+ nscoord borderEdgeWidth =
+ nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH);
+
+ // if inner area is empty, then make it big enough for at least the icon
+ if (inner.IsEmpty()) {
+ inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)),
+ 2 * (nsPresContext::CSSPixelsToAppUnits(
+ ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)));
+ }
+
+ // Make sure we have enough room to actually render the border within
+ // our frame bounds
+ if ((inner.width < 2 * borderEdgeWidth) ||
+ (inner.height < 2 * borderEdgeWidth)) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // If the TextDrawTarget requires fallback we need to rollback everything we
+ // may have added to the display list, but we don't find that out until the
+ // end.
+ bool textDrawResult = true;
+ class AutoSaveRestore {
+ public:
+ explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder& aBuilder,
+ bool& aTextDrawResult)
+ : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) {
+ mBuilder.Save();
+ }
+ ~AutoSaveRestore() {
+ // If we have to use fallback for the text restore the builder and remove
+ // anything else we added to the display list, we need to use fallback.
+ if (mTextDrawResult) {
+ mBuilder.ClearSave();
+ } else {
+ mBuilder.Restore();
+ }
+ }
+
+ private:
+ mozilla::wr::DisplayListBuilder& mBuilder;
+ bool& mTextDrawResult;
+ };
+
+ AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult);
+
+ // Paint the border
+ if (!isLoading) {
+ nsRecessedBorder recessedBorder(borderEdgeWidth);
+ // Assert that we're not drawing a border-image here; if we were, we
+ // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder
+ // returns.
+ MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone());
+
+ nsRect rect = nsRect(aPt, GetSize());
+ Unused << nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
+ aItem, this, rect, aBuilder, aResources, aSc, aManager,
+ aDisplayListBuilder, recessedBorder);
+ }
+
+ // Adjust the inner rect to account for the one pixel recessed border,
+ // and a six pixel padding on each edge
+ inner.Deflate(
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH),
+ nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH));
+ if (inner.IsEmpty()) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ // Clip to this rect so we don't render outside the inner rect
+ const auto bounds = LayoutDeviceRect::FromAppUnits(
+ inner, PresContext()->AppUnitsPerDevPixel());
+ auto wrBounds = wr::ToLayoutRect(bounds);
+
+ // Check if we should display image placeholders
+ if (ShouldShowBrokenImageIcon()) {
+ ImgDrawResult result = ImgDrawResult::NOT_READY;
+ nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE);
+ imgIRequest* request = BrokenImageIcon::GetImage(this);
+
+ // If we weren't previously displaying an icon, register ourselves
+ // as an observer for load and animation updates and flag that we're
+ // doing so now.
+ if (request && !mDisplayingIcon) {
+ BrokenImageIcon::AddObserver(this);
+ mDisplayingIcon = true;
+ }
+
+ WritingMode wm = GetWritingMode();
+ const bool flushRight = wm.IsPhysicalRTL();
+
+ // If the icon in question is loaded, draw it.
+ uint32_t imageStatus = 0;
+ if (request) request->GetImageStatus(&imageStatus);
+ if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE &&
+ !(imageStatus & imgIRequest::STATUS_ERROR)) {
+ nsCOMPtr<imgIContainer> imgCon;
+ request->GetImage(getter_AddRefs(imgCon));
+ MOZ_ASSERT(imgCon, "Load complete, but no image container?");
+
+ nsRect dest(flushRight ? inner.XMost() - size : inner.x, inner.y, size,
+ size);
+
+ const int32_t factor = PresContext()->AppUnitsPerDevPixel();
+ const auto destRect = LayoutDeviceRect::FromAppUnits(dest, factor);
+
+ SVGImageContext svgContext;
+ Maybe<ImageIntRegion> region;
+ IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ imgCon, this, destRect, destRect, aSc, aFlags, svgContext,
+ region);
+ RefPtr<image::WebRenderImageProvider> provider;
+ result = imgCon->GetImageProvider(aManager->LayerManager(), decodeSize,
+ svgContext, region, aFlags,
+ getter_AddRefs(provider));
+ if (provider) {
+ bool wrResult = aManager->CommandBuilder().PushImageProvider(
+ aItem, provider, result, aBuilder, aResources, destRect, bounds);
+ result &= wrResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
+ } else {
+ // We don't use &= here because we want the result to be NOT_READY so
+ // the next block executes.
+ result = ImgDrawResult::NOT_READY;
+ }
+ }
+
+ // If we could not draw the icon, just draw some graffiti in the mean time.
+ if (result == ImgDrawResult::NOT_READY) {
+ auto color = wr::ColorF{1.0f, 0.0f, 0.0f, 1.0f};
+ bool isBackfaceVisible = !aItem->BackfaceIsHidden();
+
+ nscoord iconXPos = flushRight ? inner.XMost() - size : inner.x;
+
+ // stroked rect:
+ nsRect rect(iconXPos, inner.y, size, size);
+ auto devPxRect = LayoutDeviceRect::FromAppUnits(
+ rect, PresContext()->AppUnitsPerDevPixel());
+ auto dest = wr::ToLayoutRect(devPxRect);
+
+ auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
+ wr::BorderSide side = {color, wr::BorderStyle::Solid};
+ wr::BorderSide sides[4] = {side, side, side, side};
+ Range<const wr::BorderSide> sidesRange(sides, 4);
+ aBuilder.PushBorder(dest, wrBounds, isBackfaceVisible, borderWidths,
+ sidesRange, wr::EmptyBorderRadius());
+
+ // filled circle in bottom right quadrant of stroked rect:
+ nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2);
+ rect = nsRect(iconXPos + size / 2, inner.y + size / 2, size / 2 - twoPX,
+ size / 2 - twoPX);
+ devPxRect = LayoutDeviceRect::FromAppUnits(
+ rect, PresContext()->AppUnitsPerDevPixel());
+ dest = wr::ToLayoutRect(devPxRect);
+
+ aBuilder.PushRoundedRect(dest, wrBounds, isBackfaceVisible, color);
+ }
+
+ // Reduce the inner rect by the width of the icon, and leave an
+ // additional ICON_PADDING pixels for padding
+ int32_t paddedIconSize =
+ nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING);
+ if (wm.IsVertical()) {
+ inner.y += paddedIconSize;
+ inner.height -= paddedIconSize;
+ } else {
+ if (!flushRight) {
+ inner.x += paddedIconSize;
+ }
+ inner.width -= paddedIconSize;
+ }
+ }
+
+ // Draw text
+ if (!inner.IsEmpty()) {
+ RefPtr<TextDrawTarget> textDrawer =
+ new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, inner,
+ /* aCallerDoesSaveRestore = */ true);
+ MOZ_ASSERT(textDrawer->IsValid());
+ if (textDrawer->IsValid()) {
+ gfxContext captureCtx(textDrawer);
+
+ nsAutoString altText;
+ nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(),
+ altText);
+ DisplayAltText(PresContext(), captureCtx, altText, inner);
+
+ textDrawer->TerminateShadows();
+ textDrawResult = !textDrawer->CheckHasUnsupportedFeatures();
+ }
+ }
+
+ // Purposely ignore local DrawResult because we handled it not being success
+ // already.
+ return textDrawResult ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_READY;
+}
+
+// We want to sync-decode in this case, as otherwise we either need to flash
+// white while waiting to decode the new image, or paint the old image with a
+// different aspect-ratio, which would be bad as it'd be stretched.
+//
+// See bug 1589955.
+static bool OldImageHasDifferentRatio(const nsImageFrame& aFrame,
+ imgIContainer& aImage,
+ imgIContainer* aPrevImage) {
+ if (!aPrevImage || aPrevImage == &aImage) {
+ return false;
+ }
+
+ // If we don't depend on our intrinsic image size / ratio, we're good.
+ //
+ // FIXME(emilio): There's the case of the old image being painted
+ // intrinsically, and src and styles changing at the same time... Maybe we
+ // should keep track of the old GetPaintRect()'s ratio and the image's ratio,
+ // instead of checking this bit?
+ if (aFrame.HasAnyStateBits(IMAGE_SIZECONSTRAINED)) {
+ return false;
+ }
+
+ auto currentRatio = aFrame.GetIntrinsicRatio();
+ // If we have an image, we need to have a current request.
+ // Same if we had an image.
+ const bool hasRequest = true;
+#ifdef DEBUG
+ auto currentRatioRecomputed =
+ ComputeIntrinsicRatio(&aImage, hasRequest, aFrame);
+ // If the image encounters an error after decoding the size (and we run
+ // UpdateIntrinsicRatio) then the image will return the empty AspectRatio and
+ // the aspect ratio we compute here will be different from what was computed
+ // and stored before the image went into error state. It would be better to
+ // check that the image has an error here but we need an imgIRequest for that,
+ // not an imgIContainer. In lieu of that we check that
+ // aImage.GetIntrinsicRatio() returns Nothing() as it does when the image is
+ // in the error state and that the recomputed ratio is the zero ratio.
+ MOZ_ASSERT(
+ (!currentRatioRecomputed && aImage.GetIntrinsicRatio() == Nothing()) ||
+ currentRatio == currentRatioRecomputed,
+ "aspect-ratio got out of sync during paint? How?");
+#endif
+ auto oldRatio = ComputeIntrinsicRatio(aPrevImage, hasRequest, aFrame);
+ return oldRatio != currentRatio;
+}
+
+#ifdef DEBUG
+void nsImageFrame::AssertSyncDecodingHintIsInSync() const {
+ if (!IsForImageLoadingContent()) {
+ return;
+ }
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+
+ // The opposite is not true, we might have some other heuristics which force
+ // sync-decoding of images.
+ MOZ_ASSERT_IF(imageLoader->GetSyncDecodingHint(), mForceSyncDecoding);
+}
+#endif
+
+void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ MOZ_ASSERT(mImage);
+ auto* frame = static_cast<nsImageFrame*>(mFrame);
+ frame->AssertSyncDecodingHintIsInSync();
+
+ const bool oldImageIsDifferent =
+ OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
+
+ uint32_t flags = aBuilder->GetImageDecodeFlags();
+ if (aBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
+ frame->mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ ImgDrawResult result = frame->PaintImage(
+ *aCtx, ToReferenceFrame(), GetPaintRect(aBuilder, aCtx), mImage, flags);
+
+ if (result == ImgDrawResult::NOT_READY ||
+ result == ImgDrawResult::INCOMPLETE ||
+ result == ImgDrawResult::TEMPORARY_ERROR) {
+ // If the current image failed to paint because it's still loading or
+ // decoding, try painting the previous image.
+ if (mPrevImage) {
+ result =
+ frame->PaintImage(*aCtx, ToReferenceFrame(),
+ GetPaintRect(aBuilder, aCtx), mPrevImage, flags);
+ }
+ }
+}
+
+nsRect nsDisplayImage::GetDestRect() const {
+ bool snap = true;
+ const nsRect frameContentBox = GetBounds(&snap);
+
+ nsImageFrame* imageFrame = static_cast<nsImageFrame*>(mFrame);
+ return imageFrame->PredictedDestRect(frameContentBox);
+}
+
+nsRegion nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const {
+ *aSnap = false;
+ if (mImage && mImage->WillDrawOpaqueNow()) {
+ const nsRect frameContentBox = GetBounds(aSnap);
+ return GetDestRect().Intersect(frameContentBox);
+ }
+ return nsRegion();
+}
+
+bool nsDisplayImage::CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (!mImage) {
+ return false;
+ }
+
+ MOZ_ASSERT(mFrame->IsImageFrame() || mFrame->IsImageControlFrame());
+ // Image layer doesn't support draw focus ring for image map.
+ auto* frame = static_cast<nsImageFrame*>(mFrame);
+ if (frame->HasImageMap()) {
+ return false;
+ }
+
+ frame->AssertSyncDecodingHintIsInSync();
+ const bool oldImageIsDifferent =
+ OldImageHasDifferentRatio(*frame, *mImage, mPrevImage);
+
+ uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
+ if (aDisplayListBuilder->ShouldSyncDecodeImages() || oldImageIsDifferent ||
+ frame->mForceSyncDecoding) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+ if (StaticPrefs::image_svg_blob_image() &&
+ mImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ flags |= imgIContainer::FLAG_RECORD_BLOB;
+ }
+
+ const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect(
+ LayoutDeviceRect::FromAppUnits(GetDestRect(), factor));
+
+ SVGImageContext svgContext;
+ Maybe<ImageIntRegion> region;
+ IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImage, mFrame, destRect, destRect, aSc, flags, svgContext, region);
+
+ RefPtr<image::WebRenderImageProvider> provider;
+ ImgDrawResult drawResult =
+ mImage->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext,
+ region, flags, getter_AddRefs(provider));
+
+ if (nsCOMPtr<imgIRequest> currentRequest = frame->GetCurrentRequest()) {
+ LCPHelpers::FinalizeLCPEntryForImage(
+ frame->GetContent()->AsElement(),
+ static_cast<imgRequestProxy*>(currentRequest.get()),
+ GetDestRect() - ToReferenceFrame());
+ }
+
+ // While we got a container, it may not contain a fully decoded surface. If
+ // that is the case, and we have an image we were previously displaying which
+ // has a fully decoded surface, then we should prefer the previous image.
+ bool updatePrevImage = false;
+ switch (drawResult) {
+ case ImgDrawResult::NOT_READY:
+ case ImgDrawResult::INCOMPLETE:
+ case ImgDrawResult::TEMPORARY_ERROR:
+ if (mPrevImage && mPrevImage != mImage) {
+ // The current image and the previous image might be switching between
+ // rasterized surfaces and blob recordings, so we need to update the
+ // flags appropriately.
+ uint32_t prevFlags = flags;
+ if (StaticPrefs::image_svg_blob_image() &&
+ mPrevImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ prevFlags |= imgIContainer::FLAG_RECORD_BLOB;
+ } else {
+ prevFlags &= ~imgIContainer::FLAG_RECORD_BLOB;
+ }
+
+ RefPtr<image::WebRenderImageProvider> prevProvider;
+ ImgDrawResult prevDrawResult = mPrevImage->GetImageProvider(
+ aManager->LayerManager(), decodeSize, svgContext, region, prevFlags,
+ getter_AddRefs(prevProvider));
+ if (prevProvider && (prevDrawResult == ImgDrawResult::SUCCESS ||
+ prevDrawResult == ImgDrawResult::WRONG_SIZE)) {
+ // We use WRONG_SIZE here to ensure that when the frame next tries to
+ // invalidate due to a frame update from the current image, we don't
+ // consider the result from the previous image to be a valid result to
+ // avoid redrawing.
+ drawResult = ImgDrawResult::WRONG_SIZE;
+ provider = std::move(prevProvider);
+ flags = prevFlags;
+ break;
+ }
+
+ // Previous image was unusable; we can forget about it.
+ updatePrevImage = true;
+ }
+ break;
+ case ImgDrawResult::NOT_SUPPORTED:
+ return false;
+ default:
+ updatePrevImage = mPrevImage != mImage;
+ break;
+ }
+
+ // The previous image was not used, and is different from the current image.
+ // We should forget about it. We need to update the frame as well because the
+ // display item may get recreated.
+ if (updatePrevImage) {
+ mPrevImage = mImage;
+ frame->mPrevImage = frame->mImage;
+ }
+
+ // If the image provider is null, we don't want to fallback. Any other
+ // failure will be due to resource constraints and fallback is unlikely to
+ // help us. Hence we can ignore the return value from PushImage.
+ aManager->CommandBuilder().PushImageProvider(
+ this, provider, drawResult, aBuilder, aResources, destRect, destRect);
+ return true;
+}
+
+ImgDrawResult nsImageFrame::PaintImage(gfxContext& aRenderingContext,
+ nsPoint aPt, const nsRect& aDirtyRect,
+ imgIContainer* aImage, uint32_t aFlags) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ // Render the image into our content area (the area inside
+ // the borders and padding)
+ NS_ASSERTION(GetContentRectRelativeToSelf().width == mComputedSize.width,
+ "bad width");
+
+ // NOTE: We use mComputedSize instead of just GetContentRectRelativeToSelf()'s
+ // own size here, because GetContentRectRelativeToSelf() might be smaller if
+ // we're fragmented, whereas mComputedSize has our full content-box size
+ // (which we need for ComputeObjectDestRect to work correctly).
+ nsRect constraintRect(aPt + GetContentRectRelativeToSelf().TopLeft(),
+ mComputedSize);
+ constraintRect.y -= GetContinuationOffset();
+
+ nsPoint anchorPoint;
+ nsRect dest = nsLayoutUtils::ComputeObjectDestRect(
+ constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition(),
+ &anchorPoint);
+
+ SVGImageContext svgContext;
+ SVGImageContext::MaybeStoreContextPaint(svgContext, this, aImage);
+
+ ImgDrawResult result = nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, PresContext(), aImage,
+ nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect,
+ svgContext, aFlags, &anchorPoint);
+
+ if (nsImageMap* map = GetImageMap()) {
+ gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
+ dest.TopLeft(), PresContext()->AppUnitsPerDevPixel());
+ AutoRestoreTransform autoRestoreTransform(drawTarget);
+ drawTarget->SetTransform(
+ drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset)));
+
+ // solid white stroke:
+ ColorPattern white(ToDeviceColor(sRGBColor::OpaqueWhite()));
+ map->Draw(this, *drawTarget, white);
+
+ // then dashed black stroke over the top:
+ ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
+ StrokeOptions strokeOptions;
+ nsLayoutUtils::InitDashPattern(strokeOptions, StyleBorderStyle::Dotted);
+ map->Draw(this, *drawTarget, black, strokeOptions);
+ }
+
+ if (result == ImgDrawResult::SUCCESS) {
+ mPrevImage = aImage;
+ } else if (result == ImgDrawResult::BAD_IMAGE) {
+ mPrevImage = nullptr;
+ }
+
+ return result;
+}
+
+already_AddRefed<imgIRequest> nsImageFrame::GetCurrentRequest() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ return do_AddRef(mOwnedRequest);
+ }
+
+ MOZ_ASSERT(!mOwnedRequest);
+
+ nsCOMPtr<imgIRequest> request;
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ MOZ_ASSERT(imageLoader);
+ imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ return request.forget();
+}
+
+void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) {
+ return;
+ }
+
+ DisplayBorderBackgroundOutline(aBuilder, aLists);
+
+ if (HidesContent()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+ return;
+ }
+
+ uint32_t clipFlags =
+ nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())
+ ? 0
+ : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT;
+
+ DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip(
+ aBuilder, this, clipFlags);
+
+ if (mComputedSize.width != 0 && mComputedSize.height != 0) {
+ const bool imageOK = mKind != Kind::ImageLoadingContent ||
+ ImageOk(mContent->AsElement()->State());
+
+ nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+
+ const bool isImageFromStyle =
+ mKind != Kind::ImageLoadingContent && mKind != Kind::XULImage;
+ const bool drawAltFeedback = [&] {
+ if (!imageOK) {
+ return true;
+ }
+ // If we're a gradient, we don't need to draw alt feedback.
+ if (isImageFromStyle && !GetImageFromStyle()->IsImageRequestType()) {
+ return false;
+ }
+ // XXX(seth): The SizeIsAvailable check here should not be necessary - the
+ // intention is that a non-null mImage means we have a size, but there is
+ // currently some code that violates this invariant.
+ return !mImage || !SizeIsAvailable(currentRequest);
+ }();
+
+ if (drawAltFeedback) {
+ // No image yet, or image load failed. Draw the alt-text and an icon
+ // indicating the status
+ aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this);
+
+ // This image is visible (we are being asked to paint it) but it's not
+ // decoded yet. And we are not going to ask the image to draw, so this
+ // may be the only chance to tell it that it should decode.
+ if (currentRequest) {
+ uint32_t status = 0;
+ currentRequest->GetImageStatus(&status);
+ if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) {
+ MaybeDecodeForPredictedSize();
+ }
+ // Increase loading priority if the image is ready to be displayed.
+ if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
+ currentRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
+ }
+ }
+ } else {
+ if (mImage) {
+ aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage,
+ mPrevImage);
+ } else if (isImageFromStyle) {
+ aLists.Content()->AppendNewToTop<nsDisplayGradient>(aBuilder, this);
+ }
+
+ // If we were previously displaying an icon, we're not anymore
+ if (mDisplayingIcon) {
+ BrokenImageIcon::RemoveObserver(this);
+ mDisplayingIcon = false;
+ }
+ }
+ }
+
+ if (ShouldDisplaySelection()) {
+ DisplaySelectionOverlay(aBuilder, aLists.Content(),
+ nsISelectionDisplay::DISPLAY_IMAGES);
+ }
+
+ BuildDisplayListForNonBlockChildren(aBuilder, aLists);
+}
+
+bool nsImageFrame::ShouldDisplaySelection() {
+ int16_t displaySelection = PresShell()->GetSelectionFlags();
+ if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) {
+ // no need to check the blue border, we cannot be drawn selected.
+ return false;
+ }
+
+ if (displaySelection != nsISelectionDisplay::DISPLAY_ALL) {
+ return true;
+ }
+
+ // If the image is currently resize target of the editor, don't draw the
+ // selection overlay.
+ HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(PresContext());
+ if (!htmlEditor) {
+ return true;
+ }
+
+ return htmlEditor->GetResizerTarget() != mContent;
+}
+
+nsImageMap* nsImageFrame::GetImageMap() {
+ if (!mImageMap) {
+ if (nsIContent* map = GetMapElement()) {
+ mImageMap = new nsImageMap();
+ mImageMap->Init(this, map);
+ }
+ }
+
+ return mImageMap;
+}
+
+bool nsImageFrame::IsServerImageMap() {
+ return mContent->AsElement()->HasAttr(nsGkAtoms::ismap);
+}
+
+CSSIntPoint nsImageFrame::TranslateEventCoords(const nsPoint& aPoint) {
+ const nsRect contentRect = GetContentRectRelativeToSelf();
+ // Subtract out border and padding here so that the coordinates are
+ // now relative to the content area of this frame.
+ return CSSPixel::FromAppUnitsRounded(aPoint - contentRect.TopLeft());
+}
+
+bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget,
+ nsIContent** aNode) {
+ aTarget.Truncate();
+ *aHref = nullptr;
+ *aNode = nullptr;
+
+ // Walk up the content tree, looking for an nsIDOMAnchorElement
+ for (nsIContent* content = mContent->GetParent(); content;
+ content = content->GetParent()) {
+ nsCOMPtr<dom::Link> link = do_QueryInterface(content);
+ if (!link) {
+ continue;
+ }
+ if (nsCOMPtr<nsIURI> href = link->GetURI()) {
+ href.forget(aHref);
+ }
+
+ if (auto* anchor = HTMLAnchorElement::FromNode(content)) {
+ anchor->GetTarget(aTarget);
+ }
+ NS_ADDREF(*aNode = content);
+ return *aHref != nullptr;
+ }
+ return false;
+}
+
+bool nsImageFrame::IsLeafDynamic() const {
+ if (mKind != Kind::ImageLoadingContent) {
+ // Image frames created for "content: url()" could have an author-controlled
+ // shadow root, we want to be a regular leaf for those.
+ return true;
+ }
+ // For elements that create image frames, calling attachShadow() will throw,
+ // so the only ShadowRoot we could ever have is a UA widget.
+ const auto* shadow = mContent->AsElement()->GetShadowRoot();
+ MOZ_ASSERT_IF(shadow, shadow->IsUAWidget());
+ return !shadow;
+}
+
+nsresult nsImageFrame::GetContentForEvent(const WidgetEvent* aEvent,
+ nsIContent** aContent) {
+ NS_ENSURE_ARG_POINTER(aContent);
+
+ nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this);
+ if (f != this) {
+ return f->GetContentForEvent(aEvent, aContent);
+ }
+
+ // XXX We need to make this special check for area element's capturing the
+ // mouse due to bug 135040. Remove it once that's fixed.
+ nsIContent* capturingContent = aEvent->HasMouseEventMessage()
+ ? PresShell::GetCapturingContent()
+ : nullptr;
+ if (capturingContent && capturingContent->GetPrimaryFrame() == this) {
+ *aContent = capturingContent;
+ NS_IF_ADDREF(*aContent);
+ return NS_OK;
+ }
+
+ if (nsImageMap* map = GetImageMap()) {
+ const CSSIntPoint p = TranslateEventCoords(
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this}));
+ nsCOMPtr<nsIContent> area = map->GetArea(p);
+ if (area) {
+ area.forget(aContent);
+ return NS_OK;
+ }
+ }
+
+ *aContent = GetContent();
+ NS_IF_ADDREF(*aContent);
+ return NS_OK;
+}
+
+// XXX what should clicks on transparent pixels do?
+nsresult nsImageFrame::HandleEvent(nsPresContext* aPresContext,
+ WidgetGUIEvent* aEvent,
+ nsEventStatus* aEventStatus) {
+ NS_ENSURE_ARG_POINTER(aEventStatus);
+
+ if ((aEvent->mMessage == eMouseClick &&
+ aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) ||
+ aEvent->mMessage == eMouseMove) {
+ nsImageMap* map = GetImageMap();
+ bool isServerMap = IsServerImageMap();
+ if (map || isServerMap) {
+ CSSIntPoint p =
+ TranslateEventCoords(nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, RelativeTo{this}));
+
+ // Even though client-side image map triggering happens
+ // through content, we need to make sure we're not inside
+ // (in case we deal with a case of both client-side and
+ // sever-side on the same image - it happens!)
+ const bool inside = map && map->GetArea(p);
+
+ if (!inside && isServerMap) {
+ // Server side image maps use the href in a containing anchor
+ // element to provide the basis for the destination url.
+ nsCOMPtr<nsIURI> uri;
+ nsAutoString target;
+ nsCOMPtr<nsIContent> anchorNode;
+ if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target,
+ getter_AddRefs(anchorNode))) {
+ // XXX if the mouse is over/clicked in the border/padding area
+ // we should probably just pretend nothing happened. Nav4
+ // keeps the x,y coordinates positive as we do; IE doesn't
+ // bother. Both of them send the click through even when the
+ // mouse is over the border.
+ if (p.x < 0) p.x = 0;
+ if (p.y < 0) p.y = 0;
+
+ nsAutoCString spec;
+ nsresult rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ spec += nsPrintfCString("?%d,%d", p.x.value, p.y.value);
+ rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool clicked = false;
+ if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) {
+ *aEventStatus = nsEventStatus_eConsumeDoDefault;
+ clicked = true;
+ }
+ nsContentUtils::TriggerLink(anchorNode, uri, target, clicked,
+ /* isTrusted */ true);
+ }
+ }
+ }
+ }
+
+ return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent,
+ aEventStatus);
+}
+
+nsIFrame::Cursor nsImageFrame::GetCursor(const nsPoint& aPoint) {
+ nsImageMap* map = GetImageMap();
+ if (!map) {
+ return nsIFrame::GetCursor(aPoint);
+ }
+ const CSSIntPoint p = TranslateEventCoords(aPoint);
+ HTMLAreaElement* area = map->GetArea(p);
+ if (!area) {
+ return nsIFrame::GetCursor(aPoint);
+ }
+
+ // Use the cursor from the style of the *area* element.
+ RefPtr<ComputedStyle> areaStyle =
+ PresShell()->StyleSet()->ResolveStyleLazily(*area);
+
+ // This is one of the cases, like the <xul:tree> pseudo-style stuff, where we
+ // get styles out of the blue and expect to trigger image loads for those.
+ areaStyle->StartImageLoads(*PresContext()->Document());
+
+ StyleCursorKind kind = areaStyle->StyleUI()->Cursor().keyword;
+ if (kind == StyleCursorKind::Auto) {
+ kind = StyleCursorKind::Default;
+ }
+ return Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)};
+}
+
+nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID,
+ aAttribute, aModType);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (nsGkAtoms::alt == aAttribute) {
+ PresShell()->FrameNeedsReflow(
+ this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
+ }
+ if (mKind == Kind::XULImage && aAttribute == nsGkAtoms::src &&
+ aNameSpaceID == kNameSpaceID_None) {
+ UpdateXULImage();
+ }
+ return NS_OK;
+}
+
+void nsImageFrame::OnVisibilityChange(
+ Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
+ if (mKind == Kind::ImageLoadingContent) {
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
+ imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+ }
+
+ if (aNewVisibility == Visibility::ApproximatelyVisible &&
+ PresShell()->IsActive()) {
+ MaybeDecodeForPredictedSize();
+ }
+
+ nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsImageFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"ImageFrame"_ns, aResult);
+}
+
+void nsImageFrame::List(FILE* out, const char* aPrefix,
+ ListFlags aFlags) const {
+ nsCString str;
+ ListGeneric(str, aPrefix, aFlags);
+
+ // output the img src url
+ if (nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest()) {
+ nsCOMPtr<nsIURI> uri = currentRequest->GetURI();
+ nsAutoCString uristr;
+ uri->GetAsciiSpec(uristr);
+ str += nsPrintfCString(" [src=%s]", uristr.get());
+ }
+ fprintf_stderr(out, "%s\n", str.get());
+}
+#endif
+
+LogicalSides nsImageFrame::GetLogicalSkipSides() const {
+ LogicalSides skip(mWritingMode);
+ if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Clone)) {
+ return skip;
+ }
+ if (GetPrevInFlow()) {
+ skip |= eLogicalSideBitsBStart;
+ }
+ if (GetNextInFlow()) {
+ skip |= eLogicalSideBitsBEnd;
+ }
+ return skip;
+}
+NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver)
+
+nsImageListener::nsImageListener(nsImageFrame* aFrame) : mFrame(aFrame) {}
+
+nsImageListener::~nsImageListener() = default;
+
+void nsImageListener::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ if (!mFrame) {
+ return;
+ }
+
+ return mFrame->Notify(aRequest, aType, aData);
+}
+
+static bool IsInAutoWidthTableCellForQuirk(nsIFrame* aFrame) {
+ if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode())
+ return false;
+ // Check if the parent of the closest nsBlockFrame has auto width.
+ nsBlockFrame* ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame);
+ if (ancestor->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
+ // Assume direct parent is a table cell frame.
+ nsIFrame* grandAncestor = static_cast<nsIFrame*>(ancestor->GetParent());
+ return grandAncestor && grandAncestor->StylePosition()->mWidth.IsAuto();
+ }
+ return false;
+}
+
+void nsImageFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::MinISize);
+ bool canBreak = !IsInAutoWidthTableCellForQuirk(this);
+ aData->DefaultAddInlineMinISize(this, isize, canBreak);
+}
+
+void nsImageFrame::ReleaseGlobals() { BrokenImageIcon::Shutdown(); }