Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsDOMDataChannel.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 "nsDOMDataChannel.h"
8
9
#include "base/basictypes.h"
10
#include "mozilla/Logging.h"
11
12
#include "nsDOMDataChannelDeclarations.h"
13
#include "nsDOMDataChannel.h"
14
#include "mozilla/DOMEventTargetHelper.h"
15
#include "mozilla/dom/File.h"
16
#include "mozilla/dom/MessageEvent.h"
17
#include "mozilla/dom/MessageEventBinding.h"
18
#include "mozilla/dom/ScriptSettings.h"
19
20
#include "nsError.h"
21
#include "nsContentUtils.h"
22
#include "nsCycleCollectionParticipant.h"
23
#include "nsIScriptObjectPrincipal.h"
24
#include "nsProxyRelease.h"
25
26
#include "DataChannel.h"
27
#include "DataChannelLog.h"
28
29
#undef LOG
30
0
#define LOG(args) MOZ_LOG(mozilla::gDataChannelLog, mozilla::LogLevel::Debug, args)
31
32
// Since we've moved the windows.h include down here, we have to explicitly
33
// undef GetBinaryType, otherwise we'll get really odd conflicts
34
#ifdef GetBinaryType
35
#undef GetBinaryType
36
#endif
37
38
using namespace mozilla;
39
using namespace mozilla::dom;
40
41
nsDOMDataChannel::~nsDOMDataChannel()
42
0
{
43
0
  // Don't call us anymore!  Likely isn't an issue (or maybe just less of
44
0
  // one) once we block GC until all the (appropriate) onXxxx handlers
45
0
  // are dropped. (See WebRTC spec)
46
0
  LOG(("%p: Close()ing %p", this, mDataChannel.get()));
47
0
  mDataChannel->SetListener(nullptr, nullptr);
48
0
  mDataChannel->Close();
49
0
}
50
51
/* virtual */ JSObject*
52
nsDOMDataChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
53
0
{
54
0
  return RTCDataChannel_Binding::Wrap(aCx, this, aGivenProto);
55
0
}
56
57
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMDataChannel)
58
59
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMDataChannel,
60
0
                                                  DOMEventTargetHelper)
61
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
62
63
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMDataChannel,
64
0
                                                DOMEventTargetHelper)
65
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66
67
NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
68
NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
69
70
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMDataChannel)
71
0
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
72
73
nsDOMDataChannel::nsDOMDataChannel(already_AddRefed<mozilla::DataChannel>& aDataChannel,
74
                                   nsPIDOMWindowInner* aWindow)
75
  : DOMEventTargetHelper(aWindow)
76
  , mDataChannel(aDataChannel)
77
  , mBinaryType(DC_BINARY_TYPE_BLOB)
78
  , mCheckMustKeepAlive(true)
79
  , mSentClose(false)
80
0
{
81
0
}
82
83
nsresult
84
nsDOMDataChannel::Init(nsPIDOMWindowInner* aDOMWindow)
85
0
{
86
0
  nsresult rv;
87
0
  nsAutoString urlParam;
88
0
89
0
  MOZ_ASSERT(mDataChannel);
90
0
  mDataChannel->SetListener(this, nullptr);
91
0
92
0
  // Now grovel through the objects to get a usable origin for onMessage
93
0
  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aDOMWindow);
94
0
  NS_ENSURE_STATE(sgo);
95
0
  nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
96
0
  NS_ENSURE_STATE(scriptContext);
97
0
98
0
  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal(do_QueryInterface(aDOMWindow));
99
0
  NS_ENSURE_STATE(scriptPrincipal);
100
0
  nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
101
0
  NS_ENSURE_STATE(principal);
102
0
103
0
  // Attempt to kill "ghost" DataChannel (if one can happen): but usually too early for check to fail
104
0
  rv = CheckInnerWindowCorrectness();
105
0
  NS_ENSURE_SUCCESS(rv,rv);
106
0
107
0
  rv = nsContentUtils::GetUTFOrigin(principal,mOrigin);
