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