Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/clients/manager/ClientSource.cpp
Line
Count
Source (jump to first uncovered line)
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 "ClientSource.h"
8
9
#include "ClientManager.h"
10
#include "ClientManagerChild.h"
11
#include "ClientPrincipalUtils.h"
12
#include "ClientSourceChild.h"
13
#include "ClientState.h"
14
#include "ClientValidation.h"
15
#include "mozilla/dom/ClientIPCTypes.h"
16
#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
17
#include "mozilla/dom/ipc/StructuredCloneData.h"
18
#include "mozilla/dom/MessageEvent.h"
19
#include "mozilla/dom/MessageEventBinding.h"
20
#include "mozilla/dom/Navigator.h"
21
#include "mozilla/dom/WorkerPrivate.h"
22
#include "mozilla/dom/WorkerScope.h"
23
#include "mozilla/dom/ServiceWorker.h"
24
#include "mozilla/dom/ServiceWorkerContainer.h"
25
#include "mozilla/dom/ServiceWorkerManager.h"
26
#include "nsContentUtils.h"
27
#include "nsIDocShell.h"
28
#include "nsPIDOMWindow.h"
29
30
namespace mozilla {
31
namespace dom {
32
33
using mozilla::dom::ipc::StructuredCloneData;
34
using mozilla::ipc::PrincipalInfo;
35
using mozilla::ipc::PrincipalInfoToPrincipal;
36
37
void
38
ClientSource::Shutdown()
39
0
{
40
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
41
0
  if (IsShutdown()) {
42
0
    return;
43
0
  }
44
0
45
0
  ShutdownThing();
46
0
47
0
  mManager = nullptr;
48
0
}
49
50
void
51
ClientSource::ExecutionReady(const ClientSourceExecutionReadyArgs& aArgs)
52
0
{
53
0
  // Fast fail if we don't understand this particular principal/URL combination.
54
0
  // This can happen since we use MozURL for validation which does not handle
55
0
  // some of the more obscure internal principal/url combinations.  Normal
56
0
  // content pages will pass this check.
57
0
  if (NS_WARN_IF(!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(),
58
0
                                           aArgs.url()))) {
59
0
    Shutdown();
60
0
    return;
61
0
  }
62
0
63
0
  mClientInfo.SetURL(aArgs.url());
64
0
  mClientInfo.SetFrameType(aArgs.frameType());
65
0
  MaybeExecute([aArgs](PClientSourceChild* aActor) {
66
0
    aActor->SendExecutionReady(aArgs);
67
0
  });
68
0
}
69
70
nsresult
71
ClientSource::SnapshotWindowState(ClientState* aStateOut)
72
0
{
73
0
  MOZ_ASSERT(NS_IsMainThread());
74
0
75
0
  nsPIDOMWindowInner* window = GetInnerWindow();
76
0
  if (!window || !window->IsCurrentInnerWindow() ||
77
0
      !window->HasActiveDocument()) {
78
0
    *aStateOut = ClientState(ClientWindowState(VisibilityState::Hidden,
79
0
                                               TimeStamp(),
80
0
                                               nsContentUtils::StorageAccess::eDeny,
81
0
                                               false));
82
0
    return NS_OK;
83
0
  }
84
0
85
0
  nsIDocument* doc = window->GetExtantDoc();
86
0
  if (NS_WARN_IF(!doc)) {
87
0
    return NS_ERROR_UNEXPECTED;
88
0
  }
89
0
90
0
  ErrorResult rv;
91
0
  bool focused = doc->HasFocus(rv);
92
0
  if (NS_WARN_IF(rv.Failed())) {
93
0
    rv.SuppressException();
94
0
    return rv.StealNSResult();
95
0
  }
96
0
97
0
  nsContentUtils::StorageAccess storage =
98
0
    nsContentUtils::StorageAllowedForDocument(doc);
99
0
100
0
  *aStateOut = ClientState(ClientWindowState(doc->VisibilityState(),
101
0
                                             doc->LastFocusTime(), storage,
102
0
                                             focused));
103
0
104
0
  return NS_OK;
105
0
}
106
107
WorkerPrivate*
108
ClientSource::GetWorkerPrivate() const
109
0
{
110
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
111
0
  if (!mOwner.is<WorkerPrivate*>()) {
112
0
    return nullptr;
113
0
  }
114
0
  return mOwner.as<WorkerPrivate*>();
115
0
}
116
117
nsIDocShell*
118
ClientSource::GetDocShell() const
119
0
{
120
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
121
0
  if (!mOwner.is<nsCOMPtr<nsIDocShell>>()) {
122
0
    return nullptr;
123
0
  }
124
0
  return mOwner.as<nsCOMPtr<nsIDocShell>>();
125
0
}
126
127
nsIGlobalObject*
128
ClientSource::GetGlobal() const
129
0
{
130
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
131
0
  nsPIDOMWindowInner* win = GetInnerWindow();
132
0
  if (win) {
133
0
    return win->AsGlobal();
134
0
  }
135
0
136
0
  WorkerPrivate* wp = GetWorkerPrivate();
137
0
  if (wp) {
138
0
    return wp->GlobalScope();
139
0
  }
140
0
141
0
  // Note, ClientSource objects attached to docshell for conceptual
142
0
  // initial about:blank will get nullptr here.  The caller should
143
0
  // use MaybeCreateIntitialDocument() to create the window before
144
0
  // GetGlobal() if it wants this before.
145
0
146
0
  return nullptr;
147
0
}
148
149
void
150
ClientSource::MaybeCreateInitialDocument()
151
0
{
152
0
  nsIDocShell* docshell = GetDocShell();
153
0
  if (docshell) {
154
0
    // Force the create of the initial document if it does not exist yet.
155
0
    Unused << docshell->GetDocument();
156
0
157
0
    MOZ_DIAGNOSTIC_ASSERT(GetInnerWindow());
158
0
  }
159
0
}
160
161
ClientSource::ClientSource(ClientManager* aManager,
162
                           nsISerialEventTarget* aEventTarget,
163
                           const ClientSourceConstructorArgs& aArgs)
164
  : mManager(aManager)
165
  , mEventTarget(aEventTarget)
166
  , mOwner(AsVariant(Nothing()))
167
  , mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(), aArgs.creationTime())