108
0
  LOG(("%s: origin = %s\n",__FUNCTION__,NS_LossyConvertUTF16toASCII(mOrigin).get()));
109
0
  return rv;
110
0
}
111
112
// Most of the GetFoo()/SetFoo()s don't need to touch shared resources and
113
// are safe after Close()
114
void
115
nsDOMDataChannel::GetLabel(nsAString& aLabel)
116
0
{
117
0
  mDataChannel->GetLabel(aLabel);
118
0
}
119
120
void
121
nsDOMDataChannel::GetProtocol(nsAString& aProtocol)
122
0
{
123
0
  mDataChannel->GetProtocol(aProtocol);
124
0
}
125
126
uint16_t
127
nsDOMDataChannel::Id() const
128
0
{
129
0
  return mDataChannel->GetStream();
130
0
}
131
132
// XXX should be GetType()?  Open question for the spec
133
bool
134
nsDOMDataChannel::Reliable() const
135
0
{
136
0
  return mDataChannel->GetType() == mozilla::DataChannelConnection::RELIABLE;
137
0
}
138
139
mozilla::dom::Nullable<uint16_t>
140
nsDOMDataChannel::GetMaxPacketLifeTime() const
141
0
{
142
0
  return mDataChannel->GetMaxPacketLifeTime();
143
0
}
144
145
mozilla::dom::Nullable<uint16_t>
146
nsDOMDataChannel::GetMaxRetransmits() const
147
0
{
148
0
  return mDataChannel->GetMaxRetransmits();
149
0
}
150
151
bool
152
nsDOMDataChannel::Ordered() const
153
0
{
154
0
  return mDataChannel->GetOrdered();
155
0
}
156
157
RTCDataChannelState
158
nsDOMDataChannel::ReadyState() const
159
0
{
160
0
  return static_cast<RTCDataChannelState>(mDataChannel->GetReadyState());
161
0
}
162
163
164
uint32_t
165
nsDOMDataChannel::BufferedAmount() const
166
0
{
167
0
  if (!mSentClose) {
168
0
    return mDataChannel->GetBufferedAmount();
169
0
  }
170
0
  return 0;
171
0
}
172
173
uint32_t
174
nsDOMDataChannel::BufferedAmountLowThreshold() const
175
0
{
176
0
  return mDataChannel->GetBufferedAmountLowThreshold();
177
0
}
178
179
void
180
nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold)
181
0
{
182
0
  mDataChannel->SetBufferedAmountLowThreshold(aThreshold);
183
0
}
184
185
void
186
nsDOMDataChannel::Close()
187
0
{
188
0
  mDataChannel->Close();
189
0
  UpdateMustKeepAlive();
190
0
}
191
192
// All of the following is copy/pasted from WebSocket.cpp.
193
void
194
nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv)
195
0
{
196
0
  NS_ConvertUTF16toUTF8 msgString(aData);
197
0
  Send(nullptr, msgString, false, aRv);
198
0
}
199
200
void
201
nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv)
202
0
{
203
0
  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
204
0
205
0
  nsCOMPtr<nsIInputStream> msgStream;
206
0
  aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
207
0
  if (NS_WARN_IF(aRv.Failed())){
208
0
    return;
209
0
  }
210
0
211
0
  uint64_t msgLength = aData.GetSize(aRv);
212
0
  if (NS_WARN_IF(aRv.Failed())){
213
0
    return;
214
0
  }
215
0
216
0
  if (msgLength > UINT32_MAX) {
217
0
    aRv.Throw(NS_ERROR_FILE_TOO_BIG);
218
0
    return;
219
0
  }
220
0
221
0
  Send(msgStream, EmptyCString(), true, aRv);
222
0
}
223
224
void
225
nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv)
226
0
{
227
0
  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
228
0
229
0
  aData.ComputeLengthAndData();
230
0
231
0
  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
232
0
233
0
  uint32_t len = aData.Length();
234
0
  char* data = reinterpret_cast<char*>(aData.Data());
235
0
236
0
  nsDependentCSubstring msgString(data, len);
237
0
  Send(nullptr, msgString, true, aRv);
238
0
}
239
240
void
241
nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv)
242
0
{
243
0
  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
244
0
245
0
  aData.ComputeLengthAndData();
246
0
247
0
  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
248
0
249
0
  uint32_t len = aData.Length();
250
0
  char* data = reinterpret_cast<char*>(aData.Data());
251
0
252
0
  nsDependentCSubstring msgString(data, len);
253
0
  Send(nullptr, msgString, true, aRv);
254
0
}
255
256
void
257
nsDOMDataChannel::Send(nsIInputStream* aMsgStream,
258
                       const nsACString& aMsgString,
259
                       bool aIsBinary,
260
                       ErrorResult& aRv)
