michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* rendering object for replaced elements with bitmap image data */ michael@0: michael@0: #include "nsImageFrame.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIImageLoadingContent.h" michael@0: #include "nsString.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsStyleCoord.h" michael@0: #include "nsTransform2D.h" michael@0: #include "nsImageMap.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsISupportsPriority.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsIDOMHTMLAnchorElement.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #endif michael@0: #include "nsIDOMNode.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: michael@0: #include "imgIContainer.h" michael@0: #include "imgLoader.h" michael@0: #include "imgRequestProxy.h" michael@0: michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsIDOMRange.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "nsBidiUtils.h" michael@0: #include "nsBidiPresUtils.h" michael@0: #include "mozIThirdPartyUtil.h" michael@0: michael@0: #include "gfxRect.h" michael@0: #include "ImageLayers.h" michael@0: #include "ImageContainer.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsStyleStructInlines.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #include "mozilla/dom/Link.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // sizes (pixels) for image icon, padding and border frame michael@0: #define ICON_SIZE (16) michael@0: #define ICON_PADDING (3) michael@0: #define ALT_BORDER_WIDTH (1) michael@0: michael@0: michael@0: //we must add hooks soon michael@0: #define IMAGE_EDITOR_CHECK 1 michael@0: michael@0: // Default alignment value (so we can tell an unset value from a set value) michael@0: #define ALIGN_UNSET uint8_t(-1) michael@0: michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::dom; michael@0: michael@0: // static icon information michael@0: nsImageFrame::IconLoad* nsImageFrame::gIconLoad = nullptr; michael@0: michael@0: // cached IO service for loading icons michael@0: nsIIOService* nsImageFrame::sIOService; michael@0: michael@0: // test if the width and height are fixed, looking at the style data michael@0: static bool HaveFixedSize(const nsStylePosition* aStylePosition) michael@0: { michael@0: // check the width and height values in the reflow state's style struct michael@0: // - if width and height are specified as either coord or percentage, then michael@0: // the size of the image frame is constrained michael@0: return aStylePosition->mWidth.IsCoordPercentCalcUnit() && michael@0: aStylePosition->mHeight.IsCoordPercentCalcUnit(); michael@0: } michael@0: // use the data in the reflow state to decide if the image has a constrained size michael@0: // (i.e. width and height that are based on the containing block size and not the image size) michael@0: // so we can avoid animated GIF related reflows michael@0: inline bool HaveFixedSize(const nsHTMLReflowState& aReflowState) michael@0: { michael@0: NS_ASSERTION(aReflowState.mStylePosition, "crappy reflowState - null stylePosition"); michael@0: // when an image has percent css style height or width, but ComputedHeight() michael@0: // or ComputedWidth() of reflow state is NS_UNCONSTRAINEDSIZE michael@0: // it needs to return false to cause an incremental reflow later michael@0: // if an image is inside table like bug 156731 simple testcase III, michael@0: // during pass 1 reflow, ComputedWidth() is NS_UNCONSTRAINEDSIZE michael@0: // in pass 2 reflow, ComputedWidth() is 0, it also needs to return false michael@0: // see bug 156731 michael@0: const nsStyleCoord &height = aReflowState.mStylePosition->mHeight; michael@0: const nsStyleCoord &width = aReflowState.mStylePosition->mWidth; michael@0: return ((height.HasPercent() && michael@0: NS_UNCONSTRAINEDSIZE == aReflowState.ComputedHeight()) || michael@0: (width.HasPercent() && michael@0: (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedWidth() || michael@0: 0 == aReflowState.ComputedWidth()))) michael@0: ? false michael@0: : HaveFixedSize(aReflowState.mStylePosition); michael@0: } michael@0: michael@0: nsIFrame* michael@0: NS_NewImageFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsImageFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsImageFrame) michael@0: michael@0: michael@0: nsImageFrame::nsImageFrame(nsStyleContext* aContext) : michael@0: ImageFrameSuper(aContext), michael@0: mComputedSize(0, 0), michael@0: mIntrinsicRatio(0, 0), michael@0: mDisplayingIcon(false), michael@0: mFirstFrameComplete(false), michael@0: mReflowCallbackPosted(false) michael@0: { michael@0: // We assume our size is not constrained and we haven't gotten an michael@0: // initial reflow yet, so don't touch those flags. michael@0: mIntrinsicSize.width.SetCoordValue(0); michael@0: mIntrinsicSize.height.SetCoordValue(0); michael@0: } michael@0: michael@0: nsImageFrame::~nsImageFrame() michael@0: { michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsImageFrame) michael@0: NS_QUERYFRAME_ENTRY(nsImageFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(ImageFrameSuper) michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsImageFrame::AccessibleType() michael@0: { michael@0: // Don't use GetImageMap() to avoid reentrancy into accessibility. michael@0: if (HasImageMap()) { michael@0: return a11y::eHTMLImageMapType; michael@0: } michael@0: michael@0: return a11y::eImageType; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsImageFrame::DisconnectMap() michael@0: { michael@0: if (mImageMap) { michael@0: mImageMap->Destroy(); michael@0: NS_RELEASE(mImageMap); michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: nsAccessibilityService* accService = GetAccService(); michael@0: if (accService) { michael@0: accService->RecreateAccessible(PresContext()->PresShell(), mContent); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: if (mReflowCallbackPosted) { michael@0: PresContext()->PresShell()->CancelReflowCallback(this); michael@0: mReflowCallbackPosted = false; michael@0: } michael@0: michael@0: // Tell our image map, if there is one, to clean up michael@0: // This causes the nsImageMap to unregister itself as michael@0: // a DOM listener. michael@0: DisconnectMap(); michael@0: michael@0: // set the frame to null so we don't send messages to a dead object. michael@0: if (mListener) { michael@0: nsCOMPtr imageLoader = do_QueryInterface(mContent); michael@0: if (imageLoader) { michael@0: // Notify our image loading content that we are going away so it can michael@0: // deregister with our refresh driver. michael@0: imageLoader->FrameDestroyed(this); michael@0: michael@0: imageLoader->RemoveObserver(mListener); michael@0: } michael@0: michael@0: reinterpret_cast(mListener.get())->SetFrame(nullptr); michael@0: } michael@0: michael@0: mListener = nullptr; michael@0: michael@0: // If we were displaying an icon, take ourselves off the list michael@0: if (mDisplayingIcon) michael@0: gIconLoad->RemoveIconObserver(this); michael@0: michael@0: nsSplittableFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsImageFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsSplittableFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: mListener = new nsImageListener(this); michael@0: michael@0: nsCOMPtr imageLoader = do_QueryInterface(aContent); michael@0: if (!imageLoader) { michael@0: NS_RUNTIMEABORT("Why do we have an nsImageFrame here at all?"); michael@0: } michael@0: michael@0: imageLoader->AddObserver(mListener); michael@0: michael@0: nsPresContext *aPresContext = PresContext(); michael@0: michael@0: if (!gIconLoad) michael@0: LoadIcons(aPresContext); michael@0: michael@0: // We have a PresContext now, so we need to notify the image content node michael@0: // that it can register images. michael@0: imageLoader->FrameCreated(this); michael@0: michael@0: // Give image loads associated with an image frame a small priority boost! michael@0: nsCOMPtr currentRequest; michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(currentRequest)); michael@0: nsCOMPtr p = do_QueryInterface(currentRequest); michael@0: if (p) michael@0: p->AdjustPriority(-1); michael@0: michael@0: // If we already have an image container, OnStartContainer won't be called michael@0: if (currentRequest) { michael@0: nsCOMPtr image; michael@0: currentRequest->GetImage(getter_AddRefs(image)); michael@0: OnStartContainer(currentRequest, image); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::UpdateIntrinsicSize(imgIContainer* aImage) michael@0: { michael@0: NS_PRECONDITION(aImage, "null image"); michael@0: if (!aImage) michael@0: return false; michael@0: michael@0: IntrinsicSize oldIntrinsicSize = mIntrinsicSize; michael@0: mIntrinsicSize = IntrinsicSize(); michael@0: michael@0: // Set intrinsic size to match aImage's reported intrinsic width & height. michael@0: nsSize intrinsicSize; michael@0: if (NS_SUCCEEDED(aImage->GetIntrinsicSize(&intrinsicSize))) { michael@0: // If the image has no intrinsic width, intrinsicSize.width will be -1, and michael@0: // we can leave mIntrinsicSize.width at its default value of eStyleUnit_None. michael@0: // Otherwise we use intrinsicSize.width. Height works the same way. michael@0: if (intrinsicSize.width != -1) michael@0: mIntrinsicSize.width.SetCoordValue(intrinsicSize.width); michael@0: if (intrinsicSize.height != -1) michael@0: mIntrinsicSize.height.SetCoordValue(intrinsicSize.height); michael@0: } else { michael@0: // Failure means that the image hasn't loaded enough to report a result. We michael@0: // treat this case as if the image's intrinsic size was 0x0. michael@0: mIntrinsicSize.width.SetCoordValue(0); michael@0: mIntrinsicSize.height.SetCoordValue(0); michael@0: } michael@0: michael@0: return mIntrinsicSize != oldIntrinsicSize; michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::UpdateIntrinsicRatio(imgIContainer* aImage) michael@0: { michael@0: NS_PRECONDITION(aImage, "null image"); michael@0: michael@0: if (!aImage) michael@0: return false; michael@0: michael@0: nsSize oldIntrinsicRatio = mIntrinsicRatio; michael@0: michael@0: // Set intrinsic ratio to match aImage's reported intrinsic ratio. michael@0: if (NS_FAILED(aImage->GetIntrinsicRatio(&mIntrinsicRatio))) michael@0: mIntrinsicRatio.SizeTo(0, 0); michael@0: michael@0: return mIntrinsicRatio != oldIntrinsicRatio; michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::GetSourceToDestTransform(nsTransform2D& aTransform) michael@0: { michael@0: // Set the translation components. michael@0: // XXXbz does this introduce rounding errors because of the cast to michael@0: // float? Should we just manually add that stuff in every time michael@0: // instead? michael@0: nsRect innerArea = GetInnerArea(); michael@0: aTransform.SetToTranslate(float(innerArea.x), michael@0: float(innerArea.y - GetContinuationOffset())); michael@0: michael@0: // Set the scale factors. michael@0: if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && michael@0: mIntrinsicSize.width.GetCoordValue() != 0 && michael@0: mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && michael@0: mIntrinsicSize.height.GetCoordValue() != 0 && michael@0: mIntrinsicSize.width.GetCoordValue() != mComputedSize.width && michael@0: mIntrinsicSize.height.GetCoordValue() != mComputedSize.height) { michael@0: michael@0: aTransform.SetScale(float(mComputedSize.width) / michael@0: float(mIntrinsicSize.width.GetCoordValue()), michael@0: float(mComputedSize.height) / michael@0: float(mIntrinsicSize.height.GetCoordValue())); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * These two functions basically do the same check. The first one michael@0: * checks that the given request is the current request for our michael@0: * mContent. The second checks that the given image container the michael@0: * same as the image container on the current request for our michael@0: * mContent. michael@0: */ michael@0: bool michael@0: nsImageFrame::IsPendingLoad(imgIRequest* aRequest) const michael@0: { michael@0: // Default to pending load in case of errors michael@0: nsCOMPtr imageLoader(do_QueryInterface(mContent)); michael@0: NS_ASSERTION(imageLoader, "No image loading content?"); michael@0: michael@0: int32_t requestType = nsIImageLoadingContent::UNKNOWN_REQUEST; michael@0: imageLoader->GetRequestType(aRequest, &requestType); michael@0: michael@0: return requestType != nsIImageLoadingContent::CURRENT_REQUEST; michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::IsPendingLoad(imgIContainer* aContainer) const michael@0: { michael@0: // default to pending load in case of errors michael@0: if (!aContainer) { michael@0: NS_ERROR("No image container!"); michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr imageLoader(do_QueryInterface(mContent)); michael@0: NS_ASSERTION(imageLoader, "No image loading content?"); michael@0: michael@0: nsCOMPtr currentRequest; michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(currentRequest)); michael@0: if (!currentRequest) { michael@0: NS_ERROR("No current request"); michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr currentContainer; michael@0: currentRequest->GetImage(getter_AddRefs(currentContainer)); michael@0: michael@0: return currentContainer != aContainer; michael@0: michael@0: } michael@0: michael@0: nsRect michael@0: nsImageFrame::SourceRectToDest(const nsIntRect& aRect) michael@0: { michael@0: // When scaling the image, row N of the source image may (depending on michael@0: // the scaling function) be used to draw any row in the destination image michael@0: // between floor(F * (N-1)) and ceil(F * (N+1)), where F is the michael@0: // floating-point scaling factor. The same holds true for columns. michael@0: // So, we start by computing that bound without the floor and ceiling. michael@0: michael@0: nsRect r(nsPresContext::CSSPixelsToAppUnits(aRect.x - 1), michael@0: nsPresContext::CSSPixelsToAppUnits(aRect.y - 1), michael@0: nsPresContext::CSSPixelsToAppUnits(aRect.width + 2), michael@0: nsPresContext::CSSPixelsToAppUnits(aRect.height + 2)); michael@0: michael@0: nsTransform2D sourceToDest; michael@0: if (!GetSourceToDestTransform(sourceToDest)) { michael@0: // Failed to generate transform matrix. Return our whole inner area, michael@0: // to be on the safe side (since this method is used for generating michael@0: // invalidation rects). michael@0: return GetInnerArea(); michael@0: } michael@0: michael@0: sourceToDest.TransformCoord(&r.x, &r.y, &r.width, &r.height); michael@0: michael@0: // Now, round the edges out to the pixel boundary. michael@0: nscoord scale = nsPresContext::CSSPixelsToAppUnits(1); michael@0: nscoord right = r.x + r.width; michael@0: nscoord bottom = r.y + r.height; michael@0: michael@0: r.x -= (scale + (r.x % scale)) % scale; michael@0: r.y -= (scale + (r.y % scale)) % scale; michael@0: r.width = right + ((scale - (right % scale)) % scale) - r.x; michael@0: r.height = bottom + ((scale - (bottom % scale)) % scale) - r.y; michael@0: michael@0: return r; michael@0: } michael@0: michael@0: // Note that we treat NS_EVENT_STATE_SUPPRESSED images as "OK". This means michael@0: // that we'll construct image frames for them as needed if their display is michael@0: // toggled from "none" (though we won't paint them, unless their visibility michael@0: // is changed too). michael@0: #define BAD_STATES (NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED | \ michael@0: NS_EVENT_STATE_LOADING) michael@0: michael@0: // This is a macro so that we don't evaluate the boolean last arg michael@0: // unless we have to; it can be expensive michael@0: #define IMAGE_OK(_state, _loadingOK) \ michael@0: (!(_state).HasAtLeastOneOfStates(BAD_STATES) || \ michael@0: (!(_state).HasAtLeastOneOfStates(NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_USERDISABLED) && \ michael@0: (_state).HasState(NS_EVENT_STATE_LOADING) && (_loadingOK))) michael@0: michael@0: /* static */ michael@0: bool michael@0: nsImageFrame::ShouldCreateImageFrameFor(Element* aElement, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: EventStates state = aElement->State(); michael@0: if (IMAGE_OK(state, michael@0: HaveFixedSize(aStyleContext->StylePosition()))) { michael@0: // Image is fine; do the image frame thing michael@0: return true; michael@0: } michael@0: michael@0: // Check if we want to use a placeholder box with an icon or just michael@0: // let the presShell make us into inline text. Decide as follows: michael@0: // michael@0: // - if our special "force icons" style is set, show an icon michael@0: // - else if our "do not show placeholders" pref is set, skip the icon michael@0: // - else: michael@0: // - if there is a src attribute, there is no alt attribute, michael@0: // and this is not an (which could not possibly have michael@0: // such an attribute), show an icon. michael@0: // - if QuirksMode, and the IMG has a size show an icon. michael@0: // - otherwise, skip the icon michael@0: bool useSizedBox; michael@0: michael@0: if (aStyleContext->StyleUIReset()->mForceBrokenImageIcon) { michael@0: useSizedBox = true; michael@0: } michael@0: else if (gIconLoad && gIconLoad->mPrefForceInlineAltText) { michael@0: useSizedBox = false; michael@0: } michael@0: else if (aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::src) && michael@0: !aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::alt) && michael@0: !aElement->IsHTML(nsGkAtoms::object) && michael@0: !aElement->IsHTML(nsGkAtoms::input)) { michael@0: // Use a sized box if we have no alt text. This means no alt attribute michael@0: // and the node is not an object or an input (since those always have alt michael@0: // text). michael@0: useSizedBox = true; michael@0: } michael@0: else if (aStyleContext->PresContext()->CompatibilityMode() != michael@0: eCompatibility_NavQuirks) { michael@0: useSizedBox = false; michael@0: } michael@0: else { michael@0: // check whether we have fixed size michael@0: useSizedBox = HaveFixedSize(aStyleContext->StylePosition()); michael@0: } michael@0: michael@0: return useSizedBox; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (aType == imgINotificationObserver::SIZE_AVAILABLE) { michael@0: nsCOMPtr image; michael@0: aRequest->GetImage(getter_AddRefs(image)); michael@0: return OnStartContainer(aRequest, image); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::FRAME_UPDATE) { michael@0: return OnDataAvailable(aRequest, aData); michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::FRAME_COMPLETE) { michael@0: mFirstFrameComplete = true; michael@0: } michael@0: michael@0: if (aType == imgINotificationObserver::LOAD_COMPLETE) { michael@0: uint32_t imgStatus; michael@0: aRequest->GetImageStatus(&imgStatus); michael@0: nsresult status = michael@0: imgStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK; michael@0: return OnStopRequest(aRequest, status); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: SizeIsAvailable(imgIRequest* aRequest) michael@0: { michael@0: if (!aRequest) michael@0: return false; michael@0: michael@0: uint32_t imageStatus = 0; michael@0: nsresult rv = aRequest->GetImageStatus(&imageStatus); michael@0: michael@0: return NS_SUCCEEDED(rv) && (imageStatus & imgIRequest::STATUS_SIZE_AVAILABLE); michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::OnStartContainer(imgIRequest *aRequest, imgIContainer *aImage) michael@0: { michael@0: if (!aImage) return NS_ERROR_INVALID_ARG; michael@0: michael@0: /* Get requested animation policy from the pres context: michael@0: * normal = 0 michael@0: * one frame = 1 michael@0: * one loop = 2 michael@0: */ michael@0: nsPresContext *presContext = PresContext(); michael@0: aImage->SetAnimationMode(presContext->ImageAnimationMode()); michael@0: michael@0: if (IsPendingLoad(aRequest)) { michael@0: // We don't care michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool intrinsicSizeChanged = false; michael@0: if (SizeIsAvailable(aRequest)) { michael@0: // This is valid and for the current request, so update our stored image michael@0: // container, orienting according to our style. michael@0: mImage = nsLayoutUtils::OrientImage(aImage, StyleVisibility()->mImageOrientation); michael@0: michael@0: intrinsicSizeChanged = UpdateIntrinsicSize(mImage); michael@0: intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged; michael@0: } else { michael@0: // We no longer have a valid image, so release our stored image container. michael@0: mImage = nullptr; michael@0: michael@0: // Have to size to 0,0 so that GetDesiredSize recalculates the size. michael@0: mIntrinsicSize.width.SetCoordValue(0); michael@0: mIntrinsicSize.height.SetCoordValue(0); michael@0: mIntrinsicRatio.SizeTo(0, 0); michael@0: intrinsicSizeChanged = true; michael@0: } michael@0: michael@0: if (intrinsicSizeChanged && (mState & IMAGE_GOTINITIALREFLOW)) { michael@0: // Now we need to reflow if we have an unconstrained size and have michael@0: // already gotten the initial reflow michael@0: if (!(mState & IMAGE_SIZECONSTRAINED)) { michael@0: nsIPresShell *presShell = presContext->GetPresShell(); michael@0: NS_ASSERTION(presShell, "No PresShell."); michael@0: if (presShell) { michael@0: presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::OnDataAvailable(imgIRequest *aRequest, michael@0: const nsIntRect *aRect) michael@0: { michael@0: if (mFirstFrameComplete) { michael@0: nsCOMPtr container; michael@0: aRequest->GetImage(getter_AddRefs(container)); michael@0: return FrameChanged(aRequest, container); michael@0: } michael@0: michael@0: // XXX do we need to make sure that the reflow from the michael@0: // OnStartContainer has been processed before we start calling michael@0: // invalidate? michael@0: michael@0: NS_ENSURE_ARG_POINTER(aRect); michael@0: michael@0: if (!(mState & IMAGE_GOTINITIALREFLOW)) { michael@0: // Don't bother to do anything; we have a reflow coming up! michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (IsPendingLoad(aRequest)) { michael@0: // We don't care michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef DEBUG_decode michael@0: printf("Source rect (%d,%d,%d,%d)\n", michael@0: aRect->x, aRect->y, aRect->width, aRect->height); michael@0: #endif michael@0: michael@0: if (aRect->IsEqualInterior(nsIntRect::GetMaxSizedIntRect())) { michael@0: InvalidateFrame(nsDisplayItem::TYPE_IMAGE); michael@0: InvalidateFrame(nsDisplayItem::TYPE_ALT_FEEDBACK); michael@0: } else { michael@0: nsRect invalid = SourceRectToDest(*aRect); michael@0: InvalidateFrameWithRect(invalid, nsDisplayItem::TYPE_IMAGE); michael@0: InvalidateFrameWithRect(invalid, nsDisplayItem::TYPE_ALT_FEEDBACK); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::OnStopRequest(imgIRequest *aRequest, michael@0: nsresult aStatus) michael@0: { michael@0: // Check what request type we're dealing with michael@0: nsCOMPtr imageLoader = do_QueryInterface(mContent); michael@0: NS_ASSERTION(imageLoader, "Who's notifying us??"); michael@0: int32_t loadType = nsIImageLoadingContent::UNKNOWN_REQUEST; michael@0: imageLoader->GetRequestType(aRequest, &loadType); michael@0: if (loadType != nsIImageLoadingContent::CURRENT_REQUEST && michael@0: loadType != nsIImageLoadingContent::PENDING_REQUEST) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NotifyNewCurrentRequest(aRequest, aStatus); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::NotifyNewCurrentRequest(imgIRequest *aRequest, michael@0: nsresult aStatus) michael@0: { michael@0: nsCOMPtr image; michael@0: aRequest->GetImage(getter_AddRefs(image)); michael@0: NS_ASSERTION(image || NS_FAILED(aStatus), "Successful load with no container?"); michael@0: michael@0: // May have to switch sizes here! michael@0: bool intrinsicSizeChanged = true; michael@0: if (NS_SUCCEEDED(aStatus) && image && SizeIsAvailable(aRequest)) { michael@0: // Update our stored image container, orienting according to our style. michael@0: mImage = nsLayoutUtils::OrientImage(image, StyleVisibility()->mImageOrientation); michael@0: michael@0: intrinsicSizeChanged = UpdateIntrinsicSize(mImage); michael@0: intrinsicSizeChanged = UpdateIntrinsicRatio(mImage) || intrinsicSizeChanged; michael@0: } else { michael@0: // We no longer have a valid image, so release our stored image container. michael@0: mImage = nullptr; michael@0: michael@0: // Have to size to 0,0 so that GetDesiredSize recalculates the size michael@0: mIntrinsicSize.width.SetCoordValue(0); michael@0: mIntrinsicSize.height.SetCoordValue(0); michael@0: mIntrinsicRatio.SizeTo(0, 0); michael@0: } michael@0: michael@0: if (mState & IMAGE_GOTINITIALREFLOW) { // do nothing if we haven't gotten the initial reflow yet michael@0: if (!(mState & IMAGE_SIZECONSTRAINED) && intrinsicSizeChanged) { michael@0: nsIPresShell *presShell = PresContext()->GetPresShell(); michael@0: if (presShell) { michael@0: presShell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: // Update border+content to account for image change michael@0: InvalidateFrame(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::FrameChanged(imgIRequest *aRequest, michael@0: imgIContainer *aContainer) michael@0: { michael@0: if (!StyleVisibility()->IsVisible()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (IsPendingLoad(aContainer)) { michael@0: // We don't care about it michael@0: return NS_OK; michael@0: } michael@0: michael@0: InvalidateLayer(nsDisplayItem::TYPE_IMAGE); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::EnsureIntrinsicSizeAndRatio(nsPresContext* aPresContext) michael@0: { michael@0: // If mIntrinsicSize.width and height are 0, then we need to update from the michael@0: // image container. michael@0: if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && michael@0: mIntrinsicSize.width.GetCoordValue() == 0 && michael@0: mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord && michael@0: mIntrinsicSize.height.GetCoordValue() == 0) { michael@0: michael@0: if (mImage) { michael@0: UpdateIntrinsicSize(mImage); michael@0: UpdateIntrinsicRatio(mImage); michael@0: } else { michael@0: // image request is null or image size not known, probably an michael@0: // invalid image specified michael@0: // - make the image big enough for the icon (it may not be michael@0: // used if inline alt expansion is used instead) michael@0: if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { michael@0: nscoord edgeLengthToUse = michael@0: nsPresContext::CSSPixelsToAppUnits( michael@0: ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); michael@0: mIntrinsicSize.width.SetCoordValue(edgeLengthToUse); michael@0: mIntrinsicSize.height.SetCoordValue(edgeLengthToUse); michael@0: mIntrinsicRatio.SizeTo(1, 1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsImageFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: nsPresContext *presContext = PresContext(); michael@0: EnsureIntrinsicSizeAndRatio(presContext); michael@0: michael@0: return nsLayoutUtils::ComputeSizeWithIntrinsicDimensions( michael@0: aRenderingContext, this, michael@0: mIntrinsicSize, mIntrinsicRatio, aCBSize, michael@0: aMargin, aBorder, aPadding); michael@0: } michael@0: michael@0: nsRect michael@0: nsImageFrame::GetInnerArea() const michael@0: { michael@0: return GetContentRect() - GetPosition(); michael@0: } michael@0: michael@0: // get the offset into the content area of the image where aImg starts if it is a continuation. michael@0: nscoord michael@0: nsImageFrame::GetContinuationOffset() const michael@0: { michael@0: nscoord offset = 0; michael@0: for (nsIFrame *f = GetPrevInFlow(); f; f = f->GetPrevInFlow()) { michael@0: offset += f->GetContentRect().height; michael@0: } michael@0: NS_ASSERTION(offset >= 0, "bogus GetContentRect"); michael@0: return offset; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsImageFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: // XXX The caller doesn't account for constraints of the height, michael@0: // min-height, and max-height properties. michael@0: DebugOnly result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: nsPresContext *presContext = PresContext(); michael@0: EnsureIntrinsicSizeAndRatio(presContext); michael@0: return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ? michael@0: mIntrinsicSize.width.GetCoordValue() : 0; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsImageFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: // XXX The caller doesn't account for constraints of the height, michael@0: // min-height, and max-height properties. michael@0: DebugOnly result; michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: nsPresContext *presContext = PresContext(); michael@0: EnsureIntrinsicSizeAndRatio(presContext); michael@0: // convert from normal twips to scaled twips (printing...) michael@0: return mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord ? michael@0: mIntrinsicSize.width.GetCoordValue() : 0; michael@0: } michael@0: michael@0: /* virtual */ IntrinsicSize michael@0: nsImageFrame::GetIntrinsicSize() michael@0: { michael@0: return mIntrinsicSize; michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsImageFrame::GetIntrinsicRatio() michael@0: { michael@0: return mIntrinsicRatio; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsImageFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("enter nsImageFrame::Reflow: availSize=%d,%d", michael@0: aReflowState.AvailableWidth(), aReflowState.AvailableHeight())); michael@0: michael@0: NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: // see if we have a frozen size (i.e. a fixed width and height) michael@0: if (HaveFixedSize(aReflowState)) { michael@0: mState |= IMAGE_SIZECONSTRAINED; michael@0: } else { michael@0: mState &= ~IMAGE_SIZECONSTRAINED; michael@0: } michael@0: michael@0: // XXXldb These two bits are almost exact opposites (except in the michael@0: // middle of the initial reflow); remove IMAGE_GOTINITIALREFLOW. michael@0: if (GetStateBits() & NS_FRAME_FIRST_REFLOW) { michael@0: mState |= IMAGE_GOTINITIALREFLOW; michael@0: } michael@0: michael@0: mComputedSize = michael@0: nsSize(aReflowState.ComputedWidth(), aReflowState.ComputedHeight()); michael@0: michael@0: aMetrics.Width() = mComputedSize.width; michael@0: aMetrics.Height() = mComputedSize.height; michael@0: michael@0: // add borders and padding michael@0: aMetrics.Width() += aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: aMetrics.Height() += aReflowState.ComputedPhysicalBorderPadding().TopBottom(); michael@0: michael@0: if (GetPrevInFlow()) { michael@0: aMetrics.Width() = GetPrevInFlow()->GetSize().width; michael@0: nscoord y = GetContinuationOffset(); michael@0: aMetrics.Height() -= y + aReflowState.ComputedPhysicalBorderPadding().top; michael@0: aMetrics.Height() = std::max(0, aMetrics.Height()); michael@0: } michael@0: michael@0: michael@0: // we have to split images if we are: michael@0: // in Paginated mode, we need to have a constrained height, and have a height larger than our available height michael@0: uint32_t loadStatus = imgIRequest::STATUS_NONE; michael@0: nsCOMPtr imageLoader = do_QueryInterface(mContent); michael@0: NS_ASSERTION(imageLoader, "No content node??"); michael@0: if (imageLoader) { michael@0: nsCOMPtr currentRequest; michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(currentRequest)); michael@0: if (currentRequest) { michael@0: currentRequest->GetImageStatus(&loadStatus); michael@0: } michael@0: } michael@0: if (aPresContext->IsPaginated() && michael@0: ((loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) || (mState & IMAGE_SIZECONSTRAINED)) && michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight() && michael@0: aMetrics.Height() > aReflowState.AvailableHeight()) { michael@0: // our desired height was greater than 0, so to avoid infinite michael@0: // splitting, use 1 pixel as the min michael@0: aMetrics.Height() = std::max(nsPresContext::CSSPixelsToAppUnits(1), aReflowState.AvailableHeight()); michael@0: aStatus = NS_FRAME_NOT_COMPLETE; michael@0: } michael@0: michael@0: aMetrics.SetOverflowAreasToDesiredBounds(); michael@0: EventStates contentState = mContent->AsElement()->State(); michael@0: bool imageOK = IMAGE_OK(contentState, true); michael@0: michael@0: // Determine if the size is available michael@0: bool haveSize = false; michael@0: if (loadStatus & imgIRequest::STATUS_SIZE_AVAILABLE) { michael@0: haveSize = true; michael@0: } michael@0: michael@0: if (!imageOK || !haveSize) { michael@0: nsRect altFeedbackSize(0, 0, michael@0: nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH)), michael@0: nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+2*(ICON_PADDING+ALT_BORDER_WIDTH))); michael@0: // We include the altFeedbackSize in our visual overflow, but not in our michael@0: // scrollable overflow, since it doesn't really need to be scrolled to michael@0: // outside the image. michael@0: static_assert(eOverflowType_LENGTH == 2, "Unknown overflow types?"); michael@0: nsRect& visualOverflow = aMetrics.VisualOverflow(); michael@0: visualOverflow.UnionRect(visualOverflow, altFeedbackSize); michael@0: } michael@0: FinishAndStoreOverflow(&aMetrics); michael@0: michael@0: if ((GetStateBits() & NS_FRAME_FIRST_REFLOW) && !mReflowCallbackPosted) { michael@0: nsIPresShell* shell = PresContext()->PresShell(); michael@0: mReflowCallbackPosted = true; michael@0: shell->PostReflowCallback(this); michael@0: } michael@0: michael@0: NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, michael@0: ("exit nsImageFrame::Reflow: size=%d,%d", michael@0: aMetrics.Width(), aMetrics.Height())); michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::ReflowFinished() michael@0: { michael@0: mReflowCallbackPosted = false; michael@0: michael@0: nsLayoutUtils::UpdateImageVisibilityForFrame(this); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::ReflowCallbackCanceled() michael@0: { michael@0: mReflowCallbackPosted = false; michael@0: } michael@0: michael@0: // Computes the width of the specified string. aMaxWidth specifies the maximum michael@0: // width available. Once this limit is reached no more characters are measured. michael@0: // The number of characters that fit within the maximum width are returned in michael@0: // aMaxFit. NOTE: it is assumed that the fontmetrics have already been selected michael@0: // into the rendering context before this is called (for performance). MMP michael@0: nscoord michael@0: nsImageFrame::MeasureString(const char16_t* aString, michael@0: int32_t aLength, michael@0: nscoord aMaxWidth, michael@0: uint32_t& aMaxFit, michael@0: nsRenderingContext& aContext) michael@0: { michael@0: nscoord totalWidth = 0; michael@0: aContext.SetTextRunRTL(false); michael@0: nscoord spaceWidth = aContext.GetWidth(' '); michael@0: michael@0: aMaxFit = 0; michael@0: while (aLength > 0) { michael@0: // Find the next place we can line break michael@0: uint32_t len = aLength; michael@0: bool trailingSpace = false; michael@0: for (int32_t i = 0; i < aLength; i++) { michael@0: if (dom::IsSpaceCharacter(aString[i]) && (i > 0)) { michael@0: len = i; // don't include the space when measuring michael@0: trailingSpace = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Measure this chunk of text, and see if it fits michael@0: nscoord width = michael@0: nsLayoutUtils::GetStringWidth(this, &aContext, aString, len); michael@0: bool fits = (totalWidth + width) <= aMaxWidth; michael@0: michael@0: // If it fits on the line, or it's the first word we've processed then michael@0: // include it michael@0: if (fits || (0 == totalWidth)) { michael@0: // New piece fits michael@0: totalWidth += width; michael@0: michael@0: // If there's a trailing space then see if it fits as well michael@0: if (trailingSpace) { michael@0: if ((totalWidth + spaceWidth) <= aMaxWidth) { michael@0: totalWidth += spaceWidth; michael@0: } else { michael@0: // Space won't fit. Leave it at the end but don't include it in michael@0: // the width michael@0: fits = false; michael@0: } michael@0: michael@0: len++; michael@0: } michael@0: michael@0: aMaxFit += len; michael@0: aString += len; michael@0: aLength -= len; michael@0: } michael@0: michael@0: if (!fits) { michael@0: break; michael@0: } michael@0: } michael@0: return totalWidth; michael@0: } michael@0: michael@0: // Formats the alt-text to fit within the specified rectangle. Breaks lines michael@0: // between words if a word would extend past the edge of the rectangle michael@0: void michael@0: nsImageFrame::DisplayAltText(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsString& aAltText, michael@0: const nsRect& aRect) michael@0: { michael@0: // Set font and color michael@0: aRenderingContext.SetColor(StyleColor()->mColor); michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), michael@0: nsLayoutUtils::FontSizeInflationFor(this)); michael@0: aRenderingContext.SetFont(fm); michael@0: michael@0: // Format the text to display within the formatting rect michael@0: michael@0: nscoord maxAscent = fm->MaxAscent(); michael@0: nscoord maxDescent = fm->MaxDescent(); michael@0: nscoord height = fm->MaxHeight(); michael@0: michael@0: // XXX It would be nice if there was a way to have the font metrics tell michael@0: // use where to break the text given a maximum width. At a minimum we need michael@0: // to be able to get the break character... michael@0: const char16_t* str = aAltText.get(); michael@0: int32_t strLen = aAltText.Length(); michael@0: nscoord y = aRect.y; michael@0: michael@0: if (!aPresContext->BidiEnabled() && HasRTLChars(aAltText)) { michael@0: aPresContext->SetBidiEnabled(); michael@0: } michael@0: michael@0: // Always show the first line, even if we have to clip it below michael@0: bool firstLine = true; michael@0: while ((strLen > 0) && (firstLine || (y + maxDescent) < aRect.YMost())) { michael@0: // Determine how much of the text to display on this line michael@0: uint32_t maxFit; // number of characters that fit michael@0: nscoord strWidth = MeasureString(str, strLen, aRect.width, maxFit, michael@0: aRenderingContext); michael@0: michael@0: // Display the text michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: if (aPresContext->BidiEnabled()) { michael@0: const nsStyleVisibility* vis = StyleVisibility(); michael@0: if (vis->mDirection == NS_STYLE_DIRECTION_RTL) michael@0: rv = nsBidiPresUtils::RenderText(str, maxFit, NSBIDI_RTL, michael@0: aPresContext, aRenderingContext, michael@0: aRenderingContext, michael@0: aRect.XMost() - strWidth, y + maxAscent); michael@0: else michael@0: rv = nsBidiPresUtils::RenderText(str, maxFit, NSBIDI_LTR, michael@0: aPresContext, aRenderingContext, michael@0: aRenderingContext, michael@0: aRect.x, y + maxAscent); michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: aRenderingContext.DrawString(str, maxFit, aRect.x, y + maxAscent); michael@0: michael@0: // Move to the next line michael@0: str += maxFit; michael@0: strLen -= maxFit; michael@0: y += height; michael@0: firstLine = false; michael@0: } michael@0: } michael@0: michael@0: struct nsRecessedBorder : public nsStyleBorder { michael@0: nsRecessedBorder(nscoord aBorderWidth, nsPresContext* aPresContext) michael@0: : nsStyleBorder(aPresContext) michael@0: { michael@0: NS_FOR_CSS_SIDES(side) { michael@0: // Note: use SetBorderColor here because we want to make sure michael@0: // the "special" flags are unset. michael@0: SetBorderColor(side, NS_RGB(0, 0, 0)); michael@0: mBorder.Side(side) = aBorderWidth; michael@0: // Note: use SetBorderStyle here because we want to affect michael@0: // mComputedBorder michael@0: SetBorderStyle(side, NS_STYLE_BORDER_STYLE_INSET); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: class nsDisplayAltFeedback : public nsDisplayItem { michael@0: public: michael@0: nsDisplayAltFeedback(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) michael@0: : nsDisplayItem(aBuilder, aFrame) {} michael@0: michael@0: virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, michael@0: bool* aSnap) MOZ_OVERRIDE michael@0: { michael@0: *aSnap = false; michael@0: return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); michael@0: } michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE michael@0: { michael@0: nsImageFrame* f = static_cast(mFrame); michael@0: EventStates state = f->GetContent()->AsElement()->State(); michael@0: f->DisplayAltFeedback(*aCtx, michael@0: mVisibleRect, michael@0: IMAGE_OK(state, true) michael@0: ? nsImageFrame::gIconLoad->mLoadingImage michael@0: : nsImageFrame::gIconLoad->mBrokenImage, michael@0: ToReferenceFrame()); michael@0: michael@0: } michael@0: michael@0: NS_DISPLAY_DECL_NAME("AltFeedback", TYPE_ALT_FEEDBACK) michael@0: }; michael@0: michael@0: void michael@0: nsImageFrame::DisplayAltFeedback(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: imgIRequest* aRequest, michael@0: nsPoint aPt) michael@0: { michael@0: // We should definitely have a gIconLoad here. michael@0: NS_ABORT_IF_FALSE(gIconLoad, "How did we succeed in Init then?"); michael@0: michael@0: // Calculate the inner area michael@0: nsRect inner = GetInnerArea() + aPt; michael@0: michael@0: // Display a recessed one pixel border michael@0: nscoord borderEdgeWidth = nsPresContext::CSSPixelsToAppUnits(ALT_BORDER_WIDTH); michael@0: michael@0: // if inner area is empty, then make it big enough for at least the icon michael@0: if (inner.IsEmpty()){ michael@0: inner.SizeTo(2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH)), michael@0: 2*(nsPresContext::CSSPixelsToAppUnits(ICON_SIZE+ICON_PADDING+ALT_BORDER_WIDTH))); michael@0: } michael@0: michael@0: // Make sure we have enough room to actually render the border within michael@0: // our frame bounds michael@0: if ((inner.width < 2 * borderEdgeWidth) || (inner.height < 2 * borderEdgeWidth)) { michael@0: return; michael@0: } michael@0: michael@0: // Paint the border michael@0: nsRecessedBorder recessedBorder(borderEdgeWidth, PresContext()); michael@0: nsCSSRendering::PaintBorderWithStyleBorder(PresContext(), aRenderingContext, michael@0: this, inner, inner, michael@0: recessedBorder, mStyleContext); michael@0: michael@0: // Adjust the inner rect to account for the one pixel recessed border, michael@0: // and a six pixel padding on each edge michael@0: inner.Deflate(nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH), michael@0: nsPresContext::CSSPixelsToAppUnits(ICON_PADDING+ALT_BORDER_WIDTH)); michael@0: if (inner.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // Clip so we don't render outside the inner rect michael@0: aRenderingContext.PushState(); michael@0: aRenderingContext.IntersectClip(inner); michael@0: michael@0: // Check if we should display image placeholders michael@0: if (gIconLoad->mPrefShowPlaceholders) { michael@0: const nsStyleVisibility* vis = StyleVisibility(); michael@0: nscoord size = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE); michael@0: michael@0: bool iconUsed = false; michael@0: michael@0: // If we weren't previously displaying an icon, register ourselves michael@0: // as an observer for load and animation updates and flag that we're michael@0: // doing so now. michael@0: if (aRequest && !mDisplayingIcon) { michael@0: gIconLoad->AddIconObserver(this); michael@0: mDisplayingIcon = true; michael@0: } michael@0: michael@0: michael@0: // If the icon in question is loaded and decoded, draw it michael@0: uint32_t imageStatus = 0; michael@0: if (aRequest) michael@0: aRequest->GetImageStatus(&imageStatus); michael@0: if (imageStatus & imgIRequest::STATUS_FRAME_COMPLETE) { michael@0: nsCOMPtr imgCon; michael@0: aRequest->GetImage(getter_AddRefs(imgCon)); michael@0: NS_ABORT_IF_FALSE(imgCon, "Frame Complete, but no image container?"); michael@0: nsRect dest((vis->mDirection == NS_STYLE_DIRECTION_RTL) ? michael@0: inner.XMost() - size : inner.x, michael@0: inner.y, size, size); michael@0: nsLayoutUtils::DrawSingleImage(&aRenderingContext, imgCon, michael@0: nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect, michael@0: nullptr, imgIContainer::FLAG_NONE); michael@0: iconUsed = true; michael@0: } michael@0: michael@0: // if we could not draw the icon, flag that we're waiting for it and michael@0: // just draw some graffiti in the mean time michael@0: if (!iconUsed) { michael@0: nscoord iconXPos = (vis->mDirection == NS_STYLE_DIRECTION_RTL) ? michael@0: inner.XMost() - size : inner.x; michael@0: nscoord twoPX = nsPresContext::CSSPixelsToAppUnits(2); michael@0: aRenderingContext.DrawRect(iconXPos, inner.y,size,size); michael@0: aRenderingContext.PushState(); michael@0: aRenderingContext.SetColor(NS_RGB(0xFF,0,0)); michael@0: aRenderingContext.FillEllipse(size/2 + iconXPos, size/2 + inner.y, michael@0: size/2 - twoPX, size/2 - twoPX); michael@0: aRenderingContext.PopState(); michael@0: } michael@0: michael@0: // Reduce the inner rect by the width of the icon, and leave an michael@0: // additional ICON_PADDING pixels for padding michael@0: int32_t iconWidth = nsPresContext::CSSPixelsToAppUnits(ICON_SIZE + ICON_PADDING); michael@0: if (vis->mDirection != NS_STYLE_DIRECTION_RTL) michael@0: inner.x += iconWidth; michael@0: inner.width -= iconWidth; michael@0: } michael@0: michael@0: // If there's still room, display the alt-text michael@0: if (!inner.IsEmpty()) { michael@0: nsIContent* content = GetContent(); michael@0: if (content) { michael@0: nsXPIDLString altText; michael@0: nsCSSFrameConstructor::GetAlternateTextFor(content, content->Tag(), michael@0: altText); michael@0: DisplayAltText(PresContext(), aRenderingContext, altText, inner); michael@0: } michael@0: } michael@0: michael@0: aRenderingContext.PopState(); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static void PaintDebugImageMap(nsIFrame* aFrame, nsRenderingContext* aCtx, michael@0: const nsRect& aDirtyRect, nsPoint aPt) { michael@0: nsImageFrame* f = static_cast(aFrame); michael@0: nsRect inner = f->GetInnerArea() + aPt; michael@0: michael@0: aCtx->SetColor(NS_RGB(0, 0, 0)); michael@0: aCtx->PushState(); michael@0: aCtx->Translate(inner.TopLeft()); michael@0: f->GetImageMap()->Draw(aFrame, *aCtx); michael@0: aCtx->PopState(); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsDisplayImage::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) { michael@0: uint32_t flags = imgIContainer::FLAG_NONE; michael@0: if (aBuilder->ShouldSyncDecodeImages()) { michael@0: flags |= imgIContainer::FLAG_SYNC_DECODE; michael@0: } michael@0: if (aBuilder->IsPaintingToWindow()) { michael@0: flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; michael@0: } michael@0: static_cast(mFrame)-> michael@0: PaintImage(*aCtx, ToReferenceFrame(), mVisibleRect, mImage, flags); michael@0: } michael@0: michael@0: void michael@0: nsDisplayImage::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion* aInvalidRegion) michael@0: { michael@0: if (aBuilder->ShouldSyncDecodeImages() && mImage && !mImage->IsDecoded()) { michael@0: bool snap; michael@0: aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); michael@0: } michael@0: michael@0: nsDisplayImageContainer::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDisplayImage::GetContainer(LayerManager* aManager, michael@0: nsDisplayListBuilder* aBuilder) michael@0: { michael@0: nsRefPtr container; michael@0: nsresult rv = mImage->GetImageContainer(aManager, getter_AddRefs(container)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: return container.forget(); michael@0: } michael@0: michael@0: gfxRect michael@0: nsDisplayImage::GetDestRect() michael@0: { michael@0: int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: nsImageFrame* imageFrame = static_cast(mFrame); michael@0: michael@0: nsRect dest = imageFrame->GetInnerArea() + ToReferenceFrame(); michael@0: gfxRect destRect(dest.x, dest.y, dest.width, dest.height); michael@0: destRect.ScaleInverse(factor); michael@0: michael@0: return destRect; michael@0: } michael@0: michael@0: LayerState michael@0: nsDisplayImage::GetLayerState(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: const ContainerLayerParameters& aParameters) michael@0: { michael@0: bool animated = false; michael@0: if (!nsLayoutUtils::AnimatedImageLayersEnabled() || michael@0: mImage->GetType() != imgIContainer::TYPE_RASTER || michael@0: NS_FAILED(mImage->GetAnimated(&animated)) || michael@0: !animated) { michael@0: if (!aManager->IsCompositingCheap() || michael@0: !nsLayoutUtils::GPUImageScalingEnabled()) { michael@0: return LAYER_NONE; michael@0: } michael@0: } michael@0: michael@0: if (!animated) { michael@0: int32_t imageWidth; michael@0: int32_t imageHeight; michael@0: mImage->GetWidth(&imageWidth); michael@0: mImage->GetHeight(&imageHeight); michael@0: michael@0: NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); michael@0: michael@0: gfxRect destRect = GetDestRect(); michael@0: michael@0: destRect.width *= aParameters.mXScale; michael@0: destRect.height *= aParameters.mYScale; michael@0: michael@0: // Calculate the scaling factor for the frame. michael@0: gfxSize scale = gfxSize(destRect.width / imageWidth, michael@0: destRect.height / imageHeight); michael@0: michael@0: // If we are not scaling at all, no point in separating this into a layer. michael@0: if (scale.width == 1.0f && scale.height == 1.0f) { michael@0: return LAYER_NONE; michael@0: } michael@0: michael@0: // If the target size is pretty small, no point in using a layer. michael@0: if (destRect.width * destRect.height < 64 * 64) { michael@0: return LAYER_NONE; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr container; michael@0: mImage->GetImageContainer(aManager, getter_AddRefs(container)); michael@0: if (!container) { michael@0: return LAYER_NONE; michael@0: } michael@0: michael@0: return LAYER_ACTIVE; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDisplayImage::BuildLayer(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: const ContainerLayerParameters& aParameters) michael@0: { michael@0: nsRefPtr container; michael@0: nsresult rv = mImage->GetImageContainer(aManager, getter_AddRefs(container)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: nsRefPtr layer = static_cast michael@0: (aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); michael@0: if (!layer) { michael@0: layer = aManager->CreateImageLayer(); michael@0: if (!layer) michael@0: return nullptr; michael@0: } michael@0: layer->SetContainer(container); michael@0: ConfigureLayer(layer, aParameters.mOffset); michael@0: return layer.forget(); michael@0: } michael@0: michael@0: void michael@0: nsDisplayImage::ConfigureLayer(ImageLayer *aLayer, const nsIntPoint& aOffset) michael@0: { michael@0: aLayer->SetFilter(nsLayoutUtils::GetGraphicsFilterForFrame(mFrame)); michael@0: michael@0: int32_t imageWidth; michael@0: int32_t imageHeight; michael@0: mImage->GetWidth(&imageWidth); michael@0: mImage->GetHeight(&imageHeight); michael@0: michael@0: NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); michael@0: michael@0: const gfxRect destRect = GetDestRect(); michael@0: michael@0: gfx::Matrix transform; michael@0: gfxPoint p = destRect.TopLeft() + aOffset; michael@0: transform.Translate(p.x, p.y); michael@0: transform.Scale(destRect.Width()/imageWidth, michael@0: destRect.Height()/imageHeight); michael@0: aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); michael@0: aLayer->SetVisibleRegion(nsIntRect(0, 0, imageWidth, imageHeight)); michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::PaintImage(nsRenderingContext& aRenderingContext, nsPoint aPt, michael@0: const nsRect& aDirtyRect, imgIContainer* aImage, michael@0: uint32_t aFlags) michael@0: { michael@0: // Render the image into our content area (the area inside michael@0: // the borders and padding) michael@0: NS_ASSERTION(GetInnerArea().width == mComputedSize.width, "bad width"); michael@0: nsRect inner = GetInnerArea() + aPt; michael@0: nsRect dest(inner.TopLeft(), mComputedSize); michael@0: dest.y -= GetContinuationOffset(); michael@0: michael@0: nsLayoutUtils::DrawSingleImage(&aRenderingContext, aImage, michael@0: nsLayoutUtils::GetGraphicsFilterForFrame(this), dest, aDirtyRect, michael@0: nullptr, aFlags); michael@0: michael@0: nsImageMap* map = GetImageMap(); michael@0: if (nullptr != map) { michael@0: aRenderingContext.PushState(); michael@0: aRenderingContext.Translate(inner.TopLeft()); michael@0: aRenderingContext.SetColor(NS_RGB(255, 255, 255)); michael@0: aRenderingContext.SetLineStyle(nsLineStyle_kSolid); michael@0: map->Draw(this, aRenderingContext); michael@0: aRenderingContext.SetColor(NS_RGB(0, 0, 0)); michael@0: aRenderingContext.SetLineStyle(nsLineStyle_kDotted); michael@0: map->Draw(this, aRenderingContext); michael@0: aRenderingContext.PopState(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!IsVisibleForPainting(aBuilder)) michael@0: return; michael@0: michael@0: DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: michael@0: DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox michael@0: clip(aBuilder, this, DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT); michael@0: michael@0: if (mComputedSize.width != 0 && mComputedSize.height != 0) { michael@0: nsCOMPtr imageLoader = do_QueryInterface(mContent); michael@0: NS_ASSERTION(imageLoader, "Not an image loading content?"); michael@0: michael@0: nsCOMPtr currentRequest; michael@0: if (imageLoader) { michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(currentRequest)); michael@0: } michael@0: michael@0: EventStates contentState = mContent->AsElement()->State(); michael@0: bool imageOK = IMAGE_OK(contentState, true); michael@0: michael@0: // XXX(seth): The SizeIsAvailable check here should not be necessary - the michael@0: // intention is that a non-null mImage means we have a size, but there is michael@0: // currently some code that violates this invariant. michael@0: if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) { michael@0: // No image yet, or image load failed. Draw the alt-text and an icon michael@0: // indicating the status michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayAltFeedback(aBuilder, this)); michael@0: } else { michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayImage(aBuilder, this, mImage)); michael@0: michael@0: // If we were previously displaying an icon, we're not anymore michael@0: if (mDisplayingIcon) { michael@0: gIconLoad->RemoveIconObserver(this); michael@0: mDisplayingIcon = false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (GetShowFrameBorders() && GetImageMap()) { michael@0: aLists.Outlines()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayGeneric(aBuilder, this, PaintDebugImageMap, "DebugImageMap", michael@0: nsDisplayItem::TYPE_DEBUG_IMAGE_MAP)); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: if (ShouldDisplaySelection()) { michael@0: DisplaySelectionOverlay(aBuilder, aLists.Content(), michael@0: nsISelectionDisplay::DISPLAY_IMAGES); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::ShouldDisplaySelection() michael@0: { michael@0: // XXX what on EARTH is this code for? michael@0: nsresult result; michael@0: nsPresContext* presContext = PresContext(); michael@0: int16_t displaySelection = presContext->PresShell()->GetSelectionFlags(); michael@0: if (!(displaySelection & nsISelectionDisplay::DISPLAY_IMAGES)) michael@0: return false;//no need to check the blue border, we cannot be drawn selected michael@0: //insert hook here for image selection drawing michael@0: #if IMAGE_EDITOR_CHECK michael@0: //check to see if this frame is in an editor context michael@0: //isEditor check. this needs to be changed to have better way to check michael@0: if (displaySelection == nsISelectionDisplay::DISPLAY_ALL) michael@0: { michael@0: nsCOMPtr selCon; michael@0: result = GetSelectionController(presContext, getter_AddRefs(selCon)); michael@0: if (NS_SUCCEEDED(result) && selCon) michael@0: { michael@0: nsCOMPtr selection; michael@0: result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection)); michael@0: if (NS_SUCCEEDED(result) && selection) michael@0: { michael@0: int32_t rangeCount; michael@0: selection->GetRangeCount(&rangeCount); michael@0: if (rangeCount == 1) //if not one then let code drop to nsFrame::Paint michael@0: { michael@0: nsCOMPtr parentContent = mContent->GetParent(); michael@0: if (parentContent) michael@0: { michael@0: int32_t thisOffset = parentContent->IndexOf(mContent); michael@0: nsCOMPtr parentNode = do_QueryInterface(parentContent); michael@0: nsCOMPtr rangeNode; michael@0: int32_t rangeOffset; michael@0: nsCOMPtr range; michael@0: selection->GetRangeAt(0,getter_AddRefs(range)); michael@0: if (range) michael@0: { michael@0: range->GetStartContainer(getter_AddRefs(rangeNode)); michael@0: range->GetStartOffset(&rangeOffset); michael@0: michael@0: if (parentNode && rangeNode && (rangeNode == parentNode) && rangeOffset == thisOffset) michael@0: { michael@0: range->GetEndContainer(getter_AddRefs(rangeNode)); michael@0: range->GetEndOffset(&rangeOffset); michael@0: if ((rangeNode == parentNode) && (rangeOffset == (thisOffset +1))) //+1 since that would mean this whole content is selected only michael@0: return false; //do not allow nsFrame do draw any further selection michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: nsImageMap* michael@0: nsImageFrame::GetImageMap() michael@0: { michael@0: if (!mImageMap) { michael@0: nsIContent* map = GetMapElement(); michael@0: if (map) { michael@0: mImageMap = new nsImageMap(); michael@0: NS_ADDREF(mImageMap); michael@0: mImageMap->Init(this, map); michael@0: } michael@0: } michael@0: michael@0: return mImageMap; michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::IsServerImageMap() michael@0: { michael@0: return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::ismap); michael@0: } michael@0: michael@0: // Translate an point that is relative to our frame michael@0: // into a localized pixel coordinate that is relative to the michael@0: // content area of this frame (inside the border+padding). michael@0: void michael@0: nsImageFrame::TranslateEventCoords(const nsPoint& aPoint, michael@0: nsIntPoint& aResult) michael@0: { michael@0: nscoord x = aPoint.x; michael@0: nscoord y = aPoint.y; michael@0: michael@0: // Subtract out border and padding here so that the coordinates are michael@0: // now relative to the content area of this frame. michael@0: nsRect inner = GetInnerArea(); michael@0: x -= inner.x; michael@0: y -= inner.y; michael@0: michael@0: aResult.x = nsPresContext::AppUnitsToIntCSSPixels(x); michael@0: aResult.y = nsPresContext::AppUnitsToIntCSSPixels(y); michael@0: } michael@0: michael@0: bool michael@0: nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, michael@0: nsIContent** aNode) michael@0: { michael@0: bool status = false; michael@0: aTarget.Truncate(); michael@0: *aHref = nullptr; michael@0: *aNode = nullptr; michael@0: michael@0: // Walk up the content tree, looking for an nsIDOMAnchorElement michael@0: for (nsIContent* content = mContent->GetParent(); michael@0: content; content = content->GetParent()) { michael@0: nsCOMPtr link(do_QueryInterface(content)); michael@0: if (link) { michael@0: nsCOMPtr href = content->GetHrefURI(); michael@0: if (href) { michael@0: href->Clone(aHref); michael@0: } michael@0: status = (*aHref != nullptr); michael@0: michael@0: nsCOMPtr anchor(do_QueryInterface(content)); michael@0: if (anchor) { michael@0: anchor->GetTarget(aTarget); michael@0: } michael@0: NS_ADDREF(*aNode = content); michael@0: break; michael@0: } michael@0: } michael@0: return status; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::GetContentForEvent(WidgetEvent* aEvent, michael@0: nsIContent** aContent) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aContent); michael@0: michael@0: nsIFrame* f = nsLayoutUtils::GetNonGeneratedAncestor(this); michael@0: if (f != this) { michael@0: return f->GetContentForEvent(aEvent, aContent); michael@0: } michael@0: michael@0: // XXX We need to make this special check for area element's capturing the michael@0: // mouse due to bug 135040. Remove it once that's fixed. michael@0: nsIContent* capturingContent = michael@0: aEvent->HasMouseEventMessage() ? nsIPresShell::GetCapturingContent() : michael@0: nullptr; michael@0: if (capturingContent && capturingContent->GetPrimaryFrame() == this) { michael@0: *aContent = capturingContent; michael@0: NS_IF_ADDREF(*aContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsImageMap* map = GetImageMap(); michael@0: michael@0: if (nullptr != map) { michael@0: nsIntPoint p; michael@0: TranslateEventCoords( michael@0: nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); michael@0: nsCOMPtr area = map->GetArea(p.x, p.y); michael@0: if (area) { michael@0: area.forget(aContent); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aContent = GetContent(); michael@0: NS_IF_ADDREF(*aContent); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX what should clicks on transparent pixels do? michael@0: nsresult michael@0: nsImageFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEventStatus); michael@0: michael@0: if ((aEvent->message == NS_MOUSE_BUTTON_UP && michael@0: aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) || michael@0: aEvent->message == NS_MOUSE_MOVE) { michael@0: nsImageMap* map = GetImageMap(); michael@0: bool isServerMap = IsServerImageMap(); michael@0: if ((nullptr != map) || isServerMap) { michael@0: nsIntPoint p; michael@0: TranslateEventCoords( michael@0: nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this), p); michael@0: bool inside = false; michael@0: // Even though client-side image map triggering happens michael@0: // through content, we need to make sure we're not inside michael@0: // (in case we deal with a case of both client-side and michael@0: // sever-side on the same image - it happens!) michael@0: if (nullptr != map) { michael@0: inside = !!map->GetArea(p.x, p.y); michael@0: } michael@0: michael@0: if (!inside && isServerMap) { michael@0: michael@0: // Server side image maps use the href in a containing anchor michael@0: // element to provide the basis for the destination url. michael@0: nsCOMPtr uri; michael@0: nsAutoString target; michael@0: nsCOMPtr anchorNode; michael@0: if (GetAnchorHREFTargetAndNode(getter_AddRefs(uri), target, michael@0: getter_AddRefs(anchorNode))) { michael@0: // XXX if the mouse is over/clicked in the border/padding area michael@0: // we should probably just pretend nothing happened. Nav4 michael@0: // keeps the x,y coordinates positive as we do; IE doesn't michael@0: // bother. Both of them send the click through even when the michael@0: // mouse is over the border. michael@0: if (p.x < 0) p.x = 0; michael@0: if (p.y < 0) p.y = 0; michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: spec += nsPrintfCString("?%d,%d", p.x, p.y); michael@0: uri->SetSpec(spec); michael@0: michael@0: bool clicked = false; michael@0: if (aEvent->message == NS_MOUSE_BUTTON_UP) { michael@0: *aEventStatus = nsEventStatus_eConsumeDoDefault; michael@0: clicked = true; michael@0: } michael@0: nsContentUtils::TriggerLink(anchorNode, aPresContext, uri, target, michael@0: clicked, true, true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return nsSplittableFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::GetCursor(const nsPoint& aPoint, michael@0: nsIFrame::Cursor& aCursor) michael@0: { michael@0: nsImageMap* map = GetImageMap(); michael@0: if (nullptr != map) { michael@0: nsIntPoint p; michael@0: TranslateEventCoords(aPoint, p); michael@0: nsCOMPtr area = map->GetArea(p.x, p.y); michael@0: if (area) { michael@0: // Use the cursor from the style of the *area* element. michael@0: // XXX Using the image as the parent style context isn't michael@0: // technically correct, but it's probably the right thing to do michael@0: // here, since it means that areas on which the cursor isn't michael@0: // specified will inherit the style from the image. michael@0: nsRefPtr areaStyle = michael@0: PresContext()->PresShell()->StyleSet()-> michael@0: ResolveStyleFor(area->AsElement(), StyleContext()); michael@0: FillCursorInformationFromStyle(areaStyle->StyleUserInterface(), michael@0: aCursor); michael@0: if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { michael@0: aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return nsFrame::GetCursor(aPoint, aCursor); michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: nsresult rv = nsSplittableFrame::AttributeChanged(aNameSpaceID, michael@0: aAttribute, aModType); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (nsGkAtoms::alt == aAttribute) michael@0: { michael@0: PresContext()->PresShell()->FrameNeedsReflow(this, michael@0: nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsImageFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::imageFrame; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsImageFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("ImageFrame"), aResult); michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const michael@0: { michael@0: nsCString str; michael@0: ListGeneric(str, aPrefix, aFlags); michael@0: michael@0: // output the img src url michael@0: nsCOMPtr imageLoader = do_QueryInterface(mContent); michael@0: if (imageLoader) { michael@0: nsCOMPtr currentRequest; michael@0: imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, michael@0: getter_AddRefs(currentRequest)); michael@0: if (currentRequest) { michael@0: nsCOMPtr uri; michael@0: currentRequest->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString uristr; michael@0: uri->GetAsciiSpec(uristr); michael@0: str += nsPrintfCString(" [src=%s]", uristr.get()); michael@0: } michael@0: } michael@0: fprintf_stderr(out, "%s\n", str.get()); michael@0: } michael@0: #endif michael@0: michael@0: int michael@0: nsImageFrame::GetLogicalSkipSides(const nsHTMLReflowState* aReflowState) const michael@0: { michael@0: int skip = 0; michael@0: if (nullptr != GetPrevInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_START; michael@0: } michael@0: if (nullptr != GetNextInFlow()) { michael@0: skip |= LOGICAL_SIDE_B_END; michael@0: } michael@0: return skip; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::GetIntrinsicImageSize(nsSize& aSize) michael@0: { michael@0: if (mIntrinsicSize.width.GetUnit() == eStyleUnit_Coord && michael@0: mIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { michael@0: aSize.SizeTo(mIntrinsicSize.width.GetCoordValue(), michael@0: mIntrinsicSize.height.GetCoordValue()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageFrame::LoadIcon(const nsAString& aSpec, michael@0: nsPresContext *aPresContext, michael@0: imgRequestProxy** aRequest) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: NS_PRECONDITION(!aSpec.IsEmpty(), "What happened??"); michael@0: NS_PRECONDITION(aPresContext, "NULL PresContext"); michael@0: michael@0: if (!sIOService) { michael@0: rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr realURI; michael@0: SpecToURI(aSpec, sIOService, getter_AddRefs(realURI)); michael@0: michael@0: nsRefPtr il = michael@0: nsContentUtils::GetImgLoaderForDocument(aPresContext->Document()); michael@0: michael@0: nsCOMPtr loadGroup; michael@0: GetLoadGroup(aPresContext, getter_AddRefs(loadGroup)); michael@0: michael@0: // For icon loads, we don't need to merge with the loadgroup flags michael@0: nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; michael@0: michael@0: nsCOMPtr firstPartyIsolationURI; michael@0: nsCOMPtr thirdPartySvc michael@0: = do_GetService(THIRDPARTYUTIL_CONTRACTID); michael@0: // XXX: Should we pass the loadgroup, too? Is document ever likely michael@0: // to be unset? michael@0: thirdPartySvc->GetFirstPartyIsolationURI(nullptr, aPresContext->Document(), michael@0: getter_AddRefs(firstPartyIsolationURI)); michael@0: michael@0: return il->LoadImage(realURI, /* icon URI */ michael@0: firstPartyIsolationURI, /* initial document URI; this is only michael@0: relevant for cookies, so does not michael@0: apply to icons. */ michael@0: nullptr, /* referrer (not relevant for icons) */ michael@0: nullptr, /* principal (not relevant for icons) */ michael@0: loadGroup, michael@0: gIconLoad, michael@0: nullptr, /* Not associated with any particular document */ michael@0: loadFlags, michael@0: nullptr, michael@0: nullptr, /* channel policy not needed */ michael@0: EmptyString(), michael@0: aRequest); michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::GetDocumentCharacterSet(nsACString& aCharset) const michael@0: { michael@0: if (mContent) { michael@0: NS_ASSERTION(mContent->GetDocument(), michael@0: "Frame still alive after content removed from document!"); michael@0: aCharset = mContent->GetDocument()->GetDocumentCharacterSet(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::SpecToURI(const nsAString& aSpec, nsIIOService *aIOService, michael@0: nsIURI **aURI) michael@0: { michael@0: nsCOMPtr baseURI; michael@0: if (mContent) { michael@0: baseURI = mContent->GetBaseURI(); michael@0: } michael@0: nsAutoCString charset; michael@0: GetDocumentCharacterSet(charset); michael@0: NS_NewURI(aURI, aSpec, michael@0: charset.IsEmpty() ? nullptr : charset.get(), michael@0: baseURI, aIOService); michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup) michael@0: { michael@0: if (!aPresContext) michael@0: return; michael@0: michael@0: NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer"); michael@0: michael@0: nsIPresShell *shell = aPresContext->GetPresShell(); michael@0: michael@0: if (!shell) michael@0: return; michael@0: michael@0: nsIDocument *doc = shell->GetDocument(); michael@0: if (!doc) michael@0: return; michael@0: michael@0: *aLoadGroup = doc->GetDocumentLoadGroup().take(); michael@0: } michael@0: michael@0: nsresult nsImageFrame::LoadIcons(nsPresContext *aPresContext) michael@0: { michael@0: NS_ASSERTION(!gIconLoad, "called LoadIcons twice"); michael@0: michael@0: NS_NAMED_LITERAL_STRING(loadingSrc,"resource://gre-resources/loading-image.png"); michael@0: NS_NAMED_LITERAL_STRING(brokenSrc,"resource://gre-resources/broken-image.png"); michael@0: michael@0: gIconLoad = new IconLoad(); michael@0: NS_ADDREF(gIconLoad); michael@0: michael@0: nsresult rv; michael@0: // create a loader and load the images michael@0: rv = LoadIcon(loadingSrc, michael@0: aPresContext, michael@0: getter_AddRefs(gIconLoad->mLoadingImage)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = LoadIcon(brokenSrc, michael@0: aPresContext, michael@0: getter_AddRefs(gIconLoad->mBrokenImage)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsImageFrame::IconLoad, nsIObserver, michael@0: imgINotificationObserver) michael@0: michael@0: static const char* kIconLoadPrefs[] = { michael@0: "browser.display.force_inline_alttext", michael@0: "browser.display.show_image_placeholders", michael@0: nullptr michael@0: }; michael@0: michael@0: nsImageFrame::IconLoad::IconLoad() michael@0: { michael@0: // register observers michael@0: Preferences::AddStrongObservers(this, kIconLoadPrefs); michael@0: GetPrefs(); michael@0: } michael@0: michael@0: void michael@0: nsImageFrame::IconLoad::Shutdown() michael@0: { michael@0: Preferences::RemoveObservers(this, kIconLoadPrefs); michael@0: // in case the pref service releases us later michael@0: if (mLoadingImage) { michael@0: mLoadingImage->CancelAndForgetObserver(NS_ERROR_FAILURE); michael@0: mLoadingImage = nullptr; michael@0: } michael@0: if (mBrokenImage) { michael@0: mBrokenImage->CancelAndForgetObserver(NS_ERROR_FAILURE); michael@0: mBrokenImage = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsImageFrame::IconLoad::Observe(nsISupports *aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), michael@0: "wrong topic"); michael@0: #ifdef DEBUG michael@0: // assert |aData| is one of our prefs. michael@0: for (uint32_t i = 0; i < ArrayLength(kIconLoadPrefs) || michael@0: (NS_NOTREACHED("wrong pref"), false); ++i) michael@0: if (NS_ConvertASCIItoUTF16(kIconLoadPrefs[i]) == nsDependentString(aData)) michael@0: break; michael@0: #endif michael@0: michael@0: GetPrefs(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void nsImageFrame::IconLoad::GetPrefs() michael@0: { michael@0: mPrefForceInlineAltText = michael@0: Preferences::GetBool("browser.display.force_inline_alttext"); michael@0: michael@0: mPrefShowPlaceholders = michael@0: Preferences::GetBool("browser.display.show_image_placeholders", true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsImageFrame::IconLoad::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (aType != imgINotificationObserver::LOAD_COMPLETE && michael@0: aType != imgINotificationObserver::FRAME_UPDATE) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsTObserverArray::ForwardIterator iter(mIconObservers); michael@0: nsImageFrame *frame; michael@0: while (iter.HasMore()) { michael@0: frame = iter.GetNext(); michael@0: frame->InvalidateFrame(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsImageListener, imgINotificationObserver) michael@0: michael@0: nsImageListener::nsImageListener(nsImageFrame *aFrame) : michael@0: mFrame(aFrame) michael@0: { michael@0: } michael@0: michael@0: nsImageListener::~nsImageListener() michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsImageListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) michael@0: { michael@0: if (!mFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mFrame->Notify(aRequest, aType, aData); michael@0: } michael@0: michael@0: static bool michael@0: IsInAutoWidthTableCellForQuirk(nsIFrame *aFrame) michael@0: { michael@0: if (eCompatibility_NavQuirks != aFrame->PresContext()->CompatibilityMode()) michael@0: return false; michael@0: // Check if the parent of the closest nsBlockFrame has auto width. michael@0: nsBlockFrame *ancestor = nsLayoutUtils::FindNearestBlockAncestor(aFrame); michael@0: if (ancestor->StyleContext()->GetPseudo() == nsCSSAnonBoxes::cellContent) { michael@0: // Assume direct parent is a table cell frame. michael@0: nsFrame *grandAncestor = static_cast(ancestor->GetParent()); michael@0: return grandAncestor && michael@0: grandAncestor->StylePosition()->mWidth.GetUnit() == eStyleUnit_Auto; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsImageFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlineMinWidthData *aData) michael@0: { michael@0: michael@0: NS_ASSERTION(GetParent(), "Must have a parent if we get here!"); michael@0: michael@0: nsIFrame* parent = GetParent(); michael@0: bool canBreak = michael@0: !CanContinueTextRun() && michael@0: parent->StyleText()->WhiteSpaceCanWrap(parent) && michael@0: !IsInAutoWidthTableCellForQuirk(this); michael@0: michael@0: if (canBreak) michael@0: aData->OptionallyBreak(aRenderingContext); michael@0: michael@0: aData->trailingWhitespace = 0; michael@0: aData->skipWhitespace = false; michael@0: aData->trailingTextFrame = nullptr; michael@0: aData->currentLine += nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: this, nsLayoutUtils::MIN_WIDTH); michael@0: aData->atStartOfLine = false; michael@0: michael@0: if (canBreak) michael@0: aData->OptionallyBreak(aRenderingContext); michael@0: michael@0: }