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"
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;
49 nsresult
StorageObserver::Init() {
50 static_assert(kPrivateBrowsingIdCount
* sizeof(mBackgroundThread
[0]) ==
51 sizeof(mBackgroundThread
));
56 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
58 return NS_ERROR_UNEXPECTED
;
61 sSelf
= new StorageObserver();
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);
75 obs
->AddObserver(sSelf
, "profile-after-change", true);
76 if (XRE_IsParentProcess()) {
77 obs
->AddObserver(sSelf
, "profile-before-change", true);
81 #ifdef DOM_STORAGE_TESTS
82 Preferences::RegisterCallbackAndCall(TestingPrefChanged
, kTestingPref
);
89 nsresult
StorageObserver::Shutdown() {
90 AssertIsOnMainThread();
93 return NS_ERROR_NOT_INITIALIZED
; // Is this always an error?
96 sSelf
->mSinks
.Clear();
103 void StorageObserver::TestingPrefChanged(const char* aPrefName
,
105 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
106 if (!obs
|| !sSelf
) {
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);
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();
132 mSinks
.AppendElement(aObs
);
135 void StorageObserver::RemoveSink(StorageObserverSink
* aObs
) {
136 AssertIsOnMainThread();
140 mSinks
.RemoveElement(aObs
);
143 void StorageObserver::Notify(const char* aTopic
,
144 const nsAString
& aOriginAttributesPattern
,
145 const nsACString
& aOriginScope
) {
146 AssertIsOnMainThread();
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
) {
166 NS_ConvertUTF16toUTF8
domain(aData
);
168 nsAutoCString convertedDomain
;
169 nsCOMPtr
<nsIIDNService
> converter
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
171 // Convert the domain name to the ACE format
172 rv
= converter
->ConvertUTF8toACE(domain
, convertedDomain
);
174 // In case the IDN service is not available, this is the best we can come
176 rv
= NS_EscapeURL(domain
, esc_OnlyNonASCII
| esc_AlwaysCopy
,
177 convertedDomain
, fallible
);
179 if (NS_WARN_IF(NS_FAILED(rv
))) {
183 nsCString originScope
;
184 rv
= CreateReversedDomain(convertedDomain
, originScope
);
185 if (NS_WARN_IF(NS_FAILED(rv
))) {
189 aOriginScope
= originScope
;
194 StorageObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
195 const char16_t
* aData
) {
196 if (NS_WARN_IF(!sSelf
)) { // Shutdown took place
202 // Start the thread that opens the database.
203 if (!strcmp(aTopic
, kStartupTopic
)) {
204 MOZ_ASSERT(XRE_IsParentProcess());
206 if (NextGenLocalStorageEnabled()) {
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
,
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
);
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();
244 // Clear everything, caches + database
245 if (!strcmp(aTopic
, "cookie-changed")) {
246 if (!u
"cleared"_ns
.Equals(aData
)) {
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");
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
));
281 if (type
!= "cookie"_ns
) {
286 perm
->GetCapability(&cap
);
287 if (!(cap
& nsICookiePermission::ACCESS_SESSION
) ||
288 !u
"deleted"_ns
.Equals(nsDependentString(aData
))) {
292 nsCOMPtr
<nsIPrincipal
> principal
;
293 perm
->GetPrincipal(getter_AddRefs(principal
));
298 nsAutoCString originSuffix
;
299 BasePrincipal::Cast(principal
)->OriginAttributesRef().CreateSuffix(
303 principal
->GetHost(host
);
304 if (host
.IsEmpty()) {
308 nsAutoCString originScope
;
309 rv
= CreateReversedDomain(host
, originScope
);
310 NS_ENSURE_SUCCESS(rv
, rv
);
312 Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix
),
318 if (!strcmp(aTopic
, "extension:purge-localStorage")) {
319 if (NextGenLocalStorageEnabled()) {
323 const char topic
[] = "extension:purge-localStorage-caches";
326 nsCString originScope
;
328 rv
= GetOriginScope(aData
, originScope
);
329 if (NS_WARN_IF(NS_FAILED(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
);
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();
365 if (!strcmp(aTopic
, "browser:purge-sessionStorage")) {
367 nsCString originScope
;
368 rv
= GetOriginScope(aData
, originScope
);
369 if (NS_WARN_IF(NS_FAILED(rv
))) {
373 Notify(aTopic
, u
""_ns
, originScope
);
375 Notify(aTopic
, u
""_ns
, ""_ns
);
381 // Clear all private-browsing caches
382 if (!strcmp(aTopic
, "last-pb-context-exited")) {
383 if (NextGenLocalStorageEnabled()) {
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
);
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
));
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
));
449 if (!strcmp(aTopic
, "profile-after-change")) {
450 Notify("profile-change");
455 if (!strcmp(aTopic
, "profile-before-change")) {
456 MOZ_ASSERT(XRE_IsParentProcess());
458 if (NextGenLocalStorageEnabled()) {
462 for (const uint32_t id
: {0, 1}) {
463 if (mBackgroundThread
[id
]) {
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;
482 #ifdef DOM_STORAGE_TESTS
483 if (!strcmp(aTopic
, "domstorage-test-flush-force")) {
484 if (NextGenLocalStorageEnabled()) {
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();
500 if (!strcmp(aTopic
, "domstorage-test-flushed")) {
501 if (NextGenLocalStorageEnabled()) {
505 // Only used to propagate to IPC children
506 Notify("test-flushed");
511 if (!strcmp(aTopic
, "domstorage-test-reload")) {
512 if (NextGenLocalStorageEnabled()) {
516 Notify("test-reload");
522 NS_ERROR("Unexpected topic");
523 return NS_ERROR_UNEXPECTED
;
527 StorageObserver::GetName(nsACString
& aName
) {
528 aName
.AssignLiteral("StorageObserver");
532 } // namespace mozilla::dom