261
0
{
262
0
  MOZ_ASSERT(NS_IsMainThread());
263
0
  uint16_t state = mozilla::DataChannel::CLOSED;
264
0
  if (!mSentClose) {
265
0
    state = mDataChannel->GetReadyState();
266
0
  }
267
0
268
0
  // In reality, the DataChannel protocol allows this, but we want it to
269
0
  // look like WebSockets
270
0
  if (state == mozilla::DataChannel::CONNECTING) {
271
0
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
272
0
    return;
273
0
  }
274
0
275
0
  if (state == mozilla::DataChannel::CLOSING ||
276
0
      state == mozilla::DataChannel::CLOSED) {
277
0
    return;
278
0
  }
279
0
280
0
  MOZ_ASSERT(state == mozilla::DataChannel::OPEN,
281
0
             "Unknown state in nsDOMDataChannel::Send");
282
0
283
0
  if (aMsgStream) {
284
0
    mDataChannel->SendBinaryStream(aMsgStream, aRv);
285
0
  } else {
286
0
    if (aIsBinary) {
287
0
      mDataChannel->SendBinaryMsg(aMsgString, aRv);
288
0
    } else {
289
0
      mDataChannel->SendMsg(aMsgString, aRv);
290
0
    }
291
0
  }
292
0
}
293
294
nsresult
295
nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,
296
                                       bool aBinary)
297
0
{
298
0
  MOZ_ASSERT(NS_IsMainThread());
299
0
300
0
  LOG(("DoOnMessageAvailable%s\n",aBinary ? ((mBinaryType == DC_BINARY_TYPE_BLOB) ? " (blob)" : " (binary)") : ""));
301
0
302
0
  nsresult rv = CheckInnerWindowCorrectness();
303
0
  if (NS_FAILED(rv)) {
304
0
    return NS_OK;
305
0
  }
306
0
307
0
  AutoJSAPI jsapi;
308
0
  if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
309
0
    return NS_ERROR_FAILURE;
310
0
  }
311
0
  JSContext* cx = jsapi.cx();
312
0
313
0
  JS::Rooted<JS::Value> jsData(cx);
314
0
315
0
  if (aBinary) {
316
0
    if (mBinaryType == DC_BINARY_TYPE_BLOB) {
317
0
      RefPtr<Blob> blob =
318
0
        Blob::CreateStringBlob(GetOwner(), aData, EmptyString());
319
0
      MOZ_ASSERT(blob);
320
0
321
0
      if (!ToJSValue(cx, blob, &jsData)) {
322
0
        return NS_ERROR_FAILURE;
323
0
      }
324
0
    } else if (mBinaryType == DC_BINARY_TYPE_ARRAYBUFFER) {
325
0
      JS::Rooted<JSObject*> arrayBuf(cx);
326
0
      rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
327
0
      NS_ENSURE_SUCCESS(rv, rv);
328
0
      jsData.setObject(*arrayBuf);
329
0
    } else {
330
0
      MOZ_CRASH("Unknown binary type!");
331
0
      return NS_ERROR_UNEXPECTED;
332
0
    }
333
0
  } else {
334
0
    NS_ConvertUTF8toUTF16 utf16data(aData);
335
0
    JSString* jsString = JS_NewUCStringCopyN(cx, utf16data.get(), utf16data.Length());
336
0
    NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
337
0
338
0
    jsData.setString(jsString);
339
0
  }
