Coverage Report

Created: 2018-09-25 14:53

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