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 "ServiceWorkerRegistration.h"
8
9
#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
10
#include "mozilla/dom/Notification.h"
11
#include "mozilla/dom/Promise.h"
12
#include "mozilla/dom/PushManager.h"
13
#include "mozilla/dom/ServiceWorker.h"
14
#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
15
#include "mozilla/dom/ServiceWorkerUtils.h"
16
#include "mozilla/dom/WorkerPrivate.h"
17
#include "nsCycleCollectionParticipant.h"
18
#include "nsPIDOMWindow.h"
19
#include "RemoteServiceWorkerRegistrationImpl.h"
20
#include "ServiceWorkerRegistrationImpl.h"
21
22
namespace mozilla {
23
namespace dom {
24
25
NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerRegistration,
26
DOMEventTargetHelper, mInstallingWorker,
27
mWaitingWorker, mActiveWorker, mPushManager);
28
29
NS_IMPL_ADDREF_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
30
NS_IMPL_RELEASE_INHERITED(ServiceWorkerRegistration, DOMEventTargetHelper)
31
32
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerRegistration)
33
NS_INTERFACE_MAP_ENTRY(ServiceWorkerRegistration)
34
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
35
36
namespace {
37
const uint64_t kInvalidUpdateFoundId = 0;
38
} // anonymous namespace
39
40
ServiceWorkerRegistration::ServiceWorkerRegistration(
41
nsIGlobalObject* aGlobal,
42
const ServiceWorkerRegistrationDescriptor& aDescriptor,
43
ServiceWorkerRegistration::Inner* aInner)
44
: DOMEventTargetHelper(aGlobal),
45
mDescriptor(aDescriptor),
46
mInner(aInner),
47
mScheduledUpdateFoundId(kInvalidUpdateFoundId),
48
mDispatchedUpdateFoundId(kInvalidUpdateFoundId) {
49
MOZ_DIAGNOSTIC_ASSERT(mInner);
50
51
KeepAliveIfHasListenersFor(NS_LITERAL_STRING("updatefound"));
52
53
UpdateState(mDescriptor);
54
mInner->SetServiceWorkerRegistration(this);
55
}
56
57
ServiceWorkerRegistration::~ServiceWorkerRegistration() {
58
mInner->ClearServiceWorkerRegistration(this);
59
}
60
61
JSObject* ServiceWorkerRegistration::WrapObject(
62
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
63
return ServiceWorkerRegistration_Binding::Wrap(aCx, this, aGivenProto);
64
}
65
66
/* static */
67
already_AddRefed<ServiceWorkerRegistration>
68
ServiceWorkerRegistration::CreateForMainThread(
69
nsPIDOMWindowInner* aWindow,
70
const ServiceWorkerRegistrationDescriptor& aDescriptor) {
71
MOZ_ASSERT(aWindow);
72
MOZ_ASSERT(NS_IsMainThread());
73
74
RefPtr<Inner> inner;
75
if (ServiceWorkerParentInterceptEnabled()) {
76
inner = new RemoteServiceWorkerRegistrationImpl(aDescriptor);
77
} else {
78
inner = new ServiceWorkerRegistrationMainThread(aDescriptor);
79
}
80
NS_ENSURE_TRUE(inner, nullptr);
81
82
RefPtr<ServiceWorkerRegistration> registration =
83
new ServiceWorkerRegistration(aWindow->AsGlobal(), aDescriptor, inner);
84
85
return registration.forget();
86
}
87
88
/* static */
89
already_AddRefed<ServiceWorkerRegistration>
90
ServiceWorkerRegistration::CreateForWorker(
91
WorkerPrivate* aWorkerPrivate, nsIGlobalObject* aGlobal,
92
const ServiceWorkerRegistrationDescriptor& aDescriptor) {
93
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
94
MOZ_DIAGNOSTIC_ASSERT(aGlobal);
95
aWorkerPrivate->AssertIsOnWorkerThread();
96
97
RefPtr<Inner> inner;
98
if (ServiceWorkerParentInterceptEnabled()) {
99
inner = new RemoteServiceWorkerRegistrationImpl(aDescriptor);
100
} else {
101
inner = new ServiceWorkerRegistrationWorkerThread(aDescriptor);
102
}
103
NS_ENSURE_TRUE(inner, nullptr);
104
105
RefPtr<ServiceWorkerRegistration> registration =
106
new ServiceWorkerRegistration(aGlobal, aDescriptor, inner);
107
108
return registration.forget();
109
}
110
111
void ServiceWorkerRegistration::DisconnectFromOwner() {
112
DOMEventTargetHelper::DisconnectFromOwner();
113
}
114
115
void ServiceWorkerRegistration::RegistrationCleared() {
116
// Its possible that the registration will fail to install and be
117
// immediately removed. In that case we may never receive the
118
// UpdateState() call if the actor was too slow to connect, etc.
119
// Ensure that we force all our known actors to redundant so that
120
// the appropriate statechange events are fired. If we got the
121
// UpdateState() already then this will be a no-op.
122
UpdateStateInternal(Maybe<ServiceWorkerDescriptor>(),
123
Maybe<ServiceWorkerDescriptor>(),
124
Maybe<ServiceWorkerDescriptor>());
125
126
// Our underlying registration was removed from SWM, so we
127
// will never get an updatefound event again. We can let
128
// the object GC if content is not holding it alive.
129
IgnoreKeepAliveIfHasListenersFor(NS_LITERAL_STRING("updatefound"));
130
}
131
132
already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetInstalling()
133
const {
134
RefPtr<ServiceWorker> ref = mInstallingWorker;
135
return ref.forget();
136
}
137
138
already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetWaiting() const {
139
RefPtr<ServiceWorker> ref = mWaitingWorker;
140
return ref.forget();
141
}
142
143
already_AddRefed<ServiceWorker> ServiceWorkerRegistration::GetActive() const {
144
RefPtr<ServiceWorker> ref = mActiveWorker;
145
return ref.forget();
146
}
147
148
void ServiceWorkerRegistration::UpdateState(
149
const ServiceWorkerRegistrationDescriptor& aDescriptor) {
150
MOZ_DIAGNOSTIC_ASSERT(MatchesDescriptor(aDescriptor));
151
152
mDescriptor = aDescriptor;
153
154
UpdateStateInternal(aDescriptor.GetInstalling(), aDescriptor.GetWaiting(),
155
aDescriptor.GetActive());
156
157
nsTArray<UniquePtr<VersionCallback>> callbackList;
158
mVersionCallbackList.SwapElements(callbackList);
159
for (auto& cb : callbackList) {
160
if (cb->mVersion > mDescriptor.Version()) {
161
mVersionCallbackList.AppendElement(std::move(cb));
162
continue;
163
}
164
165
cb->mFunc(cb->mVersion == mDescriptor.Version());
166
}
167
}
168
169
bool ServiceWorkerRegistration::MatchesDescriptor(
170
const ServiceWorkerRegistrationDescriptor& aDescriptor) const {
171
return aDescriptor.Id() == mDescriptor.Id() &&
172
aDescriptor.PrincipalInfo() == mDescriptor.PrincipalInfo() &&
173
aDescriptor.Scope() == mDescriptor.Scope();
174
}
175
176
void ServiceWorkerRegistration::GetScope(nsAString& aScope) const {
177
CopyUTF8toUTF16(mDescriptor.Scope(), aScope);
178
}
179
180
ServiceWorkerUpdateViaCache ServiceWorkerRegistration::GetUpdateViaCache(
181
ErrorResult& aRv) const {
182
return mDescriptor.UpdateViaCache();
183
}
184
185
already_AddRefed<Promise> ServiceWorkerRegistration::Update(ErrorResult& aRv) {
186
if (!mInner) {
187
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
188
return nullptr;
189
}
190
191
nsIGlobalObject* global = GetParentObject();
192
if (!global) {
193
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
194
return nullptr;
195
}
196
197
RefPtr<Promise> outer = Promise::Create(global, aRv);
198
if (NS_WARN_IF(aRv.Failed())) {
199
return nullptr;
200
}
201
202
/**
203
* `ServiceWorker` objects are not exposed on worker threads yet, so calling
204
* `ServiceWorkerRegistration::Get{Installing,Waiting,Active}` won't work.
205
*/
206
const bool hasNewestWorker = mDescriptor.GetInstalling() ||
207
mDescriptor.GetWaiting() ||
208
mDescriptor.GetActive();
209
210
/**
211
* If newestWorker is null, return a promise rejected with an
212
* "InvalidStateError" DOMException and abort these steps.
213
*/
214
if (!hasNewestWorker) {
215
outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
216
return outer.forget();
217
}
218
219
/**
220
* If the context object’s relevant settings object’s global object
221
* globalObject is a ServiceWorkerGlobalScope object, and globalObject’s
222
* associated service worker's state is "installing", return a promise
223
* rejected with an "InvalidStateError" DOMException and abort these steps.
224
*/
225
if (!NS_IsMainThread()) {
226
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
227
MOZ_ASSERT(workerPrivate);
228
229
if (workerPrivate->IsServiceWorker() &&
230
(workerPrivate->GetServiceWorkerDescriptor().State() ==
231
ServiceWorkerState::Installing)) {
232
outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
233
return outer.forget();
234
}
235
}
236
237
RefPtr<ServiceWorkerRegistration> self = this;
238
239
mInner->Update(
240
[outer, self](const ServiceWorkerRegistrationDescriptor& aDesc) {
241
nsIGlobalObject* global = self->GetParentObject();
242
// It's possible this binding was detached from the global. In cases
243
// where we use IPC with Promise callbacks, we use
244
// DOMMozPromiseRequestHolder in order to auto-disconnect the promise
245
// that would hold these callbacks. However in bug 1466681 we changed
246
// this call to use (synchronous) callbacks because the use of
247
// MozPromise introduced an additional runnable scheduling which made
248
// it very difficult to maintain ordering required by the standard.
249
//
250
// If we were to delete this actor at the time of DETH detaching, we
251
// would not need to do this check because the IPC callback of the
252
// RemoteServiceWorkerRegistrationImpl lambdas would never occur.
253
// However, its actors currently depend on asking the parent to delete
254
// the actor for us. Given relaxations in the IPC lifecyle, we could
255
// potentially issue a direct termination, but that requires additional
256
// evaluation.
257
if (!global) {
258
outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
259
return;
260
}
261
RefPtr<ServiceWorkerRegistration> ref =
262
global->GetOrCreateServiceWorkerRegistration(aDesc);
263
if (!ref) {
264
outer->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
265
return;
266
}
267
outer->MaybeResolve(ref);
268
},
269
[outer, self](ErrorResult& aRv) { outer->MaybeReject(aRv); });
270
271
return outer.forget();
272
}
273
274
already_AddRefed<Promise> ServiceWorkerRegistration::Unregister(
275
ErrorResult& aRv) {
276
nsIGlobalObject* global = GetParentObject();
277
if (NS_WARN_IF(!global)) {
278
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
279
return nullptr;
280
}
281
282
RefPtr<Promise> outer = Promise::Create(global, aRv);
283
if (NS_WARN_IF(aRv.Failed())) {
284
return nullptr;
285
}
286
287
if (!mInner) {
288
outer->MaybeResolve(false);
289
return outer.forget();
290
}
291
292
mInner->Unregister([outer](bool aSuccess) { outer->MaybeResolve(aSuccess); },
293
[outer](ErrorResult& aRv) {
294
// register() should be resilient and resolve false
295
// instead of rejecting in most cases.
296
outer->MaybeResolve(false);
297
});
298
299
return outer.forget();
300
}
301
302
already_AddRefed<PushManager> ServiceWorkerRegistration::GetPushManager(
303
JSContext* aCx, ErrorResult& aRv) {
304
if (!mPushManager) {
305
nsCOMPtr<nsIGlobalObject> globalObject = GetParentObject();
306
307
if (!globalObject) {
308
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
309
return nullptr;
310
}
311
312
GlobalObject global(aCx, globalObject->GetGlobalJSObject());
313
mPushManager = PushManager::Constructor(
314
global, NS_ConvertUTF8toUTF16(mDescriptor.Scope()), aRv);
315
if (aRv.Failed()) {
316
return nullptr;
317
}
318
}
319
320
RefPtr<PushManager> ret = mPushManager;
321
return ret.forget();
322
}
323
324
already_AddRefed<Promise> ServiceWorkerRegistration::ShowNotification(
325
JSContext* aCx, const nsAString& aTitle,
326
const NotificationOptions& aOptions, ErrorResult& aRv) {
327
nsIGlobalObject* global = GetParentObject();
328
if (!global) {
329
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
330
return nullptr;
331
}
332
333
NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope());
334
335
// Until we ship ServiceWorker objects on worker threads the active
336
// worker will always be nullptr. So limit this check to main
337
// thread for now.
338
if (mDescriptor.GetActive().isNothing() && NS_IsMainThread()) {
339
aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(scope);
340
return nullptr;
341
}
342
343
RefPtr<Promise> p = Notification::ShowPersistentNotification(
344
aCx, global, scope, aTitle, aOptions, mDescriptor, aRv);
345
if (NS_WARN_IF(aRv.Failed())) {
346
return nullptr;
347
}
348
349
return p.forget();
350
}
351
352
already_AddRefed<Promise> ServiceWorkerRegistration::GetNotifications(
353
const GetNotificationOptions& aOptions, ErrorResult& aRv) {
354
nsIGlobalObject* global = GetParentObject();
355
if (!global) {
356
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
357
return nullptr;
358
}
359
360
NS_ConvertUTF8toUTF16 scope(mDescriptor.Scope());
361
362
if (NS_IsMainThread()) {
363
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
364
if (NS_WARN_IF(!window)) {
365
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
366
return nullptr;
367
}
368
return Notification::Get(window, aOptions, scope, aRv);
369
}
370
371
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
372
worker->AssertIsOnWorkerThread();
373
return Notification::WorkerGet(worker, aOptions, scope, aRv);
374
}
375
376
const ServiceWorkerRegistrationDescriptor&
377
ServiceWorkerRegistration::Descriptor() const {
378
return mDescriptor;
379
}
380
381
void ServiceWorkerRegistration::WhenVersionReached(
382
uint64_t aVersion, ServiceWorkerBoolCallback&& aCallback) {
383
if (aVersion <= mDescriptor.Version()) {
384
aCallback(aVersion == mDescriptor.Version());
385
return;
386
}
387
388
mVersionCallbackList.AppendElement(
389
MakeUnique<VersionCallback>(aVersion, std::move(aCallback)));
390
}
391
392
void ServiceWorkerRegistration::MaybeScheduleUpdateFound(
393
const Maybe<ServiceWorkerDescriptor>& aInstallingDescriptor) {
394
// This function sets mScheduledUpdateFoundId to note when we were told about
395
// a new installing worker. We rely on a call to
396
// MaybeDispatchUpdateFoundRunnable (called indirectly from UpdateJobCallback)
397
// to actually fire the event.
398
uint64_t newId = aInstallingDescriptor.isSome()
399
? aInstallingDescriptor.ref().Id()
400
: kInvalidUpdateFoundId;
401
402
if (mScheduledUpdateFoundId != kInvalidUpdateFoundId) {
403
if (mScheduledUpdateFoundId == newId) {
404
return;
405
}
406
MaybeDispatchUpdateFound();
407
MOZ_DIAGNOSTIC_ASSERT(mScheduledUpdateFoundId == kInvalidUpdateFoundId);
408
}
409
410
bool updateFound =
411
newId != kInvalidUpdateFoundId && mDispatchedUpdateFoundId != newId;
412
413
if (!updateFound) {
414
return;
415
}
416
417
mScheduledUpdateFoundId = newId;
418
}
419
420
void ServiceWorkerRegistration::MaybeDispatchUpdateFoundRunnable() {
421
if (mScheduledUpdateFoundId == kInvalidUpdateFoundId) {
422
return;
423
}
424
425
nsIGlobalObject* global = GetParentObject();
426
NS_ENSURE_TRUE_VOID(global);
427
428
nsCOMPtr<nsIRunnable> r = NewCancelableRunnableMethod(
429
"ServiceWorkerRegistration::MaybeDispatchUpdateFound", this,
430
&ServiceWorkerRegistration::MaybeDispatchUpdateFound);
431
432
Unused << global->EventTargetFor(TaskCategory::Other)
433
->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
434
}
435
436
void ServiceWorkerRegistration::MaybeDispatchUpdateFound() {
437
uint64_t scheduledId = mScheduledUpdateFoundId;
438
mScheduledUpdateFoundId = kInvalidUpdateFoundId;
439
440
if (scheduledId == kInvalidUpdateFoundId ||
441
scheduledId == mDispatchedUpdateFoundId) {
442
return;
443
}
444
445
mDispatchedUpdateFoundId = scheduledId;
446
DispatchTrustedEvent(NS_LITERAL_STRING("updatefound"));
447
}
448
449
void ServiceWorkerRegistration::UpdateStateInternal(
450
const Maybe<ServiceWorkerDescriptor>& aInstalling,
451
const Maybe<ServiceWorkerDescriptor>& aWaiting,
452
const Maybe<ServiceWorkerDescriptor>& aActive) {
453
// Do this immediately as it may flush an already pending updatefound
454
// event. In that case we want to fire the pending event before
455
// modifying any of the registration properties.
456
MaybeScheduleUpdateFound(aInstalling);
457
458
// Move the currently exposed workers into a separate list
459
// of "old" workers. We will then potentially add them
460
// back to the registration properties below based on the
461
// given descriptor. Any that are not restored will need
462
// to be moved to the redundant state.
463
AutoTArray<RefPtr<ServiceWorker>, 3> oldWorkerList({
464
mInstallingWorker.forget(),
465
mWaitingWorker.forget(),
466
mActiveWorker.forget(),
467
});
468
469
// Its important that all state changes are actually applied before
470
// dispatching any statechange events. Each ServiceWorker object
471
// should be in the correct state and the ServiceWorkerRegistration
472
// properties need to be set correctly as well. To accomplish this
473
// we use a ScopeExit to dispatch any statechange events.
474
auto scopeExit = MakeScopeExit([&] {
475
// Check to see if any of the "old" workers was completely discarded.
476
// Set these workers to the redundant state.
477
for (auto& oldWorker : oldWorkerList) {
478
if (!oldWorker || oldWorker == mInstallingWorker ||
479
oldWorker == mWaitingWorker || oldWorker == mActiveWorker) {
480
continue;
481
}
482
483
oldWorker->SetState(ServiceWorkerState::Redundant);
484
}
485
486
// Check each worker to see if it needs a statechange event dispatched.
487
if (mInstallingWorker) {
488
mInstallingWorker->MaybeDispatchStateChangeEvent();
489
}
490
if (mWaitingWorker) {
491
mWaitingWorker->MaybeDispatchStateChangeEvent();
492
}
493
if (mActiveWorker) {
494
mActiveWorker->MaybeDispatchStateChangeEvent();
495
}
496
497
// We also check the "old" workers to see if they need a statechange
498
// event as well. Note, these may overlap with the known worker properties
499
// above, but MaybeDispatchStateChangeEvent() will ignore duplicated calls.
500
for (auto& oldWorker : oldWorkerList) {
501
if (!oldWorker) {
502
continue;
503
}
504
505
oldWorker->MaybeDispatchStateChangeEvent();
506
}
507
});
508
509
// Clear all workers if the registration has been detached from the global.
510
// Also, we cannot expose ServiceWorker objects on worker threads yet, so
511
// do the same on when off-main-thread. This main thread check should be
512
// removed as part of bug 1113522.
513
nsCOMPtr<nsIGlobalObject> global = GetParentObject();
514
if (!global || !NS_IsMainThread()) {
515
return;
516
}
517
518
if (aActive.isSome()) {
519
if ((mActiveWorker = global->GetOrCreateServiceWorker(aActive.ref()))) {
520
mActiveWorker->SetState(aActive.ref().State());
521
}
522
} else {
523
mActiveWorker = nullptr;
524
}
525
526
if (aWaiting.isSome()) {
527
if ((mWaitingWorker = global->GetOrCreateServiceWorker(aWaiting.ref()))) {
528
mWaitingWorker->SetState(aWaiting.ref().State());
529
}
530
} else {
531
mWaitingWorker = nullptr;
532
}
533
534
if (aInstalling.isSome()) {
535
if ((mInstallingWorker =
536
global->GetOrCreateServiceWorker(aInstalling.ref()))) {
537
mInstallingWorker->SetState(aInstalling.ref().State());
538
}
539
} else {
540
mInstallingWorker = nullptr;
541
}
542
}
543
544
} // namespace dom
545
} // namespace mozilla