/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsHTMLImageLoader.h" #include "nsIHTMLReflow.h" #include "nsFrame.h" #include "nsIURL.h" #include "nsNeckoUtil.h" #ifdef DEBUG #undef NOISY_IMAGE_LOADING #else #undef NOISY_IMAGE_LOADING #endif MOZ_DECL_CTOR_COUNTER(nsHTMLImageLoader); nsHTMLImageLoader::nsHTMLImageLoader() : mBaseURL(nsnull), mFrame(nsnull), mCallBack(nsnull), mClosure(nsnull), mImageLoader(nsnull), mAllFlags(0), mIntrinsicImageSize(0, 0), mComputedImageSize(0, 0) { MOZ_COUNT_CTOR(nsHTMLImageLoader); } nsHTMLImageLoader::~nsHTMLImageLoader() { MOZ_COUNT_DTOR(nsHTMLImageLoader); NS_IF_RELEASE(mBaseURL); NS_IF_RELEASE(mImageLoader); } void nsHTMLImageLoader::Init(nsIFrame* aFrame, nsHTMLImageLoaderCB aCallBack, void* aClosure, nsIURI* aBaseURL, const nsString& aURLSpec) { mFrame = aFrame; mCallBack = aCallBack; mClosure = aClosure; mBaseURL = aBaseURL; NS_IF_ADDREF(mBaseURL); SetURL(aURLSpec); } nsIImage* nsHTMLImageLoader::GetImage() { nsIImage* image = nsnull; if (mImageLoader) { mImageLoader->GetImage(&image); } return image; } void nsHTMLImageLoader::SetURL(const nsString& aNewSpec) { mURLSpec = aNewSpec; if (mBaseURL && !aNewSpec.Equals("")) { nsString empty; nsresult rv; rv = NS_MakeAbsoluteURI(mURLSpec, mBaseURL, mURL); if (NS_FAILED(rv)) { mURL = mURLSpec; } } else { mURL = mURLSpec; } } void nsHTMLImageLoader::StopLoadImage(nsIPresContext* aPresContext) { if (mImageLoader) { aPresContext->StopLoadImage(mFrame, mImageLoader); NS_RELEASE(mImageLoader); } } void nsHTMLImageLoader::StopAllLoadImages(nsIPresContext* aPresContext) { aPresContext->StopAllLoadImagesFor(mFrame); } nsresult nsHTMLImageLoader::ImageLoadCB(nsIPresContext* aPresContext, nsIFrameImageLoader* aLoader, nsIFrame* aFrame, void* aClosure, PRUint32 aStatus) { if (aClosure) { ((nsHTMLImageLoader*)aClosure)->Update(aPresContext, aFrame, aStatus); } return NS_OK; } void nsHTMLImageLoader::Update(nsIPresContext* aPresContext, nsIFrame* aFrame, PRUint32 aStatus) { #ifdef NOISY_IMAGE_LOADING nsFrame::ListTag(stdout, aFrame); printf(": update: status=%x [loader=%p] callBack=%p squelch=%s\n", aStatus, mImageLoader, mCallBack, mFlags.mSquelchCallback ? "yes" : "no"); #endif if (NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE & aStatus) { if (mImageLoader) { mImageLoader->GetSize(mIntrinsicImageSize); if (mFlags.mNeedIntrinsicImageSize) { mFlags.mHaveIntrinsicImageSize = PR_TRUE; } } if ((NS_IMAGE_LOAD_STATUS_SIZE_AVAILABLE == aStatus) && !mFlags.mNeedSizeNotification) { return; } } // Pass on update to the user of this object if they want it if (mCallBack) { if (!mFlags.mSquelchCallback) { (*mCallBack)(aPresContext, this, aFrame, mClosure, aStatus); } } } // The prefix for special "internal" images that are well known #define GOPHER_SPEC "internal-gopher-" // Note: sizeof a string constant includes the \0 at the end so // subtract one #define GOPHER_SPEC_SIZE (sizeof(GOPHER_SPEC) - 1) nsresult nsHTMLImageLoader::StartLoadImage(nsIPresContext* aPresContext) { if (!mFrame) { // We were not initialized! return NS_ERROR_NULL_POINTER; } if (mURL.Equals("")) { return NS_OK; } // Note: navigator 4.* and earlier releases ignored the base tags // effect on the builtin images. So we do too. Use mURLSpec instead // of the absolute url... nsAutoString internalImageURLSpec; nsString* urlSpec = &mURL; if (mURLSpec.Compare(GOPHER_SPEC, PR_FALSE, GOPHER_SPEC_SIZE) == 0) { // We found a special image source value that refers to a // builtin image. Rewrite the source url as a resource url. urlSpec = &internalImageURLSpec; mURLSpec.Mid(internalImageURLSpec, GOPHER_SPEC_SIZE, mURLSpec.Length() - GOPHER_SPEC_SIZE); internalImageURLSpec.Insert("resource:/res/html/gopher-", 0); internalImageURLSpec.Append(".gif"); } // This is kind of sick, but its possible that we will get a // notification *before* we have setup mImageLoader. To get around // this, we let the pres-context store into mImageLoader and sort // things after it returns. nsIFrameImageLoader* oldLoader = mImageLoader; nsSize* sizeToLoadWidth = nsnull; if (!mFlags.mAutoImageSize && !mFlags.mNeedIntrinsicImageSize) { sizeToLoadWidth = &mComputedImageSize; } nsresult rv = aPresContext->StartLoadImage(*urlSpec, nsnull, sizeToLoadWidth, mFrame, ImageLoadCB, (void*)this, &mImageLoader); #ifdef NOISY_IMAGE_LOADING nsFrame::ListTag(stdout, mFrame); printf(": loading image '"); fputs(mURL, stdout); printf("' @ "); if (mFlags.mNeedIntrinsicImageSize) { printf("intrinsic size "); } printf("%d,%d; oldLoader=%p newLoader=%p", mComputedImageSize.width, mComputedImageSize.height, oldLoader, mImageLoader); if (sizeToLoadWidth) { printf(" sizeToLoadWidth=%d,%d", sizeToLoadWidth->width, sizeToLoadWidth->height); } else { printf(" autoImageSize=%s needIntrinsicImageSize=%s", mFlags.mAutoImageSize ? "yes" : "no", mFlags.mNeedIntrinsicImageSize ? "yes" : "no"); } printf("\n"); #endif if (oldLoader != mImageLoader) { if (nsnull != oldLoader) { // Tell presentation context we are done with the old image loader aPresContext->StopLoadImage(mFrame, oldLoader); } } // Release the old image loader NS_IF_RELEASE(oldLoader); return rv; } void nsHTMLImageLoader::UpdateURLSpec(nsIPresContext* aPresContext, const nsString& aNewSpec) { SetURL(aNewSpec); // Start image loading with the previously computed size information StartLoadImage(aPresContext); } #define MINMAX(_value,_min,_max) \ ((_value) < (_min) \ ? (_min) \ : ((_value) > (_max) \ ? (_max) \ : (_value))) PRBool nsHTMLImageLoader::GetDesiredSize(nsIPresContext* aPresContext, const nsHTMLReflowState* aReflowState, nsHTMLReflowMetrics& aDesiredSize) { nscoord widthConstraint = NS_INTRINSICSIZE; nscoord heightConstraint = NS_INTRINSICSIZE; PRBool fixedContentWidth = PR_FALSE; PRBool fixedContentHeight = PR_FALSE; nscoord minWidth, maxWidth, minHeight, maxHeight; if (aReflowState) { // Determine whether the image has fixed content width widthConstraint = aReflowState->mComputedWidth; minWidth = aReflowState->mComputedMinWidth; maxWidth = aReflowState->mComputedMaxWidth; if (NS_INTRINSICSIZE != widthConstraint) { fixedContentWidth = PR_TRUE; } else if (mFlags.mHaveIntrinsicImageSize) { // At this point we know that the width value was not // constrained and that we now know the intrinsic size of the // image. // // The css2 spec states that if a min/max width value is // provided then it acts as a substitute value for the "width" // property, if it applies. Therefore, we will force the // fixedContentWidth flag to true in these cases. if ((0 != minWidth) || (NS_UNCONSTRAINEDSIZE != maxWidth)) { // Use the intrinsic image height for the min-max comparisons, // since the width property is "auto". widthConstraint = mIntrinsicImageSize.width; fixedContentWidth = PR_TRUE; } } // Determine whether the image has fixed content height heightConstraint = aReflowState->mComputedHeight; minHeight = aReflowState->mComputedMinHeight; maxHeight = aReflowState->mComputedMaxHeight; if (NS_UNCONSTRAINEDSIZE != heightConstraint) { fixedContentHeight = PR_TRUE; } else if (mFlags.mHaveIntrinsicImageSize) { // At this point we know that the height value was not // constrained and that we now know the intrinsic size of the // image. // // The css2 spec states that if a min/max height value is // provided then it acts as a substitute value for the "height" // property, if it applies. Therefore, we will force the // fixedContentHeight flag to true in these cases. if ((0 != minHeight) || (NS_UNCONSTRAINEDSIZE != maxHeight)) { // Use the intrinsic image height for the min-max comparisons, // since the height property is "auto". heightConstraint = mIntrinsicImageSize.height; fixedContentHeight = PR_TRUE; } } } else { minWidth = minHeight = 0; maxWidth = maxHeight = NS_UNCONSTRAINEDSIZE; } for (;;) { PRBool haveComputedSize = PR_FALSE; PRBool needIntrinsicImageSize = PR_FALSE; nscoord newWidth, newHeight; mFlags.mAutoImageSize = PR_FALSE; mFlags.mNeedSizeNotification = PR_FALSE; if (fixedContentWidth) { if (fixedContentHeight) { newWidth = MINMAX(widthConstraint, minWidth, maxWidth); newHeight = MINMAX(heightConstraint, minHeight, maxHeight); haveComputedSize = PR_TRUE; } else { // We have a width, and an auto height. Compute height from // width once we have the intrinsic image size. newWidth = MINMAX(widthConstraint, minWidth, maxWidth); if (mFlags.mHaveIntrinsicImageSize) { float width = mIntrinsicImageSize.width ? (float) mIntrinsicImageSize.width : (float) mIntrinsicImageSize.height; // avoid divide by zero float height = (float) mIntrinsicImageSize.height; newHeight = (nscoord) NSToIntRound(newWidth * height / width); newHeight = MINMAX(newHeight, minHeight, maxHeight); haveComputedSize = PR_TRUE; } else { newHeight = 1; needIntrinsicImageSize = PR_TRUE; mFlags.mNeedSizeNotification = PR_TRUE; } } } else if (fixedContentHeight) { // We have a height, and an auto width. Compute width from height // once we have the intrinsic image size. newHeight = MINMAX(heightConstraint, minHeight, maxHeight); if (mFlags.mHaveIntrinsicImageSize) { float width = (float) mIntrinsicImageSize.width; float height = mIntrinsicImageSize.height ? (float) mIntrinsicImageSize.height : (float) mIntrinsicImageSize.width; // avoid divide by zero newWidth = (nscoord) NSToIntRound(newHeight * width / height); newWidth = MINMAX(newWidth, minWidth, maxWidth); haveComputedSize = PR_TRUE; } else { newWidth = 1; needIntrinsicImageSize = PR_TRUE; mFlags.mNeedSizeNotification = PR_TRUE; } } else { mFlags.mAutoImageSize = PR_TRUE; if (mFlags.mHaveIntrinsicImageSize) { newWidth = MINMAX(mIntrinsicImageSize.width, minWidth, maxWidth); newHeight = MINMAX(mIntrinsicImageSize.height, minHeight, maxHeight); haveComputedSize = PR_TRUE; } else { newWidth = 1; newHeight = 1; needIntrinsicImageSize = PR_TRUE; mFlags.mNeedSizeNotification = PR_TRUE; } } mFlags.mNeedIntrinsicImageSize = needIntrinsicImageSize; mFlags.mHaveComputedSize = haveComputedSize; mComputedImageSize.width = newWidth; mComputedImageSize.height = newHeight; #ifdef NOISY_IMAGE_LOADING nsFrame::ListTag(stdout, mFrame); printf(": %s%scomputedSize=%d,%d min=%d,%d max=%d,%d fixed=%s,%s\n", mFlags.mNeedIntrinsicImageSize ? "need-instrinsic-size " : "", mFlags.mHaveComputedSize ? "have-computed-size " : "", mComputedImageSize.width, mComputedImageSize.height, minWidth, minHeight, maxWidth, maxHeight, fixedContentWidth ? "yes" : "no", fixedContentHeight ? "yes" : "no"); #endif // Load the image at the desired size if ((0 != newWidth) && (0 != newHeight)) { // Make sure we squelch a callback to the client of this image // loader during a start-load-image. Its possible the image we // want is ready to go and will therefore fire a notification // during the StartLoadImage call. Since this routine is already // returning size information there is no point in passing on the // callbacks to the client. mFlags.mSquelchCallback = PR_TRUE; StartLoadImage(aPresContext); mFlags.mSquelchCallback = PR_FALSE; // See if we just got the intrinsic size if (mFlags.mNeedIntrinsicImageSize && mFlags.mHaveIntrinsicImageSize) { // We just learned our intrinisic size. Start over from the top... #ifdef NOISY_IMAGE_LOADING printf(" *** size arrived during StartLoadImage, looping...\n"); #endif continue; } } break; } aDesiredSize.width = mComputedImageSize.width; aDesiredSize.height = mComputedImageSize.height; if ((mFlags.mNeedIntrinsicImageSize && !mFlags.mHaveIntrinsicImageSize) || mFlags.mNeedSizeNotification) { return PR_FALSE; } return PR_TRUE; } PRBool nsHTMLImageLoader::GetLoadImageFailed() const { PRBool result = PR_FALSE; if (nsnull != mImageLoader) { // Ask the image loader whether the load failed PRUint32 loadStatus; mImageLoader->GetImageLoadStatus(&loadStatus); result = 0 != (loadStatus & NS_IMAGE_LOAD_STATUS_ERROR); return result; } result = mFlags.mLoadImageFailed ? PR_TRUE : PR_FALSE; return result; } PRUint32 nsHTMLImageLoader::GetLoadStatus() const { PRUint32 loadStatus = NS_IMAGE_LOAD_STATUS_NONE; if (mImageLoader) { mImageLoader->GetImageLoadStatus(&loadStatus); } return loadStatus; } #ifdef DEBUG // Note: this doesn't factor in: // -- the mBaseURL (it might be shared) // -- the mFrame (that will be counted elsewhere most likely) // -- the mClosure (we don't know what type it is) // -- the mImageLoader (it might be shared) void nsHTMLImageLoader::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const { PRUint32 sum = sizeof(*this) - sizeof(mURLSpec) - sizeof(mURL); PRUint32 ss; mURLSpec.SizeOf(aHandler, &ss); sum += ss; mURL.SizeOf(aHandler, &ss); sum += ss; *aResult = sum; } #endif