Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/presentation/PresentationRequest.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 "PresentationRequest.h"
8
9
#include "AvailabilityCollection.h"
10
#include "ControllerConnectionCollection.h"
11
#include "mozilla/BasePrincipal.h"
12
#include "mozilla/dom/Navigator.h"
13
#include "mozilla/dom/PresentationRequestBinding.h"
14
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
15
#include "mozilla/dom/Promise.h"
16
#include "mozilla/Move.h"
17
#include "mozIThirdPartyUtil.h"
18
#include "nsContentSecurityManager.h"
19
#include "nsCycleCollectionParticipant.h"
20
#include "nsGlobalWindow.h"
21
#include "nsIDocument.h"
22
#include "nsIPresentationService.h"
23
#include "nsIURI.h"
24
#include "nsIUUIDGenerator.h"
25
#include "nsNetUtil.h"
26
#include "nsSandboxFlags.h"
27
#include "nsServiceManagerUtils.h"
28
#include "Presentation.h"
29
#include "PresentationAvailability.h"
30
#include "PresentationCallbacks.h"
31
#include "PresentationLog.h"
32
#include "PresentationTransportBuilderConstructor.h"
33
34
using namespace mozilla;
35
using namespace mozilla::dom;
36
37
NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper)
38
NS_IMPL_RELEASE_INHERITED(PresentationRequest, DOMEventTargetHelper)
39
40
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationRequest)
41
0
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
42
43
static nsresult
44
GetAbsoluteURL(const nsAString& aUrl,
45
               nsIURI* aBaseUri,
46
               nsIDocument* aDocument,
47
               nsAString& aAbsoluteUrl)
48
0
{
49
0
  nsCOMPtr<nsIURI> uri;
50
0
  nsresult rv;
51
0
  if (aDocument) {
52
0
    rv = NS_NewURI(getter_AddRefs(uri), aUrl,
53
0
                   aDocument->GetDocumentCharacterSet(), aBaseUri);
54
0
  } else {
55
0
    rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBaseUri);
56
0
  }
57
0
58
0
  if (NS_FAILED(rv)) {
59
0
    return rv;
60
0
  }
61
0
62
0
  nsAutoCString spec;
63
0
  uri->GetSpec(spec);
64
0
65
0
  aAbsoluteUrl = NS_ConvertUTF8toUTF16(spec);
66
0
67
0
  return NS_OK;
68
0
}
69
70
/* static */ already_AddRefed<PresentationRequest>
71
PresentationRequest::Constructor(const GlobalObject& aGlobal,
72
                                 const nsAString& aUrl,
73
                                 ErrorResult& aRv)
74
0
{
75
0
  Sequence<nsString> urls;
76
0
  urls.AppendElement(aUrl, fallible);
77
0
  return Constructor(aGlobal, urls, aRv);
78
0
}
79
80
/* static */ already_AddRefed<PresentationRequest>
81
PresentationRequest::Constructor(const GlobalObject& aGlobal,
82
                                 const Sequence<nsString>& aUrls,
83
                                 ErrorResult& aRv)
84
0
{
85
0
  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
86
0
  if (!window) {
87
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
88
0
    return nullptr;
89
0
  }
90
0
91
0
  if (aUrls.IsEmpty()) {
92
0
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
93
0
    return nullptr;
94
0
  }
95
0
96
0
  // Resolve relative URL to absolute URL
97
0
  nsCOMPtr<nsIURI> baseUri = window->GetDocBaseURI();
98
0
  nsTArray<nsString> urls;
99
0
  for (const auto& url : aUrls) {
100
0
    nsAutoString absoluteUrl;
101
0
    nsresult rv =
102
0
      GetAbsoluteURL(url, baseUri, window->GetExtantDoc(), absoluteUrl);
103
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
104
0
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
105
0
      return nullptr;
106
0
    }
107
0
108
0
    urls.AppendElement(absoluteUrl);
109
0
  }
110
0
111
0
  RefPtr<PresentationRequest> request =