340
0
341
0
  RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
342
0
343
0
  event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"), CanBubble::eNo,
344
0
                          Cancelable::eNo, jsData, mOrigin, EmptyString(),
345
0
                          nullptr, Sequence<OwningNonNull<MessagePort>>());
346
0
  event->SetTrusted(true);
347
0
348
0
  LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
349
0
  ErrorResult err;
350
0
  DispatchEvent(*event, err);
351
0
  if (err.Failed()) {
352
0
    NS_WARNING("Failed to dispatch the message event!!!");
353
0
  }
354
0
  return err.StealNSResult();
355
0
}
356
357
nsresult
358
nsDOMDataChannel::OnMessageAvailable(nsISupports* aContext,
359
                                     const nsACString& aMessage)
360
0
{
361
0
  MOZ_ASSERT(NS_IsMainThread());
362
0
  return DoOnMessageAvailable(aMessage, false);
363
0
}
364
365
nsresult
366
nsDOMDataChannel::OnBinaryMessageAvailable(nsISupports* aContext,
367
                                           const nsACString& aMessage)
368
0
{
369
0
  MOZ_ASSERT(NS_IsMainThread());
370
0
  return DoOnMessageAvailable(aMessage, true);
371
0
}
372
373
nsresult
374
nsDOMDataChannel::OnSimpleEvent(nsISupports* aContext, const nsAString& aName)
375
0
{
376
0
  MOZ_ASSERT(NS_IsMainThread());
377
0
378
0
  nsresult rv = CheckInnerWindowCorrectness();
379
0
  if (NS_FAILED(rv)) {
380
0
    return NS_OK;
381
0
  }
382
0
383
0
  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
384
0
385
0
  event->InitEvent(aName, CanBubble::eNo, Cancelable::eNo);
386
0
  event->SetTrusted(true);
387
0
388
0
  ErrorResult err;
389
0
  DispatchEvent(*event, err);
390
0
  return err.StealNSResult();
391
0
}
392
393
nsresult
394
nsDOMDataChannel::OnChannelConnected(nsISupports* aContext)
395
0
{
396
0
  LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
397
0
398
0
  return OnSimpleEvent(aContext, NS_LITERAL_STRING("open"));
399
0
}
400
401
nsresult
402
nsDOMDataChannel::OnChannelClosed(nsISupports* aContext)
403
0
{
404
0
  nsresult rv;
405
0
  // so we don't have to worry if we're notified from different paths in
406
0
  // the underlying code
407
0
  if (!mSentClose) {
408
0
    // Ok, we're done with it.
409
0
    mDataChannel->ReleaseConnection();
410
0
    LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
411
0
412
0
    rv = OnSimpleEvent(aContext, NS_LITERAL_STRING("close"));
413
0
    // no more events can happen
414
0
    mSentClose = true;
415
0
  } else {
416
0
    rv = NS_OK;
417
0
  }
418
0
  DontKeepAliveAnyMore();
419
0
  return rv;
420
0
}
421
422
nsresult
423
nsDOMDataChannel::OnBufferLow(nsISupports* aContext)
424
0
{
425
0
  LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__));
426
0
427
0
  return OnSimpleEvent(aContext, NS_LITERAL_STRING("bufferedamountlow"));
