forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2908 lines
		
	
	
	
		
			104 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2908 lines
		
	
	
	
		
			104 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/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::DestroyFrom(nsIFrame* aDestructRoot,
 | |
|                                PostDestroyData& aPostDestroyData) {
 | |
|   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::DestroyFrom(aDestructRoot, aPostDestroyData);
 | |
| }
 | |
| 
 | |
| 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 containAxes.ContainIntrinsicSize(IntrinsicSize(0, 0), aFrame);
 | |
|   }
 | |
| 
 | |
|   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());
 | |
|     }
 | |
|     return containAxes.ContainIntrinsicSize(intrinsicSize, aFrame);
 | |
|   }
 | |
| 
 | |
|   if (aKind == nsImageFrame::Kind::ListStyleImage) {
 | |
|     // Note: images are handled above, this handles gradients etc.
 | |
|     nscoord defaultLength = ListImageDefaultLength(aFrame);
 | |
|     return containAxes.ContainIntrinsicSize(
 | |
|         IntrinsicSize(defaultLength, defaultLength), aFrame);
 | |
|   }
 | |
| 
 | |
|   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 containAxes.ContainIntrinsicSize(intrinsicSize, aFrame);
 | |
|   }
 | |
| 
 | |
|   if (aFrame.ShouldShowBrokenImageIcon()) {
 | |
|     nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
 | |
|         ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
 | |
|     return containAxes.ContainIntrinsicSize(
 | |
|         IntrinsicSize(edgeLengthToUse, edgeLengthToUse), aFrame);
 | |
|   }
 | |
| 
 | |
|   if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio.HasRatio()) {
 | |
|     return IntrinsicSize();
 | |
|   }
 | |
| 
 | |
|   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 our "do not show placeholders" pref is set, skip the icon
 | |
|   if (StaticPrefs::browser_display_force_inline_alttext()) {
 | |
|     return ImageFrameType::None;
 | |
|   }
 | |
| 
 | |
|   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) {
 | |
|     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 = PresContext()->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 =
 | |
|         containAxes.ContainIntrinsicSize(IntrinsicSize(0, 0), *this);
 | |
|     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 {
 | |
|   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
 | |
|   return imageLoader ? static_cast<nsImageLoadingContent*>(imageLoader.get())
 | |
|                            ->FindImageMap()
 | |
|                      : 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 =
 | |
|       nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight());
 | |
| 
 | |
|   aMetrics.Width() = mComputedSize.width;
 | |
|   aMetrics.Height() = mComputedSize.height;
 | |
| 
 | |
|   // add borders and padding
 | |
|   aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight();
 | |
|   aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom();
 | |
| 
 | |
|   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 {
 | |
|   nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext)
 | |
|       : nsStyleBorder(*aPresContext->Document()) {
 | |
|     for (const auto side : mozilla::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, PresContext());
 | |
| 
 | |
|     // 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, PresContext());
 | |
|     // 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));
 | |
| 
 | |
|   // 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);
 | |
| }
 | |
| 
 | |
| Maybe<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 Some(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(); }
 | 