112
0
    new PresentationRequest(window, std::move(urls));
113
0
  return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
114
0
}
115
116
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
117
                                         nsTArray<nsString>&& aUrls)
118
  : DOMEventTargetHelper(aWindow)
119
  , mUrls(std::move(aUrls))
120
0
{
121
0
}
122
123
PresentationRequest::~PresentationRequest()
124
0
{
125
0
}
126
127
bool
128
PresentationRequest::Init()
129
0
{
130
0
  return true;
131
0
}
132
133
/* virtual */ JSObject*
134
PresentationRequest::WrapObject(JSContext* aCx,
135
                                JS::Handle<JSObject*> aGivenProto)
136
0
{
137
0
  return PresentationRequest_Binding::Wrap(aCx, this, aGivenProto);
138
0
}
139
140
already_AddRefed<Promise>
141
PresentationRequest::Start(ErrorResult& aRv)
142
0
{
143
0
  return StartWithDevice(VoidString(), aRv);
144
0
}
145
146
already_AddRefed<Promise>
147
PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
148
                                     ErrorResult& aRv)
149
0
{
150
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
151
0
  if (NS_WARN_IF(!global)) {
152
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
153
0
    return nullptr;
154
0
  }
155
0
156
0
  // Get the origin.
157
0
  nsAutoString origin;
158
0
  nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
159
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
160
0
    aRv.Throw(rv);
161
0
    return nullptr;
162
0
  }
163
0
164
0
  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
165
0
  if (NS_WARN_IF(!doc)) {
166
0
    aRv.Throw(NS_ERROR_FAILURE);
167
0
    return nullptr;
168
0
  }
169
0
170
0
  RefPtr<Promise> promise = Promise::Create(global, aRv);
171
0
  if (NS_WARN_IF(aRv.Failed())) {
172
0
    return nullptr;
173
0
  }
174
0
175
0
  if (nsContentUtils::ShouldResistFingerprinting()) {
176
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
177
0
    return promise.forget();
178
0
  }
179
0
180
0
  if (IsProhibitMixedSecurityContexts(doc) &&
181
0
      !IsAllURLAuthenticated()) {
182
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
183
0
    return promise.forget();
184
0
  }
185
0
186
0
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
187
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
188
0
    return promise.forget();
189
0
  }
190
0
191
0
  RefPtr<Navigator> navigator = nsGlobalWindowInner::Cast(GetOwner())->Navigator();
192
0
  if (NS_WARN_IF(aRv.Failed())) {
193
0
    return nullptr;
194
0
  }
195
0
196
0
  RefPtr<Presentation> presentation = navigator->GetPresentation(aRv);
197
0
  if (NS_WARN_IF(aRv.Failed())) {
198
0
    return nullptr;
199
0
  }
200
0
201
0
  if (presentation->IsStartSessionUnsettled()) {
202
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
203
0
    return promise.forget();
204
0
  }
205
0
206
0
  // Generate a session ID.
207
0
  nsCOMPtr<nsIUUIDGenerator> uuidgen =
208
0
    do_GetService("@mozilla.org/uuid-generator;1");
209
0
  if(NS_WARN_IF(!uuidgen)) {
210
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
211
0
    return promise.forget();
212
0
  }
213
0
214
0
  nsID uuid;
215
0
  uuidgen->GenerateUUIDInPlace(&uuid);
216
0
  char buffer[NSID_LENGTH];
217
0
  uuid.ToProvidedString(buffer);
218
0
  nsAutoString id;
219
0
  CopyASCIItoUTF16(MakeSpan(buffer, NSID_LENGTH - 1), id);
220
0
221
0
  nsCOMPtr<nsIPresentationService> service =
222
0
    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
223
0
  if(NS_WARN_IF(!service)) {
224
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
225
0
    return promise.forget();
226
0
  }
227
0
228
0
  presentation->SetStartSessionUnsettled(true);
229
0
230
0
  // Get xul:browser element in parent process or nsWindowRoot object in child
