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