michael@0: /* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #if defined(MOZ_LOGGING) michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "nsOfflineCacheUpdate.h" michael@0: michael@0: #include "nsCPrefetchService.h" michael@0: #include "nsCURILoader.h" michael@0: #include "nsIApplicationCacheContainer.h" michael@0: #include "nsIApplicationCacheChannel.h" michael@0: #include "nsIApplicationCacheService.h" michael@0: #include "nsICache.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsICacheSession.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIContent.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsIDocumentLoader.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIDOMOfflineResourceList.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIURL.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsICryptoHash.h" michael@0: #include "nsICacheEntry.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "prlog.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static const uint32_t kRescheduleLimit = 3; michael@0: // Max number of retries for every entry of pinned app. michael@0: static const uint32_t kPinnedEntryRetriesLimit = 3; michael@0: // Maximum number of parallel items loads michael@0: static const uint32_t kParallelLoadLimit = 15; michael@0: michael@0: // Quota for offline apps when preloading michael@0: static const int32_t kCustomProfileQuota = 512000; michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsOfflineCacheUpdate:5 michael@0: // set NSPR_LOG_FILE=offlineupdate.log michael@0: // michael@0: // this enables PR_LOG_ALWAYS level information and places all output in michael@0: // the file offlineupdate.log michael@0: // michael@0: extern PRLogModuleInfo *gOfflineCacheUpdateLog; michael@0: #endif michael@0: michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(gOfflineCacheUpdateLog, 4, args) michael@0: michael@0: #undef LOG_ENABLED michael@0: #define LOG_ENABLED() PR_LOG_TEST(gOfflineCacheUpdateLog, 4) michael@0: michael@0: class AutoFreeArray { michael@0: public: michael@0: AutoFreeArray(uint32_t count, char **values) michael@0: : mCount(count), mValues(values) {}; michael@0: ~AutoFreeArray() { NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mValues); } michael@0: private: michael@0: uint32_t mCount; michael@0: char **mValues; michael@0: }; michael@0: michael@0: namespace { // anon michael@0: michael@0: nsresult michael@0: DropReferenceFromURL(nsIURI * aURI) michael@0: { michael@0: // XXXdholbert If this SetRef fails, callers of this method probably michael@0: // want to call aURI->CloneIgnoringRef() and use the result of that. michael@0: return aURI->SetRef(EmptyCString()); michael@0: } michael@0: michael@0: void michael@0: LogToConsole(const char * message, nsOfflineCacheUpdateItem * item = nullptr) michael@0: { michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (consoleService) michael@0: { michael@0: nsAutoString messageUTF16 = NS_ConvertUTF8toUTF16(message); michael@0: if (item && item->mURI) { michael@0: nsAutoCString uriSpec; michael@0: item->mURI->GetSpec(uriSpec); michael@0: michael@0: messageUTF16.Append(NS_LITERAL_STRING(", URL=")); michael@0: messageUTF16.Append(NS_ConvertUTF8toUTF16(uriSpec)); michael@0: } michael@0: consoleService->LogStringMessage(messageUTF16.get()); michael@0: } michael@0: } michael@0: michael@0: } // anon namespace michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsManifestCheck MOZ_FINAL : public nsIStreamListener michael@0: , public nsIChannelEventSink michael@0: , public nsIInterfaceRequestor michael@0: { michael@0: public: michael@0: nsManifestCheck(nsOfflineCacheUpdate *aUpdate, michael@0: nsIURI *aURI, michael@0: nsIURI *aReferrerURI) michael@0: : mUpdate(aUpdate) michael@0: , mURI(aURI) michael@0: , mReferrerURI(aReferrerURI) michael@0: {} michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSICHANNELEVENTSINK michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: michael@0: nsresult Begin(); michael@0: michael@0: private: michael@0: michael@0: static NS_METHOD ReadManifest(nsIInputStream *aInputStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aBytesConsumed); michael@0: michael@0: nsRefPtr mUpdate; michael@0: nsCOMPtr mURI; michael@0: nsCOMPtr mReferrerURI; michael@0: nsCOMPtr mManifestHash; michael@0: nsCOMPtr mChannel; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMPL_ISUPPORTS(nsManifestCheck, michael@0: nsIRequestObserver, michael@0: nsIStreamListener, michael@0: nsIChannelEventSink, michael@0: nsIInterfaceRequestor) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsManifestCheck::Begin() michael@0: { michael@0: nsresult rv; michael@0: mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mManifestHash->Init(nsICryptoHash::MD5); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_NewChannel(getter_AddRefs(mChannel), michael@0: mURI, michael@0: nullptr, nullptr, nullptr, michael@0: nsIRequest::LOAD_BYPASS_CACHE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // configure HTTP specific stuff michael@0: nsCOMPtr httpChannel = michael@0: do_QueryInterface(mChannel); michael@0: if (httpChannel) { michael@0: httpChannel->SetReferrer(mReferrerURI); michael@0: httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), michael@0: NS_LITERAL_CSTRING("offline-resource"), michael@0: false); michael@0: } michael@0: michael@0: rv = mChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: /* static */ michael@0: NS_METHOD michael@0: nsManifestCheck::ReadManifest(nsIInputStream *aInputStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aBytesConsumed) michael@0: { michael@0: nsManifestCheck *manifestCheck = michael@0: static_cast(aClosure); michael@0: michael@0: nsresult rv; michael@0: *aBytesConsumed = aCount; michael@0: michael@0: rv = manifestCheck->mManifestHash->Update( michael@0: reinterpret_cast(aFromSegment), aCount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck::nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsManifestCheck::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsManifestCheck::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: uint32_t bytesRead; michael@0: aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsManifestCheck::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: nsAutoCString manifestHash; michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: mManifestHash->Finish(true, manifestHash); michael@0: } michael@0: michael@0: mUpdate->ManifestCheckCompleted(aStatus, manifestHash); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck::nsIInterfaceRequestor michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsManifestCheck::GetInterface(const nsIID &aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: NS_ADDREF_THIS(); michael@0: *aResult = static_cast(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsManifestCheck::nsIChannelEventSink michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsManifestCheck::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: // Redirects should cause the load (and therefore the update) to fail. michael@0: if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { michael@0: callback->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: LogToConsole("Manifest check failed because its response is a redirect"); michael@0: michael@0: aOldChannel->Cancel(NS_ERROR_ABORT); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsOfflineCacheUpdateItem, michael@0: nsIRequestObserver, michael@0: nsIStreamListener, michael@0: nsIRunnable, michael@0: nsIInterfaceRequestor, michael@0: nsIChannelEventSink) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIApplicationCache *aApplicationCache, michael@0: nsIApplicationCache *aPreviousApplicationCache, michael@0: uint32_t type) michael@0: : mURI(aURI) michael@0: , mReferrerURI(aReferrerURI) michael@0: , mApplicationCache(aApplicationCache) michael@0: , mPreviousApplicationCache(aPreviousApplicationCache) michael@0: , mItemType(type) michael@0: , mChannel(nullptr) michael@0: , mState(LoadStatus::UNINITIALIZED) michael@0: , mBytesRead(0) michael@0: { michael@0: } michael@0: michael@0: nsOfflineCacheUpdateItem::~nsOfflineCacheUpdateItem() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdateItem::OpenChannel(nsOfflineCacheUpdate *aUpdate) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString spec; michael@0: mURI->GetSpec(spec); michael@0: LOG(("%p: Opening channel for %s", this, spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: if (mUpdate) { michael@0: // Holding a reference to the update means this item is already michael@0: // in progress (has a channel, or is just in between OnStopRequest() michael@0: // and its Run() call. We must never open channel on this item again. michael@0: LOG((" %p is already running! ignoring", this)); michael@0: return NS_ERROR_ALREADY_OPENED; michael@0: } michael@0: michael@0: nsresult rv = nsOfflineCacheUpdate::GetCacheKey(mURI, mCacheKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t flags = nsIRequest::LOAD_BACKGROUND | michael@0: nsICachingChannel::LOAD_ONLY_IF_MODIFIED | michael@0: nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE; michael@0: michael@0: if (mApplicationCache == mPreviousApplicationCache) { michael@0: // Same app cache to read from and to write to is used during michael@0: // an only-update-check procedure. Here we protect the existing michael@0: // cache from being modified. michael@0: flags |= nsIRequest::INHIBIT_CACHING; michael@0: } michael@0: michael@0: rv = NS_NewChannel(getter_AddRefs(mChannel), michael@0: mURI, michael@0: nullptr, nullptr, this, michael@0: flags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr appCacheChannel = michael@0: do_QueryInterface(mChannel, &rv); michael@0: michael@0: // Support for nsIApplicationCacheChannel is required. michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Use the existing application cache as the cache to check. michael@0: rv = appCacheChannel->SetApplicationCache(mPreviousApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set the new application cache as the target for write. michael@0: rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // configure HTTP specific stuff michael@0: nsCOMPtr httpChannel = michael@0: do_QueryInterface(mChannel); michael@0: if (httpChannel) { michael@0: httpChannel->SetReferrer(mReferrerURI); michael@0: httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), michael@0: NS_LITERAL_CSTRING("offline-resource"), michael@0: false); michael@0: } michael@0: michael@0: rv = mChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mUpdate = aUpdate; michael@0: michael@0: mState = LoadStatus::REQUESTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdateItem::Cancel() michael@0: { michael@0: if (mChannel) { michael@0: mChannel->Cancel(NS_ERROR_ABORT); michael@0: mChannel = nullptr; michael@0: } michael@0: michael@0: mState = LoadStatus::UNINITIALIZED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem::nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: mState = LoadStatus::RECEIVING; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: uint32_t bytesRead = 0; michael@0: aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &bytesRead); michael@0: mBytesRead += bytesRead; michael@0: LOG(("loaded %u bytes into offline cache [offset=%llu]\n", michael@0: bytesRead, aOffset)); michael@0: michael@0: mUpdate->OnByteProgress(bytesRead); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: #if defined(PR_LOGGING) michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString spec; michael@0: mURI->GetSpec(spec); michael@0: LOG(("%p: Done fetching offline item %s [status=%x]\n", michael@0: this, spec.get(), aStatus)); michael@0: } michael@0: #endif michael@0: michael@0: if (mBytesRead == 0 && aStatus == NS_OK) { michael@0: // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was michael@0: // specified), but the object should report loadedSize as if it michael@0: // did. michael@0: mChannel->GetContentLength(&mBytesRead); michael@0: mUpdate->OnByteProgress(mBytesRead); michael@0: } michael@0: michael@0: if (NS_FAILED(aStatus)) { michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel); michael@0: if (httpChannel) { michael@0: bool isNoStore; michael@0: if (NS_SUCCEEDED(httpChannel->IsNoStoreResponse(&isNoStore)) michael@0: && isNoStore) { michael@0: LogToConsole("Offline cache manifest item has Cache-control: no-store header", michael@0: this); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We need to notify the update that the load is complete, but we michael@0: // want to give the channel a chance to close the cache entries. michael@0: NS_DispatchToCurrentThread(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem::nsIRunnable michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::Run() michael@0: { michael@0: // Set mState to LOADED here rather than in OnStopRequest to prevent michael@0: // race condition when checking state of all mItems in ProcessNextURI(). michael@0: // If state would have been set in OnStopRequest we could mistakenly michael@0: // take this item as already finished and finish the update process too michael@0: // early when ProcessNextURI() would get called between OnStopRequest() michael@0: // and Run() of this item. Finish() would then have been called twice. michael@0: mState = LoadStatus::LOADED; michael@0: michael@0: nsRefPtr update; michael@0: update.swap(mUpdate); michael@0: update->LoadCompleted(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem::nsIInterfaceRequestor michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::GetInterface(const nsIID &aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: NS_ADDREF_THIS(); michael@0: *aResult = static_cast(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdateItem::nsIChannelEventSink michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdateItem::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *cb) michael@0: { michael@0: if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) { michael@0: // Don't allow redirect in case of non-internal redirect and cancel michael@0: // the channel to clean the cache entry. michael@0: LogToConsole("Offline cache manifest failed because an item redirects", this); michael@0: michael@0: aOldChannel->Cancel(NS_ERROR_ABORT); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: nsCOMPtr newURI; michael@0: nsresult rv = aNewChannel->GetURI(getter_AddRefs(newURI)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr appCacheChannel = michael@0: do_QueryInterface(aNewChannel); michael@0: if (appCacheChannel) { michael@0: rv = appCacheChannel->SetApplicationCacheForWrite(mApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsAutoCString oldScheme; michael@0: mURI->GetScheme(oldScheme); michael@0: michael@0: bool match; michael@0: if (NS_FAILED(newURI->SchemeIs(oldScheme.get(), &match)) || !match) { michael@0: LOG(("rejected: redirected to a different scheme\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: // HTTP request headers are not automatically forwarded to the new channel. michael@0: nsCOMPtr httpChannel = do_QueryInterface(aNewChannel); michael@0: NS_ENSURE_STATE(httpChannel); michael@0: michael@0: httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), michael@0: NS_LITERAL_CSTRING("offline-resource"), michael@0: false); michael@0: michael@0: mChannel = aNewChannel; michael@0: michael@0: cb->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdateItem::GetRequestSucceeded(bool * succeeded) michael@0: { michael@0: *succeeded = false; michael@0: michael@0: if (!mChannel) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool reqSucceeded; michael@0: rv = httpChannel->GetRequestSucceeded(&reqSucceeded); michael@0: if (NS_ERROR_NOT_AVAILABLE == rv) michael@0: return NS_OK; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!reqSucceeded) { michael@0: LOG(("Request failed")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult channelStatus; michael@0: rv = httpChannel->GetStatus(&channelStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (NS_FAILED(channelStatus)) { michael@0: LOG(("Channel status=0x%08x", channelStatus)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: *succeeded = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheUpdateItem::IsScheduled() michael@0: { michael@0: return mState == LoadStatus::UNINITIALIZED; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheUpdateItem::IsInProgress() michael@0: { michael@0: return mState == LoadStatus::REQUESTED || michael@0: mState == LoadStatus::RECEIVING; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheUpdateItem::IsCompleted() michael@0: { michael@0: return mState == LoadStatus::LOADED; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdateItem::GetStatus(uint16_t *aStatus) michael@0: { michael@0: if (!mChannel) { michael@0: *aStatus = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr httpChannel = do_QueryInterface(mChannel, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t httpStatus; michael@0: rv = httpChannel->GetResponseStatus(&httpStatus); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: *aStatus = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aStatus = uint16_t(httpStatus); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineManifestItem michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineManifestItem michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsOfflineManifestItem::nsOfflineManifestItem(nsIURI *aURI, michael@0: nsIURI *aReferrerURI, michael@0: nsIApplicationCache *aApplicationCache, michael@0: nsIApplicationCache *aPreviousApplicationCache) michael@0: : nsOfflineCacheUpdateItem(aURI, aReferrerURI, michael@0: aApplicationCache, aPreviousApplicationCache, michael@0: nsIApplicationCache::ITEM_MANIFEST) michael@0: , mParserState(PARSE_INIT) michael@0: , mNeedsUpdate(true) michael@0: , mManifestHashInitialized(false) michael@0: { michael@0: ReadStrictFileOriginPolicyPref(); michael@0: } michael@0: michael@0: nsOfflineManifestItem::~nsOfflineManifestItem() michael@0: { michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineManifestItem michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: /* static */ michael@0: NS_METHOD michael@0: nsOfflineManifestItem::ReadManifest(nsIInputStream *aInputStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aBytesConsumed) michael@0: { michael@0: nsOfflineManifestItem *manifest = michael@0: static_cast(aClosure); michael@0: michael@0: nsresult rv; michael@0: michael@0: *aBytesConsumed = aCount; michael@0: michael@0: if (manifest->mParserState == PARSE_ERROR) { michael@0: // parse already failed, ignore this michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!manifest->mManifestHashInitialized) { michael@0: // Avoid re-creation of crypto hash when it fails from some reason the first time michael@0: manifest->mManifestHashInitialized = true; michael@0: michael@0: manifest->mManifestHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = manifest->mManifestHash->Init(nsICryptoHash::MD5); michael@0: if (NS_FAILED(rv)) { michael@0: manifest->mManifestHash = nullptr; michael@0: LOG(("Could not initialize manifest hash for byte-to-byte check, rv=%08x", rv)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (manifest->mManifestHash) { michael@0: rv = manifest->mManifestHash->Update(reinterpret_cast(aFromSegment), aCount); michael@0: if (NS_FAILED(rv)) { michael@0: manifest->mManifestHash = nullptr; michael@0: LOG(("Could not update manifest hash, rv=%08x", rv)); michael@0: } michael@0: } michael@0: michael@0: manifest->mReadBuf.Append(aFromSegment, aCount); michael@0: michael@0: nsCString::const_iterator begin, iter, end; michael@0: manifest->mReadBuf.BeginReading(begin); michael@0: manifest->mReadBuf.EndReading(end); michael@0: michael@0: for (iter = begin; iter != end; iter++) { michael@0: if (*iter == '\r' || *iter == '\n') { michael@0: nsresult rv = manifest->HandleManifestLine(begin, iter); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("HandleManifestLine failed with 0x%08x", rv)); michael@0: *aBytesConsumed = 0; // Avoid assertion failure in stream tee michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: begin = iter; michael@0: begin++; michael@0: } michael@0: } michael@0: michael@0: // any leftovers are saved for next time michael@0: manifest->mReadBuf = Substring(begin, end); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineManifestItem::AddNamespace(uint32_t namespaceType, michael@0: const nsCString &namespaceSpec, michael@0: const nsCString &data) michael@0: michael@0: { michael@0: nsresult rv; michael@0: if (!mNamespaces) { michael@0: mNamespaces = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr ns = michael@0: do_CreateInstance(NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ns->Init(namespaceType, namespaceSpec, data); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mNamespaces->AppendElement(ns, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegin, michael@0: const nsCString::const_iterator &aEnd) michael@0: { michael@0: nsCString::const_iterator begin = aBegin; michael@0: nsCString::const_iterator end = aEnd; michael@0: michael@0: // all lines ignore trailing spaces and tabs michael@0: nsCString::const_iterator last = end; michael@0: --last; michael@0: while (end != begin && (*last == ' ' || *last == '\t')) { michael@0: --end; michael@0: --last; michael@0: } michael@0: michael@0: if (mParserState == PARSE_INIT) { michael@0: // Allow a UTF-8 BOM michael@0: if (begin != end && static_cast(*begin) == 0xef) { michael@0: if (++begin == end || static_cast(*begin) != 0xbb || michael@0: ++begin == end || static_cast(*begin) != 0xbf) { michael@0: mParserState = PARSE_ERROR; michael@0: LogToConsole("Offline cache manifest BOM error", this); michael@0: return NS_OK; michael@0: } michael@0: ++begin; michael@0: } michael@0: michael@0: const nsCSubstring &magic = Substring(begin, end); michael@0: michael@0: if (!magic.EqualsLiteral("CACHE MANIFEST")) { michael@0: mParserState = PARSE_ERROR; michael@0: LogToConsole("Offline cache manifest magic incorect", this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mParserState = PARSE_CACHE_ENTRIES; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // lines other than the first ignore leading spaces and tabs michael@0: while (begin != end && (*begin == ' ' || *begin == '\t')) michael@0: begin++; michael@0: michael@0: // ignore blank lines and comments michael@0: if (begin == end || *begin == '#') michael@0: return NS_OK; michael@0: michael@0: const nsCSubstring &line = Substring(begin, end); michael@0: michael@0: if (line.EqualsLiteral("CACHE:")) { michael@0: mParserState = PARSE_CACHE_ENTRIES; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (line.EqualsLiteral("FALLBACK:")) { michael@0: mParserState = PARSE_FALLBACK_ENTRIES; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (line.EqualsLiteral("NETWORK:")) { michael@0: mParserState = PARSE_BYPASS_ENTRIES; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Every other section type we don't know must be silently ignored. michael@0: nsCString::const_iterator lastChar = end; michael@0: if (*(--lastChar) == ':') { michael@0: mParserState = PARSE_UNKNOWN_SECTION; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: switch(mParserState) { michael@0: case PARSE_INIT: michael@0: case PARSE_ERROR: { michael@0: // this should have been dealt with earlier michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: case PARSE_UNKNOWN_SECTION: { michael@0: // just jump over michael@0: return NS_OK; michael@0: } michael@0: michael@0: case PARSE_CACHE_ENTRIES: { michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), line, nullptr, mURI); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: if (NS_FAILED(DropReferenceFromURL(uri))) michael@0: break; michael@0: michael@0: nsAutoCString scheme; michael@0: uri->GetScheme(scheme); michael@0: michael@0: // Manifest URIs must have the same scheme as the manifest. michael@0: bool match; michael@0: if (NS_FAILED(mURI->SchemeIs(scheme.get(), &match)) || !match) michael@0: break; michael@0: michael@0: mExplicitURIs.AppendObject(uri); michael@0: break; michael@0: } michael@0: michael@0: case PARSE_FALLBACK_ENTRIES: { michael@0: int32_t separator = line.FindChar(' '); michael@0: if (separator == kNotFound) { michael@0: separator = line.FindChar('\t'); michael@0: if (separator == kNotFound) michael@0: break; michael@0: } michael@0: michael@0: nsCString namespaceSpec(Substring(line, 0, separator)); michael@0: nsCString fallbackSpec(Substring(line, separator + 1)); michael@0: namespaceSpec.CompressWhitespace(); michael@0: fallbackSpec.CompressWhitespace(); michael@0: michael@0: nsCOMPtr namespaceURI; michael@0: rv = NS_NewURI(getter_AddRefs(namespaceURI), namespaceSpec, nullptr, mURI); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: if (NS_FAILED(DropReferenceFromURL(namespaceURI))) michael@0: break; michael@0: rv = namespaceURI->GetAsciiSpec(namespaceSpec); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: michael@0: nsCOMPtr fallbackURI; michael@0: rv = NS_NewURI(getter_AddRefs(fallbackURI), fallbackSpec, nullptr, mURI); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: if (NS_FAILED(DropReferenceFromURL(fallbackURI))) michael@0: break; michael@0: rv = fallbackURI->GetAsciiSpec(fallbackSpec); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: // Manifest and namespace must be same origin michael@0: if (!NS_SecurityCompareURIs(mURI, namespaceURI, michael@0: mStrictFileOriginPolicy)) michael@0: break; michael@0: michael@0: // Fallback and namespace must be same origin michael@0: if (!NS_SecurityCompareURIs(namespaceURI, fallbackURI, michael@0: mStrictFileOriginPolicy)) michael@0: break; michael@0: michael@0: mFallbackURIs.AppendObject(fallbackURI); michael@0: michael@0: AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_FALLBACK, michael@0: namespaceSpec, fallbackSpec); michael@0: break; michael@0: } michael@0: michael@0: case PARSE_BYPASS_ENTRIES: { michael@0: if (line[0] == '*' && (line.Length() == 1 || line[1] == ' ' || line[1] == '\t')) michael@0: { michael@0: // '*' indicates to make the online whitelist wildcard flag open, michael@0: // i.e. do allow load of resources not present in the offline cache michael@0: // or not conforming any namespace. michael@0: // We achive that simply by adding an 'empty' - i.e. universal michael@0: // namespace of BYPASS type into the cache. michael@0: AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS, michael@0: EmptyCString(), EmptyCString()); michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr bypassURI; michael@0: rv = NS_NewURI(getter_AddRefs(bypassURI), line, nullptr, mURI); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: nsAutoCString scheme; michael@0: bypassURI->GetScheme(scheme); michael@0: bool equals; michael@0: if (NS_FAILED(mURI->SchemeIs(scheme.get(), &equals)) || !equals) michael@0: break; michael@0: if (NS_FAILED(DropReferenceFromURL(bypassURI))) michael@0: break; michael@0: nsCString spec; michael@0: if (NS_FAILED(bypassURI->GetAsciiSpec(spec))) michael@0: break; michael@0: michael@0: AddNamespace(nsIApplicationCacheNamespace::NAMESPACE_BYPASS, michael@0: spec, EmptyCString()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineManifestItem::GetOldManifestContentHash(nsIRequest *aRequest) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr cachingChannel = do_QueryInterface(aRequest, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // load the main cache token that is actually the old offline cache token and michael@0: // read previous manifest content hash value michael@0: nsCOMPtr cacheToken; michael@0: cachingChannel->GetCacheToken(getter_AddRefs(cacheToken)); michael@0: if (cacheToken) { michael@0: nsCOMPtr cacheDescriptor(do_QueryInterface(cacheToken, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheDescriptor->GetMetaDataElement("offline-manifest-hash", getter_Copies(mOldManifestHashValue)); michael@0: if (NS_FAILED(rv)) michael@0: mOldManifestHashValue.Truncate(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineManifestItem::CheckNewManifestContentHash(nsIRequest *aRequest) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!mManifestHash) { michael@0: // Nothing to compare against... michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCString newManifestHashValue; michael@0: rv = mManifestHash->Finish(true, mManifestHashValue); michael@0: mManifestHash = nullptr; michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Could not finish manifest hash, rv=%08x", rv)); michael@0: // This is not critical error michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!ParseSucceeded()) { michael@0: // Parsing failed, the hash is not valid michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mOldManifestHashValue == mManifestHashValue) { michael@0: LOG(("Update not needed, downloaded manifest content is byte-for-byte identical")); michael@0: mNeedsUpdate = false; michael@0: } michael@0: michael@0: // Store the manifest content hash value to the new michael@0: // offline cache token michael@0: nsCOMPtr cachingChannel = do_QueryInterface(aRequest, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cacheToken; michael@0: cachingChannel->GetOfflineCacheToken(getter_AddRefs(cacheToken)); michael@0: if (cacheToken) { michael@0: nsCOMPtr cacheDescriptor(do_QueryInterface(cacheToken, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheDescriptor->SetMetaDataElement("offline-manifest-hash", mManifestHashValue.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsOfflineManifestItem::ReadStrictFileOriginPolicyPref() michael@0: { michael@0: mStrictFileOriginPolicy = michael@0: Preferences::GetBool("security.fileuri.strict_origin_policy", true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineManifestItem::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(aRequest, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool succeeded; michael@0: rv = channel->GetRequestSucceeded(&succeeded); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!succeeded) { michael@0: LOG(("HTTP request failed")); michael@0: LogToConsole("Offline cache manifest HTTP request failed", this); michael@0: mParserState = PARSE_ERROR; michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: rv = GetOldManifestContentHash(aRequest); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return nsOfflineCacheUpdateItem::OnStartRequest(aRequest, aContext); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineManifestItem::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsIInputStream *aStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: uint32_t bytesRead = 0; michael@0: aStream->ReadSegments(ReadManifest, this, aCount, &bytesRead); michael@0: mBytesRead += bytesRead; michael@0: michael@0: if (mParserState == PARSE_ERROR) { michael@0: LOG(("OnDataAvailable is canceling the request due a parse error\n")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: LOG(("loaded %u bytes into offline cache [offset=%u]\n", michael@0: bytesRead, aOffset)); michael@0: michael@0: // All the parent method does is read and discard, don't bother michael@0: // chaining up. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineManifestItem::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: if (mBytesRead == 0) { michael@0: // We didn't need to read (because LOAD_ONLY_IF_MODIFIED was michael@0: // specified). michael@0: mNeedsUpdate = false; michael@0: } else { michael@0: // Handle any leftover manifest data. michael@0: nsCString::const_iterator begin, end; michael@0: mReadBuf.BeginReading(begin); michael@0: mReadBuf.EndReading(end); michael@0: nsresult rv = HandleManifestLine(begin, end); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CheckNewManifestContentHash(aRequest); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return nsOfflineCacheUpdateItem::OnStopRequest(aRequest, aContext, aStatus); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdate::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(nsOfflineCacheUpdate, michael@0: nsIOfflineCacheUpdateObserver, michael@0: nsIOfflineCacheUpdate, michael@0: nsIRunnable) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdate michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsOfflineCacheUpdate::nsOfflineCacheUpdate() michael@0: : mState(STATE_UNINITIALIZED) michael@0: , mAddedItems(false) michael@0: , mPartialUpdate(false) michael@0: , mOnlyCheckUpdate(false) michael@0: , mSucceeded(true) michael@0: , mObsolete(false) michael@0: , mAppID(NECKO_NO_APP_ID) michael@0: , mInBrowser(false) michael@0: , mItemsInProgress(0) michael@0: , mRescheduleCount(0) michael@0: , mPinnedEntryRetriesCount(0) michael@0: , mPinned(false) michael@0: { michael@0: } michael@0: michael@0: nsOfflineCacheUpdate::~nsOfflineCacheUpdate() michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::~nsOfflineCacheUpdate [%p]", this)); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey) michael@0: { michael@0: aKey.Truncate(); michael@0: michael@0: nsCOMPtr newURI; michael@0: nsresult rv = aURI->CloneIgnoringRef(getter_AddRefs(newURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = newURI->GetAsciiSpec(aKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::InitInternal(nsIURI *aManifestURI) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Only http and https applications are supported. michael@0: bool match; michael@0: rv = aManifestURI->SchemeIs("http", &match); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!match) { michael@0: rv = aManifestURI->SchemeIs("https", &match); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!match) michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: mManifestURI = aManifestURI; michael@0: michael@0: rv = mManifestURI->GetAsciiHost(mUpdateDomain); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mPartialUpdate = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::Init(nsIURI *aManifestURI, michael@0: nsIURI *aDocumentURI, michael@0: nsIDOMDocument *aDocument, michael@0: nsIFile *aCustomProfileDir, michael@0: uint32_t aAppID, michael@0: bool aInBrowser) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Make sure the service has been initialized michael@0: nsOfflineCacheUpdateService* service = michael@0: nsOfflineCacheUpdateService::EnsureService(); michael@0: if (!service) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: LOG(("nsOfflineCacheUpdate::Init [%p]", this)); michael@0: michael@0: rv = InitInternal(aManifestURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mDocumentURI = aDocumentURI; michael@0: michael@0: if (aCustomProfileDir) { michael@0: rv = cacheService->BuildGroupIDForApp(aManifestURI, michael@0: aAppID, aInBrowser, michael@0: mGroupID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create only a new offline application cache in the custom profile michael@0: // This is a preload of a new cache. michael@0: michael@0: // XXX Custom updates don't support "updating" of an existing cache michael@0: // in the custom profile at the moment. This support can be, though, michael@0: // simply added as well when needed. michael@0: mPreviousApplicationCache = nullptr; michael@0: michael@0: rv = cacheService->CreateCustomApplicationCache(mGroupID, michael@0: aCustomProfileDir, michael@0: kCustomProfileQuota, michael@0: getter_AddRefs(mApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mCustomProfileDir = aCustomProfileDir; michael@0: } michael@0: else { michael@0: rv = cacheService->BuildGroupIDForApp(aManifestURI, michael@0: aAppID, aInBrowser, michael@0: mGroupID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->GetActiveCache(mGroupID, michael@0: getter_AddRefs(mPreviousApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->CreateApplicationCache(mGroupID, michael@0: getter_AddRefs(mApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI, michael@0: nullptr, michael@0: &mPinned); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mAppID = aAppID; michael@0: mInBrowser = aInBrowser; michael@0: michael@0: mState = STATE_INITIALIZED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::InitForUpdateCheck(nsIURI *aManifestURI, michael@0: uint32_t aAppID, michael@0: bool aInBrowser, michael@0: nsIObserver *aObserver) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Make sure the service has been initialized michael@0: nsOfflineCacheUpdateService* service = michael@0: nsOfflineCacheUpdateService::EnsureService(); michael@0: if (!service) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: LOG(("nsOfflineCacheUpdate::InitForUpdateCheck [%p]", this)); michael@0: michael@0: rv = InitInternal(aManifestURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->BuildGroupIDForApp(aManifestURI, michael@0: aAppID, aInBrowser, michael@0: mGroupID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->GetActiveCache(mGroupID, michael@0: getter_AddRefs(mPreviousApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // To load the manifest properly using current app cache to satisfy and michael@0: // also to compare the cached content hash value we have to set 'some' michael@0: // app cache to write to on the channel. Otherwise the cached version will michael@0: // be used and no actual network request will be made. We use the same michael@0: // app cache here. OpenChannel prevents caching in this case using michael@0: // INHIBIT_CACHING load flag. michael@0: mApplicationCache = mPreviousApplicationCache; michael@0: michael@0: rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aManifestURI, michael@0: nullptr, michael@0: &mPinned); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mUpdateAvailableObserver = aObserver; michael@0: mOnlyCheckUpdate = true; michael@0: michael@0: mState = STATE_INITIALIZED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI, michael@0: const nsACString& clientID, michael@0: nsIURI *aDocumentURI) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Make sure the service has been initialized michael@0: nsOfflineCacheUpdateService* service = michael@0: nsOfflineCacheUpdateService::EnsureService(); michael@0: if (!service) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this)); michael@0: michael@0: mPartialUpdate = true; michael@0: mDocumentURI = aDocumentURI; michael@0: michael@0: mManifestURI = aManifestURI; michael@0: rv = mManifestURI->GetAsciiHost(mUpdateDomain); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->GetApplicationCache(clientID, michael@0: getter_AddRefs(mApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mApplicationCache) { michael@0: nsAutoCString manifestSpec; michael@0: rv = GetCacheKey(mManifestURI, manifestSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheService->CreateApplicationCache michael@0: (manifestSpec, getter_AddRefs(mApplicationCache)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = mApplicationCache->GetManifestURI(getter_AddRefs(mManifestURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString groupID; michael@0: rv = mApplicationCache->GetGroupID(groupID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(aDocumentURI, michael@0: nullptr, michael@0: &mPinned); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mState = STATE_INITIALIZED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::HandleManifest(bool *aDoUpdate) michael@0: { michael@0: // Be pessimistic michael@0: *aDoUpdate = false; michael@0: michael@0: bool succeeded; michael@0: nsresult rv = mManifestItem->GetRequestSucceeded(&succeeded); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!succeeded || !mManifestItem->ParseSucceeded()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mManifestItem->NeedsUpdate()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Add items requested by the manifest. michael@0: const nsCOMArray &manifestURIs = mManifestItem->GetExplicitURIs(); michael@0: for (int32_t i = 0; i < manifestURIs.Count(); i++) { michael@0: rv = AddURI(manifestURIs[i], nsIApplicationCache::ITEM_EXPLICIT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: const nsCOMArray &fallbackURIs = mManifestItem->GetFallbackURIs(); michael@0: for (int32_t i = 0; i < fallbackURIs.Count(); i++) { michael@0: rv = AddURI(fallbackURIs[i], nsIApplicationCache::ITEM_FALLBACK); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // The document that requested the manifest is implicitly included michael@0: // as part of that manifest update. michael@0: rv = AddURI(mDocumentURI, nsIApplicationCache::ITEM_IMPLICIT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add items previously cached implicitly michael@0: rv = AddExistingItems(nsIApplicationCache::ITEM_IMPLICIT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add items requested by the script API michael@0: rv = AddExistingItems(nsIApplicationCache::ITEM_DYNAMIC); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add opportunistically cached items conforming current opportunistic michael@0: // namespace list michael@0: rv = AddExistingItems(nsIApplicationCache::ITEM_OPPORTUNISTIC, michael@0: &mManifestItem->GetOpportunisticNamespaces()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aDoUpdate = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheUpdate::CheckUpdateAvailability() michael@0: { michael@0: nsresult rv; michael@0: michael@0: bool succeeded; michael@0: rv = mManifestItem->GetRequestSucceeded(&succeeded); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (!succeeded || !mManifestItem->ParseSucceeded()) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mPinned) { michael@0: uint16_t status; michael@0: rv = mManifestItem->GetStatus(&status); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Treat these as there would be an update available, michael@0: // since this is indication of demand to remove this michael@0: // offline cache. michael@0: if (status == 404 || status == 410) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return mManifestItem->NeedsUpdate(); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::LoadCompleted(nsOfflineCacheUpdateItem *aItem) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this)); michael@0: michael@0: if (mState == STATE_FINISHED) { michael@0: LOG((" after completion, ignoring")); michael@0: return; michael@0: } michael@0: michael@0: // Keep the object alive through a Finish() call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (mState == STATE_CANCELLED) { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: if (mState == STATE_CHECKING) { michael@0: // Manifest load finished. michael@0: michael@0: if (mOnlyCheckUpdate) { michael@0: Finish(); michael@0: NotifyUpdateAvailability(CheckUpdateAvailability()); michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mManifestItem, michael@0: "Must have a manifest item in STATE_CHECKING."); michael@0: NS_ASSERTION(mManifestItem == aItem, michael@0: "Unexpected aItem in nsOfflineCacheUpdate::LoadCompleted"); michael@0: michael@0: // A 404 or 410 is interpreted as an intentional removal of michael@0: // the manifest file, rather than a transient server error. michael@0: // Obsolete this cache group if one of these is returned. michael@0: uint16_t status; michael@0: rv = mManifestItem->GetStatus(&status); michael@0: if (status == 404 || status == 410) { michael@0: LogToConsole("Offline cache manifest removed, cache cleared", mManifestItem); michael@0: mSucceeded = false; michael@0: if (mPreviousApplicationCache) { michael@0: if (mPinned) { michael@0: // Do not obsolete a pinned application. michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE); michael@0: } else { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_OBSOLETE); michael@0: mObsolete = true; michael@0: } michael@0: } else { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: mObsolete = true; michael@0: } michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: bool doUpdate; michael@0: if (NS_FAILED(HandleManifest(&doUpdate))) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: if (!doUpdate) { michael@0: LogToConsole("Offline cache doesn't need to update", mManifestItem); michael@0: michael@0: mSucceeded = false; michael@0: michael@0: AssociateDocuments(mPreviousApplicationCache); michael@0: michael@0: ScheduleImplicit(); michael@0: michael@0: // If we didn't need an implicit update, we can michael@0: // send noupdate and end the update now. michael@0: if (!mImplicitUpdate) { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE); michael@0: Finish(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: rv = mApplicationCache->MarkEntry(mManifestItem->mCacheKey, michael@0: mManifestItem->mItemType); michael@0: if (NS_FAILED(rv)) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: mState = STATE_DOWNLOADING; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING); michael@0: michael@0: // Start fetching resources. michael@0: ProcessNextURI(); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Normal load finished. michael@0: if (mItemsInProgress) // Just to be safe here! michael@0: --mItemsInProgress; michael@0: michael@0: bool succeeded; michael@0: rv = aItem->GetRequestSucceeded(&succeeded); michael@0: michael@0: if (mPinned && NS_SUCCEEDED(rv) && succeeded) { michael@0: uint32_t dummy_cache_type; michael@0: rv = mApplicationCache->GetTypes(aItem->mCacheKey, &dummy_cache_type); michael@0: bool item_doomed = NS_FAILED(rv); // can not find it? -> doomed michael@0: michael@0: if (item_doomed && michael@0: mPinnedEntryRetriesCount < kPinnedEntryRetriesLimit && michael@0: (aItem->mItemType & (nsIApplicationCache::ITEM_EXPLICIT | michael@0: nsIApplicationCache::ITEM_FALLBACK))) { michael@0: rv = EvictOneNonPinned(); michael@0: if (NS_FAILED(rv)) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: // This reverts the item state to UNINITIALIZED that makes it to michael@0: // be scheduled for download again. michael@0: rv = aItem->Cancel(); michael@0: if (NS_FAILED(rv)) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: mPinnedEntryRetriesCount++; michael@0: michael@0: LogToConsole("An unpinned offline cache deleted"); michael@0: michael@0: // Retry this item. michael@0: ProcessNextURI(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // According to parallelism this may imply more pinned retries count, michael@0: // but that is not critical, since at one moment the algoritm will michael@0: // stop anyway. Also, this code may soon be completely removed michael@0: // after we have a separate storage for pinned apps. michael@0: mPinnedEntryRetriesCount = 0; michael@0: michael@0: // Check for failures. 3XX, 4XX and 5XX errors on items explicitly michael@0: // listed in the manifest will cause the update to fail. michael@0: if (NS_FAILED(rv) || !succeeded) { michael@0: if (aItem->mItemType & michael@0: (nsIApplicationCache::ITEM_EXPLICIT | michael@0: nsIApplicationCache::ITEM_FALLBACK)) { michael@0: LogToConsole("Offline cache manifest item failed to load", aItem); michael@0: mSucceeded = false; michael@0: } michael@0: } else { michael@0: rv = mApplicationCache->MarkEntry(aItem->mCacheKey, aItem->mItemType); michael@0: if (NS_FAILED(rv)) { michael@0: mSucceeded = false; michael@0: } michael@0: } michael@0: michael@0: if (!mSucceeded) { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: Finish(); michael@0: return; michael@0: } michael@0: michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMCOMPLETED); michael@0: michael@0: ProcessNextURI(); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::ManifestCheckCompleted(nsresult aStatus, michael@0: const nsCString &aManifestHash) michael@0: { michael@0: // Keep the object alive through a Finish() call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: nsAutoCString firstManifestHash; michael@0: mManifestItem->GetManifestHash(firstManifestHash); michael@0: if (aManifestHash != firstManifestHash) { michael@0: LOG(("Manifest has changed during cache items download [%p]", this)); michael@0: LogToConsole("Offline cache manifest changed during update", mManifestItem); michael@0: aStatus = NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(aStatus)) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: } michael@0: michael@0: if (NS_FAILED(aStatus) && mRescheduleCount < kRescheduleLimit) { michael@0: // Do the final stuff but prevent notification of STATE_FINISHED. michael@0: // That would disconnect listeners that are responsible for document michael@0: // association after a successful update. Forwarding notifications michael@0: // from a new update through this dead update to them is absolutely michael@0: // correct. michael@0: FinishNoNotify(); michael@0: michael@0: nsRefPtr newUpdate = michael@0: new nsOfflineCacheUpdate(); michael@0: // Leave aDocument argument null. Only glues and children keep michael@0: // document instances. michael@0: newUpdate->Init(mManifestURI, mDocumentURI, nullptr, michael@0: mCustomProfileDir, mAppID, mInBrowser); michael@0: michael@0: // In a rare case the manifest will not be modified on the next refetch michael@0: // transfer all master document URIs to the new update to ensure that michael@0: // all documents refering it will be properly cached. michael@0: for (int32_t i = 0; i < mDocumentURIs.Count(); i++) { michael@0: newUpdate->StickDocument(mDocumentURIs[i]); michael@0: } michael@0: michael@0: newUpdate->mRescheduleCount = mRescheduleCount + 1; michael@0: newUpdate->AddObserver(this, false); michael@0: newUpdate->Schedule(); michael@0: } michael@0: else { michael@0: LogToConsole("Offline cache update done", mManifestItem); michael@0: Finish(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::Begin() michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::Begin [%p]", this)); michael@0: michael@0: // Keep the object alive through a ProcessNextURI()/Finish() call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: mItemsInProgress = 0; michael@0: michael@0: if (mState == STATE_CANCELLED) { michael@0: nsRefPtr > errorNotification = michael@0: NS_NewRunnableMethod(this, michael@0: &nsOfflineCacheUpdate::AsyncFinishWithError); michael@0: nsresult rv = NS_DispatchToMainThread(errorNotification); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mPartialUpdate) { michael@0: mState = STATE_DOWNLOADING; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_DOWNLOADING); michael@0: ProcessNextURI(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Start checking the manifest. michael@0: nsCOMPtr uri; michael@0: michael@0: mManifestItem = new nsOfflineManifestItem(mManifestURI, michael@0: mDocumentURI, michael@0: mApplicationCache, michael@0: mPreviousApplicationCache); michael@0: if (!mManifestItem) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: mState = STATE_CHECKING; michael@0: mByteProgress = 0; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_CHECKING); michael@0: michael@0: nsresult rv = mManifestItem->OpenChannel(this); michael@0: if (NS_FAILED(rv)) { michael@0: LoadCompleted(mManifestItem); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdate michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::AddExistingItems(uint32_t aType, michael@0: nsTArray* namespaceFilter) michael@0: { michael@0: if (!mPreviousApplicationCache) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (namespaceFilter && namespaceFilter->Length() == 0) { michael@0: // Don't bother to walk entries when there are no namespaces michael@0: // defined. michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t count = 0; michael@0: char **keys = nullptr; michael@0: nsresult rv = mPreviousApplicationCache->GatherEntries(aType, michael@0: &count, &keys); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: AutoFreeArray autoFree(count, keys); michael@0: michael@0: for (uint32_t i = 0; i < count; i++) { michael@0: if (namespaceFilter) { michael@0: bool found = false; michael@0: for (uint32_t j = 0; j < namespaceFilter->Length() && !found; j++) { michael@0: found = StringBeginsWith(nsDependentCString(keys[i]), michael@0: namespaceFilter->ElementAt(j)); michael@0: } michael@0: michael@0: if (!found) michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr uri; michael@0: if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) { michael@0: rv = AddURI(uri, aType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::ProcessNextURI() michael@0: { michael@0: // Keep the object alive through a Finish() call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, inprogress=%d, numItems=%d]", michael@0: this, mItemsInProgress, mItems.Length())); michael@0: michael@0: if (mState != STATE_DOWNLOADING) { michael@0: LOG((" should only be called from the DOWNLOADING state, ignoring")); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsOfflineCacheUpdateItem * runItem = nullptr; michael@0: uint32_t completedItems = 0; michael@0: for (uint32_t i = 0; i < mItems.Length(); ++i) { michael@0: nsOfflineCacheUpdateItem * item = mItems[i]; michael@0: michael@0: if (item->IsScheduled()) { michael@0: runItem = item; michael@0: break; michael@0: } michael@0: michael@0: if (item->IsCompleted()) michael@0: ++completedItems; michael@0: } michael@0: michael@0: if (completedItems == mItems.Length()) { michael@0: LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]: all items loaded", this)); michael@0: michael@0: if (mPartialUpdate) { michael@0: return Finish(); michael@0: } else { michael@0: // Verify that the manifest wasn't changed during the michael@0: // update, to prevent capturing a cache while the server michael@0: // is being updated. The check will call michael@0: // ManifestCheckCompleted() when it's done. michael@0: nsRefPtr manifestCheck = michael@0: new nsManifestCheck(this, mManifestURI, mDocumentURI); michael@0: if (NS_FAILED(manifestCheck->Begin())) { michael@0: mSucceeded = false; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: return Finish(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (!runItem) { michael@0: LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:" michael@0: " No more items to include in parallel load", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (LOG_ENABLED()) { michael@0: nsAutoCString spec; michael@0: runItem->mURI->GetSpec(spec); michael@0: LOG(("%p: Opening channel for %s", this, spec.get())); michael@0: } michael@0: #endif michael@0: michael@0: ++mItemsInProgress; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMSTARTED); michael@0: michael@0: nsresult rv = runItem->OpenChannel(this); michael@0: if (NS_FAILED(rv)) { michael@0: LoadCompleted(runItem); michael@0: return rv; michael@0: } michael@0: michael@0: if (mItemsInProgress >= kParallelLoadLimit) { michael@0: LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p]:" michael@0: " At parallel load limit", this)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This calls this method again via a post triggering michael@0: // a parallel item load michael@0: return NS_DispatchToCurrentThread(this); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::GatherObservers(nsCOMArray &aObservers) michael@0: { michael@0: for (int32_t i = 0; i < mWeakObservers.Count(); i++) { michael@0: nsCOMPtr observer = michael@0: do_QueryReferent(mWeakObservers[i]); michael@0: if (observer) michael@0: aObservers.AppendObject(observer); michael@0: else michael@0: mWeakObservers.RemoveObjectAt(i--); michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mObservers.Count(); i++) { michael@0: aObservers.AppendObject(mObservers[i]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::NotifyState(uint32_t state) michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::NotifyState [%p, %d]", this, state)); michael@0: michael@0: if (state == STATE_ERROR) { michael@0: LogToConsole("Offline cache update error", mManifestItem); michael@0: } michael@0: michael@0: nsCOMArray observers; michael@0: GatherObservers(observers); michael@0: michael@0: for (int32_t i = 0; i < observers.Count(); i++) { michael@0: observers[i]->UpdateStateChanged(this, state); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::NotifyUpdateAvailability(bool updateAvailable) michael@0: { michael@0: if (!mUpdateAvailableObserver) michael@0: return; michael@0: michael@0: LOG(("nsOfflineCacheUpdate::NotifyUpdateAvailability [this=%p, avail=%d]", michael@0: this, updateAvailable)); michael@0: michael@0: const char* topic = updateAvailable michael@0: ? "offline-cache-update-available" michael@0: : "offline-cache-update-unavailable"; michael@0: michael@0: nsCOMPtr observer; michael@0: observer.swap(mUpdateAvailableObserver); michael@0: observer->Observe(mManifestURI, topic, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::AssociateDocuments(nsIApplicationCache* cache) michael@0: { michael@0: if (!cache) { michael@0: LOG(("nsOfflineCacheUpdate::AssociateDocuments bypassed" michael@0: ", no cache provided [this=%p]", this)); michael@0: return; michael@0: } michael@0: michael@0: nsCOMArray observers; michael@0: GatherObservers(observers); michael@0: michael@0: for (int32_t i = 0; i < observers.Count(); i++) { michael@0: observers[i]->ApplicationCacheAvailable(cache); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::StickDocument(nsIURI *aDocumentURI) michael@0: { michael@0: if (!aDocumentURI) michael@0: return; michael@0: michael@0: mDocumentURIs.AppendObject(aDocumentURI); michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::SetOwner(nsOfflineCacheUpdateOwner *aOwner) michael@0: { michael@0: NS_ASSERTION(!mOwner, "Tried to set cache update owner twice."); michael@0: mOwner = aOwner->asWeakPtr(); michael@0: } michael@0: michael@0: bool michael@0: nsOfflineCacheUpdate::IsForGroupID(const nsCSubstring &groupID) michael@0: { michael@0: return mGroupID == groupID; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::UpdateFinished(nsOfflineCacheUpdate *aUpdate) michael@0: { michael@0: // Keep the object alive through a Finish() call. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: mImplicitUpdate = nullptr; michael@0: michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_NOUPDATE); michael@0: Finish(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::OnByteProgress(uint64_t byteIncrement) michael@0: { michael@0: mByteProgress += byteIncrement; michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ITEMPROGRESS); michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::ScheduleImplicit() michael@0: { michael@0: if (mDocumentURIs.Count() == 0) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsRefPtr update = new nsOfflineCacheUpdate(); michael@0: NS_ENSURE_TRUE(update, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsAutoCString clientID; michael@0: if (mPreviousApplicationCache) { michael@0: rv = mPreviousApplicationCache->GetClientID(clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else if (mApplicationCache) { michael@0: rv = mApplicationCache->GetClientID(clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: NS_ERROR("Offline cache update not having set mApplicationCache?"); michael@0: } michael@0: michael@0: rv = update->InitPartial(mManifestURI, clientID, mDocumentURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (int32_t i = 0; i < mDocumentURIs.Count(); i++) { michael@0: rv = update->AddURI(mDocumentURIs[i], michael@0: nsIApplicationCache::ITEM_IMPLICIT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: update->SetOwner(this); michael@0: rv = update->Begin(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mImplicitUpdate = update; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::FinishNoNotify() michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::Finish [%p]", this)); michael@0: michael@0: mState = STATE_FINISHED; michael@0: michael@0: if (!mPartialUpdate && !mOnlyCheckUpdate) { michael@0: if (mSucceeded) { michael@0: nsIArray *namespaces = mManifestItem->GetNamespaces(); michael@0: nsresult rv = mApplicationCache->AddNamespaces(namespaces); michael@0: if (NS_FAILED(rv)) { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: mSucceeded = false; michael@0: } michael@0: michael@0: rv = mApplicationCache->Activate(); michael@0: if (NS_FAILED(rv)) { michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_ERROR); michael@0: mSucceeded = false; michael@0: } michael@0: michael@0: AssociateDocuments(mApplicationCache); michael@0: } michael@0: michael@0: if (mObsolete) { michael@0: nsCOMPtr appCacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID); michael@0: if (appCacheService) { michael@0: nsAutoCString groupID; michael@0: mApplicationCache->GetGroupID(groupID); michael@0: appCacheService->DeactivateGroup(groupID); michael@0: } michael@0: } michael@0: michael@0: if (!mSucceeded) { michael@0: // Update was not merged, mark all the loads as failures michael@0: for (uint32_t i = 0; i < mItems.Length(); i++) { michael@0: mItems[i]->Cancel(); michael@0: } michael@0: michael@0: mApplicationCache->Discard(); michael@0: } michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (mOwner) { michael@0: rv = mOwner->UpdateFinished(this); michael@0: // mozilla::WeakPtr is missing some key features, like setting it to michael@0: // null explicitly. michael@0: mOwner = mozilla::WeakPtr(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::Finish() michael@0: { michael@0: nsresult rv = FinishNoNotify(); michael@0: michael@0: NotifyState(nsIOfflineCacheUpdateObserver::STATE_FINISHED); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsOfflineCacheUpdate::AsyncFinishWithError() michael@0: { michael@0: NotifyState(nsOfflineCacheUpdate::STATE_ERROR); michael@0: Finish(); michael@0: } michael@0: michael@0: static nsresult michael@0: EvictOneOfCacheGroups(nsIApplicationCacheService *cacheService, michael@0: uint32_t count, const char * const *groups) michael@0: { michael@0: nsresult rv; michael@0: unsigned int i; michael@0: michael@0: for (i = 0; i < count; i++) { michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), groups[i]); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsDependentCString group_name(groups[i]); michael@0: nsCOMPtr cache; michael@0: rv = cacheService->GetActiveCache(group_name, getter_AddRefs(cache)); michael@0: // Maybe someone in another thread or process have deleted it. michael@0: if (NS_FAILED(rv) || !cache) michael@0: continue; michael@0: michael@0: bool pinned; michael@0: rv = nsOfflineCacheUpdateService::OfflineAppPinnedForURI(uri, michael@0: nullptr, michael@0: &pinned); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!pinned) { michael@0: rv = cache->Discard(); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::EvictOneNonPinned() michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr cacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t count; michael@0: char **groups; michael@0: rv = cacheService->GetGroupsTimeOrdered(&count, &groups); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = EvictOneOfCacheGroups(cacheService, count, groups); michael@0: michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, groups); michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdate::nsIOfflineCacheUpdate michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetUpdateDomain(nsACString &aUpdateDomain) michael@0: { michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: aUpdateDomain = mUpdateDomain; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetStatus(uint16_t *aStatus) michael@0: { michael@0: switch (mState) { michael@0: case STATE_CHECKING : michael@0: *aStatus = nsIDOMOfflineResourceList::CHECKING; michael@0: return NS_OK; michael@0: case STATE_DOWNLOADING : michael@0: *aStatus = nsIDOMOfflineResourceList::DOWNLOADING; michael@0: return NS_OK; michael@0: default : michael@0: *aStatus = nsIDOMOfflineResourceList::IDLE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetPartial(bool *aPartial) michael@0: { michael@0: *aPartial = mPartialUpdate || mOnlyCheckUpdate; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetManifestURI(nsIURI **aManifestURI) michael@0: { michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: NS_IF_ADDREF(*aManifestURI = mManifestURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetSucceeded(bool *aSucceeded) michael@0: { michael@0: NS_ENSURE_TRUE(mState == STATE_FINISHED, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: *aSucceeded = mSucceeded; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetIsUpgrade(bool *aIsUpgrade) michael@0: { michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: *aIsUpgrade = (mPreviousApplicationCache != nullptr); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsOfflineCacheUpdate::AddURI(nsIURI *aURI, uint32_t aType) michael@0: { michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (mState >= STATE_DOWNLOADING) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // Resource URIs must have the same scheme as the manifest. michael@0: nsAutoCString scheme; michael@0: aURI->GetScheme(scheme); michael@0: michael@0: bool match; michael@0: if (NS_FAILED(mManifestURI->SchemeIs(scheme.get(), &match)) || !match) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Don't fetch the same URI twice. michael@0: for (uint32_t i = 0; i < mItems.Length(); i++) { michael@0: bool equals; michael@0: if (NS_SUCCEEDED(mItems[i]->mURI->Equals(aURI, &equals)) && equals) { michael@0: // retain both types. michael@0: mItems[i]->mItemType |= aType; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: nsRefPtr item = michael@0: new nsOfflineCacheUpdateItem(aURI, michael@0: mDocumentURI, michael@0: mApplicationCache, michael@0: mPreviousApplicationCache, michael@0: aType); michael@0: if (!item) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: mItems.AppendElement(item); michael@0: mAddedItems = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::AddDynamicURI(nsIURI *aURI) michael@0: { michael@0: if (GeckoProcessType_Default != XRE_GetProcessType()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: // If this is a partial update and the resource is already in the michael@0: // cache, we should only mark the entry, not fetch it again. michael@0: if (mPartialUpdate) { michael@0: nsAutoCString key; michael@0: GetCacheKey(aURI, key); michael@0: michael@0: uint32_t types; michael@0: nsresult rv = mApplicationCache->GetTypes(key, &types); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (!(types & nsIApplicationCache::ITEM_DYNAMIC)) { michael@0: mApplicationCache->MarkEntry michael@0: (key, nsIApplicationCache::ITEM_DYNAMIC); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return AddURI(aURI, nsIApplicationCache::ITEM_DYNAMIC); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::Cancel() michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::Cancel [%p]", this)); michael@0: michael@0: if ((mState == STATE_FINISHED) || (mState == STATE_CANCELLED)) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: mState = STATE_CANCELLED; michael@0: mSucceeded = false; michael@0: michael@0: // Cancel all running downloads michael@0: for (uint32_t i = 0; i < mItems.Length(); ++i) { michael@0: nsOfflineCacheUpdateItem * item = mItems[i]; michael@0: michael@0: if (item->IsInProgress()) michael@0: item->Cancel(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::AddObserver(nsIOfflineCacheUpdateObserver *aObserver, michael@0: bool aHoldWeak) michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::AddObserver [%p] to update [%p]", aObserver, this)); michael@0: michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aHoldWeak) { michael@0: nsCOMPtr weakRef = do_GetWeakReference(aObserver); michael@0: mWeakObservers.AppendObject(weakRef); michael@0: } else { michael@0: mObservers.AppendObject(aObserver); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::RemoveObserver(nsIOfflineCacheUpdateObserver *aObserver) michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::RemoveObserver [%p] from update [%p]", aObserver, this)); michael@0: michael@0: NS_ENSURE_TRUE(mState >= STATE_INITIALIZED, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: for (int32_t i = 0; i < mWeakObservers.Count(); i++) { michael@0: nsCOMPtr observer = michael@0: do_QueryReferent(mWeakObservers[i]); michael@0: if (observer == aObserver) { michael@0: mWeakObservers.RemoveObjectAt(i); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mObservers.Count(); i++) { michael@0: if (mObservers[i] == aObserver) { michael@0: mObservers.RemoveObjectAt(i); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::GetByteProgress(uint64_t * _result) michael@0: { michael@0: NS_ENSURE_ARG(_result); michael@0: michael@0: *_result = mByteProgress; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::Schedule() michael@0: { michael@0: LOG(("nsOfflineCacheUpdate::Schedule [%p]", this)); michael@0: michael@0: nsOfflineCacheUpdateService* service = michael@0: nsOfflineCacheUpdateService::EnsureService(); michael@0: michael@0: if (!service) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return service->ScheduleUpdate(this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate, michael@0: uint32_t aState) michael@0: { michael@0: if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) { michael@0: // Take the mSucceeded flag from the underlying update, we will be michael@0: // queried for it soon. mSucceeded of this update is false (manifest michael@0: // check failed) but the subsequent re-fetch update might succeed michael@0: bool succeeded; michael@0: aUpdate->GetSucceeded(&succeeded); michael@0: mSucceeded = succeeded; michael@0: } michael@0: michael@0: NotifyState(aState); michael@0: if (aState == nsIOfflineCacheUpdateObserver::STATE_FINISHED) michael@0: aUpdate->RemoveObserver(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::ApplicationCacheAvailable(nsIApplicationCache *applicationCache) michael@0: { michael@0: AssociateDocuments(applicationCache); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsOfflineCacheUpdate::nsIRunable michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsOfflineCacheUpdate::Run() michael@0: { michael@0: ProcessNextURI(); michael@0: return NS_OK; michael@0: }