1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DOMStorageObserver.h"
8 #include "DOMStorageDBThread.h"
9 #include "DOMStorageCache.h"
11 #include "nsIObserverService.h"
14 #include "nsIScriptSecurityManager.h"
15 #include "nsIPermission.h"
16 #include "nsIIDNService.h"
17 #include "mozIApplicationClearPrivateDataParams.h"
18 #include "nsICookiePermission.h"
20 #include "nsPrintfCString.h"
21 #include "nsXULAppAPI.h"
24 #include "mozilla/Services.h"
25 #include "nsServiceManagerUtils.h"
30 static const char kStartupTopic
[] = "sessionstore-windows-restored";
31 static const uint32_t kStartupDelay
= 0;
33 NS_IMPL_ISUPPORTS(DOMStorageObserver
,
35 nsISupportsWeakReference
)
37 DOMStorageObserver
* DOMStorageObserver::sSelf
= nullptr;
40 CreateReversedDomain(const nsACString
& aAsciiDomain
, nsACString
& aKey
);
44 DOMStorageObserver::Init()
50 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
52 return NS_ERROR_UNEXPECTED
;
55 sSelf
= new DOMStorageObserver();
58 // Chrome clear operations.
59 obs
->AddObserver(sSelf
, kStartupTopic
, true);
60 obs
->AddObserver(sSelf
, "cookie-changed", true);
61 obs
->AddObserver(sSelf
, "perm-changed", true);
62 obs
->AddObserver(sSelf
, "browser:purge-domain-data", true);
63 obs
->AddObserver(sSelf
, "last-pb-context-exited", true);
64 obs
->AddObserver(sSelf
, "webapps-clear-data", true);
67 obs
->AddObserver(sSelf
, "profile-after-change", true);
68 obs
->AddObserver(sSelf
, "profile-before-change", true);
69 obs
->AddObserver(sSelf
, "xpcom-shutdown", true);
71 // Observe low device storage notifications.
72 obs
->AddObserver(sSelf
, "disk-space-watcher", true);
74 #ifdef DOM_STORAGE_TESTS
76 obs
->AddObserver(sSelf
, "domstorage-test-flush-force", true);
77 if (XRE_GetProcessType() == GeckoProcessType_Default
) {
78 // Only to forward to child process.
79 obs
->AddObserver(sSelf
, "domstorage-test-flushed", true);
82 obs
->AddObserver(sSelf
, "domstorage-test-reload", true);
90 DOMStorageObserver::Shutdown()
93 return NS_ERROR_NOT_INITIALIZED
;
101 DOMStorageObserver::AddSink(DOMStorageObserverSink
* aObs
)
103 mSinks
.AppendElement(aObs
);
107 DOMStorageObserver::RemoveSink(DOMStorageObserverSink
* aObs
)
109 mSinks
.RemoveElement(aObs
);
113 DOMStorageObserver::Notify(const char* aTopic
, const nsACString
& aData
)
115 for (uint32_t i
= 0; i
< mSinks
.Length(); ++i
) {
116 DOMStorageObserverSink
* sink
= mSinks
[i
];
117 sink
->Observe(aTopic
, aData
);
122 DOMStorageObserver::Observe(nsISupports
* aSubject
,
124 const char16_t
* aData
)
128 // Start the thread that opens the database.
129 if (!strcmp(aTopic
, kStartupTopic
)) {
130 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
131 obs
->RemoveObserver(this, kStartupTopic
);
133 mDBThreadStartDelayTimer
= do_CreateInstance(NS_TIMER_CONTRACTID
);
134 if (!mDBThreadStartDelayTimer
) {
135 return NS_ERROR_UNEXPECTED
;
138 mDBThreadStartDelayTimer
->Init(this, nsITimer::TYPE_ONE_SHOT
, kStartupDelay
);
143 // Timer callback used to start the database a short timer after startup
144 if (!strcmp(aTopic
, NS_TIMER_CALLBACK_TOPIC
)) {
145 nsCOMPtr
<nsITimer
> timer
= do_QueryInterface(aSubject
);
147 return NS_ERROR_UNEXPECTED
;
150 if (timer
== mDBThreadStartDelayTimer
) {
151 mDBThreadStartDelayTimer
= nullptr;
153 DOMStorageDBBridge
* db
= DOMStorageCache::StartDatabase();
154 NS_ENSURE_TRUE(db
, NS_ERROR_FAILURE
);
160 // Clear everything, caches + database
161 if (!strcmp(aTopic
, "cookie-changed")) {
162 if (!NS_LITERAL_STRING("cleared").Equals(aData
)) {
166 DOMStorageDBBridge
* db
= DOMStorageCache::StartDatabase();
167 NS_ENSURE_TRUE(db
, NS_ERROR_FAILURE
);
171 Notify("cookie-cleared");
176 // Clear from caches everything that has been stored
177 // while in session-only mode
178 if (!strcmp(aTopic
, "perm-changed")) {
179 // Check for cookie permission change
180 nsCOMPtr
<nsIPermission
> perm(do_QueryInterface(aSubject
));
187 if (type
!= NS_LITERAL_CSTRING("cookie")) {
192 perm
->GetCapability(&cap
);
193 if (!(cap
& nsICookiePermission::ACCESS_SESSION
) ||
194 !NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData
))) {
200 if (host
.IsEmpty()) {
205 rv
= CreateReversedDomain(host
, scope
);
206 NS_ENSURE_SUCCESS(rv
, rv
);
208 Notify("session-only-cleared", scope
);
213 // Clear everything (including so and pb data) from caches and database
214 // for the gived domain and subdomains.
215 if (!strcmp(aTopic
, "browser:purge-domain-data")) {
216 // Convert the domain name to the ACE format
217 nsAutoCString aceDomain
;
218 nsCOMPtr
<nsIIDNService
> converter
= do_GetService(NS_IDNSERVICE_CONTRACTID
);
220 rv
= converter
->ConvertUTF8toACE(NS_ConvertUTF16toUTF8(aData
), aceDomain
);
221 NS_ENSURE_SUCCESS(rv
, rv
);
223 // In case the IDN service is not available, this is the best we can come up with!
224 NS_EscapeURL(NS_ConvertUTF16toUTF8(aData
),
225 esc_OnlyNonASCII
| esc_AlwaysCopy
,
229 nsAutoCString scopePrefix
;
230 rv
= CreateReversedDomain(aceDomain
, scopePrefix
);
231 NS_ENSURE_SUCCESS(rv
, rv
);
233 DOMStorageDBBridge
* db
= DOMStorageCache::StartDatabase();
234 NS_ENSURE_TRUE(db
, NS_ERROR_FAILURE
);
236 db
->AsyncClearMatchingScope(scopePrefix
);
238 Notify("domain-data-cleared", scopePrefix
);
243 // Clear all private-browsing caches
244 if (!strcmp(aTopic
, "last-pb-context-exited")) {
245 Notify("private-browsing-data-cleared");
250 // Clear data beloging to an app.
251 if (!strcmp(aTopic
, "webapps-clear-data")) {
252 nsCOMPtr
<mozIApplicationClearPrivateDataParams
> params
=
253 do_QueryInterface(aSubject
);
255 NS_ERROR("'webapps-clear-data' notification's subject should be a mozIApplicationClearPrivateDataParams");
256 return NS_ERROR_UNEXPECTED
;
262 rv
= params
->GetAppId(&appId
);
263 NS_ENSURE_SUCCESS(rv
, rv
);
265 rv
= params
->GetBrowserOnly(&browserOnly
);
266 NS_ENSURE_SUCCESS(rv
, rv
);
268 MOZ_ASSERT(appId
!= nsIScriptSecurityManager::UNKNOWN_APP_ID
);
270 DOMStorageDBBridge
* db
= DOMStorageCache::StartDatabase();
271 NS_ENSURE_TRUE(db
, NS_ERROR_FAILURE
);
274 scope
.AppendInt(appId
);
275 scope
.AppendLiteral(":t:");
276 db
->AsyncClearMatchingScope(scope
);
277 Notify("app-data-cleared", scope
);
281 scope
.AppendInt(appId
);
282 scope
.AppendLiteral(":f:");
283 db
->AsyncClearMatchingScope(scope
);
284 Notify("app-data-cleared", scope
);
290 if (!strcmp(aTopic
, "profile-after-change")) {
291 Notify("profile-change");
296 if (!strcmp(aTopic
, "profile-before-change") ||
297 !strcmp(aTopic
, "xpcom-shutdown")) {
298 rv
= DOMStorageCache::StopDatabase();
300 NS_WARNING("Error while stopping DOMStorage DB background thread");
306 if (!strcmp(aTopic
, "disk-space-watcher")) {
307 if (NS_LITERAL_STRING("full").Equals(aData
)) {
308 Notify("low-disk-space");
309 } else if (NS_LITERAL_STRING("free").Equals(aData
)) {
310 Notify("no-low-disk-space");
316 #ifdef DOM_STORAGE_TESTS
317 if (!strcmp(aTopic
, "domstorage-test-flush-force")) {
318 DOMStorageDBBridge
* db
= DOMStorageCache::GetDatabase();
326 if (!strcmp(aTopic
, "domstorage-test-flushed")) {
327 // Only used to propagate to IPC children
328 Notify("test-flushed");
333 if (!strcmp(aTopic
, "domstorage-test-reload")) {
334 Notify("test-reload");
340 NS_ERROR("Unexpected topic");
341 return NS_ERROR_UNEXPECTED
;