diff options
Diffstat (limited to 'layout/xul/nsImageBoxFrame.cpp')
-rw-r--r-- | layout/xul/nsImageBoxFrame.cpp | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/layout/xul/nsImageBoxFrame.cpp b/layout/xul/nsImageBoxFrame.cpp new file mode 100644 index 0000000000..9e10b58f43 --- /dev/null +++ b/layout/xul/nsImageBoxFrame.cpp @@ -0,0 +1,805 @@ +/* -*- 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/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "gfxContext.h" +#include "nsImageBoxFrame.h" +#include "nsGkAtoms.h" +#include "mozilla/ComputedStyle.h" +#include "nsStyleConsts.h" +#include "nsStyleUtil.h" +#include "nsCOMPtr.h" +#include "nsLayoutUtils.h" +#include "nsPresContext.h" +#include "nsBoxLayoutState.h" + +#include "nsHTMLParts.h" +#include "nsString.h" +#include "nsLeafFrame.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsImageMap.h" +#include "nsContainerFrame.h" +#include "nsCSSRendering.h" +#include "nsNameSpaceManager.h" +#include "nsTextFragment.h" +#include "nsTransform2D.h" +#include "nsITheme.h" + +#include "nsIURI.h" +#include "nsThreadUtils.h" +#include "nsDisplayList.h" +#include "ImageRegion.h" +#include "ImageContainer.h" +#include "nsIContent.h" + +#include "nsContentUtils.h" + +#include "mozilla/BasicEvents.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Maybe.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_image.h" +#include "mozilla/SVGImageContext.h" +#include "Units.h" +#include "mozilla/image/WebRenderImageProvider.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/dom/ImageTracker.h" + +#if defined(XP_WIN) +// Undefine LoadImage to prevent naming conflict with Windows. +# undef LoadImage +#endif + +#define ONLOAD_CALLED_TOO_EARLY 1 + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; +using namespace mozilla::layers; + +using mozilla::dom::Document; +using mozilla::dom::Element; +using mozilla::dom::ReferrerInfo; + +class nsImageBoxFrameEvent : public Runnable { + public: + nsImageBoxFrameEvent(nsIContent* content, EventMessage message) + : mozilla::Runnable("nsImageBoxFrameEvent"), + mContent(content), + mMessage(message) {} + + NS_IMETHOD Run() override; + + private: + const nsCOMPtr<nsIContent> mContent; + EventMessage mMessage; +}; + +// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398) +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsImageBoxFrameEvent::Run() { + RefPtr<nsPresContext> presContext = mContent->OwnerDoc()->GetPresContext(); + if (!presContext) { + return NS_OK; + } + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(true, mMessage); + + event.mFlags.mBubbles = false; + EventDispatcher::Dispatch(mContent, presContext, &event, nullptr, &status); + return NS_OK; +} + +// Fire off an event that'll asynchronously call the image elements +// onload handler once handled. This is needed since the image library +// can't decide if it wants to call its observer methods +// synchronously or asynchronously. If an image is loaded from the +// cache the notifications come back synchronously, but if the image +// is loaded from the network the notifications come back +// asynchronously. +static void FireImageDOMEvent(nsIContent* aContent, EventMessage aMessage) { + NS_ASSERTION(aMessage == eLoad || aMessage == eLoadError, "invalid message"); + + nsCOMPtr<nsIRunnable> event = new nsImageBoxFrameEvent(aContent, aMessage); + nsresult rv = + aContent->OwnerDoc()->Dispatch(TaskCategory::Other, event.forget()); + if (NS_FAILED(rv)) { + NS_WARNING("failed to dispatch image event"); + } +} + +// +// NS_NewImageBoxFrame +// +// Creates a new image frame and returns it +// +nsIFrame* NS_NewImageBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) nsImageBoxFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame) +NS_QUERYFRAME_HEAD(nsImageBoxFrame) + NS_QUERYFRAME_ENTRY(nsImageBoxFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) + +nsresult nsImageBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + nsresult rv = + nsLeafBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); + + if (aAttribute == nsGkAtoms::src) { + UpdateImage(); + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); + } else if (aAttribute == nsGkAtoms::validate) + UpdateLoadFlags(); + + return rv; +} + +nsImageBoxFrame::nsImageBoxFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsLeafBoxFrame(aStyle, aPresContext, kClassID), + mIntrinsicSize(0, 0), + mLoadFlags(nsIRequest::LOAD_NORMAL), + mRequestRegistered(false), + mUseSrcAttr(false), + mSuppressStyleCheck(false) { + MarkIntrinsicISizesDirty(); +} + +nsImageBoxFrame::~nsImageBoxFrame() = default; + +/* virtual */ +void nsImageBoxFrame::MarkIntrinsicISizesDirty() { + XULSizeNeedsRecalc(mImageSize); + nsLeafBoxFrame::MarkIntrinsicISizesDirty(); +} + +void nsImageBoxFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + if (mImageRequest) { + nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest, + &mRequestRegistered); + + mImageRequest->UnlockImage(); + + if (mUseSrcAttr) { + PresContext()->Document()->ImageTracker()->Remove(mImageRequest); + } + + // Release image loader first so that it's refcnt can go to zero + mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); + } + + if (mListener) { + // set the frame to null so we don't send messages to a dead object. + reinterpret_cast<nsImageBoxListener*>(mListener.get())->ClearFrame(); + } + + nsLeafBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +void nsImageBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + if (!mListener) { + RefPtr<nsImageBoxListener> listener = new nsImageBoxListener(this); + mListener = std::move(listener); + } + + mSuppressStyleCheck = true; + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + mSuppressStyleCheck = false; + + UpdateLoadFlags(); + UpdateImage(); +} + +void nsImageBoxFrame::UpdateImage() { + nsPresContext* presContext = PresContext(); + Document* doc = presContext->Document(); + + RefPtr<imgRequestProxy> oldImageRequest = mImageRequest; + + if (mImageRequest) { + nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest, + &mRequestRegistered); + mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); + if (mUseSrcAttr) { + doc->ImageTracker()->Remove(mImageRequest); + } + mImageRequest = nullptr; + } + + // get the new image src + nsAutoString src; + mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src); + mUseSrcAttr = !src.IsEmpty(); + if (mUseSrcAttr) { + 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, doc, + mContent->GetBaseURI()); + if (uri) { + auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mContent->AsElement()); + nsresult rv = nsContentUtils::LoadImage( + uri, mContent, doc, triggeringPrincipal, requestContextID, + referrerInfo, mListener, mLoadFlags, u""_ns, + getter_AddRefs(mImageRequest), contentPolicyType); + + if (NS_SUCCEEDED(rv) && mImageRequest) { + nsLayoutUtils::RegisterImageRequestIfAnimated( + presContext, mImageRequest, &mRequestRegistered); + + // Add to the ImageTracker so that we can find it when media + // feature values change (e.g. when the system theme changes) + // and invalidate the image. This allows favicons to respond + // to these changes. + doc->ImageTracker()->Add(mImageRequest); + } + } + } else if (auto* styleImage = GetImageFromStyle()) { + if (auto* styleRequest = styleImage->GetImageRequest()) { + styleRequest->SyncClone(mListener, mContent->GetComposedDoc(), + getter_AddRefs(mImageRequest)); + } + } + + if (!mImageRequest) { + // We have no image, so size to 0 + mIntrinsicSize.SizeTo(0, 0); + } else { + // We don't want discarding or decode-on-draw for xul images. + mImageRequest->StartDecoding(imgIContainer::FLAG_ASYNC_NOTIFY); + mImageRequest->LockImage(); + } + + // Do this _after_ locking the new image in case they are the same image. + if (oldImageRequest) { + oldImageRequest->UnlockImage(); + } +} + +void nsImageBoxFrame::UpdateLoadFlags() { + static Element::AttrValuesArray strings[] = {nsGkAtoms::always, + nsGkAtoms::never, nullptr}; + switch (mContent->AsElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::validate, strings, eCaseMatters)) { + case 0: + mLoadFlags = nsIRequest::VALIDATE_ALWAYS; + break; + case 1: + mLoadFlags = nsIRequest::VALIDATE_NEVER | nsIRequest::LOAD_FROM_CACHE; + break; + default: + mLoadFlags = nsIRequest::LOAD_NORMAL; + break; + } +} + +void nsImageBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + nsLeafBoxFrame::BuildDisplayList(aBuilder, aLists); + + if ((0 == mRect.width) || (0 == mRect.height)) { + // Do not render when given a zero area. This avoids some useless + // scaling work while we wait for our image dimensions to arrive + // asynchronously. + return; + } + + if (!IsVisibleForPainting()) return; + + uint32_t clipFlags = + nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) + ? 0 + : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; + + DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip( + aBuilder, this, clipFlags); + + aLists.Content()->AppendNewToTop<nsDisplayXULImage>(aBuilder, this); +} + +already_AddRefed<imgIContainer> nsImageBoxFrame::GetImageContainerForPainting( + const nsPoint& aPt, ImgDrawResult& aDrawResult, + Maybe<nsPoint>& aAnchorPoint, nsRect& aDest) { + if (!mImageRequest) { + // This probably means we're drawn by a native theme. + aDrawResult = ImgDrawResult::SUCCESS; + return nullptr; + } + + // Don't draw if the image's size isn't available. + uint32_t imgStatus; + if (!NS_SUCCEEDED(mImageRequest->GetImageStatus(&imgStatus)) || + !(imgStatus & imgIRequest::STATUS_SIZE_AVAILABLE)) { + aDrawResult = ImgDrawResult::NOT_READY; + return nullptr; + } + + nsCOMPtr<imgIContainer> imgCon; + mImageRequest->GetImage(getter_AddRefs(imgCon)); + + if (!imgCon) { + aDrawResult = ImgDrawResult::NOT_READY; + return nullptr; + } + + aDest = GetDestRect(aPt, aAnchorPoint); + aDrawResult = ImgDrawResult::SUCCESS; + return imgCon.forget(); +} + +ImgDrawResult nsImageBoxFrame::PaintImage(gfxContext& aRenderingContext, + const nsRect& aDirtyRect, nsPoint aPt, + uint32_t aFlags) { + ImgDrawResult result; + Maybe<nsPoint> anchorPoint; + nsRect dest; + nsCOMPtr<imgIContainer> imgCon = + GetImageContainerForPainting(aPt, result, anchorPoint, dest); + if (!imgCon) { + return result; + } + + // don't draw if the image is not dirty + // XXX(seth): Can this actually happen anymore? + nsRect dirty; + if (!dirty.IntersectRect(aDirtyRect, dest)) { + return ImgDrawResult::TEMPORARY_ERROR; + } + + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + + SVGImageContext svgContext; + SVGImageContext::MaybeStoreContextPaint(svgContext, this, imgCon); + return nsLayoutUtils::DrawSingleImage( + aRenderingContext, PresContext(), imgCon, + nsLayoutUtils::GetSamplingFilterForFrame(this), dest, dirty, svgContext, + aFlags, anchorPoint.ptrOr(nullptr), hasSubRect ? &mSubRect : nullptr); +} + +ImgDrawResult nsImageBoxFrame::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem, + nsPoint aPt, uint32_t aFlags) { + ImgDrawResult result; + Maybe<nsPoint> anchorPoint; + nsRect dest; + nsCOMPtr<imgIContainer> imgCon = + GetImageContainerForPainting(aPt, result, anchorPoint, dest); + if (!imgCon) { + return result; + } + + if (StaticPrefs::image_svg_blob_image() && + imgCon->GetType() == imgIContainer::TYPE_VECTOR) { + aFlags |= imgIContainer::FLAG_RECORD_BLOB; + } + + const int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect fillRect = + LayoutDeviceRect::FromAppUnits(dest, appUnitsPerDevPixel); + + SVGImageContext svgContext; + Maybe<ImageIntRegion> region; + gfx::IntSize decodeSize = + nsLayoutUtils::ComputeImageContainerDrawingParameters( + imgCon, aItem->Frame(), fillRect, fillRect, aSc, aFlags, svgContext, + region); + + RefPtr<image::WebRenderImageProvider> provider; + result = + imgCon->GetImageProvider(aManager->LayerManager(), decodeSize, svgContext, + region, aFlags, getter_AddRefs(provider)); + + Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageProviderKey( + aItem, provider, result, aResources); + if (key.isNothing()) { + return result; + } + + auto rendering = wr::ToImageRendering(aItem->Frame()->UsedImageRendering()); + wr::LayoutRect fill = wr::ToLayoutRect(fillRect); + + aBuilder.PushImage(fill, fill, !BackfaceIsHidden(), false, rendering, + key.value()); + return result; +} + +nsRect nsImageBoxFrame::GetDestRect(const nsPoint& aOffset, + Maybe<nsPoint>& aAnchorPoint) { + nsCOMPtr<imgIContainer> imgCon; + mImageRequest->GetImage(getter_AddRefs(imgCon)); + MOZ_ASSERT(imgCon); + + nsRect clientRect; + GetXULClientRect(clientRect); + clientRect += aOffset; + nsRect dest; + if (!mUseSrcAttr) { + // Our image (if we have one) is coming from the CSS property + // 'list-style-image' (combined with '-moz-image-region'). For now, ignore + // 'object-fit' & 'object-position' in this case, and just fill our rect. + // XXXdholbert Should we even honor these properties in this case? They only + // apply to replaced elements, and I'm not sure we count as a replaced + // element when our image data is determined by CSS. + dest = clientRect; + } else { + // Determine dest rect based on intrinsic size & ratio, along with + // 'object-fit' & 'object-position' properties: + IntrinsicSize intrinsicSize; + AspectRatio intrinsicRatio; + if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) { + // Image has a valid size; use it as intrinsic size & ratio. + intrinsicSize = + IntrinsicSize(mIntrinsicSize.width, mIntrinsicSize.height); + intrinsicRatio = + AspectRatio::FromSize(mIntrinsicSize.width, mIntrinsicSize.height); + } else { + // Image doesn't have a (valid) intrinsic size. + // Try to look up intrinsic ratio and use that at least. + intrinsicRatio = imgCon->GetIntrinsicRatio().valueOr(AspectRatio()); + } + aAnchorPoint.emplace(); + dest = nsLayoutUtils::ComputeObjectDestRect(clientRect, intrinsicSize, + intrinsicRatio, StylePosition(), + aAnchorPoint.ptr()); + } + + return dest; +} + +void nsDisplayXULImage::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // Even though we call StartDecoding when we get a new image we pass + // FLAG_SYNC_DECODE_IF_FAST here for the case where the size we draw at is not + // the intrinsic size of the image and we aren't likely to implement + // predictive decoding at the correct size for this class like nsImageFrame + // has. + uint32_t flags = imgIContainer::FLAG_SYNC_DECODE_IF_FAST; + if (aBuilder->ShouldSyncDecodeImages()) + flags |= imgIContainer::FLAG_SYNC_DECODE; + if (aBuilder->UseHighQualityScaling()) + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + + Unused << static_cast<nsImageBoxFrame*>(mFrame)->PaintImage( + *aCtx, GetPaintRect(aBuilder, aCtx), ToReferenceFrame(), flags); +} + +bool nsDisplayXULImage::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsImageBoxFrame* imageFrame = static_cast<nsImageBoxFrame*>(mFrame); + if (!imageFrame->CanOptimizeToImageLayer()) { + return false; + } + + if (!imageFrame->mImageRequest) { + return true; + } + + uint32_t flags = imgIContainer::FLAG_SYNC_DECODE_IF_FAST | + imgIContainer::FLAG_ASYNC_NOTIFY; + if (aDisplayListBuilder->ShouldSyncDecodeImages()) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } + if (aDisplayListBuilder->IsPaintingToWindow()) { + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + + ImgDrawResult result = imageFrame->CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, this, ToReferenceFrame(), flags); + if (result == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + + return true; +} + +bool nsImageBoxFrame::CanOptimizeToImageLayer() { + bool hasSubRect = !mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0); + if (hasSubRect) { + return false; + } + return true; +} + +const mozilla::StyleImage* nsImageBoxFrame::GetImageFromStyle( + const ComputedStyle& aStyle) const { + const nsStyleDisplay* disp = aStyle.StyleDisplay(); + if (disp->HasAppearance()) { + nsPresContext* pc = PresContext(); + if (pc->Theme()->ThemeSupportsWidget(pc, const_cast<nsImageBoxFrame*>(this), + disp->EffectiveAppearance())) { + return nullptr; + } + } + auto& image = aStyle.StyleList()->mListStyleImage; + if (!image.IsImageRequestType()) { + return nullptr; + } + return ℑ +} + +ImageResolution nsImageBoxFrame::GetImageResolution() const { + if (auto* image = GetImageFromStyle()) { + return image->GetResolution(); + } + if (!mImageRequest) { + return {}; + } + nsCOMPtr<imgIContainer> image; + mImageRequest->GetImage(getter_AddRefs(image)); + if (!image) { + return {}; + } + return image->GetResolution(); +} + +/* virtual */ +void nsImageBoxFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { + nsLeafBoxFrame::DidSetComputedStyle(aOldStyle); + + // Fetch our subrect. + const nsStyleList* myList = StyleList(); + mSubRect = myList->GetImageRegion(); // before |mSuppressStyleCheck| test! + + if (mUseSrcAttr || mSuppressStyleCheck) { + return; // No more work required, since the image isn't specified by style. + } + + auto* oldImage = aOldStyle ? GetImageFromStyle(*aOldStyle) : nullptr; + auto* newImage = GetImageFromStyle(); + if (newImage == oldImage || + (newImage && oldImage && *oldImage == *newImage)) { + return; + } + UpdateImage(); +} + +void nsImageBoxFrame::GetImageSize() { + if (mIntrinsicSize.width > 0 && mIntrinsicSize.height > 0) { + mImageSize.width = mIntrinsicSize.width; + mImageSize.height = mIntrinsicSize.height; + } else { + mImageSize.width = 0; + mImageSize.height = 0; + } +} + +/** + * Ok return our dimensions + */ +nsSize nsImageBoxFrame::GetXULPrefSize(nsBoxLayoutState& aState) { + nsSize size(0, 0); + DISPLAY_PREF_SIZE(this, size); + if (XULNeedsRecalc(mImageSize)) { + GetImageSize(); + } + + if (!mUseSrcAttr && (mSubRect.width > 0 || mSubRect.height > 0)) { + size = mSubRect.Size(); + } else { + size = mImageSize; + } + + nsSize intrinsicSize = size; + + nsMargin borderPadding(0, 0, 0, 0); + GetXULBorderAndPadding(borderPadding); + size.width += borderPadding.LeftRight(); + size.height += borderPadding.TopBottom(); + + bool widthSet, heightSet; + nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet); + NS_ASSERTION( + size.width != NS_UNCONSTRAINEDSIZE && size.height != NS_UNCONSTRAINEDSIZE, + "non-intrinsic size expected"); + + nsSize minSize = GetXULMinSize(aState); + nsSize maxSize = GetXULMaxSize(aState); + + if (!widthSet && !heightSet) { + if (minSize.width != NS_UNCONSTRAINEDSIZE) + minSize.width -= borderPadding.LeftRight(); + if (minSize.height != NS_UNCONSTRAINEDSIZE) + minSize.height -= borderPadding.TopBottom(); + if (maxSize.width != NS_UNCONSTRAINEDSIZE) + maxSize.width -= borderPadding.LeftRight(); + if (maxSize.height != NS_UNCONSTRAINEDSIZE) + maxSize.height -= borderPadding.TopBottom(); + + size = nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions( + minSize.width, minSize.height, maxSize.width, maxSize.height, + intrinsicSize.width, intrinsicSize.height); + NS_ASSERTION(size.width != NS_UNCONSTRAINEDSIZE && + size.height != NS_UNCONSTRAINEDSIZE, + "non-intrinsic size expected"); + size.width += borderPadding.LeftRight(); + size.height += borderPadding.TopBottom(); + return size; + } + + if (!widthSet) { + if (intrinsicSize.height > 0) { + // Subtract off the border and padding from the height because the + // content-box needs to be used to determine the ratio + nscoord height = size.height - borderPadding.TopBottom(); + size.width = nscoord(int64_t(height) * int64_t(intrinsicSize.width) / + int64_t(intrinsicSize.height)); + } else { + size.width = intrinsicSize.width; + } + + size.width += borderPadding.LeftRight(); + } else if (!heightSet) { + if (intrinsicSize.width > 0) { + nscoord width = size.width - borderPadding.LeftRight(); + size.height = nscoord(int64_t(width) * int64_t(intrinsicSize.height) / + int64_t(intrinsicSize.width)); + } else { + size.height = intrinsicSize.height; + } + + size.height += borderPadding.TopBottom(); + } + + return XULBoundsCheck(minSize, size, maxSize); +} + +nsSize nsImageBoxFrame::GetXULMinSize(nsBoxLayoutState& aState) { + // An image can always scale down to (0,0). + nsSize size(0, 0); + DISPLAY_MIN_SIZE(this, size); + AddXULBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddXULMinSize(this, size, widthSet, heightSet); + return size; +} + +nscoord nsImageBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aState) { + return GetXULPrefSize(aState).height; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult nsImageBoxFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"ImageBox"_ns, aResult); +} +#endif + +void nsImageBoxFrame::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + return OnSizeAvailable(aRequest, image); + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + return OnDecodeComplete(aRequest); + } + + 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); + } + + if (aType == imgINotificationObserver::IS_ANIMATED) { + return OnImageIsAnimated(aRequest); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + return OnFrameUpdate(aRequest); + } +} + +void nsImageBoxFrame::OnSizeAvailable(imgIRequest* aRequest, + imgIContainer* aImage) { + if (NS_WARN_IF(!aImage)) { + return; + } + + // Ensure the animation (if any) is started. Note: There is no + // corresponding call to Decrement for this. This Increment will be + // 'cleaned up' by the Request when it is destroyed, but only then. + aRequest->IncrementAnimationConsumers(); + + aImage->SetAnimationMode(PresContext()->ImageAnimationMode()); + + int32_t w = 0, h = 0; + aImage->GetWidth(&w); + aImage->GetHeight(&h); + + mIntrinsicSize.SizeTo(CSSPixel::ToAppUnits(w), CSSPixel::ToAppUnits(h)); + + GetImageResolution().ApplyTo(mIntrinsicSize.width, mIntrinsicSize.height); + + if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); + } +} + +void nsImageBoxFrame::OnDecodeComplete(imgIRequest* aRequest) { + nsBoxLayoutState state(PresContext()); + this->XULRedraw(state); +} + +void nsImageBoxFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + // Fire an onload DOM event. + FireImageDOMEvent(mContent, eLoad); + } else { + // Fire an onerror DOM event. + mIntrinsicSize.SizeTo(0, 0); + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); + FireImageDOMEvent(mContent, eLoadError); + } +} + +void nsImageBoxFrame::OnImageIsAnimated(imgIRequest* aRequest) { + // Register with our refresh driver, if we're animated. + nsLayoutUtils::RegisterImageRequest(PresContext(), aRequest, + &mRequestRegistered); +} + +void nsImageBoxFrame::OnFrameUpdate(imgIRequest* aRequest) { + if ((0 == mRect.width) || (0 == mRect.height)) { + return; + } + + // 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_XUL_IMAGE; + const auto providerId = aRequest->GetProviderId(); + if (WebRenderUserData::ProcessInvalidateForImage(this, type, providerId)) { + return; + } + + InvalidateLayer(type); +} + +NS_IMPL_ISUPPORTS(nsImageBoxListener, imgINotificationObserver) + +nsImageBoxListener::nsImageBoxListener(nsImageBoxFrame* frame) + : mFrame(frame) {} + +nsImageBoxListener::~nsImageBoxListener() = default; + +void nsImageBoxListener::Notify(imgIRequest* request, int32_t aType, + const nsIntRect* aData) { + if (!mFrame) { + return; + } + + return mFrame->Notify(request, aType, aData); +} |