PresentationRequest.cpp 14.9 KB
Newer Older
1
2
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
4
5
6
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

7
8
#include "PresentationRequest.h"

9
10
#include <utility>

11
#include "AvailabilityCollection.h"
12
#include "ControllerConnectionCollection.h"
13
14
15
16
17
#include "Presentation.h"
#include "PresentationAvailability.h"
#include "PresentationCallbacks.h"
#include "PresentationLog.h"
#include "PresentationTransportBuilderConstructor.h"
18
#include "mozilla/BasePrincipal.h"
19
#include "mozilla/dom/Document.h"
20
#include "mozilla/dom/Navigator.h"
21
#include "mozilla/dom/PresentationConnectionAvailableEvent.h"
22
#include "mozilla/dom/PresentationRequestBinding.h"
23
#include "mozilla/dom/Promise.h"
24
#include "nsContentSecurityManager.h"
25
#include "nsCycleCollectionParticipant.h"
26
#include "nsGlobalWindow.h"
27
#include "nsIPresentationService.h"
28
#include "nsIURI.h"
29
#include "nsIUUIDGenerator.h"
30
#include "nsNetUtil.h"
31
#include "nsSandboxFlags.h"
32
33
34
35
36
37
38
39
#include "nsServiceManagerUtils.h"

using namespace mozilla;
using namespace mozilla::dom;

NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(PresentationRequest, DOMEventTargetHelper)

40
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationRequest)
41
42
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

43
static nsresult GetAbsoluteURL(const nsAString& aUrl, nsIURI* aBaseUri,
44
                               Document* aDocument, nsAString& aAbsoluteUrl) {
45
  nsCOMPtr<nsIURI> uri;
46
47
48
49
50
51
52
  nsresult rv;
  if (aDocument) {
    rv = NS_NewURI(getter_AddRefs(uri), aUrl,
                   aDocument->GetDocumentCharacterSet(), aBaseUri);
  } else {
    rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBaseUri);
  }
53
54
55
56
57
58
59
60
61
62
63
64
65

  if (NS_FAILED(rv)) {
    return rv;
  }

  nsAutoCString spec;
  uri->GetSpec(spec);

  aAbsoluteUrl = NS_ConvertUTF8toUTF16(spec);

  return NS_OK;
}

66
67
68
/* static */
already_AddRefed<PresentationRequest> PresentationRequest::Constructor(
    const GlobalObject& aGlobal, const nsAString& aUrl, ErrorResult& aRv) {
69
  Sequence<nsString> urls;
70
71
72
73
  if (!urls.AppendElement(aUrl, fallible)) {
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return nullptr;
  }
74
75
76
  return Constructor(aGlobal, urls, aRv);
}

77
78
79
80
/* static */
already_AddRefed<PresentationRequest> PresentationRequest::Constructor(
    const GlobalObject& aGlobal, const Sequence<nsString>& aUrls,
    ErrorResult& aRv) {
81
82
  nsCOMPtr<nsPIDOMWindowInner> window =
      do_QueryInterface(aGlobal.GetAsSupports());
83
84
85
86
87
  if (!window) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

88
89
  if (aUrls.IsEmpty()) {
    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
90
91
92
    return nullptr;
  }

93
94
  // Resolve relative URL to absolute URL
  nsCOMPtr<nsIURI> baseUri = window->GetDocBaseURI();
95
96
97
98
  nsTArray<nsString> urls;
  for (const auto& url : aUrls) {
    nsAutoString absoluteUrl;
    nsresult rv =
99
        GetAbsoluteURL(url, baseUri, window->GetExtantDoc(), absoluteUrl);
100
101
102
103
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return nullptr;
    }
104

105
    urls.AppendElement(absoluteUrl);
106
107
108
  }

  RefPtr<PresentationRequest> request =
109
      new PresentationRequest(window, std::move(urls));
110
111
112
  return NS_WARN_IF(!request->Init()) ? nullptr : request.forget();
}

113
PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow,
114
                                         nsTArray<nsString>&& aUrls)
