/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* rendering object for replaced elements with image data */ #include "nsImageFrame.h" #include "TextDrawTarget.h" #include "gfx2DGlue.h" #include "gfxContext.h" #include "gfxUtils.h" #include "mozilla/dom/NameSpaceConstants.h" #include "mozilla/intl/BidiEmbeddingLevel.h" #include "mozilla/ComputedStyle.h" #include "mozilla/DebugOnly.h" #include "mozilla/Encoding.h" #include "mozilla/HTMLEditor.h" #include "mozilla/dom/ImageTracker.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/dom/GeneratedImageContent.h" #include "mozilla/dom/HTMLAreaElement.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/dom/ResponsiveImageSelector.h" #include "mozilla/dom/LargestContentfulPaint.h" #include "mozilla/image/WebRenderImageProvider.h" #include "mozilla/layers/RenderRootStateManager.h" #include "mozilla/layers/WebRenderLayerManager.h" #include "mozilla/MouseEvents.h" #include "mozilla/PresShell.h" #include "mozilla/PresShellInlines.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_image.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/SVGImageContext.h" #include "mozilla/Unused.h" #include "nsCOMPtr.h" #include "nsFontMetrics.h" #include "nsIFrameInlines.h" #include "nsIImageLoadingContent.h" #include "nsImageLoadingContent.h" #include "nsImageRenderer.h" #include "nsObjectLoadingContent.h" #include "nsString.h" #include "nsPrintfCString.h" #include "nsPresContext.h" #include "nsGkAtoms.h" #include "mozilla/dom/Document.h" #include "nsContentUtils.h" #include "nsCSSAnonBoxes.h" #include "nsStyleConsts.h" #include "nsStyleUtil.h" #include "nsTransform2D.h" #include "nsImageMap.h" #include "nsILoadGroup.h" #include "nsNetUtil.h" #include "nsNetCID.h" #include "nsCSSRendering.h" #include "nsNameSpaceManager.h" #include #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(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(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 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 mObservers; RefPtr mImage; static StaticRefPtr gSingleton; }; StaticRefPtr BrokenImageIcon::gSingleton; NS_IMPL_ISUPPORTS(BrokenImageIcon, imgINotificationObserver) BrokenImageIcon::BrokenImageIcon(const nsImageFrame& aFrame) { constexpr auto brokenSrc = u"resource://gre-resources/broken-image.png"_ns; nsCOMPtr 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 il = nsContentUtils::GetImgLoaderForDocument(doc); nsCOMPtr 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 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 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; } // 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 currentRequest = GetCurrentRequest()) { uint32_t imageStatus; return NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) && (imageStatus & imgIRequest::STATUS_ERROR); } nsCOMPtr 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 currentURI = imageLoader->GetCurrentURI(); return !!currentURI; } nsImageFrame* nsImageFrame::CreateContinuingFrame( mozilla::PresShell* aPresShell, ComputedStyle* aStyle) const { return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), mKind); } NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame) nsImageFrame::nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID, Kind aKind) : nsAtomicContainerFrame(aStyle, aPresContext, aID), mIntrinsicSize(0, 0), mKind(aKind) { EnableVisibilityTracking(); } nsImageFrame::~nsImageFrame() = default; NS_QUERYFRAME_HEAD(nsImageFrame) NS_QUERYFRAME_ENTRY(nsImageFrame) NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) #ifdef ACCESSIBILITY a11y::AccType nsImageFrame::AccessibleType() { if (mKind == Kind::ListStyleImage) { // This is an HTMLListBulletAccessible. return a11y::eNoType; } // Don't use GetImageMap() to avoid reentrancy into accessibility. if (HasImageMap()) { return a11y::eHTMLImageMapType; } return a11y::eImageType; } #endif void nsImageFrame::DisconnectMap() { if (!mImageMap) { return; } mImageMap->Destroy(); mImageMap = nullptr; #ifdef ACCESSIBILITY if (nsAccessibilityService* accService = GetAccService()) { accService->RecreateAccessible(PresShell(), mContent); } #endif } void nsImageFrame::Destroy(DestroyContext& aContext) { MaybeSendIntrinsicSizeAndRatioToEmbedder(Nothing(), Nothing()); if (mReflowCallbackPosted) { PresShell()->CancelReflowCallback(this); mReflowCallbackPosted = false; } // Tell our image map, if there is one, to clean up // This causes the nsImageMap to unregister itself as // a DOM listener. DisconnectMap(); MOZ_ASSERT(mListener); if (mKind == Kind::ImageLoadingContent) { MOZ_ASSERT(!mOwnedRequest); MOZ_ASSERT(!mOwnedRequestRegistered); nsCOMPtr imageLoader = do_QueryInterface(mContent); MOZ_ASSERT(imageLoader); // Notify our image loading content that we are going away so it can // deregister with our refresh driver. imageLoader->FrameDestroyed(this); imageLoader->RemoveNativeObserver(mListener); } else { DeinitOwnedRequest(); } // set the frame to null so we don't send messages to a dead object. mListener->SetFrame(nullptr); mListener = nullptr; // If we were displaying an icon, take ourselves off the list if (mDisplayingIcon) { BrokenImageIcon::RemoveObserver(this); } nsAtomicContainerFrame::Destroy(aContext); } void nsImageFrame::DeinitOwnedRequest() { MOZ_ASSERT(mKind != Kind::ImageLoadingContent); if (!mOwnedRequest) { return; } PresContext()->Document()->ImageTracker()->Remove(mOwnedRequest); nsLayoutUtils::DeregisterImageRequest(PresContext(), mOwnedRequest, &mOwnedRequestRegistered); mOwnedRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); mOwnedRequest = nullptr; } void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { nsAtomicContainerFrame::DidSetComputedStyle(aOldStyle); // A ::marker's default size is calculated from the font's em-size. if (IsForMarkerPseudo()) { mIntrinsicSize = IntrinsicSize(0, 0); UpdateIntrinsicSize(); } // Normal "owned" images reframe when `content` or `list-style-image` change, // but XUL images don't (and we don't really need to). So deal with the // dynamic list-style-image change in that case. // // TODO(emilio): We might want to do the same for regular list-style-image or // even simple content: url() changes. if (mKind == Kind::XULImage) { if (!mContent->AsElement()->HasNonEmptyAttr(nsGkAtoms::src) && aOldStyle && aOldStyle->StyleList()->mListStyleImage != StyleList()->mListStyleImage) { UpdateXULImage(); } if (!mOwnedRequest && aOldStyle && aOldStyle->StyleDisplay()->EffectiveAppearance() != StyleDisplay()->EffectiveAppearance()) { UpdateIntrinsicSize(); } } // We need to update our orientation either if we had no ComputedStyle before // because this is the first time it's been set, or if the image-orientation // property changed from its previous value. bool shouldUpdateOrientation = false; nsCOMPtr 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 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(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 triggeringPrincipal; uint64_t requestContextID = 0; nsContentUtils::GetContentPolicyTypeForUIImageLoading( mContent, getter_AddRefs(triggeringPrincipal), contentPolicyType, &requestContextID); nsCOMPtr uri; nsContentUtils::NewURIWithDocumentCharset( getter_AddRefs(uri), src, pc->Document(), mContent->GetBaseURI()); if (uri) { auto referrerInfo = MakeRefPtr(*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(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 elements focusable. if (StaticPrefs::layout_image_eager_broken_image_icon()) { Unused << BrokenImageIcon::GetImage(this); } nsPresContext* pc = PresContext(); if (mKind == Kind::ImageLoadingContent) { nsCOMPtr 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 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 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 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 fm = nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc); RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(); auto emAU = font->GetMetrics(fm->Orientation()).emHeight * pc->AppUnitsPerDevPixel(); return std::max(NSToCoordRound(0.4f * emAU), nsPresContext::CSSPixelsToAppUnits(1)); } static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage, bool aUseMappedRatio, nsImageFrame::Kind aKind, const nsImageFrame& aFrame) { const auto containAxes = aFrame.GetContainSizeAxes(); if (containAxes.IsBoth()) { return aFrame.FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); } nsSize size; if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) { IntrinsicSize intrinsicSize; intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width); intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height); if (aKind == nsImageFrame::Kind::ListStyleImage) { if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) { nscoord defaultLength = ListImageDefaultLength(aFrame); if (intrinsicSize.width.isNothing()) { intrinsicSize.width = Some(defaultLength); } if (intrinsicSize.height.isNothing()) { intrinsicSize.height = Some(defaultLength); } } } if (aKind == nsImageFrame::Kind::ImageLoadingContent || (aKind == nsImageFrame::Kind::XULImage && aFrame.GetContent()->AsElement()->HasNonEmptyAttr(nsGkAtoms::src))) { ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize); } else { ScaleIntrinsicSizeForDensity( intrinsicSize, aFrame.GetImageFromStyle()->GetResolution(*aFrame.Style())); } return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize); } if (aKind == nsImageFrame::Kind::ListStyleImage) { // Note: images are handled above, this handles gradients etc. const nscoord defaultLength = ListImageDefaultLength(aFrame); return aFrame.FinishIntrinsicSize( containAxes, IntrinsicSize(defaultLength, defaultLength)); } if (aKind == nsImageFrame::Kind::XULImage && aFrame.IsThemed()) { nsPresContext* pc = aFrame.PresContext(); // FIXME: const_cast here is a bit evil but IsThemed and so does the same. const auto widgetSize = pc->Theme()->GetMinimumWidgetSize( pc, const_cast(&aFrame), aFrame.StyleDisplay()->EffectiveAppearance()); const IntrinsicSize intrinsicSize( LayoutDeviceIntSize::ToAppUnits(widgetSize, pc->AppUnitsPerDevPixel())); return aFrame.FinishIntrinsicSize(containAxes, intrinsicSize); } if (aFrame.ShouldShowBrokenImageIcon()) { nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); return aFrame.FinishIntrinsicSize( containAxes, IntrinsicSize(edgeLengthToUse, edgeLengthToUse)); } if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio.HasRatio()) { return IntrinsicSize(); } // XXX: No FinishIntrinsicSize? return IntrinsicSize(0, 0); } // For compat reasons, see bug 1602047, we don't use the intrinsic ratio from // width="" and height="" for images with no src attribute (no request). // // But we shouldn't get fooled by . We do want to apply the // ratio then... bool nsImageFrame::ShouldUseMappedAspectRatio() const { if (mKind != Kind::ImageLoadingContent) { return true; } nsCOMPtr 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 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 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 , 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 items = content.AsItems().AsSpan(); return items.Length() == 1 && items[0].IsImage(); } // Check if we want to use an image frame or just let the frame constructor make // us into an inline, and if so, which kind of image frame should we create. /* static */ auto nsImageFrame::ImageFrameTypeFor(const Element& aElement, const ComputedStyle& aStyle) -> ImageFrameType { if (ShouldCreateImageFrameForContentProperty(aElement, aStyle)) { // Prefer the content property, for compat reasons, see bug 1484928. return ImageFrameType::ForContentProperty; } if (ImageOk(aElement.State())) { // Image is fine or loading; do the image frame thing return ImageFrameType::ForElementRequest; } if (aStyle.StyleUIReset()->mMozForceBrokenImageIcon) { return ImageFrameType::ForElementRequest; } if (!HasAltText(aElement)) { return ImageFrameType::ForElementRequest; } // FIXME(emilio, bug 1788767): We definitely don't reframe when // HaveSpecifiedSize changes... if (aElement.OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks && HaveSpecifiedSize(aStyle.StylePosition())) { return ImageFrameType::ForElementRequest; } return ImageFrameType::None; } void nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aRect) { if (aType == imgINotificationObserver::SIZE_AVAILABLE) { nsCOMPtr image; aRequest->GetImage(getter_AddRefs(image)); return OnSizeAvailable(aRequest, image); } if (aType == imgINotificationObserver::FRAME_UPDATE) { return OnFrameUpdate(aRequest, aRect); } if (aType == imgINotificationObserver::FRAME_COMPLETE) { mFirstFrameComplete = true; } if (aType == imgINotificationObserver::IS_ANIMATED && mKind != Kind::ImageLoadingContent) { nsLayoutUtils::RegisterImageRequest(PresContext(), mOwnedRequest, &mOwnedRequestRegistered); } if (aType == imgINotificationObserver::LOAD_COMPLETE) { LargestContentfulPaint::MaybeProcessImageForElementTiming( static_cast(aRequest), GetContent()->AsElement()); uint32_t imgStatus; aRequest->GetImageStatus(&imgStatus); nsresult status = imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; return OnLoadComplete(aRequest, status); } } void nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) { if (!aImage) { return; } /* Get requested animation policy from the pres context: * normal = 0 * one frame = 1 * one loop = 2 */ aImage->SetAnimationMode(PresContext()->ImageAnimationMode()); if (IsPendingLoad(aRequest)) { // We don't care return; } UpdateImage(aRequest, aImage); } void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { if (SizeIsAvailable(aRequest)) { StyleImageOrientation orientation = StyleVisibility()->UsedImageOrientation(aRequest); // This is valid and for the current request, so update our stored image // container, orienting according to our style. mImage = nsLayoutUtils::OrientImage(aImage, orientation); MOZ_ASSERT(mImage); } else { // We no longer have a valid image, so release our stored image container. mImage = mPrevImage = nullptr; if (mKind == Kind::ListStyleImage) { auto* genContent = static_cast(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 aIntrinsicSize, Maybe aIntrinsicRatio) { if (!mIsInObjectOrEmbed || !mImage) { return; } nsCOMPtr 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 olc = do_QueryInterface(embedder)) { static_cast(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 or // 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 image; aRequest->GetImage(getter_AddRefs(image)); NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?"); UpdateImage(aRequest, image); } void nsImageFrame::MaybeDecodeForPredictedSize() { // Check that we're ready to decode. if (!mImage) { return; // Nothing to do yet. } if (mComputedSize.IsEmpty()) { return; // We won't draw anything, so no point in decoding. } if (GetVisibility() != Visibility::ApproximatelyVisible) { return; // We're not visible, so don't decode. } // OK, we're ready to decode. Compute the scale to the screen... mozilla::PresShell* presShell = PresShell(); MatrixScales scale = ScaleFactor( presShell->GetCumulativeResolution()) * nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this); auto resolutionToScreen = ViewAs(scale); // If we are in a remote browser, then apply scaling from ancestor browsers if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { resolutionToScreen = resolutionToScreen * ViewAs( browserChild->GetEffectsInfo().mRasterScale); } // ...and this frame's content box... const nsPoint offset = GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this)); const nsRect frameContentBox = GetContentRectRelativeToSelf() + offset; // ...and our predicted dest rect... const int32_t factor = PresContext()->AppUnitsPerDevPixel(); const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits( PredictedDestRect(frameContentBox), factor); // ...and use them to compute our predicted size in screen pixels. const ScreenSize predictedScreenSize = destRect.Size() * resolutionToScreen; const ScreenIntSize predictedScreenIntSize = RoundedToInt(predictedScreenSize); if (predictedScreenIntSize.IsEmpty()) { return; } // Determine the optimal image size to use. uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING | imgIContainer::FLAG_ASYNC_NOTIFY; SamplingFilter samplingFilter = nsLayoutUtils::GetSamplingFilterForFrame(this); gfxSize gfxPredictedScreenSize = gfxSize(predictedScreenIntSize.width, predictedScreenIntSize.height); nsIntSize predictedImageSize = mImage->OptimalImageSizeForDest( gfxPredictedScreenSize, imgIContainer::FRAME_CURRENT, samplingFilter, flags); // Request a decode. mImage->RequestDecodeForSize(predictedImageSize, flags); } nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) { // Note: To get the "dest rect", we have to provide the "constraint rect" // (which is the content-box, with the effects of fragmentation undone). nsRect constraintRect(aFrameContentBox.TopLeft(), mComputedSize); constraintRect.y -= GetContinuationOffset(); return nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition()); } bool nsImageFrame::IsForMarkerPseudo() const { if (mKind == Kind::ImageLoadingContent) { return false; } auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot(); return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker(); } void nsImageFrame::EnsureIntrinsicSizeAndRatio() { const auto containAxes = GetContainSizeAxes(); if (containAxes.IsBoth()) { // If we have 'contain:size', then we have no intrinsic aspect ratio, // and the intrinsic size is determined by contain-intrinsic-size, // regardless of what our underlying image may think. mIntrinsicSize = FinishIntrinsicSize(containAxes, IntrinsicSize(0, 0)); mIntrinsicRatio = AspectRatio(); return; } // If mIntrinsicSize.width and height are 0, then we need to update from the // image container. Note that we handle ::marker intrinsic size/ratio in // DidSetComputedStyle. if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) { return; } bool intrinsicSizeOrRatioChanged = UpdateIntrinsicSize(); intrinsicSizeOrRatioChanged = UpdateIntrinsicRatio() || intrinsicSizeOrRatioChanged; if (intrinsicSizeOrRatioChanged) { // Our aspect-ratio property value changed, and an embedding or // might care about that. MaybeSendIntrinsicSizeAndRatioToEmbedder(); } } nsIFrame::SizeComputationResult nsImageFrame::ComputeSize( gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) { EnsureIntrinsicSizeAndRatio(); return {ComputeSizeWithIntrinsicDimensions( aRenderingContext, aWM, mIntrinsicSize, GetAspectRatio(), aCBSize, aMargin, aBorderPadding, aSizeOverrides, aFlags), AspectRatioUsage::None}; } Element* nsImageFrame::GetMapElement() const { return IsForImageLoadingContent() ? nsImageLoadingContent::FindImageMap(mContent->AsElement()) : nullptr; } // get the offset into the content area of the image where aImg starts if it is // a continuation. nscoord nsImageFrame::GetContinuationOffset() const { nscoord offset = 0; for (nsIFrame* f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) { offset += f->GetContentRect().height; } NS_ASSERTION(offset >= 0, "bogus GetContentRect"); return offset; } nscoord nsImageFrame::GetMinISize(gfxContext* aRenderingContext) { // XXX The caller doesn't account for constraints of the block-size, // min-block-size, and max-block-size properties. DebugOnly 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 result; DISPLAY_PREF_INLINE_SIZE(this, result); EnsureIntrinsicSizeAndRatio(); const auto& iSize = GetWritingMode().IsVertical() ? mIntrinsicSize.height : mIntrinsicSize.width; // convert from normal twips to scaled twips (printing...) return iSize.valueOr(0); } void nsImageFrame::ReflowChildren(nsPresContext* aPresContext, const ReflowInput& aReflowInput, const LogicalSize& aImageSize) { for (nsIFrame* child : mFrames) { ReflowOutput childDesiredSize(aReflowInput); WritingMode wm = GetWritingMode(); // Shouldn't be hard to support if we want, but why bother. MOZ_ASSERT( wm == child->GetWritingMode(), "We don't expect mismatched writing-modes in content we control"); nsReflowStatus childStatus; LogicalPoint childOffset(wm); ReflowInput childReflowInput(aPresContext, aReflowInput, child, aImageSize); const nsSize containerSize = aImageSize.GetPhysicalSize(wm); ReflowChild(child, aPresContext, childDesiredSize, childReflowInput, wm, childOffset, containerSize, ReflowChildFlags::Default, childStatus); FinishReflowChild(child, aPresContext, childDesiredSize, &childReflowInput, wm, childOffset, containerSize, ReflowChildFlags::Default); } } void nsImageFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { MarkInReflow(); DO_GLOBAL_REFLOW_COUNT("nsImageFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); NS_FRAME_TRACE( NS_FRAME_TRACE_CALLS, ("enter nsImageFrame::Reflow: availSize=%d,%d", aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); MOZ_ASSERT(HasAnyStateBits(NS_FRAME_IN_REFLOW), "frame is not in reflow"); // see if we have a frozen size (i.e. a fixed width and height) if (!SizeDependsOnIntrinsicSize(aReflowInput)) { AddStateBits(IMAGE_SIZECONSTRAINED); } else { RemoveStateBits(IMAGE_SIZECONSTRAINED); } mComputedSize = aReflowInput.ComputedPhysicalSize(); const auto wm = GetWritingMode(); aMetrics.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm)); if (GetPrevInFlow()) { aMetrics.Width() = GetPrevInFlow()->GetSize().width; nscoord y = GetContinuationOffset(); aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top; aMetrics.Height() = std::max(0, aMetrics.Height()); } // we have to split images if we are: // in Paginated mode, we need to have a constrained height, and have a height // larger than our available height uint32_t loadStatus = imgIRequest::STATUS_NONE; if (nsCOMPtr 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 fm = nsLayoutUtils::GetInflatedFontMetricsForFrame(this); // Format the text to display within the formatting rect nscoord maxAscent = fm->MaxAscent(); nscoord maxDescent = fm->MaxDescent(); nscoord lineHeight = fm->MaxHeight(); // line-relative, so an x-coordinate // length if writing mode is vertical WritingMode wm = GetWritingMode(); bool isVertical = wm.IsVertical(); fm->SetVertical(isVertical); fm->SetTextOrientation(StyleVisibility()->mTextOrientation); // XXX It would be nice if there was a way to have the font metrics tell // use where to break the text given a maximum width. At a minimum we need // to be able to get the break character... const char16_t* str = aAltText.get(); int32_t strLen = aAltText.Length(); nsPoint pt = wm.IsVerticalRL() ? aRect.TopRight() - nsPoint(lineHeight, 0) : aRect.TopLeft(); nscoord iSize = isVertical ? aRect.height : aRect.width; if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) { aPresContext->SetBidiEnabled(); } // Always show the first line, even if we have to clip it below bool firstLine = true; while (strLen > 0) { if (!firstLine) { // If we've run out of space, break out of the loop if ((!isVertical && (pt.y + maxDescent) >= aRect.YMost()) || (wm.IsVerticalRL() && (pt.x + maxDescent < aRect.x)) || (wm.IsVerticalLR() && (pt.x + maxDescent >= aRect.XMost()))) { break; } } // Determine how much of the text to display on this line uint32_t maxFit; // number of characters that fit nscoord strWidth = MeasureString(str, strLen, iSize, maxFit, aRenderingContext, *fm); // Display the text nsresult rv = NS_ERROR_FAILURE; if (aPresContext->BidiEnabled()) { mozilla::intl::BidiEmbeddingLevel level; nscoord x, y; if (isVertical) { x = pt.x + maxDescent; if (wm.IsBidiLTR()) { y = aRect.y; level = mozilla::intl::BidiEmbeddingLevel::LTR(); } else { y = aRect.YMost() - strWidth; level = mozilla::intl::BidiEmbeddingLevel::RTL(); } } else { y = pt.y + maxAscent; if (wm.IsBidiLTR()) { x = aRect.x; level = mozilla::intl::BidiEmbeddingLevel::LTR(); } else { x = aRect.XMost() - strWidth; level = mozilla::intl::BidiEmbeddingLevel::RTL(); } } rv = nsBidiPresUtils::RenderText( str, maxFit, level, aPresContext, aRenderingContext, aRenderingContext.GetDrawTarget(), *fm, x, y); } if (NS_FAILED(rv)) { nsLayoutUtils::DrawUniDirString(str, maxFit, isVertical ? nsPoint(pt.x + maxDescent, pt.y) : nsPoint(pt.x, pt.y + maxAscent), *fm, aRenderingContext); } // Move to the next line str += maxFit; strLen -= maxFit; if (wm.IsVerticalRL()) { pt.x -= lineHeight; } else if (wm.IsVerticalLR()) { pt.x += lineHeight; } else { pt.y += lineHeight; } firstLine = false; } } struct nsRecessedBorder : public nsStyleBorder { explicit nsRecessedBorder(nscoord aBorderWidth) { for (const auto side : AllPhysicalSides()) { BorderColorFor(side) = StyleColor::Black(); mBorder.Side(side) = aBorderWidth; // Note: use SetBorderStyle here because we want to affect // mComputedBorder SetBorderStyle(side, StyleBorderStyle::Inset); } } }; class nsDisplayAltFeedback final : public nsPaintedDisplayItem { public: nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) : nsPaintedDisplayItem(aBuilder, aFrame) {} nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final { *aSnap = false; return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); } void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final { // Always sync decode, because these icons are UI, and since they're not // discardable we'll pay the price of sync decoding at most once. uint32_t flags = imgIContainer::FLAG_SYNC_DECODE; nsImageFrame* f = static_cast(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(mFrame); ImgDrawResult result = f->DisplayAltFeedbackWithoutLayer( this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder, ToReferenceFrame(), flags); return result == ImgDrawResult::SUCCESS; } NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK) }; ImgDrawResult nsImageFrame::DisplayAltFeedback(gfxContext& aRenderingContext, const nsRect& aDirtyRect, nsPoint aPt, uint32_t aFlags) { // Whether we draw the broken or loading icon. bool isLoading = mKind != Kind::ImageLoadingContent || ImageOk(mContent->AsElement()->State()); // Calculate the content area. nsRect inner = GetContentRectRelativeToSelf() + aPt; // Display a recessed one pixel border nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); // if inner area is empty, then make it big enough for at least the icon if (inner.IsEmpty()) { inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)), 2 * (nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH))); } // Make sure we have enough room to actually render the border within // our frame bounds if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) { return ImgDrawResult::SUCCESS; } // Paint the border if (!isLoading) { nsRecessedBorder recessedBorder(borderEdgeWidth); // Assert that we're not drawing a border-image here; if we were, we // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder // returns. MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone()); Unused << nsCSSRendering::PaintBorderWithStyleBorder( PresContext(), aRenderingContext, this, inner, inner, recessedBorder, mComputedStyle, PaintBorderFlags::SyncDecodeImages); } // Adjust the inner rect to account for the one pixel recessed border, // and a six pixel padding on each edge inner.Deflate( nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH), nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH)); if (inner.IsEmpty()) { return ImgDrawResult::SUCCESS; } DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); // Clip so we don't render outside the inner rect aRenderingContext.Save(); aRenderingContext.Clip(NSRectToSnappedRect( inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); ImgDrawResult result = ImgDrawResult::SUCCESS; // Check if we should display image placeholders if (ShouldShowBrokenImageIcon()) { result = ImgDrawResult::NOT_READY; nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); imgIRequest* request = BrokenImageIcon::GetImage(this); // If we weren't previously displaying an icon, register ourselves // as an observer for load and animation updates and flag that we're // doing so now. if (request && !mDisplayingIcon) { BrokenImageIcon::AddObserver(this); mDisplayingIcon = true; } WritingMode wm = GetWritingMode(); bool flushRight = wm.IsPhysicalRTL(); // If the icon in question is loaded, draw it. uint32_t imageStatus = 0; if (request) request->GetImageStatus(&imageStatus); if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE && !(imageStatus & imgIRequest::STATUS_ERROR)) { nsCOMPtr 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 builder = drawTarget->CreatePathBuilder(); AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); RefPtr ellipse = builder->Finish(); drawTarget->Fill(ellipse, color); } // Reduce the inner rect by the width of the icon, and leave an // additional ICON_PADDING pixels for padding int32_t paddedIconSize = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING); if (wm.IsVertical()) { inner.y += paddedIconSize; inner.height -= paddedIconSize; } else { if (!flushRight) { inner.x += paddedIconSize; } inner.width -= paddedIconSize; } } // If there's still room, display the alt-text if (!inner.IsEmpty()) { nsAutoString altText; nsCSSFrameConstructor::GetAlternateTextFor(*mContent->AsElement(), altText); DisplayAltText(PresContext(), aRenderingContext, altText, inner); } aRenderingContext.Restore(); return result; } ImgDrawResult nsImageFrame::DisplayAltFeedbackWithoutLayer( nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, mozilla::layers::RenderRootStateManager* aManager, nsDisplayListBuilder* aDisplayListBuilder, nsPoint aPt, uint32_t aFlags) { // Whether we draw the broken or loading icon. bool isLoading = mKind != Kind::ImageLoadingContent || ImageOk(mContent->AsElement()->State()); // Calculate the content area. nsRect inner = GetContentRectRelativeToSelf() + aPt; // Display a recessed one pixel border nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); // if inner area is empty, then make it big enough for at least the icon if (inner.IsEmpty()) { inner.SizeTo(2 * (nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH)), 2 * (nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + ICON_PADDING + ALT_BORDER_WIDTH))); } // Make sure we have enough room to actually render the border within // our frame bounds if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) { return ImgDrawResult::SUCCESS; } // If the TextDrawTarget requires fallback we need to rollback everything we // may have added to the display list, but we don't find that out until the // end. bool textDrawResult = true; class AutoSaveRestore { public: explicit AutoSaveRestore(mozilla::wr::DisplayListBuilder& aBuilder, bool& aTextDrawResult) : mBuilder(aBuilder), mTextDrawResult(aTextDrawResult) { mBuilder.Save(); } ~AutoSaveRestore() { // If we have to use fallback for the text restore the builder and remove // anything else we added to the display list, we need to use fallback. if (mTextDrawResult) { mBuilder.ClearSave(); } else { mBuilder.Restore(); } } private: mozilla::wr::DisplayListBuilder& mBuilder; bool& mTextDrawResult; }; AutoSaveRestore autoSaveRestore(aBuilder, textDrawResult); // Paint the border if (!isLoading) { nsRecessedBorder recessedBorder(borderEdgeWidth); // Assert that we're not drawing a border-image here; if we were, we // couldn't ignore the ImgDrawResult that PaintBorderWithStyleBorder // returns. MOZ_ASSERT(recessedBorder.mBorderImageSource.IsNone()); nsRect rect = nsRect(aPt, GetSize()); Unused << nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder( aItem, this, rect, aBuilder, aResources, aSc, aManager, aDisplayListBuilder, recessedBorder); } // Adjust the inner rect to account for the one pixel recessed border, // and a six pixel padding on each edge inner.Deflate( nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH), nsPresContext::CSSPixelsToAppUnits(ICON_PADDING + ALT_BORDER_WIDTH)); if (inner.IsEmpty()) { return ImgDrawResult::SUCCESS; } // Clip to this rect so we don't render outside the inner rect const auto bounds = LayoutDeviceRect::FromAppUnits( inner, PresContext()->AppUnitsPerDevPixel()); auto wrBounds = wr::ToLayoutRect(bounds); // Check if we should display image placeholders if (ShouldShowBrokenImageIcon()) { ImgDrawResult result = ImgDrawResult::NOT_READY; nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); imgIRequest* request = BrokenImageIcon::GetImage(this); // If we weren't previously displaying an icon, register ourselves // as an observer for load and animation updates and flag that we're // doing so now. if (request && !mDisplayingIcon) { BrokenImageIcon::AddObserver(this); mDisplayingIcon = true; } WritingMode wm = GetWritingMode(); const bool flushRight = wm.IsPhysicalRTL(); // If the icon in question is loaded, draw it. uint32_t imageStatus = 0; if (request) request->GetImageStatus(&imageStatus); if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE && !(imageStatus & imgIRequest::STATUS_ERROR)) { nsCOMPtr 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 region; IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters( imgCon, this, destRect, destRect, aSc, aFlags, svgContext, region); RefPtr 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 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 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 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(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(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(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 region; IntSize decodeSize = nsLayoutUtils::ComputeImageContainerDrawingParameters( mImage, mFrame, destRect, destRect, aSc, flags, svgContext, region); RefPtr provider; ImgDrawResult drawResult = mImage->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext, region, flags, getter_AddRefs(provider)); if (nsCOMPtr currentRequest = frame->GetCurrentRequest()) { LCPHelpers::FinalizeLCPEntryForImage( frame->GetContent()->AsElement(), static_cast(currentRequest.get()), GetDestRect() - ToReferenceFrame()); } // While we got a container, it may not contain a fully decoded surface. If // that is the case, and we have an image we were previously displaying which // has a fully decoded surface, then we should prefer the previous image. bool updatePrevImage = false; switch (drawResult) { case ImgDrawResult::NOT_READY: case ImgDrawResult::INCOMPLETE: case ImgDrawResult::TEMPORARY_ERROR: if (mPrevImage && mPrevImage != mImage) { // The current image and the previous image might be switching between // rasterized surfaces and blob recordings, so we need to update the // flags appropriately. uint32_t prevFlags = flags; if (StaticPrefs::image_svg_blob_image() && mPrevImage->GetType() == imgIContainer::TYPE_VECTOR) { prevFlags |= imgIContainer::FLAG_RECORD_BLOB; } else { prevFlags &= ~imgIContainer::FLAG_RECORD_BLOB; } RefPtr 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 nsImageFrame::GetCurrentRequest() const { if (mKind != Kind::ImageLoadingContent) { return do_AddRef(mOwnedRequest); } MOZ_ASSERT(!mOwnedRequest); nsCOMPtr request; nsCOMPtr 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 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(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(aBuilder, this, mImage, mPrevImage); } else if (isImageFromStyle) { aLists.Content()->AppendNewToTop(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 link = do_QueryInterface(content); if (!link) { continue; } if (nsCOMPtr 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 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 uri; nsAutoString target; nsCOMPtr anchorNode; if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target, getter_AddRefs(anchorNode))) { // XXX if the mouse is over/clicked in the border/padding area // we should probably just pretend nothing happened. Nav4 // keeps the x,y coordinates positive as we do; IE doesn't // bother. Both of them send the click through even when the // mouse is over the border. if (p.x < 0) p.x = 0; if (p.y < 0) p.y = 0; nsAutoCString spec; nsresult rv = uri->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); spec += nsPrintfCString("?%d,%d", p.x.value, p.y.value); rv = NS_MutateURI(uri).SetSpec(spec).Finalize(uri); NS_ENSURE_SUCCESS(rv, rv); bool clicked = false; if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) { *aEventStatus = nsEventStatus_eConsumeDoDefault; clicked = true; } nsContentUtils::TriggerLink(anchorNode, uri, target, clicked, /* isTrusted */ true); } } } } return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } nsIFrame::Cursor nsImageFrame::GetCursor(const nsPoint& aPoint) { nsImageMap* map = GetImageMap(); if (!map) { return nsIFrame::GetCursor(aPoint); } const CSSIntPoint p = TranslateEventCoords(aPoint); HTMLAreaElement* area = map->GetArea(p); if (!area) { return nsIFrame::GetCursor(aPoint); } // Use the cursor from the style of the *area* element. RefPtr areaStyle = PresShell()->StyleSet()->ResolveStyleLazily(*area); // This is one of the cases, like the pseudo-style stuff, where we // get styles out of the blue and expect to trigger image loads for those. areaStyle->StartImageLoads(*PresContext()->Document()); StyleCursorKind kind = areaStyle->StyleUI()->Cursor().keyword; if (kind == StyleCursorKind::Auto) { kind = StyleCursorKind::Default; } return Cursor{kind, AllowCustomCursorImage::Yes, std::move(areaStyle)}; } nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); if (NS_FAILED(rv)) { return rv; } if (nsGkAtoms::alt == aAttribute) { PresShell()->FrameNeedsReflow( this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); } if (mKind == Kind::XULImage && aAttribute == nsGkAtoms::src && aNameSpaceID == kNameSpaceID_None) { UpdateXULImage(); } return NS_OK; } void nsImageFrame::OnVisibilityChange( Visibility aNewVisibility, const Maybe& aNonvisibleAction) { if (mKind == Kind::ImageLoadingContent) { nsCOMPtr 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 currentRequest = GetCurrentRequest()) { nsCOMPtr 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(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(); }