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 "ServiceWorkerRegistrar.h"
8
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
9
#include "mozilla/dom/DOMException.h"
10
#include "mozilla/net/MozURL.h"
11
12
#include "nsIEventTarget.h"
13
#include "nsIInputStream.h"
14
#include "nsILineInputStream.h"
15
#include "nsIObserverService.h"
16
#include "nsIOutputStream.h"
17
#include "nsISafeOutputStream.h"
18
#include "nsIServiceWorkerManager.h"
19
20
#include "MainThreadUtils.h"
21
#include "mozilla/ClearOnShutdown.h"
22
#include "mozilla/CycleCollectedJSContext.h"
23
#include "mozilla/dom/StorageActivityService.h"
24
#include "mozilla/ErrorNames.h"
25
#include "mozilla/ipc/BackgroundChild.h"
26
#include "mozilla/ipc/BackgroundParent.h"
27
#include "mozilla/ipc/PBackgroundChild.h"
28
#include "mozilla/ModuleUtils.h"
29
#include "mozilla/Services.h"
30
#include "mozilla/StaticPtr.h"
31
#include "mozJSComponentLoader.h"
32
#include "nsAppDirectoryServiceDefs.h"
33
#include "nsContentUtils.h"
34
#include "nsDirectoryServiceUtils.h"
35
#include "nsNetCID.h"
36
#include "nsNetUtil.h"
37
#include "nsServiceManagerUtils.h"
38
#include "nsThreadUtils.h"
39
#include "nsXULAppAPI.h"
40
#include "ServiceWorkerUtils.h"
41
42
using namespace mozilla::ipc;
43
44
namespace mozilla {
45
namespace dom {
46
47
namespace {
48
49
static const char* gSupportedRegistrarVersions[] = {
50
SERVICEWORKERREGISTRAR_VERSION, "7", "6", "5", "4", "3", "2"};
51
52
static const uint32_t kInvalidGeneration = static_cast<uint32_t>(-1);
53
54
StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
55
56
nsresult GetOriginAndBaseDomain(const nsACString& aURL, nsACString& aOrigin,
57
nsACString& aBaseDomain) {
58
RefPtr<net::MozURL> url;
59
nsresult rv = net::MozURL::Init(getter_AddRefs(url), aURL);
60
if (NS_WARN_IF(NS_FAILED(rv))) {
61
return rv;
62
}
63
64
url->Origin(aOrigin);
65
66
rv = url->BaseDomain(aBaseDomain);
67
if (NS_WARN_IF(NS_FAILED(rv))) {
68
return rv;
69
}
70
71
return NS_OK;
72
}
73
74
nsresult ReadLine(nsILineInputStream* aStream, nsACString& aValue) {
75
bool hasMoreLines;
76
nsresult rv = aStream->ReadLine(aValue, &hasMoreLines);
77
if (NS_WARN_IF(NS_FAILED(rv))) {
78
return rv;
79
}
80
81
if (NS_WARN_IF(!hasMoreLines)) {
82
return NS_ERROR_FAILURE;
83
}
84
85
return NS_OK;
86
}
87
88
nsresult CreatePrincipalInfo(nsILineInputStream* aStream,
89
ServiceWorkerRegistrationData* aEntry,
90
bool aSkipSpec = false) {
91
nsAutoCString suffix;
92
nsresult rv = ReadLine(aStream, suffix);
93
if (NS_WARN_IF(NS_FAILED(rv))) {
94
return rv;
95
}
96
97
OriginAttributes attrs;
98
if (!attrs.PopulateFromSuffix(suffix)) {
99
return NS_ERROR_INVALID_ARG;
100
}
101
102
if (aSkipSpec) {
103
nsAutoCString unused;
104
nsresult rv = ReadLine(aStream, unused);
105
if (NS_WARN_IF(NS_FAILED(rv))) {
106
return rv;
107
}
108
}
109
110
rv = ReadLine(aStream, aEntry->scope());
111
if (NS_WARN_IF(NS_FAILED(rv))) {
112
return rv;
113
}
114
115
nsCString origin;
116
nsCString baseDomain;
117
rv = GetOriginAndBaseDomain(aEntry->scope(), origin, baseDomain);
118
if (NS_WARN_IF(NS_FAILED(rv))) {
119
return rv;
120
}
121
122
aEntry->principal() = mozilla::ipc::ContentPrincipalInfo(
123
attrs, origin, aEntry->scope(), Nothing(), baseDomain);
124
125
return NS_OK;
126
}
127
128
} // namespace
129
130
NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar, nsIObserver, nsIAsyncShutdownBlocker)
131
132
void ServiceWorkerRegistrar::Initialize() {
133
MOZ_ASSERT(!gServiceWorkerRegistrar);
134
135
if (!XRE_IsParentProcess()) {
136
return;
137
}
138
139
gServiceWorkerRegistrar = new ServiceWorkerRegistrar();
140
ClearOnShutdown(&gServiceWorkerRegistrar);
141
142
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
143
if (obs) {
144
DebugOnly<nsresult> rv = obs->AddObserver(gServiceWorkerRegistrar,
145
"profile-after-change", false);
146
MOZ_ASSERT(NS_SUCCEEDED(rv));
147
}
148
}
149
150
/* static */
151
already_AddRefed<ServiceWorkerRegistrar> ServiceWorkerRegistrar::Get() {
152
MOZ_ASSERT(XRE_IsParentProcess());
153
154
MOZ_ASSERT(gServiceWorkerRegistrar);
155
RefPtr<ServiceWorkerRegistrar> service = gServiceWorkerRegistrar.get();
156
return service.forget();
157
}
158
159
ServiceWorkerRegistrar::ServiceWorkerRegistrar()
160
: mMonitor("ServiceWorkerRegistrar.mMonitor"),
161
mDataLoaded(false),
162
mDataGeneration(kInvalidGeneration),
163
mFileGeneration(kInvalidGeneration),
164
mRetryCount(0),
165
mShuttingDown(false),
166
mRunnableDispatched(false) {
167
MOZ_ASSERT(NS_IsMainThread());
168
}
169
170
ServiceWorkerRegistrar::~ServiceWorkerRegistrar() {
171
MOZ_ASSERT(!mRunnableDispatched);
172
}
173
174
void ServiceWorkerRegistrar::GetRegistrations(
175
nsTArray<ServiceWorkerRegistrationData>& aValues) {
176
MOZ_ASSERT(NS_IsMainThread());
177
MOZ_ASSERT(aValues.IsEmpty());
178
179
MonitorAutoLock lock(mMonitor);
180
181
// If we don't have the profile directory, profile is not started yet (and
182
// probably we are in a utest).
183
if (!mProfileDir) {
184
return;
185
}
186
187
// We care just about the first execution because this can be blocked by
188
// loading data from disk.
189
static bool firstTime = true;
190
TimeStamp startTime;
191
192
if (firstTime) {
193
startTime = TimeStamp::NowLoRes();
194
}
195
196
// Waiting for data loaded.
197
mMonitor.AssertCurrentThreadOwns();
198
while (!mDataLoaded) {
199
mMonitor.Wait();
200
}
201
202
aValues.AppendElements(mData);
203
204
MaybeResetGeneration();
205
MOZ_DIAGNOSTIC_ASSERT(mDataGeneration != kInvalidGeneration);
206
MOZ_DIAGNOSTIC_ASSERT(mFileGeneration != kInvalidGeneration);
207
208
if (firstTime) {
209
firstTime = false;
210
Telemetry::AccumulateTimeDelta(
211
Telemetry::SERVICE_WORKER_REGISTRATION_LOADING, startTime);
212
}
213
}
214
215
namespace {
216
217
bool Equivalent(const ServiceWorkerRegistrationData& aLeft,
218
const ServiceWorkerRegistrationData& aRight) {
219
MOZ_ASSERT(aLeft.principal().type() ==
220
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
221
MOZ_ASSERT(aRight.principal().type() ==
222
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
223
224
const auto& leftPrincipal = aLeft.principal().get_ContentPrincipalInfo();
225
const auto& rightPrincipal = aRight.principal().get_ContentPrincipalInfo();
226
227
// Only compare the attributes, not the spec part of the principal.
228
// The scope comparison above already covers the origin and codebase
229
// principals include the full path in their spec which is not what
230
// we want here.
231
return aLeft.scope() == aRight.scope() &&
232
leftPrincipal.attrs() == rightPrincipal.attrs();
233
}
234
235
} // anonymous namespace
236
237
void ServiceWorkerRegistrar::RegisterServiceWorker(
238
const ServiceWorkerRegistrationData& aData) {
239
AssertIsOnBackgroundThread();
240
241
if (mShuttingDown) {
242
NS_WARNING("Failed to register a serviceWorker during shutting down.");
243
return;
244
}
245
246
{
247
MonitorAutoLock lock(mMonitor);
248
MOZ_ASSERT(mDataLoaded);
249
RegisterServiceWorkerInternal(aData);
250
}
251
252
MaybeScheduleSaveData();
253
StorageActivityService::SendActivity(aData.principal());
254
}
255
256
void ServiceWorkerRegistrar::UnregisterServiceWorker(
257
const PrincipalInfo& aPrincipalInfo, const nsACString& aScope) {
258
AssertIsOnBackgroundThread();
259
260
if (mShuttingDown) {
261
NS_WARNING("Failed to unregister a serviceWorker during shutting down.");
262
return;
263
}
264
265
bool deleted = false;
266
267
{
268
MonitorAutoLock lock(mMonitor);
269
MOZ_ASSERT(mDataLoaded);
270
271
ServiceWorkerRegistrationData tmp;
272
tmp.principal() = aPrincipalInfo;
273
tmp.scope() = aScope;
274
275
for (uint32_t i = 0; i < mData.Length(); ++i) {
276
if (Equivalent(tmp, mData[i])) {
277
mData.RemoveElementAt(i);
278
mDataGeneration = GetNextGeneration();
279
deleted = true;
280
break;
281
}
282
}
283
}
284
285
if (deleted) {
286
MaybeScheduleSaveData();
287
StorageActivityService::SendActivity(aPrincipalInfo);
288
}
289
}
290
291
void ServiceWorkerRegistrar::RemoveAll() {
292
AssertIsOnBackgroundThread();
293
294
if (mShuttingDown) {
295
NS_WARNING("Failed to remove all the serviceWorkers during shutting down.");
296
return;
297
}
298
299
bool deleted = false;
300
301
nsTArray<ServiceWorkerRegistrationData> data;
302
{
303
MonitorAutoLock lock(mMonitor);
304
MOZ_ASSERT(mDataLoaded);
305
306
// Let's take a copy in order to inform StorageActivityService.
307
data = mData;
308
309
deleted = !mData.IsEmpty();
310
mData.Clear();
311
312
mDataGeneration = GetNextGeneration();
313
}
314
315
if (!deleted) {
316
return;
317
}
318
319
MaybeScheduleSaveData();
320
321
for (uint32_t i = 0, len = data.Length(); i < len; ++i) {
322
StorageActivityService::SendActivity(data[i].principal());
323
}
324
}
325
326
void ServiceWorkerRegistrar::LoadData() {
327
MOZ_ASSERT(!NS_IsMainThread());
328
MOZ_ASSERT(!mDataLoaded);
329
330
nsresult rv = ReadData();
331
332
if (NS_WARN_IF(NS_FAILED(rv))) {
333
DeleteData();
334
// Also if the reading failed we have to notify what is waiting for data.
335
}
336
337
MonitorAutoLock lock(mMonitor);
338
MOZ_ASSERT(!mDataLoaded);
339
mDataLoaded = true;
340
mMonitor.Notify();
341
}
342
343
nsresult ServiceWorkerRegistrar::ReadData() {
344
// We cannot assert about the correct thread because normally this method
345
// runs on a IO thread, but in gTests we call it from the main-thread.
346
347
nsCOMPtr<nsIFile> file;
348
349
{
350
MonitorAutoLock lock(mMonitor);
351
352
if (!mProfileDir) {
353
return NS_ERROR_FAILURE;
354
}
355
356
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
357
if (NS_WARN_IF(NS_FAILED(rv))) {
358
return rv;
359
}
360
}
361
362
nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
363
if (NS_WARN_IF(NS_FAILED(rv))) {
364
return rv;
365
}
366
367
bool exists;
368
rv = file->Exists(&exists);
369
if (NS_WARN_IF(NS_FAILED(rv))) {
370
return rv;
371
}
372
373
if (!exists) {
374
return NS_OK;
375
}
376
377
nsCOMPtr<nsIInputStream> stream;
378
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
379
if (NS_WARN_IF(NS_FAILED(rv))) {
380
return rv;
381
}
382
383
nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream);
384
MOZ_ASSERT(lineInputStream);
385
386
nsAutoCString version;
387
bool hasMoreLines;
388
rv = lineInputStream->ReadLine(version, &hasMoreLines);
389
if (NS_WARN_IF(NS_FAILED(rv))) {
390
return rv;
391
}
392
393
if (!IsSupportedVersion(version)) {
394
nsContentUtils::LogMessageToConsole(
395
nsPrintfCString("Unsupported service worker registrar version: %s",
396
version.get())
397
.get());
398
return NS_ERROR_FAILURE;
399
}
400
401
nsTArray<ServiceWorkerRegistrationData> tmpData;
402
403
bool overwrite = false;
404
bool dedupe = false;
405
while (hasMoreLines) {
406
ServiceWorkerRegistrationData* entry = tmpData.AppendElement();
407
408
#define GET_LINE(x) \
409
rv = lineInputStream->ReadLine(x, &hasMoreLines); \
410
if (NS_WARN_IF(NS_FAILED(rv))) { \
411
return rv; \
412
} \
413
if (NS_WARN_IF(!hasMoreLines)) { \
414
return NS_ERROR_FAILURE; \
415
}
416
417
nsAutoCString line;
418
if (version.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) {
419
rv = CreatePrincipalInfo(lineInputStream, entry);
420
if (NS_WARN_IF(NS_FAILED(rv))) {
421
return rv;
422
}
423
424
GET_LINE(entry->currentWorkerURL());
425
426
nsAutoCString fetchFlag;
427
GET_LINE(fetchFlag);
428
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
429
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
430
return NS_ERROR_INVALID_ARG;
431
}
432
entry->currentWorkerHandlesFetch() =
433
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
434
435
nsAutoCString cacheName;
436
GET_LINE(cacheName);
437
CopyUTF8toUTF16(cacheName, entry->cacheName());
438
439
nsAutoCString updateViaCache;
440
GET_LINE(updateViaCache);
441
entry->updateViaCache() = updateViaCache.ToInteger(&rv, 16);
442
if (NS_WARN_IF(NS_FAILED(rv))) {
443
return rv;
444
} else if (entry->updateViaCache() >
445
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE) {
446
return NS_ERROR_INVALID_ARG;
447
}
448
449
nsAutoCString installedTimeStr;
450
GET_LINE(installedTimeStr);
451
int64_t installedTime = installedTimeStr.ToInteger64(&rv);
452
if (NS_WARN_IF(NS_FAILED(rv))) {
453
return rv;
454
}
455
entry->currentWorkerInstalledTime() = installedTime;
456
457
nsAutoCString activatedTimeStr;
458
GET_LINE(activatedTimeStr);
459
int64_t activatedTime = activatedTimeStr.ToInteger64(&rv);
460
if (NS_WARN_IF(NS_FAILED(rv))) {
461
return rv;
462
}
463
entry->currentWorkerActivatedTime() = activatedTime;
464
465
nsAutoCString lastUpdateTimeStr;
466
GET_LINE(lastUpdateTimeStr);
467
int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv);
468
if (NS_WARN_IF(NS_FAILED(rv))) {
469
return rv;
470
}
471
entry->lastUpdateTime() = lastUpdateTime;
472
} else if (version.EqualsLiteral("7")) {
473
rv = CreatePrincipalInfo(lineInputStream, entry);
474
if (NS_WARN_IF(NS_FAILED(rv))) {
475
return rv;
476
}
477
478
GET_LINE(entry->currentWorkerURL());
479
480
nsAutoCString fetchFlag;
481
GET_LINE(fetchFlag);
482
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
483
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
484
return NS_ERROR_INVALID_ARG;
485
}
486
entry->currentWorkerHandlesFetch() =
487
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
488
489
nsAutoCString cacheName;
490
GET_LINE(cacheName);
491
CopyUTF8toUTF16(cacheName, entry->cacheName());
492
493
nsAutoCString loadFlags;
494
GET_LINE(loadFlags);
495
entry->updateViaCache() =
496
loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL
497
? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL
498
: nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
499
500
if (NS_WARN_IF(NS_FAILED(rv))) {
501
return rv;
502
}
503
504
nsAutoCString installedTimeStr;
505
GET_LINE(installedTimeStr);
506
int64_t installedTime = installedTimeStr.ToInteger64(&rv);
507
if (NS_WARN_IF(NS_FAILED(rv))) {
508
return rv;
509
}
510
entry->currentWorkerInstalledTime() = installedTime;
511
512
nsAutoCString activatedTimeStr;
513
GET_LINE(activatedTimeStr);
514
int64_t activatedTime = activatedTimeStr.ToInteger64(&rv);
515
if (NS_WARN_IF(NS_FAILED(rv))) {
516
return rv;
517
}
518
entry->currentWorkerActivatedTime() = activatedTime;
519
520
nsAutoCString lastUpdateTimeStr;
521
GET_LINE(lastUpdateTimeStr);
522
int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv);
523
if (NS_WARN_IF(NS_FAILED(rv))) {
524
return rv;
525
}
526
entry->lastUpdateTime() = lastUpdateTime;
527
} else if (version.EqualsLiteral("6")) {
528
rv = CreatePrincipalInfo(lineInputStream, entry);
529
if (NS_WARN_IF(NS_FAILED(rv))) {
530
return rv;
531
}
532
533
GET_LINE(entry->currentWorkerURL());
534
535
nsAutoCString fetchFlag;
536
GET_LINE(fetchFlag);
537
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
538
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
539
return NS_ERROR_INVALID_ARG;
540
}
541
entry->currentWorkerHandlesFetch() =
542
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
543
544
nsAutoCString cacheName;
545
GET_LINE(cacheName);
546
CopyUTF8toUTF16(cacheName, entry->cacheName());
547
548
nsAutoCString loadFlags;
549
GET_LINE(loadFlags);
550
entry->updateViaCache() =
551
loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL
552
? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL
553
: nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
554
555
if (NS_WARN_IF(NS_FAILED(rv))) {
556
return rv;
557
}
558
559
entry->currentWorkerInstalledTime() = 0;
560
entry->currentWorkerActivatedTime() = 0;
561
entry->lastUpdateTime() = 0;
562
} else if (version.EqualsLiteral("5")) {
563
overwrite = true;
564
dedupe = true;
565
566
rv = CreatePrincipalInfo(lineInputStream, entry);
567
if (NS_WARN_IF(NS_FAILED(rv))) {
568
return rv;
569
}
570
571
GET_LINE(entry->currentWorkerURL());
572
573
nsAutoCString fetchFlag;
574
GET_LINE(fetchFlag);
575
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
576
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
577
return NS_ERROR_INVALID_ARG;
578
}
579
entry->currentWorkerHandlesFetch() =
580
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
581
582
nsAutoCString cacheName;
583
GET_LINE(cacheName);
584
CopyUTF8toUTF16(cacheName, entry->cacheName());
585
586
entry->updateViaCache() =
587
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
588
589
entry->currentWorkerInstalledTime() = 0;
590
entry->currentWorkerActivatedTime() = 0;
591
entry->lastUpdateTime() = 0;
592
} else if (version.EqualsLiteral("4")) {
593
overwrite = true;
594
dedupe = true;
595
596
rv = CreatePrincipalInfo(lineInputStream, entry);
597
if (NS_WARN_IF(NS_FAILED(rv))) {
598
return rv;
599
}
600
601
GET_LINE(entry->currentWorkerURL());
602
603
// default handlesFetch flag to Enabled
604
entry->currentWorkerHandlesFetch() = true;
605
606
nsAutoCString cacheName;
607
GET_LINE(cacheName);
608
CopyUTF8toUTF16(cacheName, entry->cacheName());
609
610
entry->updateViaCache() =
611
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
612
613
entry->currentWorkerInstalledTime() = 0;
614
entry->currentWorkerActivatedTime() = 0;
615
entry->lastUpdateTime() = 0;
616
} else if (version.EqualsLiteral("3")) {
617
overwrite = true;
618
dedupe = true;
619
620
rv = CreatePrincipalInfo(lineInputStream, entry, true);
621
if (NS_WARN_IF(NS_FAILED(rv))) {
622
return rv;
623
}
624
625
GET_LINE(entry->currentWorkerURL());
626
627
// default handlesFetch flag to Enabled
628
entry->currentWorkerHandlesFetch() = true;
629
630
nsAutoCString cacheName;
631
GET_LINE(cacheName);
632
CopyUTF8toUTF16(cacheName, entry->cacheName());
633
634
entry->updateViaCache() =
635
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
636
637
entry->currentWorkerInstalledTime() = 0;
638
entry->currentWorkerActivatedTime() = 0;
639
entry->lastUpdateTime() = 0;
640
} else if (version.EqualsLiteral("2")) {
641
overwrite = true;
642
dedupe = true;
643
644
rv = CreatePrincipalInfo(lineInputStream, entry, true);
645
if (NS_WARN_IF(NS_FAILED(rv))) {
646
return rv;
647
}
648
649
// scriptSpec is no more used in latest version.
650
nsAutoCString unused;
651
GET_LINE(unused);
652
653
GET_LINE(entry->currentWorkerURL());
654
655
// default handlesFetch flag to Enabled
656
entry->currentWorkerHandlesFetch() = true;
657
658
nsAutoCString cacheName;
659
GET_LINE(cacheName);
660
CopyUTF8toUTF16(cacheName, entry->cacheName());
661
662
// waitingCacheName is no more used in latest version.
663
GET_LINE(unused);
664
665
entry->updateViaCache() =
666
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
667
668
entry->currentWorkerInstalledTime() = 0;
669
entry->currentWorkerActivatedTime() = 0;
670
entry->lastUpdateTime() = 0;
671
} else {
672
MOZ_ASSERT_UNREACHABLE("Should never get here!");
673
}
674
675
#undef GET_LINE
676
677
rv = lineInputStream->ReadLine(line, &hasMoreLines);
678
if (NS_WARN_IF(NS_FAILED(rv))) {
679
return rv;
680
}
681
682
if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) {
683
return NS_ERROR_FAILURE;
684
}
685
}
686
687
stream->Close();
688
689
// XXX: The following code is writing to mData without holding a
690
// monitor lock. This might be ok since this is currently
691
// only called at startup where we block the main thread
692
// preventing further operation until it completes. We should
693
// consider better locking here in the future.
694
695
// Copy data over to mData.
696
for (uint32_t i = 0; i < tmpData.Length(); ++i) {
697
// Older versions could sometimes write out empty, useless entries.
698
// Prune those here.
699
if (!ServiceWorkerRegistrationDataIsValid(tmpData[i])) {
700
continue;
701
}
702
703
bool match = false;
704
if (dedupe) {
705
MOZ_ASSERT(overwrite);
706
// If this is an old profile, then we might need to deduplicate. In
707
// theory this can be removed in the future (Bug 1248449)
708
for (uint32_t j = 0; j < mData.Length(); ++j) {
709
// Use same comparison as RegisterServiceWorker. Scope contains
710
// basic origin information. Combine with any principal attributes.
711
if (Equivalent(tmpData[i], mData[j])) {
712
// Last match wins, just like legacy loading used to do in
713
// the ServiceWorkerManager.
714
mData[j] = tmpData[i];
715
// Dupe found, so overwrite file with reduced list.
716
match = true;
717
break;
718
}
719
}
720
} else {
721
#ifdef DEBUG
722
// Otherwise assert no duplications in debug builds.
723
for (uint32_t j = 0; j < mData.Length(); ++j) {
724
MOZ_ASSERT(!Equivalent(tmpData[i], mData[j]));
725
}
726
#endif
727
}
728
if (!match) {
729
mData.AppendElement(tmpData[i]);
730
}
731
}
732
733
// Overwrite previous version.
734
// Cannot call SaveData directly because gtest uses main-thread.
735
if (overwrite && NS_FAILED(WriteData(mData))) {
736
NS_WARNING("Failed to write data for the ServiceWorker Registations.");
737
DeleteData();
738
}
739
740
return NS_OK;
741
}
742
743
void ServiceWorkerRegistrar::DeleteData() {
744
// We cannot assert about the correct thread because normally this method
745
// runs on a IO thread, but in gTests we call it from the main-thread.
746
747
nsCOMPtr<nsIFile> file;
748
749
{
750
MonitorAutoLock lock(mMonitor);
751
mData.Clear();
752
753
if (!mProfileDir) {
754
return;
755
}
756
757
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
758
if (NS_WARN_IF(NS_FAILED(rv))) {
759
return;
760
}
761
}
762
763
nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
764
if (NS_WARN_IF(NS_FAILED(rv))) {
765
return;
766
}
767
768
rv = file->Remove(false);
769
if (rv == NS_ERROR_FILE_NOT_FOUND) {
770
return;
771
}
772
773
if (NS_WARN_IF(NS_FAILED(rv))) {
774
return;
775
}
776
}
777
778
void ServiceWorkerRegistrar::RegisterServiceWorkerInternal(
779
const ServiceWorkerRegistrationData& aData) {
780
bool found = false;
781
for (uint32_t i = 0, len = mData.Length(); i < len; ++i) {
782
if (Equivalent(aData, mData[i])) {
783
mData[i] = aData;
784
found = true;
785
break;
786
}
787
}
788
789
if (!found) {
790
MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData));
791
mData.AppendElement(aData);
792
}
793
794
mDataGeneration = GetNextGeneration();
795
}
796
797
class ServiceWorkerRegistrarSaveDataRunnable final : public Runnable {
798
nsCOMPtr<nsIEventTarget> mEventTarget;
799
const nsTArray<ServiceWorkerRegistrationData> mData;
800
const uint32_t mGeneration;
801
802
public:
803
ServiceWorkerRegistrarSaveDataRunnable(
804
nsTArray<ServiceWorkerRegistrationData>&& aData, uint32_t aGeneration)
805
: Runnable("dom::ServiceWorkerRegistrarSaveDataRunnable"),
806
mEventTarget(GetCurrentThreadEventTarget()),
807
mData(std::move(aData)),
808
mGeneration(aGeneration) {
809
AssertIsOnBackgroundThread();
810
MOZ_DIAGNOSTIC_ASSERT(mGeneration != kInvalidGeneration);
811
}
812
813
NS_IMETHOD
814
Run() override {
815
RefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get();
816
MOZ_ASSERT(service);
817
818
uint32_t fileGeneration = kInvalidGeneration;
819
820
if (NS_SUCCEEDED(service->SaveData(mData))) {
821
fileGeneration = mGeneration;
822
}
823
824
RefPtr<Runnable> runnable = NewRunnableMethod<uint32_t>(
825
"ServiceWorkerRegistrar::DataSaved", service,
826
&ServiceWorkerRegistrar::DataSaved, fileGeneration);
827
MOZ_ALWAYS_SUCCEEDS(
828
mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
829
830
return NS_OK;
831
}
832
};
833
834
void ServiceWorkerRegistrar::MaybeScheduleSaveData() {
835
AssertIsOnBackgroundThread();
836
MOZ_ASSERT(!mShuttingDown);
837
838
if (mShuttingDown || mRunnableDispatched ||
839
mDataGeneration <= mFileGeneration) {
840
return;
841
}
842
843
nsCOMPtr<nsIEventTarget> target =
844
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
845
MOZ_ASSERT(target, "Must have stream transport service");
846
847
uint32_t generation = kInvalidGeneration;
848
nsTArray<ServiceWorkerRegistrationData> data;
849
850
{
851
MonitorAutoLock lock(mMonitor);
852
generation = mDataGeneration;
853
data.AppendElements(mData);
854
}
855
856
RefPtr<Runnable> runnable =
857
new ServiceWorkerRegistrarSaveDataRunnable(std::move(data), generation);
858
nsresult rv = target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
859
NS_ENSURE_SUCCESS_VOID(rv);
860
861
mRunnableDispatched = true;
862
}
863
864
void ServiceWorkerRegistrar::ShutdownCompleted() {
865
MOZ_ASSERT(NS_IsMainThread());
866
867
DebugOnly<nsresult> rv = GetShutdownPhase()->RemoveBlocker(this);
868
MOZ_ASSERT(NS_SUCCEEDED(rv));
869
}
870
871
nsresult ServiceWorkerRegistrar::SaveData(
872
const nsTArray<ServiceWorkerRegistrationData>& aData) {
873
MOZ_ASSERT(!NS_IsMainThread());
874
875
nsresult rv = WriteData(aData);
876
if (NS_FAILED(rv)) {
877
NS_WARNING("Failed to write data for the ServiceWorker Registations.");
878
// Don't touch the file or in-memory state. Writing files can
879
// sometimes fail due to virus scanning, etc. We should just leave
880
// things as is so the next save operation can pick up any changes
881
// without losing data.
882
}
883
return rv;
884
}
885
886
void ServiceWorkerRegistrar::DataSaved(uint32_t aFileGeneration) {
887
AssertIsOnBackgroundThread();
888
MOZ_ASSERT(mRunnableDispatched);
889
890
mRunnableDispatched = false;
891
892
// Check for shutdown before possibly triggering any more saves
893
// runnables.
894
MaybeScheduleShutdownCompleted();
895
if (mShuttingDown) {
896
return;
897
}
898
899
// If we got a valid generation, then the save was successful.
900
if (aFileGeneration != kInvalidGeneration) {
901
// Update the file generation. We also check to see if we
902
// can reset the generation back to zero if the file and data
903
// are now in sync. This allows us to avoid dealing with wrap
904
// around of the generation count.
905
mFileGeneration = aFileGeneration;
906
MaybeResetGeneration();
907
908
// Successful write resets the retry count.
909
mRetryCount = 0;
910
911
// Possibly schedule another save operation if more data
912
// has come in while processing this one.
913
MaybeScheduleSaveData();
914
915
return;
916
}
917
918
// Otherwise, the save failed since the generation is invalid. We
919
// want to retry the save, but only a limited number of times.
920
static const uint32_t kMaxRetryCount = 2;
921
if (mRetryCount >= kMaxRetryCount) {
922
return;
923
}
924
925
mRetryCount += 1;
926
MaybeScheduleSaveData();
927
}
928
929
void ServiceWorkerRegistrar::MaybeScheduleShutdownCompleted() {
930
AssertIsOnBackgroundThread();
931
932
if (mRunnableDispatched || !mShuttingDown) {
933
return;
934
}
935
936
RefPtr<Runnable> runnable =
937
NewRunnableMethod("dom::ServiceWorkerRegistrar::ShutdownCompleted", this,
938
&ServiceWorkerRegistrar::ShutdownCompleted);
939
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
940
}
941
942
uint32_t ServiceWorkerRegistrar::GetNextGeneration() {
943
uint32_t ret = mDataGeneration + 1;
944
if (ret == kInvalidGeneration) {
945
ret += 1;
946
}
947
return ret;
948
}
949
950
void ServiceWorkerRegistrar::MaybeResetGeneration() {
951
if (mDataGeneration != mFileGeneration) {
952
return;
953
}
954
mDataGeneration = mFileGeneration = 0;
955
}
956
957
bool ServiceWorkerRegistrar::IsSupportedVersion(
958
const nsACString& aVersion) const {
959
uint32_t numVersions = ArrayLength(gSupportedRegistrarVersions);
960
for (uint32_t i = 0; i < numVersions; i++) {
961
if (aVersion.EqualsASCII(gSupportedRegistrarVersions[i])) {
962
return true;
963
}
964
}
965
return false;
966
}
967
968
nsresult ServiceWorkerRegistrar::WriteData(
969
const nsTArray<ServiceWorkerRegistrationData>& aData) {
970
// We cannot assert about the correct thread because normally this method
971
// runs on a IO thread, but in gTests we call it from the main-thread.
972
973
nsCOMPtr<nsIFile> file;
974
975
{
976
MonitorAutoLock lock(mMonitor);
977
978
if (!mProfileDir) {
979
return NS_ERROR_FAILURE;
980
}
981
982
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
983
if (NS_WARN_IF(NS_FAILED(rv))) {
984
return rv;
985
}
986
}
987
988
nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
989
if (NS_WARN_IF(NS_FAILED(rv))) {
990
return rv;
991
}
992
993
nsCOMPtr<nsIOutputStream> stream;
994
rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
995
if (NS_WARN_IF(NS_FAILED(rv))) {
996
return rv;
997
}
998
999
nsAutoCString buffer;
1000
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_VERSION);
1001
buffer.Append('\n');
1002
1003
uint32_t count;
1004
rv = stream->Write(buffer.Data(), buffer.Length(), &count);
1005
if (NS_WARN_IF(NS_FAILED(rv))) {
1006
return rv;
1007
}
1008
1009
if (count != buffer.Length()) {
1010
return NS_ERROR_UNEXPECTED;
1011
}
1012
1013
for (uint32_t i = 0, len = aData.Length(); i < len; ++i) {
1014
// We have an assertion further up the stack, but as a last
1015
// resort avoid writing out broken entries here.
1016
if (!ServiceWorkerRegistrationDataIsValid(aData[i])) {
1017
continue;
1018
}
1019
1020
const mozilla::ipc::PrincipalInfo& info = aData[i].principal();
1021
1022
MOZ_ASSERT(info.type() ==
1023
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
1024
1025
const mozilla::ipc::ContentPrincipalInfo& cInfo =
1026
info.get_ContentPrincipalInfo();
1027
1028
nsAutoCString suffix;
1029
cInfo.attrs().CreateSuffix(suffix);
1030
1031
buffer.Truncate();
1032
buffer.Append(suffix.get());
1033
buffer.Append('\n');
1034
1035
buffer.Append(aData[i].scope());
1036
buffer.Append('\n');
1037
1038
buffer.Append(aData[i].currentWorkerURL());
1039
buffer.Append('\n');
1040
1041
buffer.Append(aData[i].currentWorkerHandlesFetch()
1042
? SERVICEWORKERREGISTRAR_TRUE
1043
: SERVICEWORKERREGISTRAR_FALSE);
1044
buffer.Append('\n');
1045
1046
buffer.Append(NS_ConvertUTF16toUTF8(aData[i].cacheName()));
1047
buffer.Append('\n');
1048
1049
buffer.AppendInt(aData[i].updateViaCache(), 16);
1050
buffer.Append('\n');
1051
MOZ_DIAGNOSTIC_ASSERT(
1052
aData[i].updateViaCache() ==
1053
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS ||
1054
aData[i].updateViaCache() ==
1055
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL ||
1056
aData[i].updateViaCache() ==
1057
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE);
1058
1059
static_assert(nsIRequest::LOAD_NORMAL == 0,
1060
"LOAD_NORMAL matches serialized value.");
1061
static_assert(nsIRequest::VALIDATE_ALWAYS == (1 << 11),
1062
"VALIDATE_ALWAYS matches serialized value");
1063
1064
buffer.AppendInt(aData[i].currentWorkerInstalledTime());
1065
buffer.Append('\n');
1066
1067
buffer.AppendInt(aData[i].currentWorkerActivatedTime());
1068
buffer.Append('\n');
1069
1070
buffer.AppendInt(aData[i].lastUpdateTime());
1071
buffer.Append('\n');
1072
1073
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR);
1074
buffer.Append('\n');
1075
1076
rv = stream->Write(buffer.Data(), buffer.Length(), &count);
1077
if (NS_WARN_IF(NS_FAILED(rv))) {
1078
return rv;
1079
}
1080
1081
if (count != buffer.Length()) {
1082
return NS_ERROR_UNEXPECTED;
1083
}
1084
}
1085
1086
nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
1087
MOZ_ASSERT(safeStream);
1088
1089
rv = safeStream->Finish();
1090
if (NS_WARN_IF(NS_FAILED(rv))) {
1091
return rv;
1092
}
1093
1094
return NS_OK;
1095
}
1096
1097
void ServiceWorkerRegistrar::ProfileStarted() {
1098
MOZ_ASSERT(NS_IsMainThread());
1099
1100
MonitorAutoLock lock(mMonitor);
1101
MOZ_DIAGNOSTIC_ASSERT(!mProfileDir);
1102
1103
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1104
getter_AddRefs(mProfileDir));
1105
if (NS_WARN_IF(NS_FAILED(rv))) {
1106
return;
1107
}
1108
1109
rv = GetShutdownPhase()->AddBlocker(
1110
this, NS_LITERAL_STRING(__FILE__), __LINE__,
1111
NS_LITERAL_STRING("ServiceWorkerRegistrar: Flushing data"));
1112
if (NS_WARN_IF(NS_FAILED(rv))) {
1113
return;
1114
}
1115
1116
nsCOMPtr<nsIEventTarget> target =
1117
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
1118
MOZ_ASSERT(target, "Must have stream transport service");
1119
1120
nsCOMPtr<nsIRunnable> runnable =
1121
NewRunnableMethod("dom::ServiceWorkerRegistrar::LoadData", this,
1122
&ServiceWorkerRegistrar::LoadData);
1123
rv = target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
1124
if (NS_FAILED(rv)) {
1125
NS_WARNING("Failed to dispatch the LoadDataRunnable.");
1126
}
1127
}
1128
1129
void ServiceWorkerRegistrar::ProfileStopped() {
1130
MOZ_ASSERT(NS_IsMainThread());
1131
1132
MonitorAutoLock lock(mMonitor);
1133
1134
if (!mProfileDir) {
1135
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1136
getter_AddRefs(mProfileDir));
1137
if (NS_WARN_IF(NS_FAILED(rv))) {
1138
return;
1139
}
1140
}
1141
1142
PBackgroundChild* child = BackgroundChild::GetForCurrentThread();
1143
if (!child) {
1144
// Mutations to the ServiceWorkerRegistrar happen on the PBackground thread,
1145
// issued by the ServiceWorkerManagerService, so the appropriate place to
1146
// trigger shutdown is on that thread.
1147
//
1148
// However, it's quite possible that the PBackground thread was not brought
1149
// into existence for xpcshell tests. We don't cause it to be created
1150
// ourselves for any reason, for example.
1151
//
1152
// In this scenario, we know that:
1153
// - We will receive exactly one call to ourself from BlockShutdown() and
1154
// BlockShutdown() will be called (at most) once.
1155
// - The only way our Shutdown() method gets called is via
1156
// BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() being
1157
// invoked, which only happens if we get to that send below here that we
1158
// can't get to.
1159
// - All Shutdown() does is set mShuttingDown=true (essential for
1160
// invariants) and invoke MaybeScheduleShutdownCompleted().
1161
// - Since there is no PBackground thread, mRunnableDispatched must be false
1162
// because only MaybeScheduleSaveData() set it and it only runs on the
1163
// background thread, so it cannot have run. And so we would expect
1164
// MaybeScheduleShutdownCompleted() to schedule an invocation of
1165
// ShutdownCompleted on the main thread.
1166
//
1167
// So it's appropriate for us to set mShuttingDown=true (as Shutdown would
1168
// do) and directly invoke ShutdownCompleted() (as Shutdown would indirectly
1169
// do via MaybeScheduleShutdownCompleted).
1170
mShuttingDown = true;
1171
ShutdownCompleted();
1172
return;
1173
}
1174
1175
child->SendShutdownServiceWorkerRegistrar();
1176
}
1177
1178
// Async shutdown blocker methods
1179
1180
NS_IMETHODIMP
1181
ServiceWorkerRegistrar::BlockShutdown(nsIAsyncShutdownClient* aClient) {
1182
ProfileStopped();
1183
return NS_OK;
1184
}
1185
1186
NS_IMETHODIMP
1187
ServiceWorkerRegistrar::GetName(nsAString& aName) {
1188
aName = NS_LITERAL_STRING("ServiceWorkerRegistrar: Flushing data");
1189
return NS_OK;
1190
}
1191
1192
NS_IMETHODIMP
1193
ServiceWorkerRegistrar::GetState(nsIPropertyBag**) { return NS_OK; }
1194
1195
#define RELEASE_ASSERT_SUCCEEDED(rv, name) \
1196
do { \
1197
if (NS_FAILED(rv)) { \
1198
mozJSComponentLoader::Get()->AnnotateCrashReport(); \
1199
if (rv == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) { \
1200
if (auto* context = CycleCollectedJSContext::Get()) { \
1201
if (RefPtr<Exception> exn = context->GetPendingException()) { \
1202
MOZ_CRASH_UNSAFE_PRINTF("Failed to get " name ": %s", \
1203
exn->GetMessageMoz().get()); \
1204
} \
1205
} \
1206
} \
1207
\
1208
nsAutoCString errorName; \
1209
GetErrorName(rv, errorName); \
1210
MOZ_CRASH_UNSAFE_PRINTF("Failed to get " name ": %s", errorName.get()); \
1211
} \
1212
} while (0)
1213
1214
nsCOMPtr<nsIAsyncShutdownClient> ServiceWorkerRegistrar::GetShutdownPhase()
1215
const {
1216
nsresult rv;
1217
nsCOMPtr<nsIAsyncShutdownService> svc =
1218
do_GetService("@mozilla.org/async-shutdown-service;1", &rv);
1219
// If this fails, something is very wrong on the JS side (or we're out of
1220
// memory), and there's no point in continuing startup. Include as much
1221
// information as possible in the crash report.
1222
RELEASE_ASSERT_SUCCEEDED(rv, "async shutdown service");
1223
1224
nsCOMPtr<nsIAsyncShutdownClient> client;
1225
rv = svc->GetProfileBeforeChange(getter_AddRefs(client));
1226
RELEASE_ASSERT_SUCCEEDED(rv, "profileBeforeChange shutdown blocker");
1227
return client;
1228
}
1229
1230
#undef RELEASE_ASSERT_SUCCEEDED
1231
1232
void ServiceWorkerRegistrar::Shutdown() {
1233
AssertIsOnBackgroundThread();
1234
MOZ_ASSERT(!mShuttingDown);
1235
1236
mShuttingDown = true;
1237
MaybeScheduleShutdownCompleted();
1238
}
1239
1240
NS_IMETHODIMP
1241
ServiceWorkerRegistrar::Observe(nsISupports* aSubject, const char* aTopic,
1242
const char16_t* aData) {
1243
MOZ_ASSERT(NS_IsMainThread());
1244
1245
if (!strcmp(aTopic, "profile-after-change")) {
1246
nsCOMPtr<nsIObserverService> observerService =
1247
services::GetObserverService();
1248
observerService->RemoveObserver(this, "profile-after-change");
1249
1250
// The profile is fully loaded, now we can proceed with the loading of data
1251
// from disk.
1252
ProfileStarted();
1253
1254
return NS_OK;
1255
}
1256
1257
MOZ_ASSERT(false, "ServiceWorkerRegistrar got unexpected topic!");
1258
return NS_ERROR_UNEXPECTED;
1259
}
1260
1261
} // namespace dom
1262
} // namespace mozilla