168
0
{
169
0
  MOZ_ASSERT(mManager);
170
0
  MOZ_ASSERT(mEventTarget);
171
0
}
172
173
void
174
ClientSource::Activate(PClientManagerChild* aActor)
175
0
{
176
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
177
0
  MOZ_ASSERT(!GetActor());
178
0
179
0
  if (IsShutdown()) {
180
0
    return;
181
0
  }
182
0
183
0
  // Fast fail if we don't understand this particular kind of PrincipalInfo.
184
0
  // This can happen since we use MozURL for validation which does not handle
185
0
  // some of the more obscure internal principal/url combinations.  Normal
186
0
  // content pages will pass this check.
187
0
  if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
188
0
    Shutdown();
189
0
    return;
190
0
  }
191
0
192
0
  ClientSourceConstructorArgs args(mClientInfo.Id(), mClientInfo.Type(),
193
0
                                   mClientInfo.PrincipalInfo(),
194
0
                                   mClientInfo.CreationTime());
195
0
  PClientSourceChild* actor = aActor->SendPClientSourceConstructor(args);
196
0
  if (!actor) {
197
0
    Shutdown();
198
0
    return;
199
0
  }
200
0
201
0
  ActivateThing(static_cast<ClientSourceChild*>(actor));
202
0
}
203
204
ClientSource::~ClientSource()
205
0
{
206
0
  Shutdown();
207
0
}
208
209
nsPIDOMWindowInner*
210
ClientSource::GetInnerWindow() const
211
0
{
212
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
213
0
  if (!mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
214
0
    return nullptr;
215
0
  }
216
0
  return mOwner.as<RefPtr<nsPIDOMWindowInner>>();
217
0
}
218
219
void
220
ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate)
221
0
{
222
0
  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
223
0
  aWorkerPrivate->AssertIsOnWorkerThread();
224
0
225
0
  if (IsShutdown()) {
226
0
    return;
227
0
  }
228
0
229
0
  // A client without access to storage should never be controlled by
230
0
  // a service worker.  Check this here in case we were controlled before
231
0
  // execution ready.  We can't reliably determine what our storage policy
232
0
  // is before execution ready, unfortunately.
233
0
  if (mController.isSome()) {
234
0
    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed() ||
235
0
                          StringBeginsWith(aWorkerPrivate->ScriptURL(),
236
0
                                           NS_LITERAL_STRING("blob:")));
237
0
  }
