Bug 1708193 - Remove mozapps/extensions/internal/Content.js r=rpl
[gecko.git] / dom / storage / StorageObserver.cpp
blobff88f0f9cbd79b3d4c579df0c481004bedbd82db
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 {
33 namespace dom {
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;
49 // static
50 nsresult StorageObserver::Init() {
51 static_assert(kPrivateBrowsingIdCount * sizeof(mBackgroundThread[0]) ==
52 sizeof(mBackgroundThread));
53 if (sSelf) {
54 return NS_OK;
57 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
58 if (!obs) {
59 return NS_ERROR_UNEXPECTED;
62 sSelf = new StorageObserver();
63 NS_ADDREF(sSelf);
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);
75 // Shutdown
76 obs->AddObserver(sSelf, "profile-after-change", true);
77 if (XRE_IsParentProcess()) {
78 obs->AddObserver(sSelf, "profile-before-change", true);
81 // Testing
82 #ifdef DOM_STORAGE_TESTS
83 Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
84 #endif
86 return NS_OK;
89 // static
90 nsresult StorageObserver::Shutdown() {
91 if (!sSelf) {
92 return NS_ERROR_NOT_INITIALIZED;
95 NS_RELEASE(sSelf);
96 return NS_OK;
99 // static
100 void StorageObserver::TestingPrefChanged(const char* aPrefName,
101 void* aClosure) {
102 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
103 if (!obs) {
104 return;
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);
114 } else {
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) {
149 nsresult rv;
151 NS_ConvertUTF16toUTF8 domain(aData);
153 nsAutoCString convertedDomain;
154 nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
155 if (converter) {
156 // Convert the domain name to the ACE format
157 rv = converter->ConvertUTF8toACE(domain, convertedDomain);
158 } else {
159 // In case the IDN service is not available, this is the best we can come
160 // up with!
161 rv = NS_EscapeURL(domain, esc_OnlyNonASCII | esc_AlwaysCopy,
162 convertedDomain, fallible);
164 if (NS_WARN_IF(NS_FAILED(rv))) {
165 return rv;
168 nsCString originScope;
169 rv = CreateReversedDomain(convertedDomain, originScope);
170 if (NS_WARN_IF(NS_FAILED(rv))) {
171 return rv;
174 aOriginScope = originScope;
175 return NS_OK;
178 NS_IMETHODIMP
179 StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
180 const char16_t* aData) {
181 nsresult rv;
183 // Start the thread that opens the database.
184 if (!strcmp(aTopic, kStartupTopic)) {
185 MOZ_ASSERT(XRE_IsParentProcess());
187 if (NextGenLocalStorageEnabled()) {
188 return NS_OK;
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,
196 kStartupDelay);
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);
205 if (!timer) {
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();
222 return NS_OK;
225 // Clear everything, caches + database
226 if (!strcmp(aTopic, "cookie-changed")) {
227 if (!u"cleared"_ns.Equals(aData)) {
228 return NS_OK;
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");
248 return NS_OK;
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));
256 if (!perm) {
257 return NS_OK;
260 nsAutoCString type;
261 perm->GetType(type);
262 if (type != "cookie"_ns) {
263 return NS_OK;
266 uint32_t cap = 0;
267 perm->GetCapability(&cap);
268 if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
269 !u"deleted"_ns.Equals(nsDependentString(aData))) {
270 return NS_OK;
273 nsCOMPtr<nsIPrincipal> principal;
274 perm->GetPrincipal(getter_AddRefs(principal));
275 if (!principal) {
276 return NS_OK;
279 nsAutoCString originSuffix;
280 BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(
281 originSuffix);
283 nsAutoCString host;
284 principal->GetHost(host);
285 if (host.IsEmpty()) {
286 return NS_OK;
289 nsAutoCString originScope;
290 rv = CreateReversedDomain(host, originScope);
291 NS_ENSURE_SUCCESS(rv, rv);
293 Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
294 originScope);
296 return NS_OK;
299 if (!strcmp(aTopic, "extension:purge-localStorage")) {
300 if (NextGenLocalStorageEnabled()) {
301 return NS_OK;
304 const char topic[] = "extension:purge-localStorage-caches";
306 if (aData) {
307 nsCString originScope;
309 rv = GetOriginScope(aData, originScope);
310 if (NS_WARN_IF(NS_FAILED(rv))) {
311 return 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);
326 } else {
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();
340 Notify(topic);
343 return NS_OK;
346 if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
347 if (aData) {
348 nsCString originScope;
349 rv = GetOriginScope(aData, originScope);
350 if (NS_WARN_IF(NS_FAILED(rv))) {
351 return rv;
354 Notify(aTopic, u""_ns, originScope);
355 } else {
356 Notify(aTopic, u""_ns, ""_ns);
359 return NS_OK;
362 // Clear all private-browsing caches
363 if (!strcmp(aTopic, "last-pb-context-exited")) {
364 if (NextGenLocalStorageEnabled()) {
365 return NS_OK;
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);
396 return NS_OK;
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));
413 return NS_OK;
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));
427 return NS_OK;
430 if (!strcmp(aTopic, "profile-after-change")) {
431 Notify("profile-change");
433 return NS_OK;
436 if (!strcmp(aTopic, "profile-before-change")) {
437 MOZ_ASSERT(XRE_IsParentProcess());
439 if (NextGenLocalStorageEnabled()) {
440 return NS_OK;
443 for (const uint32_t id : {0, 1}) {
444 if (mBackgroundThread[id]) {
445 bool done = false;
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;
460 return NS_OK;
463 #ifdef DOM_STORAGE_TESTS
464 if (!strcmp(aTopic, "domstorage-test-flush-force")) {
465 if (NextGenLocalStorageEnabled()) {
466 return NS_OK;
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();
478 return NS_OK;
481 if (!strcmp(aTopic, "domstorage-test-flushed")) {
482 if (NextGenLocalStorageEnabled()) {
483 return NS_OK;
486 // Only used to propagate to IPC children
487 Notify("test-flushed");
489 return NS_OK;
492 if (!strcmp(aTopic, "domstorage-test-reload")) {
493 if (NextGenLocalStorageEnabled()) {
494 return NS_OK;
497 Notify("test-reload");
499 return NS_OK;
501 #endif
503 NS_ERROR("Unexpected topic");
504 return NS_ERROR_UNEXPECTED;
507 NS_IMETHODIMP
508 StorageObserver::GetName(nsACString& aName) {
509 aName.AssignLiteral("StorageObserver");
510 return NS_OK;
513 } // namespace dom
514 } // namespace mozilla