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 "CacheObserver.h" michael@0: michael@0: #include "CacheStorageService.h" michael@0: #include "CacheFileIOManager.h" michael@0: #include "LoadContextInfo.h" michael@0: #include "nsICacheStorage.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozIApplicationClearPrivateDataParams.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "prsystem.h" michael@0: #include michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: CacheObserver* CacheObserver::sSelf = nullptr; michael@0: michael@0: static uint32_t const kDefaultUseNewCache = 0; // Don't use the new cache by default michael@0: uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache; michael@0: michael@0: static bool sUseNewCacheTemp = false; // Temp trigger to not lose early adopters michael@0: michael@0: static int32_t const kAutoDeleteCacheVersion = -1; // Auto-delete off by default michael@0: static int32_t sAutoDeleteCacheVersion = kAutoDeleteCacheVersion; michael@0: michael@0: static int32_t const kDefaultHalfLifeExperiment = -1; // Disabled michael@0: int32_t CacheObserver::sHalfLifeExperiment = kDefaultHalfLifeExperiment; michael@0: michael@0: static uint32_t const kDefaultHalfLifeHours = 6; // 6 hours michael@0: uint32_t CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours; michael@0: michael@0: static bool const kDefaultUseDiskCache = true; michael@0: bool CacheObserver::sUseDiskCache = kDefaultUseDiskCache; michael@0: michael@0: static bool const kDefaultUseMemoryCache = true; michael@0: bool CacheObserver::sUseMemoryCache = kDefaultUseMemoryCache; michael@0: michael@0: static uint32_t const kDefaultMetadataMemoryLimit = 250; // 0.25 MB michael@0: uint32_t CacheObserver::sMetadataMemoryLimit = kDefaultMetadataMemoryLimit; michael@0: michael@0: static int32_t const kDefaultMemoryCacheCapacity = -1; // autodetect michael@0: int32_t CacheObserver::sMemoryCacheCapacity = kDefaultMemoryCacheCapacity; michael@0: // Cache of the calculated memory capacity based on the system memory size michael@0: int32_t CacheObserver::sAutoMemoryCacheCapacity = -1; michael@0: michael@0: static uint32_t const kDefaultDiskCacheCapacity = 250 * 1024; // 250 MB michael@0: uint32_t CacheObserver::sDiskCacheCapacity = kDefaultDiskCacheCapacity; michael@0: michael@0: static bool const kDefaultSmartCacheSizeEnabled = false; michael@0: bool CacheObserver::sSmartCacheSizeEnabled = kDefaultSmartCacheSizeEnabled; michael@0: michael@0: static uint32_t const kDefaultMaxMemoryEntrySize = 4 * 1024; // 4 MB michael@0: uint32_t CacheObserver::sMaxMemoryEntrySize = kDefaultMaxMemoryEntrySize; michael@0: michael@0: static uint32_t const kDefaultMaxDiskEntrySize = 50 * 1024; // 50 MB michael@0: uint32_t CacheObserver::sMaxDiskEntrySize = kDefaultMaxDiskEntrySize; michael@0: michael@0: static uint32_t const kDefaultCompressionLevel = 1; michael@0: uint32_t CacheObserver::sCompressionLevel = kDefaultCompressionLevel; michael@0: michael@0: static bool kDefaultSanitizeOnShutdown = false; michael@0: bool CacheObserver::sSanitizeOnShutdown = kDefaultSanitizeOnShutdown; michael@0: michael@0: static bool kDefaultClearCacheOnShutdown = false; michael@0: bool CacheObserver::sClearCacheOnShutdown = kDefaultClearCacheOnShutdown; michael@0: michael@0: NS_IMPL_ISUPPORTS(CacheObserver, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: // static michael@0: nsresult michael@0: CacheObserver::Init() michael@0: { michael@0: if (sSelf) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (!obs) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: sSelf = new CacheObserver(); michael@0: NS_ADDREF(sSelf); michael@0: michael@0: obs->AddObserver(sSelf, "prefservice:after-app-defaults", true); michael@0: obs->AddObserver(sSelf, "profile-do-change", true); michael@0: obs->AddObserver(sSelf, "sessionstore-windows-restored", true); michael@0: obs->AddObserver(sSelf, "profile-before-change", true); michael@0: obs->AddObserver(sSelf, "xpcom-shutdown", true); michael@0: obs->AddObserver(sSelf, "last-pb-context-exited", true); michael@0: obs->AddObserver(sSelf, "webapps-clear-data", true); michael@0: obs->AddObserver(sSelf, "memory-pressure", true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: CacheObserver::Shutdown() michael@0: { michael@0: if (!sSelf) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: NS_RELEASE(sSelf); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CacheObserver::AttachToPreferences() michael@0: { michael@0: sAutoDeleteCacheVersion = mozilla::Preferences::GetInt( michael@0: "browser.cache.auto_delete_cache_version", kAutoDeleteCacheVersion); michael@0: michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache); michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sUseNewCacheTemp, "browser.cache.use_new_backend_temp", false); michael@0: michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sUseDiskCache, "browser.cache.disk.enable", kDefaultUseDiskCache); michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sUseMemoryCache, "browser.cache.memory.enable", kDefaultUseMemoryCache); michael@0: michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sMetadataMemoryLimit, "browser.cache.disk.metadata_memory_limit", kDefaultMetadataMemoryLimit); michael@0: michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sDiskCacheCapacity, "browser.cache.disk.capacity", kDefaultDiskCacheCapacity); michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sSmartCacheSizeEnabled, "browser.cache.disk.smart_size.enabled", kDefaultSmartCacheSizeEnabled); michael@0: mozilla::Preferences::AddIntVarCache( michael@0: &sMemoryCacheCapacity, "browser.cache.memory.capacity", kDefaultMemoryCacheCapacity); michael@0: michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sMaxDiskEntrySize, "browser.cache.disk.max_entry_size", kDefaultMaxDiskEntrySize); michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sMaxMemoryEntrySize, "browser.cache.memory.max_entry_size", kDefaultMaxMemoryEntrySize); michael@0: michael@0: // http://mxr.mozilla.org/mozilla-central/source/netwerk/cache/nsCacheEntryDescriptor.cpp#367 michael@0: mozilla::Preferences::AddUintVarCache( michael@0: &sCompressionLevel, "browser.cache.compression_level", kDefaultCompressionLevel); michael@0: michael@0: mozilla::Preferences::GetComplex( michael@0: "browser.cache.disk.parent_directory", NS_GET_IID(nsIFile), michael@0: getter_AddRefs(mCacheParentDirectoryOverride)); michael@0: michael@0: // First check the default value. If it is at -1, the experient michael@0: // is turned off. If it is at 0, then use the user pref value michael@0: // instead. michael@0: sHalfLifeExperiment = mozilla::Preferences::GetDefaultInt( michael@0: "browser.cache.frecency_experiment", kDefaultHalfLifeExperiment); michael@0: michael@0: if (sHalfLifeExperiment == 0) { michael@0: // Default preferences indicate we want to run the experiment, michael@0: // hence read the user value. michael@0: sHalfLifeExperiment = mozilla::Preferences::GetInt( michael@0: "browser.cache.frecency_experiment", sHalfLifeExperiment); michael@0: } michael@0: michael@0: if (sHalfLifeExperiment == 0) { michael@0: // The experiment has not yet been initialized but is engaged, do michael@0: // the initialization now. michael@0: srand(time(NULL)); michael@0: sHalfLifeExperiment = (rand() % 4) + 1; michael@0: // Store the experiemnt value, since we need it not to change between michael@0: // browser sessions. michael@0: mozilla::Preferences::SetInt( michael@0: "browser.cache.frecency_experiment", sHalfLifeExperiment); michael@0: } michael@0: michael@0: switch (sHalfLifeExperiment) { michael@0: case 1: // The experiment is engaged michael@0: sHalfLifeHours = 6; michael@0: break; michael@0: case 2: michael@0: sHalfLifeHours = 24; michael@0: break; michael@0: case 3: michael@0: sHalfLifeHours = 7 * 24; michael@0: break; michael@0: case 4: michael@0: sHalfLifeHours = 50 * 24; michael@0: break; michael@0: michael@0: case -1: michael@0: default: // The experiment is off or broken michael@0: sHalfLifeExperiment = -1; michael@0: sHalfLifeHours = std::max(1U, std::min(1440U, mozilla::Preferences::GetUint( michael@0: "browser.cache.frecency_half_life_hours", kDefaultHalfLifeHours))); michael@0: break; michael@0: } michael@0: michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sSanitizeOnShutdown, "privacy.sanitize.sanitizeOnShutdown", kDefaultSanitizeOnShutdown); michael@0: mozilla::Preferences::AddBoolVarCache( michael@0: &sClearCacheOnShutdown, "privacy.clearOnShutdown.cache", kDefaultClearCacheOnShutdown); michael@0: } michael@0: michael@0: // static michael@0: uint32_t const CacheObserver::MemoryCacheCapacity() michael@0: { michael@0: if (sMemoryCacheCapacity >= 0) michael@0: return sMemoryCacheCapacity << 10; michael@0: michael@0: if (sAutoMemoryCacheCapacity != -1) michael@0: return sAutoMemoryCacheCapacity; michael@0: michael@0: static uint64_t bytes = PR_GetPhysicalMemorySize(); michael@0: // If getting the physical memory failed, arbitrarily assume michael@0: // 32 MB of RAM. We use a low default to have a reasonable michael@0: // size on all the devices we support. michael@0: if (bytes == 0) michael@0: bytes = 32 * 1024 * 1024; michael@0: michael@0: // Conversion from unsigned int64_t to double doesn't work on all platforms. michael@0: // We need to truncate the value at INT64_MAX to make sure we don't michael@0: // overflow. michael@0: if (bytes > INT64_MAX) michael@0: bytes = INT64_MAX; michael@0: michael@0: uint64_t kbytes = bytes >> 10; michael@0: double kBytesD = double(kbytes); michael@0: double x = log(kBytesD)/log(2.0) - 14; michael@0: michael@0: int32_t capacity = 0; michael@0: if (x > 0) { michael@0: capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding michael@0: if (capacity > 32) michael@0: capacity = 32; michael@0: capacity <<= 20; michael@0: } michael@0: michael@0: // Result is in bytes. michael@0: return sAutoMemoryCacheCapacity = capacity; michael@0: } michael@0: michael@0: void CacheObserver::SchduleAutoDelete() michael@0: { michael@0: // Auto-delete not set michael@0: if (sAutoDeleteCacheVersion == -1) michael@0: return; michael@0: michael@0: // Don't autodelete the same version of the cache user has setup michael@0: // to use. michael@0: int32_t activeVersion = UseNewCache() ? 1 : 0; michael@0: if (sAutoDeleteCacheVersion == activeVersion) michael@0: return; michael@0: michael@0: CacheStorageService::WipeCacheDirectory(sAutoDeleteCacheVersion); michael@0: } michael@0: michael@0: // static michael@0: bool const CacheObserver::UseNewCache() michael@0: { michael@0: uint32_t useNewCache = sUseNewCache; michael@0: michael@0: if (sUseNewCacheTemp) michael@0: useNewCache = 1; michael@0: michael@0: switch (useNewCache) { michael@0: case 0: // use the old cache backend michael@0: return false; michael@0: michael@0: case 1: // use the new cache backend michael@0: return true; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: CacheObserver::SetDiskCacheCapacity(uint32_t aCapacity) michael@0: { michael@0: sDiskCacheCapacity = aCapacity >> 10; michael@0: michael@0: if (!sSelf) { michael@0: return; michael@0: } michael@0: michael@0: if (NS_IsMainThread()) { michael@0: sSelf->StoreDiskCacheCapacity(); michael@0: } else { michael@0: nsCOMPtr event = michael@0: NS_NewRunnableMethod(sSelf, &CacheObserver::StoreDiskCacheCapacity); michael@0: NS_DispatchToMainThread(event); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CacheObserver::StoreDiskCacheCapacity() michael@0: { michael@0: mozilla::Preferences::SetInt("browser.cache.disk.capacity", michael@0: sDiskCacheCapacity); michael@0: } michael@0: michael@0: // static michael@0: void CacheObserver::ParentDirOverride(nsIFile** aDir) michael@0: { michael@0: if (NS_WARN_IF(!aDir)) michael@0: return; michael@0: michael@0: *aDir = nullptr; michael@0: michael@0: if (!sSelf) michael@0: return; michael@0: if (!sSelf->mCacheParentDirectoryOverride) michael@0: return; michael@0: michael@0: sSelf->mCacheParentDirectoryOverride->Clone(aDir); michael@0: } michael@0: michael@0: namespace { // anon michael@0: michael@0: class CacheStorageEvictHelper michael@0: { michael@0: public: michael@0: nsresult Run(mozIApplicationClearPrivateDataParams* aParams); michael@0: michael@0: private: michael@0: uint32_t mAppId; michael@0: nsresult ClearStorage(bool const aPrivate, michael@0: bool const aInBrowser, michael@0: bool const aAnonymous); michael@0: }; michael@0: michael@0: nsresult michael@0: CacheStorageEvictHelper::Run(mozIApplicationClearPrivateDataParams* aParams) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = aParams->GetAppId(&mAppId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool aBrowserOnly; michael@0: rv = aParams->GetBrowserOnly(&aBrowserOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: MOZ_ASSERT(mAppId != nsILoadContextInfo::UNKNOWN_APP_ID); michael@0: michael@0: // Clear all [private X anonymous] combinations michael@0: rv = ClearStorage(false, aBrowserOnly, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = ClearStorage(false, aBrowserOnly, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = ClearStorage(true, aBrowserOnly, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = ClearStorage(true, aBrowserOnly, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CacheStorageEvictHelper::ClearStorage(bool const aPrivate, michael@0: bool const aInBrowser, michael@0: bool const aAnonymous) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsRefPtr info = GetLoadContextInfo( michael@0: aPrivate, mAppId, aInBrowser, aAnonymous); michael@0: michael@0: nsCOMPtr storage; michael@0: nsRefPtr service = CacheStorageService::Self(); michael@0: NS_ENSURE_TRUE(service, NS_ERROR_FAILURE); michael@0: michael@0: // Clear disk storage michael@0: rv = service->DiskCacheStorage(info, false, getter_AddRefs(storage)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = storage->AsyncEvictStorage(nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Clear memory storage michael@0: rv = service->MemoryCacheStorage(info, getter_AddRefs(storage)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = storage->AsyncEvictStorage(nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!aInBrowser) { michael@0: rv = ClearStorage(aPrivate, true, aAnonymous); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // anon michael@0: michael@0: // static michael@0: bool const CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) michael@0: { michael@0: // If custom limit is set, check it. michael@0: int64_t preferredLimit = aUsingDisk michael@0: ? static_cast(sMaxDiskEntrySize) << 10 michael@0: : static_cast(sMaxMemoryEntrySize) << 10; michael@0: michael@0: if (preferredLimit != -1 && aSize > preferredLimit) michael@0: return true; michael@0: michael@0: // Otherwise (or when in the custom limit), check limit based on the global michael@0: // limit. It's 1/8 (>> 3) of the respective capacity. michael@0: int64_t derivedLimit = aUsingDisk michael@0: ? (static_cast(DiskCacheCapacity() >> 3)) michael@0: : (static_cast(MemoryCacheCapacity() >> 3)); michael@0: michael@0: if (aSize > derivedLimit) michael@0: return true; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CacheObserver::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp(aTopic, "prefservice:after-app-defaults")) { michael@0: CacheFileIOManager::Init(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "profile-do-change")) { michael@0: AttachToPreferences(); michael@0: CacheFileIOManager::Init(); michael@0: CacheFileIOManager::OnProfile(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "sessionstore-windows-restored")) { michael@0: SchduleAutoDelete(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "profile-before-change")) { michael@0: nsRefPtr service = CacheStorageService::Self(); michael@0: if (service) michael@0: service->Shutdown(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "xpcom-shutdown")) { michael@0: nsRefPtr service = CacheStorageService::Self(); michael@0: if (service) michael@0: service->Shutdown(); michael@0: michael@0: CacheFileIOManager::Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "last-pb-context-exited")) { michael@0: nsRefPtr service = CacheStorageService::Self(); michael@0: if (service) michael@0: service->DropPrivateBrowsingEntries(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "webapps-clear-data")) { michael@0: nsCOMPtr params = michael@0: do_QueryInterface(aSubject); michael@0: if (!params) { michael@0: NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: CacheStorageEvictHelper helper; michael@0: nsresult rv = helper.Run(params); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, "memory-pressure")) { michael@0: nsRefPtr service = CacheStorageService::Self(); michael@0: if (service) michael@0: service->PurgeFromMemory(nsICacheStorageService::PURGE_EVERYTHING); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(false, "Missing observer handler"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // net michael@0: } // mozilla