238
0
239
0
  // Its safe to store the WorkerPrivate* here because the ClientSource
240
0
  // is explicitly destroyed by WorkerPrivate before exiting its run loop.
241
0
  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
242
0
  mOwner = AsVariant(aWorkerPrivate);
243
0
244
0
  ClientSourceExecutionReadyArgs args(
245
0
    aWorkerPrivate->GetLocationInfo().mHref,
246
0
    FrameType::None);
247
0
248
0
  ExecutionReady(args);
249
0
}
250
251
nsresult
252
ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow)
253
0
{
254
0
  MOZ_ASSERT(NS_IsMainThread());
255
0
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow);
256
0
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->IsCurrentInnerWindow());
257
0
  MOZ_DIAGNOSTIC_ASSERT(aInnerWindow->HasActiveDocument());
258
0
259
0
  if (IsShutdown()) {
260
0
    return NS_OK;
261
0
  }
262
0
263
0
  nsIDocument* doc = aInnerWindow->GetExtantDoc();
264
0
  NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
265
0
266
0
  nsIURI* uri = doc->GetOriginalURI();
267
0
  NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
268
0
269
0
  // Don't use nsAutoCString here since IPC requires a full nsCString anyway.
270
0
  nsCString spec;
271
0
  nsresult rv = uri->GetSpec(spec);
272
0
  NS_ENSURE_SUCCESS(rv, rv);
273
0
274
0
  // A client without access to storage should never be controlled by
275
0
  // a service worker.  Check this here in case we were controlled before
276
0
  // execution ready.  We can't reliably determine what our storage policy
277
0
  // is before execution ready, unfortunately.
278
0
  //
279
0
  // Note, explicitly avoid checking storage policy for windows that inherit
280
0
  // service workers from their parent.  If a user opens a controlled window
281
0
  // and then blocks storage, that window will continue to be controlled by
282
0
  // the SW until the window is closed.  Any about:blank or blob URL should
283
0
  // continue to inherit the SW as well.  We need to avoid triggering the
284
0
  // assertion in this corner case.
285
0
  if (mController.isSome()) {
286
0
    MOZ_DIAGNOSTIC_ASSERT(spec.LowerCaseEqualsLiteral("about:blank") ||
287
0
                          StringBeginsWith(spec, NS_LITERAL_CSTRING("blob:")) ||
288
0
                          nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
289
0
                          nsContentUtils::StorageAccess::eAllow);
290
0
  }
291
0
292
0
  nsPIDOMWindowOuter* outer = aInnerWindow->GetOuterWindow();
293
0
  NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED);
294
0
295
0
  FrameType frameType = FrameType::Top_level;
296
0
  if (!outer->IsTopLevelWindow()) {
297
0
    frameType = FrameType::Nested;
298
0
  } else if(outer->HadOriginalOpener()) {
299
0
    frameType = FrameType::Auxiliary;
300
0
  }
301
0
302
0
  // We should either be setting a window execution ready for the
303
0
  // first time or setting the same window execution ready again.
304
0
  // The secondary calls are due to initial about:blank replacement.
305
0
  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>() ||
306
0
                        mOwner.is<nsCOMPtr<nsIDocShell>>() ||
307
0
                        GetInnerWindow() == aInnerWindow);
308
0
309
0
  // This creates a cycle with the window.  It is broken when
310
0
  // nsGlobalWindow::FreeInnerObjects() deletes the ClientSource.
311
0
  mOwner = AsVariant(RefPtr<nsPIDOMWindowInner>(aInnerWindow));
312
0
313
0
  ClientSourceExecutionReadyArgs args(spec, frameType);
314
0
  ExecutionReady(args);
315
0
316
0
  return NS_OK;