115
    : DOMEventTargetHelper(aWindow), mUrls(std::move(aUrls)) {}
116

117
PresentationRequest::~PresentationRequest() = default;
118

119
bool PresentationRequest::Init() { return true; }
120

121
122
123
/* virtual */
JSObject* PresentationRequest::WrapObject(JSContext* aCx,
                                          JS::Handle<JSObject*> aGivenProto) {
124
  return PresentationRequest_Binding::Wrap(aCx, this, aGivenProto);
125
126
}

127
already_AddRefed<Promise> PresentationRequest::Start(ErrorResult& aRv) {
128
  return StartWithDevice(VoidString(), aRv);
129
130
}

131
132
already_AddRefed<Promise> PresentationRequest::StartWithDevice(
    const nsAString& aDeviceId, ErrorResult& aRv) {
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
  if (NS_WARN_IF(!global)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  // Get the origin.
  nsAutoString origin;
  nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aRv.Throw(rv);
    return nullptr;
  }

147
  nsCOMPtr<Document> doc = GetOwner()->GetExtantDoc();
148
149
150
151
152
  if (NS_WARN_IF(!doc)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

153
  RefPtr<Promise> promise = Promise::Create(global, aRv);
154
155
156
157
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

158
159
160
161
162
  if (nsContentUtils::ShouldResistFingerprinting()) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

163
  if (IsProhibitMixedSecurityContexts(doc) && !IsAllURLAuthenticated()) {
164
165
166
167
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

168
169
170
171
172
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

173
174
  RefPtr<Navigator> navigator =
      nsGlobalWindowInner::Cast(GetOwner())->Navigator();
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  RefPtr<Presentation> presentation = navigator->GetPresentation(aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  if (presentation->IsStartSessionUnsettled()) {
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return promise.forget();
  }

189
190
  // Generate a session ID.
  nsCOMPtr<nsIUUIDGenerator> uuidgen =
191
192
      do_GetService("@mozilla.org/uuid-generator;1");
  if (NS_WARN_IF(!uuidgen)) {
193
194
195
196
197
198
199
200
201
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return promise.forget();
  }

  nsID uuid;
  uuidgen->GenerateUUIDInPlace(&uuid);
  char buffer[NSID_LENGTH];
  uuid.ToProvidedString(buffer);
  nsAutoString id;
202
  CopyASCIItoUTF16(MakeSpan(buffer, NSID_LENGTH - 1), id);
203
204

  nsCOMPtr<nsIPresentationService> service =
205
206
      do_GetService(PRESENTATION_SERVICE_CONTRACTID);
  if (NS_WARN_IF(!service)) {
207
208
209
210
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return promise.forget();
  }

211
212
  presentation->SetStartSessionUnsettled(true);

213
214
215
216
  // Get xul:browser element in parent process or nsWindowRoot object in child
  // process. If it's in child process, the corresponding xul:browser element
  // will be obtained at PresentationRequestParent::DoRequest in its parent
  // process.
217
  nsCOMPtr<EventTarget> handler = GetOwner()->GetChromeEventHandler();
218
  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
219
  nsCOMPtr<nsIPresentationServiceCallback> callback =
220
      new PresentationRequesterCallback(this, id, promise);
221
  nsCOMPtr<nsIPresentationTransportBuilderConstructor> constructor =
222
223
224
225
      PresentationTransportBuilderConstructor::Create();
  rv = service->StartSession(mUrls, id, origin, aDeviceId,
                             GetOwner()->WindowID(), handler, principal,
                             callback, constructor);
226
227
  if (NS_WARN_IF(NS_FAILED(rv))) {
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
228
    NotifyPromiseSettled();
229
230
231
232
233
  }

  return promise.forget();
}

234
235
already_AddRefed<Promise> PresentationRequest::Reconnect(
    const nsAString& aPresentationId, ErrorResult& aRv) {
236
237
238
239
240
241
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
  if (NS_WARN_IF(!global)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

242
  nsCOMPtr<Document> doc = GetOwner()->GetExtantDoc();
243
244
245
246
247
248
249
250
251
252
  if (NS_WARN_IF(!doc)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  RefPtr<Promise> promise = Promise::Create(global, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

253
254
255
256
257
  if (nsContentUtils::ShouldResistFingerprinting()) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

258
  if (IsProhibitMixedSecurityContexts(doc) && !IsAllURLAuthenticated()) {
259
260
261
262
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

263
264
265
266
267
268
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

  nsString presentationId = nsString(aPresentationId);
269
  nsCOMPtr<nsIRunnable> r = NewRunnableMethod<nsString, RefPtr<Promise>>(
270
271
272
      "dom::PresentationRequest::FindOrCreatePresentationConnection", this,
      &PresentationRequest::FindOrCreatePresentationConnection, presentationId,
      promise);
273
274
275
276
277
278
279
280

  if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
    promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
  }

  return promise.forget();
}

281
282
void PresentationRequest::FindOrCreatePresentationConnection(
    const nsAString& aPresentationId, Promise* aPromise) {
283
284
285
286
287
288
289
290
  MOZ_ASSERT(aPromise);

  if (NS_WARN_IF(!GetOwner())) {
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return;
  }

  RefPtr<PresentationConnection> connection =
291
292
293
      ControllerConnectionCollection::GetSingleton()->FindConnection(
          GetOwner()->WindowID(), aPresentationId,
          nsIPresentationService::ROLE_CONTROLLER);
294
295
296
297

  if (connection) {
    nsAutoString url;
    connection->GetUrl(url);
298
    if (mUrls.Contains(url)) {
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
      switch (connection->State()) {
        case PresentationConnectionState::Closed:
          // We found the matched connection.
          break;
        case PresentationConnectionState::Connecting:
        case PresentationConnectionState::Connected:
          aPromise->MaybeResolve(connection);
          return;
        case PresentationConnectionState::Terminated:
          // A terminated connection cannot be reused.
          connection = nullptr;
          break;
        default:
          MOZ_CRASH("Unknown presentation session state.");
          return;
      }
    } else {
      connection = nullptr;
    }
  }

  nsCOMPtr<nsIPresentationService> service =
321
322
      do_GetService(PRESENTATION_SERVICE_CONTRACTID);
  if (NS_WARN_IF(!service)) {
323
324
325
326
327
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return;
  }

  nsCOMPtr<nsIPresentationServiceCallback> callback =
328
329
330
331
332
333
      new PresentationReconnectCallback(this, aPresentationId, aPromise,
                                        connection);

  nsresult rv = service->ReconnectSession(
      mUrls, aPresentationId, nsIPresentationService::ROLE_CONTROLLER,
      callback);
334
335
336
337
338
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
  }
}

339
340
already_AddRefed<Promise> PresentationRequest::GetAvailability(
    ErrorResult& aRv) {
341
  PRES_DEBUG("%s\n", __func__);
342
343
344
345
346
347
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
  if (NS_WARN_IF(!global)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

348
  nsCOMPtr<Document> doc = GetOwner()->GetExtantDoc();
349
350
351
352
353
  if (NS_WARN_IF(!doc)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

354
  RefPtr<Promise> promise = Promise::Create(global, aRv);
355
356
357
358
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

359
360
361
362
363
  if (nsContentUtils::ShouldResistFingerprinting()) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

364
  if (IsProhibitMixedSecurityContexts(doc) && !IsAllURLAuthenticated()) {
365
366
367
368
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

369
370
371
372
373
  if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
    promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
    return promise.forget();
  }

374
375
376
377
378
  FindOrCreatePresentationAvailability(promise);

  return promise.forget();
}

379
380
void PresentationRequest::FindOrCreatePresentationAvailability(
    RefPtr<Promise>& aPromise) {
381
382
383
384
385
386
387
388
389
390
391
392
  MOZ_ASSERT(aPromise);

  if (NS_WARN_IF(!GetOwner())) {
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return;
  }

  AvailabilityCollection* collection = AvailabilityCollection::GetSingleton();
  if (NS_WARN_IF(!collection)) {
    aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
    return;
  }
393
394

  RefPtr<PresentationAvailability> availability =
395
      collection->Find(GetOwner()->WindowID(), mUrls);
396
397

  if (!availability) {
398
399
    availability =
        PresentationAvailability::Create(GetOwner(), mUrls, aPromise);
400
  } else {
401
    PRES_DEBUG(">resolve with same object\n");
402
403
404
405
406
407
408
409
410

    // Fetching cached available devices is asynchronous in our implementation,
    // we need to ensure the promise is resolved in order.
    if (availability->IsCachedValueReady()) {
      aPromise->MaybeResolve(availability);
      return;
    }

    availability->EnqueuePromise(aPromise);
411
412
  }

413
414
415
416
  if (!availability) {
    aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    return;
  }
417
}
418

419
420
nsresult PresentationRequest::DispatchConnectionAvailableEvent(
    PresentationConnection* aConnection) {
421
422
423
424
  if (nsContentUtils::ShouldResistFingerprinting()) {
    return NS_OK;
  }

425
426
  PresentationConnectionAvailableEventInit init;
  init.mConnection = aConnection;
427

428
  RefPtr<PresentationConnectionAvailableEvent> event =
429
430
      PresentationConnectionAvailableEvent::Constructor(
          this, NS_LITERAL_STRING("connectionavailable"), init);
431
432
433
434
435
  if (NS_WARN_IF(!event)) {
    return NS_ERROR_FAILURE;
  }
  event->SetTrusted(true);

436
  RefPtr<AsyncEventDispatcher> asyncDispatcher =
437
      new AsyncEventDispatcher(this, event);
438
439
  return asyncDispatcher->PostDOMEvent();
}
440

441
void PresentationRequest::NotifyPromiseSettled() {
442
443
444
445
446
447
  PRES_DEBUG("%s\n", __func__);

  if (!GetOwner()) {
    return;
  }

448
449
  RefPtr<Navigator> navigator =
      nsGlobalWindowInner::Cast(GetOwner())->Navigator();
450
451
452
453
  if (!navigator) {
    return;
  }

454
  ErrorResult rv;
455
456
457
458
459
460
461
  RefPtr<Presentation> presentation = navigator->GetPresentation(rv);

  if (presentation) {
    presentation->SetStartSessionUnsettled(false);
  }
}

462
bool PresentationRequest::IsProhibitMixedSecurityContexts(Document* aDocument) {
463
464
465
466
467
468
  MOZ_ASSERT(aDocument);

  if (nsContentUtils::IsChromeDoc(aDocument)) {
    return true;
  }

469
  nsCOMPtr<Document> doc = aDocument;
470
  while (doc && !nsContentUtils::IsChromeDoc(doc)) {
471
472
    if (nsContentUtils::HttpsStateIsModern(doc) ||
        nsContentUtils::DocumentHasOnionURI(doc)) {
473
474
475
      return true;
    }

476
    doc = doc->GetInProcessParentDocument();
477
478
479
480
481
  }

  return false;
}

482
bool PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl) {
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
  nsCOMPtr<nsIURI> uri;
  if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aUrl))) {
    return false;
  }

  nsAutoCString scheme;
  nsresult rv = uri->GetScheme(scheme);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  if (scheme.EqualsLiteral("data")) {
    return true;
  }

  nsAutoCString uriSpec;
  rv = uri->GetSpec(uriSpec);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return false;
  }

  if (uriSpec.EqualsLiteral("about:blank") ||
      uriSpec.EqualsLiteral("about:srcdoc")) {
    return true;
  }

509
  OriginAttributes attrs;
510
  nsCOMPtr<nsIPrincipal> principal =
511
      BasePrincipal::CreateContentPrincipal(uri, attrs);
512
513
514
515
  if (NS_WARN_IF(!principal)) {
    return false;
  }

516
  return principal->GetIsOriginPotentiallyTrustworthy();
517
}
518

519
bool PresentationRequest::IsAllURLAuthenticated() {
520
521
522
523
524
525
526
527
  for (const auto& url : mUrls) {
    if (!IsPrioriAuthenticatedURL(url)) {
      return false;
    }
  }

  return true;
}