/* -*- 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 "nsError.h" #include "mozilla/Components.h" #include "mozilla/dom/DOMStringList.h" #include "nsMemory.h" #include "nsNetUtil.h" #include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "nsICookieJarSettings.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIOfflineCacheUpdate.h" #include "nsContentUtils.h" #include "nsILoadContext.h" #include "nsIObserverService.h" #include "nsIScriptGlobalObject.h" #include "nsIWebNavigation.h" #include "nsOfflineCacheUpdate.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/OfflineResourceListBinding.h" #include "mozilla/EventDispatcher.h" #include "mozilla/Preferences.h" #include "mozilla/BasePrincipal.h" #include "mozilla/StaticPrefs_browser.h" #include "nsXULAppAPI.h" #define IS_CHILD_PROCESS() (GeckoProcessType_Default != XRE_GetProcessType()) using namespace mozilla; using namespace mozilla::dom; // Event names #define CHECKING_STR u"checking" #define ERROR_STR u"error" #define NOUPDATE_STR u"noupdate" #define DOWNLOADING_STR u"downloading" #define PROGRESS_STR u"progress" #define CACHED_STR u"cached" #define UPDATEREADY_STR u"updateready" #define OBSOLETE_STR u"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_WEAK_INHERITED(nsDOMOfflineResourceList, DOMEventTargetHelper, mCacheUpdate, mPendingEvents) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMOfflineResourceList) 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) nsDOMOfflineResourceList::nsDOMOfflineResourceList( nsIURI* aManifestURI, nsIURI* aDocumentURI, nsIPrincipal* aLoadingPrincipal, nsPIDOMWindowInner* aWindow) : DOMEventTargetHelper(aWindow), mInitialized(false), mManifestURI(aManifestURI), mDocumentURI(aDocumentURI), mLoadingPrincipal(aLoadingPrincipal), mExposeCacheUpdateStatus(true), mStatus(OfflineResourceList_Binding::IDLE) {} nsDOMOfflineResourceList::~nsDOMOfflineResourceList() { ClearCachedKeys(); } JSObject* nsDOMOfflineResourceList::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return OfflineResourceList_Binding::Wrap(aCx, this, aGivenProto); } nsresult nsDOMOfflineResourceList::Init() { if (mInitialized) { return NS_OK; } if (!mManifestURI) { return NS_ERROR_DOM_INVALID_STATE_ERR; } mManifestURI->GetAsciiSpec(mManifestSpec); bool isPrivateWin = mLoadingPrincipal ? mLoadingPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0 : false; nsresult rv = nsContentUtils::GetSecurityManager()->CheckSameOriginURI( mManifestURI, mDocumentURI, true, isPrivateWin); NS_ENSURE_SUCCESS(rv, rv); // Dynamically-managed resources are stored as a separate ownership list // from the manifest. nsCOMPtr 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 cacheUpdateService = components::OfflineCacheUpdate::Service(); NS_ENSURE_TRUE(cacheUpdateService, NS_ERROR_UNEXPECTED); uint32_t numUpdates; rv = cacheUpdateService->GetNumUpdates(&numUpdates); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t i = 0; i < numUpdates; i++) { nsCOMPtr cacheUpdate; rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate)); NS_ENSURE_SUCCESS(rv, rv); UpdateAdded(cacheUpdate); } } // watch for new offline cache updates nsCOMPtr 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; } } already_AddRefed nsDOMOfflineResourceList::GetMozItems( ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return nullptr; } RefPtr items = new DOMStringList(); // If we are not associated with an application cache, return an // empty list. nsCOMPtr appCache = GetDocumentAppCache(); if (!appCache) { return items.forget(); } aRv = Init(); if (aRv.Failed()) { return nullptr; } nsTArray keys; aRv = appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, keys); if (aRv.Failed()) { return nullptr; } for (auto& key : keys) { items->Add(NS_ConvertUTF8toUTF16(key)); } return items.forget(); } bool nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return false; } nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return false; } nsCOMPtr appCache = GetDocumentAppCache(); if (!appCache) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return false; } nsAutoCString key; rv = GetCacheKey(aURI, key); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return false; } uint32_t types; rv = appCache->GetTypes(key, &types); if (rv == NS_ERROR_CACHE_KEY_NOT_FOUND) { return false; } if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return false; } return types & nsIApplicationCache::ITEM_DYNAMIC; } uint32_t nsDOMOfflineResourceList::GetMozLength(ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return 0; } if (!mManifestURI) { return 0; } nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return 0; } rv = CacheKeys(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return 0; } return mCachedKeys->Length(); } void nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI, ErrorResult& aRv) { bool found; IndexedGetter(aIndex, found, aURI, aRv); if (!aRv.Failed() && !found) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); } } void nsDOMOfflineResourceList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aURI, ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return; } nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = CacheKeys(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (aIndex >= mCachedKeys->Length()) { aFound = false; return; } aFound = true; CopyUTF8toUTF16(mCachedKeys->ElementAt(aIndex), aURI); } void nsDOMOfflineResourceList::MozAdd(const nsAString& aURI, ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return; } nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr appCache = GetDocumentAppCache(); if (!appCache) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (aURI.Length() > MAX_URI_LENGTH) { aRv.Throw(NS_ERROR_DOM_BAD_URI); return; } // this will fail if the URI is not absolute nsCOMPtr requestedURI; rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } nsAutoCString scheme; rv = requestedURI->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } bool match; rv = mManifestURI->SchemeIs(scheme.get(), &match); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (!match) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } uint32_t length = GetMozLength(aRv); if (NS_WARN_IF(aRv.Failed())) { return; } uint32_t maxEntries = Preferences::GetUint(kMaxEntriesPref, DEFAULT_MAX_ENTRIES); if (length > maxEntries) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); return; } ClearCachedKeys(); nsCOMPtr update = new nsOfflineCacheUpdate(); nsAutoCString clientID; rv = appCache->GetClientID(clientID); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } RefPtr doc = GetOwner()->GetExtantDoc(); nsCOMPtr cjs = doc ? doc->CookieJarSettings() : nullptr; rv = update->InitPartial(mManifestURI, clientID, mDocumentURI, mLoadingPrincipal, cjs); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = update->AddDynamicURI(requestedURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } rv = update->Schedule(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } } void nsDOMOfflineResourceList::MozRemove(const nsAString& aURI, ErrorResult& aRv) { if (IS_CHILD_PROCESS()) { aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); return; } nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr appCache = GetDocumentAppCache(); if (!appCache) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsAutoCString key; rv = GetCacheKey(aURI, key); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } 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); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } } uint16_t nsDOMOfflineResourceList::GetStatus(ErrorResult& aRv) { 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)) { return OfflineResourceList_Binding::UNCACHED; } if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return 0; } // If this object is not associated with a cache, return UNCACHED nsCOMPtr appCache = GetDocumentAppCache(); if (!appCache) { return OfflineResourceList_Binding::UNCACHED; } // If there is an update in process, use its status. if (mCacheUpdate && mExposeCacheUpdateStatus) { uint16_t status; rv = mCacheUpdate->GetStatus(&status); if (NS_SUCCEEDED(rv) && status != OfflineResourceList_Binding::IDLE) { return status; } } if (mAvailableApplicationCache) { return OfflineResourceList_Binding::UPDATEREADY; } return mStatus; } void nsDOMOfflineResourceList::Update(ErrorResult& aRv) { nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } // If the storage is disabled this operation should be ignored. if (!StaticPrefs::browser_cache_offline_storage_enable()) { return; } if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr updateService = components::OfflineCacheUpdate::Service(); if (NS_WARN_IF(!updateService)) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } nsCOMPtr window = GetOwner(); nsCOMPtr update; rv = updateService->ScheduleUpdate(mManifestURI, mDocumentURI, mLoadingPrincipal, window, getter_AddRefs(update)); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } } void nsDOMOfflineResourceList::SwapCache(ErrorResult& aRv) { nsresult rv = Init(); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; } nsCOMPtr currentAppCache = GetDocumentAppCache(); if (!currentAppCache) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } // Check the current and potentially newly available cache are not identical. if (mAvailableApplicationCache == currentAppCache) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } if (mAvailableApplicationCache) { nsCString currClientId, availClientId; currentAppCache->GetClientID(currClientId); mAvailableApplicationCache->GetClientID(availClientId); if (availClientId == currClientId) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } } else if (mStatus != OfflineResourceList_Binding::OBSOLETE) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } ClearCachedKeys(); nsCOMPtr 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); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return; } } mAvailableApplicationCache = nullptr; mStatus = OfflineResourceList_Binding::IDLE; } void nsDOMOfflineResourceList::FirePendingEvents() { for (int32_t i = 0; i < mPendingEvents.Count(); ++i) { RefPtr event = mPendingEvents[i]; DispatchEvent(*event); } mPendingEvents.Clear(); } void nsDOMOfflineResourceList::SendEvent(const nsAString& aEventName) { // Don't send events to closed windows if (!GetOwner()) { return; } if (!GetOwner()->GetDocShell()) { return; } RefPtr 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; } DispatchEvent(*event); } // // nsDOMOfflineResourceList::nsIObserver // NS_IMETHODIMP nsDOMOfflineResourceList::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, "offline-cache-update-added")) { nsCOMPtr update = do_QueryInterface(aSubject); if (update) { UpdateAdded(update); } } else if (!strcmp(aTopic, "offline-cache-update-completed")) { nsCOMPtr 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(nsLiteralString(ERROR_STR)); break; case STATE_CHECKING: SendEvent(nsLiteralString(CHECKING_STR)); break; case STATE_NOUPDATE: SendEvent(nsLiteralString(NOUPDATE_STR)); break; case STATE_OBSOLETE: mStatus = OfflineResourceList_Binding::OBSOLETE; mAvailableApplicationCache = nullptr; SendEvent(nsLiteralString(OBSOLETE_STR)); break; case STATE_DOWNLOADING: SendEvent(nsLiteralString(DOWNLOADING_STR)); break; case STATE_ITEMSTARTED: SendEvent(nsLiteralString(PROGRESS_STR)); break; case STATE_ITEMCOMPLETED: // Nothing to do here... break; } return NS_OK; } NS_IMETHODIMP nsDOMOfflineResourceList::ApplicationCacheAvailable( nsIApplicationCache* aApplicationCache) { nsCOMPtr 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 appCacheContainer = GetDocumentAppCacheContainer(); if (appCacheContainer) { appCacheContainer->SetApplicationCache(aApplicationCache); } mAvailableApplicationCache = nullptr; return NS_OK; } nsresult nsDOMOfflineResourceList::GetCacheKey(const nsAString& aURI, nsCString& aKey) { nsCOMPtr requestedURI; nsresult rv = NS_NewURI(getter_AddRefs(requestedURI), aURI); NS_ENSURE_SUCCESS(rv, rv); return GetCacheKey(requestedURI, aKey); } void nsDOMOfflineResourceList::UpdateAdded(nsIOfflineCacheUpdate* aUpdate) { // Ignore partial updates. bool partial; nsresult rv = aUpdate->GetPartial(&partial); NS_ENSURE_SUCCESS_VOID(rv); if (partial) { return; } nsCOMPtr updateURI; rv = aUpdate->GetManifestURI(getter_AddRefs(updateURI)); NS_ENSURE_SUCCESS_VOID(rv); bool equals; rv = updateURI->Equals(mManifestURI, &equals); NS_ENSURE_SUCCESS_VOID(rv); if (!equals) { // This update doesn't belong to us return; } if (mCacheUpdate) { return; } // 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); } already_AddRefed nsDOMOfflineResourceList::GetDocumentAppCacheContainer() { nsCOMPtr webnav = do_GetInterface(GetOwner()); if (!webnav) { return nullptr; } nsCOMPtr appCacheContainer = do_GetInterface(webnav); return appCacheContainer.forget(); } already_AddRefed nsDOMOfflineResourceList::GetDocumentAppCache() { nsCOMPtr appCacheContainer = GetDocumentAppCacheContainer(); if (appCacheContainer) { nsCOMPtr applicationCache; appCacheContainer->GetApplicationCache(getter_AddRefs(applicationCache)); return applicationCache.forget(); } return nullptr; } void nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate* aUpdate) { if (aUpdate != mCacheUpdate) { // This isn't the update we're watching. return; } 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 = OfflineResourceList_Binding::IDLE; if (isUpgrade) { SendEvent(nsLiteralString(UPDATEREADY_STR)); } else { SendEvent(nsLiteralString(CACHED_STR)); } } } 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 window = do_QueryInterface(GetOwner()); nsCOMPtr webNav = do_GetInterface(window); nsCOMPtr loadContext = do_QueryInterface(webNav); nsAutoCString originSuffix; if (loadContext) { mozilla::OriginAttributes oa; loadContext->GetOriginAttributes(oa); oa.CreateSuffix(originSuffix); } nsAutoCString groupID; mApplicationCacheService->BuildGroupIDForSuffix(mManifestURI, originSuffix, groupID); nsCOMPtr appCache; mApplicationCacheService->GetActiveCache(groupID, getter_AddRefs(appCache)); if (!appCache) { return NS_ERROR_DOM_INVALID_STATE_ERR; } mCachedKeys.emplace(); nsresult rv = appCache->GatherEntries(nsIApplicationCache::ITEM_DYNAMIC, *mCachedKeys); if (NS_FAILED(rv)) { mCachedKeys.reset(); } return rv; } void nsDOMOfflineResourceList::ClearCachedKeys() { mCachedKeys.reset(); }