317
0
}
318
319
nsresult
320
ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell)
321
0
{
322
0
  MOZ_ASSERT(NS_IsMainThread());
323
0
  MOZ_DIAGNOSTIC_ASSERT(aDocShell);
324
0
325
0
  if (IsShutdown()) {
326
0
    return NS_OK;
327
0
  }
328
0
329
0
  nsPIDOMWindowOuter* outer = aDocShell->GetWindow();
330
0
  if (NS_WARN_IF(!outer)) {
331
0
    return NS_ERROR_UNEXPECTED;
332
0
  }
333
0
334
0
  // Note: We don't assert storage access for a controlled client.  If
335
0
  // the about:blank actually gets used then WindowExecutionReady() will
336
0
  // get called which asserts storage access.
337
0
338
0
  // TODO: dedupe this with WindowExecutionReady
339
0
  FrameType frameType = FrameType::Top_level;
340
0
  if (!outer->IsTopLevelWindow()) {
341
0
    frameType = FrameType::Nested;
342
0
  } else if(outer->HadOriginalOpener()) {
343
0
    frameType = FrameType::Auxiliary;
344
0
  }
345
0
346
0
  MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
347
0
348
0
  // This creates a cycle with the docshell.  It is broken when
349
0
  // nsDocShell::Destroy() deletes the ClientSource.
350
0
  mOwner = AsVariant(nsCOMPtr<nsIDocShell>(aDocShell));
351
0
352
0
  ClientSourceExecutionReadyArgs args(NS_LITERAL_CSTRING("about:blank"),
353
0
                                      frameType);
354
0
  ExecutionReady(args);
355
0
356
0
  return NS_OK;
357
0
}
358
359
void
360
ClientSource::Freeze()
361
0
{
362
0
  MaybeExecute([](PClientSourceChild* aActor) {
363
0
    aActor->SendFreeze();
364
0
  });
365
0
}
366
367
void
368
ClientSource::Thaw()
369
0
{
370
0
  MaybeExecute([](PClientSourceChild* aActor) {
371
0
    aActor->SendThaw();
372
0
  });
373
0
}
374
375
const ClientInfo&
376
ClientSource::Info() const
377
0
{
378
0
  return mClientInfo;
379
0
}
380
381
void
382
ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate)
383
0
{
384
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
385
0
  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
386
0
387
0
  if (IsShutdown()) {
388
0
    return;
389
0
  }
390
0
391
0
  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
392
0
  aWorkerPrivate->AssertIsOnWorkerThread();
393
0
  MOZ_DIAGNOSTIC_ASSERT(GetActor());
394
0
395
0
  GetActor()->SendWorkerSyncPing();
396
0
}
397
398
void
399
ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
400
0
{
401
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
402
0
403
0
  // We should never have a cross-origin controller.  Since this would be
404
0
  // same-origin policy violation we do a full release assertion here.
405
0
  MOZ_RELEASE_ASSERT(ClientMatchPrincipalInfo(mClientInfo.PrincipalInfo(),
406
0
                                              aServiceWorker.PrincipalInfo()));
407
0
408
0
  // A client in private browsing mode should never be controlled by
409
0
  // a service worker.  The principal origin attributes should guarantee
410
0
  // this invariant.
411
0
  MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
412
0
413
0
  // A client without access to storage should never be controlled a
414
0
  // a service worker.  If we are already execution ready with a real
415
0
  // window or worker, then verify assert the storage policy is correct.
416
0
  //
417
0
  // Note, explicitly avoid checking storage policy for clients that inherit
418
0
  // service workers from their parent.  This basically means blob: URLs
419
0
  // and about:blank windows.
420
0
  if (GetInnerWindow()) {
421
0
    MOZ_DIAGNOSTIC_ASSERT(Info().URL().LowerCaseEqualsLiteral("about:blank") ||
422
0
                          StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
423
0
                          nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
424
0
                          nsContentUtils::StorageAccess::eAllow);
425
0
  } else if (GetWorkerPrivate()) {
426
0
    MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed() ||
427
0
                          StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
428
0
                                           NS_LITERAL_STRING("blob:")));
429
0
  }
430
0
431
0
  if (mController.isSome() && mController.ref() == aServiceWorker) {
432
0
    return;
433
0
  }
434
0
435
0
  mController.reset();
436
0
  mController.emplace(aServiceWorker);
437
0
438
0
  RefPtr<ServiceWorkerContainer> swc;
439
0
  nsPIDOMWindowInner* window = GetInnerWindow();
440
0
  if (window) {
441
0
    swc = window->Navigator()->ServiceWorker();
442
0
  }
443
0
444
0
  // TODO: Also self.navigator.serviceWorker on workers when its exposed there
445
0
446
0
  if (swc && nsContentUtils::IsSafeToRunScript()) {
447
0
    swc->ControllerChanged(IgnoreErrors());
448
0
  }