231
0
  // process. If it's in child process, the corresponding xul:browser element
232
0
  // will be obtained at PresentationRequestParent::DoRequest in its parent
233
0
  // process.
234
0
  nsCOMPtr<EventTarget> handler = GetOwner()->GetChromeEventHandler();
235
0
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
236
0
  nsCOMPtr<nsIPresentationServiceCallback> callback =
237
0
    new PresentationRequesterCallback(this, id, promise);
238
0
  nsCOMPtr<nsIPresentationTransportBuilderConstructor> constructor =
239
0
    PresentationTransportBuilderConstructor::Create();
240
0
  rv = service->StartSession(mUrls,
241
0
                             id,
242
0
                             origin,
243
0
                             aDeviceId,
244
0
                             GetOwner()->WindowID(),
245
0
                             handler,
246
0
                             principal,
247
0
                             callback,
248
0
                             constructor);
249
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
250
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
251
0
    NotifyPromiseSettled();
252
0
  }
253
0
254
0
  return promise.forget();
255
0
}
256
257
already_AddRefed<Promise>
258
PresentationRequest::Reconnect(const nsAString& aPresentationId,
259
                               ErrorResult& aRv)
260
0
{
261
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
262
0
  if (NS_WARN_IF(!global)) {
263
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
264
0
    return nullptr;
265
0
  }
266
0
267
0
  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
268
0
  if (NS_WARN_IF(!doc)) {
269
0
    aRv.Throw(NS_ERROR_FAILURE);
270
0
    return nullptr;
271
0
  }
272
0
273
0
  RefPtr<Promise> promise = Promise::Create(global, aRv);
274
0
  if (NS_WARN_IF(aRv.Failed())) {
275
0
    return nullptr;
276
0
  }
277
0
278
0
  if (nsContentUtils::ShouldResistFingerprinting()) {
279
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
280
0
    return promise.forget();
281
0
  }
282
0
283
0
  if (IsProhibitMixedSecurityContexts(doc) &&
284
0
      !IsAllURLAuthenticated()) {
285
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
286
0
    return promise.forget();
287
0
  }
288
0
289
0
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
290
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
291
0
    return promise.forget();
292
0
  }
293
0
294
0
  nsString presentationId = nsString(aPresentationId);
295
0
  nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsString, RefPtr<Promise>>(
296
0
    "dom::PresentationRequest::FindOrCreatePresentationConnection",
297
0
    this,
298
0
    &PresentationRequest::FindOrCreatePresentationConnection,
299
0
    presentationId,
300
0
    promise);
301
0
302
0
  if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
303
0
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
304
0
  }
305
0
306
0
  return promise.forget();
307
0
}
308
309
void
310
PresentationRequest::FindOrCreatePresentationConnection(
311
  const nsAString& aPresentationId,
312
  Promise* aPromise)
313
0
{
314
0
  MOZ_ASSERT(aPromise);
315
0
316
0
  if (NS_WARN_IF(!GetOwner())) {
317
0
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
318
0
    return;
319
0
  }
320
0
321
0
  RefPtr<PresentationConnection> connection =
322
0
    ControllerConnectionCollection::GetSingleton()->FindConnection(
323
0
      GetOwner()->WindowID(),
324
0
      aPresentationId,
325
0
      nsIPresentationService::ROLE_CONTROLLER);
326
0
327
0
  if (connection) {
328
0
    nsAutoString url;
329
0
    connection->GetUrl(url);
330
0
    if (mUrls.Contains(url)) {
331
0
      switch (connection->State()) {
332
0
        case PresentationConnectionState::Closed:
333
0
          // We found the matched connection.
334
0
          break;
335
0
        case PresentationConnectionState::Connecting:
336
0
        case PresentationConnectionState::Connected:
337
0
          aPromise->MaybeResolve(connection);
338
0
          return;
339
0
        case PresentationConnectionState::Terminated:
340
0
          // A terminated connection cannot be reused.
341
0
          connection = nullptr;
342
0
          break;
343
0
        default:
344
0
          MOZ_CRASH("Unknown presentation session state.");
345
0
          return;
346
0
      }
347
0
    } else {
348
0
      connection = nullptr;
349
0
    }
350
0
  }
351
0
352
0
  nsCOMPtr<nsIPresentationService> service =
353
0
    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
354
0
  if(NS_WARN_IF(!service)) {
355
0
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
356
0
    return;
357
0
  }
358
0
359
0
  nsCOMPtr<nsIPresentationServiceCallback> callback =
360
0
    new PresentationReconnectCallback(this,
361
0
                                      aPresentationId,
362
0
                                      aPromise,
363
0
                                      connection);
364
0
365
0
  nsresult rv =
366
0
    service->ReconnectSession(mUrls,
367
0
                              aPresentationId,
368
0
                              nsIPresentationService::ROLE_CONTROLLER,
369
0
                              callback);
370
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
371
0
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
372
0
  }
