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 "PresentationSessionInfo.h"
8
9 #include <utility>
10
11 #include "PresentationLog.h"
12 #include "PresentationService.h"
13 #include "mozilla/Logging.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/dom/BrowserParent.h"
16 #include "mozilla/dom/ContentParent.h"
17 #include "mozilla/dom/HTMLIFrameElementBinding.h"
18 #include "nsContentUtils.h"
19 #include "nsFrameLoader.h"
20 #include "nsFrameLoaderOwner.h"
21 #include "nsGlobalWindow.h"
22 #include "nsIDocShell.h"
23 #include "nsIMutableArray.h"
24 #include "nsINetAddr.h"
25 #include "nsISocketTransport.h"
26 #include "nsISupportsPrimitives.h"
27 #include "nsNetCID.h"
28 #include "nsQueryObject.h"
29 #include "nsServiceManagerUtils.h"
30 #include "nsThreadUtils.h"
31
32 #ifdef MOZ_WIDGET_ANDROID
33 # include "nsIPresentationNetworkHelper.h"
34 #endif // MOZ_WIDGET_ANDROID
35
36 using namespace mozilla;
37 using namespace mozilla::dom;
38 using namespace mozilla::services;
39
40 /*
41 * Implementation of PresentationChannelDescription
42 */
43
44 namespace mozilla {
45 namespace dom {
46
47 #ifdef MOZ_WIDGET_ANDROID
48
49 namespace {
50
51 class PresentationNetworkHelper final
52 : public nsIPresentationNetworkHelperListener {
53 public:
54 NS_DECL_ISUPPORTS
55 NS_DECL_NSIPRESENTATIONNETWORKHELPERLISTENER
56
57 using Function = nsresult (PresentationControllingInfo::*)(const nsACString&);
58
59 explicit PresentationNetworkHelper(PresentationControllingInfo* aInfo,
60 const Function& aFunc);
61
62 nsresult GetWifiIPAddress();
63
64 private:
65 ~PresentationNetworkHelper() = default;
66
67 RefPtr<PresentationControllingInfo> mInfo;
68 Function mFunc;
69 };
70
NS_IMPL_ISUPPORTS(PresentationNetworkHelper,nsIPresentationNetworkHelperListener)71 NS_IMPL_ISUPPORTS(PresentationNetworkHelper,
72 nsIPresentationNetworkHelperListener)
73
74 PresentationNetworkHelper::PresentationNetworkHelper(
75 PresentationControllingInfo* aInfo, const Function& aFunc)
76 : mInfo(aInfo), mFunc(aFunc) {
77 MOZ_ASSERT(aInfo);
78 MOZ_ASSERT(aFunc);
79 }
80
GetWifiIPAddress()81 nsresult PresentationNetworkHelper::GetWifiIPAddress() {
82 nsresult rv;
83
84 nsCOMPtr<nsIPresentationNetworkHelper> networkHelper =
85 do_GetService(PRESENTATION_NETWORK_HELPER_CONTRACTID, &rv);
86 if (NS_WARN_IF(NS_FAILED(rv))) {
87 return rv;
88 }
89
90 return networkHelper->GetWifiIPAddress(this);
91 }
92
93 NS_IMETHODIMP
OnError(const nsACString & aReason)94 PresentationNetworkHelper::OnError(const nsACString& aReason) {
95 PRES_ERROR("PresentationNetworkHelper::OnError: %s",
96 nsPromiseFlatCString(aReason).get());
97 return NS_OK;
98 }
99
100 NS_IMETHODIMP
OnGetWifiIPAddress(const nsACString & aIPAddress)101 PresentationNetworkHelper::OnGetWifiIPAddress(const nsACString& aIPAddress) {
102 MOZ_ASSERT(mInfo);
103 MOZ_ASSERT(mFunc);
104
105 NS_DispatchToMainThread(NewRunnableMethod<nsCString>(
106 "dom::PresentationNetworkHelper::OnGetWifiIPAddress", mInfo, mFunc,
107 aIPAddress));
108 return NS_OK;
109 }
110
111 } // anonymous namespace
112
113 #endif // MOZ_WIDGET_ANDROID
114
115 class TCPPresentationChannelDescription final
116 : public nsIPresentationChannelDescription {
117 public:
118 NS_DECL_ISUPPORTS
119 NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION
120
TCPPresentationChannelDescription(const nsACString & aAddress,uint16_t aPort)121 TCPPresentationChannelDescription(const nsACString& aAddress, uint16_t aPort)
122 : mAddress(aAddress), mPort(aPort) {}
123
124 private:
125 ~TCPPresentationChannelDescription() = default;
126
127 nsCString mAddress;
128 uint16_t mPort;
129 };
130
131 } // namespace dom
132 } // namespace mozilla
133
NS_IMPL_ISUPPORTS(TCPPresentationChannelDescription,nsIPresentationChannelDescription)134 NS_IMPL_ISUPPORTS(TCPPresentationChannelDescription,
135 nsIPresentationChannelDescription)
136
137 NS_IMETHODIMP
138 TCPPresentationChannelDescription::GetType(uint8_t* aRetVal) {
139 if (NS_WARN_IF(!aRetVal)) {
140 return NS_ERROR_INVALID_POINTER;
141 }
142
143 *aRetVal = nsIPresentationChannelDescription::TYPE_TCP;
144 return NS_OK;
145 }
146
147 NS_IMETHODIMP
GetTcpAddress(nsIArray ** aRetVal)148 TCPPresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal) {
149 if (NS_WARN_IF(!aRetVal)) {
150 return NS_ERROR_INVALID_POINTER;
151 }
152
153 nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
154 if (NS_WARN_IF(!array)) {
155 return NS_ERROR_OUT_OF_MEMORY;
156 }
157
158 // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
159 // into account. And at the first stage Presentation API is only exposed on
160 // Firefox OS where the first IP appears enough for most scenarios.
161 nsCOMPtr<nsISupportsCString> address =
162 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
163 if (NS_WARN_IF(!address)) {
164 return NS_ERROR_OUT_OF_MEMORY;
165 }
166 address->SetData(mAddress);
167
168 array->AppendElement(address);
169 array.forget(aRetVal);
170
171 return NS_OK;
172 }
173
174 NS_IMETHODIMP
GetTcpPort(uint16_t * aRetVal)175 TCPPresentationChannelDescription::GetTcpPort(uint16_t* aRetVal) {
176 if (NS_WARN_IF(!aRetVal)) {
177 return NS_ERROR_INVALID_POINTER;
178 }
179
180 *aRetVal = mPort;
181 return NS_OK;
182 }
183
184 NS_IMETHODIMP
GetDataChannelSDP(nsAString & aDataChannelSDP)185 TCPPresentationChannelDescription::GetDataChannelSDP(
186 nsAString& aDataChannelSDP) {
187 aDataChannelSDP.Truncate();
188 return NS_OK;
189 }
190
191 /*
192 * Implementation of PresentationSessionInfo
193 */
194
195 NS_IMPL_ISUPPORTS(PresentationSessionInfo,
196 nsIPresentationSessionTransportCallback,
197 nsIPresentationControlChannelListener,
198 nsIPresentationSessionTransportBuilderListener);
199
200 /* virtual */
Init(nsIPresentationControlChannel * aControlChannel)201 nsresult PresentationSessionInfo::Init(
202 nsIPresentationControlChannel* aControlChannel) {
203 SetControlChannel(aControlChannel);
204 return NS_OK;
205 }
206
207 /* virtual */
Shutdown(nsresult aReason)208 void PresentationSessionInfo::Shutdown(nsresult aReason) {
209 PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
210 NS_ConvertUTF16toUTF8(mSessionId).get(),
211 static_cast<uint32_t>(aReason), mRole);
212
213 NS_WARNING_ASSERTION(NS_SUCCEEDED(aReason), "bad reason");
214
215 // Close the control channel if any.
216 if (mControlChannel) {
217 Unused << NS_WARN_IF(NS_FAILED(mControlChannel->Disconnect(aReason)));
218 }
219
220 // Close the data transport channel if any.
221 if (mTransport) {
222 // |mIsTransportReady| will be unset once |NotifyTransportClosed| is called.
223 Unused << NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
224 }
225
226 mIsResponderReady = false;
227 mIsOnTerminating = false;
228
229 ResetBuilder();
230 }
231
SetListener(nsIPresentationSessionListener * aListener)232 nsresult PresentationSessionInfo::SetListener(
233 nsIPresentationSessionListener* aListener) {
234 mListener = aListener;
235
236 if (mListener) {
237 // Enable data notification for the transport channel if it's available.
238 if (mTransport) {
239 nsresult rv = mTransport->EnableDataNotification();
240 if (NS_WARN_IF(NS_FAILED(rv))) {
241 return rv;
242 }
243 }
244
245 // The transport might become ready, or might become un-ready again, before
246 // the listener has registered. So notify the listener of the state change.
247 return mListener->NotifyStateChange(mSessionId, mState, mReason);
248 }
249
250 return NS_OK;
251 }
252
Send(const nsAString & aData)253 nsresult PresentationSessionInfo::Send(const nsAString& aData) {
254 if (NS_WARN_IF(!IsSessionReady())) {
255 return NS_ERROR_DOM_INVALID_STATE_ERR;
256 }
257
258 if (NS_WARN_IF(!mTransport)) {
259 return NS_ERROR_NOT_AVAILABLE;
260 }
261
262 return mTransport->Send(aData);
263 }
264
SendBinaryMsg(const nsACString & aData)265 nsresult PresentationSessionInfo::SendBinaryMsg(const nsACString& aData) {
266 if (NS_WARN_IF(!IsSessionReady())) {
267 return NS_ERROR_DOM_INVALID_STATE_ERR;
268 }
269
270 if (NS_WARN_IF(!mTransport)) {
271 return NS_ERROR_NOT_AVAILABLE;
272 }
273
274 return mTransport->SendBinaryMsg(aData);
275 }
276
SendBlob(Blob * aBlob)277 nsresult PresentationSessionInfo::SendBlob(Blob* aBlob) {
278 if (NS_WARN_IF(!IsSessionReady())) {
279 return NS_ERROR_DOM_INVALID_STATE_ERR;
280 }
281
282 if (NS_WARN_IF(!mTransport)) {
283 return NS_ERROR_NOT_AVAILABLE;
284 }
285
286 return mTransport->SendBlob(aBlob);
287 }
288
Close(nsresult aReason,uint32_t aState)289 nsresult PresentationSessionInfo::Close(nsresult aReason, uint32_t aState) {
290 // Do nothing if session is already terminated.
291 if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
292 return NS_OK;
293 }
294
295 SetStateWithReason(aState, aReason);
296
297 switch (aState) {
298 case nsIPresentationSessionListener::STATE_CLOSED: {
299 Shutdown(aReason);
300 break;
301 }
302 case nsIPresentationSessionListener::STATE_TERMINATED: {
303 if (!mControlChannel) {
304 nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
305 nsresult rv =
306 mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
307 if (NS_FAILED(rv)) {
308 Shutdown(rv);
309 return rv;
310 }
311
312 SetControlChannel(ctrlChannel);
313 return rv;
314 }
315
316 ContinueTermination();
317 return NS_OK;
318 }
319 }
320
321 return NS_OK;
322 }
323
OnTerminate(nsIPresentationControlChannel * aControlChannel)324 void PresentationSessionInfo::OnTerminate(
325 nsIPresentationControlChannel* aControlChannel) {
326 mIsOnTerminating = true; // Mark for terminating transport channel
327 SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, NS_OK);
328 SetControlChannel(aControlChannel);
329 }
330
ReplySuccess()331 nsresult PresentationSessionInfo::ReplySuccess() {
332 SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTED, NS_OK);
333 return NS_OK;
334 }
335
ReplyError(nsresult aError)336 nsresult PresentationSessionInfo::ReplyError(nsresult aError) {
337 Shutdown(aError);
338
339 // Remove itself since it never succeeds.
340 return UntrackFromService();
341 }
342
343 /* virtual */
UntrackFromService()344 nsresult PresentationSessionInfo::UntrackFromService() {
345 nsCOMPtr<nsIPresentationService> service =
346 do_GetService(PRESENTATION_SERVICE_CONTRACTID);
347 if (NS_WARN_IF(!service)) {
348 return NS_ERROR_NOT_AVAILABLE;
349 }
350 static_cast<PresentationService*>(service.get())
351 ->UntrackSessionInfo(mSessionId, mRole);
352
353 return NS_OK;
354 }
355
GetWindow()356 nsPIDOMWindowInner* PresentationSessionInfo::GetWindow() {
357 nsCOMPtr<nsIPresentationService> service =
358 do_GetService(PRESENTATION_SERVICE_CONTRACTID);
359 if (NS_WARN_IF(!service)) {
360 return nullptr;
361 }
362 uint64_t windowId = 0;
363 if (NS_WARN_IF(NS_FAILED(
364 service->GetWindowIdBySessionId(mSessionId, mRole, &windowId)))) {
365 return nullptr;
366 }
367
368 return nsGlobalWindowInner::GetInnerWindowWithId(windowId);
369 }
370
371 /* virtual */
IsAccessible(base::ProcessId aProcessId)372 bool PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId) {
373 // No restriction by default.
374 return true;
375 }
376
ContinueTermination()377 void PresentationSessionInfo::ContinueTermination() {
378 MOZ_ASSERT(NS_IsMainThread());
379 MOZ_ASSERT(mControlChannel);
380
381 if (NS_WARN_IF(NS_FAILED(mControlChannel->Terminate(mSessionId))) ||
382 mIsOnTerminating) {
383 Shutdown(NS_OK);
384 }
385 }
386
387 // nsIPresentationSessionTransportCallback
388 NS_IMETHODIMP
NotifyTransportReady()389 PresentationSessionInfo::NotifyTransportReady() {
390 PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
391 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
392
393 MOZ_ASSERT(NS_IsMainThread());
394
395 if (mState != nsIPresentationSessionListener::STATE_CONNECTING &&
396 mState != nsIPresentationSessionListener::STATE_CONNECTED) {
397 return NS_OK;
398 }
399
400 mIsTransportReady = true;
401
402 // Established RTCDataChannel implies responder is ready.
403 if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
404 mIsResponderReady = true;
405 }
406
407 // At sender side, session might not be ready at this point (waiting for
408 // receiver's answer). Yet at receiver side, session must be ready at this
409 // point since the data transport channel is created after the receiver page
410 // is ready for presentation use.
411 if (IsSessionReady()) {
412 return ReplySuccess();
413 }
414
415 return NS_OK;
416 }
417
418 NS_IMETHODIMP
NotifyTransportClosed(nsresult aReason)419 PresentationSessionInfo::NotifyTransportClosed(nsresult aReason) {
420 PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
421 NS_ConvertUTF16toUTF8(mSessionId).get(),
422 static_cast<uint32_t>(aReason), mRole);
423
424 MOZ_ASSERT(NS_IsMainThread());
425
426 // Nullify |mTransport| here so it won't try to re-close |mTransport| in
427 // potential subsequent |Shutdown| calls.
428 mTransport = nullptr;
429
430 if (NS_WARN_IF(!IsSessionReady() &&
431 mState == nsIPresentationSessionListener::STATE_CONNECTING)) {
432 // It happens before the session is ready. Reply the callback.
433 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
434 }
435
436 // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
437 mIsTransportReady = false;
438
439 if (mState == nsIPresentationSessionListener::STATE_CONNECTED) {
440 // The transport channel is closed unexpectedly (not caused by a |Close|
441 // call).
442 SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
443 }
444
445 Shutdown(aReason);
446
447 if (mState == nsIPresentationSessionListener::STATE_TERMINATED) {
448 // Directly untrack the session info from the service.
449 return UntrackFromService();
450 }
451
452 return NS_OK;
453 }
454
455 NS_IMETHODIMP
NotifyData(const nsACString & aData,bool aIsBinary)456 PresentationSessionInfo::NotifyData(const nsACString& aData, bool aIsBinary) {
457 MOZ_ASSERT(NS_IsMainThread());
458
459 if (NS_WARN_IF(!IsSessionReady())) {
460 return NS_ERROR_DOM_INVALID_STATE_ERR;
461 }
462
463 if (NS_WARN_IF(!mListener)) {
464 return NS_ERROR_NOT_AVAILABLE;
465 }
466
467 return mListener->NotifyMessage(mSessionId, aData, aIsBinary);
468 }
469
470 // nsIPresentationSessionTransportBuilderListener
471 NS_IMETHODIMP
OnSessionTransport(nsIPresentationSessionTransport * aTransport)472 PresentationSessionInfo::OnSessionTransport(
473 nsIPresentationSessionTransport* aTransport) {
474 PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
475 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
476
477 ResetBuilder();
478
479 if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
480 return NS_ERROR_FAILURE;
481 }
482
483 if (NS_WARN_IF(!aTransport)) {
484 return NS_ERROR_INVALID_ARG;
485 }
486
487 mTransport = aTransport;
488
489 nsresult rv = mTransport->SetCallback(this);
490 if (NS_WARN_IF(NS_FAILED(rv))) {
491 return rv;
492 }
493
494 if (mListener) {
495 mTransport->EnableDataNotification();
496 }
497
498 return NS_OK;
499 }
500
501 NS_IMETHODIMP
OnError(nsresult aReason)502 PresentationSessionInfo::OnError(nsresult aReason) {
503 PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
504 NS_ConvertUTF16toUTF8(mSessionId).get(),
505 static_cast<uint32_t>(aReason), mRole);
506
507 ResetBuilder();
508 return ReplyError(aReason);
509 }
510
511 NS_IMETHODIMP
SendOffer(nsIPresentationChannelDescription * aOffer)512 PresentationSessionInfo::SendOffer(nsIPresentationChannelDescription* aOffer) {
513 return mControlChannel->SendOffer(aOffer);
514 }
515
516 NS_IMETHODIMP
SendAnswer(nsIPresentationChannelDescription * aAnswer)517 PresentationSessionInfo::SendAnswer(
518 nsIPresentationChannelDescription* aAnswer) {
519 return mControlChannel->SendAnswer(aAnswer);
520 }
521
522 NS_IMETHODIMP
SendIceCandidate(const nsAString & candidate)523 PresentationSessionInfo::SendIceCandidate(const nsAString& candidate) {
524 return mControlChannel->SendIceCandidate(candidate);
525 }
526
527 NS_IMETHODIMP
Close(nsresult reason)528 PresentationSessionInfo::Close(nsresult reason) {
529 return mControlChannel->Disconnect(reason);
530 }
531
532 /**
533 * Implementation of PresentationControllingInfo
534 *
535 * During presentation session establishment, the sender expects the following
536 * after trying to establish the control channel: (The order between step 3 and
537 * 4 is not guaranteed.)
538 * 1. |Init| is called to open a socket |mServerSocket| for data transport
539 * channel.
540 * 2. |NotifyConnected| of |nsIPresentationControlChannelListener| is called to
541 * indicate the control channel is ready to use. Then send the offer to the
542 * receiver via the control channel.
543 * 3.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the
544 * data transport channel is connected. Then initialize |mTransport|.
545 * 3.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
546 * called.
547 * 4. |OnAnswer| of |nsIPresentationControlChannelListener| is called to
548 * indicate the receiver is ready. Close the control channel since it's no
549 * longer needed.
550 * 5. Once both step 3 and 4 are done, the presentation session is ready to use.
551 * So notify the listener of CONNECTED state.
552 */
553
NS_IMPL_ISUPPORTS_INHERITED(PresentationControllingInfo,PresentationSessionInfo,nsIServerSocketListener)554 NS_IMPL_ISUPPORTS_INHERITED(PresentationControllingInfo,
555 PresentationSessionInfo, nsIServerSocketListener)
556
557 nsresult PresentationControllingInfo::Init(
558 nsIPresentationControlChannel* aControlChannel) {
559 PresentationSessionInfo::Init(aControlChannel);
560
561 // Initialize |mServerSocket| for bootstrapping the data transport channel and
562 // use |this| as the listener.
563 mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID);
564 if (NS_WARN_IF(!mServerSocket)) {
565 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
566 }
567
568 nsresult rv = mServerSocket->Init(-1, false, -1);
569 if (NS_WARN_IF(NS_FAILED(rv))) {
570 return rv;
571 }
572
573 rv = mServerSocket->AsyncListen(this);
574 if (NS_WARN_IF(NS_FAILED(rv))) {
575 return rv;
576 }
577
578 int32_t port;
579 rv = mServerSocket->GetPort(&port);
580 if (!NS_WARN_IF(NS_FAILED(rv))) {
581 PRES_DEBUG("%s:ServerSocket created.port[%d]\n", __func__, port);
582 }
583
584 return NS_OK;
585 }
586
Shutdown(nsresult aReason)587 void PresentationControllingInfo::Shutdown(nsresult aReason) {
588 PresentationSessionInfo::Shutdown(aReason);
589
590 // Close the server socket if any.
591 if (mServerSocket) {
592 Unused << NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
593 mServerSocket = nullptr;
594 }
595 }
596
GetAddress()597 nsresult PresentationControllingInfo::GetAddress() {
598 if (nsContentUtils::ShouldResistFingerprinting()) {
599 return NS_ERROR_FAILURE;
600 }
601
602 #if defined(MOZ_WIDGET_ANDROID)
603 RefPtr<PresentationNetworkHelper> networkHelper =
604 new PresentationNetworkHelper(this,
605 &PresentationControllingInfo::OnGetAddress);
606 nsresult rv = networkHelper->GetWifiIPAddress();
607 if (NS_WARN_IF(NS_FAILED(rv))) {
608 return rv;
609 }
610
611 #else
612 nsCOMPtr<nsINetworkInfoService> networkInfo =
613 do_GetService(NETWORKINFOSERVICE_CONTRACT_ID);
614 MOZ_ASSERT(networkInfo);
615
616 nsresult rv = networkInfo->ListNetworkAddresses(this);
617
618 if (NS_WARN_IF(NS_FAILED(rv))) {
619 return rv;
620 }
621 #endif
622
623 return NS_OK;
624 }
625
OnGetAddress(const nsACString & aAddress)626 nsresult PresentationControllingInfo::OnGetAddress(const nsACString& aAddress) {
627 MOZ_ASSERT(NS_IsMainThread());
628
629 if (NS_WARN_IF(!mServerSocket)) {
630 return NS_ERROR_FAILURE;
631 }
632 if (NS_WARN_IF(!mControlChannel)) {
633 return NS_ERROR_FAILURE;
634 }
635
636 // Prepare and send the offer.
637 int32_t port;
638 nsresult rv = mServerSocket->GetPort(&port);
639 if (NS_WARN_IF(NS_FAILED(rv))) {
640 return rv;
641 }
642
643 RefPtr<TCPPresentationChannelDescription> description =
644 new TCPPresentationChannelDescription(aAddress,
645 static_cast<uint16_t>(port));
646 return mControlChannel->SendOffer(description);
647 }
648
649 // nsIPresentationControlChannelListener
650 NS_IMETHODIMP
OnIceCandidate(const nsAString & aCandidate)651 PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate) {
652 if (mTransportType != nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
653 return NS_ERROR_FAILURE;
654 }
655
656 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
657 do_QueryInterface(mBuilder);
658
659 if (NS_WARN_IF(!builder)) {
660 return NS_ERROR_FAILURE;
661 }
662
663 return builder->OnIceCandidate(aCandidate);
664 }
665
666 NS_IMETHODIMP
OnOffer(nsIPresentationChannelDescription * aDescription)667 PresentationControllingInfo::OnOffer(
668 nsIPresentationChannelDescription* aDescription) {
669 MOZ_ASSERT(false, "Sender side should not receive offer.");
670 return NS_ERROR_FAILURE;
671 }
672
673 NS_IMETHODIMP
OnAnswer(nsIPresentationChannelDescription * aDescription)674 PresentationControllingInfo::OnAnswer(
675 nsIPresentationChannelDescription* aDescription) {
676 if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
677 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
678 do_QueryInterface(mBuilder);
679
680 if (NS_WARN_IF(!builder)) {
681 return NS_ERROR_FAILURE;
682 }
683
684 return builder->OnAnswer(aDescription);
685 }
686
687 mIsResponderReady = true;
688
689 // Close the control channel since it's no longer needed.
690 nsresult rv = mControlChannel->Disconnect(NS_OK);
691 if (NS_WARN_IF(NS_FAILED(rv))) {
692 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
693 }
694
695 // Session might not be ready at this moment (waiting for the establishment of
696 // the data transport channel).
697 if (IsSessionReady()) {
698 return ReplySuccess();
699 }
700
701 return NS_OK;
702 }
703
704 NS_IMETHODIMP
NotifyConnected()705 PresentationControllingInfo::NotifyConnected() {
706 PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
707 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
708
709 MOZ_ASSERT(NS_IsMainThread());
710
711 switch (mState) {
712 case nsIPresentationSessionListener::STATE_CONNECTING: {
713 if (mIsReconnecting) {
714 return ContinueReconnect();
715 }
716
717 nsresult rv = mControlChannel->Launch(GetSessionId(), GetUrl());
718 if (NS_WARN_IF(NS_FAILED(rv))) {
719 return rv;
720 }
721 Unused << NS_WARN_IF(NS_FAILED(BuildTransport()));
722 break;
723 }
724 case nsIPresentationSessionListener::STATE_TERMINATED: {
725 ContinueTermination();
726 break;
727 }
728 default:
729 break;
730 }
731
732 return NS_OK;
733 }
734
735 NS_IMETHODIMP
NotifyReconnected()736 PresentationControllingInfo::NotifyReconnected() {
737 PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
738 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
739
740 MOZ_ASSERT(NS_IsMainThread());
741
742 if (NS_WARN_IF(mState != nsIPresentationSessionListener::STATE_CONNECTING)) {
743 return NS_ERROR_FAILURE;
744 }
745
746 return NotifyReconnectResult(NS_OK);
747 }
748
BuildTransport()749 nsresult PresentationControllingInfo::BuildTransport() {
750 MOZ_ASSERT(NS_IsMainThread());
751
752 if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
753 return NS_OK;
754 }
755
756 if (NS_WARN_IF(!mBuilderConstructor)) {
757 return NS_ERROR_NOT_AVAILABLE;
758 }
759
760 if (!Preferences::GetBool(
761 "dom.presentation.session_transport.data_channel.enable")) {
762 // Build TCP session transport
763 return GetAddress();
764 }
765 /**
766 * Generally transport is maintained by the chrome process. However, data
767 * channel should be live with the DOM , which implies RTCDataChannel in an
768 * OOP page should be establish in the content process.
769 *
770 * |mBuilderConstructor| is responsible for creating a builder, which is for
771 * building a data channel transport.
772 *
773 * In the OOP case, |mBuilderConstructor| would create a builder which is
774 * an object of |PresentationBuilderParent|. So, |BuildDataChannelTransport|
775 * triggers an IPC call to make content process establish a RTCDataChannel
776 * transport.
777 */
778
779 mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
780 if (NS_WARN_IF(NS_FAILED(mBuilderConstructor->CreateTransportBuilder(
781 mTransportType, getter_AddRefs(mBuilder))))) {
782 return NS_ERROR_NOT_AVAILABLE;
783 }
784
785 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
786 dataChannelBuilder(do_QueryInterface(mBuilder));
787 if (NS_WARN_IF(!dataChannelBuilder)) {
788 return NS_ERROR_NOT_AVAILABLE;
789 }
790
791 // OOP window would be set from content process
792 nsPIDOMWindowInner* window = GetWindow();
793
794 nsresult rv = dataChannelBuilder->BuildDataChannelTransport(
795 nsIPresentationService::ROLE_CONTROLLER, window, this);
796 if (NS_WARN_IF(NS_FAILED(rv))) {
797 return rv;
798 }
799
800 return NS_OK;
801 }
802
803 NS_IMETHODIMP
NotifyDisconnected(nsresult aReason)804 PresentationControllingInfo::NotifyDisconnected(nsresult aReason) {
805 PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
806 NS_ConvertUTF16toUTF8(mSessionId).get(),
807 static_cast<uint32_t>(aReason), mRole);
808
809 MOZ_ASSERT(NS_IsMainThread());
810
811 if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
812 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
813 do_QueryInterface(mBuilder);
814 if (builder) {
815 Unused << NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
816 }
817 }
818
819 // Unset control channel here so it won't try to re-close it in potential
820 // subsequent |Shutdown| calls.
821 SetControlChannel(nullptr);
822
823 if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
824 // The presentation session instance may already exist.
825 // Change the state to CLOSED if it is not terminated.
826 if (nsIPresentationSessionListener::STATE_TERMINATED != mState) {
827 SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
828 }
829
830 // If |aReason| is NS_OK, it implies that the user closes the connection
831 // before becomming connected. No need to call |ReplyError| in this case.
832 if (NS_FAILED(aReason)) {
833 if (mIsReconnecting) {
834 NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
835 }
836 // Reply error for an abnormal close.
837 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
838 }
839 Shutdown(aReason);
840 }
841
842 // This is the case for reconnecting a connection which is in
843 // connecting state and |mTransport| is not ready.
844 if (mDoReconnectAfterClose && !mTransport) {
845 mDoReconnectAfterClose = false;
846 return Reconnect(mReconnectCallback);
847 }
848
849 return NS_OK;
850 }
851
852 // nsIServerSocketListener
853 NS_IMETHODIMP
OnSocketAccepted(nsIServerSocket * aServerSocket,nsISocketTransport * aTransport)854 PresentationControllingInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
855 nsISocketTransport* aTransport) {
856 int32_t port;
857 nsresult rv = aTransport->GetPort(&port);
858 if (!NS_WARN_IF(NS_FAILED(rv))) {
859 PRES_DEBUG("%s:receive from port[%d]\n", __func__, port);
860 }
861
862 MOZ_ASSERT(NS_IsMainThread());
863
864 if (NS_WARN_IF(!mBuilderConstructor)) {
865 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
866 }
867
868 // Initialize session transport builder and use |this| as the callback.
869 nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder;
870 if (NS_SUCCEEDED(mBuilderConstructor->CreateTransportBuilder(
871 nsIPresentationChannelDescription::TYPE_TCP,
872 getter_AddRefs(mBuilder)))) {
873 builder = do_QueryInterface(mBuilder);
874 }
875
876 if (NS_WARN_IF(!builder)) {
877 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
878 }
879
880 mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
881 return builder->BuildTCPSenderTransport(aTransport, this);
882 }
883
884 NS_IMETHODIMP
OnStopListening(nsIServerSocket * aServerSocket,nsresult aStatus)885 PresentationControllingInfo::OnStopListening(nsIServerSocket* aServerSocket,
886 nsresult aStatus) {
887 PRES_DEBUG("controller %s:status[%" PRIx32 "]\n", __func__,
888 static_cast<uint32_t>(aStatus));
889
890 MOZ_ASSERT(NS_IsMainThread());
891
892 if (aStatus ==
893 NS_BINDING_ABORTED) { // The server socket was manually closed.
894 return NS_OK;
895 }
896
897 Shutdown(aStatus);
898
899 if (NS_WARN_IF(!IsSessionReady())) {
900 // It happens before the session is ready. Reply the callback.
901 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
902 }
903
904 // It happens after the session is ready. Change the state to CLOSED.
905 SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aStatus);
906
907 return NS_OK;
908 }
909
910 /**
911 * The steps to reconnect a session are summarized below:
912 * 1. Change |mState| to CONNECTING.
913 * 2. Check whether |mControlChannel| is existed or not. Usually we have to
914 * create a new control cahnnel.
915 * 3.1 |mControlChannel| is null, which means we have to create a new one.
916 * |EstablishControlChannel| is called to create a new control channel.
917 * At this point, |mControlChannel| is not able to use yet. Set
918 * |mIsReconnecting| to true and wait until |NotifyConnected|.
919 * 3.2 |mControlChannel| is not null and is avaliable.
920 * We can just call |ContinueReconnect| to send reconnect command.
921 * 4. |NotifyReconnected| of |nsIPresentationControlChannelListener| is called
922 * to indicate the receiver is ready for reconnecting.
923 * 5. Once both step 3 and 4 are done, the rest is to build a new data
924 * transport channel by following the same steps as starting a
925 * new session.
926 */
927
Reconnect(nsIPresentationServiceCallback * aCallback)928 nsresult PresentationControllingInfo::Reconnect(
929 nsIPresentationServiceCallback* aCallback) {
930 PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
931 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
932
933 if (!aCallback) {
934 return NS_ERROR_INVALID_ARG;
935 }
936
937 mReconnectCallback = aCallback;
938
939 if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) {
940 return NotifyReconnectResult(NS_ERROR_DOM_INVALID_STATE_ERR);
941 }
942
943 // If |mState| is not CLOSED, we have to close the connection before
944 // reconnecting. The process to reconnect will be continued after
945 // |NotifyDisconnected| or |NotifyTransportClosed| is invoked.
946 if (mState == nsIPresentationSessionListener::STATE_CONNECTING ||
947 mState == nsIPresentationSessionListener::STATE_CONNECTED) {
948 mDoReconnectAfterClose = true;
949 return Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
950 }
951
952 // Make sure |mState| is closed at this point.
953 MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
954
955 mState = nsIPresentationSessionListener::STATE_CONNECTING;
956 mIsReconnecting = true;
957
958 nsresult rv = NS_OK;
959 if (!mControlChannel) {
960 nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
961 rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
962 if (NS_WARN_IF(NS_FAILED(rv))) {
963 return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
964 }
965
966 rv = Init(ctrlChannel);
967 if (NS_WARN_IF(NS_FAILED(rv))) {
968 return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
969 }
970 } else {
971 return ContinueReconnect();
972 }
973
974 return NS_OK;
975 }
976
ContinueReconnect()977 nsresult PresentationControllingInfo::ContinueReconnect() {
978 MOZ_ASSERT(NS_IsMainThread());
979 MOZ_ASSERT(mControlChannel);
980
981 mIsReconnecting = false;
982 if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl())))) {
983 return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
984 }
985
986 return NS_OK;
987 }
988
989 // nsIListNetworkAddressesListener
990 NS_IMETHODIMP
OnListedNetworkAddresses(const nsTArray<nsCString> & aAddressArray)991 PresentationControllingInfo::OnListedNetworkAddresses(
992 const nsTArray<nsCString>& aAddressArray) {
993 if (aAddressArray.IsEmpty()) {
994 return OnListNetworkAddressesFailed();
995 }
996
997 // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
998 // into account.
999
1000 // On Firefox desktop, the IP address is retrieved from a callback function.
1001 // To make consistent code sequence, following function call is dispatched
1002 // into main thread instead of calling it directly.
1003 NS_DispatchToMainThread(NewRunnableMethod<nsCString>(
1004 "dom::PresentationControllingInfo::OnGetAddress", this,
1005 &PresentationControllingInfo::OnGetAddress, aAddressArray[0]));
1006
1007 return NS_OK;
1008 }
1009
1010 NS_IMETHODIMP
OnListNetworkAddressesFailed()1011 PresentationControllingInfo::OnListNetworkAddressesFailed() {
1012 PRES_ERROR("PresentationControllingInfo:OnListNetworkAddressesFailed");
1013
1014 // In 1-UA case, transport channel can still be established
1015 // on loopback interface even if no network address available.
1016 NS_DispatchToMainThread(NewRunnableMethod<nsCString>(
1017 "dom::PresentationControllingInfo::OnGetAddress", this,
1018 &PresentationControllingInfo::OnGetAddress, "127.0.0.1"));
1019
1020 return NS_OK;
1021 }
1022
NotifyReconnectResult(nsresult aStatus)1023 nsresult PresentationControllingInfo::NotifyReconnectResult(nsresult aStatus) {
1024 if (!mReconnectCallback) {
1025 MOZ_ASSERT(false, "mReconnectCallback can not be null here.");
1026 return NS_ERROR_FAILURE;
1027 }
1028
1029 mIsReconnecting = false;
1030 nsCOMPtr<nsIPresentationServiceCallback> callback =
1031 std::move(mReconnectCallback);
1032 if (NS_FAILED(aStatus)) {
1033 return callback->NotifyError(aStatus);
1034 }
1035
1036 return callback->NotifySuccess(GetUrl());
1037 }
1038
1039 // nsIPresentationSessionTransportCallback
1040 NS_IMETHODIMP
NotifyTransportReady()1041 PresentationControllingInfo::NotifyTransportReady() {
1042 return PresentationSessionInfo::NotifyTransportReady();
1043 }
1044
1045 NS_IMETHODIMP
NotifyTransportClosed(nsresult aReason)1046 PresentationControllingInfo::NotifyTransportClosed(nsresult aReason) {
1047 if (!mDoReconnectAfterClose) {
1048 return PresentationSessionInfo::NotifyTransportClosed(aReason);
1049 ;
1050 }
1051
1052 MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
1053
1054 mTransport = nullptr;
1055 mIsTransportReady = false;
1056 mDoReconnectAfterClose = false;
1057 return Reconnect(mReconnectCallback);
1058 }
1059
1060 NS_IMETHODIMP
NotifyData(const nsACString & aData,bool aIsBinary)1061 PresentationControllingInfo::NotifyData(const nsACString& aData,
1062 bool aIsBinary) {
1063 return PresentationSessionInfo::NotifyData(aData, aIsBinary);
1064 }
1065
1066 /**
1067 * Implementation of PresentationPresentingInfo
1068 *
1069 * During presentation session establishment, the receiver expects the following
1070 * after trying to launch the app by notifying "presentation-launch-receiver":
1071 * (The order between step 2 and 3 is not guaranteed.)
1072 * 1. |Observe| of |nsIObserver| is called with
1073 * "presentation-receiver-launched".
1074 * Then start listen to document |STATE_TRANSFERRING| event.
1075 * 2. |NotifyResponderReady| is called to indicate the receiver page is ready
1076 * for presentation use.
1077 * 3. |OnOffer| of |nsIPresentationControlChannelListener| is called.
1078 * 4. Once both step 2 and 3 are done, establish the data transport channel and
1079 * send the answer. (The control channel will be closed by the sender once it
1080 * receives the answer.)
1081 * 5. |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
1082 * called. The presentation session is ready to use, so notify the listener
1083 * of CONNECTED state.
1084 */
1085
NS_IMPL_ISUPPORTS_INHERITED(PresentationPresentingInfo,PresentationSessionInfo,nsITimerCallback,nsINamed)1086 NS_IMPL_ISUPPORTS_INHERITED(PresentationPresentingInfo, PresentationSessionInfo,
1087 nsITimerCallback, nsINamed)
1088
1089 nsresult PresentationPresentingInfo::Init(
1090 nsIPresentationControlChannel* aControlChannel) {
1091 PresentationSessionInfo::Init(aControlChannel);
1092
1093 // Add a timer to prevent waiting indefinitely in case the receiver page fails
1094 // to become ready.
1095 nsresult rv;
1096 int32_t timeout =
1097 Preferences::GetInt("presentation.receiver.loading.timeout", 10000);
1098 rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, timeout,
1099 nsITimer::TYPE_ONE_SHOT);
1100 if (NS_WARN_IF(NS_FAILED(rv))) {
1101 return rv;
1102 }
1103
1104 return NS_OK;
1105 }
1106
Shutdown(nsresult aReason)1107 void PresentationPresentingInfo::Shutdown(nsresult aReason) {
1108 PresentationSessionInfo::Shutdown(aReason);
1109
1110 if (mTimer) {
1111 mTimer->Cancel();
1112 }
1113
1114 mLoadingCallback = nullptr;
1115 mRequesterDescription = nullptr;
1116 mPendingCandidates.Clear();
1117 mPromise = nullptr;
1118 mHasFlushPendingEvents = false;
1119 }
1120
1121 // nsIPresentationSessionTransportBuilderListener
1122 NS_IMETHODIMP
OnSessionTransport(nsIPresentationSessionTransport * aTransport)1123 PresentationPresentingInfo::OnSessionTransport(
1124 nsIPresentationSessionTransport* aTransport) {
1125 nsresult rv = PresentationSessionInfo::OnSessionTransport(aTransport);
1126
1127 if (NS_WARN_IF(NS_FAILED(rv))) {
1128 return rv;
1129 }
1130
1131 // The session transport is managed by content process
1132 if (NS_WARN_IF(!aTransport)) {
1133 return NS_ERROR_INVALID_ARG;
1134 }
1135
1136 // send answer for TCP session transport
1137 if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
1138 // Prepare and send the answer.
1139 // In the current implementation of |PresentationSessionTransport|,
1140 // |GetSelfAddress| cannot return the real info when it's initialized via
1141 // |buildTCPReceiverTransport|. Yet this deficiency only affects the channel
1142 // description for the answer, which is not actually checked at requester
1143 // side.
1144 nsCOMPtr<nsINetAddr> selfAddr;
1145 rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
1146 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "GetSelfAddress failed");
1147
1148 nsCString address;
1149 uint16_t port = 0;
1150 if (NS_SUCCEEDED(rv)) {
1151 selfAddr->GetAddress(address);
1152 selfAddr->GetPort(&port);
1153 }
1154 nsCOMPtr<nsIPresentationChannelDescription> description =
1155 new TCPPresentationChannelDescription(address, port);
1156
1157 return mControlChannel->SendAnswer(description);
1158 }
1159
1160 return NS_OK;
1161 }
1162
1163 // Delegate the pending offer and ICE candidates to builder.
1164 NS_IMETHODIMP
FlushPendingEvents(nsIPresentationDataChannelSessionTransportBuilder * builder)1165 PresentationPresentingInfo::FlushPendingEvents(
1166 nsIPresentationDataChannelSessionTransportBuilder* builder) {
1167 if (NS_WARN_IF(!builder)) {
1168 return NS_ERROR_FAILURE;
1169 }
1170
1171 mHasFlushPendingEvents = true;
1172
1173 if (mRequesterDescription) {
1174 builder->OnOffer(mRequesterDescription);
1175 }
1176 mRequesterDescription = nullptr;
1177
1178 for (size_t i = 0; i < mPendingCandidates.Length(); ++i) {
1179 builder->OnIceCandidate(mPendingCandidates[i]);
1180 }
1181 mPendingCandidates.Clear();
1182 return NS_OK;
1183 }
1184
InitTransportAndSendAnswer()1185 nsresult PresentationPresentingInfo::InitTransportAndSendAnswer() {
1186 MOZ_ASSERT(NS_IsMainThread());
1187 MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CONNECTING);
1188
1189 uint8_t type = 0;
1190 nsresult rv = mRequesterDescription->GetType(&type);
1191 if (NS_WARN_IF(NS_FAILED(rv))) {
1192 return rv;
1193 }
1194
1195 if (NS_WARN_IF(!mBuilderConstructor)) {
1196 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1197 }
1198
1199 if (NS_WARN_IF(NS_FAILED(mBuilderConstructor->CreateTransportBuilder(
1200 type, getter_AddRefs(mBuilder))))) {
1201 return NS_ERROR_NOT_AVAILABLE;
1202 }
1203
1204 if (type == nsIPresentationChannelDescription::TYPE_TCP) {
1205 // Establish a data transport channel |mTransport| to the sender and use
1206 // |this| as the callback.
1207 nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
1208 do_QueryInterface(mBuilder);
1209 if (NS_WARN_IF(!builder)) {
1210 return NS_ERROR_NOT_AVAILABLE;
1211 }
1212
1213 mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
1214 return builder->BuildTCPReceiverTransport(mRequesterDescription, this);
1215 }
1216
1217 if (type == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
1218 if (!Preferences::GetBool(
1219 "dom.presentation.session_transport.data_channel.enable")) {
1220 return NS_ERROR_NOT_IMPLEMENTED;
1221 }
1222 /**
1223 * Generally transport is maintained by the chrome process. However, data
1224 * channel should be live with the DOM , which implies RTCDataChannel in an
1225 * OOP page should be establish in the content process.
1226 *
1227 * |mBuilderConstructor| is responsible for creating a builder, which is for
1228 * building a data channel transport.
1229 *
1230 * In the OOP case, |mBuilderConstructor| would create a builder which is
1231 * an object of |PresentationBuilderParent|. So, |BuildDataChannelTransport|
1232 * triggers an IPC call to make content process establish a RTCDataChannel
1233 * transport.
1234 */
1235
1236 mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
1237
1238 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
1239 dataChannelBuilder = do_QueryInterface(mBuilder);
1240 if (NS_WARN_IF(!dataChannelBuilder)) {
1241 return NS_ERROR_NOT_AVAILABLE;
1242 }
1243
1244 nsPIDOMWindowInner* window = GetWindow();
1245
1246 rv = dataChannelBuilder->BuildDataChannelTransport(
1247 nsIPresentationService::ROLE_RECEIVER, window, this);
1248 if (NS_WARN_IF(NS_FAILED(rv))) {
1249 return rv;
1250 }
1251
1252 rv = FlushPendingEvents(dataChannelBuilder);
1253 if (NS_WARN_IF(NS_FAILED(rv))) {
1254 return rv;
1255 }
1256
1257 return NS_OK;
1258 }
1259
1260 MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!");
1261 return NS_ERROR_UNEXPECTED;
1262 }
1263
UntrackFromService()1264 nsresult PresentationPresentingInfo::UntrackFromService() {
1265 // Remove the OOP responding info (if it has never been used).
1266 if (mContentParent) {
1267 Unused << NS_WARN_IF(
1268 !static_cast<ContentParent*>(mContentParent.get())
1269 ->SendNotifyPresentationReceiverCleanUp(mSessionId));
1270 }
1271
1272 // Receiver device might need clean up after session termination.
1273 if (mDevice) {
1274 mDevice->Disconnect();
1275 }
1276 mDevice = nullptr;
1277
1278 // Remove the session info (and the in-process responding info if there's
1279 // any).
1280 nsCOMPtr<nsIPresentationService> service =
1281 do_GetService(PRESENTATION_SERVICE_CONTRACTID);
1282 if (NS_WARN_IF(!service)) {
1283 return NS_ERROR_NOT_AVAILABLE;
1284 }
1285 static_cast<PresentationService*>(service.get())
1286 ->UntrackSessionInfo(mSessionId, mRole);
1287
1288 return NS_OK;
1289 }
1290
IsAccessible(base::ProcessId aProcessId)1291 bool PresentationPresentingInfo::IsAccessible(base::ProcessId aProcessId) {
1292 // Only the specific content process should access the responder info.
1293 return (mContentParent)
1294 ? aProcessId ==
1295 static_cast<ContentParent*>(mContentParent.get())->OtherPid()
1296 : false;
1297 }
1298
NotifyResponderReady()1299 nsresult PresentationPresentingInfo::NotifyResponderReady() {
1300 PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
1301 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
1302
1303 if (mTimer) {
1304 mTimer->Cancel();
1305 mTimer = nullptr;
1306 }
1307
1308 mIsResponderReady = true;
1309
1310 // Initialize |mTransport| and send the answer to the sender if sender's
1311 // description is already offered.
1312 if (mRequesterDescription) {
1313 nsresult rv = InitTransportAndSendAnswer();
1314 if (NS_WARN_IF(NS_FAILED(rv))) {
1315 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1316 }
1317 }
1318
1319 return NS_OK;
1320 }
1321
NotifyResponderFailure()1322 nsresult PresentationPresentingInfo::NotifyResponderFailure() {
1323 PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
1324 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
1325
1326 if (mTimer) {
1327 mTimer->Cancel();
1328 mTimer = nullptr;
1329 }
1330
1331 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1332 }
1333
DoReconnect()1334 nsresult PresentationPresentingInfo::DoReconnect() {
1335 PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
1336 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
1337
1338 MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
1339
1340 SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
1341
1342 return NotifyResponderReady();
1343 }
1344
1345 // nsIPresentationControlChannelListener
1346 NS_IMETHODIMP
OnOffer(nsIPresentationChannelDescription * aDescription)1347 PresentationPresentingInfo::OnOffer(
1348 nsIPresentationChannelDescription* aDescription) {
1349 if (NS_WARN_IF(mHasFlushPendingEvents)) {
1350 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1351 }
1352
1353 if (NS_WARN_IF(!aDescription)) {
1354 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1355 }
1356
1357 mRequesterDescription = aDescription;
1358
1359 // Initialize |mTransport| and send the answer to the sender if the receiver
1360 // page is ready for presentation use.
1361 if (mIsResponderReady) {
1362 nsresult rv = InitTransportAndSendAnswer();
1363 if (NS_WARN_IF(NS_FAILED(rv))) {
1364 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1365 }
1366 }
1367
1368 return NS_OK;
1369 }
1370
1371 NS_IMETHODIMP
OnAnswer(nsIPresentationChannelDescription * aDescription)1372 PresentationPresentingInfo::OnAnswer(
1373 nsIPresentationChannelDescription* aDescription) {
1374 MOZ_ASSERT(false, "Receiver side should not receive answer.");
1375 return NS_ERROR_FAILURE;
1376 }
1377
1378 NS_IMETHODIMP
OnIceCandidate(const nsAString & aCandidate)1379 PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate) {
1380 if (!mBuilder && !mHasFlushPendingEvents) {
1381 mPendingCandidates.AppendElement(nsString(aCandidate));
1382 return NS_OK;
1383 }
1384
1385 if (NS_WARN_IF(!mBuilder && mHasFlushPendingEvents)) {
1386 return NS_ERROR_FAILURE;
1387 }
1388
1389 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
1390 do_QueryInterface(mBuilder);
1391
1392 return builder->OnIceCandidate(aCandidate);
1393 }
1394
1395 NS_IMETHODIMP
NotifyConnected()1396 PresentationPresentingInfo::NotifyConnected() {
1397 PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
1398 NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
1399
1400 if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
1401 ContinueTermination();
1402 }
1403
1404 return NS_OK;
1405 }
1406
1407 NS_IMETHODIMP
NotifyReconnected()1408 PresentationPresentingInfo::NotifyReconnected() {
1409 MOZ_ASSERT(false, "NotifyReconnected should not be called at receiver side.");
1410 return NS_OK;
1411 }
1412
1413 NS_IMETHODIMP
NotifyDisconnected(nsresult aReason)1414 PresentationPresentingInfo::NotifyDisconnected(nsresult aReason) {
1415 PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__,
1416 NS_ConvertUTF16toUTF8(mSessionId).get(),
1417 static_cast<uint32_t>(aReason), mRole);
1418
1419 MOZ_ASSERT(NS_IsMainThread());
1420
1421 if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
1422 nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
1423 do_QueryInterface(mBuilder);
1424 if (builder) {
1425 Unused << NS_WARN_IF(NS_FAILED(builder->NotifyDisconnected(aReason)));
1426 }
1427 }
1428
1429 // Unset control channel here so it won't try to re-close it in potential
1430 // subsequent |Shutdown| calls.
1431 SetControlChannel(nullptr);
1432
1433 if (NS_WARN_IF(NS_FAILED(aReason))) {
1434 // The presentation session instance may already exist.
1435 // Change the state to TERMINATED since it never succeeds.
1436 SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED,
1437 aReason);
1438
1439 // Reply error for an abnormal close.
1440 return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1441 }
1442
1443 return NS_OK;
1444 }
1445
1446 // nsITimerCallback
1447 NS_IMETHODIMP
Notify(nsITimer * aTimer)1448 PresentationPresentingInfo::Notify(nsITimer* aTimer) {
1449 MOZ_ASSERT(NS_IsMainThread());
1450 NS_WARNING("The receiver page fails to become ready before timeout.");
1451
1452 mTimer = nullptr;
1453 return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR);
1454 }
1455
1456 // nsITimerCallback
1457 NS_IMETHODIMP
GetName(nsACString & aName)1458 PresentationPresentingInfo::GetName(nsACString& aName) {
1459 aName.AssignLiteral("PresentationPresentingInfo");
1460 return NS_OK;
1461 }
1462
1463 // PromiseNativeHandler
ResolvedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1464 void PresentationPresentingInfo::ResolvedCallback(
1465 JSContext* aCx, JS::Handle<JS::Value> aValue) {
1466 MOZ_ASSERT(NS_IsMainThread());
1467
1468 if (NS_WARN_IF(!aValue.isObject())) {
1469 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1470 return;
1471 }
1472
1473 JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
1474 if (NS_WARN_IF(!obj)) {
1475 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1476 return;
1477 }
1478
1479 // Start to listen to document state change event |STATE_TRANSFERRING|.
1480 // Use Element to support both HTMLIFrameElement and nsXULElement.
1481 Element* frame = nullptr;
1482 nsresult rv = UNWRAP_OBJECT(Element, &obj, frame);
1483 if (NS_WARN_IF(!frame)) {
1484 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1485 return;
1486 }
1487
1488 RefPtr<nsFrameLoaderOwner> owner = do_QueryObject(frame);
1489 if (NS_WARN_IF(!owner)) {
1490 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1491 return;
1492 }
1493
1494 RefPtr<nsFrameLoader> frameLoader = owner->GetFrameLoader();
1495 if (NS_WARN_IF(!frameLoader)) {
1496 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1497 return;
1498 }
1499
1500 RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(frameLoader);
1501 if (browserParent) {
1502 // OOP frame
1503 // Notify the content process that a receiver page has launched, so it can
1504 // start monitoring the loading progress.
1505 mContentParent = browserParent->Manager();
1506 Unused << NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())
1507 ->SendNotifyPresentationReceiverLaunched(
1508 browserParent, mSessionId));
1509 } else {
1510 // In-process frame
1511 IgnoredErrorResult error;
1512 nsCOMPtr<nsIDocShell> docShell = frameLoader->GetDocShell(error);
1513 if (NS_WARN_IF(error.Failed())) {
1514 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1515 return;
1516 }
1517
1518 // Keep an eye on the loading progress of the receiver page.
1519 mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
1520 rv = mLoadingCallback->Init(docShell);
1521 if (NS_WARN_IF(NS_FAILED(rv))) {
1522 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1523 return;
1524 }
1525 }
1526 }
1527
RejectedCallback(JSContext * aCx,JS::Handle<JS::Value> aValue)1528 void PresentationPresentingInfo::RejectedCallback(
1529 JSContext* aCx, JS::Handle<JS::Value> aValue) {
1530 MOZ_ASSERT(NS_IsMainThread());
1531 NS_WARNING("Launching the receiver page has been rejected.");
1532
1533 if (mTimer) {
1534 mTimer->Cancel();
1535 mTimer = nullptr;
1536 }
1537
1538 ReplyError(NS_ERROR_DOM_OPERATION_ERR);
1539 }
1540