449
0
}
450
451
RefPtr<ClientOpPromise>
452
ClientSource::Control(const ClientControlledArgs& aArgs)
453
0
{
454
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
455
0
456
0
  // Determine if the client is allowed to be controlled.  Currently we
457
0
  // prevent service workers from controlling clients that cannot access
458
0
  // storage.  We exempt this restriction for local URL clients, like about:blank
459
0
  // and blob:, since access to service workers is dictated by their parent.
460
0
  //
461
0
  // Note, we default to allowing the client to be controlled in the case
462
0
  // where we are not execution ready yet.  This can only happen if the
463
0
  // the non-subresource load is intercepted by a service worker.  Since
464
0
  // ServiceWorkerInterceptController() uses StorageAllowedForChannel()
465
0
  // it should be fine to accept these control messages.
466
0
  //
467
0
  // Its also fine to default to allowing ClientSource attached to a docshell
468
0
  // to be controlled.  These clients represent inital about:blank windows
469
0
  // that do not have an inner window created yet.  We explicitly allow initial
470
0
  // about:blank.
471
0
  bool controlAllowed = true;
472
0
  if (GetInnerWindow()) {
473
0
474
0
    // Local URL windows and windows with access to storage can be controlled.
475
0
    controlAllowed = Info().URL().LowerCaseEqualsLiteral("about:blank") ||
476
0
                     StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
477
0
                     nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
478
0
                      nsContentUtils::StorageAccess::eAllow;
479
0
  } else if (GetWorkerPrivate()) {
480
0
    // Local URL workers and workers with access to storage cna be controlled.
481
0
    controlAllowed = GetWorkerPrivate()->IsStorageAllowed() ||
482
0
                     StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
483
0
                                      NS_LITERAL_STRING("blob:"));
484
0
  }
485
0
486
0
  RefPtr<ClientOpPromise> ref;
487
0
488
0
  if (NS_WARN_IF(!controlAllowed)) {
489
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
490
0
                                           __func__);
491
0
    return ref.forget();
492
0
  }
493
0
494
0
  SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
495
0
496
0
  ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
497
0
  return ref.forget();
498
0
}
499
500
void
501
ClientSource::InheritController(const ServiceWorkerDescriptor& aServiceWorker)
502
0
{
503
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
504
0
505
0
  // If we are in legacy child-side intercept mode then we must tell the current
506
0
  // process SWM that this client inherited a controller.  This will only update
507
0
  // the local SWM data and not send any messages to the ClientManagerService.
508
0
  //
509
0
  // Note, we only do this when inheriting the controller for main thread
510
0
  // windows.  The legacy mode never proprly marked inherited blob URL workers
511
0
  // controlled in the SWM.
512
0
  if (!ServiceWorkerParentInterceptEnabled() && GetDocShell()) {
513
0
    AssertIsOnMainThread();
514
0
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
515
0
    if (swm) {
516
0
      swm->NoteInheritedController(mClientInfo, aServiceWorker);
517
0
    }
518
0
  }
519
0
520
0
  // Also tell the parent-side ClientManagerService that the controller was
521
0
  // inherited.  This is necessary for clients.matchAll() to work properly.
522
0
  // In parent-side intercept mode this will also note the inheritance in
523
0
  // the parent-side SWM.
524
0
  MaybeExecute([aServiceWorker](PClientSourceChild* aActor) {
525
0
    aActor->SendInheritController(ClientControlledArgs(aServiceWorker.ToIPC()));
526
0
  });
527
0
528
0
  // Finally, record the new controller in our local ClientSource for any
529
0
  // immediate synchronous access.
530
0
  SetController(aServiceWorker);
531
0
}
532
533
const Maybe<ServiceWorkerDescriptor>&
534
ClientSource::GetController() const
535
0
{
536
0
  return mController;
537
0
}
538
539
void
540
ClientSource::NoteDOMContentLoaded()
541
0
{
542
0
  if (mController.isSome() && !ServiceWorkerParentInterceptEnabled()) {
543
0
    AssertIsOnMainThread();
544
0
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
545
0
    if (swm) {
546
0
      swm->MaybeCheckNavigationUpdate(mClientInfo);
547
0
    }
548
0
  }
549
0
550
0
  MaybeExecute([] (PClientSourceChild* aActor) {
551
0
    aActor->SendNoteDOMContentLoaded();
552
0
  });
553
0
}
554
555
RefPtr<ClientOpPromise>
556
ClientSource::Focus(const ClientFocusArgs& aArgs)
557
0
{
558
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
559
0
560
0
  RefPtr<ClientOpPromise> ref;
561
0
562
0
  if (mClientInfo.Type() != ClientType::Window) {
563
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
564
0
                                           __func__);
565
0
    return ref.forget();
566
0
  }
