|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "CacheObserver.h" |
|
6 |
|
7 #include "CacheStorageService.h" |
|
8 #include "CacheFileIOManager.h" |
|
9 #include "LoadContextInfo.h" |
|
10 #include "nsICacheStorage.h" |
|
11 #include "nsIObserverService.h" |
|
12 #include "mozIApplicationClearPrivateDataParams.h" |
|
13 #include "mozilla/Services.h" |
|
14 #include "mozilla/Preferences.h" |
|
15 #include "nsServiceManagerUtils.h" |
|
16 #include "prsystem.h" |
|
17 #include <time.h> |
|
18 #include <math.h> |
|
19 |
|
20 namespace mozilla { |
|
21 namespace net { |
|
22 |
|
23 CacheObserver* CacheObserver::sSelf = nullptr; |
|
24 |
|
25 static uint32_t const kDefaultUseNewCache = 0; // Don't use the new cache by default |
|
26 uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache; |
|
27 |
|
28 static bool sUseNewCacheTemp = false; // Temp trigger to not lose early adopters |
|
29 |
|
30 static int32_t const kAutoDeleteCacheVersion = -1; // Auto-delete off by default |
|
31 static int32_t sAutoDeleteCacheVersion = kAutoDeleteCacheVersion; |
|
32 |
|
33 static int32_t const kDefaultHalfLifeExperiment = -1; // Disabled |
|
34 int32_t CacheObserver::sHalfLifeExperiment = kDefaultHalfLifeExperiment; |
|
35 |
|
36 static uint32_t const kDefaultHalfLifeHours = 6; // 6 hours |
|
37 uint32_t CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours; |
|
38 |
|
39 static bool const kDefaultUseDiskCache = true; |
|
40 bool CacheObserver::sUseDiskCache = kDefaultUseDiskCache; |
|
41 |
|
42 static bool const kDefaultUseMemoryCache = true; |
|
43 bool CacheObserver::sUseMemoryCache = kDefaultUseMemoryCache; |
|
44 |
|
45 static uint32_t const kDefaultMetadataMemoryLimit = 250; // 0.25 MB |
|
46 uint32_t CacheObserver::sMetadataMemoryLimit = kDefaultMetadataMemoryLimit; |
|
47 |
|
48 static int32_t const kDefaultMemoryCacheCapacity = -1; // autodetect |
|
49 int32_t CacheObserver::sMemoryCacheCapacity = kDefaultMemoryCacheCapacity; |
|
50 // Cache of the calculated memory capacity based on the system memory size |
|
51 int32_t CacheObserver::sAutoMemoryCacheCapacity = -1; |
|
52 |
|
53 static uint32_t const kDefaultDiskCacheCapacity = 250 * 1024; // 250 MB |
|
54 uint32_t CacheObserver::sDiskCacheCapacity = kDefaultDiskCacheCapacity; |
|
55 |
|
56 static bool const kDefaultSmartCacheSizeEnabled = false; |
|
57 bool CacheObserver::sSmartCacheSizeEnabled = kDefaultSmartCacheSizeEnabled; |
|
58 |
|
59 static uint32_t const kDefaultMaxMemoryEntrySize = 4 * 1024; // 4 MB |
|
60 uint32_t CacheObserver::sMaxMemoryEntrySize = kDefaultMaxMemoryEntrySize; |
|
61 |
|
62 static uint32_t const kDefaultMaxDiskEntrySize = 50 * 1024; // 50 MB |
|
63 uint32_t CacheObserver::sMaxDiskEntrySize = kDefaultMaxDiskEntrySize; |
|
64 |
|
65 static uint32_t const kDefaultCompressionLevel = 1; |
|
66 uint32_t CacheObserver::sCompressionLevel = kDefaultCompressionLevel; |
|
67 |
|
68 static bool kDefaultSanitizeOnShutdown = false; |
|
69 bool CacheObserver::sSanitizeOnShutdown = kDefaultSanitizeOnShutdown; |
|
70 |
|
71 static bool kDefaultClearCacheOnShutdown = false; |
|
72 bool CacheObserver::sClearCacheOnShutdown = kDefaultClearCacheOnShutdown; |
|
73 |
|
74 NS_IMPL_ISUPPORTS(CacheObserver, |
|
75 nsIObserver, |
|
76 nsISupportsWeakReference) |
|
77 |
|
78 // static |
|
79 nsresult |
|
80 CacheObserver::Init() |
|
81 { |
|
82 if (sSelf) { |
|
83 return NS_OK; |
|
84 } |
|
85 |
|
86 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
|
87 if (!obs) { |
|
88 return NS_ERROR_UNEXPECTED; |
|
89 } |
|
90 |
|
91 sSelf = new CacheObserver(); |
|
92 NS_ADDREF(sSelf); |
|
93 |
|
94 obs->AddObserver(sSelf, "prefservice:after-app-defaults", true); |
|
95 obs->AddObserver(sSelf, "profile-do-change", true); |
|
96 obs->AddObserver(sSelf, "sessionstore-windows-restored", true); |
|
97 obs->AddObserver(sSelf, "profile-before-change", true); |
|
98 obs->AddObserver(sSelf, "xpcom-shutdown", true); |
|
99 obs->AddObserver(sSelf, "last-pb-context-exited", true); |
|
100 obs->AddObserver(sSelf, "webapps-clear-data", true); |
|
101 obs->AddObserver(sSelf, "memory-pressure", true); |
|
102 |
|
103 return NS_OK; |
|
104 } |
|
105 |
|
106 // static |
|
107 nsresult |
|
108 CacheObserver::Shutdown() |
|
109 { |
|
110 if (!sSelf) { |
|
111 return NS_ERROR_NOT_INITIALIZED; |
|
112 } |
|
113 |
|
114 NS_RELEASE(sSelf); |
|
115 return NS_OK; |
|
116 } |
|
117 |
|
118 void |
|
119 CacheObserver::AttachToPreferences() |
|
120 { |
|
121 sAutoDeleteCacheVersion = mozilla::Preferences::GetInt( |
|
122 "browser.cache.auto_delete_cache_version", kAutoDeleteCacheVersion); |
|
123 |
|
124 mozilla::Preferences::AddUintVarCache( |
|
125 &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache); |
|
126 mozilla::Preferences::AddBoolVarCache( |
|
127 &sUseNewCacheTemp, "browser.cache.use_new_backend_temp", false); |
|
128 |
|
129 mozilla::Preferences::AddBoolVarCache( |
|
130 &sUseDiskCache, "browser.cache.disk.enable", kDefaultUseDiskCache); |
|
131 mozilla::Preferences::AddBoolVarCache( |
|
132 &sUseMemoryCache, "browser.cache.memory.enable", kDefaultUseMemoryCache); |
|
133 |
|
134 mozilla::Preferences::AddUintVarCache( |
|
135 &sMetadataMemoryLimit, "browser.cache.disk.metadata_memory_limit", kDefaultMetadataMemoryLimit); |
|
136 |
|
137 mozilla::Preferences::AddUintVarCache( |
|
138 &sDiskCacheCapacity, "browser.cache.disk.capacity", kDefaultDiskCacheCapacity); |
|
139 mozilla::Preferences::AddBoolVarCache( |
|
140 &sSmartCacheSizeEnabled, "browser.cache.disk.smart_size.enabled", kDefaultSmartCacheSizeEnabled); |
|
141 mozilla::Preferences::AddIntVarCache( |
|
142 &sMemoryCacheCapacity, "browser.cache.memory.capacity", kDefaultMemoryCacheCapacity); |
|
143 |
|
144 mozilla::Preferences::AddUintVarCache( |
|
145 &sMaxDiskEntrySize, "browser.cache.disk.max_entry_size", kDefaultMaxDiskEntrySize); |
|
146 mozilla::Preferences::AddUintVarCache( |
|
147 &sMaxMemoryEntrySize, "browser.cache.memory.max_entry_size", kDefaultMaxMemoryEntrySize); |
|
148 |
|
149 // http://mxr.mozilla.org/mozilla-central/source/netwerk/cache/nsCacheEntryDescriptor.cpp#367 |
|
150 mozilla::Preferences::AddUintVarCache( |
|
151 &sCompressionLevel, "browser.cache.compression_level", kDefaultCompressionLevel); |
|
152 |
|
153 mozilla::Preferences::GetComplex( |
|
154 "browser.cache.disk.parent_directory", NS_GET_IID(nsIFile), |
|
155 getter_AddRefs(mCacheParentDirectoryOverride)); |
|
156 |
|
157 // First check the default value. If it is at -1, the experient |
|
158 // is turned off. If it is at 0, then use the user pref value |
|
159 // instead. |
|
160 sHalfLifeExperiment = mozilla::Preferences::GetDefaultInt( |
|
161 "browser.cache.frecency_experiment", kDefaultHalfLifeExperiment); |
|
162 |
|
163 if (sHalfLifeExperiment == 0) { |
|
164 // Default preferences indicate we want to run the experiment, |
|
165 // hence read the user value. |
|
166 sHalfLifeExperiment = mozilla::Preferences::GetInt( |
|
167 "browser.cache.frecency_experiment", sHalfLifeExperiment); |
|
168 } |
|
169 |
|
170 if (sHalfLifeExperiment == 0) { |
|
171 // The experiment has not yet been initialized but is engaged, do |
|
172 // the initialization now. |
|
173 srand(time(NULL)); |
|
174 sHalfLifeExperiment = (rand() % 4) + 1; |
|
175 // Store the experiemnt value, since we need it not to change between |
|
176 // browser sessions. |
|
177 mozilla::Preferences::SetInt( |
|
178 "browser.cache.frecency_experiment", sHalfLifeExperiment); |
|
179 } |
|
180 |
|
181 switch (sHalfLifeExperiment) { |
|
182 case 1: // The experiment is engaged |
|
183 sHalfLifeHours = 6; |
|
184 break; |
|
185 case 2: |
|
186 sHalfLifeHours = 24; |
|
187 break; |
|
188 case 3: |
|
189 sHalfLifeHours = 7 * 24; |
|
190 break; |
|
191 case 4: |
|
192 sHalfLifeHours = 50 * 24; |
|
193 break; |
|
194 |
|
195 case -1: |
|
196 default: // The experiment is off or broken |
|
197 sHalfLifeExperiment = -1; |
|
198 sHalfLifeHours = std::max(1U, std::min(1440U, mozilla::Preferences::GetUint( |
|
199 "browser.cache.frecency_half_life_hours", kDefaultHalfLifeHours))); |
|
200 break; |
|
201 } |
|
202 |
|
203 mozilla::Preferences::AddBoolVarCache( |
|
204 &sSanitizeOnShutdown, "privacy.sanitize.sanitizeOnShutdown", kDefaultSanitizeOnShutdown); |
|
205 mozilla::Preferences::AddBoolVarCache( |
|
206 &sClearCacheOnShutdown, "privacy.clearOnShutdown.cache", kDefaultClearCacheOnShutdown); |
|
207 } |
|
208 |
|
209 // static |
|
210 uint32_t const CacheObserver::MemoryCacheCapacity() |
|
211 { |
|
212 if (sMemoryCacheCapacity >= 0) |
|
213 return sMemoryCacheCapacity << 10; |
|
214 |
|
215 if (sAutoMemoryCacheCapacity != -1) |
|
216 return sAutoMemoryCacheCapacity; |
|
217 |
|
218 static uint64_t bytes = PR_GetPhysicalMemorySize(); |
|
219 // If getting the physical memory failed, arbitrarily assume |
|
220 // 32 MB of RAM. We use a low default to have a reasonable |
|
221 // size on all the devices we support. |
|
222 if (bytes == 0) |
|
223 bytes = 32 * 1024 * 1024; |
|
224 |
|
225 // Conversion from unsigned int64_t to double doesn't work on all platforms. |
|
226 // We need to truncate the value at INT64_MAX to make sure we don't |
|
227 // overflow. |
|
228 if (bytes > INT64_MAX) |
|
229 bytes = INT64_MAX; |
|
230 |
|
231 uint64_t kbytes = bytes >> 10; |
|
232 double kBytesD = double(kbytes); |
|
233 double x = log(kBytesD)/log(2.0) - 14; |
|
234 |
|
235 int32_t capacity = 0; |
|
236 if (x > 0) { |
|
237 capacity = (int32_t)(x * x / 3.0 + x + 2.0 / 3 + 0.1); // 0.1 for rounding |
|
238 if (capacity > 32) |
|
239 capacity = 32; |
|
240 capacity <<= 20; |
|
241 } |
|
242 |
|
243 // Result is in bytes. |
|
244 return sAutoMemoryCacheCapacity = capacity; |
|
245 } |
|
246 |
|
247 void CacheObserver::SchduleAutoDelete() |
|
248 { |
|
249 // Auto-delete not set |
|
250 if (sAutoDeleteCacheVersion == -1) |
|
251 return; |
|
252 |
|
253 // Don't autodelete the same version of the cache user has setup |
|
254 // to use. |
|
255 int32_t activeVersion = UseNewCache() ? 1 : 0; |
|
256 if (sAutoDeleteCacheVersion == activeVersion) |
|
257 return; |
|
258 |
|
259 CacheStorageService::WipeCacheDirectory(sAutoDeleteCacheVersion); |
|
260 } |
|
261 |
|
262 // static |
|
263 bool const CacheObserver::UseNewCache() |
|
264 { |
|
265 uint32_t useNewCache = sUseNewCache; |
|
266 |
|
267 if (sUseNewCacheTemp) |
|
268 useNewCache = 1; |
|
269 |
|
270 switch (useNewCache) { |
|
271 case 0: // use the old cache backend |
|
272 return false; |
|
273 |
|
274 case 1: // use the new cache backend |
|
275 return true; |
|
276 } |
|
277 |
|
278 return true; |
|
279 } |
|
280 |
|
281 // static |
|
282 void |
|
283 CacheObserver::SetDiskCacheCapacity(uint32_t aCapacity) |
|
284 { |
|
285 sDiskCacheCapacity = aCapacity >> 10; |
|
286 |
|
287 if (!sSelf) { |
|
288 return; |
|
289 } |
|
290 |
|
291 if (NS_IsMainThread()) { |
|
292 sSelf->StoreDiskCacheCapacity(); |
|
293 } else { |
|
294 nsCOMPtr<nsIRunnable> event = |
|
295 NS_NewRunnableMethod(sSelf, &CacheObserver::StoreDiskCacheCapacity); |
|
296 NS_DispatchToMainThread(event); |
|
297 } |
|
298 } |
|
299 |
|
300 void |
|
301 CacheObserver::StoreDiskCacheCapacity() |
|
302 { |
|
303 mozilla::Preferences::SetInt("browser.cache.disk.capacity", |
|
304 sDiskCacheCapacity); |
|
305 } |
|
306 |
|
307 // static |
|
308 void CacheObserver::ParentDirOverride(nsIFile** aDir) |
|
309 { |
|
310 if (NS_WARN_IF(!aDir)) |
|
311 return; |
|
312 |
|
313 *aDir = nullptr; |
|
314 |
|
315 if (!sSelf) |
|
316 return; |
|
317 if (!sSelf->mCacheParentDirectoryOverride) |
|
318 return; |
|
319 |
|
320 sSelf->mCacheParentDirectoryOverride->Clone(aDir); |
|
321 } |
|
322 |
|
323 namespace { // anon |
|
324 |
|
325 class CacheStorageEvictHelper |
|
326 { |
|
327 public: |
|
328 nsresult Run(mozIApplicationClearPrivateDataParams* aParams); |
|
329 |
|
330 private: |
|
331 uint32_t mAppId; |
|
332 nsresult ClearStorage(bool const aPrivate, |
|
333 bool const aInBrowser, |
|
334 bool const aAnonymous); |
|
335 }; |
|
336 |
|
337 nsresult |
|
338 CacheStorageEvictHelper::Run(mozIApplicationClearPrivateDataParams* aParams) |
|
339 { |
|
340 nsresult rv; |
|
341 |
|
342 rv = aParams->GetAppId(&mAppId); |
|
343 NS_ENSURE_SUCCESS(rv, rv); |
|
344 |
|
345 bool aBrowserOnly; |
|
346 rv = aParams->GetBrowserOnly(&aBrowserOnly); |
|
347 NS_ENSURE_SUCCESS(rv, rv); |
|
348 |
|
349 MOZ_ASSERT(mAppId != nsILoadContextInfo::UNKNOWN_APP_ID); |
|
350 |
|
351 // Clear all [private X anonymous] combinations |
|
352 rv = ClearStorage(false, aBrowserOnly, false); |
|
353 NS_ENSURE_SUCCESS(rv, rv); |
|
354 rv = ClearStorage(false, aBrowserOnly, true); |
|
355 NS_ENSURE_SUCCESS(rv, rv); |
|
356 rv = ClearStorage(true, aBrowserOnly, false); |
|
357 NS_ENSURE_SUCCESS(rv, rv); |
|
358 rv = ClearStorage(true, aBrowserOnly, true); |
|
359 NS_ENSURE_SUCCESS(rv, rv); |
|
360 |
|
361 return NS_OK; |
|
362 } |
|
363 |
|
364 nsresult |
|
365 CacheStorageEvictHelper::ClearStorage(bool const aPrivate, |
|
366 bool const aInBrowser, |
|
367 bool const aAnonymous) |
|
368 { |
|
369 nsresult rv; |
|
370 |
|
371 nsRefPtr<LoadContextInfo> info = GetLoadContextInfo( |
|
372 aPrivate, mAppId, aInBrowser, aAnonymous); |
|
373 |
|
374 nsCOMPtr<nsICacheStorage> storage; |
|
375 nsRefPtr<CacheStorageService> service = CacheStorageService::Self(); |
|
376 NS_ENSURE_TRUE(service, NS_ERROR_FAILURE); |
|
377 |
|
378 // Clear disk storage |
|
379 rv = service->DiskCacheStorage(info, false, getter_AddRefs(storage)); |
|
380 NS_ENSURE_SUCCESS(rv, rv); |
|
381 rv = storage->AsyncEvictStorage(nullptr); |
|
382 NS_ENSURE_SUCCESS(rv, rv); |
|
383 |
|
384 // Clear memory storage |
|
385 rv = service->MemoryCacheStorage(info, getter_AddRefs(storage)); |
|
386 NS_ENSURE_SUCCESS(rv, rv); |
|
387 rv = storage->AsyncEvictStorage(nullptr); |
|
388 NS_ENSURE_SUCCESS(rv, rv); |
|
389 |
|
390 if (!aInBrowser) { |
|
391 rv = ClearStorage(aPrivate, true, aAnonymous); |
|
392 NS_ENSURE_SUCCESS(rv, rv); |
|
393 } |
|
394 |
|
395 return NS_OK; |
|
396 } |
|
397 |
|
398 } // anon |
|
399 |
|
400 // static |
|
401 bool const CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk) |
|
402 { |
|
403 // If custom limit is set, check it. |
|
404 int64_t preferredLimit = aUsingDisk |
|
405 ? static_cast<int64_t>(sMaxDiskEntrySize) << 10 |
|
406 : static_cast<int64_t>(sMaxMemoryEntrySize) << 10; |
|
407 |
|
408 if (preferredLimit != -1 && aSize > preferredLimit) |
|
409 return true; |
|
410 |
|
411 // Otherwise (or when in the custom limit), check limit based on the global |
|
412 // limit. It's 1/8 (>> 3) of the respective capacity. |
|
413 int64_t derivedLimit = aUsingDisk |
|
414 ? (static_cast<int64_t>(DiskCacheCapacity() >> 3)) |
|
415 : (static_cast<int64_t>(MemoryCacheCapacity() >> 3)); |
|
416 |
|
417 if (aSize > derivedLimit) |
|
418 return true; |
|
419 |
|
420 return false; |
|
421 } |
|
422 |
|
423 NS_IMETHODIMP |
|
424 CacheObserver::Observe(nsISupports* aSubject, |
|
425 const char* aTopic, |
|
426 const char16_t* aData) |
|
427 { |
|
428 if (!strcmp(aTopic, "prefservice:after-app-defaults")) { |
|
429 CacheFileIOManager::Init(); |
|
430 return NS_OK; |
|
431 } |
|
432 |
|
433 if (!strcmp(aTopic, "profile-do-change")) { |
|
434 AttachToPreferences(); |
|
435 CacheFileIOManager::Init(); |
|
436 CacheFileIOManager::OnProfile(); |
|
437 return NS_OK; |
|
438 } |
|
439 |
|
440 if (!strcmp(aTopic, "sessionstore-windows-restored")) { |
|
441 SchduleAutoDelete(); |
|
442 return NS_OK; |
|
443 } |
|
444 |
|
445 if (!strcmp(aTopic, "profile-before-change")) { |
|
446 nsRefPtr<CacheStorageService> service = CacheStorageService::Self(); |
|
447 if (service) |
|
448 service->Shutdown(); |
|
449 |
|
450 return NS_OK; |
|
451 } |
|
452 |
|
453 if (!strcmp(aTopic, "xpcom-shutdown")) { |
|
454 nsRefPtr<CacheStorageService> service = CacheStorageService::Self(); |
|
455 if (service) |
|
456 service->Shutdown(); |
|
457 |
|
458 CacheFileIOManager::Shutdown(); |
|
459 return NS_OK; |
|
460 } |
|
461 |
|
462 if (!strcmp(aTopic, "last-pb-context-exited")) { |
|
463 nsRefPtr<CacheStorageService> service = CacheStorageService::Self(); |
|
464 if (service) |
|
465 service->DropPrivateBrowsingEntries(); |
|
466 |
|
467 return NS_OK; |
|
468 } |
|
469 |
|
470 if (!strcmp(aTopic, "webapps-clear-data")) { |
|
471 nsCOMPtr<mozIApplicationClearPrivateDataParams> params = |
|
472 do_QueryInterface(aSubject); |
|
473 if (!params) { |
|
474 NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams"); |
|
475 return NS_ERROR_UNEXPECTED; |
|
476 } |
|
477 |
|
478 CacheStorageEvictHelper helper; |
|
479 nsresult rv = helper.Run(params); |
|
480 NS_ENSURE_SUCCESS(rv, rv); |
|
481 |
|
482 return NS_OK; |
|
483 } |
|
484 |
|
485 if (!strcmp(aTopic, "memory-pressure")) { |
|
486 nsRefPtr<CacheStorageService> service = CacheStorageService::Self(); |
|
487 if (service) |
|
488 service->PurgeFromMemory(nsICacheStorageService::PURGE_EVERYTHING); |
|
489 |
|
490 return NS_OK; |
|
491 } |
|
492 |
|
493 MOZ_ASSERT(false, "Missing observer handler"); |
|
494 return NS_OK; |
|
495 } |
|
496 |
|
497 } // net |
|
498 } // mozilla |