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