567
0
  nsPIDOMWindowOuter* outer = nullptr;
568
0
569
0
  nsPIDOMWindowInner* inner = GetInnerWindow();
570
0
  if (inner) {
571
0
    outer = inner->GetOuterWindow();
572
0
  } else {
573
0
    nsIDocShell* docshell = GetDocShell();
574
0
    if (docshell) {
575
0
      outer = docshell->GetWindow();
576
0
    }
577
0
  }
578
0
579
0
  if (!outer) {
580
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
581
0
                                           __func__);
582
0
    return ref.forget();
583
0
  }
584
0
585
0
  MOZ_ASSERT(NS_IsMainThread());
586
0
587
0
  nsresult rv = nsContentUtils::DispatchFocusChromeEvent(outer);
588
0
  if (NS_FAILED(rv)) {
589
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
590
0
    return ref.forget();
591
0
  }
592
0
593
0
  ClientState state;
594
0
  rv = SnapshotState(&state);
595
0
  if (NS_FAILED(rv)) {
596
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
597
0
    return ref.forget();
598
0
  }
599
0
600
0
  ref = ClientOpPromise::CreateAndResolve(state.ToIPC(), __func__);
601
0
  return ref.forget();
602
0
}
603
604
RefPtr<ClientOpPromise>
605
ClientSource::PostMessage(const ClientPostMessageArgs& aArgs)
606
0
{
607
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
608
0
  RefPtr<ClientOpPromise> ref;
609
0
610
0
  ServiceWorkerDescriptor source(aArgs.serviceWorker());
611
0
  const PrincipalInfo& principalInfo = source.PrincipalInfo();
612
0
613
0
  StructuredCloneData clonedData;
614
0
  clonedData.BorrowFromClonedMessageDataForBackgroundChild(aArgs.clonedData());
615
0
616
0
  // Currently we only support firing these messages on window Clients.
617
0
  // Once we expose ServiceWorkerContainer and the ServiceWorker on Worker
618
0
  // threads then this will need to change.  See bug 1113522.
619
0
  if (mClientInfo.Type() != ClientType::Window) {
620
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__);
621
0
    return ref.forget();
622
0
  }
623
0
624
0
  MOZ_ASSERT(NS_IsMainThread());
625
0
626
0
  RefPtr<ServiceWorkerContainer> target;
627
0
  nsCOMPtr<nsIGlobalObject> globalObject;
628
0
629
0
  // We don't need to force the creation of the about:blank document
630
0
  // here because there is no postMessage listener.  If a listener
631
0
  // was registered then the document will already be created.
632
0
  nsPIDOMWindowInner* window = GetInnerWindow();
633
0
  if (window) {
634
0
    globalObject = do_QueryInterface(window);
635
0
    target = window->Navigator()->ServiceWorker();
636
0
  }
637
0
638
0
  if (NS_WARN_IF(!target)) {
639
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
640
0
                                           __func__);
641
0
    return ref.forget();
642
0
  }
643
0
644
0
  // If AutoJSAPI::Init() fails then either global is nullptr or not
645
0
  // in a usable state.
646
0
  AutoJSAPI jsapi;
647
0
  if (!jsapi.Init(globalObject)) {
648
0
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
649
0
    return ref.forget();
650
0
  }
651
0
652
0
  JSContext* cx = jsapi.cx();
653
0
654
0
  ErrorResult result;
655
0
  JS::Rooted<JS::Value> messageData(cx);
656
0
  clonedData.Read(cx, &messageData, result);
657
0
  if (result.MaybeSetPendingException(cx)) {
658
0
    // We reported the error in the current window context.  Resolve
659
0
    // promise instead of rejecting.
660
0
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
661
0
    return ref.forget();
662
0
  }
663
0
664
0
  RootedDictionary<MessageEventInit> init(cx);
665
0
666
0
  init.mData = messageData;