373
0
}
374
375
already_AddRefed<Promise>
376
PresentationRequest::GetAvailability(ErrorResult& aRv)
377
0
{
378
0
  PRES_DEBUG("%s\n", __func__);
379
0
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
380
0
  if (NS_WARN_IF(!global)) {
381
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
382
0
    return nullptr;
383
0
  }
384
0
385
0
  nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
386
0
  if (NS_WARN_IF(!doc)) {
387
0
    aRv.Throw(NS_ERROR_FAILURE);
388
0
    return nullptr;
389
0
  }
390
0
391
0
  RefPtr<Promise> promise = Promise::Create(global, aRv);
392
0
  if (NS_WARN_IF(aRv.Failed())) {
393
0
    return nullptr;
394
0
  }
395
0
396
0
  if (nsContentUtils::ShouldResistFingerprinting()) {
397
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
398
0
    return promise.forget();
399
0
  }
400
0
401
0
  if (IsProhibitMixedSecurityContexts(doc) &&
402
0
      !IsAllURLAuthenticated()) {
403
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
404
0
    return promise.forget();
405
0
  }
406
0
407
0
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
408
0
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
409
0
    return promise.forget();
410
0
  }
411
0
412
0
  FindOrCreatePresentationAvailability(promise);
413
0
414
0
  return promise.forget();
