Bug 1816170 - Disable perftest-on-autoland cron. r=aglavic
[gecko.git] / dom / storage / StorageObserver.cpp
blobbdad1a5ea29aaa39ebe328dde618a427d05b2ca5
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "StorageObserver.h"
9 #include "LocalStorageCache.h"
10 #include "StorageCommon.h"
11 #include "StorageDBThread.h"
12 #include "StorageIPC.h"
13 #include "StorageUtils.h"
15 #include "mozilla/BasePrincipal.h"
16 #include "nsIObserverService.h"
17 #include "nsIURI.h"
18 #include "nsIPermission.h"
19 #include "nsIIDNService.h"
20 #include "nsICookiePermission.h"
22 #include "nsPrintfCString.h"
23 #include "nsXULAppAPI.h"
24 #include "nsEscape.h"
25 #include "nsNetCID.h"
26 #include "mozilla/dom/LocalStorageCommon.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/Services.h"
29 #include "mozilla/SpinEventLoopUntil.h"
30 #include "nsServiceManagerUtils.h"
32 namespace mozilla::dom {
34 using namespace StorageUtils;
36 static const char kStartupTopic[] = "sessionstore-windows-restored";
37 static const uint32_t kStartupDelay = 0;
39 const char kTestingPref[] = "dom.storage.testing";
41 constexpr auto kPrivateBrowsingPattern = u"{ \"privateBrowsingId\": 1 }"_ns;
43 NS_IMPL_ISUPPORTS(StorageObserver, nsIObserver, nsINamed,
44 nsISupportsWeakReference)
46 StorageObserver* StorageObserver::sSelf = nullptr;
48 // static
49 nsresult StorageObserver::Init() {
50 static_assert(kPrivateBrowsingIdCount * sizeof(mBackgroundThread[0]) ==
51 sizeof(mBackgroundThread));
52 if (sSelf) {
53 return NS_OK;
56 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
57 if (!obs) {
58 return NS_ERROR_UNEXPECTED;
61 sSelf = new StorageObserver();
62 NS_ADDREF(sSelf);
64 // Chrome clear operations.
65 obs->AddObserver(sSelf, kStartupTopic, true);
66 obs->AddObserver(sSelf, "cookie-changed", true);
67 obs->AddObserver(sSelf, "perm-changed", true);
68 obs->AddObserver(sSelf, "last-pb-context-exited", true);
69 obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
70 obs->AddObserver(sSelf, "dom-storage:clear-origin-attributes-data", true);
71 obs->AddObserver(sSelf, "extension:purge-localStorage", true);
72 obs->AddObserver(sSelf, "browser:purge-sessionStorage", true);
74 // Shutdown
75 obs->AddObserver(sSelf, "profile-after-change", true);
76 if (XRE_IsParentProcess()) {
77 obs->AddObserver(sSelf, "profile-before-change", true);
80 // Testing
81 #ifdef DOM_STORAGE_TESTS
82 Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
83 #endif
85 return NS_OK;
88 // static
89 nsresult StorageObserver::Shutdown() {
90 AssertIsOnMainThread();
92 if (!sSelf) {
93 return NS_ERROR_NOT_INITIALIZED; // Is this always an error?
96 sSelf->mSinks.Clear();
98 NS_RELEASE(sSelf);
99 return NS_OK;
102 // static
103 void StorageObserver::TestingPrefChanged(const char* aPrefName,
104 void* aClosure) {
105 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
106 if (!obs || !sSelf) {
107 return;
110 if (Preferences::GetBool(kTestingPref)) {
111 obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
112 if (XRE_IsParentProcess()) {
113 // Only to forward to child process.
114 obs->AddObserver(sSelf, "domstorage-test-flushed", true);
116 obs->AddObserver(sSelf, "domstorage-test-reload", true);
117 } else {
118 obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
119 if (XRE_IsParentProcess()) {
120 // Only to forward to child process.
121 obs->RemoveObserver(sSelf, "domstorage-test-flushed");
123 obs->RemoveObserver(sSelf, "domstorage-test-reload");
127 void StorageObserver::AddSink(StorageObserverSink* aObs) {
128 AssertIsOnMainThread();
130 MOZ_ASSERT(sSelf);
132 mSinks.AppendElement(aObs);
135 void StorageObserver::RemoveSink(StorageObserverSink* aObs) {
136 AssertIsOnMainThread();
138 MOZ_ASSERT(sSelf);
140 mSinks.RemoveElement(aObs);
143 void StorageObserver::Notify(const char* aTopic,
144 const nsAString& aOriginAttributesPattern,
145 const nsACString& aOriginScope) {
146 AssertIsOnMainThread();
148 MOZ_ASSERT(sSelf);
150 for (auto sink : mSinks.ForwardRange()) {
151 sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
155 void StorageObserver::NoteBackgroundThread(const uint32_t aPrivateBrowsingId,
156 nsIEventTarget* aBackgroundThread) {
157 MOZ_RELEASE_ASSERT(aPrivateBrowsingId < kPrivateBrowsingIdCount);
159 mBackgroundThread[aPrivateBrowsingId] = aBackgroundThread;
162 nsresult StorageObserver::GetOriginScope(const char16_t* aData,
163 nsACString& aOriginScope) {
164 nsresult rv;
166 NS_ConvertUTF16toUTF8 domain(aData);
168 nsAutoCString convertedDomain;
169 nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
170 if (converter) {
171 // Convert the domain name to the ACE format
172 rv = converter->ConvertUTF8toACE(domain, convertedDomain);
173 } else {
174 // In case the IDN service is not available, this is the best we can come
175 // up with!
176 rv = NS_EscapeURL(domain, esc_OnlyNonASCII | esc_AlwaysCopy,
177 convertedDomain, fallible);
179 if (NS_WARN_IF(NS_FAILED(rv))) {
180 return rv;
183 nsCString originScope;
184 rv = CreateReversedDomain(convertedDomain, originScope);
185 if (NS_WARN_IF(NS_FAILED(rv))) {
186 return rv;
189 aOriginScope = originScope;
190 return NS_OK;
193 NS_IMETHODIMP
194 StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
195 const char16_t* aData) {
196 if (NS_WARN_IF(!sSelf)) { // Shutdown took place
197 return NS_OK;
200 nsresult rv;
202 // Start the thread that opens the database.
203 if (!strcmp(aTopic, kStartupTopic)) {
204 MOZ_ASSERT(XRE_IsParentProcess());
206 if (NextGenLocalStorageEnabled()) {
207 return NS_OK;
210 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
211 obs->RemoveObserver(this, kStartupTopic);
213 return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer),
214 this, nsITimer::TYPE_ONE_SHOT,
215 kStartupDelay);
218 // Timer callback used to start the database a short timer after startup
219 if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
220 MOZ_ASSERT(XRE_IsParentProcess());
221 MOZ_ASSERT(!NextGenLocalStorageEnabled());
223 nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
224 if (!timer) {
225 return NS_ERROR_UNEXPECTED;
228 if (timer == mDBThreadStartDelayTimer) {
229 mDBThreadStartDelayTimer = nullptr;
231 for (const uint32_t id : {0, 1}) {
232 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
233 if (NS_WARN_IF(!storageChild)) {
234 return NS_ERROR_FAILURE;
237 storageChild->SendStartup();
241 return NS_OK;
244 // Clear everything, caches + database
245 if (!strcmp(aTopic, "cookie-changed")) {
246 if (!u"cleared"_ns.Equals(aData)) {
247 return NS_OK;
250 if (!NextGenLocalStorageEnabled()) {
251 for (const uint32_t id : {0, 1}) {
252 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
253 if (NS_WARN_IF(!storageChild)) {
254 return NS_ERROR_FAILURE;
257 storageChild->AsyncClearAll();
259 if (XRE_IsParentProcess()) {
260 storageChild->SendClearAll();
265 Notify("cookie-cleared");
267 return NS_OK;
270 // Clear from caches everything that has been stored
271 // while in session-only mode
272 if (!strcmp(aTopic, "perm-changed")) {
273 // Check for cookie permission change
274 nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
275 if (!perm) {
276 return NS_OK;
279 nsAutoCString type;
280 perm->GetType(type);
281 if (type != "cookie"_ns) {
282 return NS_OK;
285 uint32_t cap = 0;
286 perm->GetCapability(&cap);
287 if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
288 !u"deleted"_ns.Equals(nsDependentString(aData))) {
289 return NS_OK;
292 nsCOMPtr<nsIPrincipal> principal;
293 perm->GetPrincipal(getter_AddRefs(principal));
294 if (!principal) {
295 return NS_OK;
298 nsAutoCString originSuffix;
299 BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(
300 originSuffix);
302 nsAutoCString host;
303 principal->GetHost(host);
304 if (host.IsEmpty()) {
305 return NS_OK;
308 nsAutoCString originScope;
309 rv = CreateReversedDomain(host, originScope);
310 NS_ENSURE_SUCCESS(rv, rv);
312 Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
313 originScope);
315 return NS_OK;
318 if (!strcmp(aTopic, "extension:purge-localStorage")) {
319 if (NextGenLocalStorageEnabled()) {
320 return NS_OK;
323 const char topic[] = "extension:purge-localStorage-caches";
325 if (aData) {
326 nsCString originScope;
328 rv = GetOriginScope(aData, originScope);
329 if (NS_WARN_IF(NS_FAILED(rv))) {
330 return rv;
333 if (XRE_IsParentProcess()) {
334 for (const uint32_t id : {0, 1}) {
335 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
336 if (NS_WARN_IF(!storageChild)) {
337 return NS_ERROR_FAILURE;
340 storageChild->SendClearMatchingOrigin(originScope);
344 Notify(topic, u""_ns, originScope);
345 } else {
346 for (const uint32_t id : {0, 1}) {
347 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
348 if (NS_WARN_IF(!storageChild)) {
349 return NS_ERROR_FAILURE;
352 storageChild->AsyncClearAll();
354 if (XRE_IsParentProcess()) {
355 storageChild->SendClearAll();
359 Notify(topic);
362 return NS_OK;
365 if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
366 if (aData) {
367 nsCString originScope;
368 rv = GetOriginScope(aData, originScope);
369 if (NS_WARN_IF(NS_FAILED(rv))) {
370 return rv;
373 Notify(aTopic, u""_ns, originScope);
374 } else {
375 Notify(aTopic, u""_ns, ""_ns);
378 return NS_OK;
381 // Clear all private-browsing caches
382 if (!strcmp(aTopic, "last-pb-context-exited")) {
383 if (NextGenLocalStorageEnabled()) {
384 return NS_OK;
387 // We get the notification in both processes (parent and content), but the
388 // clearing of the in-memory database should be triggered from the parent
389 // process only to avoid creation of redundant clearing operations.
390 // Also, if we create a new StorageDBChild instance late during content
391 // process shutdown, then it might be leaked in debug builds because it
392 // could happen that there is no chance to properly destroy it.
393 if (XRE_IsParentProcess()) {
394 // This doesn't use a loop with privateBrowsingId 0 and 1, since we only
395 // need to clear the in-memory database which is represented by
396 // privateBrowsingId 1.
397 static const uint32_t id = 1;
399 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
400 if (NS_WARN_IF(!storageChild)) {
401 return NS_ERROR_FAILURE;
404 OriginAttributesPattern pattern;
405 if (!pattern.Init(kPrivateBrowsingPattern)) {
406 NS_ERROR("Cannot parse origin attributes pattern");
407 return NS_ERROR_FAILURE;
410 storageChild->SendClearMatchingOriginAttributes(pattern);
413 Notify("private-browsing-data-cleared", kPrivateBrowsingPattern);
415 return NS_OK;
418 // Clear data of the origins whose prefixes will match the suffix.
419 if (!strcmp(aTopic, "clear-origin-attributes-data") ||
420 !strcmp(aTopic, "dom-storage:clear-origin-attributes-data")) {
421 MOZ_ASSERT(XRE_IsParentProcess());
423 OriginAttributesPattern pattern;
424 if (!pattern.Init(nsDependentString(aData))) {
425 NS_ERROR("Cannot parse origin attributes pattern");
426 return NS_ERROR_FAILURE;
429 if (NextGenLocalStorageEnabled()) {
430 Notify("session-storage:clear-origin-attributes-data",
431 nsDependentString(aData));
432 return NS_OK;
435 for (const uint32_t id : {0, 1}) {
436 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
437 if (NS_WARN_IF(!storageChild)) {
438 return NS_ERROR_FAILURE;
441 storageChild->SendClearMatchingOriginAttributes(pattern);
444 Notify(aTopic, nsDependentString(aData));
446 return NS_OK;
449 if (!strcmp(aTopic, "profile-after-change")) {
450 Notify("profile-change");
452 return NS_OK;
455 if (!strcmp(aTopic, "profile-before-change")) {
456 MOZ_ASSERT(XRE_IsParentProcess());
458 if (NextGenLocalStorageEnabled()) {
459 return NS_OK;
462 for (const uint32_t id : {0, 1}) {
463 if (mBackgroundThread[id]) {
464 bool done = false;
466 RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
467 new StorageDBThread::ShutdownRunnable(id, done);
468 MOZ_ALWAYS_SUCCEEDS(mBackgroundThread[id]->Dispatch(
469 shutdownRunnable, NS_DISPATCH_NORMAL));
471 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
472 "StorageObserver::Observe profile-before-change"_ns,
473 [&]() { return done; }));
475 mBackgroundThread[id] = nullptr;
479 return NS_OK;
482 #ifdef DOM_STORAGE_TESTS
483 if (!strcmp(aTopic, "domstorage-test-flush-force")) {
484 if (NextGenLocalStorageEnabled()) {
485 return NS_OK;
488 for (const uint32_t id : {0, 1}) {
489 StorageDBChild* storageChild = StorageDBChild::GetOrCreate(id);
490 if (NS_WARN_IF(!storageChild)) {
491 return NS_ERROR_FAILURE;
494 storageChild->SendAsyncFlush();
497 return NS_OK;
500 if (!strcmp(aTopic, "domstorage-test-flushed")) {
501 if (NextGenLocalStorageEnabled()) {
502 return NS_OK;
505 // Only used to propagate to IPC children
506 Notify("test-flushed");
508 return NS_OK;
511 if (!strcmp(aTopic, "domstorage-test-reload")) {
512 if (NextGenLocalStorageEnabled()) {
513 return NS_OK;
516 Notify("test-reload");
518 return NS_OK;
520 #endif
522 NS_ERROR("Unexpected topic");
523 return NS_ERROR_UNEXPECTED;
526 NS_IMETHODIMP
527 StorageObserver::GetName(nsACString& aName) {
528 aName.AssignLiteral("StorageObserver");
529 return NS_OK;
532 } // namespace mozilla::dom