667
0
  if (!clonedData.TakeTransferredPortsAsSequence(init.mPorts)) {
668
0
    // Report the error in the current window context and resolve the
669
0
    // promise instead of rejecting.
670
0
    xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY);
671
0
    ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
672
0
    return ref.forget();
673
0
  }
674
0
675
0
  nsresult rv = NS_OK;
676
0
  nsCOMPtr<nsIPrincipal> principal =
677
0
    PrincipalInfoToPrincipal(principalInfo, &rv);
678
0
  if (NS_FAILED(rv) || !principal) {
679
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
680
0
    return ref.forget();
681
0
  }
682
0
683
0
  nsAutoCString origin;
684
0
  rv = principal->GetOriginNoSuffix(origin);
685
0
  if (NS_SUCCEEDED(rv)) {
686
0
    CopyUTF8toUTF16(origin, init.mOrigin);
687
0
  }
688
0
689
0
  RefPtr<ServiceWorker> instance;
690
0
691
0
  if (ServiceWorkerParentInterceptEnabled()) {
692
0
    instance = globalObject->GetOrCreateServiceWorker(source);
693
0
  } else {
694
0
    // If we are in legacy child-side intercept mode then we need to verify
695
0
    // this registration exists in the current process.
696
0
    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
697
0
    if (!swm) {
698
0
      // Shutting down. Just don't deliver this message.
699
0
      ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
700
0
      return ref.forget();
701
0
    }
702
0
703
0
    RefPtr<ServiceWorkerRegistrationInfo> reg =
704
0
      swm->GetRegistration(principal, source.Scope());
705
0
    if (reg) {
706
0
      instance = globalObject->GetOrCreateServiceWorker(source);
707
0
    }
708
0
  }
709
0
710
0
  if (instance) {
711
0
    init.mSource.SetValue().SetAsServiceWorker() = instance;
712
0
  }
713
0
714
0
  RefPtr<MessageEvent> event =
715
0
    MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init);
716
0
  event->SetTrusted(true);
717
0
718
0
  target->DispatchEvent(*event, result);
719
0
  if (result.Failed()) {
720
0
    result.SuppressException();
721
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
722
0
    return ref.forget();
723
0
  }
724
0
725
0
  ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
726
0
  return ref.forget();
727
0
}
728
729
RefPtr<ClientOpPromise>
730
ClientSource::Claim(const ClientClaimArgs& aArgs)
731
0
{
732
0
  // The ClientSource::Claim method is only needed in the legacy
733
0
  // mode where the ServiceWorkerManager is run in each child-process.
734
0
  // In parent-process mode this method should not be called.
735
0
  MOZ_DIAGNOSTIC_ASSERT(!ServiceWorkerParentInterceptEnabled());
736
0
737
0
  RefPtr<ClientOpPromise> ref;
738
0
739
0
  nsIGlobalObject* global = GetGlobal();
740
0
  if (NS_WARN_IF(!global)) {
741
0
    ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
742
0
                                           __func__);
743
0
    return ref.forget();
744
0
  }
745
0
746
0
  // Note, we cannot just mark the ClientSource controlled.  We must go through
747
0
  // the SWM so that it can keep track of which clients are controlled by each
748
0
  // registration.  We must tell the child-process SWM in legacy child-process
749
0
  // mode.  In parent-process service worker mode the SWM is notified in the
750
0
  // parent-process in ClientManagerService::Claim().
751
0
752
0
  RefPtr<GenericPromise::Private> innerPromise =
753
0
    new GenericPromise::Private(__func__);
754
0
  ServiceWorkerDescriptor swd(aArgs.serviceWorker());
755
0
756
0
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
757
0
    "ClientSource::Claim",
758
0
    [innerPromise, clientInfo = mClientInfo, swd] () mutable {
759
0
      RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
760
0
      if (NS_WARN_IF(!swm)) {
761
0
        innerPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
762
0
        return;
763
0
      }
764
0
765
0
      RefPtr<GenericPromise> p = swm->MaybeClaimClient(clientInfo, swd);
766
0
      p->ChainTo(innerPromise.forget(), __func__);
767
0
    });
768
0
769
0
  if (NS_IsMainThread()) {
770
0
    r->Run();
771
0
  } else {
772
0
    MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
773
0
  }
774
0
775
0
  RefPtr<ClientOpPromise::Private> outerPromise =
776
0
    new ClientOpPromise::Private(__func__);
