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"
18 #include "nsIPermission.h"
19 #include "nsIIDNService.h"
20 #include "nsICookiePermission.h"
22 #include "nsPrintfCString.h"
23 #include "nsXULAppAPI.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"
35 using namespace StorageUtils
;
37 static const char kStartupTopic
[] = "sessionstore-windows-restored";
38 static const uint32_t kStartupDelay
= 0;
40 const char kTestingPref
[] = "dom.storage.testing";
42 constexpr auto kPrivateBrowsingPattern
= u
"{ \"privateBrowsingId\": 1 }"_ns
;
44 NS_IMPL_ISUPPORTS(StorageObserver
, nsIObserver
, nsINamed
,
45 nsISupportsWeakReference
)
47 StorageObserver
* StorageObserver::sSelf
= nullptr;
50 nsresult
StorageObserver::Init() {
51 static_assert(kPrivateBrowsingIdCount
* sizeof(mBackgroundThread
[0]) ==
52 sizeof(mBackgroundThread
));
57 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
59 return NS_ERROR_UNEXPECTED
;
62 sSelf
= new StorageObserver();
65 // Chrome clear operations.
66 obs
->AddObserver(sSelf
, kStartupTopic
, true);
67 obs
->AddObserver(sSelf
, "cookie-changed", true);
68 obs
->AddObserver(sSelf
, "perm-changed", true);
69 obs
->AddObserver(sSelf
, "last-pb-context-exited", true);
70 obs
->AddObserver(sSelf
, "clear-origin-attributes-data", true);
71 obs
->AddObserver(sSelf
, "dom-storage:clear-origin-attributes-data", true);
72 obs
->AddObserver(sSelf
, "extension:purge-localStorage", true);
73 obs
->AddObserver(sSelf
, "browser:purge-sessionStorage", true);
76 obs
->AddObserver(sSelf
, "profile-after-change", true);
77 if (XRE_IsParentProcess()) {
78 obs
->AddObserver(sSelf
, "profile-before-change", true);
82 #ifdef DOM_STORAGE_TESTS
83 Preferences::RegisterCallbackAndCall(TestingPrefChanged
, kTestingPref
);
90 nsresult
StorageObserver::Shutdown() {
92 return NS_ERROR_NOT_INITIALIZED
;
100 void StorageObserver::TestingPrefChanged(const char* aPrefName
,
102 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
107 if (Preferences::GetBool(kTestingPref
)) {
108 obs
->AddObserver(sSelf
, "domstorage-test-flush-force", true);
109 if (XRE_IsParentProcess()) {
110 // Only to forward to child process.
111 obs
->AddObserver(sSelf
, "domstorage-test-flushed", true);
113 obs
->AddObserver(sSelf
, "domstorage-test-reload", true);
115 obs
->RemoveObserver(sSelf
, "domstorage-test-flush-force");
116 if (XRE_IsParentProcess()) {
117 // Only to forward to child process.
118 obs
->RemoveObserver(sSelf
, "domstorage-test-flushed");
120 obs
->RemoveObserver(sSelf
, "domstorage-test-reload");
124 void StorageObserver::AddSink(StorageObserverSink
* aObs
) {
125 mSinks
.AppendElement(aObs
);
128 void StorageObserver::RemoveSink(StorageObserverSink
* aObs
) {
129 mSinks
.RemoveElement(aObs
);
132 void StorageObserver::Notify(const char* aTopic
,
133 const nsAString
& aOriginAttributesPattern
,
134 const nsACString
& aOriginScope
) {
135 for (auto* sink
: mSinks
.ForwardRange()) {
136 sink
->Observe(aTopic
, aOriginAttributesPattern
, aOriginScope
);
140 void StorageObserver::NoteBackgroundThread(const uint32_t aPrivateBrowsingId
,
141 nsIEventTarget
* aBackgroundThread
) {
142 MOZ_RELEASE_ASSERT(aPrivateBrowsingId
< kPrivateBrowsingIdCount
);
144 mBackgroundThread
[aPrivateBrowsingId
] = aBackgroundThread
;
147 nsresult
StorageObserver::GetOriginScope(const char16_t
* aData
,
148 nsACString
& aOriginScope
) {
151 NS_ConvertUTF16toUTF8
domain(aData
);
153 nsAutoCString convertedDomain
;
154 nsCOMPtr
<nsIIDNService
> converter
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
156 // Convert the domain name to the ACE format
157 rv
= converter
->ConvertUTF8toACE(domain
, convertedDomain
);
159 // In case the IDN service is not available, this is the best we can come
161 rv
= NS_EscapeURL(domain
, esc_OnlyNonASCII
| esc_AlwaysCopy
,
162 convertedDomain
, fallible
);
164 if (NS_WARN_IF(NS_FAILED(rv
))) {
168 nsCString originScope
;
169 rv
= CreateReversedDomain(convertedDomain
, originScope
);
170 if (NS_WARN_IF(NS_FAILED(rv
))) {
174 aOriginScope
= originScope
;
179 StorageObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
180 const char16_t
* aData
) {
183 // Start the thread that opens the database.
184 if (!strcmp(aTopic
, kStartupTopic
)) {
185 MOZ_ASSERT(XRE_IsParentProcess());
187 if (NextGenLocalStorageEnabled()) {
191 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
192 obs
->RemoveObserver(this, kStartupTopic
);
194 return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer
),
195 this, nsITimer::TYPE_ONE_SHOT
,
199 // Timer callback used to start the database a short timer after startup
200 if (!strcmp(aTopic
, NS_TIMER_CALLBACK_TOPIC
)) {
201 MOZ_ASSERT(XRE_IsParentProcess());
202 MOZ_ASSERT(!NextGenLocalStorageEnabled());
204 nsCOMPtr
<nsITimer
> timer
= do_QueryInterface(aSubject
);
206 return NS_ERROR_UNEXPECTED
;
209 if (timer
== mDBThreadStartDelayTimer
) {
210 mDBThreadStartDelayTimer
= nullptr;
212 for (const uint32_t id
: {0, 1}) {
213 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
214 if (NS_WARN_IF(!storageChild
)) {
215 return NS_ERROR_FAILURE
;
218 storageChild
->SendStartup();
225 // Clear everything, caches + database
226 if (!strcmp(aTopic
, "cookie-changed")) {
227 if (!u
"cleared"_ns
.Equals(aData
)) {
231 if (!NextGenLocalStorageEnabled()) {
232 for (const uint32_t id
: {0, 1}) {
233 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
234 if (NS_WARN_IF(!storageChild
)) {
235 return NS_ERROR_FAILURE
;
238 storageChild
->AsyncClearAll();
240 if (XRE_IsParentProcess()) {
241 storageChild
->SendClearAll();
246 Notify("cookie-cleared");
251 // Clear from caches everything that has been stored
252 // while in session-only mode
253 if (!strcmp(aTopic
, "perm-changed")) {
254 // Check for cookie permission change
255 nsCOMPtr
<nsIPermission
> perm(do_QueryInterface(aSubject
));
262 if (type
!= "cookie"_ns
) {
267 perm
->GetCapability(&cap
);
268 if (!(cap
& nsICookiePermission::ACCESS_SESSION
) ||
269 !u
"deleted"_ns
.Equals(nsDependentString(aData
))) {
273 nsCOMPtr
<nsIPrincipal
> principal
;
274 perm
->GetPrincipal(getter_AddRefs(principal
));
279 nsAutoCString originSuffix
;
280 BasePrincipal::Cast(principal
)->OriginAttributesRef().CreateSuffix(
284 principal
->GetHost(host
);
285 if (host
.IsEmpty()) {
289 nsAutoCString originScope
;
290 rv
= CreateReversedDomain(host
, originScope
);
291 NS_ENSURE_SUCCESS(rv
, rv
);
293 Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix
),
299 if (!strcmp(aTopic
, "extension:purge-localStorage")) {
300 if (NextGenLocalStorageEnabled()) {
304 const char topic
[] = "extension:purge-localStorage-caches";
307 nsCString originScope
;
309 rv
= GetOriginScope(aData
, originScope
);
310 if (NS_WARN_IF(NS_FAILED(rv
))) {
314 if (XRE_IsParentProcess()) {
315 for (const uint32_t id
: {0, 1}) {
316 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
317 if (NS_WARN_IF(!storageChild
)) {
318 return NS_ERROR_FAILURE
;
321 storageChild
->SendClearMatchingOrigin(originScope
);
325 Notify(topic
, u
""_ns
, originScope
);
327 for (const uint32_t id
: {0, 1}) {
328 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
329 if (NS_WARN_IF(!storageChild
)) {
330 return NS_ERROR_FAILURE
;
333 storageChild
->AsyncClearAll();
335 if (XRE_IsParentProcess()) {
336 storageChild
->SendClearAll();
346 if (!strcmp(aTopic
, "browser:purge-sessionStorage")) {
348 nsCString originScope
;
349 rv
= GetOriginScope(aData
, originScope
);
350 if (NS_WARN_IF(NS_FAILED(rv
))) {
354 Notify(aTopic
, u
""_ns
, originScope
);
356 Notify(aTopic
, u
""_ns
, ""_ns
);
362 // Clear all private-browsing caches
363 if (!strcmp(aTopic
, "last-pb-context-exited")) {
364 if (NextGenLocalStorageEnabled()) {
368 // We get the notification in both processes (parent and content), but the
369 // clearing of the in-memory database should be triggered from the parent
370 // process only to avoid creation of redundant clearing operations.
371 // Also, if we create a new StorageDBChild instance late during content
372 // process shutdown, then it might be leaked in debug builds because it
373 // could happen that there is no chance to properly destroy it.
374 if (XRE_IsParentProcess()) {
375 // This doesn't use a loop with privateBrowsingId 0 and 1, since we only
376 // need to clear the in-memory database which is represented by
377 // privateBrowsingId 1.
378 static const uint32_t id
= 1;
380 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
381 if (NS_WARN_IF(!storageChild
)) {
382 return NS_ERROR_FAILURE
;
385 OriginAttributesPattern pattern
;
386 if (!pattern
.Init(kPrivateBrowsingPattern
)) {
387 NS_ERROR("Cannot parse origin attributes pattern");
388 return NS_ERROR_FAILURE
;
391 storageChild
->SendClearMatchingOriginAttributes(pattern
);
394 Notify("private-browsing-data-cleared", kPrivateBrowsingPattern
);
399 // Clear data of the origins whose prefixes will match the suffix.
400 if (!strcmp(aTopic
, "clear-origin-attributes-data") ||
401 !strcmp(aTopic
, "dom-storage:clear-origin-attributes-data")) {
402 MOZ_ASSERT(XRE_IsParentProcess());
404 OriginAttributesPattern pattern
;
405 if (!pattern
.Init(nsDependentString(aData
))) {
406 NS_ERROR("Cannot parse origin attributes pattern");
407 return NS_ERROR_FAILURE
;
410 if (NextGenLocalStorageEnabled()) {
411 Notify("session-storage:clear-origin-attributes-data",
412 nsDependentString(aData
));
416 for (const uint32_t id
: {0, 1}) {
417 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
418 if (NS_WARN_IF(!storageChild
)) {
419 return NS_ERROR_FAILURE
;
422 storageChild
->SendClearMatchingOriginAttributes(pattern
);
425 Notify(aTopic
, nsDependentString(aData
));
430 if (!strcmp(aTopic
, "profile-after-change")) {
431 Notify("profile-change");
436 if (!strcmp(aTopic
, "profile-before-change")) {
437 MOZ_ASSERT(XRE_IsParentProcess());
439 if (NextGenLocalStorageEnabled()) {
443 for (const uint32_t id
: {0, 1}) {
444 if (mBackgroundThread
[id
]) {
447 RefPtr
<StorageDBThread::ShutdownRunnable
> shutdownRunnable
=
448 new StorageDBThread::ShutdownRunnable(id
, done
);
449 MOZ_ALWAYS_SUCCEEDS(mBackgroundThread
[id
]->Dispatch(
450 shutdownRunnable
, NS_DISPATCH_NORMAL
));
452 MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
453 "StorageObserver::Observe profile-before-change"_ns
,
454 [&]() { return done
; }));
456 mBackgroundThread
[id
] = nullptr;
463 #ifdef DOM_STORAGE_TESTS
464 if (!strcmp(aTopic
, "domstorage-test-flush-force")) {
465 if (NextGenLocalStorageEnabled()) {
469 for (const uint32_t id
: {0, 1}) {
470 StorageDBChild
* storageChild
= StorageDBChild::GetOrCreate(id
);
471 if (NS_WARN_IF(!storageChild
)) {
472 return NS_ERROR_FAILURE
;
475 storageChild
->SendAsyncFlush();
481 if (!strcmp(aTopic
, "domstorage-test-flushed")) {
482 if (NextGenLocalStorageEnabled()) {
486 // Only used to propagate to IPC children
487 Notify("test-flushed");
492 if (!strcmp(aTopic
, "domstorage-test-reload")) {
493 if (NextGenLocalStorageEnabled()) {
497 Notify("test-reload");
503 NS_ERROR("Unexpected topic");
504 return NS_ERROR_UNEXPECTED
;
508 StorageObserver::GetName(nsACString
& aName
) {
509 aName
.AssignLiteral("StorageObserver");
514 } // namespace mozilla