/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "gfx2DGlue.h" #include "gfxUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/EventStates.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "mozilla/gfx/PathHelpers.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/MouseEvents.h" #include "mozilla/Unused.h" #include "nsCOMPtr.h" #include "nsFontMetrics.h" #include "nsIImageLoadingContent.h" #include "nsString.h" #include "nsPrintfCString.h" #include "nsPresContext.h" #include "nsRenderingContext.h" #include "nsIPresShell.h" #include "nsGkAtoms.h" #include "nsIDocument.h" #include "nsContentUtils.h" #include "nsCSSAnonBoxes.h" #include "nsStyleContext.h" #include "nsStyleConsts.h" #include "nsStyleCoord.h" #include "nsStyleUtil.h" #include "nsTransform2D.h" #include "nsImageMap.h" #include "nsIIOService.h" #include "nsILoadGroup.h" #include "nsISupportsPriority.h" #include "nsNetUtil.h" #include "nsNetCID.h" #include "nsCSSRendering.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsNameSpaceManager.h" #include #ifdef ACCESSIBILITY #include "nsAccessibilityService.h" #endif #include "nsIDOMNode.h" #include "nsLayoutUtils.h" #include "nsDisplayList.h" #include "nsIContent.h" #include "nsIDocument.h" #include "FrameLayerBuilder.h" #include "nsISelectionController.h" #include "nsISelection.h" #include "imgIContainer.h" #include "imgLoader.h" #include "imgRequestProxy.h" #include "nsCSSFrameConstructor.h" #include "nsIDOMRange.h" #include "nsError.h" #include "nsBidiUtils.h" #include "nsBidiPresUtils.h" #include "gfxRect.h" #include "ImageLayers.h" #include "ImageContainer.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" #include "nsBlockFrame.h" #include "nsStyleStructInlines.h" #include "mozilla/Preferences.h" #include "mozilla/dom/Link.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::image; using namespace mozilla::layers; // sizes (pixels) for image icon, padding and border frame #define ICON_SIZE (16) #define ICON_PADDING (3) #define ALT_BORDER_WIDTH (1) //we must add hooks soon #define IMAGE_EDITOR_CHECK 1 // Default alignment value (so we can tell an unset value from a set value) #define ALIGN_UNSET uint8_t(-1) // static icon information nsImageFrame::IconLoad* nsImageFrame::gIconLoad = nullptr; // cached IO service for loading icons nsIIOService* nsImageFrame::sIOService; // test if the width and height are fixed, looking at the style data // This is used by nsImageFrame::ShouldCreateImageFrameFor and should // not be used for layout decisions. static bool HaveSpecifiedSize(const nsStylePosition* aStylePosition) { // check the width and height values in the reflow state'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.IsCoordPercentCalcUnit() && aStylePosition->mHeight.IsCoordPercentCalcUnit(); } // Decide whether we can optimize away reflows that result from the // image's intrinsic size changing. static bool HaveFixedSize(const ReflowInput& aReflowInput) { NS_ASSERTION(aReflowInput.mStylePosition, "crappy reflowInput - null stylePosition"); // 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). return aReflowInput.mStylePosition->mHeight.ConvertsToLength() && aReflowInput.mStylePosition->mWidth.ConvertsToLength(); } nsIFrame* NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsImageFrame(aContext); } NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame) nsImageFrame::nsImageFrame(nsStyleContext* aContext) : nsAtomicContainerFrame(aContext), mComputedSize(0, 0), mDisplayingIcon(false), mFirstFrameComplete(false), mReflowCallbackPosted(false), mForceSyncDecoding(false) { EnableVisibilityTracking(); // We assume our size is not constrained and we haven't gotten an // initial reflow yet, so don't touch those flags. mIntrinsicSize.width.SetCoordValue(0); mIntrinsicSize.height.SetCoordValue(0); } nsImageFrame::~nsImageFrame() { } NS_QUERYFRAME_HEAD(nsImageFrame) NS_QUERYFRAME_ENTRY(nsImageFrame) NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) #ifdef ACCESSIBILITY a11y::AccType nsImageFrame::AccessibleType() { // Don't use GetImageMap() to avoid reentrancy into accessibility. if (HasImageMap()) { return a11y::eHTMLImageMapType; } return a11y::eImageType; } #endif void nsImageFrame::DisconnectMap() { if (mImageMap) { mImageMap->Destroy(); mImageMap = nullptr; #ifdef ACCESSIBILITY nsAccessibilityService* accService = GetAccService(); if (accService) { accService->RecreateAccessible(PresContext()->PresShell(), mContent); } #endif } } void nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot) { if (mReflowCallbackPosted) { PresContext()->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(); // set the frame to null so we don't send messages to a dead object. if (mListener) { nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { // Notify our image loading content that we are going away so it can // deregister with our refresh driver. imageLoader->FrameDestroyed(this); imageLoader->RemoveObserver(mListener); } reinterpret_cast(mListener.get())->SetFrame(nullptr); } mListener = nullptr; // If we were displaying an icon, take ourselves off the list if (mDisplayingIcon) gIconLoad->RemoveIconObserver(this); nsAtomicContainerFrame::DestroyFrom(aDestructRoot); } void nsImageFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { nsAtomicContainerFrame::DidSetStyleContext(aOldStyleContext); nsStyleImageOrientation newOrientation = StyleVisibility()->mImageOrientation; // We need to update our orientation either if we had no style context before // because this is the first time it's been set, or if the image-orientation // property changed from its previous value. bool shouldUpdateOrientation = mImage && (!aOldStyleContext || aOldStyleContext->StyleVisibility()->mImageOrientation != newOrientation); if (shouldUpdateOrientation) { nsCOMPtr image(mImage->Unwrap()); mImage = nsLayoutUtils::OrientImage(image, newOrientation); UpdateIntrinsicSize(); UpdateIntrinsicRatio(); } else if (!aOldStyleContext || aOldStyleContext->StylePosition()->mAspectRatio != StylePosition()->mAspectRatio) { UpdateIntrinsicRatio(); } } void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); mListener = new nsImageListener(this); nsCOMPtr imageLoader = do_QueryInterface(aContent); if (!imageLoader) { NS_RUNTIMEABORT("Why do we have an nsImageFrame here at all?"); } imageLoader->AddObserver(mListener); nsPresContext *aPresContext = PresContext(); if (!gIconLoad) LoadIcons(aPresContext); // We have a PresContext now, so we need to notify the image content node // that it can register images. imageLoader->FrameCreated(this); // Give image loads associated with an image frame a small priority boost! nsCOMPtr currentRequest; imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); nsCOMPtr p = do_QueryInterface(currentRequest); if (p) p->AdjustPriority(-1); } static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage, bool aUseMappedRatio, const nsImageFrame& aFrame) { // When 'contain: size' is implemented, make sure to check for it. /* const ComputedStyle& style = *aFrame.Style(); if (style.StyleDisplay()->IsContainSize()) { return AspectRatio(); } */ nsSize size; IntrinsicSize intrinsicSize; if (aImage && NS_SUCCEEDED(aImage->GetIntrinsicSize(&size))) { if (size.width != -1) intrinsicSize.width.SetCoordValue(size.width); if (size.height != -1) intrinsicSize.height.SetCoordValue(size.height); return intrinsicSize; } // If broken images should ever lose their size /* if (aFrame.ShouldShowBrokenImageIcon()) { nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); intrinsicSize.width.SetCoordValue(edgeLengthToUse); intrinsicSize.height.SetCoordValue(edgeLengthToUse); return intrinsicSize; } */ if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio != 0.0f) { return IntrinsicSize(); } intrinsicSize.width.SetCoordValue(0); intrinsicSize.height.SetCoordValue(0); return intrinsicSize; } // 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). // // If ever gets implemented, this will need to check for it. bool nsImageFrame::ShouldUseMappedAspectRatio() const { nsCOMPtr currentRequest; nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); } 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 = static_cast(mContent); return image && image->IsAwaitingLoad(); } bool nsImageFrame::UpdateIntrinsicSize() { IntrinsicSize oldIntrinsicSize = mIntrinsicSize; mIntrinsicSize = ComputeIntrinsicSize(mImage, ShouldUseMappedAspectRatio(), *this); return mIntrinsicSize != oldIntrinsicSize; } static AspectRatio ComputeAspectRatio(imgIContainer* aImage, bool aUseMappedRatio, const nsImageFrame& aFrame) { // When 'contain: size' is implemented, make sure to check for it. /* const ComputedStyle& style = *aFrame.Style(); if (style.StyleDisplay()->IsContainSize()) { return AspectRatio(); } */ if (aImage) { AspectRatio fromImage; if (NS_SUCCEEDED(aImage->GetIntrinsicRatio(&fromImage))) { return fromImage; } } if (aUseMappedRatio && aFrame.StylePosition()->mAspectRatio != 0.0f) { return AspectRatio(aFrame.StylePosition()->mAspectRatio); } if (aFrame.ShouldShowBrokenImageIcon()) { return AspectRatio(1.0f); } return AspectRatio(); } bool nsImageFrame::UpdateIntrinsicRatio() { AspectRatio oldIntrinsicRatio = mIntrinsicRatio; mIntrinsicRatio = ComputeAspectRatio(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 GetInnerArea()'s own size here, // because GetInnerArea() 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(GetInnerArea().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)); // Set the scale factors, based on destRect and intrinsic size. if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.width.GetCoordValue() != 0 && mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.height.GetCoordValue() != 0 && mIntrinsicSize.width.GetCoordValue() != destRect.width && mIntrinsicSize.height.GetCoordValue() != destRect.height) { aTransform.SetScale(float(destRect.width) / float(mIntrinsicSize.width.GetCoordValue()), float(destRect.height) / float(mIntrinsicSize.height.GetCoordValue())); return true; } return false; } // 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 nsCOMPtr imageLoader(do_QueryInterface(mContent)); NS_ASSERTION(imageLoader, "No image loading content?"); 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 inner area, // to be on the safe side (since this method is used for generating // invalidation rects). return GetInnerArea(); } 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; } // Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK". This means // that we'll construct image frames for them as needed if their display is // toggled from "none" (though we won't paint them, unless their visibility // is changed too). #define BAD_STATES (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED) static bool ImageOk(EventStates aState) { return !aState.HasAtLeastOneOfStates(BAD_STATES); } static bool HasAltText(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)); nsAutoString altText; return aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, altText) && !altText.IsEmpty(); } // Check if we want to use an image frame or just let the frame constructor make // us into an inline. /* static */ bool nsImageFrame::ShouldCreateImageFrameFor(Element* aElement, nsStyleContext* aStyleContext) { if (ImageOk(aElement->State())) { // Image is fine or loading; do the image frame thing return true; } // If our special "force icons" style is set, show an icon if (aStyleContext->StyleUIReset()->mForceBrokenImageIcon) { return true; } // If our "do not show placeholders" pref is set, skip the icon if (gIconLoad && gIconLoad->mPrefForceInlineAltText) { return false; } // If there is no Alt text, always create an image frame (regardless of src) if (!HasAltText(aElement)) { return true; } if (aStyleContext->PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) { return HaveSpecifiedSize(aStyleContext->StylePosition()); } return false; } nsresult 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::LOAD_COMPLETE) { uint32_t imgStatus; aRequest->GetImageStatus(&imgStatus); nsresult status = imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; return OnLoadComplete(aRequest, status); } return NS_OK; } 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); } nsresult nsImageFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) { if (!aImage) return NS_ERROR_INVALID_ARG; /* Get requested animation policy from the pres context: * normal = 0 * one frame = 1 * one loop = 2 */ nsPresContext *presContext = PresContext(); aImage->SetAnimationMode(presContext->ImageAnimationMode()); if (IsPendingLoad(aRequest)) { // We don't care return NS_OK; } UpdateImage(aRequest, aImage); return NS_OK; } void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { MOZ_ASSERT(aRequest); if (SizeIsAvailable(aRequest)) { // This is valid and for the current request, so update our stored image // container, orienting according to our style. mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation); MOZ_ASSERT(mImage); } else { // We no longer have a valid image, so release our stored image container. mImage = mPrevImage = nullptr; } // NOTE(emilio): Intentionally using `|` instead of `||` to avoid // short-circuiting. bool intrinsicSizeChanged = UpdateIntrinsicSize() | UpdateIntrinsicRatio(); if (!(mState & IMAGE_GOTINITIALREFLOW)) { return; } // We're going to need to repaint now either way. InvalidateFrame(); if (intrinsicSizeChanged) { // Now we need to reflow if we have an unconstrained size and have // already gotten the initial reflow. if (!(mState & IMAGE_SIZECONSTRAINED)) { nsIPresShell *presShell = PresContext()->GetPresShell(); NS_ASSERTION(presShell, "No PresShell."); if (presShell) { presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); } } else { // We've already gotten the initial reflow, and our size hasn't changed, // so we're ready to request a decode. MaybeDecodeForPredictedSize(); } mPrevImage = nullptr; } } nsresult nsImageFrame::OnFrameUpdate(imgIRequest* aRequest, const nsIntRect* aRect) { NS_ENSURE_ARG_POINTER(aRect); if (!(mState & IMAGE_GOTINITIALREFLOW)) { // Don't bother to do anything; we have a reflow coming up! return NS_OK; } if (mFirstFrameComplete && !StyleVisibility()->IsVisible()) { return NS_OK; } if (IsPendingLoad(aRequest)) { // We don't care return NS_OK; } nsIntRect layerInvalidRect = mImage ? mImage->GetImageSpaceInvalidationRect(*aRect) : *aRect; if (layerInvalidRect.IsEqualInterior(GetMaxSizedIntRect())) { // Invalidate our entire area. InvalidateSelf(nullptr, nullptr); return NS_OK; } nsRect frameInvalidRect = SourceRectToDest(layerInvalidRect); InvalidateSelf(&layerInvalidRect, &frameInvalidRect); return NS_OK; } void nsImageFrame::InvalidateSelf(const nsIntRect* aLayerInvalidRect, const nsRect* aFrameInvalidRect) { InvalidateLayer(nsDisplayItem::TYPE_IMAGE, aLayerInvalidRect, aFrameInvalidRect); if (!mFirstFrameComplete) { InvalidateLayer(nsDisplayItem::TYPE_ALT_FEEDBACK, aLayerInvalidRect, aFrameInvalidRect); } } nsresult nsImageFrame::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus) { // Check what request type we're dealing with nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ASSERTION(imageLoader, "Who's notifying us??"); int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST; imageLoader->GetRequestType(aRequest, &loadType); if (loadType != nsIImageLoadingContent::CURRENT_REQUEST && loadType != nsIImageLoadingContent::PENDING_REQUEST) { return NS_ERROR_FAILURE; } NotifyNewCurrentRequest(aRequest, aStatus); return NS_OK; } 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::APPROXIMATELY_VISIBLE) { return; // We're not visible, so don't decode. } // OK, we're ready to decode. Compute the scale to the screen... nsIPresShell* presShell = PresContext()->GetPresShell(); LayoutDeviceToScreenScale2D resolutionToScreen( presShell->GetCumulativeResolution() * nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this)); // ...and this frame's content box... const nsPoint offset = GetOffsetToCrossDoc(nsLayoutUtils::GetReferenceFrame(this)); const nsRect frameContentBox = GetInnerArea() + 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::ShouldShowBrokenImageIcon() const { bool imageBroken = false; // Check for broken images. valid null images (eg. img src="") are // not considered broken because they have no image requests nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { nsCOMPtr currentRequest; imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); uint32_t imageStatus; imageBroken = currentRequest && NS_SUCCEEDED(currentRequest->GetImageStatus(&imageStatus)) && (imageStatus & imgIRequest::STATUS_ERROR); } return imageBroken; } void nsImageFrame::EnsureIntrinsicSizeAndRatio() { // When 'contain: size' is implemented, make sure to check for it. /* if (StyleDisplay()->IsContainSize()) { // If we have 'contain:size', then our intrinsic size and ratio are 0,0 // regardless of what our underlying image may think. mIntrinsicSize = IntrinsicSize(0, 0); mIntrinsicRatio = AspectRatio(); return; } */ // If mIntrinsicSize.width and height are 0, then we need to update from the // image container. if (!(mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.width.GetCoordValue() == 0 && mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.height.GetCoordValue() == 0)) { return; } UpdateIntrinsicSize(); UpdateIntrinsicRatio(); } /* virtual */ LogicalSize nsImageFrame::ComputeSize(nsRenderingContext *aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize, nscoord aAvailableISize, const LogicalSize& aMargin, const LogicalSize& aBorder, const LogicalSize& aPadding, ComputeSizeFlags aFlags) { EnsureIntrinsicSizeAndRatio(); nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ASSERTION(imageLoader, "No content node??"); mozilla::IntrinsicSize intrinsicSize(mIntrinsicSize); // XXX(seth): We may sometimes find ourselves in the situation where we have // mImage, but imageLoader's current request does not have a size yet. // This can happen when we load an image speculatively from cache, it fails // to validate, and the new image load hasn't fired SIZE_AVAILABLE yet. In // this situation we should always use mIntrinsicSize, because // GetNaturalWidth/Height will return 0, so we check CurrentRequestHasSize() // below. See bug 1019840. We will fix this in bug 1141395. // Content may override our default dimensions. This is termed as overriding // the intrinsic size by the spec, but all other consumers of mIntrinsic* // values are being used to refer to the real/true size of the image data. if (imageLoader && imageLoader->CurrentRequestHasSize() && mImage && intrinsicSize.width.GetUnit() == eStyleUnit_Coord && intrinsicSize.height.GetUnit() == eStyleUnit_Coord) { uint32_t width; uint32_t height; if (NS_SUCCEEDED(imageLoader->GetNaturalWidth(&width)) && NS_SUCCEEDED(imageLoader->GetNaturalHeight(&height))) { nscoord appWidth = nsPresContext::CSSPixelsToAppUnits((int32_t)width); nscoord appHeight = nsPresContext::CSSPixelsToAppUnits((int32_t)height); // If this image is rotated, we'll need to transpose the natural // width/height. bool coordFlip; if (StyleVisibility()->mImageOrientation.IsFromImage()) { coordFlip = mImage->GetOrientation().SwapsWidthAndHeight(); } else { coordFlip = StyleVisibility()->mImageOrientation.SwapsWidthAndHeight(); } intrinsicSize.width.SetCoordValue(coordFlip ? appHeight : appWidth); intrinsicSize.height.SetCoordValue(coordFlip ? appWidth : appHeight); } } return ComputeSizeWithIntrinsicDimensions(aRenderingContext, aWM, intrinsicSize, mIntrinsicRatio, aCBSize, aMargin, aBorder, aPadding, aFlags); } // XXXdholbert This function's clients should probably just be calling // GetContentRectRelativeToSelf() directly. nsRect nsImageFrame::GetInnerArea() const { return GetContentRectRelativeToSelf(); } Element* nsImageFrame::GetMapElement() const { nsAutoString usemap; if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usemap, usemap)) { return mContent->OwnerDoc()->FindImageMap(usemap); } return 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; } /* virtual */ nscoord nsImageFrame::GetMinISize(nsRenderingContext *aRenderingContext) { // XXX The caller doesn't account for constraints of the height, // min-height, and max-height properties. DebugOnly result; DISPLAY_MIN_WIDTH(this, result); EnsureIntrinsicSizeAndRatio(); return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ? mIntrinsicSize.width.GetCoordValue() : 0; } /* virtual */ nscoord nsImageFrame::GetPrefISize(nsRenderingContext *aRenderingContext) { // XXX The caller doesn't account for constraints of the height, // min-height, and max-height properties. DebugOnly result; DISPLAY_PREF_WIDTH(this, result); EnsureIntrinsicSizeAndRatio(); // convert from normal twips to scaled twips (printing...) return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ? mIntrinsicSize.width.GetCoordValue() : 0; } /* virtual */ IntrinsicSize nsImageFrame::GetIntrinsicSize() { return mIntrinsicSize; } /* virtual */ AspectRatio nsImageFrame::GetIntrinsicRatio() { return mIntrinsicRatio; } 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); NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("enter nsImageFrame::Reflow: availSize=%d,%d", aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight())); NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); aStatus = NS_FRAME_COMPLETE; // see if we have a frozen size (i.e. a fixed width and height) if (HaveFixedSize(aReflowInput)) { mState |= IMAGE_SIZECONSTRAINED; } else { mState &= ~IMAGE_SIZECONSTRAINED; } // XXXldb These two bits are almost exact opposites (except in the // middle of the initial reflow); remove IMAGE_GOTINITIALREFLOW. if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { mState |= IMAGE_GOTINITIALREFLOW; } mComputedSize = nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()); aMetrics.Width() = mComputedSize.width; aMetrics.Height() = mComputedSize.height; // add borders and padding aMetrics.Width() += aReflowInput.ComputedPhysicalBorderPadding().LeftRight(); aMetrics.Height() += aReflowInput.ComputedPhysicalBorderPadding().TopBottom(); if (GetPrevInFlow()) { aMetrics.Width() = GetPrevInFlow()->GetSize().width; nscoord y = GetContinuationOffset(); aMetrics.Height() -= y + aReflowInput.ComputedPhysicalBorderPadding().top; aMetrics.Height() = std::max(0, aMetrics.Height()); } // we have to split images if we are: // in Paginated mode, we need to have a constrained height, and have a height larger than our available height uint32_t loadStatus = imgIRequest::STATUS_NONE; nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ASSERTION(imageLoader, "No content node??"); if (imageLoader) { nsCOMPtr currentRequest; imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); if (currentRequest) { currentRequest->GetImageStatus(&loadStatus); } } if (aPresContext->IsPaginated() && ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & 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 = NS_FRAME_NOT_COMPLETE; } aMetrics.SetOverflowAreasToDesiredBounds(); bool imageOK = 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 visual overflow, but not in our // scrollable overflow, since it doesn't really need to be scrolled to // outside the image. static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?"); nsRect& visualOverflow = aMetrics.VisualOverflow(); visualOverflow.UnionRect(visualOverflow, altFeedbackSize); } else { // We've just reflowed and we should have an accurate size, so we're ready // to request a decode. MaybeDecodeForPredictedSize(); } FinishAndStoreOverflow(&aMetrics); if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) { nsIPresShell* shell = PresContext()->PresShell(); mReflowCallbackPosted = true; shell->PostReflowCallback(this); } NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsImageFrame::Reflow: size=%d,%d", aMetrics.Width(), aMetrics.Height())); NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics); } 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, nsRenderingContext& 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, nsRenderingContext& aRenderingContext, const nsString& aAltText, const nsRect& aRect) { // Set font and color aRenderingContext.ThebesContext()-> SetColor(Color::FromABGR(StyleColor()->mColor)); 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()) { nsBidiDirection dir; nscoord x, y; if (isVertical) { x = pt.x + maxDescent; if (wm.IsBidiLTR()) { y = aRect.y; dir = NSBIDI_LTR; } else { y = aRect.YMost() - strWidth; dir = NSBIDI_RTL; } } else { y = pt.y + maxAscent; if (wm.IsBidiLTR()) { x = aRect.x; dir = NSBIDI_LTR; } else { x = aRect.XMost() - strWidth; dir = NSBIDI_RTL; } } rv = nsBidiPresUtils::RenderText(str, maxFit, dir, aPresContext, aRenderingContext, aRenderingContext.GetDrawTarget(), *fm, x, y); } if (NS_FAILED(rv)) { nsLayoutUtils::DrawUniDirString(str, maxFit, isVertical ? nsPoint(pt.x + maxDescent, pt.y) : nsPoint(pt.x, pt.y + maxAscent), *fm, aRenderingContext); } // Move to the next line str += maxFit; strLen -= maxFit; if (wm.IsVerticalRL()) { pt.x -= lineHeight; } else if (wm.IsVerticalLR()) { pt.x += lineHeight; } else { pt.y += lineHeight; } firstLine = false; } } struct nsRecessedBorder : public nsStyleBorder { nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext) : nsStyleBorder(aPresContext) { NS_FOR_CSS_SIDES(side) { mBorderColor[side] = StyleComplexColor::FromColor(NS_RGB(0, 0, 0)); mBorder.Side(side) = aBorderWidth; // Note: use SetBorderStyle here because we want to affect // mComputedBorder SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET); } } }; class nsDisplayAltFeedback : public nsDisplayItem { public: nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) : nsDisplayItem(aBuilder, aFrame) {} virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override { return new nsDisplayItemGenericImageGeometry(this, aBuilder); } virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, nsRegion* aInvalidRegion) override { auto geometry = static_cast(aGeometry); if (aBuilder->ShouldSyncDecodeImages() && geometry->ShouldInvalidateToSyncDecodeImages()) { bool snap; aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); } nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); } virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override { *aSnap = false; return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); } virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override { // 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); DrawResult result = f->DisplayAltFeedback(*aCtx, mVisibleRect, ToReferenceFrame(), flags); nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); } NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK) }; DrawResult nsImageFrame::DisplayAltFeedback(nsRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsPoint aPt, uint32_t aFlags) { // We should definitely have a gIconLoad here. MOZ_ASSERT(gIconLoad, "How did we succeed in Init then?"); // Whether we draw the broken or loading icon. bool isLoading = ImageOk(mContent->AsElement()->State()); // Calculate the inner area nsRect inner = GetInnerArea() + 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 DrawResult::SUCCESS; } // Paint the border if (!isLoading || gIconLoad->mPrefShowLoadingPlaceholder) { nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext()); // Assert that we're not drawing a border-image here; if we were, we // couldn't ignore the DrawResult that PaintBorderWithStyleBorder returns. MOZ_ASSERT(recessedBorder.mBorderImageSource.GetType() == eStyleImageType_Null); Unused << nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext, this, inner, inner, recessedBorder, mStyleContext, PaintBorderFlags::SYNC_DECODE_IMAGES); } // 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 DrawResult::SUCCESS; } DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); gfxContext* gfx = aRenderingContext.ThebesContext(); // Clip so we don't render outside the inner rect gfx->Save(); gfx->Clip(NSRectToSnappedRect(inner, PresContext()->AppUnitsPerDevPixel(), *drawTarget)); DrawResult result = DrawResult::NOT_READY; // Check if we should display image placeholders if (!ShouldShowBrokenImageIcon() || !gIconLoad->mPrefShowPlaceholders || (isLoading && !gIconLoad->mPrefShowLoadingPlaceholder)) { result = DrawResult::SUCCESS; } else { nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); imgIRequest* request = isLoading ? nsImageFrame::gIconLoad->mLoadingImage : nsImageFrame::gIconLoad->mBrokenImage; // 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) { gIconLoad->AddIconObserver(this); mDisplayingIcon = true; } WritingMode wm = GetWritingMode(); bool flushRight = (!wm.IsVertical() && !wm.IsBidiLTR()) || wm.IsVerticalRL(); // If the icon in question is loaded, draw it. uint32_t imageStatus = 0; if (request) request->GetImageStatus(&imageStatus); if (imageStatus & imgIRequest::STATUS_LOAD_COMPLETE) { 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(*gfx, PresContext(), imgCon, nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, nullptr, aFlags); } // If we could not draw the icon, just draw some graffiti in the mean time. if (result == DrawResult::NOT_READY) { ColorPattern color(ToDeviceColor(Color(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()) { nsIContent* content = GetContent(); if (content) { nsXPIDLString altText; nsCSSFrameConstructor::GetAlternateTextFor(content, content->NodeInfo()->NameAtom(), altText); DisplayAltText(PresContext(), aRenderingContext, altText, inner); } } aRenderingContext.ThebesContext()->Restore(); return result; } #ifdef DEBUG static void PaintDebugImageMap(nsIFrame* aFrame, DrawTarget* aDrawTarget, const nsRect& aDirtyRect, nsPoint aPt) { nsImageFrame* f = static_cast(aFrame); nsRect inner = f->GetInnerArea() + aPt; gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(inner.TopLeft(), aFrame->PresContext()->AppUnitsPerDevPixel()); AutoRestoreTransform autoRestoreTransform(aDrawTarget); aDrawTarget->SetTransform( aDrawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); f->GetImageMap()->Draw(aFrame, *aDrawTarget, ColorPattern(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f)))); } #endif void nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) { uint32_t flags = imgIContainer::FLAG_NONE; if (aBuilder->ShouldSyncDecodeImages()) { flags |= imgIContainer::FLAG_SYNC_DECODE; } if (aBuilder->IsPaintingToWindow()) { flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; } DrawResult result = static_cast(mFrame)-> PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mImage, flags); if (result == DrawResult::NOT_READY || result == DrawResult::INCOMPLETE || result == DrawResult::TEMPORARY_ERROR) { // If the current image failed to paint because it's still loading or // decoding, try painting the previous image. if (mPrevImage) { result = static_cast(mFrame)-> PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mPrevImage, flags); } } nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result); } nsDisplayItemGeometry* nsDisplayImage::AllocateGeometry(nsDisplayListBuilder* aBuilder) { return new nsDisplayItemGenericImageGeometry(this, aBuilder); } void nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, nsRegion* aInvalidRegion) { auto geometry = static_cast(aGeometry); if (aBuilder->ShouldSyncDecodeImages() && geometry->ShouldInvalidateToSyncDecodeImages()) { bool snap; aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); } nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); } already_AddRefed nsDisplayImage::GetImage() { nsCOMPtr image = mImage; return image.forget(); } nsRect nsDisplayImage::GetDestRect() { bool snap = true; const nsRect frameContentBox = GetBounds(&snap); nsImageFrame* imageFrame = static_cast(mFrame); return imageFrame->PredictedDestRect(frameContentBox); } LayerState nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { bool animated = false; if (!nsLayoutUtils::AnimatedImageLayersEnabled() || mImage->GetType() != imgIContainer::TYPE_RASTER || NS_FAILED(mImage->GetAnimated(&animated)) || !animated) { if (!aManager->IsCompositingCheap() || !nsLayoutUtils::GPUImageScalingEnabled()) { return LAYER_NONE; } } if (!animated) { int32_t imageWidth; int32_t imageHeight; mImage->GetWidth(&imageWidth); mImage->GetHeight(&imageHeight); NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); const LayoutDeviceRect destRect = LayoutDeviceRect::FromAppUnits(GetDestRect(), factor); const LayerRect destLayerRect = destRect * aParameters.Scale(); // Calculate the scaling factor for the frame. const gfxSize scale = gfxSize(destLayerRect.width / imageWidth, destLayerRect.height / imageHeight); // If we are not scaling at all, no point in separating this into a layer. if (scale.width == 1.0f && scale.height == 1.0f) { return LAYER_NONE; } // If the target size is pretty small, no point in using a layer. if (destLayerRect.width * destLayerRect.height < 64 * 64) { return LAYER_NONE; } } uint32_t flags = aBuilder->ShouldSyncDecodeImages() ? imgIContainer::FLAG_SYNC_DECODE : imgIContainer::FLAG_NONE; if (!mImage->IsImageContainerAvailable(aManager, flags)) { return LAYER_NONE; } return LAYER_ACTIVE; } /* virtual */ nsRegion nsDisplayImage::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) { *aSnap = false; if (mImage && mImage->WillDrawOpaqueNow()) { const nsRect frameContentBox = GetBounds(aSnap); return GetDestRect().Intersect(frameContentBox); } return nsRegion(); } already_AddRefed nsDisplayImage::BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { uint32_t flags = aBuilder->ShouldSyncDecodeImages() ? imgIContainer::FLAG_SYNC_DECODE : imgIContainer::FLAG_NONE; RefPtr container = mImage->GetImageContainer(aManager, flags); if (!container) { return nullptr; } RefPtr layer = static_cast (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); if (!layer) { layer = aManager->CreateImageLayer(); if (!layer) return nullptr; } layer->SetContainer(container); ConfigureLayer(layer, aParameters); return layer.forget(); } DrawResult nsImageFrame::PaintImage(nsRenderingContext& 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(GetInnerArea().width == mComputedSize.width, "bad width"); // NOTE: We use mComputedSize instead of just GetInnerArea()'s own size here, // because GetInnerArea() 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 + GetInnerArea().TopLeft(), mComputedSize); constraintRect.y -= GetContinuationOffset(); nsPoint anchorPoint; nsRect dest = nsLayoutUtils::ComputeObjectDestRect(constraintRect, mIntrinsicSize, mIntrinsicRatio, StylePosition(), &anchorPoint); uint32_t flags = aFlags; if (mForceSyncDecoding) { flags |= imgIContainer::FLAG_SYNC_DECODE; } DrawResult result = nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(), PresContext(), aImage, nsLayoutUtils::GetSamplingFilterForFrame(this), dest, aDirtyRect, nullptr, flags, &anchorPoint); nsImageMap* map = GetImageMap(); if (map) { gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(dest.TopLeft(), PresContext()->AppUnitsPerDevPixel()); AutoRestoreTransform autoRestoreTransform(drawTarget); drawTarget->SetTransform( drawTarget->GetTransform().PreTranslate(ToPoint(devPixelOffset))); // solid white stroke: ColorPattern white(ToDeviceColor(Color(1.f, 1.f, 1.f, 1.f))); map->Draw(this, *drawTarget, white); // then dashed black stroke over the top: ColorPattern black(ToDeviceColor(Color(0.f, 0.f, 0.f, 1.f))); StrokeOptions strokeOptions; nsLayoutUtils::InitDashPattern(strokeOptions, NS_STYLE_BORDER_STYLE_DOTTED); map->Draw(this, *drawTarget, black, strokeOptions); } if (result == DrawResult::SUCCESS) { mPrevImage = aImage; } else if (result == DrawResult::BAD_IMAGE) { mPrevImage = nullptr; } return result; } void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { if (!IsVisibleForPainting(aBuilder)) return; DisplayBorderBackgroundOutline(aBuilder, aLists); 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) { nsCOMPtr imageLoader = do_QueryInterface(mContent); NS_ASSERTION(imageLoader, "Not an image loading content?"); nsCOMPtr currentRequest; if (imageLoader) { imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); } bool imageOK = ImageOk(mContent->AsElement()->State()); // 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. if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) { // No image yet, or image load failed. Draw the alt-text and an icon // indicating the status aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayAltFeedback(aBuilder, this)); // This image is visible (we are being asked to paint it) but it's not // decoded yet. And we are not going to ask the image to draw, so this // may be the only chance to tell it that it should decode. if (currentRequest) { uint32_t status = 0; currentRequest->GetImageStatus(&status); if (!(status & imgIRequest::STATUS_DECODE_COMPLETE)) { MaybeDecodeForPredictedSize(); } } } else { aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayImage(aBuilder, this, mImage, mPrevImage)); // If we were previously displaying an icon, we're not anymore if (mDisplayingIcon) { gIconLoad->RemoveIconObserver(this); mDisplayingIcon = false; } #ifdef DEBUG if (GetShowFrameBorders() && GetImageMap()) { aLists.Outlines()->AppendNewToTop(new (aBuilder) nsDisplayGeneric(aBuilder, this, PaintDebugImageMap, "DebugImageMap", nsDisplayItem::TYPE_DEBUG_IMAGE_MAP)); } #endif } } if (ShouldDisplaySelection()) { DisplaySelectionOverlay(aBuilder, aLists.Content(), nsISelectionDisplay::DISPLAY_IMAGES); } } bool nsImageFrame::ShouldDisplaySelection() { // XXX what on EARTH is this code for? nsresult result; nsPresContext* presContext = PresContext(); int16_t displaySelection = presContext->PresShell()->GetSelectionFlags(); if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) return false;//no need to check the blue border, we cannot be drawn selected //insert hook here for image selection drawing #if IMAGE_EDITOR_CHECK //check to see if this frame is in an editor context //isEditor check. this needs to be changed to have better way to check if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) { nsCOMPtr selCon; result = GetSelectionController(presContext, getter_AddRefs(selCon)); if (NS_SUCCEEDED(result) && selCon) { nsCOMPtr selection; result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); if (NS_SUCCEEDED(result) && selection) { int32_t rangeCount; selection->GetRangeCount(&rangeCount); if (rangeCount == 1) //if not one then let code drop to nsFrame::Paint { nsCOMPtr parentContent = mContent->GetParent(); if (parentContent) { int32_t thisOffset = parentContent->IndexOf(mContent); nsCOMPtr parentNode = do_QueryInterface(parentContent); nsCOMPtr rangeNode; uint32_t rangeOffset; nsCOMPtr range; selection->GetRangeAt(0,getter_AddRefs(range)); if (range) { range->GetStartContainer(getter_AddRefs(rangeNode)); range->GetStartOffset(&rangeOffset); if (parentNode && rangeNode && rangeNode == parentNode && static_cast(rangeOffset) == thisOffset) { range->GetEndContainer(getter_AddRefs(rangeNode)); range->GetEndOffset(&rangeOffset); // +1 since that would mean this whole content is selected only if (rangeNode == parentNode && static_cast(rangeOffset) == thisOffset + 1) { // Do not allow nsFrame do draw any further selection return false; } } } } } } } } #endif return true; } nsImageMap* nsImageFrame::GetImageMap() { if (!mImageMap) { nsIContent* map = GetMapElement(); if (map) { mImageMap = new nsImageMap(); mImageMap->Init(this, map); } } return mImageMap; } bool nsImageFrame::IsServerImageMap() { return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap); } // Translate an point that is relative to our frame // into a localized pixel coordinate that is relative to the // content area of this frame (inside the border+padding). void nsImageFrame::TranslateEventCoords(const nsPoint& aPoint, nsIntPoint& aResult) { nscoord x = aPoint.x; nscoord y = aPoint.y; // Subtract out border and padding here so that the coordinates are // now relative to the content area of this frame. nsRect inner = GetInnerArea(); x -= inner.x; y -= inner.y; aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x); aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y); } bool nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, nsIContent** aNode) { bool status = false; 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) { nsCOMPtr href = content->GetHrefURI(); if (href) { href->Clone(aHref); } status = (*aHref != nullptr); nsCOMPtr anchor(do_QueryInterface(content)); if (anchor) { anchor->GetTarget(aTarget); } NS_ADDREF(*aNode = content); break; } } return status; } nsresult nsImageFrame::GetContentForEvent(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() ? nsIPresShell::GetCapturingContent() : nullptr; if (capturingContent && capturingContent->GetPrimaryFrame() == this) { *aContent = capturingContent; NS_IF_ADDREF(*aContent); return NS_OK; } nsImageMap* map = GetImageMap(); if (nullptr != map) { nsIntPoint p; TranslateEventCoords( nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); nsCOMPtr area = map->GetArea(p.x, p.y); 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()->button == WidgetMouseEvent::eLeftButton) || aEvent->mMessage == eMouseMove) { nsImageMap* map = GetImageMap(); bool isServerMap = IsServerImageMap(); if ((nullptr != map) || isServerMap) { nsIntPoint p; TranslateEventCoords( nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); bool inside = false; // 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!) if (nullptr != map) { inside = !!map->GetArea(p.x, p.y); } 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, p.y); rv = uri->SetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); bool clicked = false; if (aEvent->mMessage == eMouseClick && !aEvent->DefaultPrevented()) { *aEventStatus = nsEventStatus_eConsumeDoDefault; clicked = true; } nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target, clicked, true, true); } } } } return nsAtomicContainerFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } nsresult nsImageFrame::GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) { nsImageMap* map = GetImageMap(); if (nullptr != map) { nsIntPoint p; TranslateEventCoords(aPoint, p); nsCOMPtr area = map->GetArea(p.x, p.y); if (area) { // Use the cursor from the style of the *area* element. // XXX Using the image as the parent style context isn't // technically correct, but it's probably the right thing to do // here, since it means that areas on which the cursor isn't // specified will inherit the style from the image. RefPtr areaStyle = PresContext()->PresShell()->StyleSet()-> ResolveStyleFor(area->AsElement(), StyleContext()); FillCursorInformationFromStyle(areaStyle->StyleUserInterface(), aCursor); if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; } return NS_OK; } } return nsFrame::GetCursor(aPoint, aCursor); } nsresult nsImageFrame::AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) { nsresult rv = nsAtomicContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); if (NS_FAILED(rv)) { return rv; } if (nsGkAtoms::alt == aAttribute) { PresContext()->PresShell()->FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); } return NS_OK; } void nsImageFrame::OnVisibilityChange(Visibility aNewVisibility, Maybe aNonvisibleAction) { nsCOMPtr imageLoader = do_QueryInterface(mContent); if (!imageLoader) { MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent"); nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); return; } imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction); if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) { MaybeDecodeForPredictedSize(); } nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction); } nsIAtom* nsImageFrame::GetType() const { return nsGkAtoms::imageFrame; } #ifdef DEBUG_FRAME_DUMP nsresult nsImageFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult); } void nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const { nsCString str; ListGeneric(str, aPrefix, aFlags); // output the img src url nsCOMPtr imageLoader = do_QueryInterface(mContent); if (imageLoader) { nsCOMPtr currentRequest; imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(currentRequest)); if (currentRequest) { nsCOMPtr uri; currentRequest->GetURI(getter_AddRefs(uri)); nsAutoCString uristr; uri->GetAsciiSpec(uristr); str += nsPrintfCString(" [src=%s]", uristr.get()); } } fprintf_stderr(out, "%s\n", str.get()); } #endif nsIFrame::LogicalSides nsImageFrame::GetLogicalSkipSides(const ReflowInput* aReflowInput) const { if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone)) { return LogicalSides(); } LogicalSides skip; if (nullptr != GetPrevInFlow()) { skip |= eLogicalSideBitsBStart; } if (nullptr != GetNextInFlow()) { skip |= eLogicalSideBitsBEnd; } return skip; } nsresult nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) { if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(), mIntrinsicSize.height.GetCoordValue()); return NS_OK; } return NS_ERROR_FAILURE; } nsresult nsImageFrame::LoadIcon(const nsAString& aSpec, nsPresContext *aPresContext, imgRequestProxy** aRequest) { nsresult rv = NS_OK; NS_PRECONDITION(!aSpec.IsEmpty(), "What happened??"); if (!sIOService) { rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr realURI; SpecToURI(aSpec, sIOService, getter_AddRefs(realURI)); RefPtr il = nsContentUtils::GetImgLoaderForDocument(aPresContext->Document()); nsCOMPtr loadGroup; GetLoadGroup(aPresContext, getter_AddRefs(loadGroup)); // For icon loads, we don't need to merge with the loadgroup flags nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; nsContentPolicyType contentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE; return 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) */ mozilla::net::RP_Default, nullptr, /* principal (not relevant for icons) */ loadGroup, gIconLoad, nullptr, /* No context */ nullptr, /* Not associated with any particular document */ loadFlags, nullptr, contentPolicyType, EmptyString(), aRequest); } void nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const { if (mContent) { NS_ASSERTION(mContent->GetComposedDoc(), "Frame still alive after content removed from document!"); aCharset = mContent->GetComposedDoc()->GetDocumentCharacterSet(); } } void nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService *aIOService, nsIURI **aURI) { nsCOMPtr baseURI; if (mContent) { baseURI = mContent->GetBaseURI(); } nsAutoCString charset; GetDocumentCharacterSet(charset); NS_NewURI(aURI, aSpec, charset.IsEmpty() ? nullptr : charset.get(), baseURI, aIOService); } void nsImageFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup) { if (!aPresContext) return; NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer"); nsIPresShell *shell = aPresContext->GetPresShell(); if (!shell) return; nsIDocument *doc = shell->GetDocument(); if (!doc) return; *aLoadGroup = doc->GetDocumentLoadGroup().take(); } nsresult nsImageFrame::LoadIcons(nsPresContext *aPresContext) { NS_ASSERTION(!gIconLoad, "called LoadIcons twice"); NS_NAMED_LITERAL_STRING(loadingSrc,"resource://gre-resources/loading-image.png"); NS_NAMED_LITERAL_STRING(brokenSrc,"resource://gre-resources/broken-image.png"); gIconLoad = new IconLoad(); NS_ADDREF(gIconLoad); nsresult rv; // create a loader and load the images rv = LoadIcon(loadingSrc, aPresContext, getter_AddRefs(gIconLoad->mLoadingImage)); if (NS_FAILED(rv)) { return rv; } rv = LoadIcon(brokenSrc, aPresContext, getter_AddRefs(gIconLoad->mBrokenImage)); if (NS_FAILED(rv)) { return rv; } return rv; } NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver, imgINotificationObserver) static const char* kIconLoadPrefs[] = { "browser.display.force_inline_alttext", "browser.display.show_image_placeholders", "browser.display.show_loading_image_placeholder", nullptr }; nsImageFrame::IconLoad::IconLoad() { // register observers Preferences::AddStrongObservers(this, kIconLoadPrefs); GetPrefs(); } void nsImageFrame::IconLoad::Shutdown() { Preferences::RemoveObservers(this, kIconLoadPrefs); // in case the pref service releases us later if (mLoadingImage) { mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE); mLoadingImage = nullptr; } if (mBrokenImage) { mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE); mBrokenImage = nullptr; } } NS_IMETHODIMP nsImageFrame::IconLoad::Observe(nsISupports *aSubject, const char* aTopic, const char16_t* aData) { NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), "wrong topic"); #ifdef DEBUG // assert |aData| is one of our prefs. uint32_t i = 0; for (; i < ArrayLength(kIconLoadPrefs); ++i) { if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData)) break; } MOZ_ASSERT(i < ArrayLength(kIconLoadPrefs)); #endif GetPrefs(); return NS_OK; } void nsImageFrame::IconLoad::GetPrefs() { mPrefForceInlineAltText = Preferences::GetBool("browser.display.force_inline_alttext"); mPrefShowPlaceholders = Preferences::GetBool("browser.display.show_image_placeholders", true); mPrefShowLoadingPlaceholder = Preferences::GetBool("browser.display.show_loading_image_placeholder", true); } NS_IMETHODIMP nsImageFrame::IconLoad::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData) { MOZ_ASSERT(aRequest); if (aType != imgINotificationObserver::LOAD_COMPLETE && aType != imgINotificationObserver::FRAME_UPDATE) { return NS_OK; } if (aType == imgINotificationObserver::LOAD_COMPLETE) { nsCOMPtr image; aRequest->GetImage(getter_AddRefs(image)); if (!image) { return NS_ERROR_FAILURE; } // 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); } nsTObserverArray::ForwardIterator iter(mIconObservers); nsImageFrame *frame; while (iter.HasMore()) { frame = iter.GetNext(); frame->InvalidateFrame(); } return NS_OK; } NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver) nsImageListener::nsImageListener(nsImageFrame *aFrame) : mFrame(aFrame) { } nsImageListener::~nsImageListener() { } NS_IMETHODIMP nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) { if (!mFrame) return NS_ERROR_FAILURE; 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->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) { // Assume direct parent is a table cell frame. nsFrame *grandAncestor = static_cast(ancestor->GetParent()); return grandAncestor && grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto; } return false; } /* virtual */ void nsImageFrame::AddInlineMinISize(nsRenderingContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData) { nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, this, nsLayoutUtils::MIN_ISIZE); bool canBreak = !IsInAutoWidthTableCellForQuirk(this); aData->DefaultAddInlineMinISize(this, isize, canBreak); }