777
0
778
0
  auto holder = MakeRefPtr<DOMMozPromiseRequestHolder<GenericPromise>>(global);
779
0
780
0
  innerPromise->Then(mEventTarget, __func__,
781
0
    [outerPromise, holder] (bool aResult) {
782
0
      holder->Complete();
783
0
      outerPromise->Resolve(NS_OK, __func__);
784
0
    }, [outerPromise, holder] (nsresult aResult) {
785
0
      holder->Complete();
786
0
      outerPromise->Reject(aResult, __func__);
787
0
    })->Track(*holder);
788
0
789
0
  ref = outerPromise;
790
0
  return ref.forget();
791
0
}
792
793
RefPtr<ClientOpPromise>
794
ClientSource::GetInfoAndState(const ClientGetInfoAndStateArgs& aArgs)
795
0
{
796
0
  RefPtr<ClientOpPromise> ref;
797
0
798
0
  ClientState state;
799
0
  nsresult rv = SnapshotState(&state);
800
0
  if (NS_FAILED(rv)) {
801
0
    ref = ClientOpPromise::CreateAndReject(rv, __func__);
802
0
    return ref.forget();
803
0
  }
804
0
805
0
  ref = ClientOpPromise::CreateAndResolve(ClientInfoAndState(mClientInfo.ToIPC(),
806
0
                                                             state.ToIPC()), __func__);
807
0
  return ref.forget();
808
0
}
809
810
nsresult
811
ClientSource::SnapshotState(ClientState* aStateOut)
812
0
{
813
0
  NS_ASSERT_OWNINGTHREAD(ClientSource);
814
0
  MOZ_DIAGNOSTIC_ASSERT(aStateOut);
815
0
816
0
  if (mClientInfo.Type() == ClientType::Window) {
817
0
    MaybeCreateInitialDocument();
818
0
    nsresult rv = SnapshotWindowState(aStateOut);
819
0
    if (NS_FAILED(rv)) {
820
0
      return rv;
821
0
    }
822
0
    return NS_OK;
823
0
  }
824
0
825
0
  WorkerPrivate* workerPrivate = GetWorkerPrivate();
826
0
  if (!workerPrivate) {
827
0
    return NS_ERROR_DOM_INVALID_STATE_ERR;
828
0
  }
829
0
830
0
  // Workers only keep a boolean for storage access at the moment.
831
0
  // Map this back to eAllow or eDeny for now.
832
0
  nsContentUtils::StorageAccess storage =
833
0
    workerPrivate->IsStorageAllowed() ? nsContentUtils::StorageAccess::eAllow
834
0
                                      : nsContentUtils::StorageAccess::eDeny;
835
0
836
0
  *aStateOut = ClientState(ClientWorkerState(storage));
837
0
  return NS_OK;
838
0
}
839
840
nsISerialEventTarget*
841
ClientSource::EventTarget() const
842
0
{
843
0
  return mEventTarget;
844
0
}
845
846
void
847
ClientSource::Traverse(nsCycleCollectionTraversalCallback& aCallback,
848
                       const char* aName,
849
                       uint32_t aFlags)
850
0
{
851
0
  if (mOwner.is<RefPtr<nsPIDOMWindowInner>>()) {
852
0
    ImplCycleCollectionTraverse(aCallback,
853
0
                                mOwner.as<RefPtr<nsPIDOMWindowInner>>(),
854
0
                                aName, aFlags);
855
0
  } else if (mOwner.is<nsCOMPtr<nsIDocShell>>()) {
856
0
    ImplCycleCollectionTraverse(aCallback,
857
0
                                mOwner.as<nsCOMPtr<nsIDocShell>>(),
858
0
                                aName, aFlags);
859
0
  }
860
0
}
861
862
void
863
ClientSource::NoteCalledRegisterForServiceWorkerScope(const nsACString& aScope)
864
0
{
865
0
  if (mRegisteringScopeList.Contains(aScope)) {
866
0
    return;
867
0
  }
868
0
  mRegisteringScopeList.AppendElement(aScope);
869
0
}
870
871
bool
872
ClientSource::CalledRegisterForServiceWorkerScope(const nsACString& aScope)
873
0
{
874
0
  return mRegisteringScopeList.Contains(aScope);
875
0
}
876
877
} // namespace dom
878
} // namespace mozilla