michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 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: #include "nsDOMOfflineResourceList.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsError.h" michael@0: #include "mozilla/dom/DOMStringList.h" michael@0: #include "nsIPrefetchService.h" michael@0: #include "nsCPrefetchService.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsICacheSession.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsIOfflineCacheUpdate.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "mozilla/dom/OfflineResourceListBinding.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: #include "nsXULAppAPI.h" michael@0: #define IS_CHILD_PROCESS() \ michael@0: (GeckoProcessType_Default != XRE_GetProcessType()) michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // Event names michael@0: michael@0: #define CHECKING_STR "checking" michael@0: #define ERROR_STR "error" michael@0: #define NOUPDATE_STR "noupdate" michael@0: #define DOWNLOADING_STR "downloading" michael@0: #define PROGRESS_STR "progress" michael@0: #define CACHED_STR "cached" michael@0: #define UPDATEREADY_STR "updateready" michael@0: #define OBSOLETE_STR "obsolete" michael@0: michael@0: // To prevent abuse of the resource list for data storage, the number michael@0: // of offline urls and their length are limited. michael@0: michael@0: static const char kMaxEntriesPref[] = "offline.max_site_resources"; michael@0: #define DEFAULT_MAX_ENTRIES 100 michael@0: #define MAX_URI_LENGTH 2048 michael@0: michael@0: // michael@0: // nsDOMOfflineResourceList michael@0: // michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMOfflineResourceList, michael@0: DOMEventTargetHelper, michael@0: mCacheUpdate, michael@0: mPendingEvents) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMOfflineResourceList) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMOfflineResourceList) michael@0: NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsDOMOfflineResourceList, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(nsDOMOfflineResourceList, DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, checking) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, error) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, noupdate) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, downloading) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, progress) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, cached) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, updateready) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, obsolete) michael@0: michael@0: nsDOMOfflineResourceList::nsDOMOfflineResourceList(nsIURI *aManifestURI, michael@0: nsIURI *aDocumentURI, michael@0: nsPIDOMWindow *aWindow) michael@0: : DOMEventTargetHelper(aWindow) michael@0: , mInitialized(false) michael@0: , mManifestURI(aManifestURI) michael@0: , mDocumentURI(aDocumentURI) michael@0: , mExposeCacheUpdateStatus(true) michael@0: , mStatus(nsIDOMOfflineResourceList::IDLE) michael@0: , mCachedKeys(nullptr) michael@0: , mCachedKeysCount(0) michael@0: { michael@0: } michael@0: michael@0: nsDOMOfflineResourceList::~nsDOMOfflineResourceList() michael@0: { michael@0: ClearCachedKeys(); michael@0: } michael@0: michael@0: JSObject* michael@0: nsDOMOfflineResourceList::WrapObject(JSContext* aCx) michael@0: { michael@0: return OfflineResourceListBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::Init() michael@0: { michael@0: if (mInitialized) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mManifestURI) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: mManifestURI->GetAsciiSpec(mManifestSpec); michael@0: michael@0: nsresult rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckSameOriginURI(mManifestURI, mDocumentURI, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Dynamically-managed resources are stored as a separate ownership list michael@0: // from the manifest. michael@0: nsCOMPtr innerURI = NS_GetInnermostURI(mDocumentURI); michael@0: if (!innerURI) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!IS_CHILD_PROCESS()) michael@0: { michael@0: mApplicationCacheService = michael@0: do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Check for in-progress cache updates michael@0: nsCOMPtr cacheUpdateService = michael@0: do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t numUpdates; michael@0: rv = cacheUpdateService->GetNumUpdates(&numUpdates); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (uint32_t i = 0; i < numUpdates; i++) { michael@0: nsCOMPtr cacheUpdate; michael@0: rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: UpdateAdded(cacheUpdate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: // watch for new offline cache updates michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: rv = observerService->AddObserver(this, "offline-cache-update-added", true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = observerService->AddObserver(this, "offline-cache-update-completed", true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mInitialized = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsDOMOfflineResourceList::Disconnect() michael@0: { michael@0: mPendingEvents.Clear(); michael@0: michael@0: if (mListenerManager) { michael@0: mListenerManager->Disconnect(); michael@0: mListenerManager = nullptr; michael@0: } michael@0: } michael@0: michael@0: // michael@0: // nsDOMOfflineResourceList::nsIDOMOfflineResourceList michael@0: // michael@0: michael@0: already_AddRefed michael@0: nsDOMOfflineResourceList::GetMozItems(ErrorResult& aRv) michael@0: { michael@0: if (IS_CHILD_PROCESS()) { michael@0: aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr items = new DOMStringList(); michael@0: michael@0: // If we are not associated with an application cache, return an michael@0: // empty list. michael@0: nsCOMPtr appCache = GetDocumentAppCache(); michael@0: if (!appCache) { michael@0: return items.forget(); michael@0: } michael@0: michael@0: aRv = Init(); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: uint32_t length; michael@0: char **keys; michael@0: aRv = appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, michael@0: &length, &keys); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < length; i++) { michael@0: items->Add(NS_ConvertUTF8toUTF16(keys[i])); michael@0: } michael@0: michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(length, keys); michael@0: michael@0: return items.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::GetMozItems(nsISupports** aItems) michael@0: { michael@0: ErrorResult rv; michael@0: nsRefPtr items = GetMozItems(rv); michael@0: items.forget(aItems); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, bool* aExists) michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr appCache = GetDocumentAppCache(); michael@0: if (!appCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: nsAutoCString key; michael@0: rv = GetCacheKey(aURI, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t types; michael@0: rv = appCache->GetTypes(key, &types); michael@0: if (rv == NS_ERROR_CACHE_KEY_NOT_FOUND) { michael@0: *aExists = false; michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aExists = ((types & nsIApplicationCache::ITEM_DYNAMIC) != 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::GetMozLength(uint32_t *aLength) michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: if (!mManifestURI) { michael@0: *aLength = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CacheKeys(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aLength = mCachedKeysCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI) michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: SetDOMStringToNull(aURI); michael@0: michael@0: rv = CacheKeys(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aIndex >= mCachedKeysCount) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: CopyUTF8toUTF16(mCachedKeys[aIndex], aURI); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::MozAdd(const nsAString& aURI) michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsCOMPtr appCache = GetDocumentAppCache(); michael@0: if (!appCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: if (aURI.Length() > MAX_URI_LENGTH) return NS_ERROR_DOM_BAD_URI; michael@0: michael@0: // this will fail if the URI is not absolute michael@0: nsCOMPtr requestedURI; michael@0: rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString scheme; michael@0: rv = requestedURI->GetScheme(scheme); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool match; michael@0: rv = mManifestURI->SchemeIs(scheme.get(), &match); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!match) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: uint32_t length; michael@0: rv = GetMozLength(&length); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: uint32_t maxEntries = michael@0: Preferences::GetUint(kMaxEntriesPref, DEFAULT_MAX_ENTRIES); michael@0: michael@0: if (length > maxEntries) return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: ClearCachedKeys(); michael@0: michael@0: nsCOMPtr update = michael@0: do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString clientID; michael@0: rv = appCache->GetClientID(clientID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = update->InitPartial(mManifestURI, clientID, mDocumentURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = update->AddDynamicURI(requestedURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = update->Schedule(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::MozRemove(const nsAString& aURI) michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsCOMPtr appCache = GetDocumentAppCache(); michael@0: if (!appCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: nsAutoCString key; michael@0: rv = GetCacheKey(aURI, key); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ClearCachedKeys(); michael@0: michael@0: // XXX: This is a race condition. remove() is specced to remove michael@0: // from the currently associated application cache, but if this michael@0: // happens during an update (or after an update, if we haven't michael@0: // swapped yet), that remove() will be lost when the next update is michael@0: // finished. Need to bring this issue up. michael@0: michael@0: rv = appCache->UnmarkEntry(key, nsIApplicationCache::ITEM_DYNAMIC); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::GetStatus(uint16_t *aStatus) michael@0: { michael@0: nsresult rv = Init(); michael@0: michael@0: // Init may fail with INVALID_STATE_ERR if there is no manifest URI. michael@0: // The status attribute should not throw that exception, convert it michael@0: // to an UNCACHED. michael@0: if (rv == NS_ERROR_DOM_INVALID_STATE_ERR || michael@0: !nsContentUtils::OfflineAppAllowed(mDocumentURI)) { michael@0: *aStatus = nsIDOMOfflineResourceList::UNCACHED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If this object is not associated with a cache, return UNCACHED michael@0: nsCOMPtr appCache = GetDocumentAppCache(); michael@0: if (!appCache) { michael@0: *aStatus = nsIDOMOfflineResourceList::UNCACHED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // If there is an update in process, use its status. michael@0: if (mCacheUpdate && mExposeCacheUpdateStatus) { michael@0: rv = mCacheUpdate->GetStatus(aStatus); michael@0: if (NS_SUCCEEDED(rv) && *aStatus != nsIDOMOfflineResourceList::IDLE) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (mAvailableApplicationCache) { michael@0: *aStatus = nsIDOMOfflineResourceList::UPDATEREADY; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *aStatus = mStatus; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::Update() michael@0: { michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsCOMPtr updateService = michael@0: do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr window = michael@0: do_QueryInterface(GetOwner()); michael@0: michael@0: nsCOMPtr update; michael@0: rv = updateService->ScheduleUpdate(mManifestURI, mDocumentURI, michael@0: window, getter_AddRefs(update)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::SwapCache() michael@0: { michael@0: nsresult rv = Init(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsCOMPtr currentAppCache = GetDocumentAppCache(); michael@0: if (!currentAppCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: // Check the current and potentially newly available cache are not identical. michael@0: if (mAvailableApplicationCache == currentAppCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: if (mAvailableApplicationCache) { michael@0: nsCString currClientId, availClientId; michael@0: currentAppCache->GetClientID(currClientId); michael@0: mAvailableApplicationCache->GetClientID(availClientId); michael@0: if (availClientId == currClientId) michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } else if (mStatus != OBSOLETE) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: ClearCachedKeys(); michael@0: michael@0: nsCOMPtr appCacheContainer = michael@0: GetDocumentAppCacheContainer(); michael@0: michael@0: // In the case of an obsolete cache group, newAppCache might be null. michael@0: // We will disassociate from the cache in that case. michael@0: if (appCacheContainer) { michael@0: rv = appCacheContainer->SetApplicationCache(mAvailableApplicationCache); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mAvailableApplicationCache = nullptr; michael@0: mStatus = nsIDOMOfflineResourceList::IDLE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsDOMOfflineResourceList::nsIDOMEventTarget michael@0: // michael@0: michael@0: void michael@0: nsDOMOfflineResourceList::FirePendingEvents() michael@0: { michael@0: for (int32_t i = 0; i < mPendingEvents.Count(); ++i) { michael@0: bool dummy; michael@0: nsCOMPtr event = mPendingEvents[i]; michael@0: DispatchEvent(event, &dummy); michael@0: } michael@0: mPendingEvents.Clear(); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::SendEvent(const nsAString &aEventName) michael@0: { michael@0: // Don't send events to closed windows michael@0: if (!GetOwner()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!GetOwner()->GetDocShell()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: nsresult rv = EventDispatcher::CreateEvent(this, nullptr, nullptr, michael@0: NS_LITERAL_STRING("Events"), michael@0: getter_AddRefs(event)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: event->InitEvent(aEventName, false, true); michael@0: michael@0: // We assume anyone that managed to call SendEvent is trusted michael@0: event->SetTrusted(true); michael@0: michael@0: // If the window is frozen or we're still catching up on events that were michael@0: // queued while frozen, save the event for later. michael@0: if (GetOwner()->IsFrozen() || mPendingEvents.Count() > 0) { michael@0: mPendingEvents.AppendObject(event); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool dummy; michael@0: DispatchEvent(event, &dummy); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // nsDOMOfflineResourceList::nsIObserver michael@0: // michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!strcmp(aTopic, "offline-cache-update-added")) { michael@0: nsCOMPtr update = do_QueryInterface(aSubject); michael@0: if (update) { michael@0: UpdateAdded(update); michael@0: } michael@0: } else if (!strcmp(aTopic, "offline-cache-update-completed")) { michael@0: nsCOMPtr update = do_QueryInterface(aSubject); michael@0: if (update) { michael@0: UpdateCompleted(update); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsDOMOfflineResourceList::nsIOfflineCacheUpdateObserver michael@0: // michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate, michael@0: uint32_t event) michael@0: { michael@0: mExposeCacheUpdateStatus = michael@0: (event == STATE_CHECKING) || michael@0: (event == STATE_DOWNLOADING) || michael@0: (event == STATE_ITEMSTARTED) || michael@0: (event == STATE_ITEMCOMPLETED) || michael@0: // During notification of "obsolete" we must expose state of the update michael@0: (event == STATE_OBSOLETE); michael@0: michael@0: switch (event) { michael@0: case STATE_ERROR: michael@0: SendEvent(NS_LITERAL_STRING(ERROR_STR)); michael@0: break; michael@0: case STATE_CHECKING: michael@0: SendEvent(NS_LITERAL_STRING(CHECKING_STR)); michael@0: break; michael@0: case STATE_NOUPDATE: michael@0: SendEvent(NS_LITERAL_STRING(NOUPDATE_STR)); michael@0: break; michael@0: case STATE_OBSOLETE: michael@0: mStatus = nsIDOMOfflineResourceList::OBSOLETE; michael@0: mAvailableApplicationCache = nullptr; michael@0: SendEvent(NS_LITERAL_STRING(OBSOLETE_STR)); michael@0: break; michael@0: case STATE_DOWNLOADING: michael@0: SendEvent(NS_LITERAL_STRING(DOWNLOADING_STR)); michael@0: break; michael@0: case STATE_ITEMSTARTED: michael@0: SendEvent(NS_LITERAL_STRING(PROGRESS_STR)); michael@0: break; michael@0: case STATE_ITEMCOMPLETED: michael@0: // Nothing to do here... michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMOfflineResourceList::ApplicationCacheAvailable(nsIApplicationCache *aApplicationCache) michael@0: { michael@0: nsCOMPtr currentAppCache = GetDocumentAppCache(); michael@0: if (currentAppCache) { michael@0: // Document already has a cache, we cannot override it. swapCache is michael@0: // here to do it on demand. michael@0: michael@0: // If the newly available cache is identical to the current cache, then michael@0: // just ignore this event. michael@0: if (aApplicationCache == currentAppCache) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCString currClientId, availClientId; michael@0: currentAppCache->GetClientID(currClientId); michael@0: aApplicationCache->GetClientID(availClientId); michael@0: if (availClientId == currClientId) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mAvailableApplicationCache = aApplicationCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr appCacheContainer = michael@0: GetDocumentAppCacheContainer(); michael@0: michael@0: if (appCacheContainer) { michael@0: appCacheContainer->SetApplicationCache(aApplicationCache); michael@0: } michael@0: michael@0: mAvailableApplicationCache = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::GetCacheKey(const nsAString &aURI, nsCString &aKey) michael@0: { michael@0: nsCOMPtr requestedURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return GetCacheKey(requestedURI, aKey); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::UpdateAdded(nsIOfflineCacheUpdate *aUpdate) michael@0: { michael@0: // Ignore partial updates. michael@0: bool partial; michael@0: nsresult rv = aUpdate->GetPartial(&partial); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (partial) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr updateURI; michael@0: rv = aUpdate->GetManifestURI(getter_AddRefs(updateURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool equals; michael@0: rv = updateURI->Equals(mManifestURI, &equals); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!equals) { michael@0: // This update doesn't belong to us michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(!mCacheUpdate, NS_ERROR_FAILURE); michael@0: michael@0: // We don't need to emit signals here. Updates are either added michael@0: // when they are scheduled (in which case they are always IDLE) or michael@0: // they are added when the applicationCache object is initialized, so there michael@0: // are no listeners to accept signals anyway. michael@0: michael@0: mCacheUpdate = aUpdate; michael@0: mCacheUpdate->AddObserver(this, true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMOfflineResourceList::GetDocumentAppCacheContainer() michael@0: { michael@0: nsCOMPtr webnav = do_GetInterface(GetOwner()); michael@0: if (!webnav) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr appCacheContainer = michael@0: do_GetInterface(webnav); michael@0: return appCacheContainer.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMOfflineResourceList::GetDocumentAppCache() michael@0: { michael@0: nsCOMPtr appCacheContainer = michael@0: GetDocumentAppCacheContainer(); michael@0: michael@0: if (appCacheContainer) { michael@0: nsCOMPtr applicationCache; michael@0: appCacheContainer->GetApplicationCache( michael@0: getter_AddRefs(applicationCache)); michael@0: return applicationCache.forget(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate) michael@0: { michael@0: if (aUpdate != mCacheUpdate) { michael@0: // This isn't the update we're watching. michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool partial; michael@0: mCacheUpdate->GetPartial(&partial); michael@0: bool isUpgrade; michael@0: mCacheUpdate->GetIsUpgrade(&isUpgrade); michael@0: michael@0: bool succeeded; michael@0: nsresult rv = mCacheUpdate->GetSucceeded(&succeeded); michael@0: michael@0: mCacheUpdate->RemoveObserver(this); michael@0: mCacheUpdate = nullptr; michael@0: michael@0: if (NS_SUCCEEDED(rv) && succeeded && !partial) { michael@0: mStatus = nsIDOMOfflineResourceList::IDLE; michael@0: if (isUpgrade) { michael@0: SendEvent(NS_LITERAL_STRING(UPDATEREADY_STR)); michael@0: } else { michael@0: SendEvent(NS_LITERAL_STRING(CACHED_STR)); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::GetCacheKey(nsIURI *aURI, nsCString &aKey) michael@0: { michael@0: nsresult rv = aURI->GetAsciiSpec(aKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // url fragments aren't used in cache keys michael@0: nsAutoCString::const_iterator specStart, specEnd; michael@0: aKey.BeginReading(specStart); michael@0: aKey.EndReading(specEnd); michael@0: if (FindCharInReadable('#', specStart, specEnd)) { michael@0: aKey.BeginReading(specEnd); michael@0: aKey = Substring(specEnd, specStart); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMOfflineResourceList::CacheKeys() michael@0: { michael@0: if (IS_CHILD_PROCESS()) michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: michael@0: if (mCachedKeys) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr window = do_QueryInterface(GetOwner()); michael@0: nsCOMPtr webNav = do_GetInterface(window); michael@0: nsCOMPtr loadContext = do_QueryInterface(webNav); michael@0: michael@0: uint32_t appId = 0; michael@0: bool inBrowser = false; michael@0: if (loadContext) { michael@0: loadContext->GetAppId(&appId); michael@0: loadContext->GetIsInBrowserElement(&inBrowser); michael@0: } michael@0: michael@0: nsAutoCString groupID; michael@0: mApplicationCacheService->BuildGroupIDForApp( michael@0: mManifestURI, appId, inBrowser, groupID); michael@0: michael@0: nsCOMPtr appCache; michael@0: mApplicationCacheService->GetActiveCache(groupID, michael@0: getter_AddRefs(appCache)); michael@0: michael@0: if (!appCache) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: return appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, michael@0: &mCachedKeysCount, &mCachedKeys); michael@0: } michael@0: michael@0: void michael@0: nsDOMOfflineResourceList::ClearCachedKeys() michael@0: { michael@0: if (mCachedKeys) { michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCachedKeysCount, mCachedKeys); michael@0: mCachedKeys = nullptr; michael@0: mCachedKeysCount = 0; michael@0: } michael@0: }