Source code

Revision control

Other Tools

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/. */
6
7
#include "StorageObserver.h"
8
9
#include "LocalStorageCache.h"
10
#include "StorageDBThread.h"
11
#include "StorageUtils.h"
12
13
#include "mozilla/BasePrincipal.h"
14
#include "nsIObserverService.h"
15
#include "nsIURI.h"
16
#include "nsIPermission.h"
17
#include "nsIIDNService.h"
18
#include "nsICookiePermission.h"
19
20
#include "nsPrintfCString.h"
21
#include "nsXULAppAPI.h"
22
#include "nsEscape.h"
23
#include "nsNetCID.h"
24
#include "mozilla/Preferences.h"
25
#include "mozilla/Services.h"
26
#include "nsServiceManagerUtils.h"
27
28
namespace mozilla {
29
namespace dom {
30
31
using namespace StorageUtils;
32
33
static const char kStartupTopic[] = "sessionstore-windows-restored";
34
static const uint32_t kStartupDelay = 0;
35
36
const char kTestingPref[] = "dom.storage.testing";
37
38
NS_IMPL_ISUPPORTS(StorageObserver, nsIObserver, nsISupportsWeakReference)
39
40
StorageObserver* StorageObserver::sSelf = nullptr;
41
42
// static
43
nsresult StorageObserver::Init() {
44
if (sSelf) {
45
return NS_OK;
46
}
47
48
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
49
if (!obs) {
50
return NS_ERROR_UNEXPECTED;
51
}
52
53
sSelf = new StorageObserver();
54
NS_ADDREF(sSelf);
55
56
// Chrome clear operations.
57
obs->AddObserver(sSelf, kStartupTopic, true);
58
obs->AddObserver(sSelf, "cookie-changed", true);
59
obs->AddObserver(sSelf, "perm-changed", true);
60
obs->AddObserver(sSelf, "last-pb-context-exited", true);
61
obs->AddObserver(sSelf, "clear-origin-attributes-data", true);
62
obs->AddObserver(sSelf, "extension:purge-localStorage", true);
63
obs->AddObserver(sSelf, "browser:purge-sessionStorage", true);
64
65
// Shutdown
66
obs->AddObserver(sSelf, "profile-after-change", true);
67
if (XRE_IsParentProcess()) {
68
obs->AddObserver(sSelf, "profile-before-change", true);
69
}
70
71
// Testing
72
#ifdef DOM_STORAGE_TESTS
73
Preferences::RegisterCallbackAndCall(TestingPrefChanged, kTestingPref);
74
#endif
75
76
return NS_OK;
77
}
78
79
// static
80
nsresult StorageObserver::Shutdown() {
81
if (!sSelf) {
82
return NS_ERROR_NOT_INITIALIZED;
83
}
84
85
NS_RELEASE(sSelf);
86
return NS_OK;
87
}
88
89
// static
90
void StorageObserver::TestingPrefChanged(const char* aPrefName,
91
void* aClosure) {
92
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
93
if (!obs) {
94
return;
95
}
96
97
if (Preferences::GetBool(kTestingPref)) {
98
obs->AddObserver(sSelf, "domstorage-test-flush-force", true);
99
if (XRE_IsParentProcess()) {
100
// Only to forward to child process.
101
obs->AddObserver(sSelf, "domstorage-test-flushed", true);
102
}
103
obs->AddObserver(sSelf, "domstorage-test-reload", true);
104
} else {
105
obs->RemoveObserver(sSelf, "domstorage-test-flush-force");
106
if (XRE_IsParentProcess()) {
107
// Only to forward to child process.
108
obs->RemoveObserver(sSelf, "domstorage-test-flushed");
109
}
110
obs->RemoveObserver(sSelf, "domstorage-test-reload");
111
}
112
}
113
114
void StorageObserver::AddSink(StorageObserverSink* aObs) {
115
mSinks.AppendElement(aObs);
116
}
117
118
void StorageObserver::RemoveSink(StorageObserverSink* aObs) {
119
mSinks.RemoveElement(aObs);
120
}
121
122
void StorageObserver::Notify(const char* aTopic,
123
const nsAString& aOriginAttributesPattern,
124
const nsACString& aOriginScope) {
125
nsTObserverArray<StorageObserverSink*>::ForwardIterator iter(mSinks);
126
while (iter.HasMore()) {
127
StorageObserverSink* sink = iter.GetNext();
128
sink->Observe(aTopic, aOriginAttributesPattern, aOriginScope);
129
}
130
}
131
132
void StorageObserver::NoteBackgroundThread(nsIEventTarget* aBackgroundThread) {
133
mBackgroundThread = aBackgroundThread;
134
}
135
136
nsresult StorageObserver::GetOriginScope(const char16_t* aData,
137
nsACString& aOriginScope) {
138
nsresult rv;
139
140
NS_ConvertUTF16toUTF8 domain(aData);
141
142
nsAutoCString convertedDomain;
143
nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID);
144
if (converter) {
145
// Convert the domain name to the ACE format
146
rv = converter->ConvertUTF8toACE(domain, convertedDomain);
147
} else {
148
// In case the IDN service is not available, this is the best we can come
149
// up with!
150
rv = NS_EscapeURL(domain, esc_OnlyNonASCII | esc_AlwaysCopy,
151
convertedDomain, fallible);
152
}
153
if (NS_WARN_IF(NS_FAILED(rv))) {
154
return rv;
155
}
156
157
nsCString originScope;
158
rv = CreateReversedDomain(convertedDomain, originScope);
159
if (NS_WARN_IF(NS_FAILED(rv))) {
160
return rv;
161
}
162
163
aOriginScope = originScope;
164
return NS_OK;
165
}
166
167
NS_IMETHODIMP
168
StorageObserver::Observe(nsISupports* aSubject, const char* aTopic,
169
const char16_t* aData) {
170
nsresult rv;
171
172
// Start the thread that opens the database.
173
if (!strcmp(aTopic, kStartupTopic)) {
174
MOZ_ASSERT(XRE_IsParentProcess());
175
176
if (NextGenLocalStorageEnabled()) {
177
return NS_OK;
178
}
179
180
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
181
obs->RemoveObserver(this, kStartupTopic);
182
183
return NS_NewTimerWithObserver(getter_AddRefs(mDBThreadStartDelayTimer),
184
this, nsITimer::TYPE_ONE_SHOT,
185
kStartupDelay);
186
}
187
188
// Timer callback used to start the database a short timer after startup
189
if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
190
MOZ_ASSERT(XRE_IsParentProcess());
191
MOZ_ASSERT(!NextGenLocalStorageEnabled());
192
193
nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
194
if (!timer) {
195
return NS_ERROR_UNEXPECTED;
196
}
197
198
if (timer == mDBThreadStartDelayTimer) {
199
mDBThreadStartDelayTimer = nullptr;
200
201
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
202
if (NS_WARN_IF(!storageChild)) {
203
return NS_ERROR_FAILURE;
204
}
205
206
storageChild->SendStartup();
207
}
208
209
return NS_OK;
210
}
211
212
// Clear everything, caches + database
213
if (!strcmp(aTopic, "cookie-changed")) {
214
if (!NS_LITERAL_STRING("cleared").Equals(aData)) {
215
return NS_OK;
216
}
217
218
if (!NextGenLocalStorageEnabled()) {
219
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
220
if (NS_WARN_IF(!storageChild)) {
221
return NS_ERROR_FAILURE;
222
}
223
224
storageChild->AsyncClearAll();
225
226
if (XRE_IsParentProcess()) {
227
storageChild->SendClearAll();
228
}
229
}
230
231
Notify("cookie-cleared");
232
233
return NS_OK;
234
}
235
236
// Clear from caches everything that has been stored
237
// while in session-only mode
238
if (!strcmp(aTopic, "perm-changed")) {
239
// Check for cookie permission change
240
nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
241
if (!perm) {
242
return NS_OK;
243
}
244
245
nsAutoCString type;
246
perm->GetType(type);
247
if (type != NS_LITERAL_CSTRING("cookie")) {
248
return NS_OK;
249
}
250
251
uint32_t cap = 0;
252
perm->GetCapability(&cap);
253
if (!(cap & nsICookiePermission::ACCESS_SESSION) ||
254
!NS_LITERAL_STRING("deleted").Equals(nsDependentString(aData))) {
255
return NS_OK;
256
}
257
258
nsCOMPtr<nsIPrincipal> principal;
259
perm->GetPrincipal(getter_AddRefs(principal));
260
if (!principal) {
261
return NS_OK;
262
}
263
264
nsAutoCString originSuffix;
265
BasePrincipal::Cast(principal)->OriginAttributesRef().CreateSuffix(
266
originSuffix);
267
268
nsCOMPtr<nsIURI> origin;
269
principal->GetURI(getter_AddRefs(origin));
270
if (!origin) {
271
return NS_OK;
272
}
273
274
nsAutoCString host;
275
origin->GetHost(host);
276
if (host.IsEmpty()) {
277
return NS_OK;
278
}
279
280
nsAutoCString originScope;
281
rv = CreateReversedDomain(host, originScope);
282
NS_ENSURE_SUCCESS(rv, rv);
283
284
Notify("session-only-cleared", NS_ConvertUTF8toUTF16(originSuffix),
285
originScope);
286
287
return NS_OK;
288
}
289
290
if (!strcmp(aTopic, "extension:purge-localStorage")) {
291
if (NextGenLocalStorageEnabled()) {
292
return NS_OK;
293
}
294
295
const char topic[] = "extension:purge-localStorage-caches";
296
297
if (aData) {
298
nsCString originScope;
299
300
rv = GetOriginScope(aData, originScope);
301
if (NS_WARN_IF(NS_FAILED(rv))) {
302
return rv;
303
}
304
305
if (XRE_IsParentProcess()) {
306
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
307
if (NS_WARN_IF(!storageChild)) {
308
return NS_ERROR_FAILURE;
309
}
310
311
storageChild->SendClearMatchingOrigin(originScope);
312
}
313
314
Notify(topic, EmptyString(), originScope);
315
} else {
316
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
317
if (NS_WARN_IF(!storageChild)) {
318
return NS_ERROR_FAILURE;
319
}
320
321
storageChild->AsyncClearAll();
322
323
if (XRE_IsParentProcess()) {
324
storageChild->SendClearAll();
325
}
326
327
Notify(topic);
328
}
329
330
return NS_OK;
331
}
332
333
if (!strcmp(aTopic, "browser:purge-sessionStorage")) {
334
if (aData) {
335
nsCString originScope;
336
rv = GetOriginScope(aData, originScope);
337
if (NS_WARN_IF(NS_FAILED(rv))) {
338
return rv;
339
}
340
341
Notify(aTopic, EmptyString(), originScope);
342
} else {
343
Notify(aTopic, EmptyString(), EmptyCString());
344
}
345
346
return NS_OK;
347
}
348
349
// Clear all private-browsing caches
350
if (!strcmp(aTopic, "last-pb-context-exited")) {
351
if (NextGenLocalStorageEnabled()) {
352
return NS_OK;
353
}
354
355
Notify("private-browsing-data-cleared");
356
357
return NS_OK;
358
}
359
360
// Clear data of the origins whose prefixes will match the suffix.
361
if (!strcmp(aTopic, "clear-origin-attributes-data")) {
362
MOZ_ASSERT(XRE_IsParentProcess());
363
364
if (NextGenLocalStorageEnabled()) {
365
return NS_OK;
366
}
367
368
OriginAttributesPattern pattern;
369
if (!pattern.Init(nsDependentString(aData))) {
370
NS_ERROR("Cannot parse origin attributes pattern");
371
return NS_ERROR_FAILURE;
372
}
373
374
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
375
if (NS_WARN_IF(!storageChild)) {
376
return NS_ERROR_FAILURE;
377
}
378
379
storageChild->SendClearMatchingOriginAttributes(pattern);
380
381
Notify("origin-attr-pattern-cleared", nsDependentString(aData));
382
383
return NS_OK;
384
}
385
386
if (!strcmp(aTopic, "profile-after-change")) {
387
Notify("profile-change");
388
389
return NS_OK;
390
}
391
392
if (!strcmp(aTopic, "profile-before-change")) {
393
MOZ_ASSERT(XRE_IsParentProcess());
394
395
if (NextGenLocalStorageEnabled()) {
396
return NS_OK;
397
}
398
399
if (mBackgroundThread) {
400
bool done = false;
401
402
RefPtr<StorageDBThread::ShutdownRunnable> shutdownRunnable =
403
new StorageDBThread::ShutdownRunnable(done);
404
MOZ_ALWAYS_SUCCEEDS(
405
mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
406
407
MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
408
409
mBackgroundThread = nullptr;
410
}
411
412
return NS_OK;
413
}
414
415
#ifdef DOM_STORAGE_TESTS
416
if (!strcmp(aTopic, "domstorage-test-flush-force")) {
417
if (NextGenLocalStorageEnabled()) {
418
return NS_OK;
419
}
420
421
StorageDBChild* storageChild = StorageDBChild::GetOrCreate();
422
if (NS_WARN_IF(!storageChild)) {
423
return NS_ERROR_FAILURE;
424
}
425
426
storageChild->SendAsyncFlush();
427
428
return NS_OK;
429
}
430
431
if (!strcmp(aTopic, "domstorage-test-flushed")) {
432
if (NextGenLocalStorageEnabled()) {
433
return NS_OK;
434
}
435
436
// Only used to propagate to IPC children
437
Notify("test-flushed");
438
439
return NS_OK;
440
}
441
442
if (!strcmp(aTopic, "domstorage-test-reload")) {
443
if (NextGenLocalStorageEnabled()) {
444
return NS_OK;
445
}
446
447
Notify("test-reload");
448
449
return NS_OK;
450
}
451
#endif
452
453
NS_ERROR("Unexpected topic");
454
return NS_ERROR_UNEXPECTED;
455
}
456
457
} // namespace dom
458
} // namespace mozilla