diff options
Diffstat (limited to 'dom/offline')
-rw-r--r-- | dom/offline/crashtests/408431-1.html | 25 | ||||
-rw-r--r-- | dom/offline/crashtests/crashtests.list | 1 | ||||
-rw-r--r-- | dom/offline/moz.build | 18 | ||||
-rw-r--r-- | dom/offline/nsDOMOfflineResourceList.cpp | 848 | ||||
-rw-r--r-- | dom/offline/nsDOMOfflineResourceList.h | 171 |
5 files changed, 1063 insertions, 0 deletions
diff --git a/dom/offline/crashtests/408431-1.html b/dom/offline/crashtests/408431-1.html new file mode 100644 index 0000000000..14bb2203f1 --- /dev/null +++ b/dom/offline/crashtests/408431-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +try { + navigator.pendingOfflineLoads[0]; +} catch (e) { +} + +try { + navigator.pendingOfflineLoads[1000]; +} catch (e) { +} + +try { + navigator.pendingOfflineLoads[-1]; +} catch (e) { +} + +</script> +</head> +<body> +</body> +</html> diff --git a/dom/offline/crashtests/crashtests.list b/dom/offline/crashtests/crashtests.list new file mode 100644 index 0000000000..a185168c26 --- /dev/null +++ b/dom/offline/crashtests/crashtests.list @@ -0,0 +1 @@ +load 408431-1.html diff --git a/dom/offline/moz.build b/dom/offline/moz.build new file mode 100644 index 0000000000..1e3c192c50 --- /dev/null +++ b/dom/offline/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsDOMOfflineResourceList.h', +] +UNIFIED_SOURCES += [ + 'nsDOMOfflineResourceList.cpp', +] + +LOCAL_INCLUDES += [ + '/dom/base', +] + +FINAL_LIBRARY = 'xul' diff --git a/dom/offline/nsDOMOfflineResourceList.cpp b/dom/offline/nsDOMOfflineResourceList.cpp new file mode 100644 index 0000000000..fa3409ff4f --- /dev/null +++ b/dom/offline/nsDOMOfflineResourceList.cpp @@ -0,0 +1,848 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDOMOfflineResourceList.h" +#include "nsIDOMEvent.h" +#include "nsIScriptSecurityManager.h" +#include "nsError.h" +#include "mozilla/dom/DOMStringList.h" +#include "nsIPrefetchService.h" +#include "nsCPrefetchService.h" +#include "nsNetUtil.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsContentUtils.h" +#include "nsIObserverService.h" +#include "nsIScriptGlobalObject.h" +#include "nsIWebNavigation.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/OfflineResourceListBinding.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/BasePrincipal.h" + +#include "nsXULAppAPI.h" +#define IS_CHILD_PROCESS() \ + (GeckoProcessType_Default != XRE_GetProcessType()) + +using namespace mozilla; +using namespace mozilla::dom; + +// Event names + +#define CHECKING_STR "checking" +#define ERROR_STR "error" +#define NOUPDATE_STR "noupdate" +#define DOWNLOADING_STR "downloading" +#define PROGRESS_STR "progress" +#define CACHED_STR "cached" +#define UPDATEREADY_STR "updateready" +#define OBSOLETE_STR "obsolete" + +// To prevent abuse of the resource list for data storage, the number +// of offline urls and their length are limited. + +static const char kMaxEntriesPref[] = "offline.max_site_resources"; +#define DEFAULT_MAX_ENTRIES 100 +#define MAX_URI_LENGTH 2048 + +// +// nsDOMOfflineResourceList +// + +NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMOfflineResourceList, + DOMEventTargetHelper, + mCacheUpdate, + mPendingEvents) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMOfflineResourceList) + NS_INTERFACE_MAP_ENTRY(nsIDOMOfflineResourceList) + NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(nsDOMOfflineResourceList, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(nsDOMOfflineResourceList, DOMEventTargetHelper) + +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, checking) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, error) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, noupdate) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, downloading) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, progress) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, cached) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, updateready) +NS_IMPL_EVENT_HANDLER(nsDOMOfflineResourceList, obsolete) + +nsDOMOfflineResourceList::nsDOMOfflineResourceList(nsIURI *aManifestURI, + nsIURI *aDocumentURI, + nsIPrincipal *aLoadingPrincipal, + nsPIDOMWindowInner *aWindow) + : DOMEventTargetHelper(aWindow) + , mInitialized(false) + , mManifestURI(aManifestURI) + , mDocumentURI(aDocumentURI) + , mLoadingPrincipal(aLoadingPrincipal) + , mExposeCacheUpdateStatus(true) + , mStatus(nsIDOMOfflineResourceList::IDLE) + , mCachedKeys(nullptr) + , mCachedKeysCount(0) +{ +} + +nsDOMOfflineResourceList::~nsDOMOfflineResourceList() +{ + ClearCachedKeys(); +} + +JSObject* +nsDOMOfflineResourceList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return OfflineResourceListBinding::Wrap(aCx, this, aGivenProto); +} + +nsresult +nsDOMOfflineResourceList::Init() +{ + if (mInitialized) { + return NS_OK; + } + + if (!mManifestURI) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + mManifestURI->GetAsciiSpec(mManifestSpec); + + nsresult rv = nsContentUtils::GetSecurityManager()-> + CheckSameOriginURI(mManifestURI, mDocumentURI, true); + NS_ENSURE_SUCCESS(rv, rv); + + // Dynamically-managed resources are stored as a separate ownership list + // from the manifest. + nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(mDocumentURI); + if (!innerURI) + return NS_ERROR_FAILURE; + + if (!IS_CHILD_PROCESS()) + { + mApplicationCacheService = + do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Check for in-progress cache updates + nsCOMPtr<nsIOfflineCacheUpdateService> cacheUpdateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numUpdates; + rv = cacheUpdateService->GetNumUpdates(&numUpdates); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < numUpdates; i++) { + nsCOMPtr<nsIOfflineCacheUpdate> cacheUpdate; + rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate)); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateAdded(cacheUpdate); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // watch for new offline cache updates + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + rv = observerService->AddObserver(this, "offline-cache-update-added", true); + NS_ENSURE_SUCCESS(rv, rv); + rv = observerService->AddObserver(this, "offline-cache-update-completed", true); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return NS_OK; +} + +void +nsDOMOfflineResourceList::Disconnect() +{ + mPendingEvents.Clear(); + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } +} + +// +// nsDOMOfflineResourceList::nsIDOMOfflineResourceList +// + +already_AddRefed<DOMStringList> +nsDOMOfflineResourceList::GetMozItems(ErrorResult& aRv) +{ + if (IS_CHILD_PROCESS()) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + RefPtr<DOMStringList> items = new DOMStringList(); + + // If we are not associated with an application cache, return an + // empty list. + nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache(); + if (!appCache) { + return items.forget(); + } + + aRv = Init(); + if (aRv.Failed()) { + return nullptr; + } + + uint32_t length; + char **keys; + aRv = appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, + &length, &keys); + if (aRv.Failed()) { + return nullptr; + } + + for (uint32_t i = 0; i < length; i++) { + items->Add(NS_ConvertUTF8toUTF16(keys[i])); + } + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(length, keys); + + return items.forget(); +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::GetMozItems(nsISupports** aItems) +{ + ErrorResult rv; + RefPtr<DOMStringList> items = GetMozItems(rv); + items.forget(aItems); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, bool* aExists) +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache(); + if (!appCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsAutoCString key; + rv = GetCacheKey(aURI, key); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t types; + rv = appCache->GetTypes(key, &types); + if (rv == NS_ERROR_CACHE_KEY_NOT_FOUND) { + *aExists = false; + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + *aExists = ((types & nsIApplicationCache::ITEM_DYNAMIC) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::GetMozLength(uint32_t *aLength) +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + if (!mManifestURI) { + *aLength = 0; + return NS_OK; + } + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CacheKeys(); + NS_ENSURE_SUCCESS(rv, rv); + + *aLength = mCachedKeysCount; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI) +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + SetDOMStringToNull(aURI); + + rv = CacheKeys(); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIndex >= mCachedKeysCount) + return NS_ERROR_NOT_AVAILABLE; + + CopyUTF8toUTF16(mCachedKeys[aIndex], aURI); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::MozAdd(const nsAString& aURI) +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache(); + if (!appCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (aURI.Length() > MAX_URI_LENGTH) return NS_ERROR_DOM_BAD_URI; + + // this will fail if the URI is not absolute + nsCOMPtr<nsIURI> requestedURI; + rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString scheme; + rv = requestedURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + + bool match; + rv = mManifestURI->SchemeIs(scheme.get(), &match); + NS_ENSURE_SUCCESS(rv, rv); + + if (!match) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + uint32_t length; + rv = GetMozLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t maxEntries = + Preferences::GetUint(kMaxEntriesPref, DEFAULT_MAX_ENTRIES); + + if (length > maxEntries) return NS_ERROR_NOT_AVAILABLE; + + ClearCachedKeys(); + + nsCOMPtr<nsIOfflineCacheUpdate> update = + do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString clientID; + rv = appCache->GetClientID(clientID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = update->InitPartial(mManifestURI, clientID, + mDocumentURI, mLoadingPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + rv = update->AddDynamicURI(requestedURI); + NS_ENSURE_SUCCESS(rv, rv); + + rv = update->Schedule(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::MozRemove(const nsAString& aURI) +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache(); + if (!appCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + nsAutoCString key; + rv = GetCacheKey(aURI, key); + NS_ENSURE_SUCCESS(rv, rv); + + ClearCachedKeys(); + + // XXX: This is a race condition. remove() is specced to remove + // from the currently associated application cache, but if this + // happens during an update (or after an update, if we haven't + // swapped yet), that remove() will be lost when the next update is + // finished. Need to bring this issue up. + + rv = appCache->UnmarkEntry(key, nsIApplicationCache::ITEM_DYNAMIC); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::GetStatus(uint16_t *aStatus) +{ + nsresult rv = Init(); + + // Init may fail with INVALID_STATE_ERR if there is no manifest URI. + // The status attribute should not throw that exception, convert it + // to an UNCACHED. + if (rv == NS_ERROR_DOM_INVALID_STATE_ERR || + !nsContentUtils::OfflineAppAllowed(mDocumentURI)) { + *aStatus = nsIDOMOfflineResourceList::UNCACHED; + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + + // If this object is not associated with a cache, return UNCACHED + nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache(); + if (!appCache) { + *aStatus = nsIDOMOfflineResourceList::UNCACHED; + return NS_OK; + } + + + // If there is an update in process, use its status. + if (mCacheUpdate && mExposeCacheUpdateStatus) { + rv = mCacheUpdate->GetStatus(aStatus); + if (NS_SUCCEEDED(rv) && *aStatus != nsIDOMOfflineResourceList::IDLE) { + return NS_OK; + } + } + + if (mAvailableApplicationCache) { + *aStatus = nsIDOMOfflineResourceList::UPDATEREADY; + return NS_OK; + } + + *aStatus = mStatus; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::Update() +{ + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIOfflineCacheUpdateService> updateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsPIDOMWindowInner> window = GetOwner(); + + nsCOMPtr<nsIOfflineCacheUpdate> update; + rv = updateService->ScheduleUpdate(mManifestURI, mDocumentURI, mLoadingPrincipal, + window, getter_AddRefs(update)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::SwapCache() +{ + nsresult rv = Init(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIApplicationCache> currentAppCache = GetDocumentAppCache(); + if (!currentAppCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + // Check the current and potentially newly available cache are not identical. + if (mAvailableApplicationCache == currentAppCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (mAvailableApplicationCache) { + nsCString currClientId, availClientId; + currentAppCache->GetClientID(currClientId); + mAvailableApplicationCache->GetClientID(availClientId); + if (availClientId == currClientId) + return NS_ERROR_DOM_INVALID_STATE_ERR; + } else if (mStatus != OBSOLETE) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + ClearCachedKeys(); + + nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer = + GetDocumentAppCacheContainer(); + + // In the case of an obsolete cache group, newAppCache might be null. + // We will disassociate from the cache in that case. + if (appCacheContainer) { + rv = appCacheContainer->SetApplicationCache(mAvailableApplicationCache); + NS_ENSURE_SUCCESS(rv, rv); + } + + mAvailableApplicationCache = nullptr; + mStatus = nsIDOMOfflineResourceList::IDLE; + + return NS_OK; +} + +// +// nsDOMOfflineResourceList::nsIDOMEventTarget +// + +void +nsDOMOfflineResourceList::FirePendingEvents() +{ + for (int32_t i = 0; i < mPendingEvents.Count(); ++i) { + bool dummy; + nsCOMPtr<nsIDOMEvent> event = mPendingEvents[i]; + DispatchEvent(event, &dummy); + } + mPendingEvents.Clear(); +} + +nsresult +nsDOMOfflineResourceList::SendEvent(const nsAString &aEventName) +{ + // Don't send events to closed windows + if (!GetOwner()) { + return NS_OK; + } + + if (!GetOwner()->GetDocShell()) { + return NS_OK; + } + + RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); + event->InitEvent(aEventName, false, true); + + // We assume anyone that managed to call SendEvent is trusted + event->SetTrusted(true); + + // If the window is frozen or we're still catching up on events that were + // queued while frozen, save the event for later. + if (GetOwner()->IsFrozen() || mPendingEvents.Count() > 0) { + mPendingEvents.AppendObject(event); + return NS_OK; + } + + bool dummy; + DispatchEvent(event, &dummy); + + return NS_OK; +} + + +// +// nsDOMOfflineResourceList::nsIObserver +// +NS_IMETHODIMP +nsDOMOfflineResourceList::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + if (!strcmp(aTopic, "offline-cache-update-added")) { + nsCOMPtr<nsIOfflineCacheUpdate> update = do_QueryInterface(aSubject); + if (update) { + UpdateAdded(update); + } + } else if (!strcmp(aTopic, "offline-cache-update-completed")) { + nsCOMPtr<nsIOfflineCacheUpdate> update = do_QueryInterface(aSubject); + if (update) { + UpdateCompleted(update); + } + } + + return NS_OK; +} + +// +// nsDOMOfflineResourceList::nsIOfflineCacheUpdateObserver +// +NS_IMETHODIMP +nsDOMOfflineResourceList::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate, + uint32_t event) +{ + mExposeCacheUpdateStatus = + (event == STATE_CHECKING) || + (event == STATE_DOWNLOADING) || + (event == STATE_ITEMSTARTED) || + (event == STATE_ITEMCOMPLETED) || + // During notification of "obsolete" we must expose state of the update + (event == STATE_OBSOLETE); + + switch (event) { + case STATE_ERROR: + SendEvent(NS_LITERAL_STRING(ERROR_STR)); + break; + case STATE_CHECKING: + SendEvent(NS_LITERAL_STRING(CHECKING_STR)); + break; + case STATE_NOUPDATE: + SendEvent(NS_LITERAL_STRING(NOUPDATE_STR)); + break; + case STATE_OBSOLETE: + mStatus = nsIDOMOfflineResourceList::OBSOLETE; + mAvailableApplicationCache = nullptr; + SendEvent(NS_LITERAL_STRING(OBSOLETE_STR)); + break; + case STATE_DOWNLOADING: + SendEvent(NS_LITERAL_STRING(DOWNLOADING_STR)); + break; + case STATE_ITEMSTARTED: + SendEvent(NS_LITERAL_STRING(PROGRESS_STR)); + break; + case STATE_ITEMCOMPLETED: + // Nothing to do here... + break; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDOMOfflineResourceList::ApplicationCacheAvailable(nsIApplicationCache *aApplicationCache) +{ + nsCOMPtr<nsIApplicationCache> currentAppCache = GetDocumentAppCache(); + if (currentAppCache) { + // Document already has a cache, we cannot override it. swapCache is + // here to do it on demand. + + // If the newly available cache is identical to the current cache, then + // just ignore this event. + if (aApplicationCache == currentAppCache) { + return NS_OK; + } + + nsCString currClientId, availClientId; + currentAppCache->GetClientID(currClientId); + aApplicationCache->GetClientID(availClientId); + if (availClientId == currClientId) { + return NS_OK; + } + + mAvailableApplicationCache = aApplicationCache; + return NS_OK; + } + + nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer = + GetDocumentAppCacheContainer(); + + if (appCacheContainer) { + appCacheContainer->SetApplicationCache(aApplicationCache); + } + + mAvailableApplicationCache = nullptr; + return NS_OK; +} + +nsresult +nsDOMOfflineResourceList::GetCacheKey(const nsAString &aURI, nsCString &aKey) +{ + nsCOMPtr<nsIURI> requestedURI; + nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); + NS_ENSURE_SUCCESS(rv, rv); + + return GetCacheKey(requestedURI, aKey); +} + +nsresult +nsDOMOfflineResourceList::UpdateAdded(nsIOfflineCacheUpdate *aUpdate) +{ + // Ignore partial updates. + bool partial; + nsresult rv = aUpdate->GetPartial(&partial); + NS_ENSURE_SUCCESS(rv, rv); + + if (partial) { + return NS_OK; + } + + nsCOMPtr<nsIURI> updateURI; + rv = aUpdate->GetManifestURI(getter_AddRefs(updateURI)); + NS_ENSURE_SUCCESS(rv, rv); + + bool equals; + rv = updateURI->Equals(mManifestURI, &equals); + NS_ENSURE_SUCCESS(rv, rv); + + if (!equals) { + // This update doesn't belong to us + return NS_OK; + } + + NS_ENSURE_TRUE(!mCacheUpdate, NS_ERROR_FAILURE); + + // We don't need to emit signals here. Updates are either added + // when they are scheduled (in which case they are always IDLE) or + // they are added when the applicationCache object is initialized, so there + // are no listeners to accept signals anyway. + + mCacheUpdate = aUpdate; + mCacheUpdate->AddObserver(this, true); + + return NS_OK; +} + +already_AddRefed<nsIApplicationCacheContainer> +nsDOMOfflineResourceList::GetDocumentAppCacheContainer() +{ + nsCOMPtr<nsIWebNavigation> webnav = do_GetInterface(GetOwner()); + if (!webnav) { + return nullptr; + } + + nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer = + do_GetInterface(webnav); + return appCacheContainer.forget(); +} + +already_AddRefed<nsIApplicationCache> +nsDOMOfflineResourceList::GetDocumentAppCache() +{ + nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer = + GetDocumentAppCacheContainer(); + + if (appCacheContainer) { + nsCOMPtr<nsIApplicationCache> applicationCache; + appCacheContainer->GetApplicationCache( + getter_AddRefs(applicationCache)); + return applicationCache.forget(); + } + + return nullptr; +} + +nsresult +nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate) +{ + if (aUpdate != mCacheUpdate) { + // This isn't the update we're watching. + return NS_OK; + } + + bool partial; + mCacheUpdate->GetPartial(&partial); + bool isUpgrade; + mCacheUpdate->GetIsUpgrade(&isUpgrade); + + bool succeeded; + nsresult rv = mCacheUpdate->GetSucceeded(&succeeded); + + mCacheUpdate->RemoveObserver(this); + mCacheUpdate = nullptr; + + if (NS_SUCCEEDED(rv) && succeeded && !partial) { + mStatus = nsIDOMOfflineResourceList::IDLE; + if (isUpgrade) { + SendEvent(NS_LITERAL_STRING(UPDATEREADY_STR)); + } else { + SendEvent(NS_LITERAL_STRING(CACHED_STR)); + } + } + + return NS_OK; +} + +nsresult +nsDOMOfflineResourceList::GetCacheKey(nsIURI *aURI, nsCString &aKey) +{ + nsresult rv = aURI->GetAsciiSpec(aKey); + NS_ENSURE_SUCCESS(rv, rv); + + // url fragments aren't used in cache keys + nsAutoCString::const_iterator specStart, specEnd; + aKey.BeginReading(specStart); + aKey.EndReading(specEnd); + if (FindCharInReadable('#', specStart, specEnd)) { + aKey.BeginReading(specEnd); + aKey = Substring(specEnd, specStart); + } + + return NS_OK; +} + +nsresult +nsDOMOfflineResourceList::CacheKeys() +{ + if (IS_CHILD_PROCESS()) + return NS_ERROR_NOT_IMPLEMENTED; + + if (mCachedKeys) + return NS_OK; + + nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(GetOwner()); + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav); + + nsAutoCString originSuffix; + if (loadContext) { + mozilla::DocShellOriginAttributes oa; + bool ok = loadContext->GetOriginAttributes(oa); + NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED); + + oa.CreateSuffix(originSuffix); + } + + nsAutoCString groupID; + mApplicationCacheService->BuildGroupIDForSuffix( + mManifestURI, originSuffix, groupID); + + nsCOMPtr<nsIApplicationCache> appCache; + mApplicationCacheService->GetActiveCache(groupID, + getter_AddRefs(appCache)); + + if (!appCache) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + return appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, + &mCachedKeysCount, &mCachedKeys); +} + +void +nsDOMOfflineResourceList::ClearCachedKeys() +{ + if (mCachedKeys) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCachedKeysCount, mCachedKeys); + mCachedKeys = nullptr; + mCachedKeysCount = 0; + } +} diff --git a/dom/offline/nsDOMOfflineResourceList.h b/dom/offline/nsDOMOfflineResourceList.h new file mode 100644 index 0000000000..94c4ba7751 --- /dev/null +++ b/dom/offline/nsDOMOfflineResourceList.h @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDOMOfflineResourceList_h___ +#define nsDOMOfflineResourceList_h___ + +#include "nscore.h" +#include "nsIDOMOfflineResourceList.h" +#include "nsIApplicationCache.h" +#include "nsIApplicationCacheContainer.h" +#include "nsIApplicationCacheService.h" +#include "nsIOfflineCacheUpdate.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" +#include "nsIDOMEventListener.h" +#include "nsIObserver.h" +#include "nsIScriptContext.h" +#include "nsCycleCollectionParticipant.h" +#include "nsPIDOMWindow.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/ErrorResult.h" + +namespace mozilla { +namespace dom { +class DOMStringList; +} // namespace dom +} // namespace mozilla + +class nsDOMOfflineResourceList final : public mozilla::DOMEventTargetHelper, + public nsIDOMOfflineResourceList, + public nsIObserver, + public nsIOfflineCacheUpdateObserver, + public nsSupportsWeakReference +{ + typedef mozilla::ErrorResult ErrorResult; + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDOMOFFLINERESOURCELIST + NS_DECL_NSIOBSERVER + NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMOfflineResourceList, + mozilla::DOMEventTargetHelper) + + nsDOMOfflineResourceList(nsIURI* aManifestURI, + nsIURI* aDocumentURI, + nsIPrincipal* aLoadingPrincipal, + nsPIDOMWindowInner* aWindow); + + void FirePendingEvents(); + void Disconnect(); + + nsresult Init(); + + nsPIDOMWindowInner* GetParentObject() const + { + return GetOwner(); + } + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + + uint16_t GetStatus(ErrorResult& aRv) + { + uint16_t status = 0; + aRv = GetStatus(&status); + return status; + } + void Update(ErrorResult& aRv) + { + aRv = Update(); + } + void SwapCache(ErrorResult& aRv) + { + aRv = SwapCache(); + } + + IMPL_EVENT_HANDLER(checking) + IMPL_EVENT_HANDLER(error) + IMPL_EVENT_HANDLER(noupdate) + IMPL_EVENT_HANDLER(downloading) + IMPL_EVENT_HANDLER(progress) + IMPL_EVENT_HANDLER(cached) + IMPL_EVENT_HANDLER(updateready) + IMPL_EVENT_HANDLER(obsolete) + + already_AddRefed<mozilla::dom::DOMStringList> GetMozItems(ErrorResult& aRv); + bool MozHasItem(const nsAString& aURI, ErrorResult& aRv) + { + bool hasItem = false; + aRv = MozHasItem(aURI, &hasItem); + return hasItem; + } + uint32_t GetMozLength(ErrorResult& aRv) + { + uint32_t length = 0; + aRv = GetMozLength(&length); + return length; + } + void MozItem(uint32_t aIndex, nsAString& aURI, ErrorResult& aRv) + { + aRv = MozItem(aIndex, aURI); + } + void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aURI, + ErrorResult& aRv) + { + MozItem(aIndex, aURI, aRv); + aFound = !aURI.IsVoid(); + } + uint32_t Length() + { + mozilla::IgnoredErrorResult rv; + uint32_t length = GetMozLength(rv); + return rv.Failed() ? 0 : length; + } + void MozAdd(const nsAString& aURI, ErrorResult& aRv) + { + aRv = MozAdd(aURI); + } + void MozRemove(const nsAString& aURI, ErrorResult& aRv) + { + aRv = MozRemove(aURI); + } + +protected: + virtual ~nsDOMOfflineResourceList(); + +private: + nsresult SendEvent(const nsAString &aEventName); + + nsresult UpdateAdded(nsIOfflineCacheUpdate *aUpdate); + nsresult UpdateCompleted(nsIOfflineCacheUpdate *aUpdate); + + already_AddRefed<nsIApplicationCacheContainer> GetDocumentAppCacheContainer(); + already_AddRefed<nsIApplicationCache> GetDocumentAppCache(); + + nsresult GetCacheKey(const nsAString &aURI, nsCString &aKey); + nsresult GetCacheKey(nsIURI *aURI, nsCString &aKey); + + nsresult CacheKeys(); + void ClearCachedKeys(); + + bool mInitialized; + + nsCOMPtr<nsIURI> mManifestURI; + // AsciiSpec of mManifestURI + nsCString mManifestSpec; + + nsCOMPtr<nsIURI> mDocumentURI; + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + nsCOMPtr<nsIApplicationCacheService> mApplicationCacheService; + nsCOMPtr<nsIApplicationCache> mAvailableApplicationCache; + nsCOMPtr<nsIOfflineCacheUpdate> mCacheUpdate; + bool mExposeCacheUpdateStatus; + uint16_t mStatus; + + // The set of dynamic keys for this application cache object. + char **mCachedKeys; + uint32_t mCachedKeysCount; + + nsCOMArray<nsIDOMEvent> mPendingEvents; +}; + +#endif |