428
0
}
429
430
nsresult
431
nsDOMDataChannel::NotBuffered(nsISupports* aContext)
432
0
{
433
0
  // In the rare case that we held off GC to let the buffer drain
434
0
  UpdateMustKeepAlive();
435
0
  return NS_OK;
436
0
}
437
438
void
439
nsDOMDataChannel::AppReady()
440
0
{
441
0
  if (!mSentClose) { // may not be possible, simpler to just test anyways
442
0
    mDataChannel->AppReady();
443
0
  }
444
0
}
445
446
//-----------------------------------------------------------------------------
447
// Methods that keep alive the DataChannel object when:
448
//   1. the object has registered event listeners that can be triggered
449
//      ("strong event listeners");
450
//   2. there are outgoing not sent messages.
451
//-----------------------------------------------------------------------------
452
453
void
454
nsDOMDataChannel::UpdateMustKeepAlive()
455
0
{
456
0
  MOZ_ASSERT(NS_IsMainThread());
457
0
458
0
  if (!mCheckMustKeepAlive) {
459
0
    return;
460
0
  }
461
0
462
0
  bool shouldKeepAlive = false;
463
0
  uint16_t readyState = mDataChannel->GetReadyState();
464
0
465
0
  switch (readyState)
466
0
  {
467
0
    case DataChannel::CONNECTING:
468
0
    case DataChannel::WAITING_TO_OPEN:
469
0
    {
470
0
      if (mListenerManager &&
471
0
          (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
472
0
           mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
473
0
           mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
474
0
           mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
475
0
           mListenerManager->HasListenersFor(nsGkAtoms::onclose))) {
476
0
        shouldKeepAlive = true;
477
0
      }
478
0
    }
479
0
    break;
480
0
481
0
    case DataChannel::OPEN:
482
0
    case DataChannel::CLOSING:
483
0
    {
484
0
      if (mDataChannel->GetBufferedAmount() != 0 ||
485
0
          (mListenerManager &&
486
0
           (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
487
0
            mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
488
0
            mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
489
0
            mListenerManager->HasListenersFor(nsGkAtoms::onclose)))) {
490
0
        shouldKeepAlive = true;
491
0
      }
492
0
    }
493
0
    break;
494
0
495
0
    case DataChannel::CLOSED:
496
0
    {
497
0
      shouldKeepAlive = false;
498
0
    }
499
0
  }
500
0
501
0
  if (mSelfRef && !shouldKeepAlive) {
502
0
    ReleaseSelf();
503
0
  } else if (!mSelfRef && shouldKeepAlive) {
504
0
    mSelfRef = this;
505
0
  }
506
0
}
507
508
void
509
nsDOMDataChannel::DontKeepAliveAnyMore()
510
0
{
511
0
  MOZ_ASSERT(NS_IsMainThread());
512
0
513
0
  if (mSelfRef) {
514
0
    // Since we're on MainThread, force an eventloop trip to avoid deleting
515
0
    // ourselves.
516
0
    ReleaseSelf();
517
0
  }
518
0
519
0
  mCheckMustKeepAlive = false;
520
0
}
521
522
void
523
nsDOMDataChannel::ReleaseSelf()
524
0
{
525
0
  // release our self-reference (safely) by putting it in an event (always)
526
0
  NS_ReleaseOnMainThreadSystemGroup("nsDOMDataChannel::mSelfRef",
527
0
                                    mSelfRef.forget(), true);
528
0
}
529
530
void
531
nsDOMDataChannel::EventListenerAdded(nsAtom* aType)
532
0
{
533
0
  MOZ_ASSERT(NS_IsMainThread());
534
0
  UpdateMustKeepAlive();
535
0
}
536
537
void
538
nsDOMDataChannel::EventListenerRemoved(nsAtom* aType)
539
0
{
540
0
  MOZ_ASSERT(NS_IsMainThread());
541
0
  UpdateMustKeepAlive();
542
0
}
543
544
545
/* static */
546
nsresult
547
NS_NewDOMDataChannel(already_AddRefed<mozilla::DataChannel>&& aDataChannel,
548
                     nsPIDOMWindowInner* aWindow,
549
                     nsDOMDataChannel** aDomDataChannel)
550
0
{
551
0
  RefPtr<nsDOMDataChannel> domdc =
552
0
    new nsDOMDataChannel(aDataChannel, aWindow);
553
0
554
0
  nsresult rv = domdc->Init(aWindow);
555
0
  NS_ENSURE_SUCCESS(rv,rv);
556
0
557
0
  domdc.forget(aDomDataChannel);
558
0
  return NS_OK;
559
0
}