From d533375dd9a7b7829169920e25c39c069141ebe8 Mon Sep 17 00:00:00 2001 From: Joe Drew Date: Tue, 19 Aug 2008 16:28:28 -0400 Subject: [PATCH] Bug 430061 - replace imglib use of necko memory cache with an imglib-specific cache, r=pav --- modules/libpr0n/build/nsImageModule.cpp | 10 +- modules/libpr0n/src/Makefile.in | 1 - modules/libpr0n/src/imgCache.cpp | 357 ------ modules/libpr0n/src/imgCache.h | 82 -- modules/libpr0n/src/imgLoader.cpp | 1456 +++++++++++++++-------- modules/libpr0n/src/imgLoader.h | 224 +++- modules/libpr0n/src/imgRequest.cpp | 25 +- modules/libpr0n/src/imgRequest.h | 8 +- modules/libpr0n/test/Makefile.in | 2 +- modules/libpref/src/init/all.js | 7 + 10 files changed, 1242 insertions(+), 930 deletions(-) delete mode 100644 modules/libpr0n/src/imgCache.cpp delete mode 100644 modules/libpr0n/src/imgCache.h diff --git a/modules/libpr0n/build/nsImageModule.cpp b/modules/libpr0n/build/nsImageModule.cpp index a16da30b05eb..7f43bd1c9493 100644 --- a/modules/libpr0n/build/nsImageModule.cpp +++ b/modules/libpr0n/build/nsImageModule.cpp @@ -53,7 +53,6 @@ #include "nsXPCOMCID.h" #include "nsServiceManagerUtils.h" -#include "imgCache.h" #include "imgContainer.h" #include "imgLoader.h" #include "imgRequest.h" @@ -99,7 +98,6 @@ // objects that just require generic constructors -NS_GENERIC_FACTORY_CONSTRUCTOR(imgCache) NS_GENERIC_FACTORY_CONSTRUCTOR(imgContainer) NS_GENERIC_FACTORY_CONSTRUCTOR(imgLoader) NS_GENERIC_FACTORY_CONSTRUCTOR(imgRequestProxy) @@ -203,9 +201,9 @@ static NS_METHOD ImageUnregisterProc(nsIComponentManager *aCompMgr, static const nsModuleComponentInfo components[] = { { "image cache", - NS_IMGCACHE_CID, + NS_IMGLOADER_CID, "@mozilla.org/image/cache;1", - imgCacheConstructor, }, + imgLoaderConstructor, }, { "image container", NS_IMGCONTAINER_CID, "@mozilla.org/image/container;1", @@ -315,14 +313,14 @@ static const nsModuleComponentInfo components[] = PR_STATIC_CALLBACK(nsresult) imglib_Initialize(nsIModule* aSelf) { - imgCache::Init(); + imgLoader::InitCache(); return NS_OK; } PR_STATIC_CALLBACK(void) imglib_Shutdown(nsIModule* aSelf) { - imgCache::Shutdown(); + imgLoader::Shutdown(); } NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(nsImageLib2Module, components, diff --git a/modules/libpr0n/src/Makefile.in b/modules/libpr0n/src/Makefile.in index 6db5245b12c2..7296212c9127 100644 --- a/modules/libpr0n/src/Makefile.in +++ b/modules/libpr0n/src/Makefile.in @@ -61,7 +61,6 @@ REQUIRES = xpcom \ $(NULL) CPPSRCS = \ - imgCache.cpp \ imgContainer.cpp \ imgLoader.cpp \ imgRequest.cpp \ diff --git a/modules/libpr0n/src/imgCache.cpp b/modules/libpr0n/src/imgCache.cpp deleted file mode 100644 index 46161950292f..000000000000 --- a/modules/libpr0n/src/imgCache.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2001 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Stuart Parmenter - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "imgCache.h" - -#include "ImageLogging.h" - -#include "imgRequest.h" - -#include "nsXPIDLString.h" -#include "nsCOMPtr.h" -#include "nsIServiceManager.h" -#include "nsIMemory.h" -#include "nsIObserverService.h" - -#include "nsICache.h" -#include "nsICacheService.h" -#include "nsICacheSession.h" -#include "nsICacheEntryDescriptor.h" - -#include "nsIFile.h" -#include "nsIFileURL.h" - -NS_IMPL_ISUPPORTS3(imgCache, imgICache, nsIObserver, nsISupportsWeakReference) - -imgCache::imgCache() -{ - /* member initializers and constructor code */ -} - -imgCache::~imgCache() -{ - /* destructor code */ -} - -nsresult imgCache::Init() -{ - nsresult rv; - nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1", &rv); - if (NS_FAILED(rv)) - return rv; - - imgCache* cache = new imgCache(); - if(!cache) return NS_ERROR_OUT_OF_MEMORY; - - os->AddObserver(cache, "memory-pressure", PR_FALSE); - os->AddObserver(cache, "chrome-flush-skin-caches", PR_FALSE); - os->AddObserver(cache, "chrome-flush-caches", PR_FALSE); - - return NS_OK; -} - -/* void clearCache (in boolean chrome); */ -NS_IMETHODIMP imgCache::ClearCache(PRBool chrome) -{ - if (chrome) - return imgCache::ClearChromeImageCache(); - else - return imgCache::ClearImageCache(); -} - - -/* void removeEntry(in nsIURI uri); */ -NS_IMETHODIMP imgCache::RemoveEntry(nsIURI *uri) -{ - if (imgCache::Remove(uri)) - return NS_OK; - - return NS_ERROR_NOT_AVAILABLE; -} - -/* imgIRequest findEntry(in nsIURI uri); */ -NS_IMETHODIMP imgCache::FindEntryProperties(nsIURI *uri, nsIProperties **_retval) -{ - PRBool expired; - // This is an owning reference that must be released. - imgRequest *request = nsnull; - nsCOMPtr entry; - - // addrefs request - imgCache::Get(uri, &expired, &request, getter_AddRefs(entry)); - - *_retval = nsnull; - - if (request) { - *_retval = request->Properties(); - NS_ADDREF(*_retval); - } - - NS_IF_RELEASE(request); - - return NS_OK; -} - - -static nsCOMPtr gSession = nsnull; -static nsCOMPtr gChromeSession = nsnull; - -void GetCacheSession(nsIURI *aURI, nsICacheSession **_retval) -{ - NS_ASSERTION(aURI, "Null URI!"); - - PRBool isChrome = PR_FALSE; - aURI->SchemeIs("chrome", &isChrome); - - if (gSession && !isChrome) { - *_retval = gSession; - NS_ADDREF(*_retval); - return; - } - - if (gChromeSession && isChrome) { - *_retval = gChromeSession; - NS_ADDREF(*_retval); - return; - } - - nsCOMPtr cacheService(do_GetService("@mozilla.org/network/cache-service;1")); - if (!cacheService) { - NS_WARNING("Unable to get the cache service"); - return; - } - - nsCOMPtr newSession; - cacheService->CreateSession(isChrome ? "image-chrome" : "image", - nsICache::STORE_IN_MEMORY, - nsICache::NOT_STREAM_BASED, - getter_AddRefs(newSession)); - - if (!newSession) { - NS_WARNING("Unable to create a cache session"); - return; - } - - if (isChrome) - gChromeSession = newSession; - else { - gSession = newSession; - gSession->SetDoomEntriesIfExpired(PR_FALSE); - } - - *_retval = newSession; - NS_ADDREF(*_retval); -} - - -void imgCache::Shutdown() -{ - gSession = nsnull; - gChromeSession = nsnull; -} - - -nsresult imgCache::ClearChromeImageCache() -{ - if (!gChromeSession) - return NS_OK; - - return gChromeSession->EvictEntries(); -} - -nsresult imgCache::ClearImageCache() -{ - if (!gSession) - return NS_OK; - - return gSession->EvictEntries(); -} - - - -PRBool imgCache::Put(nsIURI *aKey, imgRequest *request, nsICacheEntryDescriptor **aEntry) -{ - LOG_STATIC_FUNC(gImgLog, "imgCache::Put"); - - nsresult rv; - - nsCOMPtr ses; - GetCacheSession(aKey, getter_AddRefs(ses)); - if (!ses) return PR_FALSE; - - nsCAutoString spec; - aKey->GetAsciiSpec(spec); - - nsCOMPtr entry; - - rv = ses->OpenCacheEntry(spec, nsICache::ACCESS_WRITE, nsICache::BLOCKING, getter_AddRefs(entry)); - - if (NS_FAILED(rv) || !entry) - return PR_FALSE; - - nsCOMPtr sup = reinterpret_cast(request); - entry->SetCacheElement(sup); - - entry->MarkValid(); - - // If file, force revalidation on expiration - PRBool isFile; - aKey->SchemeIs("file", &isFile); - if (isFile) - entry->SetMetaDataElement("MustValidateIfExpired", "true"); - - *aEntry = entry; - NS_ADDREF(*aEntry); - - return PR_TRUE; -} - -static PRUint32 -SecondsFromPRTime(PRTime prTime) -{ - PRInt64 microSecondsPerSecond, intermediateResult; - PRUint32 seconds; - - LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC); - LL_DIV(intermediateResult, prTime, microSecondsPerSecond); - LL_L2UI(seconds, intermediateResult); - return seconds; -} - - -PRBool imgCache::Get(nsIURI *aKey, PRBool *aHasExpired, imgRequest **aRequest, nsICacheEntryDescriptor **aEntry) -{ - LOG_STATIC_FUNC(gImgLog, "imgCache::Get"); - - nsresult rv; - - nsCOMPtr ses; - GetCacheSession(aKey, getter_AddRefs(ses)); - if (!ses) return PR_FALSE; - - nsCAutoString spec; - aKey->GetAsciiSpec(spec); - - nsCOMPtr entry; - - rv = ses->OpenCacheEntry(spec, nsICache::ACCESS_READ, nsICache::BLOCKING, getter_AddRefs(entry)); - - if (NS_FAILED(rv) || !entry) - return PR_FALSE; - - if (aHasExpired) { - PRUint32 expirationTime; - rv = entry->GetExpirationTime(&expirationTime); - if (NS_FAILED(rv) || (expirationTime <= SecondsFromPRTime(PR_Now()))) { - *aHasExpired = PR_TRUE; - } else { - *aHasExpired = PR_FALSE; - } - // Special treatment for file URLs - entry has expired if file has changed - nsCOMPtr fileUrl(do_QueryInterface(aKey)); - if (fileUrl) { - PRUint32 lastModTime; - entry->GetLastModified(&lastModTime); - - nsCOMPtr theFile; - rv = fileUrl->GetFile(getter_AddRefs(theFile)); - if (NS_SUCCEEDED(rv)) { - PRInt64 fileLastMod; - rv = theFile->GetLastModifiedTime(&fileLastMod); - if (NS_SUCCEEDED(rv)) { - // nsIFile uses millisec, NSPR usec - PRInt64 one_thousand = LL_INIT(0, 1000); - LL_MUL(fileLastMod, fileLastMod, one_thousand); - *aHasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; - } - } - } - } - - nsCOMPtr sup; - entry->GetCacheElement(getter_AddRefs(sup)); - - *aRequest = reinterpret_cast(sup.get()); - NS_IF_ADDREF(*aRequest); - - *aEntry = entry; - NS_ADDREF(*aEntry); - - return PR_TRUE; -} - - -PRBool imgCache::Remove(nsIURI *aKey) -{ - LOG_STATIC_FUNC(gImgLog, "imgCache::Remove"); - if (!aKey) return PR_FALSE; - - nsresult rv; - nsCOMPtr ses; - GetCacheSession(aKey, getter_AddRefs(ses)); - if (!ses) return PR_FALSE; - - nsCAutoString spec; - aKey->GetAsciiSpec(spec); - - nsCOMPtr entry; - - rv = ses->OpenCacheEntry(spec, nsICache::ACCESS_READ, nsICache::BLOCKING, getter_AddRefs(entry)); - - if (NS_FAILED(rv) || !entry) - return PR_FALSE; - - entry->Doom(); - - return PR_TRUE; -} - - -NS_IMETHODIMP -imgCache::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aSomeData) -{ - if (strcmp(aTopic, "memory-pressure") == 0) { - ClearCache(PR_FALSE); - ClearCache(PR_TRUE); - } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 || - strcmp(aTopic, "chrome-flush-caches") == 0) { - ClearCache(PR_TRUE); - } - return NS_OK; -} diff --git a/modules/libpr0n/src/imgCache.h b/modules/libpr0n/src/imgCache.h deleted file mode 100644 index 8d4b259fb3e0..000000000000 --- a/modules/libpr0n/src/imgCache.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 2001 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Stuart Parmenter - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "imgICache.h" -#include "nsIObserver.h" -#include "nsWeakReference.h" - -#include "prtypes.h" - -class imgRequest; -class nsIURI; -class nsICacheEntryDescriptor; - -#define NS_IMGCACHE_CID \ -{ /* fb4fd28a-1dd1-11b2-8391-e14242c59a41 */ \ - 0xfb4fd28a, \ - 0x1dd1, \ - 0x11b2, \ - {0x83, 0x91, 0xe1, 0x42, 0x42, 0xc5, 0x9a, 0x41} \ -} - -class imgCache : public imgICache, - public nsIObserver, - public nsSupportsWeakReference -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_IMGICACHE - NS_DECL_NSIOBSERVER - - imgCache(); - virtual ~imgCache(); - - static nsresult Init(); - - static void Shutdown(); // for use by the factory - - /* additional members */ - static PRBool Put(nsIURI *aKey, imgRequest *request, nsICacheEntryDescriptor **aEntry); - static PRBool Get(nsIURI *aKey, PRBool *aHasExpired, imgRequest **aRequest, nsICacheEntryDescriptor **aEntry); - static PRBool Remove(nsIURI *aKey); - - static nsresult ClearChromeImageCache(); - static nsresult ClearImageCache(); -}; - diff --git a/modules/libpr0n/src/imgLoader.cpp b/modules/libpr0n/src/imgLoader.cpp index dc35239be756..0a21a201bd3b 100644 --- a/modules/libpr0n/src/imgLoader.cpp +++ b/modules/libpr0n/src/imgLoader.cpp @@ -44,15 +44,18 @@ #include "nsNetUtil.h" #include "nsIHttpChannel.h" #include "nsICachingChannel.h" +#include "nsIObserverService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" #include "nsIProxyObjectManager.h" #include "nsIServiceManager.h" +#include "nsIFileURL.h" #include "nsThreadUtils.h" #include "nsXPIDLString.h" #include "nsCRT.h" #include "netCore.h" -#include "imgCache.h" #include "imgRequest.h" #include "imgRequestProxy.h" @@ -103,37 +106,36 @@ static void PrintImageDecoders() } #endif -NS_IMPL_ISUPPORTS2(imgLoader, imgILoader, nsIContentSniffer) - -imgLoader::imgLoader() +static PRBool NewRequestAndEntry(nsIURI *uri, imgRequest **request, imgCacheEntry **entry) { - /* member initializers and constructor code */ -#ifdef DEBUG_pavlov - PrintImageDecoders(); -#endif + // If file, force revalidation on expiration + PRBool isFile; + uri->SchemeIs("file", &isFile); + + *request = new imgRequest(); + if (!*request) + return PR_FALSE; + + *entry = new imgCacheEntry(*request, /* mustValidateIfExpired = */ isFile); + if (!*entry) { + delete *request; + return PR_FALSE; + } + + NS_ADDREF(*request); + NS_ADDREF(*entry); + + return PR_TRUE; } -imgLoader::~imgLoader() -{ - /* destructor code */ -} - -#define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \ - nsIRequest::LOAD_FROM_CACHE) - -#define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \ - nsIRequest::VALIDATE_NEVER | \ - nsIRequest::VALIDATE_ONCE_PER_SESSION) - - -static PRBool RevalidateEntry(nsICacheEntryDescriptor *aEntry, +static PRBool ShouldRevalidateEntry(imgCacheEntry *aEntry, nsLoadFlags aFlags, PRBool aHasExpired) { PRBool bValidateEntry = PR_FALSE; - NS_ASSERTION(!(aFlags & nsIRequest::LOAD_BYPASS_CACHE), - "MUST not revalidate when BYPASS_CACHE is specified."); + if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) + return PR_FALSE; if (aFlags & nsIRequest::VALIDATE_ALWAYS) { bValidateEntry = PR_TRUE; @@ -151,13 +153,7 @@ static PRBool RevalidateEntry(nsICacheEntryDescriptor *aEntry, if (aFlags & (nsIRequest::VALIDATE_NEVER | nsIRequest::VALIDATE_ONCE_PER_SESSION)) { - nsXPIDLCString value; - - aEntry->GetMetaDataElement("MustValidateIfExpired", - getter_Copies(value)); - if (PL_strcmp(value, "true")) { - bValidateEntry = PR_TRUE; - } + bValidateEntry = aEntry->GetMustValidateIfExpired(); } // // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise, @@ -171,7 +167,6 @@ static PRBool RevalidateEntry(nsICacheEntryDescriptor *aEntry, return bValidateEntry; } - static nsresult NewImageChannel(nsIChannel **aResult, nsIURI *aURI, nsIURI *aInitialDocumentURI, @@ -238,437 +233,151 @@ static nsresult NewImageChannel(nsIChannel **aResult, return NS_OK; } -/* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */ - -NS_IMETHODIMP imgLoader::LoadImage(nsIURI *aURI, - nsIURI *aInitialDocumentURI, - nsIURI *aReferrerURI, - nsILoadGroup *aLoadGroup, - imgIDecoderObserver *aObserver, - nsISupports *aCX, - nsLoadFlags aLoadFlags, - nsISupports *cacheKey, - imgIRequest *aRequest, - imgIRequest **_retval) +static PRUint32 SecondsFromPRTime(PRTime prTime) { - NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); - - // CreateNewProxyForRequest treats _retval as inout - null out - // to make sure the passed value doesn't affect the behavior of - // this method - *_retval = nsnull; - - if (!aURI) - return NS_ERROR_NULL_POINTER; - -#if defined(PR_LOGGING) - nsCAutoString spec; - aURI->GetAsciiSpec(spec); - LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", spec.get()); -#endif - - // This is an owning reference that must be released. - imgRequest *request = nsnull; - - nsresult rv; - nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; - - // Get the default load flags from the loadgroup (if possible)... - if (aLoadGroup) { - aLoadGroup->GetLoadFlags(&requestFlags); - } - // - // Merge the default load flags with those passed in via aLoadFlags. - // Currently, *only* the caching, validation and background load flags - // are merged... - // - // The flags in aLoadFlags take precidence over the default flags! - // - if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { - // Override the default caching flags... - requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | - (aLoadFlags & LOAD_FLAGS_CACHE_MASK); - } - if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { - // Override the default validation flags... - requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | - (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); - } - if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { - // Propagate background loading... - requestFlags |= nsIRequest::LOAD_BACKGROUND; - } - - nsCOMPtr entry; - PRBool bCanCacheRequest = PR_TRUE; - PRBool bHasExpired = PR_FALSE; - PRBool bValidateRequest = PR_FALSE; - - // XXX For now ignore the cache key. We will need it in the future - // for correctly dealing with image load requests that are a result - // of post data. - imgCache::Get(aURI, &bHasExpired, - &request, getter_AddRefs(entry)); // addrefs request - - if (request && entry) { - - // request's null out their mCacheEntry when all proxy's are removed. - // If we are about to add a new one back, go ahead and re-set the cache - // entry so it can be used. - if (!request->mCacheEntry) { - request->mCacheEntry = entry; - } - - // If the request's loadId is the same as the aCX, then it is ok to use - // this one because it has already been validated for this context. - // - // XXX: nsnull seems to be a 'special' key value that indicates that NO - // validation is required. - // - void *key = (void*)aCX; - if (request->mLoadId != key) { - - // LOAD_BYPASS_CACHE - Always re-fetch - if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { - // doom cache entry; be sure to break the reference cycle between the - // request and cache entry. NOTE: the request might not own the cache - // entry at this point, so we explicitly Doom |entry| just in case. - entry->Doom(); - entry = nsnull; - request->RemoveFromCache(); - NS_RELEASE(request); - } else { - // Determine whether the cache entry must be revalidated... - bValidateRequest = RevalidateEntry(entry, requestFlags, bHasExpired); - - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("imgLoader::LoadImage validating cache entry. " - "bValidateRequest = %d", bValidateRequest)); - } - - } -#if defined(PR_LOGGING) - else if (!key) { - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("imgLoader::LoadImage BYPASSING cache validation for %s " - "because of NULL LoadID", spec.get())); - } -#endif - } - - // - // Get the current thread... This is used as a cacheId to prevent - // sharing requests which are being loaded across multiple threads... - // - void *cacheId = NS_GetCurrentThread(); - if (request && !request->IsReusable(cacheId)) { - // - // The current request is still being loaded and lives on a different - // event queue. - // - // Since its event queue is NOT active, do not reuse this imgRequest !! - // Instead, force a new request to be created but DO NOT allow it to be - // cached! - // - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("[this=%p] imgLoader::LoadImage -- DANGER!! Unable to use cached " - "imgRequest [request=%p]\n", this, request)); - - entry = nsnull; - NS_RELEASE(request); - - bCanCacheRequest = PR_FALSE; - } - - // - // Time to load the request... There are 3 possible cases: - // ======================================================= - // 1. There is no cached request (ie. nothing was found in the cache). - // - // 2. There is a cached request that must be validated. - // - // 3. There is a valid cached request. - // - if (request && bValidateRequest) { - /* Case #2: the cache request cache must be revalidated. */ - LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache hit| must validate"); - - // now we need to insert a new channel request object inbetween the real - // request and the proxy that basically delays loading the image until it - // gets a 304 or figures out that this needs to be a new request - - if (request->mValidator) { - rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, - requestFlags, aRequest, _retval); - - if (*_retval) - request->mValidator->AddProxy(static_cast(*_retval)); - - NS_RELEASE(request); - return rv; - - } else { - nsCOMPtr newChannel; - rv = NewImageChannel(getter_AddRefs(newChannel), - aURI, - aInitialDocumentURI, - aReferrerURI, - aLoadGroup, - requestFlags); - if (NS_FAILED(rv)) { - NS_RELEASE(request); - return NS_ERROR_FAILURE; - } - - nsCOMPtr cacheChan(do_QueryInterface(newChannel)); - - if (cacheChan) { - // since this channel supports nsICachingChannel, we can ask it - // to only stream us data if the data comes off the net. - PRUint32 loadFlags; - if (NS_SUCCEEDED(newChannel->GetLoadFlags(&loadFlags))) - newChannel->SetLoadFlags(loadFlags | nsICachingChannel::LOAD_ONLY_IF_MODIFIED); - - } - nsCOMPtr req; - rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, - requestFlags, aRequest, getter_AddRefs(req)); - if (NS_FAILED(rv)) { - NS_RELEASE(request); - return rv; - } - - imgCacheValidator *hvc = new imgCacheValidator(request, aCX); - if (!hvc) { - NS_RELEASE(request); - return NS_ERROR_OUT_OF_MEMORY; - } - - NS_ADDREF(hvc); - request->mValidator = hvc; - - hvc->AddProxy(static_cast - (static_cast(req.get()))); - - rv = newChannel->AsyncOpen(static_cast(hvc), nsnull); - if (NS_SUCCEEDED(rv)) - NS_ADDREF(*_retval = req.get()); - - NS_RELEASE(hvc); - - NS_RELEASE(request); - - return rv; - } - } else if (!request) { - /* Case #1: no request from the cache. do a new load */ - LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|"); - - nsCOMPtr newChannel; - rv = NewImageChannel(getter_AddRefs(newChannel), - aURI, - aInitialDocumentURI, - aReferrerURI, - aLoadGroup, - requestFlags); - if (NS_FAILED(rv)) - return NS_ERROR_FAILURE; - - NS_NEWXPCOM(request, imgRequest); - if (!request) return NS_ERROR_OUT_OF_MEMORY; - - NS_ADDREF(request); - - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request)); - - // Add the new request into the imgCache if its cachable... - if (bCanCacheRequest) { - imgCache::Put(aURI, request, getter_AddRefs(entry)); - } - - // Create a loadgroup for this new channel. This way if the channel - // is redirected, we'll have a way to cancel the resulting channel. - nsCOMPtr loadGroup = - do_CreateInstance(NS_LOADGROUP_CONTRACTID); - newChannel->SetLoadGroup(loadGroup); - - request->Init(aURI, loadGroup, entry, cacheId, aCX); - - // create the proxy listener - ProxyListener *pl = new ProxyListener(static_cast(request)); - if (!pl) { - request->Cancel(NS_ERROR_OUT_OF_MEMORY); - NS_RELEASE(request); - return NS_ERROR_OUT_OF_MEMORY; - } - - NS_ADDREF(pl); - - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this)); - - nsresult openRes; - openRes = newChannel->AsyncOpen(static_cast(pl), nsnull); - - NS_RELEASE(pl); - - if (NS_FAILED(openRes)) { - PR_LOG(gImgLog, PR_LOG_DEBUG, - ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n", - this, openRes)); - request->Cancel(openRes); - NS_RELEASE(request); - return openRes; - } - - } else { - /* Case #3: request found in cache. use it */ - // XXX: Should this be executed if an expired cache entry does not have a caching channel?? - LOG_MSG_WITH_PARAM(gImgLog, - "imgLoader::LoadImage |cache hit|", "request", request); - - // Update the request's LoadId - request->SetLoadId(aCX); - } - - LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request."); - - rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, - requestFlags, aRequest, _retval); - - imgRequestProxy *proxy = (imgRequestProxy *)*_retval; - - // Note that it's OK to add here even if the request is done. If it is, - // it'll send a OnStopRequest() to the proxy in NotifyProxyListener and the - // proxy will be removed from the loadgroup. - proxy->AddToLoadGroup(); - - // if we have to validate the request, then we will send the - // notifications later. - if (!bValidateRequest) { - request->NotifyProxyListener(proxy); - } - - NS_RELEASE(request); - - return rv; + return PRUint32(PRInt64(prTime) / PRInt64(PR_USEC_PER_SEC)); } -/* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */ -NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval) +imgCacheEntry::imgCacheEntry(imgRequest *request, PRBool mustValidateIfExpired /* = PR_FALSE */) + : mRequest(request), + mDataSize(0), + mTouchedTime(SecondsFromPRTime(PR_Now())), + mExpiryTime(0), + mMustValidateIfExpired(mustValidateIfExpired), + mEvicted(PR_FALSE) +{} + +void imgCacheEntry::TouchWithSize(PRInt32 diff) { - NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer"); + LOG_SCOPE(gImgLog, "imgCacheEntry::TouchWithSize"); - // CreateNewProxyForRequest treats _retval as inout - null out - // to make sure the passed value doesn't affect the behavior of - // this method - *_retval = nsnull; + mTouchedTime = SecondsFromPRTime(PR_Now()); - nsresult rv; - imgRequest *request = nsnull; - - nsCOMPtr uri; - channel->GetURI(getter_AddRefs(uri)); - - nsCOMPtr entry; - PRBool bHasExpired; - - imgCache::Get(uri, &bHasExpired, &request, getter_AddRefs(entry)); // addrefs request - - nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; - - channel->GetLoadFlags(&requestFlags); - - if (request) { - PRBool bUseCacheCopy = PR_TRUE; - - // LOAD_BYPASS_CACHE - Always re-fetch - if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { - bUseCacheCopy = PR_FALSE; - } - else if (RevalidateEntry(entry, requestFlags, bHasExpired)) { - nsCOMPtr cacheChan(do_QueryInterface(channel)); - if (cacheChan) { - cacheChan->IsFromCache(&bUseCacheCopy); - } else { - bUseCacheCopy = PR_FALSE; - } - } - - if (!bUseCacheCopy) { - // doom cache entry; be sure to break the reference cycle between the - // request and cache entry. NOTE: the request might not own the cache - // entry at this point, so we explicitly Doom |entry| just in case. - entry->Doom(); - entry = nsnull; - request->RemoveFromCache(); - NS_RELEASE(request); - } + if (!Evicted()) { + nsCOMPtr uri; + mRequest->GetURI(getter_AddRefs(uri)); + imgLoader::CacheEntriesChanged(uri, diff); } - - nsCOMPtr loadGroup; - channel->GetLoadGroup(getter_AddRefs(loadGroup)); - - if (request) { - // we have this in our cache already.. cancel the current (document) load - - channel->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED); // this should fire an OnStopRequest - - *listener = nsnull; // give them back a null nsIStreamListener - } else { - // - // Get the current Thread... This is used as a cacheId to prevent - // sharing requests which are being loaded across multiple threads... - // - nsIThread *thread = NS_GetCurrentThread(); - - NS_NEWXPCOM(request, imgRequest); - if (!request) return NS_ERROR_OUT_OF_MEMORY; - - NS_ADDREF(request); - - imgCache::Put(uri, request, getter_AddRefs(entry)); - - // XXX(darin): I'm not sure that using the original URI is correct here. - // Perhaps we should use the same URI that indexes the cache? Or, perhaps - // the cache should use the original URI? See bug 89419. - nsCOMPtr originalURI; - channel->GetOriginalURI(getter_AddRefs(originalURI)); - request->Init(originalURI, channel, entry, thread, aCX); - - ProxyListener *pl = new ProxyListener(static_cast(request)); - if (!pl) { - NS_RELEASE(request); - return NS_ERROR_OUT_OF_MEMORY; - } - - NS_ADDREF(pl); - - *listener = static_cast(pl); - NS_ADDREF(*listener); - - NS_RELEASE(pl); - } - - // XXX: It looks like the wrong load flags are being passed in... - requestFlags &= 0xFFFF; - - rv = CreateNewProxyForRequest(request, loadGroup, aObserver, - requestFlags, nsnull, _retval); - request->NotifyProxyListener(static_cast(*_retval)); - - NS_RELEASE(request); - - return rv; } +void imgCacheEntry::Touch(PRBool updateTime /* = PR_TRUE */) +{ + LOG_SCOPE(gImgLog, "imgCacheEntry::Touch"); -nsresult -imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, - imgIDecoderObserver *aObserver, - nsLoadFlags aLoadFlags, imgIRequest *aProxyRequest, - imgIRequest **_retval) + if (updateTime) + mTouchedTime = SecondsFromPRTime(PR_Now()); + + if (!Evicted()) { + nsCOMPtr uri; + mRequest->GetURI(getter_AddRefs(uri)); + imgLoader::CacheEntriesChanged(uri); + } +} + +imgCacheQueue::imgCacheQueue() + : mDirty(PR_FALSE), + mSize(0) +{} + +void imgCacheQueue::EvictAll() +{ + for (queueContainer::iterator it = mQueue.begin(); it != mQueue.end(); ++it) + (*it)->SetEvicted(PR_TRUE); + + mQueue.clear(); +} + +void imgCacheQueue::UpdateSize(PRInt32 diff) +{ + mSize += diff; +} + +PRUint32 imgCacheQueue::GetSize() const +{ + return mSize; +} + +#include + +void imgCacheQueue::Remove(imgCacheEntry *entry) +{ + queueContainer::iterator it = find(mQueue.begin(), mQueue.end(), entry); + if (it != mQueue.end()) { + mSize -= (*it)->GetDataSize(); + mQueue.erase(it); + MarkDirty(); + } +} + +void imgCacheQueue::Push(imgCacheEntry *entry) +{ + mSize += entry->GetDataSize(); + + nsRefPtr refptr(entry); + mQueue.push_back(refptr); + MarkDirty(); +} + +already_AddRefed imgCacheQueue::Pop() +{ + if (mQueue.empty()) + return nsnull; + if (IsDirty()) + Refresh(); + + nsRefPtr entry = mQueue[0]; + std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); + mQueue.pop_back(); + + mSize -= entry->GetDataSize(); + imgCacheEntry *ret = entry; + NS_ADDREF(ret); + return ret; +} + +void imgCacheQueue::Refresh() +{ + std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries); + mDirty = PR_FALSE; +} + +void imgCacheQueue::MarkDirty() +{ + mDirty = PR_TRUE; +} + +PRBool imgCacheQueue::IsDirty() +{ + return mDirty; +} + +PRUint32 imgCacheQueue::GetNumElements() const +{ + return mQueue.size(); +} + +imgCacheQueue::iterator imgCacheQueue::begin() +{ + return mQueue.begin(); +} +imgCacheQueue::const_iterator imgCacheQueue::begin() const +{ + return mQueue.begin(); +} + +imgCacheQueue::iterator imgCacheQueue::end() +{ + return mQueue.end(); +} +imgCacheQueue::const_iterator imgCacheQueue::end() const +{ + return mQueue.end(); +} + +nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, + nsLoadFlags aLoadFlags, imgIRequest *aProxyRequest, + imgIRequest **_retval) { LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest", "imgRequest", aRequest); @@ -698,16 +407,831 @@ imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGro return rv; } - if (*_retval) { - (*_retval)->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED); - NS_RELEASE(*_retval); - } // transfer reference to caller *_retval = static_cast(proxyRequest); return NS_OK; } +class imgCacheObserver : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER +private: + imgLoader mLoader; +}; + +NS_IMPL_ISUPPORTS1(imgCacheObserver, nsIObserver) + +NS_IMETHODIMP +imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aSomeData) +{ + if (strcmp(aTopic, "memory-pressure") == 0) { + mLoader.ClearCache(PR_FALSE); + mLoader.ClearCache(PR_TRUE); + } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 || + strcmp(aTopic, "chrome-flush-caches") == 0) { + mLoader.ClearCache(PR_TRUE); + } + return NS_OK; +} + +class imgCacheExpirationTracker : public nsExpirationTracker +{ + enum { TIMEOUT_SECONDS = 10 }; +public: + imgCacheExpirationTracker(); + +protected: + void NotifyExpired(imgCacheEntry *entry); +}; + +imgCacheExpirationTracker::imgCacheExpirationTracker() + : nsExpirationTracker(TIMEOUT_SECONDS * 1000) +{} + +void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry) +{ + // We can be called multiple times on the same entry. Don't do work multiple + // times. + if (!entry->Evicted()) + imgLoader::RemoveFromCache(entry); + + imgLoader::VerifyCacheSizes(); +} + +imgCacheObserver *gCacheObserver; +imgCacheExpirationTracker *gCacheTracker; + +imgLoader::imgCacheTable imgLoader::sCache; +imgCacheQueue imgLoader::sCacheQueue; + +imgLoader::imgCacheTable imgLoader::sChromeCache; +imgCacheQueue imgLoader::sChromeCacheQueue; + +PRFloat64 imgLoader::sCacheTimeWeight; +PRUint32 imgLoader::sCacheMaxSize; + +NS_IMPL_ISUPPORTS4(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference) + +imgLoader::imgLoader() +{ + /* member initializers and constructor code */ +#ifdef DEBUG_pavlov + PrintImageDecoders(); +#endif +} + +imgLoader::~imgLoader() +{ + /* destructor code */ +} + +void imgLoader::VerifyCacheSizes() +{ + PRUint32 queuesize = sCacheQueue.GetNumElements() + sChromeCacheQueue.GetNumElements(); + PRUint32 cachesize = sCache.Count() + sChromeCache.Count(); + PRUint32 trackersize = 0; + for (nsExpirationTracker::Iterator it(gCacheTracker); it.Next(); ) + trackersize++; + NS_ASSERTION(queuesize == cachesize, "Queue and cache sizes out of sync!"); + NS_ASSERTION(queuesize == trackersize, "Queue and tracker sizes out of sync!"); +} + +imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI) +{ + PRBool chrome = PR_FALSE; + aURI->SchemeIs("chrome", &chrome); + if (chrome) + return sChromeCache; + else + return sCache; +} + +imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI) +{ + PRBool chrome = PR_FALSE; + aURI->SchemeIs("chrome", &chrome); + if (chrome) + return sChromeCacheQueue; + else + return sCacheQueue; +} + +nsresult imgLoader::InitCache() +{ + nsresult rv; + nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1", &rv); + if (NS_FAILED(rv)) + return rv; + + gCacheObserver = new imgCacheObserver(); + if (!gCacheObserver) + return NS_ERROR_OUT_OF_MEMORY; + + os->AddObserver(gCacheObserver, "memory-pressure", PR_FALSE); + os->AddObserver(gCacheObserver, "chrome-flush-skin-caches", PR_FALSE); + os->AddObserver(gCacheObserver, "chrome-flush-caches", PR_FALSE); + + gCacheTracker = new imgCacheExpirationTracker(); + if (!gCacheTracker) + return NS_ERROR_OUT_OF_MEMORY; + + if (!sCache.Init()) + return NS_ERROR_OUT_OF_MEMORY; + if (!sChromeCache.Init()) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + PRInt32 timeweight; + rv = prefs->GetIntPref("image.cache.timeweight", &timeweight); + if (NS_SUCCEEDED(rv)) + sCacheTimeWeight = timeweight / 1000.0; + else + sCacheTimeWeight = 0.5; + + PRInt32 cachesize; + rv = prefs->GetIntPref("image.cache.size", &cachesize); + if (NS_SUCCEEDED(rv)) + sCacheMaxSize = cachesize; + else + sCacheMaxSize = 5 * 1024 * 1024; + + return NS_OK; +} + +/* void clearCache (in boolean chrome); */ +NS_IMETHODIMP imgLoader::ClearCache(PRBool chrome) +{ + if (chrome) + return ClearChromeImageCache(); + else + return ClearImageCache(); +} + +/* void removeEntry(in nsIURI uri); */ +NS_IMETHODIMP imgLoader::RemoveEntry(nsIURI *uri) +{ + if (RemoveFromCache(uri)) + return NS_OK; + + return NS_ERROR_NOT_AVAILABLE; +} + +/* imgIRequest findEntry(in nsIURI uri); */ +NS_IMETHODIMP imgLoader::FindEntryProperties(nsIURI *uri, nsIProperties **_retval) +{ + nsRefPtr entry; + nsCAutoString spec; + imgCacheTable &cache = GetCache(uri); + + uri->GetSpec(spec); + *_retval = nsnull; + + if (cache.Get(spec, getter_AddRefs(entry)) && entry) { + gCacheTracker->MarkUsed(entry); + nsRefPtr request = getter_AddRefs(entry->GetRequest()); + if (request) { + *_retval = request->Properties(); + NS_ADDREF(*_retval); + } + } + + return NS_OK; +} + +void imgLoader::Shutdown() +{ + // TODO: Should this release the caches? +} + +nsresult imgLoader::ClearChromeImageCache() +{ + return EvictEntries(sChromeCache, sChromeCacheQueue); +} + +nsresult imgLoader::ClearImageCache() +{ + return EvictEntries(sCache, sCacheQueue); +} + +PRBool imgLoader::PutIntoCache(nsIURI *key, imgCacheEntry *entry) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::PutIntoCache"); + + imgCacheTable &cache = GetCache(key); + + nsCAutoString spec; + key->GetSpec(spec); + + // Check to see if this request already exists in the cache and is being + // loaded on a different thread. If so, don't allow this entry to be added to + // the cache. + nsRefPtr tmpCacheEntry; + if (cache.Get(spec, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) { + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache", nsnull)); + nsRefPtr tmpRequest = getter_AddRefs(tmpCacheEntry->GetRequest()); + void *cacheId = NS_GetCurrentThread(); + + if (!tmpRequest->IsReusable(cacheId)) + return PR_FALSE; + + gCacheTracker->MarkUsed(tmpCacheEntry); + + return PR_TRUE; + } + + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("[this=%p] imgLoader::PutIntoCache -- Element NOT already in the cache", nsnull)); + + if (!cache.Put(spec, entry)) + return PR_FALSE; + + imgCacheQueue &queue = GetCacheQueue(key); + queue.Push(entry); + + gCacheTracker->AddObject(entry); + + CheckCacheLimits(cache, queue); + + return PR_TRUE; +} + +void imgLoader::CacheEntriesChanged(nsIURI *uri, PRInt32 sizediff /* = 0 */) +{ + imgCacheQueue &queue = GetCacheQueue(uri); + queue.MarkDirty(); + queue.UpdateSize(sizediff); +} + +void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue) +{ + if (queue.GetNumElements() == 0) + NS_ASSERTION(queue.GetSize() == 0, + "imgLoader::CheckCacheLimits -- incorrect cache size"); + + // Remove entries from the cache until we're back under our desired size. + while (queue.GetSize() >= sCacheMaxSize) { + // Remove the first entry in the queue. + nsRefPtr entry(queue.Pop()); + + NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer"); + + if (entry) + RemoveFromCache(entry); + } +} + +PRBool imgLoader::ValidateRequestWithNewChannel(imgRequest *request, + nsIURI *aURI, + nsIURI *aInitialDocumentURI, + nsIURI *aReferrerURI, + nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, + nsISupports *aCX, + nsLoadFlags aLoadFlags, + imgIRequest *aExistingRequest, + imgIRequest **aProxyRequest) +{ + // now we need to insert a new channel request object inbetween the real + // request and the proxy that basically delays loading the image until it + // gets a 304 or figures out that this needs to be a new request + + nsresult rv; + + if (request->mValidator) { + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + aLoadFlags, aExistingRequest, + reinterpret_cast(aProxyRequest)); + + if (*aProxyRequest) + request->mValidator->AddProxy(static_cast(*aProxyRequest)); + + return NS_SUCCEEDED(rv); + + } else { + nsCOMPtr newChannel; + rv = NewImageChannel(getter_AddRefs(newChannel), + aURI, + aInitialDocumentURI, + aReferrerURI, + aLoadGroup, + aLoadFlags); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + nsCOMPtr cacheChan(do_QueryInterface(newChannel)); + + if (cacheChan) { + // since this channel supports nsICachingChannel, we can ask it + // to only stream us data if the data comes off the net. + PRUint32 loadFlags; + if (NS_SUCCEEDED(newChannel->GetLoadFlags(&loadFlags))) + newChannel->SetLoadFlags(loadFlags | nsICachingChannel::LOAD_ONLY_IF_MODIFIED); + } + + nsCOMPtr req; + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + aLoadFlags, aExistingRequest, getter_AddRefs(req)); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + imgCacheValidator *hvc = new imgCacheValidator(request, aCX); + if (!hvc) { + return PR_FALSE; + } + + NS_ADDREF(hvc); + request->mValidator = hvc; + + hvc->AddProxy(static_cast + (static_cast(req.get()))); + + rv = newChannel->AsyncOpen(static_cast(hvc), nsnull); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(*aProxyRequest = req.get()); + + NS_RELEASE(hvc); + + return NS_SUCCEEDED(rv); + } +} + +PRBool imgLoader::ValidateEntry(imgCacheEntry *aEntry, + nsIURI *aURI, + nsIURI *aInitialDocumentURI, + nsIURI *aReferrerURI, + nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, + nsISupports *aCX, + nsLoadFlags aLoadFlags, + PRBool aCanMakeNewChannel, + imgIRequest *aExistingRequest, + imgIRequest **aProxyRequest) +{ + LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry"); + + PRBool hasExpired; + PRUint32 expirationTime = aEntry->GetExpiryTime(); + if (expirationTime <= SecondsFromPRTime(PR_Now())) { + hasExpired = PR_TRUE; + } else { + hasExpired = PR_FALSE; + } + + nsresult rv; + + // Special treatment for file URLs - aEntry has expired if file has changed + nsCOMPtr fileUrl(do_QueryInterface(aURI)); + if (fileUrl) { + PRUint32 lastModTime = aEntry->GetTouchedTime(); + + nsCOMPtr theFile; + rv = fileUrl->GetFile(getter_AddRefs(theFile)); + if (NS_SUCCEEDED(rv)) { + PRInt64 fileLastMod; + rv = theFile->GetLastModifiedTime(&fileLastMod); + if (NS_SUCCEEDED(rv)) { + // nsIFile uses millisec, NSPR usec + fileLastMod *= 1000; + hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime; + } + } + } + + nsRefPtr request(aEntry->GetRequest()); + + if (!request) + return PR_FALSE; + + PRBool validateRequest = PR_FALSE; + + // If the request's loadId is the same as the aCX, then it is ok to use + // this one because it has already been validated for this context. + // + // XXX: nsnull seems to be a 'special' key value that indicates that NO + // validation is required. + // + void *key = (void *)aCX; + if (request->mLoadId != key) { + // Determine whether the cache aEntry must be revalidated... + validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired); + + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("imgLoader::ValidateEntry validating cache entry. " + "validateRequest = %d", validateRequest)); + } +#if defined(PR_LOGGING) + else if (!key) { + nsCAutoString spec; + aURI->GetSpec(spec); + + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("imgLoader::ValidateEntry BYPASSING cache validation for %s " + "because of NULL LoadID", spec.get())); + } +#endif + + // + // Get the current thread... This is used as a cacheId to prevent + // sharing requests which are being loaded across multiple threads... + // + void *cacheId = NS_GetCurrentThread(); + if (!request->IsReusable(cacheId)) { + // + // The current request is still being loaded and lives on a different + // event queue. + // + // Since its event queue is NOT active, do not reuse this imgRequest. + // PutIntoCache() will also ensure that we don't cache it. + // + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("imgLoader::ValidateEntry -- DANGER!! Unable to use cached " + "imgRequest [request=%p]\n", address_of(request))); + + return PR_FALSE; + } + + if (validateRequest && aCanMakeNewChannel) { + LOG_SCOPE(gImgLog, "imgLoader::ValidateRequest |cache hit| must validate"); + + return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI, + aReferrerURI, aLoadGroup, aObserver, + aCX, aLoadFlags, aExistingRequest, + aProxyRequest); + } + + return !validateRequest; +} + + +PRBool imgLoader::RemoveFromCache(nsIURI *aKey) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache uri"); + if (!aKey) return PR_FALSE; + + imgCacheTable &cache = GetCache(aKey); + imgCacheQueue &queue = GetCacheQueue(aKey); + + nsCAutoString spec; + aKey->GetSpec(spec); + + nsRefPtr entry; + if (cache.Get(spec, getter_AddRefs(entry)) && entry) { + gCacheTracker->RemoveObject(entry); + cache.Remove(spec); + queue.Remove(entry); + entry->SetEvicted(PR_TRUE); + return PR_TRUE; + } + else + return PR_FALSE; +} + +PRBool imgLoader::RemoveFromCache(imgCacheEntry *entry) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry"); + PRBool ret = PR_FALSE; + nsRefPtr request(getter_AddRefs(entry->GetRequest())); + if (request) { + nsCOMPtr key; + if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(key))) && key) + ret = RemoveFromCache(key); + } + + return ret; +} + +nsresult imgLoader::EvictEntries(imgCacheTable &aCacheToClear, imgCacheQueue &aQueueToClear) +{ + LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries"); + for (imgCacheQueue::iterator it = aQueueToClear.begin(); it != aQueueToClear.end(); ++it) + if (!RemoveFromCache(*it)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +#define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \ + nsIRequest::LOAD_FROM_CACHE) + +#define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \ + nsIRequest::VALIDATE_NEVER | \ + nsIRequest::VALIDATE_ONCE_PER_SESSION) + + +/* imgIRequest loadImage (in nsIURI aURI, in nsIURI initialDocumentURI, in nsILoadGroup aLoadGroup, in imgIDecoderObserver aObserver, in nsISupports aCX, in nsLoadFlags aLoadFlags, in nsISupports cacheKey, in imgIRequest aRequest); */ + +NS_IMETHODIMP imgLoader::LoadImage(nsIURI *aURI, + nsIURI *aInitialDocumentURI, + nsIURI *aReferrerURI, + nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, + nsISupports *aCX, + nsLoadFlags aLoadFlags, + nsISupports *aCacheKey, + imgIRequest *aRequest, + imgIRequest **_retval) +{ + VerifyCacheSizes(); + + NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer"); + + if (!aURI) + return NS_ERROR_NULL_POINTER; + +#if defined(PR_LOGGING) + nsCAutoString spec; + aURI->GetSpec(spec); + LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI", spec.get()); +#endif + + *_retval = nsnull; + + // This is an owning reference that must be released. + imgRequest *request = nsnull; + + nsresult rv; + nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; + + // Get the default load flags from the loadgroup (if possible)... + if (aLoadGroup) { + aLoadGroup->GetLoadFlags(&requestFlags); + } + // + // Merge the default load flags with those passed in via aLoadFlags. + // Currently, *only* the caching, validation and background load flags + // are merged... + // + // The flags in aLoadFlags take precedence over the default flags! + // + if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) { + // Override the default caching flags... + requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) | + (aLoadFlags & LOAD_FLAGS_CACHE_MASK); + } + if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) { + // Override the default validation flags... + requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) | + (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK); + } + if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) { + // Propagate background loading... + requestFlags |= nsIRequest::LOAD_BACKGROUND; + } + + nsRefPtr entry; + + // If we're bypassing the cache, we are guaranteed to want a new cache entry, + // since we're going to do a new load. + if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) { + RemoveFromCache(aURI); + } else { + // Look in the cache for our URI, and then validate it. + // XXX For now ignore aCacheKey. We will need it in the future + // for correctly dealing with image load requests that are a result + // of post data. + imgCacheTable &cache = GetCache(aURI); + nsCAutoString spec; + + aURI->GetSpec(spec); + + if (cache.Get(spec, getter_AddRefs(entry)) && entry) { + gCacheTracker->MarkUsed(entry); + + if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI, aLoadGroup, aObserver, aCX, + requestFlags, PR_TRUE, aRequest, _retval)) { + nsRefPtr req(entry->GetRequest()); + request = req; + NS_ADDREF(request); + + entry->Touch(); +#ifdef DEBUG_joe + printf("CACHEGET: %d %s %d\n", time(NULL), spec.get(), entry->GetDataSize()); +#endif + } + else + entry = nsnull; + } + } + + // If we didn't get a cache hit, we need to load from the network. + if (!request) { + LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|"); + + nsCOMPtr newChannel; + rv = NewImageChannel(getter_AddRefs(newChannel), + aURI, + aInitialDocumentURI, + aReferrerURI, + aLoadGroup, + requestFlags); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + if (!NewRequestAndEntry(aURI, &request, getter_AddRefs(entry))) + return NS_ERROR_OUT_OF_MEMORY; + + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request)); + + // Create a loadgroup for this new channel. This way if the channel + // is redirected, we'll have a way to cancel the resulting channel. + nsCOMPtr loadGroup = + do_CreateInstance(NS_LOADGROUP_CONTRACTID); + newChannel->SetLoadGroup(loadGroup); + + void *cacheId = NS_GetCurrentThread(); + request->Init(aURI, loadGroup, entry, cacheId, aCX); + + // create the proxy listener + ProxyListener *pl = new ProxyListener(static_cast(request)); + if (!pl) { + request->Cancel(NS_ERROR_OUT_OF_MEMORY); + NS_RELEASE(request); + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(pl); + + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this)); + + nsresult openRes = newChannel->AsyncOpen(static_cast(pl), nsnull); + + NS_RELEASE(pl); + + if (NS_FAILED(openRes)) { + PR_LOG(gImgLog, PR_LOG_DEBUG, + ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n", + this, openRes)); + request->Cancel(openRes); + NS_RELEASE(request); + return openRes; + } + + // Try to add the new request into the cache. + PutIntoCache(aURI, entry); + + // If we did get a cache hit, use it. + } else { + // XXX: Should this be executed if an expired cache entry does not have a caching channel?? + LOG_MSG_WITH_PARAM(gImgLog, + "imgLoader::LoadImage |cache hit|", "request", request); + + // Update the request's LoadId + request->SetLoadId(aCX); + } + + // If we didn't get a proxy when validating the cache entry, we need to create one. + if (!*_retval) { + LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request."); + + rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver, + requestFlags, aRequest, _retval); + imgRequestProxy *proxy = static_cast(*_retval); + + // Note that it's OK to add here even if the request is done. If it is, + // it'll send a OnStopRequest() to the proxy in NotifyProxyListener and the + // proxy will be removed from the loadgroup. + proxy->AddToLoadGroup(); + + request->NotifyProxyListener(proxy); + + NS_RELEASE(request); + + return rv; + } + + NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value"); + + return NS_OK; +} + +/* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */ +NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval) +{ + NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer"); + + nsresult rv; + imgRequest *request = nsnull; + + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + + nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL; + channel->GetLoadFlags(&requestFlags); + + nsRefPtr entry; + + if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) { + RemoveFromCache(uri); + } else { + // Look in the cache for our URI, and then validate it. + // XXX For now ignore aCacheKey. We will need it in the future + // for correctly dealing with image load requests that are a result + // of post data. + imgCacheTable &cache = GetCache(uri); + nsCAutoString spec; + + uri->GetSpec(spec); + + if (cache.Get(spec, getter_AddRefs(entry)) && entry) { + gCacheTracker->MarkUsed(entry); + + // We don't want to kick off another network load. So we ask + // ValidateEntry to only do validation without creating a new proxy. If + // it says that the entry isn't valid any more, we'll only use the entry + // we're getting if the channel is loading from the cache anyways. + // + // XXX -- should this be changed? it's pretty much verbatim from the old + // code, but seems nonsensical. + if (ValidateEntry(entry, uri, nsnull, nsnull, nsnull, aObserver, aCX, + requestFlags, PR_FALSE, nsnull, nsnull)) { + nsRefPtr req(entry->GetRequest()); + request = req; + NS_ADDREF(request); + } else { + nsCOMPtr cacheChan(do_QueryInterface(channel)); + PRBool bUseCacheCopy; + + if (cacheChan) + cacheChan->IsFromCache(&bUseCacheCopy); + else + bUseCacheCopy = PR_FALSE; + + if (!bUseCacheCopy) + entry = nsnull; + else { + nsRefPtr req = entry->GetRequest(); + request = req; + NS_ADDREF(request); + } + } + } + } + + nsCOMPtr loadGroup; + channel->GetLoadGroup(getter_AddRefs(loadGroup)); + + if (request) { + // we have this in our cache already.. cancel the current (document) load + + channel->Cancel(NS_IMAGELIB_ERROR_LOAD_ABORTED); // this should fire an OnStopRequest + + *listener = nsnull; // give them back a null nsIStreamListener + } else { + + // Get the current Thread... This is used as a cacheId to prevent + // sharing requests which are being loaded across multiple threads... + nsIThread *thread = NS_GetCurrentThread(); + + NewRequestAndEntry(uri, &request, getter_AddRefs(entry)); + + // XXX(darin): I'm not sure that using the original URI is correct here. + // Perhaps we should use the same URI that indexes the cache? Or, perhaps + // the cache should use the original URI? See bug 89419. + nsCOMPtr originalURI; + channel->GetOriginalURI(getter_AddRefs(originalURI)); + request->Init(originalURI, channel, entry, thread, aCX); + + ProxyListener *pl = new ProxyListener(static_cast(request)); + if (!pl) { + NS_RELEASE(request); + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(pl); + + *listener = static_cast(pl); + NS_ADDREF(*listener); + + NS_RELEASE(pl); + + // Try to add the new request into the cache. + PutIntoCache(uri, entry); + } + + // XXX: It looks like the wrong load flags are being passed in... + requestFlags &= 0xFFFF; + + rv = CreateNewProxyForRequest(request, loadGroup, aObserver, + requestFlags, nsnull, _retval); + request->NotifyProxyListener(static_cast(*_retval)); + + NS_RELEASE(request); + + return rv; +} + + NS_IMETHODIMP imgLoader::SupportImageWithMimeType(const char* aMimeType, PRBool *_retval) { *_retval = PR_FALSE; @@ -877,16 +1401,14 @@ NS_IMETHODIMP ProxyListener::OnDataAvailable(nsIRequest *aRequest, nsISupports * return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count); } - - - - /** * http validate class. check a channel for a 304 */ NS_IMPL_ISUPPORTS2(imgCacheValidator, nsIStreamListener, nsIRequestObserver) +imgLoader imgCacheValidator::sImgLoader; + imgCacheValidator::imgCacheValidator(imgRequest *request, void *aContext) : mContext(aContext) { @@ -938,9 +1460,10 @@ NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupport return NS_OK; } } + // fun stuff. nsCOMPtr channel(do_QueryInterface(aRequest)); - nsCOMPtr entry; + nsRefPtr entry; nsCOMPtr uri; // Doom the old request's cache entry @@ -952,11 +1475,9 @@ NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupport NS_RELEASE(mRequest); // assigns null imgRequest *request; - NS_NEWXPCOM(request, imgRequest); - if (!request) return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(request); - imgCache::Put(uri, request, getter_AddRefs(entry)); + if (!NewRequestAndEntry(uri, &request, getter_AddRefs(entry))) + return NS_ERROR_OUT_OF_MEMORY; // XXX(darin): I'm not sure that using the original URI is correct here. // Perhaps we should use the same URI that indexes the cache? Or, perhaps @@ -980,6 +1501,9 @@ NS_IMETHODIMP imgCacheValidator::OnStartRequest(nsIRequest *aRequest, nsISupport request->NotifyProxyListener(proxy); } + // Try to add the new request into the cache. + sImgLoader.PutIntoCache(uri, entry); + NS_RELEASE(request); if (!mDestListener) diff --git a/modules/libpr0n/src/imgLoader.h b/modules/libpr0n/src/imgLoader.h index e264198637d6..a1c2107a6037 100644 --- a/modules/libpr0n/src/imgLoader.h +++ b/modules/libpr0n/src/imgLoader.h @@ -38,7 +38,14 @@ * ***** END LICENSE BLOCK ***** */ #include "imgILoader.h" +#include "imgICache.h" +#include "nsWeakReference.h" #include "nsIContentSniffer.h" +#include "nsRefPtrHashtable.h" +#include "nsExpirationTracker.h" +#include "nsAutoPtr.h" +#include "prtypes.h" +#include "imgRequest.h" #ifdef LOADER_THREADSAFE #include "prlock.h" @@ -50,6 +57,115 @@ class imgIRequest; class imgIDecoderObserver; class nsILoadGroup; +class imgCacheEntry +{ +public: + imgCacheEntry(imgRequest *request, PRBool mustValidateIfExpired = PR_FALSE); + + nsrefcnt AddRef() + { + NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "illegal refcnt"); + NS_ASSERT_OWNINGTHREAD(imgCacheEntry); + ++mRefCnt; + return mRefCnt; + } + + nsrefcnt Release() + { + NS_PRECONDITION(0 != mRefCnt, "dup release"); + NS_ASSERT_OWNINGTHREAD(imgCacheEntry); + --mRefCnt; + if (mRefCnt == 0) { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return mRefCnt; + } + + PRUint32 GetDataSize() const + { + return mDataSize; + } + void SetDataSize(PRUint32 aDataSize) + { + PRInt32 oldsize = mDataSize; + mDataSize = aDataSize; + TouchWithSize(mDataSize - oldsize); + } + + PRInt32 GetTouchedTime() const + { + return mTouchedTime; + } + void SetTouchedTime(PRInt32 time) + { + mTouchedTime = time; + Touch(/* updateTime = */ PR_FALSE); + } + + PRInt32 GetExpiryTime() const + { + return mExpiryTime; + } + void SetExpiryTime(PRInt32 aExpiryTime) + { + mExpiryTime = aExpiryTime; + Touch(); + } + + PRBool GetMustValidateIfExpired() const + { + return mMustValidateIfExpired; + } + void SetMustValidateIfExpired(PRBool aValidate) + { + mMustValidateIfExpired = aValidate; + Touch(); + } + + already_AddRefed GetRequest() const + { + imgRequest *req = mRequest; + NS_ADDREF(req); + return req; + } + + PRBool Evicted() const + { + return mEvicted; + } + + nsExpirationState *GetExpirationState() + { + return &mExpirationState; + } + +private: // methods + friend class imgLoader; + friend class imgCacheQueue; + void Touch(PRBool updateTime = PR_TRUE); + void TouchWithSize(PRInt32 diff); + void SetEvicted(PRBool evict) + { + mEvicted = evict; + } + +private: // data + nsAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + + nsRefPtr mRequest; + PRUint32 mDataSize; + PRInt32 mTouchedTime; + PRInt32 mExpiryTime; + nsExpirationState mExpirationState; + PRBool mMustValidateIfExpired; + PRBool mEvicted; +}; + +#include + #define NS_IMGLOADER_CID \ { /* 9f6a0d2e-1dd1-11b2-a5b8-951f13c846f7 */ \ 0x9f6a0d2e, \ @@ -58,23 +174,125 @@ class nsILoadGroup; {0xa5, 0xb8, 0x95, 0x1f, 0x13, 0xc8, 0x46, 0xf7} \ } -class imgLoader : public imgILoader, public nsIContentSniffer +class imgCacheQueue +{ +public: + imgCacheQueue(); + void EvictAll(); + void Remove(imgCacheEntry *); + void Push(imgCacheEntry *); + void MarkDirty(); + PRBool IsDirty(); + already_AddRefed Pop(); + void Refresh(); + PRUint32 GetSize() const; + void UpdateSize(PRInt32 diff); + PRUint32 GetNumElements() const; + typedef std::vector > queueContainer; + typedef queueContainer::iterator iterator; + typedef queueContainer::const_iterator const_iterator; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + +private: + queueContainer mQueue; + PRBool mDirty; + PRUint32 mSize; +}; + +class imgLoader : public imgILoader, + public nsIContentSniffer, + public imgICache, + public nsSupportsWeakReference { public: NS_DECL_ISUPPORTS NS_DECL_IMGILOADER NS_DECL_NSICONTENTSNIFFER + NS_DECL_IMGICACHE imgLoader(); virtual ~imgLoader(); static nsresult GetMimeTypeFromContent(const char* aContents, PRUint32 aLength, nsACString& aContentType); -private: + static void Shutdown(); // for use by the factory + + static nsresult ClearChromeImageCache(); + static nsresult ClearImageCache(); + + static nsresult InitCache(); + + static PRBool RemoveFromCache(nsIURI *aKey); + static PRBool RemoveFromCache(imgCacheEntry *entry); + + static PRBool PutIntoCache(nsIURI *key, imgCacheEntry *entry); + + // Returns true if |one| is less than |two| + // This mixes units in the worst way, but provides reasonable results. + inline static bool CompareCacheEntries(const nsRefPtr &one, + const nsRefPtr &two) + { + if (!one) + return false; + if (!two) + return true; + + const PRFloat64 sizeweight = 1.0 - sCacheTimeWeight; + PRInt32 diffsize = PRInt32(two->GetDataSize()) - PRInt32(one->GetDataSize()); + PRInt32 difftime = one->GetTouchedTime() - two->GetTouchedTime(); + return difftime * sCacheTimeWeight + diffsize * sizeweight < 0; + } + + static void VerifyCacheSizes(); + +private: // methods + + + PRBool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aKey, + nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI, + nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, nsISupports *aCX, + nsLoadFlags aLoadFlags, PRBool aCanMakeNewChannel, + imgIRequest *aExistingRequest, + imgIRequest **aProxyRequest); + PRBool ValidateRequestWithNewChannel(imgRequest *request, nsIURI *aURI, + nsIURI *aInitialDocumentURI, + nsIURI *aReferrerURI, + nsILoadGroup *aLoadGroup, + imgIDecoderObserver *aObserver, + nsISupports *aCX, nsLoadFlags aLoadFlags, + imgIRequest *aExistingRequest, + imgIRequest **aProxyRequest); + nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup, imgIDecoderObserver *aObserver, nsLoadFlags aLoadFlags, imgIRequest *aRequestProxy, imgIRequest **_retval); + + + typedef nsRefPtrHashtable imgCacheTable; + + static nsresult EvictEntries(imgCacheTable &aCacheToClear, imgCacheQueue &aQueueToClear); + + static imgCacheTable &GetCache(nsIURI *aURI); + static imgCacheQueue &GetCacheQueue(nsIURI *aURI); + static void CacheEntriesChanged(nsIURI *aURI, PRInt32 sizediff = 0); + static void CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue); + +private: // data + friend class imgCacheEntry; + + static imgCacheTable sCache; + static imgCacheQueue sCacheQueue; + + static imgCacheTable sChromeCache; + static imgCacheQueue sChromeCacheQueue; + static PRFloat64 sCacheTimeWeight; + static PRUint32 sCacheMaxSize; }; @@ -128,4 +346,6 @@ private: nsCOMArray mProxies; void *mContext; + + static imgLoader sImgLoader; }; diff --git a/modules/libpr0n/src/imgRequest.cpp b/modules/libpr0n/src/imgRequest.cpp index c0a62b75d85c..ff06a45eb6e0 100644 --- a/modules/libpr0n/src/imgRequest.cpp +++ b/modules/libpr0n/src/imgRequest.cpp @@ -63,6 +63,8 @@ #include "nsISupportsPrimitives.h" #include "nsIScriptSecurityManager.h" +#include "nsICacheVisitor.h" + #include "nsString.h" #include "nsXPIDLString.h" #include "plstr.h" // PL_strcasestr(...) @@ -92,7 +94,7 @@ imgRequest::~imgRequest() nsresult imgRequest::Init(nsIURI *aURI, nsIRequest *aRequest, - nsICacheEntryDescriptor *aCacheEntry, + imgCacheEntry *aCacheEntry, void *aCacheId, void *aLoadId) { @@ -326,7 +328,7 @@ void imgRequest::RemoveFromCache() LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache"); if (mCacheEntry) { - mCacheEntry->Doom(); + imgLoader::RemoveFromCache(mURI); mCacheEntry = nsnull; } } @@ -513,13 +515,19 @@ NS_IMETHODIMP imgRequest::OnStopFrame(imgIRequest *request, mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE; if (mCacheEntry) { - PRUint32 cacheSize = 0; - mCacheEntry->GetDataSize(&cacheSize); + PRUint32 cacheSize = mCacheEntry->GetDataSize(); PRUint32 imageSize = 0; frame->GetImageDataLength(&imageSize); mCacheEntry->SetDataSize(cacheSize + imageSize); + +#ifdef DEBUG_joe + nsCAutoString url; + mURI->GetSpec(url); + + printf("CACHEPUT: %d %s %d\n", time(NULL), url.get(), cacheSize + imageSize); +#endif } nsTObserverArray::ForwardIterator iter(mObservers); @@ -633,7 +641,7 @@ NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt entryDesc->GetExpirationTime(&expiration); /* set the expiration time on our entry */ - mCacheEntry->SetExpirationTime(expiration); + mCacheEntry->SetExpiryTime(expiration); } } } @@ -664,9 +672,7 @@ NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt } } - if (bMustRevalidate) { - mCacheEntry->SetMetaDataElement("MustValidateIfExpired", "true"); - } + mCacheEntry->SetMustValidateIfExpired(bMustRevalidate); } } @@ -739,9 +745,6 @@ NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, return NS_OK; } - - - /* prototype for this defined below */ static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment, PRUint32 toOffset, PRUint32 count, PRUint32 *writeCount); diff --git a/modules/libpr0n/src/imgRequest.h b/modules/libpr0n/src/imgRequest.h index d45de097def7..25bcc0db76ad 100644 --- a/modules/libpr0n/src/imgRequest.h +++ b/modules/libpr0n/src/imgRequest.h @@ -46,7 +46,6 @@ #include "imgIDecoder.h" #include "imgIDecoderObserver.h" -#include "nsICacheEntryDescriptor.h" #include "nsIContentSniffer.h" #include "nsIRequest.h" #include "nsIProperties.h" @@ -63,6 +62,7 @@ class imgCacheValidator; class imgRequestProxy; +class imgCacheEntry; enum { onStartRequest = PR_BIT(0), @@ -86,7 +86,7 @@ public: nsresult Init(nsIURI *aURI, nsIRequest *aRequest, - nsICacheEntryDescriptor *aCacheEntry, + imgCacheEntry *aCacheEntry, void *aCacheId, void *aLoadId); @@ -109,10 +109,10 @@ public: nsresult GetNetworkStatus(); private: + friend class imgCacheEntry; friend class imgRequestProxy; friend class imgLoader; friend class imgCacheValidator; - friend class imgCache; inline void SetLoadId(void *aLoadId) { mLoadId = aLoadId; @@ -168,7 +168,7 @@ private: PRUint32 mState; nsCString mContentType; - nsCOMPtr mCacheEntry; /* we hold on to this to this so long as we have observers */ + nsRefPtr mCacheEntry; /* we hold on to this to this so long as we have observers */ void *mCacheId; diff --git a/modules/libpr0n/test/Makefile.in b/modules/libpr0n/test/Makefile.in index 7c93898f3300..8efbb5921112 100644 --- a/modules/libpr0n/test/Makefile.in +++ b/modules/libpr0n/test/Makefile.in @@ -49,7 +49,7 @@ MODULE = test_libpr0n XPCSHELL_TESTS = unit #ifdef MOZ_MOCHITEST -#DIRS += mochitest +DIRS += mochitest #endif include $(topsrcdir)/config/rules.mk diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index aa6cdeac96e8..a9588964ff8a 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -2618,3 +2618,10 @@ pref("browser.zoom.full", false); pref("zoom.minPercent", 30); pref("zoom.maxPercent", 300); pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3"); + +// Image cache prefs +// The maximum size, in bytes, of the decoded images we cache +pref("image.cache.size", 5242880); +// A weight, from 0-1000, to place on time when comparing to size. +// Size is given a weight of 1000 - timeweight. +pref("image.cache.timeweight", 500);