diff --git a/content/base/src/nsContentSink.cpp b/content/base/src/nsContentSink.cpp index af55b8df6a8..4e2edd2ca88 100644 --- a/content/base/src/nsContentSink.cpp +++ b/content/base/src/nsContentSink.cpp @@ -45,6 +45,7 @@ #include "nsContentSink.h" #include "nsScriptLoader.h" #include "nsIDocument.h" +#include "nsIDOMDocument.h" #include "nsICSSLoader.h" #include "nsStyleConsts.h" #include "nsStyleLinkElement.h" @@ -72,7 +73,8 @@ #include "nsICache.h" #include "nsICacheService.h" #include "nsICacheSession.h" -#include "nsIOfflineCacheSession.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsIDOMLoadStatus.h" #include "nsICookieService.h" #include "nsIPrompt.h" #include "nsServiceManagerUtils.h" @@ -679,14 +681,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, aElement, hasPrefetch, PR_FALSE); + PrefetchHref(aHref, aElement, hasPrefetch); } // fetch href into the offline cache if relation is "offline-resource" if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) { - AddOfflineResource(aHref); - if (mSaveOfflineResources) - PrefetchHref(aHref, aElement, PR_TRUE, PR_TRUE); + AddOfflineResource(aHref, aElement); } // is it a stylesheet link? @@ -771,8 +771,7 @@ nsContentSink::ProcessMETATag(nsIContent* aContent) void nsContentSink::PrefetchHref(const nsAString &aHref, nsIContent *aSource, - PRBool aExplicit, - PRBool aOffline) + PRBool aExplicit) { // // SECURITY CHECK: disable prefetching from mailnews! @@ -816,45 +815,13 @@ nsContentSink::PrefetchHref(const nsAString &aHref, mDocumentBaseURI); if (uri) { nsCOMPtr domNode = do_QueryInterface(aSource); - if (aOffline) - prefetchService->PrefetchURIForOfflineUse(uri, - mDocumentURI, - domNode, - aExplicit); - else - prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit); + prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit); } } } nsresult -nsContentSink::GetOfflineCacheSession(nsIOfflineCacheSession **aSession) -{ - if (!mOfflineCacheSession) { - nsresult rv; - nsCOMPtr serv = - do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr session; - rv = serv->CreateSession("HTTP-offline", - nsICache::STORE_OFFLINE, - nsICache::STREAM_BASED, - getter_AddRefs(session)); - NS_ENSURE_SUCCESS(rv, rv); - - mOfflineCacheSession = - do_QueryInterface(session, &rv); - NS_ENSURE_SUCCESS(rv, rv); - } - - NS_ADDREF(*aSession = mOfflineCacheSession); - - return NS_OK; -} - -nsresult -nsContentSink::AddOfflineResource(const nsAString &aHref) +nsContentSink::AddOfflineResource(const nsAString &aHref, nsIContent *aSource) { PRBool match; nsresult rv; @@ -863,17 +830,8 @@ nsContentSink::AddOfflineResource(const nsAString &aHref) if (!innerURI) return NS_ERROR_FAILURE; - nsCAutoString ownerHost; - rv = innerURI->GetHostPort(ownerHost); - NS_ENSURE_SUCCESS(rv, rv); - - nsCAutoString ownerSpec; - rv = mDocumentURI->GetSpec(ownerSpec); - NS_ENSURE_SUCCESS(rv, rv); - if (!mHaveOfflineResources) { mHaveOfflineResources = PR_TRUE; - mSaveOfflineResources = PR_FALSE; // only let http and https urls add offline resources nsresult rv = innerURI->SchemeIs("http", &match); @@ -886,18 +844,29 @@ nsContentSink::AddOfflineResource(const nsAString &aHref) return NS_OK; } - nsCOMPtr session; - rv = GetOfflineCacheSession(getter_AddRefs(session)); + // create updater + mOfflineCacheUpdate = + do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - // we're going to replace the list, clear it out - rv = session->SetOwnedKeys(ownerHost, ownerSpec, 0, nsnull); + nsCAutoString ownerDomain; + rv = innerURI->GetHostPort(ownerDomain); NS_ENSURE_SUCCESS(rv, rv); - mSaveOfflineResources = PR_TRUE; + nsCAutoString ownerSpec; + rv = mDocumentURI->GetSpec(ownerSpec); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOfflineCacheUpdate->Init(PR_FALSE, ownerDomain, + ownerSpec, mDocumentURI); + NS_ENSURE_SUCCESS(rv, rv); + + // Kick off this update when the document is done loading + nsCOMPtr doc = do_QueryInterface(mDocument); + mOfflineCacheUpdate->ScheduleOnDocumentStop(doc); } - if (!mSaveOfflineResources) return NS_OK; + if (!mOfflineCacheUpdate) return NS_OK; const nsACString &charset = mDocument->GetDocumentCharacterSet(); nsCOMPtr uri; @@ -906,38 +875,9 @@ nsContentSink::AddOfflineResource(const nsAString &aHref) mDocumentBaseURI); NS_ENSURE_SUCCESS(rv, rv); - // only http and https urls can be marked as offline resources - rv = uri->SchemeIs("http", &match); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr domNode = do_QueryInterface(aSource); - if (!match) { - rv = uri->SchemeIs("https", &match); - NS_ENSURE_SUCCESS(rv, rv); - if (!match) - return NS_OK; - } - - nsCAutoString spec; - rv = uri->GetSpec(spec); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr offlineCacheSession; - rv = GetOfflineCacheSession(getter_AddRefs(offlineCacheSession)); - NS_ENSURE_SUCCESS(rv, rv); - - // url fragments aren't used in cache keys - nsCAutoString::const_iterator specStart, specEnd; - spec.BeginReading(specStart); - spec.EndReading(specEnd); - if (FindCharInReadable('#', specStart, specEnd)) { - spec.BeginReading(specEnd); - offlineCacheSession->AddOwnedKey(ownerHost, ownerSpec, - Substring(specEnd, specStart)); - } else { - offlineCacheSession->AddOwnedKey(ownerHost, ownerSpec, spec); - } - - return NS_OK; + return mOfflineCacheUpdate->AddURI(uri, domNode); } void diff --git a/content/base/src/nsContentSink.h b/content/base/src/nsContentSink.h index 6cea2fabd8f..78709341fdb 100644 --- a/content/base/src/nsContentSink.h +++ b/content/base/src/nsContentSink.h @@ -75,7 +75,7 @@ class nsIContent; class nsIViewManager; class nsNodeInfoManager; class nsScriptLoader; -class nsIOfflineCacheSession; +class nsIOfflineCacheUpdate; #ifdef NS_DEBUG @@ -168,9 +168,8 @@ protected: const nsSubstring& aMedia); void PrefetchHref(const nsAString &aHref, nsIContent *aSource, - PRBool aExplicit, PRBool aOffline); - nsresult GetOfflineCacheSession(nsIOfflineCacheSession **aSession); - nsresult AddOfflineResource(const nsAString &aHref); + PRBool aExplicit); + nsresult AddOfflineResource(const nsAString &aHref, nsIContent *aSource); void ScrollToRef(); nsresult RefreshIfEnabled(nsIViewManager* vm); @@ -262,7 +261,7 @@ protected: PRPackedBool mNotifyOnTimer; // For saving links - nsCOMPtr mOfflineCacheSession; + nsCOMPtr mOfflineCacheUpdate; // Have we already called BeginUpdate for this set of content changes? PRUint8 mBeganUpdate : 1; @@ -278,8 +277,6 @@ protected: PRUint8 mDeferredLayoutStart : 1; // true if an nodes have been encountered. PRUint8 mHaveOfflineResources : 1; - // true if offline-resource links should be saved to the offline cache - PRUint8 mSaveOfflineResources : 1; // If true, we deferred notifications until sheets load PRUint8 mDeferredFlushTags : 1; diff --git a/content/html/document/src/nsHTMLContentSink.cpp b/content/html/document/src/nsHTMLContentSink.cpp index d26c13bc78e..c9a57c92f96 100644 --- a/content/html/document/src/nsHTMLContentSink.cpp +++ b/content/html/document/src/nsHTMLContentSink.cpp @@ -3005,16 +3005,14 @@ HTMLContentSink::ProcessLINKTag(const nsIParserNode& aNode) nsAutoString hrefVal; element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); if (!hrefVal.IsEmpty()) { - PrefetchHref(hrefVal, element, hasPrefetch, PR_FALSE); + PrefetchHref(hrefVal, element, hasPrefetch); } } if (linkTypes.IndexOf(NS_LITERAL_STRING("offline-resource")) != -1) { nsAutoString hrefVal; element->GetAttr(kNameSpaceID_None, nsGkAtoms::href, hrefVal); if (!hrefVal.IsEmpty()) { - AddOfflineResource(hrefVal); - if (mSaveOfflineResources) - PrefetchHref(hrefVal, element, PR_TRUE, PR_TRUE); + AddOfflineResource(hrefVal, element); } } } diff --git a/docshell/build/Makefile.in b/docshell/build/Makefile.in index 3eef8e060ff..bff9e639cbf 100644 --- a/docshell/build/Makefile.in +++ b/docshell/build/Makefile.in @@ -60,6 +60,7 @@ REQUIRES = xpcom \ js \ shistory \ necko \ + nkcache \ gfx \ content \ layout \ diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp index a1d76ffe185..bbdc6247b08 100644 --- a/docshell/build/nsDocShellModule.cpp +++ b/docshell/build/nsDocShellModule.cpp @@ -54,6 +54,7 @@ #include "nsOSHelperAppService.h" #include "nsExternalProtocolHandler.h" #include "nsPrefetchService.h" +#include "nsOfflineCacheUpdate.h" #include "nsHandlerAppImpl.h" // session history @@ -100,6 +101,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSHelperAppService, Init) NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlockedExternalProtocolHandler) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefetchService, Init) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheUpdateService, + nsOfflineCacheUpdateService::GetInstance) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsOfflineCacheUpdate) NS_GENERIC_FACTORY_CONSTRUCTOR(nsLocalHandlerApp) #if defined(XP_MAC) || defined(XP_MACOSX) @@ -205,6 +209,10 @@ static const nsModuleComponentInfo gDocShellModuleInfo[] = { nsBlockedExternalProtocolHandlerConstructor, }, { NS_PREFETCHSERVICE_CLASSNAME, NS_PREFETCHSERVICE_CID, NS_PREFETCHSERVICE_CONTRACTID, nsPrefetchServiceConstructor, }, + { NS_OFFLINECACHEUPDATESERVICE_CLASSNAME, NS_OFFLINECACHEUPDATESERVICE_CID, NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, + nsOfflineCacheUpdateServiceConstructor, }, + { NS_OFFLINECACHEUPDATE_CLASSNAME, NS_OFFLINECACHEUPDATE_CID, NS_OFFLINECACHEUPDATE_CONTRACTID, + nsOfflineCacheUpdateConstructor, }, { "Local Application Handler App", NS_LOCALHANDLERAPP_CID, NS_LOCALHANDLERAPP_CONTRACTID, nsLocalHandlerAppConstructor, }, #if defined(XP_MAC) || defined(XP_MACOSX) diff --git a/dom/src/base/Makefile.in b/dom/src/base/Makefile.in index bf312b7f142..329f353a7d9 100644 --- a/dom/src/base/Makefile.in +++ b/dom/src/base/Makefile.in @@ -66,6 +66,7 @@ REQUIRES = xpcom \ java \ locale \ uriloader \ + prefetch \ xuldoc \ webshell \ view \ diff --git a/dom/src/offline/nsDOMOfflineLoadStatusList.cpp b/dom/src/offline/nsDOMOfflineLoadStatusList.cpp index f102b3d58d7..d5711171415 100644 --- a/dom/src/offline/nsDOMOfflineLoadStatusList.cpp +++ b/dom/src/offline/nsDOMOfflineLoadStatusList.cpp @@ -154,6 +154,7 @@ NS_INTERFACE_MAP_BEGIN(nsDOMOfflineLoadStatusList) NS_INTERFACE_MAP_ENTRY(nsIDOMLoadStatusList) NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(LoadStatusList) NS_INTERFACE_MAP_END @@ -185,114 +186,31 @@ nsDOMOfflineLoadStatusList::Init() nsresult rv = mURI->GetHostPort(mHostPort); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr serv = - do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); + nsCOMPtr cacheUpdateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr session; - rv = serv->CreateSession("HTTP-offline", - nsICache::STORE_OFFLINE, - nsICache::STREAM_BASED, - getter_AddRefs(session)); + PRUint32 numUpdates; + rv = cacheUpdateService->GetNumUpdates(&numUpdates); NS_ENSURE_SUCCESS(rv, rv); - mCacheSession = do_QueryInterface(session, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - // get the current list of loads from the prefetch queue - nsCOMPtr prefetchService = - do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr e; - rv = prefetchService->EnumerateQueue(PR_FALSE, PR_TRUE, getter_AddRefs(e)); - NS_ENSURE_SUCCESS(rv, rv); - - PRBool more; - while (NS_SUCCEEDED(rv = e->HasMoreElements(&more)) && more) { - nsCOMPtr status; - rv = e->GetNext(getter_AddRefs(status)); + for (PRUint32 i = 0; i < numUpdates; i++) { + nsCOMPtr cacheUpdate; + rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate)); NS_ENSURE_SUCCESS(rv, rv); - PRBool shouldInclude; - rv = ShouldInclude(status, &shouldInclude); + rv = WatchUpdate(cacheUpdate); NS_ENSURE_SUCCESS(rv, rv); - - if (!shouldInclude) { - continue; - } - - nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status); - if (!wrapper) return NS_ERROR_OUT_OF_MEMORY; - - mItems.AppendObject(wrapper); } - NS_ENSURE_SUCCESS(rv, rv); - // watch for changes in the prefetch queue + // watch for new offline cache updates nsCOMPtr observerServ = do_GetService("@mozilla.org/observer-service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); - rv = observerServ->AddObserver(this, "offline-load-requested", PR_TRUE); + rv = observerServ->AddObserver(this, "offline-cache-update-added", PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); - rv = observerServ->AddObserver(this, "offline-load-completed", PR_TRUE); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -nsresult -nsDOMOfflineLoadStatusList::ShouldInclude(nsIDOMLoadStatus *aStatus, - PRBool *aShouldInclude) -{ - *aShouldInclude = PR_FALSE; - - nsAutoString uriStr; - nsresult rv = aStatus->GetUri(uriStr); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr uri; - rv = NS_NewURI(getter_AddRefs(uri), uriStr); - NS_ENSURE_SUCCESS(rv, rv); - - nsCAutoString hostport; - rv = uri->GetHostPort(hostport); - NS_ENSURE_SUCCESS(rv, rv); - - if (hostport != mHostPort) - return NS_OK; - - // Check that the URL is owned by this domain - nsCAutoString spec; - rv = uri->GetSpec(spec); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr source; - rv = aStatus->GetSource(getter_AddRefs(source)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCAutoString ownerURI; - if (source) { - // Came from a element, check that it's owned by this URI - rv = mURI->GetSpec(ownerURI); - NS_ENSURE_SUCCESS(rv, rv); - } else { - // Didn't come from a element, check that it's owned by - // resource list (no owner URI) - ownerURI.Truncate(); - } - - PRBool owned; - rv = mCacheSession->KeyIsOwned(mHostPort, ownerURI, spec, &owned); - NS_ENSURE_SUCCESS(rv, rv); - if (!owned) { - return NS_OK; - } - - *aShouldInclude = PR_TRUE; - return NS_OK; } @@ -312,6 +230,45 @@ nsDOMOfflineLoadStatusList::FindWrapper(nsIDOMLoadStatus *aStatus, return nsnull; } +nsresult +nsDOMOfflineLoadStatusList::WatchUpdate(nsIOfflineCacheUpdate *aUpdate) +{ + nsCAutoString owner; + nsresult rv = aUpdate->GetUpdateDomain(owner); + NS_ENSURE_SUCCESS(rv, rv); + + if (owner != mHostPort) { + // This update doesn't belong to us + return NS_OK; + } + + PRUint32 numItems; + rv = aUpdate->GetCount(&numItems); + NS_ENSURE_SUCCESS(rv, rv); + + for (PRUint32 i = 0; i < numItems; i++) { + nsCOMPtr status; + rv = aUpdate->Item(i, getter_AddRefs(status)); + NS_ENSURE_SUCCESS(rv, rv); + + nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status); + if (!wrapper) return NS_ERROR_OUT_OF_MEMORY; + + mItems.AppendObject(wrapper); + + rv = SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR), + mLoadRequestedEventListeners, + wrapper); + NS_ENSURE_SUCCESS(rv, rv); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = aUpdate->AddObserver(this, PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + // // nsDOMOfflineLoadStatusList::nsIDOMLoadStatusList // @@ -508,39 +465,32 @@ nsDOMOfflineLoadStatusList::Observe(nsISupports *aSubject, const PRUnichar *aData) { nsresult rv; - if (!strcmp(aTopic, "offline-load-requested")) { - nsCOMPtr status = do_QueryInterface(aSubject); - if (status) { - PRBool shouldInclude; - rv = ShouldInclude(status, &shouldInclude); - NS_ENSURE_SUCCESS(rv, rv); - - if (!shouldInclude) return NS_OK; - - nsDOMOfflineLoadStatus *wrapper = new nsDOMOfflineLoadStatus(status); - if (!wrapper) return NS_ERROR_OUT_OF_MEMORY; - - mItems.AppendObject(wrapper); - - rv = SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR), - mLoadRequestedEventListeners, - wrapper); + if (!strcmp(aTopic, "offline-cache-update-added")) { + nsCOMPtr update = do_QueryInterface(aSubject); + if (update) { + rv = WatchUpdate(update); NS_ENSURE_SUCCESS(rv, rv); } - } else if (!strcmp(aTopic, "offline-load-completed")) { - nsCOMPtr status = do_QueryInterface(aSubject); - if (status) { - PRUint32 index; - nsCOMPtr wrapper = FindWrapper(status, &index); - if (wrapper) { - mItems.RemoveObjectAt(index); + } - rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR), - mLoadCompletedEventListeners, - wrapper); - NS_ENSURE_SUCCESS(rv, rv); - } - } + return NS_OK; +} + +// +// nsDOMLoadStatusList::nsIOfflineCacheUpdateObserver +// + +NS_IMETHODIMP +nsDOMOfflineLoadStatusList::ItemCompleted(nsIDOMLoadStatus *aItem) +{ + PRUint32 index; + nsCOMPtr wrapper = FindWrapper(aItem, &index); + if (wrapper) { + mItems.RemoveObjectAt(index); + nsresult rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR), + mLoadCompletedEventListeners, + wrapper); + NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; diff --git a/dom/src/offline/nsDOMOfflineLoadStatusList.h b/dom/src/offline/nsDOMOfflineLoadStatusList.h index de050952d2e..b347e604ad2 100644 --- a/dom/src/offline/nsDOMOfflineLoadStatusList.h +++ b/dom/src/offline/nsDOMOfflineLoadStatusList.h @@ -43,7 +43,7 @@ #include "nsIDOMLoadStatus.h" #include "nsIDOMLoadStatusEvent.h" #include "nsIDOMLoadStatusList.h" -#include "nsIOfflineCacheSession.h" +#include "nsIOfflineCacheUpdate.h" #include "nsCOMPtr.h" #include "nsCOMArray.h" #include "nsIURI.h" @@ -59,6 +59,7 @@ class nsDOMOfflineLoadStatus; class nsDOMOfflineLoadStatusList : public nsIDOMLoadStatusList, public nsIDOMEventTarget, public nsIObserver, + public nsIOfflineCacheUpdateObserver, public nsSupportsWeakReference { public: @@ -66,6 +67,7 @@ public: NS_DECL_NSIDOMLOADSTATUSLIST NS_DECL_NSIDOMEVENTTARGET NS_DECL_NSIOBSERVER + NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER nsDOMOfflineLoadStatusList(nsIURI *aURI); virtual ~nsDOMOfflineLoadStatusList(); @@ -73,8 +75,7 @@ public: nsresult Init(); private : - nsresult ShouldInclude (nsIDOMLoadStatus *aStatus, - PRBool *aInclude); + nsresult WatchUpdate (nsIOfflineCacheUpdate *aUpdate); nsIDOMLoadStatus *FindWrapper (nsIDOMLoadStatus *aStatus, PRUint32 *aIndex); void NotifyEventListeners(const nsCOMArray& aListeners, @@ -89,7 +90,6 @@ private : nsCOMPtr mURI; nsCOMArray mItems; nsCString mHostPort; - nsCOMPtr mCacheSession; nsCOMPtr mScriptContext; diff --git a/dom/src/offline/nsDOMOfflineResourceList.cpp b/dom/src/offline/nsDOMOfflineResourceList.cpp index c95464b8e69..6259564ad5f 100644 --- a/dom/src/offline/nsDOMOfflineResourceList.cpp +++ b/dom/src/offline/nsDOMOfflineResourceList.cpp @@ -46,6 +46,8 @@ #include "nsICacheSession.h" #include "nsICacheService.h" #include "nsIOfflineCacheSession.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsIDOMLoadStatus.h" #include "nsAutoPtr.h" #include "nsContentUtils.h" @@ -165,16 +167,6 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI) rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); NS_ENSURE_SUCCESS(rv, rv); - // only http/https urls will work offline - PRBool match; - rv = requestedURI->SchemeIs("http", &match); - NS_ENSURE_SUCCESS(rv, rv); - if (!match) { - rv = requestedURI->SchemeIs("https", &match); - NS_ENSURE_SUCCESS(rv, rv); - if (!match) return NS_ERROR_DOM_BAD_URI; - } - PRUint32 length; rv = GetLength(&length); NS_ENSURE_SUCCESS(rv, rv); @@ -185,23 +177,20 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI) ClearCachedKeys(); - nsCAutoString key; - rv = GetCacheKey(requestedURI, key); + nsCOMPtr update = + do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - rv = mCacheSession->AddOwnedKey(mHostPort, - NS_LITERAL_CSTRING(""), - key); + rv = update->Init(PR_TRUE, mHostPort, NS_LITERAL_CSTRING(""), mURI); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr prefetchService = - do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv); + rv = update->AddURI(requestedURI, nsnull); NS_ENSURE_SUCCESS(rv, rv); - return prefetchService->PrefetchURIForOfflineUse(requestedURI, - mURI, - nsnull, - PR_TRUE); + rv = update->Schedule(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; } NS_IMETHODIMP @@ -254,28 +243,17 @@ nsDOMOfflineResourceList::Refresh() nsresult rv = Init(); NS_ENSURE_SUCCESS(rv, rv); - rv = CacheKeys(); + nsCOMPtr update = + do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - // try to start fetching it now, but it's not fatal if it fails - nsCOMPtr prefetchService = - do_GetService(NS_PREFETCHSERVICE_CONTRACTID, &rv); + rv = update->Init(PR_FALSE, mHostPort, NS_LITERAL_CSTRING(""), mURI); NS_ENSURE_SUCCESS(rv, rv); - for (PRUint32 i = 0; i < gCachedKeysCount; i++) { - // this will fail if the URI is not absolute - nsCOMPtr requestedURI; - nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), gCachedKeys[i]); - NS_ENSURE_SUCCESS(rv, rv); + rv = update->Schedule(); + NS_ENSURE_SUCCESS(rv, rv); - rv = prefetchService->PrefetchURIForOfflineUse(requestedURI, - mURI, - nsnull, - PR_TRUE); - NS_ENSURE_SUCCESS(rv, rv); - } - - return NS_OK; + return rv; } nsresult diff --git a/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html b/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html index 55f24dc46ab..d277f56b975 100644 --- a/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html +++ b/dom/tests/mochitest/ajax/offline/test_pendingOfflineLoads.html @@ -104,10 +104,6 @@ function run_test() load_completed, false); - // The should already be in the queue - ok(navigator.pendingOfflineLoads.length == 2, - " loads should already be in the queue"); - for (var i = 0; i < navigator.pendingOfflineLoads.length; i++) { var load = navigator.pendingOfflineLoads[i]; check_load_added(navigator.pendingOfflineLoads[i]); diff --git a/netwerk/cache/public/nsIOfflineCacheSession.idl b/netwerk/cache/public/nsIOfflineCacheSession.idl index b1f83f3e7d6..93607279046 100644 --- a/netwerk/cache/public/nsIOfflineCacheSession.idl +++ b/netwerk/cache/public/nsIOfflineCacheSession.idl @@ -40,7 +40,7 @@ #include "nsISupports.idl" #include "nsICache.idl" -[scriptable, uuid(e9581d9f-3c0c-4722-8d2b-3d18f8d41299)] +[scriptable, uuid(0058c32b-0d93-4cf8-a561-e6f749c8a7b1)] interface nsIOfflineCacheSession : nsISupports { /** @@ -66,6 +66,26 @@ interface nsIOfflineCacheSession : nsISupports * the domain. */ + /** + * Gets the list of owner domains in the cache. + * + * @param count The number of domains returned + * @param uris The domains that own resources in the cache + */ + void getOwnerDomains(out unsigned long count, + [array, size_is(count)]out string domains); + + /** + * Gets the list of owner URIs associated with a domain. + * + * @param ownerDomain The domain to query + * @param count The number of uris returned + * @param uris The uris in this domain that own resources + */ + void getOwnerURIs(in ACString ownerDomain, + out unsigned long count, + [array, size_is(count)]out string uris); + /** * Sets the resources owned by a given domain/URI pair. * diff --git a/netwerk/cache/src/nsCacheService.cpp b/netwerk/cache/src/nsCacheService.cpp index 2f54d7b6617..81dc3e03205 100644 --- a/netwerk/cache/src/nsCacheService.cpp +++ b/netwerk/cache/src/nsCacheService.cpp @@ -830,6 +830,49 @@ nsCacheService::IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy storagePo return PR_FALSE; } + +nsresult nsCacheService::GetOfflineOwnerDomains(nsCacheSession * session, + PRUint32 * count, + char *** domains) +{ +#ifdef NECKO_OFFLINE_CACHE + if (session->StoragePolicy() != nsICache::STORE_OFFLINE) + return NS_ERROR_NOT_AVAILABLE; + + if (!gService->mOfflineDevice) { + nsresult rv = gService->CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + + return gService->mOfflineDevice->GetOwnerDomains(session->ClientID()->get(), + count, domains); +#else // !NECKO_OFFLINE_CACHE + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + + +nsresult nsCacheService::GetOfflineOwnerURIs(nsCacheSession * session, + const nsACString & ownerDomain, + PRUint32 * count, + char *** uris) +{ +#ifdef NECKO_OFFLINE_CACHE + if (session->StoragePolicy() != nsICache::STORE_OFFLINE) + return NS_ERROR_NOT_AVAILABLE; + + if (!gService->mOfflineDevice) { + nsresult rv = gService->CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + + return gService->mOfflineDevice->GetOwnerURIs(session->ClientID()->get(), + ownerDomain, count, uris); +#else // !NECKO_OFFLINE_CACHE + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + nsresult nsCacheService::SetOfflineOwnedKeys(nsCacheSession * session, const nsACString & ownerDomain, diff --git a/netwerk/cache/src/nsCacheService.h b/netwerk/cache/src/nsCacheService.h index d676b335f2c..b8e9b278194 100644 --- a/netwerk/cache/src/nsCacheService.h +++ b/netwerk/cache/src/nsCacheService.h @@ -98,6 +98,14 @@ public: PRBool * result); + static nsresult GetOfflineOwnerDomains(nsCacheSession * session, + PRUint32 * count, + char *** domains); + static nsresult GetOfflineOwnerURIs(nsCacheSession * session, + const nsACString & ownerDomain, + PRUint32 * count, + char *** uris); + static nsresult SetOfflineOwnedKeys(nsCacheSession * session, const nsACString & ownerDomain, const nsACString & ownerUri, diff --git a/netwerk/cache/src/nsCacheSession.cpp b/netwerk/cache/src/nsCacheSession.cpp index ed02bb34dac..683be5ee7bf 100644 --- a/netwerk/cache/src/nsCacheSession.cpp +++ b/netwerk/cache/src/nsCacheSession.cpp @@ -136,6 +136,19 @@ NS_IMETHODIMP nsCacheSession::IsStorageEnabled(PRBool *result) return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result); } +NS_IMETHODIMP nsCacheSession::GetOwnerDomains(PRUint32 * count, + char *** domains) +{ + return nsCacheService::GetOfflineOwnerDomains(this, count, domains); +} + +NS_IMETHODIMP nsCacheSession::GetOwnerURIs(const nsACString & domain, + PRUint32 * count, + char *** uris) +{ + return nsCacheService::GetOfflineOwnerURIs(this, domain, count, uris); +} + NS_IMETHODIMP nsCacheSession::SetOwnedKeys(const nsACString & domain, const nsACString & uri, PRUint32 count, diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp index 61a4928fc54..5a742451883 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp @@ -789,7 +789,9 @@ nsOfflineCacheDevice::Init() StatementSql ( mStatement_AddOwnership, "INSERT INTO moz_cache_owners (ClientID, Domain, URI, Key) VALUES (?, ?, ?, ?);" ), StatementSql ( mStatement_CheckOwnership, "SELECT Key From moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ? AND Key = ?;" ), StatementSql ( mStatement_ListOwned, "SELECT Key FROM moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ?;" ), - StatementSql ( mStatement_DeleteUnowned, "DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_owners ON (moz_cache.ClientID = moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key) WHERE moz_cache.ClientID = ? AND moz_cache_owners.Domain ISNULL);" ), + StatementSql ( mStatement_ListOwnerDomains, "SELECT DISTINCT Domain FROM moz_cache_owners WHERE ClientID = ?;"), + StatementSql ( mStatement_ListOwnerURIs, "SELECT DISTINCT URI FROM moz_cache_owners WHERE ClientID = ? AND Domain = ?;"), + StatementSql ( mStatement_DeleteUnowned, "DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_owners ON (moz_cache.ClientID = moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key) WHERE moz_cache.ClientID = ? AND moz_cache_owners.Domain ISNULL);" ) }; for (PRUint32 i=0; iExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray valArray; + while (hasRows) + { + PRUint32 length; + valArray.AppendElement( + nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length))); + + rv = statement->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + } + + *count = valArray.Length(); + char **ret = static_cast(NS_Alloc(*count * sizeof(char*))); + if (!ret) return NS_ERROR_OUT_OF_MEMORY; + + for (PRUint32 i = 0; i < *count; i++) { + ret[i] = NS_strdup(valArray[i].get()); + if (!ret[i]) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + *values = ret; + + return NS_OK; +} + +nsresult +nsOfflineCacheDevice::GetOwnerDomains(const char * clientID, + PRUint32 * count, + char *** domains) +{ + LOG(("nsOfflineCacheDevice::GetOwnerDomains [cid=%s]\n", clientID)); + + AutoResetStatement statement(mStatement_ListOwnerDomains); + nsresult rv = statement->BindUTF8StringParameter( + 0, nsDependentCString(clientID)); + NS_ENSURE_SUCCESS(rv, rv); + + return RunSimpleQuery(mStatement_ListOwnerDomains, 0, count, domains); +} + +nsresult +nsOfflineCacheDevice::GetOwnerURIs(const char * clientID, + const nsACString & ownerDomain, + PRUint32 * count, + char *** domains) +{ + LOG(("nsOfflineCacheDevice::GetOwnerURIs [cid=%s]\n", clientID)); + + AutoResetStatement statement(mStatement_ListOwnerURIs); + nsresult rv = statement->BindUTF8StringParameter( + 0, nsDependentCString(clientID)); + rv = statement->BindUTF8StringParameter( + 1, ownerDomain); + NS_ENSURE_SUCCESS(rv, rv); + + return RunSimpleQuery(mStatement_ListOwnerURIs, 0, count, domains); +} + nsresult nsOfflineCacheDevice::SetOwnedKeys(const char * clientID, const nsACString & ownerDomain, @@ -1305,36 +1378,7 @@ nsOfflineCacheDevice::GetOwnedKeys(const char * clientID, rv |= statement->BindUTF8StringParameter(2, ownerURI); NS_ENSURE_SUCCESS(rv, rv); - PRBool hasRows; - rv = statement->ExecuteStep(&hasRows); - NS_ENSURE_SUCCESS(rv, rv); - - nsTArray keyArray; - while (hasRows) - { - PRUint32 length; - keyArray.AppendElement( - nsDependentCString(statement->AsSharedUTF8String(0, &length))); - - rv = statement->ExecuteStep(&hasRows); - NS_ENSURE_SUCCESS(rv, rv); - } - - *count = keyArray.Length(); - char **ret = static_cast(NS_Alloc(*count * sizeof(char*))); - if (!ret) return NS_ERROR_OUT_OF_MEMORY; - - for (PRUint32 i = 0; i < *count; i++) { - ret[i] = NS_strdup(keyArray[i].get()); - if (!ret[i]) { - NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret); - return NS_ERROR_OUT_OF_MEMORY; - } - } - - *keys = ret; - - return NS_OK; + return RunSimpleQuery(mStatement_ListOwned, 0, count, keys); } nsresult diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.h b/netwerk/cache/src/nsDiskCacheDeviceSQL.h index 23ff60d3ea7..3f12e5314ab 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h @@ -86,6 +86,13 @@ public: /* Entry ownership */ + nsresult GetOwnerDomains(const char * clientID, + PRUint32 * count, + char *** domains); + nsresult GetOwnerURIs(const char * clientID, + const nsACString & ownerDomain, + PRUint32 * count, + char *** uris); nsresult SetOwnedKeys(const char * clientID, const nsACString & ownerDomain, const nsACString & ownerUrl, @@ -135,6 +142,10 @@ private: nsresult DeleteData(nsCacheEntry *entry); nsresult EnableEvictionObserver(); nsresult DisableEvictionObserver(); + nsresult RunSimpleQuery(mozIStorageStatement *statment, + PRUint32 resultIndex, + PRUint32 * count, + char *** values); nsCOMPtr mDB; nsCOMPtr mStatement_CacheSize; @@ -152,6 +163,8 @@ private: nsCOMPtr mStatement_CheckOwnership; nsCOMPtr mStatement_DeleteUnowned; nsCOMPtr mStatement_ListOwned; + nsCOMPtr mStatement_ListOwnerDomains; + nsCOMPtr mStatement_ListOwnerURIs; nsCOMPtr mCacheDirectory; PRUint32 mCacheCapacity; diff --git a/uriloader/prefetch/Makefile.in b/uriloader/prefetch/Makefile.in index a0efc71d871..175be859c6f 100644 --- a/uriloader/prefetch/Makefile.in +++ b/uriloader/prefetch/Makefile.in @@ -57,9 +57,12 @@ REQUIRES = xpcom \ CPPSRCS = \ nsPrefetchService.cpp \ + nsOfflineCacheUpdate.cpp \ $(NULL) + XPIDLSRCS = \ nsIPrefetchService.idl \ + nsIOfflineCacheUpdate.idl \ $(NULL) EXPORTS = \ nsCPrefetchService.h \ diff --git a/uriloader/prefetch/nsCPrefetchService.h b/uriloader/prefetch/nsCPrefetchService.h index c117b272294..a0af427788c 100644 --- a/uriloader/prefetch/nsCPrefetchService.h +++ b/uriloader/prefetch/nsCPrefetchService.h @@ -55,4 +55,37 @@ {0xbe, 0x83, 0xa8, 0x1b, 0x7c, 0x0f, 0x63, 0xbf} \ } +/** + * nsOfflineCacheUpdateService : nsIOfflineCacheUpdateService + */ + +#define NS_OFFLINECACHEUPDATESERVICE_CLASSNAME \ + "nsOfflineCacheUpdateService" +#define NS_OFFLINECACHEUPDATESERVICE_CONTRACTID \ + "@mozilla.org/offlinecacheupdate-service;1" +#define NS_OFFLINECACHEUPDATESERVICE_CID \ +{ /* ec06f3fc-70db-4ecd-94e0-a6e91ca44d8a */ \ + 0xec06f3fc, \ + 0x70db, \ + 0x4ecd , \ + {0x94, 0xe0, 0xa6, 0xe9, 0x1c, 0xa4, 0x4d, 0x8a} \ +} + +/** + * nsOfflineCacheUpdate : nsIOfflineCacheUpdate + */ + +#define NS_OFFLINECACHEUPDATE_CLASSNAME \ + "nsOfflineCacheUpdate" +#define NS_OFFLINECACHEUPDATE_CONTRACTID \ + "@mozilla.org/offlinecacheupdate;1" +#define NS_OFFLINECACHEUPDATE_CID \ +{ /* e56f5e01-c7cc-4675-a9d7-b8f1e4127295 */ \ + 0xe56f5e01, \ + 0xc7cc, \ + 0x4675, \ + {0xa9, 0xd7, 0xb8, 0xf1, 0xe4, 0x12, 0x72, 0x95} \ +} + + #endif // !nsCPrefetchService_h__ diff --git a/uriloader/prefetch/nsIOfflineCacheUpdate.idl b/uriloader/prefetch/nsIOfflineCacheUpdate.idl new file mode 100644 index 00000000000..bd6bdef76cf --- /dev/null +++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl @@ -0,0 +1,159 @@ +/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Camp + * + * 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 "nsISupports.idl" + +interface nsIURI; +interface nsIDOMNode; +interface nsIDOMDocument; +interface nsIDOMLoadStatus; + +[scriptable, uuid(e0785ebb-b3a1-426a-a70e-be2b923e973e)] +interface nsIOfflineCacheUpdateObserver : nsISupports { + /** + * An item has finished loading. + * + * @param aItem load status for the item that completed. + */ + void itemCompleted(in nsIDOMLoadStatus aItem); +}; + +/** + * An nsIOfflineCacheUpdate is used to update a domain's offline resources. + * It can be used to perform partial or complete updates. + * + * Each update object maintains a list of nsIDOMLoadStatus items for the + * resources it is updating. The list of these items will be available + * after the object is scheduled. + * + * One update object will be updating at a time. The active object will + * load its items one by one, sending itemCompleted() to any registered + * observers. + */ +[scriptable, uuid(7dc06ede-1098-4384-b95e-65525ab254c9)] +interface nsIOfflineCacheUpdate : nsISupports { + /** + * The domain being updated, and the domain that will own any URIs added + * with this update. + */ + readonly attribute ACString updateDomain; + + /** + * The URI that will own any URIs added by this update + */ + readonly attribute ACString ownerURI; + + /** + * Initialize the update. + * + * @param aPartialUpdate + * TRUE if the update should just update the URIs given to it, + * FALSE if all URLs for the owner domain should be added. + * @param aUpdateDomain + * The domain which is being updated, and which will own any + * URIs added. + * @param aOwnerURI + * The owner URI for any URIs added. + * @param aReferrerURI + * The page that is requesting the update. + */ + void init(in boolean aPartialUpdate, + in ACString aUpdateDomain, + in ACString aOwnerURI, + in nsIURI aReferrerURI); + + /** + * Add a URI to the offline cache as part of the update. + * + * @param aURI + * The URI to add. + * @param aSource + * The DOM node ( tag) associated with this node (for + * implementing nsIDOMLoadStatus). + */ + void addURI(in nsIURI aURI, in nsIDOMNode aSource); + + /** + * Add the update to the offline update queue. An offline-cache-update-added + * event will be sent to the observer service. + */ + void schedule(); + + /** + * Request that the update be scheduled when a document finishes loading. + * + * @param aDocument + * When this document finishes loading, the update will be scheduled. + */ + void scheduleOnDocumentStop(in nsIDOMDocument aDocument); + + /** + * Access to the list of items in the update. + */ + readonly attribute unsigned long count; + nsIDOMLoadStatus item(in unsigned long index); + + /** + * Observe loads that are added to the update. + * + * @param aObserver + * object that notifications will be sent to. + * @param aHoldWeak + * TRUE if you want the update to hold a weak reference to the + * observer, FALSE for a strong reference. + */ + void addObserver(in nsIOfflineCacheUpdateObserver aObserver, + in boolean aHoldWeak); + + /** + * Remove an observer from the update. + * + * @param aObserver + * the observer to remove. + */ + void removeObserver(in nsIOfflineCacheUpdateObserver aObserver); +}; + +[scriptable, uuid(f99ca10f-5cde-4966-b845-433f2921a201)] +interface nsIOfflineCacheUpdateService : nsISupports { + /** + * Access to the list of cache updates that have been scheduled. + */ + readonly attribute unsigned long numUpdates; + nsIOfflineCacheUpdate getUpdate(in unsigned long index); +}; diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp new file mode 100644 index 00000000000..5be4c651290 --- /dev/null +++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp @@ -0,0 +1,1058 @@ +/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Camp + * + * 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 "nsOfflineCacheUpdate.h" + +#include "nsCPrefetchService.h" +#include "nsCURILoader.h" +#include "nsICache.h" +#include "nsICacheService.h" +#include "nsICacheSession.h" +#include "nsICachingChannel.h" +#include "nsIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIOfflineCacheSession.h" +#include "nsIWebProgress.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "prlog.h" + +static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nsnull; + +#if defined(PR_LOGGING) +// +// To enable logging (see prlog.h for full details): +// +// set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5 +// set NSPR_LOG_FILE=offlineupdate.log +// +// this enables PR_LOG_ALWAYS level information and places all output in +// the file offlineupdate.log +// +static PRLogModuleInfo *gOfflineCacheUpdateLog; +#endif +#define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args) +#define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4) + +class AutoFreeArray { +public: + AutoFreeArray(PRUint32 count, char **values) + : mCount(count), mValues(values) {}; + ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); } +private: + PRUint32 mCount; + char **mValues; +}; + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS5(nsOfflineCacheUpdateItem, + nsIDOMLoadStatus, + nsIRequestObserver, + nsIStreamListener, + nsIInterfaceRequestor, + nsIChannelEventSink) + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem +//----------------------------------------------------------------------------- + +nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate, + nsIURI *aURI, + nsIURI *aReferrerURI, + nsIDOMNode *aSource) + : mURI(aURI) + , mReferrerURI(aReferrerURI) + , mUpdate(aUpdate) + , mChannel(nsnull) + , mState(nsIDOMLoadStatus::UNINITIALIZED) + , mBytesRead(0) +{ + mSource = do_GetWeakReference(aSource); +} + +nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem() +{ +} + +nsresult +nsOfflineCacheUpdateItem::OpenChannel() +{ + nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), + mURI, + nsnull, nsnull, this, + nsIRequest::LOAD_BACKGROUND | + nsICachingChannel::LOAD_ONLY_IF_MODIFIED); + NS_ENSURE_SUCCESS(rv, rv); + + // configure HTTP specific stuff + nsCOMPtr httpChannel = + do_QueryInterface(mChannel); + if (httpChannel) { + httpChannel->SetReferrer(mReferrerURI); + httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), + NS_LITERAL_CSTRING("offline-resource"), + PR_FALSE); + } + + nsCOMPtr cachingChannel = + do_QueryInterface(mChannel); + if (cachingChannel) { + rv = cachingChannel->SetCacheForOfflineUse(PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mChannel->AsyncOpen(this, nsnull); + NS_ENSURE_SUCCESS(rv, rv); + + mState = nsIDOMLoadStatus::REQUESTED; + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdateItem::Cancel() +{ + if (mChannel) { + mChannel->Cancel(NS_ERROR_ABORT); + mChannel = nsnull; + } + + mState = nsIDOMLoadStatus::UNINITIALIZED; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + mState = nsIDOMLoadStatus::RECEIVING; + + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aStream, + PRUint32 aOffset, + PRUint32 aCount) +{ + PRUint32 bytesRead = 0; + aStream->ReadSegments(NS_DiscardSegment, nsnull, aCount, &bytesRead); + mBytesRead += bytesRead; + LOG(("loaded %u bytes into offline cache [offset=%u]\n", + bytesRead, aOffset)); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus) +{ + LOG(("done fetching offline item [status=%x]\n", aStatus)); + + mState = nsIDOMLoadStatus::LOADED; + + if (mBytesRead == 0 && aStatus == NS_OK) { + // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was + // specified), but the object should report loadedSize as if it + // did. + mChannel->GetContentLength(&mBytesRead); + } + + mUpdate->LoadCompleted(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsIInterfaceRequestor +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult) +{ + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + NS_ADDREF_THIS(); + *aResult = static_cast(this); + return NS_OK; + } + + return NS_ERROR_NO_INTERFACE; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsIChannelEventSink +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel, + nsIChannel *aNewChannel, + PRUint32 aFlags) +{ + nsCOMPtr newURI; + nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr oldCachingChannel = + do_QueryInterface(aOldChannel); + nsCOMPtr newCachingChannel = + do_QueryInterface(aOldChannel); + if (newCachingChannel) + newCachingChannel->SetCacheForOfflineUse(PR_TRUE); + + PRBool match; + rv = newURI->SchemeIs("http", &match); + if (NS_FAILED(rv) || !match) { + if (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. + nsCOMPtr httpChannel = do_QueryInterface(aNewChannel); + NS_ENSURE_STATE(httpChannel); + + httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), + NS_LITERAL_CSTRING("offline-resource"), + PR_FALSE); + + mChannel = aNewChannel; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsIDOMLoadStatus +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetSource(nsIDOMNode **aSource) +{ + if (mSource) { + return CallQueryReferent(mSource.get(), aSource); + } else { + *aSource = nsnull; + return NS_OK; + } +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetUri(nsAString &aURI) +{ + nsCAutoString spec; + nsresult rv = mURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF8toUTF16(spec, aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetTotalSize(PRInt32 *aTotalSize) +{ + if (mChannel) { + return mChannel->GetContentLength(aTotalSize); + } + + *aTotalSize = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetLoadedSize(PRInt32 *aLoadedSize) +{ + *aLoadedSize = mBytesRead; + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetReadyState(PRUint16 *aReadyState) +{ + *aReadyState = mState; + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateItem::GetStatus(PRUint16 *aStatus) +{ + if (!mChannel) { + *aStatus = 0; + return NS_OK; + } + + nsresult rv; + nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 httpStatus; + rv = httpChannel->GetResponseStatus(&httpStatus); + if (rv == NS_ERROR_NOT_AVAILABLE) { + // Someone's calling this before we got a response... Check our + // ReadyState. If we're at RECEIVING or LOADED, then this means the + // connection errored before we got any data; return a somewhat + // sensible error code in that case. + if (mState >= nsIDOMLoadStatus::RECEIVING) { + *aStatus = NS_ERROR_NOT_AVAILABLE; + return NS_OK; + } + + *aStatus = 0; + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + *aStatus = PRUint16(httpStatus); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdate::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS1(nsOfflineCacheUpdate, + nsIOfflineCacheUpdate); + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdate +//----------------------------------------------------------------------------- + +nsOfflineCacheUpdate::nsOfflineCacheUpdate() + : mState(STATE_UNINITIALIZED) + , mAddedItems(PR_FALSE) + , mPartialUpdate(PR_FALSE) +{ +} + +nsOfflineCacheUpdate::~nsOfflineCacheUpdate() +{ + LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this)); +} + +nsresult +nsOfflineCacheUpdate::Init(PRBool aPartialUpdate, + const nsACString &aUpdateDomain, + const nsACString &aOwnerURI, + nsIURI *aReferrerURI) +{ + nsresult rv; + + // Make sure the service has been initialized + if (!nsOfflineCacheUpdateService::GetInstance()) { + return NS_ERROR_FAILURE; + } + + LOG(("nsOfflineCacheUpdate::Init [%p]", this)); + + mPartialUpdate = aPartialUpdate; + mUpdateDomain = aUpdateDomain; + mOwnerURI = aOwnerURI; + mReferrerURI = aReferrerURI; + + nsCOMPtr cacheService = + do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr session; + rv = cacheService->CreateSession("HTTP-offline", + nsICache::STORE_OFFLINE, + nsICache::STREAM_BASED, + getter_AddRefs(session)); + NS_ENSURE_SUCCESS(rv, rv); + + mCacheSession = do_QueryInterface(session, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mState = STATE_INITIALIZED; + + return NS_OK; +} + +void +nsOfflineCacheUpdate::LoadCompleted() +{ + nsresult rv; + + LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this)); + + NS_ASSERTION(mItems.Length() >= 1, "Unknown load completed"); + + nsRefPtr item = mItems[0]; + mItems.RemoveElementAt(0); + + rv = NotifyCompleted(item); + if (NS_FAILED(rv)) return; + + ProcessNextURI(); +} + +nsresult +nsOfflineCacheUpdate::Begin() +{ + LOG(("nsOfflineCacheUpdate::Begin [%p]", this)); + + if (!mPartialUpdate) { + // All offline items for a domain should be updated as a group; add + // the other offline items requested for this domain + nsresult rv = AddDomainItems(); + NS_ENSURE_SUCCESS(rv, rv); + } + + mState = STATE_RUNNING; + + ProcessNextURI(); + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdate::Cancel() +{ + LOG(("nsOfflineCacheUpdate::Cancel [%p]", this)); + + mState = STATE_CANCELLED; + + if (mItems.Length() > 0) { + // First load might be running + mItems[0]->Cancel(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdate +//----------------------------------------------------------------------------- + +nsresult +nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI) +{ + PRUint32 count; + char **keys; + nsresult rv = mCacheSession->GetOwnedKeys(mUpdateDomain, aOwnerURI, + &count, &keys); + NS_ENSURE_SUCCESS(rv, rv); + + AutoFreeArray autoFree(count, keys); + + for (PRUint32 i = 0; i < count; i++) { + nsCOMPtr uri; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) { + nsRefPtr item = + new nsOfflineCacheUpdateItem(this, uri, mReferrerURI, nsnull); + if (!item) return NS_ERROR_OUT_OF_MEMORY; + + mItems.AppendElement(item); + } + } + + return NS_OK; +} + +// Add all URIs needed by this domain to the update +nsresult +nsOfflineCacheUpdate::AddDomainItems() +{ + PRUint32 count; + char **uris; + nsresult rv = mCacheSession->GetOwnerURIs(mUpdateDomain, &count, &uris); + NS_ENSURE_SUCCESS(rv, rv); + + AutoFreeArray autoFree(count, uris); + + for (PRUint32 i = 0; i < count; i++) { + const char *ownerURI = uris[i]; + // if this update includes changes to this owner URI, ignore the + // set in the database. + if (!mAddedItems || !mOwnerURI.Equals(ownerURI)) { + rv = AddOwnedItems(nsDependentCString(ownerURI)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdate::ProcessNextURI() +{ + LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, numItems=%d]", + this, mItems.Length())); + + if (mState == STATE_CANCELLED || mItems.Length() == 0) { + return Finish(); + } + +#if defined(PR_LOGGING) + if (LOG_ENABLED()) { + nsCAutoString spec; + mItems[0]->mURI->GetSpec(spec); + LOG(("%p: Opening channel for %s", this, spec.get())); + } +#endif + + nsresult rv = mItems[0]->OpenChannel(); + if (NS_FAILED(rv)) { + LoadCompleted(); + return rv; + } + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdate::NotifyCompleted(nsOfflineCacheUpdateItem *aItem) +{ + nsCOMArray observers; + + for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) { + nsCOMPtr observer = + do_QueryReferent(mWeakObservers[i]); + if (observer) + observers.AppendObject(observer); + else + mWeakObservers.RemoveObjectAt(i--); + } + + for (PRInt32 i = 0; i < mObservers.Count(); i++) { + observers.AppendObject(mObservers[i]); + } + + for (PRInt32 i = 0; i < observers.Count(); i++) { + observers[i]->ItemCompleted(aItem); + } + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdate::Finish() +{ + LOG(("nsOfflineCacheUpdate::Finish [%p]", this)); + + mState = STATE_FINISHED; + + nsOfflineCacheUpdateService *service = + nsOfflineCacheUpdateService::GetInstance(); + + if (!service) + return NS_ERROR_FAILURE; + + return service->UpdateFinished(this); +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdate::nsIOfflineCacheUpdate +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain) +{ + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + aUpdateDomain = mUpdateDomain; + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::GetOwnerURI(nsACString &aOwnerURI) +{ + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + aOwnerURI = mOwnerURI; + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource) +{ + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + if (mState >= STATE_RUNNING) + return NS_ERROR_NOT_AVAILABLE; + + // only http and https urls can be put in the offline cache + PRBool match; + nsresult rv = aURI->SchemeIs("http", &match); + NS_ENSURE_SUCCESS(rv, rv); + + if (!match) { + rv = aURI->SchemeIs("https", &match); + NS_ENSURE_SUCCESS(rv, rv); + if (!match) + return NS_ERROR_ABORT; + } + + // Save the cache key as an owned URI + nsCAutoString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // url fragments aren't used in cache keys + nsCAutoString::const_iterator specStart, specEnd; + spec.BeginReading(specStart); + spec.EndReading(specEnd); + if (FindCharInReadable('#', specStart, specEnd)) { + spec.BeginReading(specEnd); + rv = mCacheSession->AddOwnedKey(mUpdateDomain, mOwnerURI, + Substring(specEnd, specStart)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = mCacheSession->AddOwnedKey(mUpdateDomain, mOwnerURI, spec); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsRefPtr item = + new nsOfflineCacheUpdateItem(this, aURI, mReferrerURI, aSource); + if (!item) return NS_ERROR_OUT_OF_MEMORY; + + mItems.AppendElement(item); + mAddedItems = PR_TRUE; + + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::GetCount(PRUint32 *aNumItems) +{ + LOG(("nsOfflineCacheUpdate::GetNumItems [%p, num=%d]", + this, mItems.Length())); + + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + *aNumItems = mItems.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::Item(PRUint32 aIndex, nsIDOMLoadStatus **aItem) +{ + LOG(("nsOfflineCacheUpdate::GetItems [%p, index=%d]", this, aIndex)); + + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + if (aIndex < mItems.Length()) + NS_IF_ADDREF(*aItem = mItems.ElementAt(aIndex)); + else + *aItem = nsnull; + + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver, + PRBool aHoldWeak) +{ + LOG(("nsOfflineCacheUpdate::AddObserver [%p]", this)); + + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + if (aHoldWeak) { + nsCOMPtr weakRef = do_GetWeakReference(aObserver); + mWeakObservers.AppendObject(weakRef); + } else { + mObservers.AppendObject(aObserver); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver) +{ + LOG(("nsOfflineCacheUpdate::RemoveObserver [%p]", this)); + + NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); + + for (PRInt32 i = 0; i < mWeakObservers.Count(); i++) { + nsCOMPtr observer = + do_QueryReferent(mWeakObservers[i]); + if (observer == aObserver) { + mWeakObservers.RemoveObjectAt(i); + return NS_OK; + } + } + + for (PRInt32 i = 0; i < mObservers.Count(); i++) { + if (mObservers[i] == aObserver) { + mObservers.RemoveObjectAt(i); + return NS_OK; + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsOfflineCacheUpdate::Schedule() +{ + LOG(("nsOfflineCacheUpdate::Schedule [%p]", this)); + + nsOfflineCacheUpdateService *service = + nsOfflineCacheUpdateService::GetInstance(); + + if (!service) { + return NS_ERROR_FAILURE; + } + + return service->Schedule(this); +} + +NS_IMETHODIMP +nsOfflineCacheUpdate::ScheduleOnDocumentStop(nsIDOMDocument *aDocument) +{ + LOG(("nsOfflineCacheUpdate::ScheduleOnDocumentStop [%p]", this)); + + nsOfflineCacheUpdateService *service = + nsOfflineCacheUpdateService::GetInstance(); + + if (!service) { + return NS_ERROR_FAILURE; + } + + return service->ScheduleOnDocumentStop(this, aDocument); +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS4(nsOfflineCacheUpdateService, + nsIOfflineCacheUpdateService, + nsIWebProgressListener, + nsIObserver, + nsISupportsWeakReference) + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService +//----------------------------------------------------------------------------- + +nsOfflineCacheUpdateService::nsOfflineCacheUpdateService() + : mDisabled(PR_FALSE) + , mUpdateRunning(PR_FALSE) +{ +} + +nsOfflineCacheUpdateService::~nsOfflineCacheUpdateService() +{ + gOfflineCacheUpdateService = nsnull; +} + +nsresult +nsOfflineCacheUpdateService::Init() +{ + nsresult rv; + +#if defined(PR_LOGGING) + if (!gOfflineCacheUpdateLog) + gOfflineCacheUpdateLog = PR_NewLogModule("nsOfflineCacheUpdate"); +#endif + + if (!mDocUpdates.Init()) + return NS_ERROR_FAILURE; + + // Observe xpcom-shutdown event + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, + NS_XPCOM_SHUTDOWN_OBSERVER_ID, + PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + + // Register as an observer for the document loader + nsCOMPtr progress = + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); + if (progress) { + nsresult rv = progress->AddProgressListener + (this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); + NS_ENSURE_SUCCESS(rv, rv); + } + + gOfflineCacheUpdateService = this; + + return NS_OK; +} + +nsOfflineCacheUpdateService * +nsOfflineCacheUpdateService::GetInstance() +{ + if (!gOfflineCacheUpdateService) { + gOfflineCacheUpdateService = new nsOfflineCacheUpdateService(); + if (!gOfflineCacheUpdateService) + return nsnull; + NS_ADDREF(gOfflineCacheUpdateService); + nsresult rv = gOfflineCacheUpdateService->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(gOfflineCacheUpdateService); + return nsnull; + } + return gOfflineCacheUpdateService; + } + + NS_ADDREF(gOfflineCacheUpdateService); + + return gOfflineCacheUpdateService; +} + +nsresult +nsOfflineCacheUpdateService::Schedule(nsOfflineCacheUpdate *aUpdate) +{ + LOG(("nsOfflineCacheUpdateService::Schedule [%p, update=%p]", + this, aUpdate)); + + nsresult rv; + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + observerService->NotifyObservers(static_cast(aUpdate), + "offline-cache-update-added", + nsnull); + + mUpdates.AppendElement(aUpdate); + + ProcessNextUpdate(); + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate, + nsIDOMDocument *aDocument) +{ + LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, update=%p, doc=%p]", + this, aUpdate, aDocument)); + + if (!mDocUpdates.Put(aDocument, aUpdate)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsOfflineCacheUpdateService::UpdateFinished(nsOfflineCacheUpdate *aUpdate) +{ + LOG(("nsOfflineCacheUpdateService::UpdateFinished [%p, update=%p]", + this, aUpdate)); + + NS_ASSERTION(mUpdates.Length() > 0 && + mUpdates[0] == aUpdate, "Unknown update completed"); + + // keep this item alive until we're done notifying observers + nsRefPtr update = mUpdates[0]; + mUpdates.RemoveElementAt(0); + mUpdateRunning = PR_FALSE; + + nsresult rv; + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + observerService->NotifyObservers(static_cast(aUpdate), + "offline-cache-update-completed", + nsnull); + + ProcessNextUpdate(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService +//----------------------------------------------------------------------------- + +nsresult +nsOfflineCacheUpdateService::ProcessNextUpdate() +{ + LOG(("nsOfflineCacheUpdateService::ProcessNextUpdate [%p, num=%d]", + this, mUpdates.Length())); + + if (mDisabled) + return NS_ERROR_ABORT; + + if (mUpdateRunning) + return NS_OK; + + if (mUpdates.Length() > 0) { + mUpdateRunning = PR_TRUE; + return mUpdates[0]->Begin(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService::nsIOfflineCacheUpdateService +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateService::GetNumUpdates(PRUint32 *aNumUpdates) +{ + LOG(("nsOfflineCacheUpdateService::GetNumUpdates [%p]", this)); + + *aNumUpdates = mUpdates.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateService::GetUpdate(PRUint32 aIndex, + nsIOfflineCacheUpdate **aUpdate) +{ + LOG(("nsOfflineCacheUpdateService::GetUpdate [%p, %d]", this, aIndex)); + + if (aIndex < mUpdates.Length()) { + NS_ADDREF(*aUpdate = mUpdates[aIndex]); + } else { + *aUpdate = nsnull; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService::nsIObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateService::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) +{ + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + if (mUpdates.Length() > 0) + mUpdates[0]->Cancel(); + mDisabled = PR_TRUE; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateService::nsIWebProgressListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsOfflineCacheUpdateService::OnProgressChange(nsIWebProgress *aProgress, + nsIRequest *aRequest, + PRInt32 curSelfProgress, + PRInt32 maxSelfProgress, + PRInt32 curTotalProgress, + PRInt32 maxTotalProgress) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateService::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest *aRequest, + PRUint32 progressStateFlags, + nsresult aStatus) +{ + if ((progressStateFlags & STATE_IS_DOCUMENT) && + (progressStateFlags & STATE_STOP)) { + if (mDocUpdates.Count() == 0) + return NS_OK; + + nsCOMPtr window; + aWebProgress->GetDOMWindow(getter_AddRefs(window)); + if (!window) return NS_OK; + + nsCOMPtr doc; + window->GetDocument(getter_AddRefs(doc)); + if (!doc) return NS_OK; + + LOG(("nsOfflineCacheUpdateService::OnStateChange [%p, doc=%p]", + this, doc.get())); + + nsRefPtr update; + if (mDocUpdates.Get(doc, getter_AddRefs(update))) { + Schedule(update); + mDocUpdates.Remove(doc); + } + + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateService::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI *location) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateService::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const PRUnichar* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsOfflineCacheUpdateService::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + PRUint32 state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.h b/uriloader/prefetch/nsOfflineCacheUpdate.h new file mode 100644 index 00000000000..8914df2982f --- /dev/null +++ b/uriloader/prefetch/nsOfflineCacheUpdate.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** 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 + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dave Camp + * + * 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 ***** */ + +#ifndef nsOfflineCacheUpdate_h__ +#define nsOfflineCacheUpdate_h__ + +#include "nsIOfflineCacheUpdate.h" + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsICacheService.h" +#include "nsIChannelEventSink.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMLoadStatus.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOfflineCacheSession.h" +#include "nsIPrefetchService.h" +#include "nsIRequestObserver.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" +#include "nsIWebProgressListener.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsWeakReference.h" + +class nsOfflineCacheUpdate; + +class nsOfflineCacheUpdateItem : public nsIDOMLoadStatus + , public nsIStreamListener + , public nsIInterfaceRequestor + , public nsIChannelEventSink +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMLOADSTATUS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate, + nsIURI *aURI, + nsIURI *aReferrerURI, + nsIDOMNode *aSource); + ~nsOfflineCacheUpdateItem(); + + nsCOMPtr mURI; + nsCOMPtr mReferrerURI; + nsCOMPtr mSource; + + nsresult OpenChannel(); + nsresult Cancel(); + +private: + nsOfflineCacheUpdate* mUpdate; + nsCOMPtr mChannel; + PRUint16 mState; + PRInt32 mBytesRead; +}; + +class nsOfflineCacheUpdate : public nsIOfflineCacheUpdate +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOFFLINECACHEUPDATE + + nsOfflineCacheUpdate(); + ~nsOfflineCacheUpdate(); + + nsresult Init(); + + nsresult Begin(); + nsresult Cancel(); + + void LoadCompleted(); +private: + nsresult ProcessNextURI(); + nsresult AddOwnedItems(const nsACString &aOwnerURI); + nsresult AddDomainItems(); + nsresult NotifyAdded(nsOfflineCacheUpdateItem *aItem); + nsresult NotifyCompleted(nsOfflineCacheUpdateItem *aItem); + nsresult Finish(); + + enum { + STATE_UNINITIALIZED, + STATE_INITIALIZED, + STATE_RUNNING, + STATE_CANCELLED, + STATE_FINISHED + } mState; + + PRBool mAddedItems; + PRBool mPartialUpdate; + nsCString mUpdateDomain; + nsCString mOwnerURI; + nsCOMPtr mReferrerURI; + + nsCOMPtr mCacheSession; + nsCOMPtr mObserverService; + + /* Items being updated */ + nsTArray > mItems; + + /* Clients watching this update for changes */ + nsCOMArray mWeakObservers; + nsCOMArray mObservers; +}; + +class nsOfflineCacheUpdateService : public nsIOfflineCacheUpdateService + , public nsIWebProgressListener + , public nsIObserver + , public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOFFLINECACHEUPDATESERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIOBSERVER + + nsOfflineCacheUpdateService(); + ~nsOfflineCacheUpdateService(); + + nsresult Init(); + + nsresult Schedule(nsOfflineCacheUpdate *aUpdate); + nsresult ScheduleOnDocumentStop(nsOfflineCacheUpdate *aUpdate, + nsIDOMDocument *aDocument); + nsresult UpdateFinished(nsOfflineCacheUpdate *aUpdate); + + static nsOfflineCacheUpdateService *GetInstance(); + +private: + nsresult ProcessNextUpdate(); + + nsTArray > mUpdates; + nsRefPtrHashtable mDocUpdates; + + PRBool mDisabled; + PRBool mUpdateRunning; +}; + +#endif