diff --git a/mozilla/content/base/src/nsContentSink.cpp b/mozilla/content/base/src/nsContentSink.cpp index 1b01830d868..ee743beb838 100644 --- a/mozilla/content/base/src/nsContentSink.cpp +++ b/mozilla/content/base/src/nsContentSink.cpp @@ -661,7 +661,12 @@ nsContentSink::ProcessLink(nsIContent* aElement, PRBool hasPrefetch = (linkTypes.IndexOf(NS_LITERAL_STRING("prefetch")) != -1); // prefetch href if relation is "next" or "prefetch" if (hasPrefetch || linkTypes.IndexOf(NS_LITERAL_STRING("next")) != -1) { - PrefetchHref(aHref, hasPrefetch); + PrefetchHref(aHref, hasPrefetch, PR_FALSE); + } + + // fetch href into the offline cache if relation is "offline-resource" + if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) { + PrefetchHref(aHref, PR_TRUE, PR_TRUE); } // is it a stylesheet link? @@ -746,7 +751,9 @@ nsContentSink::ProcessMETATag(nsIContent* aContent) void -nsContentSink::PrefetchHref(const nsAString &aHref, PRBool aExplicit) +nsContentSink::PrefetchHref(const nsAString &aHref, + PRBool aExplicit, + PRBool aOffline) { // // SECURITY CHECK: disable prefetching from mailnews! @@ -789,12 +796,14 @@ nsContentSink::PrefetchHref(const nsAString &aHref, PRBool aExplicit) charset.IsEmpty() ? nsnull : PromiseFlatCString(charset).get(), mDocumentBaseURI); if (uri) { - prefetchService->PrefetchURI(uri, mDocumentURI, aExplicit); + if (aOffline) + prefetchService->PrefetchURIForOfflineUse(uri, mDocumentURI, aExplicit); + else + prefetchService->PrefetchURI(uri, mDocumentURI, aExplicit); } } } - void nsContentSink::ScrollToRef() { diff --git a/mozilla/content/base/src/nsContentSink.h b/mozilla/content/base/src/nsContentSink.h index b9dce7472f6..8c24c4b8384 100644 --- a/mozilla/content/base/src/nsContentSink.h +++ b/mozilla/content/base/src/nsContentSink.h @@ -165,7 +165,7 @@ protected: const nsSubstring& aType, const nsSubstring& aMedia); - void PrefetchHref(const nsAString &aHref, PRBool aExplicit); + void PrefetchHref(const nsAString &aHref, PRBool aExplicit, PRBool aOffline); void ScrollToRef(); nsresult RefreshIfEnabled(nsIViewManager* vm); diff --git a/mozilla/content/html/document/src/nsHTMLContentSink.cpp b/mozilla/content/html/document/src/nsHTMLContentSink.cpp index 1cbbcb19ea7..da3f2dc1c25 100644 --- a/mozilla/content/html/document/src/nsHTMLContentSink.cpp +++ b/mozilla/content/html/document/src/nsHTMLContentSink.cpp @@ -2996,7 +2996,14 @@ HTMLContentSink::ProcessLINKTag(const nsIParserNode& aNode) nsAutoString hrefVal; element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); if (!hrefVal.IsEmpty()) { - PrefetchHref(hrefVal, hasPrefetch); + PrefetchHref(hrefVal, hasPrefetch, PR_FALSE); + } + } + if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) { + nsAutoString hrefVal; + element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); + if (!hrefVal.IsEmpty()) { + PrefetchHref(hrefVal, PR_TRUE, PR_TRUE); } } } diff --git a/mozilla/modules/libpref/src/init/all.js b/mozilla/modules/libpref/src/init/all.js index 8cc5bc3d467..b28450c53fe 100644 --- a/mozilla/modules/libpref/src/init/all.js +++ b/mozilla/modules/libpref/src/init/all.js @@ -67,6 +67,11 @@ pref("browser.cache.memory.enable", true); pref("browser.cache.disk_cache_ssl", false); // 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically pref("browser.cache.check_doc_frequency", 3); + +pref("browser.cache.offline.enable", true); +// offline cache capacity in kilobytes +pref("browser.cache.offline.capacity", 51200); + // Fastback caching - if this pref is negative, then we calculate the number // of content viewers to cache based on the amount of available memory. pref("browser.sessionhistory.max_total_viewers", -1); diff --git a/mozilla/netwerk/base/public/nsICachingChannel.idl b/mozilla/netwerk/base/public/nsICachingChannel.idl index 044099606a4..a1c2ba0d8fd 100644 --- a/mozilla/netwerk/base/public/nsICachingChannel.idl +++ b/mozilla/netwerk/base/public/nsICachingChannel.idl @@ -50,7 +50,7 @@ interface nsIFile; * 3) Support for uniquely identifying cached data in cases when the URL * is insufficient (e.g., HTTP form submission). */ -[scriptable, uuid(b1f95f5e-ee05-4434-9d34-89a935d7feef)] +[scriptable, uuid(855bcc4d-0987-45b6-b138-3bf0bf407703)] interface nsICachingChannel : nsISupports { /** @@ -97,6 +97,14 @@ interface nsICachingChannel : nsISupports */ attribute boolean cacheAsFile; + /** + * Specifies whether or not the data should be placed in the offline cache, + * in addition to normal memory/disk caching. This may fail if the offline + * cache is not present. The value of this attribute should be set before + * opening the channel. + */ + attribute boolean cacheForOfflineUse; + /** * Get the "file" where the cached data can be found. This is valid for * as long as a reference to the cache token is held. This may return diff --git a/mozilla/netwerk/base/src/nsIOService.h b/mozilla/netwerk/base/src/nsIOService.h index c8ccf1acf65..e2a3bce2fe2 100644 --- a/mozilla/netwerk/base/src/nsIOService.h +++ b/mozilla/netwerk/base/src/nsIOService.h @@ -106,6 +106,8 @@ public: return mContentSniffers.GetEntries(); } + PRBool IsOffline() { return mOffline; } + private: // These shouldn't be called directly: // - construct using GetInstance diff --git a/mozilla/netwerk/cache/public/nsICache.idl b/mozilla/netwerk/cache/public/nsICache.idl index 1b6802bb69f..cdda653a2b5 100644 --- a/mozilla/netwerk/cache/public/nsICache.idl +++ b/mozilla/netwerk/cache/public/nsICache.idl @@ -143,11 +143,14 @@ interface nsICache * storage (ie. typically on a system's hard disk). * STORE_ON_DISK_AS_FILE - Requires the cache entry to reside in persistent * storage, and in a separate file. + * STORE_OFFLINE - Requires the cache entry to reside in persistent, + * reliable storage for offline use. */ const nsCacheStoragePolicy STORE_ANYWHERE = 0; const nsCacheStoragePolicy STORE_IN_MEMORY = 1; const nsCacheStoragePolicy STORE_ON_DISK = 2; const nsCacheStoragePolicy STORE_ON_DISK_AS_FILE = 3; + const nsCacheStoragePolicy STORE_OFFLINE = 4; /** * All entries for a cache session are stored as streams of data or diff --git a/mozilla/netwerk/cache/src/Makefile.in b/mozilla/netwerk/cache/src/Makefile.in index 6c73cc43854..ee0d800330a 100644 --- a/mozilla/netwerk/cache/src/Makefile.in +++ b/mozilla/netwerk/cache/src/Makefile.in @@ -88,6 +88,8 @@ CPPSRCS += \ endif endif +LOCAL_INCLUDES=-I$(srcdir)/../../base/src + include $(topsrcdir)/config/config.mk diff --git a/mozilla/netwerk/cache/src/nsCacheEntry.h b/mozilla/netwerk/cache/src/nsCacheEntry.h index be42b5eb874..49b3dc0372f 100644 --- a/mozilla/netwerk/cache/src/nsCacheEntry.h +++ b/mozilla/netwerk/cache/src/nsCacheEntry.h @@ -192,6 +192,11 @@ public: (StoragePolicy() == nsICache::STORE_ON_DISK_AS_FILE); } + PRBool IsAllowedOffline() + { + return (StoragePolicy() == nsICache::STORE_OFFLINE); + } + nsCacheStoragePolicy StoragePolicy() { return (nsCacheStoragePolicy)(mFlags & eStoragePolicyMask); diff --git a/mozilla/netwerk/cache/src/nsCacheService.cpp b/mozilla/netwerk/cache/src/nsCacheService.cpp index 6f75cd4e9fd..8e52d1c1701 100644 --- a/mozilla/netwerk/cache/src/nsCacheService.cpp +++ b/mozilla/netwerk/cache/src/nsCacheService.cpp @@ -60,6 +60,7 @@ #include "nsIPrefBranch.h" #include "nsIPrefBranch2.h" #include "nsILocalFile.h" +#include "nsIOService.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsThreadUtils.h" @@ -82,6 +83,11 @@ #define DISK_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.disk.max_entry_size" #define DISK_CACHE_CAPACITY 51200 +#define OFFLINE_CACHE_ENABLE_PREF "browser.cache.offline.enable" +#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory" +#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity" +#define OFFLINE_CACHE_CAPACITY 51200 + #define MEMORY_CACHE_ENABLE_PREF "browser.cache.memory.enable" #define MEMORY_CACHE_CAPACITY_PREF "browser.cache.memory.capacity" #define MEMORY_CACHE_MAX_ENTRY_SIZE_PREF "browser.cache.memory.max_entry_size" @@ -97,6 +103,8 @@ public: : mHaveProfile(PR_FALSE) , mDiskCacheEnabled(PR_FALSE) , mDiskCacheCapacity(0) + , mOfflineCacheEnabled(PR_FALSE) + , mOfflineCacheCapacity(0) , mMemoryCacheEnabled(PR_TRUE) , mMemoryCacheCapacity(-1) { @@ -111,6 +119,10 @@ public: PRBool DiskCacheEnabled(); PRInt32 DiskCacheCapacity() { return mDiskCacheCapacity; } nsILocalFile * DiskCacheParentDirectory() { return mDiskCacheParentDirectory; } + + PRBool OfflineCacheEnabled(); + PRInt32 OfflineCacheCapacity() { return mOfflineCacheCapacity; } + nsILocalFile * OfflineCacheParentDirectory() { return mOfflineCacheParentDirectory; } PRBool MemoryCacheEnabled(); PRInt32 MemoryCacheCapacity(); @@ -121,6 +133,10 @@ private: PRBool mDiskCacheEnabled; PRInt32 mDiskCacheCapacity; nsCOMPtr mDiskCacheParentDirectory; + + PRBool mOfflineCacheEnabled; + PRInt32 mOfflineCacheCapacity; + nsCOMPtr mOfflineCacheParentDirectory; PRBool mMemoryCacheEnabled; PRInt32 mMemoryCacheCapacity; @@ -159,6 +175,9 @@ nsCacheProfilePrefObserver::Install() DISK_CACHE_ENABLE_PREF, DISK_CACHE_CAPACITY_PREF, DISK_CACHE_DIR_PREF, + OFFLINE_CACHE_ENABLE_PREF, + OFFLINE_CACHE_CAPACITY_PREF, + OFFLINE_CACHE_DIR_PREF, MEMORY_CACHE_ENABLE_PREF, MEMORY_CACHE_CAPACITY_PREF }; @@ -210,6 +229,11 @@ nsCacheProfilePrefObserver::Remove() prefs->RemoveObserver(DISK_CACHE_CAPACITY_PREF, this); prefs->RemoveObserver(DISK_CACHE_DIR_PREF, this); + // remove Offline cache pref observers + prefs->RemoveObserver(OFFLINE_CACHE_ENABLE_PREF, this); + prefs->RemoveObserver(OFFLINE_CACHE_CAPACITY_PREF, this); + prefs->RemoveObserver(OFFLINE_CACHE_DIR_PREF, this); + // remove Memory cache pref observers prefs->RemoveObserver(MEMORY_CACHE_ENABLE_PREF, this); prefs->RemoveObserver(MEMORY_CACHE_CAPACITY_PREF, this); @@ -278,7 +302,32 @@ nsCacheProfilePrefObserver::Observe(nsISupports * subject, // XXX the next time the profile changes (browser launch) #endif } else + + // which preference changed? + if (!strcmp(OFFLINE_CACHE_ENABLE_PREF, data.get())) { + + rv = branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, + &mOfflineCacheEnabled); + if (NS_FAILED(rv)) return rv; + nsCacheService::SetOfflineCacheEnabled(OfflineCacheEnabled()); + + } else if (!strcmp(OFFLINE_CACHE_CAPACITY_PREF, data.get())) { + + PRInt32 capacity = 0; + rv = branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &capacity); + if (NS_FAILED(rv)) return rv; + mOfflineCacheCapacity = PR_MAX(0, capacity); + nsCacheService::SetOfflineCacheCapacity(mOfflineCacheCapacity); +#if 0 + } else if (!strcmp(DISK_OFFLINE_DIR_PREF, data.get())) { + // XXX We probaby don't want to respond to this pref except after + // XXX profile changes. Ideally, there should be some kind of user + // XXX notification that the pref change won't take effect until + // XXX the next time the profile changes (browser launch) +#endif + } else #endif // !NECKO_DISK_CACHE + if (!strcmp(MEMORY_CACHE_ENABLE_PREF, data.get())) { rv = branch->GetBoolPref(MEMORY_CACHE_ENABLE_PREF, @@ -356,6 +405,47 @@ nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch) if (directory) mDiskCacheParentDirectory = do_QueryInterface(directory, &rv); } + + // read offline cache device prefs + mOfflineCacheEnabled = PR_TRUE; // presume offline cache is enabled + (void) branch->GetBoolPref(OFFLINE_CACHE_ENABLE_PREF, + &mOfflineCacheEnabled); + + mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY; + (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, + &mOfflineCacheCapacity); + mOfflineCacheCapacity = PR_MAX(0, mOfflineCacheCapacity); + + (void) branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error + NS_GET_IID(nsILocalFile), + getter_AddRefs(mOfflineCacheParentDirectory)); + + if (!mOfflineCacheParentDirectory) { + nsCOMPtr directory; + + // try to get the offline cache parent directory + rv = NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR, + getter_AddRefs(directory)); + if (NS_FAILED(rv)) { + // try to get the profile directory (there may not be a profile yet) + nsCOMPtr profDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profDir)); + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(directory)); + if (!directory) + directory = profDir; + } +#if DEBUG + if (!directory) { + // use current process directory during development + rv = NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR, + getter_AddRefs(directory)); + } +#endif + if (directory) + mOfflineCacheParentDirectory = do_QueryInterface(directory, &rv); + } #endif // !NECKO_DISK_CACHE // read memory cache device prefs @@ -376,7 +466,17 @@ nsCacheProfilePrefObserver::DiskCacheEnabled() return mDiskCacheEnabled; } - + +PRBool +nsCacheProfilePrefObserver::OfflineCacheEnabled() +{ + if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory)) + return PR_FALSE; + + return mOfflineCacheEnabled; +} + + PRBool nsCacheProfilePrefObserver::MemoryCacheEnabled() { @@ -472,6 +572,7 @@ nsCacheService::nsCacheService() mEnableDiskDevice(PR_TRUE), mMemoryDevice(nsnull), mDiskDevice(nsnull), + mOfflineDevice(nsnull), mTotalEntries(0), mCacheHits(0), mCacheMisses(0), @@ -527,9 +628,10 @@ nsCacheService::Init() NS_ADDREF(mObserver); mObserver->Install(); - mEnableDiskDevice = mObserver->DiskCacheEnabled(); - mEnableMemoryDevice = mObserver->MemoryCacheEnabled(); - + mEnableDiskDevice = mObserver->DiskCacheEnabled(); + mEnableOfflineDevice = mObserver->OfflineCacheEnabled(); + mEnableMemoryDevice = mObserver->MemoryCacheEnabled(); + mInitialized = PR_TRUE; return NS_OK; } @@ -561,10 +663,13 @@ nsCacheService::Shutdown() delete mDiskDevice; mDiskDevice = nsnull; + delete mOfflineDevice; + mOfflineDevice = nsnull; + #if defined(PR_LOGGING) LogCacheStatistics(); #endif -#endif +#endif // !NECKO_DISK_CACHE } } @@ -660,7 +765,19 @@ nsCacheService::EvictEntriesForClient(const char * clientID, if (NS_FAILED(rv)) return rv; } } -#endif // !NECKO_DISK_CACHE + + if (storagePolicy == nsICache::STORE_ANYWHERE || + storagePolicy == nsICache::STORE_OFFLINE) { + if (mEnableOfflineDevice) { + if (!mOfflineDevice) { + rv = CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + rv = mOfflineDevice->EvictEntries(clientID); + if (NS_FAILED(rv)) return rv; + } + } +#endif // ! NECKO_DISK_CACHE if (storagePolicy == nsICache::STORE_ANYWHERE || storagePolicy == nsICache::STORE_IN_MEMORY) { @@ -702,6 +819,10 @@ nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePo storagePolicy == nsICache::STORE_ON_DISK_AS_FILE)) { return PR_TRUE; } + if (gService->mEnableOfflineDevice && + storagePolicy == nsICache::STORE_OFFLINE) { + return PR_TRUE; + } return PR_FALSE; } @@ -735,6 +856,15 @@ NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor) rv = mDiskDevice->Visit(visitor); if (NS_FAILED(rv)) return rv; } + + if (mEnableOfflineDevice) { + if (!mOfflineDevice) { + rv = CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + rv = mOfflineDevice->Visit(visitor); + if (NS_FAILED(rv)) return rv; + } #endif // !NECKO_DISK_CACHE // XXX notify any shutdown process that visitation is complete for THIS visitor. @@ -786,6 +916,39 @@ nsCacheService::CreateDiskDevice() #endif } +nsresult +nsCacheService::CreateOfflineDevice() +{ +#ifdef NECKO_DISK_CACHE + CACHE_LOG_ALWAYS(("Creating offline device")); + + // XXX: want a sql-based device + if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE; + if (mOfflineDevice) return NS_OK; + + mOfflineDevice = new nsDiskCacheDevice; + if (!mOfflineDevice) return NS_ERROR_OUT_OF_MEMORY; + + // set the preferences + mOfflineDevice->SetCacheParentDirectoryAndName(mObserver->OfflineCacheParentDirectory(), + NS_LITERAL_CSTRING("OfflineCache")); + mOfflineDevice->SetCapacity(mObserver->OfflineCacheCapacity()); + + nsresult rv = mOfflineDevice->Init(); + if (NS_FAILED(rv)) { + CACHE_LOG_DEBUG(("mOfflineDevice->Init() failed (0x%.8x)\n", rv)); + CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n")); + + mEnableOfflineDevice = PR_FALSE; + delete mOfflineDevice; + mOfflineDevice = nsnull; + } + return rv; +#else // !NECKO_DISK_CACHE + NS_NOTREACHED("nsCacheService::CreateOfflineDevice"); + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} nsresult nsCacheService::CreateMemoryDevice() @@ -1115,6 +1278,23 @@ nsCacheService::SearchCacheDevices(nsCString * key, nsCacheStoragePolicy policy, #endif // !NECKO_DISK_CACHE } + if (!entry && (policy == nsICache::STORE_OFFLINE || + (policy == nsICache::STORE_ANYWHERE && + gIOService->IsOffline()))) { + +#ifdef NECKO_DISK_CACHE + if (mEnableOfflineDevice) { + if (!mOfflineDevice) { + nsresult rv = CreateOfflineDevice(); + if (NS_FAILED(rv)) + return nsnull; + } + + entry = mOfflineDevice->FindEntry(key, collision); + } +#endif + } + return entry; } @@ -1156,6 +1336,23 @@ nsCacheService::EnsureEntryHasDevice(nsCacheEntry * entry) } } +#ifdef NECKO_DISK_CACHE + if (!device && entry->IsStreamData() && + entry->IsAllowedOffline() && mEnableOfflineDevice) { + if (!mOfflineDevice) { + (void)CreateOfflineDevice(); // ignore the error (check for mOfflineDevice instead) + } + + if (mOfflineDevice) { + entry->MarkBinding(); + nsresult rv = mOfflineDevice->BindEntry(entry); + entry->ClearBinding(); + if (NS_SUCCEEDED(rv)) + device = mOfflineDevice; + } + } +#endif // ! NECKO_DISK_CACHE + if (device) entry->SetCacheDevice(device); return device; @@ -1219,6 +1416,14 @@ nsCacheService::OnProfileShutdown(PRBool cleanse) gService->mDiskDevice->Shutdown(); gService->mEnableDiskDevice = PR_FALSE; } + + if (gService->mOfflineDevice && gService->mEnableOfflineDevice) { + if (cleanse) + gService->mOfflineDevice->EvictEntries(nsnull); + + gService->mOfflineDevice->Shutdown(); + gService->mEnableOfflineDevice = PR_FALSE; + } #endif // !NECKO_DISK_CACHE if (gService->mMemoryDevice) { @@ -1236,9 +1441,10 @@ nsCacheService::OnProfileChanged() nsCacheServiceAutoLock lock; - gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled(); - gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled(); - + gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled(); + gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled(); + gService->mEnableMemoryDevice = gService->mObserver->MemoryCacheEnabled(); + #ifdef NECKO_DISK_CACHE if (gService->mDiskDevice) { gService->mDiskDevice->SetCacheParentDirectory(gService->mObserver->DiskCacheParentDirectory()); @@ -1252,6 +1458,19 @@ nsCacheService::OnProfileChanged() // XXX delete mDiskDevice? } } + + if (gService->mOfflineDevice) { + gService->mOfflineDevice->SetCacheParentDirectory(gService->mObserver->OfflineCacheParentDirectory()); + gService->mOfflineDevice->SetCapacity(gService->mObserver->OfflineCacheCapacity()); + + // XXX initialization of mOfflineDevice could be made lazily, if mEnableOfflineDevice is false + nsresult rv = gService->mOfflineDevice->Init(); + if (NS_FAILED(rv)) { + NS_ERROR("nsCacheService::OnProfileChanged: Re-initializing offline device failed"); + gService->mEnableOfflineDevice = PR_FALSE; + // XXX delete mOfflineDevice? + } + } #endif // !NECKO_DISK_CACHE // If memoryDevice exists, reset its size to the new profile @@ -1292,6 +1511,29 @@ nsCacheService::SetDiskCacheCapacity(PRInt32 capacity) gService->mEnableDiskDevice = gService->mObserver->DiskCacheEnabled(); } +void +nsCacheService::SetOfflineCacheEnabled(PRBool enabled) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + gService->mEnableOfflineDevice = enabled; +} + +void +nsCacheService::SetOfflineCacheCapacity(PRInt32 capacity) +{ + if (!gService) return; + nsCacheServiceAutoLock lock; + +#ifdef NECKO_DISK_CACHE + if (gService->mOfflineDevice) { + gService->mOfflineDevice->SetCapacity(capacity); + } +#endif // !NECKO_DISK_CACHE + + gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled(); +} + void nsCacheService::SetMemoryCache() diff --git a/mozilla/netwerk/cache/src/nsCacheService.h b/mozilla/netwerk/cache/src/nsCacheService.h index 257ef45f071..c23ec906a44 100644 --- a/mozilla/netwerk/cache/src/nsCacheService.h +++ b/mozilla/netwerk/cache/src/nsCacheService.h @@ -150,6 +150,9 @@ public: static void SetDiskCacheEnabled(PRBool enabled); static void SetDiskCacheCapacity(PRInt32 capacity); + static void SetOfflineCacheEnabled(PRBool enabled); + static void SetOfflineCacheCapacity(PRInt32 capacity); + static void SetMemoryCache(); nsresult Init(); @@ -165,6 +168,7 @@ private: static void Unlock(); nsresult CreateDiskDevice(); + nsresult CreateOfflineDevice(); nsresult CreateMemoryDevice(); nsresult CreateRequest(nsCacheSession * session, @@ -240,9 +244,11 @@ private: PRBool mEnableMemoryDevice; PRBool mEnableDiskDevice; + PRBool mEnableOfflineDevice; nsMemoryCacheDevice * mMemoryDevice; nsDiskCacheDevice * mDiskDevice; + nsDiskCacheDevice * mOfflineDevice; nsCacheEntryHashTable mActiveEntries; PRCList mDoomedEntries; diff --git a/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp b/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp index 506fc2ba77a..6649fc0a9f1 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp +++ b/mozilla/netwerk/cache/src/nsDiskCacheDevice.cpp @@ -929,6 +929,43 @@ nsDiskCacheDevice::SetCacheParentDirectory(nsILocalFile * parentDir) mCacheDirectory = do_QueryInterface(directory); } +// XXX: This is here to support the offline cache, and can be removed +// XXX: once it has its own cache implementation +void +nsDiskCacheDevice::SetCacheParentDirectoryAndName(nsILocalFile * parentDir, + const nsACString & str) +{ + nsresult rv; + PRBool exists; + + if (Initialized()) { + NS_ASSERTION(PR_FALSE, "Cannot switch cache directory when initialized"); + return; + } + + if (!parentDir) { + mCacheDirectory = nsnull; + return; + } + + // ensure parent directory exists + rv = parentDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = parentDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) return; + + // ensure cache directory exists + nsCOMPtr directory; + + rv = parentDir->Clone(getter_AddRefs(directory)); + if (NS_FAILED(rv)) return; + rv = directory->AppendNative(str); + if (NS_FAILED(rv)) return; + + mCacheDirectory = do_QueryInterface(directory); +} + + void nsDiskCacheDevice::getCacheDirectory(nsILocalFile ** result) diff --git a/mozilla/netwerk/cache/src/nsDiskCacheDevice.h b/mozilla/netwerk/cache/src/nsDiskCacheDevice.h index 2dd4f680086..20d04d3a9be 100644 --- a/mozilla/netwerk/cache/src/nsDiskCacheDevice.h +++ b/mozilla/netwerk/cache/src/nsDiskCacheDevice.h @@ -93,6 +93,10 @@ public: void SetCacheParentDirectory(nsILocalFile * parentDir); void SetCapacity(PRUint32 capacity); + // XXX: This is here to support the offline cache, and can be removed + // XXX: once it has its own cache implementation + void SetCacheParentDirectoryAndName(nsILocalFile * parentDir, + const nsACString & str); /* private: */ diff --git a/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp b/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp index 538b3b42246..9d10455fbce 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -118,6 +118,7 @@ nsHttpChannel::nsHttpChannel() , mSuppressDefensiveAuth(PR_FALSE) , mResuming(PR_FALSE) , mInitedCacheEntry(PR_FALSE) + , mCacheForOfflineUse(PR_FALSE) { LOG(("Creating nsHttpChannel @%x\n", this)); @@ -267,14 +268,9 @@ nsHttpChannel::Connect(PRBool firstTime) // true when called from AsyncOpen if (firstTime) { PRBool delayed = PR_FALSE; - PRBool offline = PR_FALSE; - - // are we offline? - nsCOMPtr ioService; - rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); - if (NS_FAILED(rv)) return rv; - ioService->GetOffline(&offline); + // are we offline? + PRBool offline = gIOService->IsOffline(); if (offline) mLoadFlags |= LOAD_ONLY_FROM_CACHE; else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0) @@ -297,7 +293,14 @@ nsHttpChannel::Connect(PRBool firstTime) return NS_ERROR_DOCUMENT_NOT_CACHED; // otherwise, let's just proceed without using the cache. } - + + // if cacheForOfflineUse has been set, open up an offline cache + // entry to update + if (mCacheForOfflineUse) { + rv = OpenOfflineCacheEntryForWriting(); + if (NS_FAILED(rv)) return rv; + } + if (NS_SUCCEEDED(rv) && delayed) return NS_OK; } @@ -785,6 +788,12 @@ nsHttpChannel::ProcessResponse() if (NS_SUCCEEDED(rv)) { InitCacheEntry(); CloseCacheEntry(); + + if (mCacheForOfflineUse) { + // Store response in the offline cache + InitOfflineCacheEntry(); + CloseOfflineCacheEntry(); + } } else { LOG(("ProcessRedirection failed [rv=%x]\n", rv)); @@ -884,8 +893,30 @@ nsHttpChannel::ProcessNormal() if (NS_FAILED(rv)) return rv; // install cache listener if we still have a cache entry open - if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) + if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) { rv = InstallCacheListener(); + if (NS_FAILED(rv)) return rv; + } + // create offline cache entry if offline caching was requested + if (mCacheForOfflineUse) { + PRBool shouldCacheForOfflineUse; + rv = ShouldUpdateOfflineCacheEntry(&shouldCacheForOfflineUse); + if (NS_FAILED(rv)) return rv; + + if (shouldCacheForOfflineUse) { + LOG(("writing to the offline cache")); + rv = InitOfflineCacheEntry(); + if (NS_FAILED(rv)) return rv; + + if (mOfflineCacheEntry) { + rv = InstallOfflineCacheListener(); + if (NS_FAILED(rv)) return rv; + } + } else { + LOG(("offline cache is up to date, not updating")); + CloseOfflineCacheEntry(); + } + } return rv; } @@ -1262,10 +1293,6 @@ nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed) storagePolicy = nsICache::STORE_IN_MEMORY; else storagePolicy = nsICache::STORE_ANYWHERE; // allow on disk - nsCOMPtr session; - rv = gHttpHandler->GetCacheSession(storagePolicy, - getter_AddRefs(session)); - if (NS_FAILED(rv)) return rv; // Set the desired cache access mode accordingly... nsCacheAccessMode accessRequested; @@ -1281,11 +1308,30 @@ nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed) else accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing + nsCOMPtr session; + rv = gHttpHandler->GetCacheSession(storagePolicy, + getter_AddRefs(session)); + if (NS_FAILED(rv)) return rv; + // we'll try to synchronously open the cache entry... however, it may be // in use and not yet validated, in which case we'll try asynchronously // opening the cache entry. rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE, getter_AddRefs(mCacheEntry)); + + if (offline && + !(NS_SUCCEEDED(rv) || rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)) { + // couldn't find it in the main cache, check the offline cache + + storagePolicy = nsICache::STORE_OFFLINE; + rv = gHttpHandler->GetCacheSession(storagePolicy, + getter_AddRefs(session)); + if (NS_FAILED(rv)) return rv; + + rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE, + getter_AddRefs(mCacheEntry)); + } + if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { // access to the cache entry has been denied (because the cache entry // is probably in use by another channel). @@ -1305,6 +1351,66 @@ nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed) return rv; } + +nsresult +nsHttpChannel::OpenOfflineCacheEntryForWriting() +{ + nsresult rv; + + LOG(("nsHttpChannel::OpenOfflineCacheEntryForWriting [this=%x]", this)); + + // make sure we're not abusing this function + NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open"); + + PRBool offline = gIOService->IsOffline(); + if (offline) { + // only put things in the offline cache while online + return NS_OK; + } + + if (mRequestHead.Method() != nsHttp::Get) { + // only cache complete documents offline + return NS_OK; + } + + if (mRequestHead.PeekHeader(nsHttp::Range)) { + // we don't support caching for byte range requests initiated + // by our clients or via nsIResumableChannel. + return NS_OK; + } + + if (RequestIsConditional()) { + // don't use the cache if our consumer is making a conditional request + // (see bug 331825). + return NS_OK; + } + + nsCAutoString cacheKey; + GenerateCacheKey(cacheKey); + + nsCOMPtr session; + rv = gHttpHandler->GetCacheSession(nsICache::STORE_OFFLINE, + getter_AddRefs(session)); + if (NS_FAILED(rv)) return rv; + + rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE, + PR_FALSE, getter_AddRefs(mOfflineCacheEntry)); + + if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) { + // access to the cache entry has been denied (because the cache entry + // is probably in use by another channel). Either the cache is being + // read from (we're offline) or it's being updated elsewhere. + return NS_OK; + } + + if (NS_SUCCEEDED(rv)) { + mOfflineCacheEntry->GetAccessGranted(&mOfflineCacheAccess); + LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess)); + } + + return rv; +} + nsresult nsHttpChannel::GenerateCacheKey(nsACString &cacheKey) { @@ -1597,6 +1703,48 @@ nsHttpChannel::CheckCache() return NS_OK; } + +nsresult +nsHttpChannel::ShouldUpdateOfflineCacheEntry(PRBool *shouldCacheForOfflineUse) +{ + *shouldCacheForOfflineUse = PR_FALSE; + + if (!mOfflineCacheEntry) { + return NS_OK; + } + + // if we're updating the cache entry, update the offline cache entry too + if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) { + *shouldCacheForOfflineUse = PR_TRUE; + return NS_OK; + } + + // if there's nothing in the offline cache, add it + if (mOfflineCacheEntry && (mOfflineCacheAccess == nsICache::ACCESS_WRITE)) { + *shouldCacheForOfflineUse = PR_TRUE; + return NS_OK; + } + + // if the document is newer than the offline entry, update it + PRUint32 docLastModifiedTime; + nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime); + if (NS_FAILED(rv)) { + *shouldCacheForOfflineUse = PR_TRUE; + return NS_OK; + } + + PRUint32 offlineLastModifiedTime; + rv = mOfflineCacheEntry->GetLastModified(&offlineLastModifiedTime); + NS_ENSURE_SUCCESS(rv, rv); + + if (docLastModifiedTime > offlineLastModifiedTime) { + *shouldCacheForOfflineUse = PR_TRUE; + return NS_OK; + } + + return NS_OK; +} + // If the data in the cache hasn't expired, then there's no need to // talk with the server, not even to do an if-modified-since. This // method creates a stream from the cache, synthesizing all the various @@ -1641,8 +1789,18 @@ nsHttpChannel::ReadFromCache() // have we been configured to skip reading from the cache? if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) { - LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED load flag\n")); - return AsyncCall(&nsHttpChannel::HandleAsyncNotModified); + // if offline caching has been requested and the offline cache needs + // updating, complete the call even if the main cache entry is + // up-to-date + PRBool shouldUpdateOffline; + if (!mCacheForOfflineUse || + NS_FAILED(ShouldUpdateOfflineCacheEntry(&shouldUpdateOffline)) || + !shouldUpdateOffline) { + + LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED " + "load flag\n")); + return AsyncCall(&nsHttpChannel::HandleAsyncNotModified); + } } // open input stream for reading... @@ -1697,6 +1855,24 @@ nsHttpChannel::CloseCacheEntry() mInitedCacheEntry = PR_FALSE; } + +void +nsHttpChannel::CloseOfflineCacheEntry() +{ + if (!mOfflineCacheEntry) + return; + + LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%x]", this)); + + if (NS_FAILED(mStatus)) { + mOfflineCacheEntry->Doom(); + } + + mOfflineCacheEntry = 0; + mOfflineCacheAccess = 0; +} + + // Initialize the cache entry for writing. // - finalize storage policy // - store security info @@ -1731,21 +1907,52 @@ nsHttpChannel::InitCacheEntry() if (NS_FAILED(rv)) return rv; } - // Store secure data in memory only - if (mSecurityInfo) - mCacheEntry->SetSecurityInfo(mSecurityInfo); - // Set the expiration time for this cache entry rv = UpdateExpirationTime(); if (NS_FAILED(rv)) return rv; + rv = AddCacheEntryHeaders(mCacheEntry); + if (NS_FAILED(rv)) return rv; + + mInitedCacheEntry = PR_TRUE; + return NS_OK; +} + + +nsresult +nsHttpChannel::InitOfflineCacheEntry() +{ + if (!mOfflineCacheEntry) { + return NS_OK; + } + + if (mResponseHead->NoStore()) { + CloseOfflineCacheEntry(); + + return NS_OK; + } + + return AddCacheEntryHeaders(mOfflineCacheEntry); +} + + +nsresult +nsHttpChannel::AddCacheEntryHeaders(nsICacheEntryDescriptor *entry) +{ + nsresult rv; + + // Store secure data in memory only + if (mSecurityInfo) + entry->SetSecurityInfo(mSecurityInfo); + // Store the HTTP request method with the cache entry so we can distinguish // for example GET and HEAD responses. - rv = mCacheEntry->SetMetaDataElement("request-method", mRequestHead.Method().get()); + rv = entry->SetMetaDataElement("request-method", + mRequestHead.Method().get()); if (NS_FAILED(rv)) return rv; // Store the HTTP authorization scheme used if any... - rv = StoreAuthorizationMetaData(); + rv = StoreAuthorizationMetaData(entry); if (NS_FAILED(rv)) return rv; // Iterate over the headers listed in the Vary response header, and @@ -1773,7 +1980,7 @@ nsHttpChannel::InitCacheEntry() if (requestVal) { // build cache meta data key and set meta data element... metaKey = prefix + nsDependentCString(token); - mCacheEntry->SetMetaDataElement(metaKey.get(), requestVal); + entry->SetMetaDataElement(metaKey.get(), requestVal); } } token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val); @@ -1786,22 +1993,20 @@ nsHttpChannel::InitCacheEntry() // the meta data. nsCAutoString head; mResponseHead->Flatten(head, PR_TRUE); - rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); - if (NS_FAILED(rv)) return rv; + rv = entry->SetMetaDataElement("response-head", head.get()); - mInitedCacheEntry = PR_TRUE; - return NS_OK; + return rv; } nsresult -nsHttpChannel::StoreAuthorizationMetaData() +nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry) { // Not applicable to proxy authorization... const char *val = mRequestHead.PeekHeader(nsHttp::Authorization); if (val) { // eg. [Basic realm="wally world"] nsCAutoString buf(Substring(val, strchr(val, ' '))); - return mCacheEntry->SetMetaDataElement("auth", buf.get()); + return entry->SetMetaDataElement("auth", buf.get()); } return NS_OK; } @@ -1857,6 +2062,33 @@ nsHttpChannel::InstallCacheListener(PRUint32 offset) return NS_OK; } +nsresult +nsHttpChannel::InstallOfflineCacheListener() +{ + nsresult rv; + + LOG(("Preparing to write data into the offline cache [uri=%s]\n", + mSpec.get())); + + NS_ASSERTION(mOfflineCacheEntry, "no offline cache entry"); + NS_ASSERTION(mListener, "no listener"); + + nsCOMPtr out; + rv = mOfflineCacheEntry->OpenOutputStream(0, getter_AddRefs(out)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr tee = + do_CreateInstance(kStreamListenerTeeCID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = tee->Init(mListener, out); + if (NS_FAILED(rv)) return rv; + + mListener = tee; + + return NS_OK; +} + //----------------------------------------------------------------------------- // nsHttpChannel //----------------------------------------------------------------------------- @@ -4059,6 +4291,9 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st if (mCacheEntry) CloseCacheEntry(); + if (mOfflineCacheEntry) + CloseOfflineCacheEntry(); + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nsnull, status); @@ -4257,6 +4492,23 @@ nsHttpChannel::SetCacheAsFile(PRBool value) return mCacheEntry->SetStoragePolicy(policy); } + +NS_IMETHODIMP +nsHttpChannel::GetCacheForOfflineUse(PRBool *value) +{ + *value = mCacheForOfflineUse; + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetCacheForOfflineUse(PRBool value) +{ + mCacheForOfflineUse = value; + + return NS_OK; +} + NS_IMETHODIMP nsHttpChannel::GetCacheFile(nsIFile **cacheFile) { diff --git a/mozilla/netwerk/protocol/http/src/nsHttpChannel.h b/mozilla/netwerk/protocol/http/src/nsHttpChannel.h index a97eaa3808c..b13b1c0187a 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpChannel.h +++ b/mozilla/netwerk/protocol/http/src/nsHttpChannel.h @@ -173,15 +173,21 @@ private: // cache specific methods nsresult OpenCacheEntry(PRBool offline, PRBool *delayed); + nsresult OpenOfflineCacheEntryForWriting(); nsresult GenerateCacheKey(nsACString &key); nsresult UpdateExpirationTime(); nsresult CheckCache(); + nsresult ShouldUpdateOfflineCacheEntry(PRBool *shouldCacheForOfflineUse); nsresult ReadFromCache(); void CloseCacheEntry(); + void CloseOfflineCacheEntry(); nsresult InitCacheEntry(); - nsresult StoreAuthorizationMetaData(); + nsresult InitOfflineCacheEntry(); + nsresult AddCacheEntryHeaders(nsICacheEntryDescriptor *entry); + nsresult StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry); nsresult FinalizeCacheEntry(); nsresult InstallCacheListener(PRUint32 offset = 0); + nsresult InstallOfflineCacheListener(); // byte range request specific methods nsresult SetupByteRangeRequest(PRUint32 partialLen); @@ -247,6 +253,9 @@ private: PRUint32 mPostID; PRUint32 mRequestTime; + nsCOMPtr mOfflineCacheEntry; + nsCacheAccessMode mOfflineCacheAccess; + // auth specific data nsISupports *mProxyAuthContinuationState; nsCString mProxyAuthType; @@ -276,6 +285,7 @@ private: PRUint32 mSuppressDefensiveAuth : 1; PRUint32 mResuming : 1; PRUint32 mInitedCacheEntry : 1; + PRUint32 mCacheForOfflineUse : 1; class nsContentEncodings : public nsIUTF8StringEnumerator { diff --git a/mozilla/netwerk/protocol/http/src/nsHttpHandler.cpp b/mozilla/netwerk/protocol/http/src/nsHttpHandler.cpp index 476468910c2..bc6e1097cc9 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpHandler.cpp +++ b/mozilla/netwerk/protocol/http/src/nsHttpHandler.cpp @@ -428,10 +428,21 @@ nsHttpHandler::GetCacheSession(nsCacheStoragePolicy storagePolicy, rv = mCacheSession_MEM->SetDoomEntriesIfExpired(PR_FALSE); if (NS_FAILED(rv)) return rv; + + rv = serv->CreateSession("HTTP-offline", + nsICache::STORE_OFFLINE, + nsICache::STREAM_BASED, + getter_AddRefs(mCacheSession_OFFLINE)); + if (NS_FAILED(rv)) return rv; + + rv = mCacheSession_OFFLINE->SetDoomEntriesIfExpired(PR_FALSE); + if (NS_FAILED(rv)) return rv; } if (storagePolicy == nsICache::STORE_IN_MEMORY) NS_ADDREF(*result = mCacheSession_MEM); + else if (storagePolicy == nsICache::STORE_OFFLINE) + NS_ADDREF(*result = mCacheSession_OFFLINE); else NS_ADDREF(*result = mCacheSession_ANY); diff --git a/mozilla/netwerk/protocol/http/src/nsHttpHandler.h b/mozilla/netwerk/protocol/http/src/nsHttpHandler.h index 1cd8a0117a4..c08313d7ae9 100644 --- a/mozilla/netwerk/protocol/http/src/nsHttpHandler.h +++ b/mozilla/netwerk/protocol/http/src/nsHttpHandler.h @@ -269,6 +269,7 @@ private: // cache support nsCOMPtr mCacheSession_ANY; nsCOMPtr mCacheSession_MEM; + nsCOMPtr mCacheSession_OFFLINE; PRUint32 mLastUniqueID; PRUint32 mSessionStartTime; diff --git a/mozilla/uriloader/prefetch/nsIPrefetchService.idl b/mozilla/uriloader/prefetch/nsIPrefetchService.idl index 3c2f6730c42..23822fce926 100644 --- a/mozilla/uriloader/prefetch/nsIPrefetchService.idl +++ b/mozilla/uriloader/prefetch/nsIPrefetchService.idl @@ -39,7 +39,7 @@ interface nsIURI; -[scriptable, uuid(933cb52a-2864-4a40-8678-a2d0851b0ef4)] +[scriptable, uuid(ae241be6-3cb6-47ec-9e38-2d89bc22042a)] interface nsIPrefetchService : nsISupports { /** @@ -51,5 +51,17 @@ interface nsIPrefetchService : nsISupports */ void prefetchURI(in nsIURI aURI, in nsIURI aReferrerURI, in boolean aExplicit); + /** + * Enqueue a request to prefetch the specified URI, making sure it's in the + * offline cache. + * + * @param aURI the URI of the document to prefetch + * @param aReferrerURI the URI of the referring page + * @param aExplicit the link element has an explicit offline link type + */ + void prefetchURIForOfflineUse(in nsIURI aURI, + in nsIURI aReferrerURI, + in boolean aExplicit); + // XXX do we need a way to cancel prefetch requests? }; diff --git a/mozilla/uriloader/prefetch/nsPrefetchService.cpp b/mozilla/uriloader/prefetch/nsPrefetchService.cpp index ccd6262db0a..6e23e6f62b8 100644 --- a/mozilla/uriloader/prefetch/nsPrefetchService.cpp +++ b/mozilla/uriloader/prefetch/nsPrefetchService.cpp @@ -144,34 +144,44 @@ nsPrefetchListener::OnStartRequest(nsIRequest *aRequest, { nsresult rv; - nsCOMPtr cachingChannel(do_QueryInterface(aRequest, &rv)); + nsCOMPtr cachingChannel = + do_QueryInterface(aRequest, &rv); if (NS_FAILED(rv)) return rv; - // no need to prefetch a document that is already in the cache - PRBool fromCache; - if (NS_SUCCEEDED(cachingChannel->IsFromCache(&fromCache)) && fromCache) { - LOG(("document is already in the cache; canceling prefetch\n")); - return NS_BINDING_ABORTED; - } - - // - // no need to prefetch a document that must be requested fresh each - // and every time. - // - nsCOMPtr cacheToken; - cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); - if (!cacheToken) - return NS_ERROR_ABORT; // bail, no cache entry - - nsCOMPtr entryInfo(do_QueryInterface(cacheToken, &rv)); + PRBool cacheForOfflineUse; + rv = cachingChannel->GetCacheForOfflineUse(&cacheForOfflineUse); if (NS_FAILED(rv)) return rv; - PRUint32 expTime; - if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expTime))) { - if (NowInSeconds() >= expTime) { - LOG(("document cannot be reused from cache; canceling prefetch\n")); + if (!cacheForOfflineUse) { + // no need to prefetch a document that is already in the cache + PRBool fromCache; + if (NS_SUCCEEDED(cachingChannel->IsFromCache(&fromCache)) && + fromCache) { + LOG(("document is already in the cache; canceling prefetch\n")); return NS_BINDING_ABORTED; } + + // + // no need to prefetch a document that must be requested fresh each + // and every time. + // + nsCOMPtr cacheToken; + cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); + if (!cacheToken) + return NS_ERROR_ABORT; // bail, no cache entry + + nsCOMPtr entryInfo = + do_QueryInterface(cacheToken, &rv); + if (NS_FAILED(rv)) return rv; + + PRUint32 expTime; + if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expTime))) { + if (NowInSeconds() >= expTime) { + LOG(("document cannot be reused from cache; " + "canceling prefetch\n")); + return NS_BINDING_ABORTED; + } + } } return NS_OK; } @@ -230,11 +240,26 @@ nsPrefetchListener::OnChannelRedirect(nsIChannel *aOldChannel, if (NS_FAILED(rv)) return rv; + PRBool offline; + nsCOMPtr oldCachingChannel = + do_QueryInterface(aOldChannel); + if (NS_SUCCEEDED(oldCachingChannel->GetCacheForOfflineUse(&offline)) && + offline) { + nsCOMPtr newCachingChannel = + do_QueryInterface(aOldChannel); + if (newCachingChannel) + newCachingChannel->SetCacheForOfflineUse(PR_TRUE); + } + PRBool match; rv = newURI->SchemeIs("http", &match); if (NS_FAILED(rv) || !match) { - LOG(("rejected: URL is not of type http\n")); - return NS_ERROR_ABORT; + if (!offline || + NS_FAILED(newURI->SchemeIs("https", &match)) || + !match) { + LOG(("rejected: URL is not of type http\n")); + return NS_ERROR_ABORT; + } } // HTTP request headers are not automatically forwarded to the new channel. @@ -242,7 +267,10 @@ nsPrefetchListener::OnChannelRedirect(nsIChannel *aOldChannel, NS_ENSURE_STATE(httpChannel); httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), - NS_LITERAL_CSTRING("prefetch"), PR_FALSE); + offline ? + NS_LITERAL_CSTRING("offline-resource") : + NS_LITERAL_CSTRING("prefetch"), + PR_FALSE); mService->UpdateCurrentChannel(aNewChannel); return NS_OK; @@ -264,7 +292,7 @@ nsPrefetchService::~nsPrefetchService() { // cannot reach destructor if prefetch in progress (listener owns reference // to this service) - EmptyQueue(); + EmptyQueue(PR_TRUE); } nsresult @@ -314,7 +342,9 @@ nsPrefetchService::ProcessNextURI() if (!listener) return; do { - rv = DequeueURI(getter_AddRefs(uri), getter_AddRefs(referrer)); + PRBool offline; + rv = DequeueURI(getter_AddRefs(uri), getter_AddRefs(referrer), + &offline); if (NS_FAILED(rv)) break; #if defined(PR_LOGGING) @@ -335,12 +365,26 @@ nsPrefetchService::ProcessNextURI() if (NS_FAILED(rv)) continue; // configure HTTP specific stuff - nsCOMPtr httpChannel(do_QueryInterface(mCurrentChannel)); + nsCOMPtr httpChannel = + do_QueryInterface(mCurrentChannel); if (httpChannel) { httpChannel->SetReferrer(referrer); - httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), - NS_LITERAL_CSTRING("prefetch"), - PR_FALSE); + httpChannel->SetRequestHeader( + NS_LITERAL_CSTRING("X-Moz"), + offline ? + NS_LITERAL_CSTRING("offline-resource") : + NS_LITERAL_CSTRING("prefetch"), + PR_FALSE); + } + + if (offline) { + nsCOMPtr cachingChannel = + do_QueryInterface(mCurrentChannel); + if (cachingChannel) { + if (NS_FAILED(cachingChannel->SetCacheForOfflineUse(PR_TRUE))) { + continue; + } + } } rv = mCurrentChannel->AsyncOpen(listener, nsnull); @@ -371,9 +415,11 @@ nsPrefetchService::RemoveProgressListener() } nsresult -nsPrefetchService::EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI) +nsPrefetchService::EnqueueURI(nsIURI *aURI, + nsIURI *aReferrerURI, + PRBool aOffline) { - nsPrefetchNode *node = new nsPrefetchNode(aURI, aReferrerURI); + nsPrefetchNode *node = new nsPrefetchNode(aURI, aReferrerURI, aOffline); if (!node) return NS_ERROR_OUT_OF_MEMORY; @@ -390,7 +436,9 @@ nsPrefetchService::EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI) } nsresult -nsPrefetchService::DequeueURI(nsIURI **aURI, nsIURI **aReferrerURI) +nsPrefetchService::DequeueURI(nsIURI **aURI, + nsIURI **aReferrerURI, + PRBool *aOffline) { if (!mQueueHead) return NS_ERROR_NOT_AVAILABLE; @@ -398,6 +446,7 @@ nsPrefetchService::DequeueURI(nsIURI **aURI, nsIURI **aReferrerURI) // remove from the head NS_ADDREF(*aURI = mQueueHead->mURI); NS_ADDREF(*aReferrerURI = mQueueHead->mReferrerURI); + *aOffline = mQueueHead->mOffline; nsPrefetchNode *node = mQueueHead; mQueueHead = mQueueHead->mNext; @@ -410,16 +459,25 @@ nsPrefetchService::DequeueURI(nsIURI **aURI, nsIURI **aReferrerURI) } void -nsPrefetchService::EmptyQueue() +nsPrefetchService::EmptyQueue(PRBool includeOffline) { - nsresult rv; - nsCOMPtr uri, referrer; + nsPrefetchNode *prev = 0; + nsPrefetchNode *node = mQueueHead; - do { - rv = DequeueURI(getter_AddRefs(uri), - getter_AddRefs(referrer)); + while (node) { + nsPrefetchNode *next = node->mNext; + if (includeOffline || !node->mOffline) { + if (prev) + prev->mNext = next; + else + mQueueHead = next; + delete node; + } + else + prev = node; + + node = next; } - while (NS_SUCCEEDED(rv)); } void @@ -453,9 +511,27 @@ nsPrefetchService::StopPrefetching() if (!mCurrentChannel) return; + // if it's an offline prefetch, requeue it for when prefetching starts + // again + nsCOMPtr cachingChannel = + do_QueryInterface(mCurrentChannel); + PRBool offline; + if (cachingChannel && + NS_SUCCEEDED(cachingChannel->GetCacheForOfflineUse(&offline)) && + offline) { + nsCOMPtr httpChannel = + do_QueryInterface(mCurrentChannel); + nsCOMPtr uri; + nsCOMPtr referrerURI; + if (NS_SUCCEEDED(mCurrentChannel->GetURI(getter_AddRefs(uri))) && + NS_SUCCEEDED(httpChannel->GetReferrer(getter_AddRefs(referrerURI)))) { + EnqueueURI(uri, referrerURI, PR_TRUE); + } + } + mCurrentChannel->Cancel(NS_BINDING_ABORTED); mCurrentChannel = nsnull; - EmptyQueue(); + EmptyQueue(PR_FALSE); } //----------------------------------------------------------------------------- @@ -469,11 +545,14 @@ NS_IMPL_ISUPPORTS4(nsPrefetchService, nsISupportsWeakReference) //----------------------------------------------------------------------------- -// nsPrefetchService::nsIPretetchService +// nsPrefetchService::nsIPrefetchService //----------------------------------------------------------------------------- -NS_IMETHODIMP -nsPrefetchService::PrefetchURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aExplicit) +nsresult +nsPrefetchService::Prefetch(nsIURI *aURI, + nsIURI *aReferrerURI, + PRBool aExplicit, + PRBool aOffline) { nsresult rv; @@ -507,8 +586,15 @@ nsPrefetchService::PrefetchURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aExpli PRBool match; rv = aURI->SchemeIs("http", &match); if (NS_FAILED(rv) || !match) { - LOG(("rejected: URL is not of type http\n")); - return NS_ERROR_ABORT; + if (aOffline) { + // Offline https urls can be prefetched + rv = aURI->SchemeIs("https", &match); + } + + if (NS_FAILED(rv) || !match) { + LOG(("rejected: URL is not of type http\n")); + return NS_ERROR_ABORT; + } } // @@ -516,8 +602,15 @@ nsPrefetchService::PrefetchURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aExpli // rv = aReferrerURI->SchemeIs("http", &match); if (NS_FAILED(rv) || !match) { - LOG(("rejected: referrer URL is not of type http\n")); - return NS_ERROR_ABORT; + if (aOffline) { + // Offline https urls can be prefetched + rv = aURI->SchemeIs("https", &match); + } + + if (NS_FAILED(rv) || !match) { + LOG(("rejected: referrer URL is not of type http\n")); + return NS_ERROR_ABORT; + } } // skip URLs that contain query strings, except URLs for which prefetching @@ -542,8 +635,24 @@ nsPrefetchService::PrefetchURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aExpli if (currentURI) { PRBool equals; if (NS_SUCCEEDED(currentURI->Equals(aURI, &equals)) && equals) { - LOG(("rejected: URL is already being prefetched\n")); - return NS_ERROR_ABORT; + if (aOffline) { + // We may still need to put it on the queue if the channel + // isn't fetching to the offline cache + nsCOMPtr cachingChannel = + do_QueryInterface(mCurrentChannel, &rv); + if (NS_SUCCEEDED(rv)) { + PRBool offline; + rv = cachingChannel->GetCacheForOfflineUse(&offline); + if (NS_SUCCEEDED(rv) && offline) { + LOG(("rejected: URL is already being prefetched\n")); + return NS_ERROR_ABORT; + } + } + } + else { + LOG(("rejected: URL is already being prefetched\n")); + return NS_ERROR_ABORT; + } } } } @@ -555,12 +664,32 @@ nsPrefetchService::PrefetchURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aExpli for (; node; node = node->mNext) { PRBool equals; if (NS_SUCCEEDED(node->mURI->Equals(aURI, &equals)) && equals) { + if (aOffline) { + // make sure the node is placed in the offline cache + node->mOffline = PR_TRUE; + } LOG(("rejected: URL is already on prefetch queue\n")); return NS_ERROR_ABORT; } } - return EnqueueURI(aURI, aReferrerURI); + return EnqueueURI(aURI, aReferrerURI, aOffline); +} + +NS_IMETHODIMP +nsPrefetchService::PrefetchURI(nsIURI *aURI, + nsIURI *aReferrerURI, + PRBool aExplicit) +{ + return Prefetch(aURI, aReferrerURI, aExplicit, PR_FALSE); +} + +NS_IMETHODIMP +nsPrefetchService::PrefetchURIForOfflineUse(nsIURI *aURI, + nsIURI *aReferrerURI, + PRBool aExplicit) +{ + return Prefetch(aURI, aReferrerURI, aExplicit, PR_TRUE); } //----------------------------------------------------------------------------- diff --git a/mozilla/uriloader/prefetch/nsPrefetchService.h b/mozilla/uriloader/prefetch/nsPrefetchService.h index 956a3921521..a4c543d9e54 100644 --- a/mozilla/uriloader/prefetch/nsPrefetchService.h +++ b/mozilla/uriloader/prefetch/nsPrefetchService.h @@ -78,11 +78,16 @@ public: private: ~nsPrefetchService(); + nsresult Prefetch(nsIURI *aURI, + nsIURI *aReferrerURI, + PRBool aExplicit, + PRBool aOffline); + void AddProgressListener(); void RemoveProgressListener(); - nsresult EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI); - nsresult DequeueURI(nsIURI **aURI, nsIURI **aReferrerURI); - void EmptyQueue(); + nsresult EnqueueURI(nsIURI *aURI, nsIURI *aReferrerURI, PRBool aOffline); + nsresult DequeueURI(nsIURI **aURI, nsIURI **aReferrerURI, PRBool *aOffline); + void EmptyQueue(PRBool includeOffline); void StartPrefetching(); void StopPrefetching(); @@ -127,15 +132,18 @@ class nsPrefetchNode { public: nsPrefetchNode(nsIURI *aURI, - nsIURI *aReferrerURI) + nsIURI *aReferrerURI, + PRBool aOffline) : mNext(nsnull) , mURI(aURI) , mReferrerURI(aReferrerURI) + , mOffline(aOffline) { } nsPrefetchNode *mNext; nsCOMPtr mURI; nsCOMPtr mReferrerURI; + PRBool mOffline; }; #endif // !nsPrefetchService_h__