415
0
}
416
417
void
418
PresentationRequest::FindOrCreatePresentationAvailability(RefPtr<Promise>& aPromise)
419
0
{
420
0
  MOZ_ASSERT(aPromise);
421
0
422
0
  if (NS_WARN_IF(!GetOwner())) {
423
0
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
424
0
    return;
425
0
  }
426
0
427
0
  AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
428
0
  if (NS_WARN_IF(!collection)) {
429
0
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
430
0
    return;
431
0
  }
432
0
433
0
  RefPtr<PresentationAvailability> availability =
434
0
    collection->Find(GetOwner()->WindowID(), mUrls);
435
0
436
0
  if (!availability) {
437
0
    availability = PresentationAvailability::Create(GetOwner(), mUrls, aPromise);
438
0
  } else {
439
0
    PRES_DEBUG(">resolve with same object\n");
440
0
441
0
    // Fetching cached available devices is asynchronous in our implementation,
442
0
    // we need to ensure the promise is resolved in order.
443
0
    if (availability->IsCachedValueReady()) {
444
0
      aPromise->MaybeResolve(availability);
445
0
      return;
446
0
    }
447
0
448
0
    availability->EnqueuePromise(aPromise);
449
0
  }
450
0
451
0
  if (!availability) {
452
0
    aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
453
0
    return;
454
0
  }
455
0
}
456
457
nsresult
458
PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
459
0
{
460
0
  if (nsContentUtils::ShouldResistFingerprinting()) {
461
0
    return NS_OK;
462
0
  }
463
0
464
0
  PresentationConnectionAvailableEventInit init;
465
0
  init.mConnection = aConnection;
466
0
467
0
  RefPtr<PresentationConnectionAvailableEvent> event =
468
0
    PresentationConnectionAvailableEvent::Constructor(this,
469
0
                                                      NS_LITERAL_STRING("connectionavailable"),
470
0
                                                      init);
471
0
  if (NS_WARN_IF(!event)) {
472
0
    return NS_ERROR_FAILURE;
473
0
  }
474
0
  event->SetTrusted(true);
475
0
476
0
  RefPtr<AsyncEventDispatcher> asyncDispatcher =
477
0
    new AsyncEventDispatcher(this, event);
478
0
  return asyncDispatcher->PostDOMEvent();
479
0
}
480
481
void
482
PresentationRequest::NotifyPromiseSettled()
483
0
{
484
0
  PRES_DEBUG("%s\n", __func__);
485
0
486
0
  if (!GetOwner()) {
487
0
    return;
488
0
  }
489
0
490
0
  RefPtr<Navigator> navigator = nsGlobalWindowInner::Cast(GetOwner())->Navigator();
491
0
  if (!navigator) {
492
0
    return;
493
0
  }
494
0
495
0
  ErrorResult rv;
496
0
  RefPtr<Presentation> presentation = navigator->GetPresentation(rv);
497
0
498
0
  if (presentation) {
499
0
    presentation->SetStartSessionUnsettled(false);
500
0
  }
501
0
}
502
503
bool
504
PresentationRequest::IsProhibitMixedSecurityContexts(nsIDocument* aDocument)
505
0
{
506
0
  MOZ_ASSERT(aDocument);
507
0
508
0
  if (nsContentUtils::IsChromeDoc(aDocument)) {
509
0
    return true;
510
0
  }
511
0
512
0
  nsCOMPtr<nsIDocument> doc = aDocument;
513
0
  while (doc && !nsContentUtils::IsChromeDoc(doc)) {
514
0
    if (nsContentUtils::HttpsStateIsModern(doc)) {
515
0
      return true;
516
0
    }
517
0
518
0
    doc = doc->GetParentDocument();
519
0
  }
520
0
521
0
  return false;
522
0
}
523
524
bool
525
PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl)
526
0
{
527
0
  nsCOMPtr<nsIURI> uri;
528
0
  if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aUrl))) {
529
0
    return false;
530
0
  }
531
0
532
0
  nsAutoCString scheme;
533
0
  nsresult rv = uri->GetScheme(scheme);
534
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
535
0
    return false;
536
0
  }
537
0
538
0
  if (scheme.EqualsLiteral("data")) {
539
0
    return true;
540
0
  }
541
0
542
0
  nsAutoCString uriSpec;
543
0
  rv = uri->GetSpec(uriSpec);
544
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
545
0
    return false;
546
0
  }
547
0
548
0
  if (uriSpec.EqualsLiteral("about:blank") ||
549
0
      uriSpec.EqualsLiteral("about:srcdoc")) {
550
0
    return true;
551
0
  }
552
0
553
0
  OriginAttributes attrs;
554
0
  nsCOMPtr<nsIPrincipal> principal =
555
0
    BasePrincipal::CreateCodebasePrincipal(uri, attrs);
556
0
  if (NS_WARN_IF(!principal)) {
557
0
    return false;
558
0
  }
559
0
560
0
  nsCOMPtr<nsIContentSecurityManager> csm =
561
0
    do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID);
562
0
  if (NS_WARN_IF(!csm)) {
563
0
    return false;
564
0
  }
565
0
566
0
  bool isTrustworthyOrigin = false;
567
0
  csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin);
568
0
  return isTrustworthyOrigin;
569
0
}
570
571
bool
572
PresentationRequest::IsAllURLAuthenticated()
573
0
{
574
0
  for (const auto& url : mUrls) {
575
0
    if (!IsPrioriAuthenticatedURL(url)) {
576
0
      return false;
577
0
    }
578
0
  }
579
0
580
0
  return true;
581
0
}