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/. */
7 #include "nsDOMDataChannel.h"
9 #include "base/basictypes.h"
10 #include "mozilla/Logging.h"
12 #include "nsDOMDataChannelDeclarations.h"
13 #include "mozilla/DOMEventTargetHelper.h"
14 #include "mozilla/EventListenerManager.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 #include "mozilla/dom/ToJSValue.h"
20 #include "mozilla/dom/Blob.h"
23 #include "nsContentUtils.h"
24 #include "nsCycleCollectionParticipant.h"
25 #include "nsIScriptContext.h"
26 #include "nsIScriptObjectPrincipal.h"
27 #include "nsProxyRelease.h"
29 #include "DataChannel.h"
30 #include "DataChannelLog.h"
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
38 using namespace mozilla
;
39 using namespace mozilla::dom
;
41 nsDOMDataChannel::~nsDOMDataChannel() {
42 // Don't call us anymore! Likely isn't an issue (or maybe just less of
43 // one) once we block GC until all the (appropriate) onXxxx handlers
44 // are dropped. (See WebRTC spec)
45 DC_DEBUG(("%p: Close()ing %p", this, mDataChannel
.get()));
46 mDataChannel
->SetListener(nullptr, nullptr);
47 mDataChannel
->Close();
51 JSObject
* nsDOMDataChannel::WrapObject(JSContext
* aCx
,
52 JS::Handle
<JSObject
*> aGivenProto
) {
53 return RTCDataChannel_Binding::Wrap(aCx
, this, aGivenProto
);
56 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMDataChannel
)
58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMDataChannel
,
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
62 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMDataChannel
,
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66 NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel
, DOMEventTargetHelper
)
67 NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel
, DOMEventTargetHelper
)
69 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMDataChannel
)
70 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
72 nsDOMDataChannel::nsDOMDataChannel(
73 already_AddRefed
<mozilla::DataChannel
>& aDataChannel
,
74 nsPIDOMWindowInner
* aWindow
)
75 : DOMEventTargetHelper(aWindow
),
76 mDataChannel(aDataChannel
),
77 mBinaryType(DC_BINARY_TYPE_BLOB
),
78 mCheckMustKeepAlive(true),
81 nsresult
nsDOMDataChannel::Init(nsPIDOMWindowInner
* aDOMWindow
) {
83 nsAutoString urlParam
;
85 MOZ_ASSERT(mDataChannel
);
86 mDataChannel
->SetListener(this, nullptr);
88 // Now grovel through the objects to get a usable origin for onMessage
89 nsCOMPtr
<nsIScriptGlobalObject
> sgo
= do_QueryInterface(aDOMWindow
);
91 nsCOMPtr
<nsIScriptContext
> scriptContext
= sgo
->GetContext();
92 NS_ENSURE_STATE(scriptContext
);
94 nsCOMPtr
<nsIScriptObjectPrincipal
> scriptPrincipal(
95 do_QueryInterface(aDOMWindow
));
96 NS_ENSURE_STATE(scriptPrincipal
);
97 nsCOMPtr
<nsIPrincipal
> principal
= scriptPrincipal
->GetPrincipal();
98 NS_ENSURE_STATE(principal
);
100 // Attempt to kill "ghost" DataChannel (if one can happen): but usually too
101 // early for check to fail
102 rv
= CheckCurrentGlobalCorrectness();
103 NS_ENSURE_SUCCESS(rv
, rv
);
105 rv
= nsContentUtils::GetWebExposedOriginSerialization(principal
, mOrigin
);
106 DC_DEBUG(("%s: origin = %s\n", __FUNCTION__
,
107 NS_LossyConvertUTF16toASCII(mOrigin
).get()));
111 // Most of the GetFoo()/SetFoo()s don't need to touch shared resources and
112 // are safe after Close()
113 void nsDOMDataChannel::GetLabel(nsAString
& aLabel
) {
114 mDataChannel
->GetLabel(aLabel
);
117 void nsDOMDataChannel::GetProtocol(nsAString
& aProtocol
) {
118 mDataChannel
->GetProtocol(aProtocol
);
121 mozilla::dom::Nullable
<uint16_t> nsDOMDataChannel::GetId() const {
122 mozilla::dom::Nullable
<uint16_t> result
= mDataChannel
->GetStream();
123 if (result
.Value() == 65535) {
129 // XXX should be GetType()? Open question for the spec
130 bool nsDOMDataChannel::Reliable() const {
131 return mDataChannel
->GetType() == mozilla::DataChannelConnection::RELIABLE
;
134 mozilla::dom::Nullable
<uint16_t> nsDOMDataChannel::GetMaxPacketLifeTime()
136 return mDataChannel
->GetMaxPacketLifeTime();
139 mozilla::dom::Nullable
<uint16_t> nsDOMDataChannel::GetMaxRetransmits() const {
140 return mDataChannel
->GetMaxRetransmits();
143 bool nsDOMDataChannel::Negotiated() const {
144 return mDataChannel
->GetNegotiated();
147 bool nsDOMDataChannel::Ordered() const { return mDataChannel
->GetOrdered(); }
149 RTCDataChannelState
nsDOMDataChannel::ReadyState() const {
150 return static_cast<RTCDataChannelState
>(mDataChannel
->GetReadyState());
153 uint32_t nsDOMDataChannel::BufferedAmount() const {
155 return mDataChannel
->GetBufferedAmount();
160 uint32_t nsDOMDataChannel::BufferedAmountLowThreshold() const {
161 return mDataChannel
->GetBufferedAmountLowThreshold();
164 void nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold
) {
165 mDataChannel
->SetBufferedAmountLowThreshold(aThreshold
);
168 void nsDOMDataChannel::Close() {
169 mDataChannel
->Close();
170 UpdateMustKeepAlive();
173 // All of the following is copy/pasted from WebSocket.cpp.
174 void nsDOMDataChannel::Send(const nsAString
& aData
, ErrorResult
& aRv
) {
175 if (!CheckReadyState(aRv
)) {
179 nsAutoCString msgString
;
180 if (!AppendUTF16toUTF8(aData
, msgString
, mozilla::fallible_t())) {
181 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
185 mDataChannel
->SendMsg(msgString
, aRv
);
188 void nsDOMDataChannel::Send(Blob
& aData
, ErrorResult
& aRv
) {
189 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
191 if (!CheckReadyState(aRv
)) {
195 nsCOMPtr
<nsIInputStream
> msgStream
;
196 aData
.CreateInputStream(getter_AddRefs(msgStream
), aRv
);
197 if (NS_WARN_IF(aRv
.Failed())) {
201 uint64_t msgLength
= aData
.GetSize(aRv
);
202 if (NS_WARN_IF(aRv
.Failed())) {
206 if (msgLength
> UINT32_MAX
) {
207 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
211 mDataChannel
->SendBinaryBlob(aData
, aRv
);
214 void nsDOMDataChannel::Send(const ArrayBuffer
& aData
, ErrorResult
& aRv
) {
215 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
217 if (!CheckReadyState(aRv
)) {
222 if (!aData
.AppendDataTo(msgString
)) {
223 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
227 mDataChannel
->SendBinaryMsg(msgString
, aRv
);
230 void nsDOMDataChannel::Send(const ArrayBufferView
& aData
, ErrorResult
& aRv
) {
231 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
233 if (!CheckReadyState(aRv
)) {
238 if (!aData
.AppendDataTo(msgString
)) {
239 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
243 mDataChannel
->SendBinaryMsg(msgString
, aRv
);
246 bool nsDOMDataChannel::CheckReadyState(ErrorResult
& aRv
) {
247 MOZ_ASSERT(NS_IsMainThread());
248 uint16_t state
= mozilla::DataChannel::CLOSED
;
250 state
= mDataChannel
->GetReadyState();
253 // In reality, the DataChannel protocol allows this, but we want it to
254 // look like WebSockets
255 if (state
== mozilla::DataChannel::CONNECTING
) {
256 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
260 if (state
== mozilla::DataChannel::CLOSING
||
261 state
== mozilla::DataChannel::CLOSED
) {
265 MOZ_ASSERT(state
== mozilla::DataChannel::OPEN
,
266 "Unknown state in nsDOMDataChannel::Send");
271 nsresult
nsDOMDataChannel::DoOnMessageAvailable(const nsACString
& aData
,
273 MOZ_ASSERT(NS_IsMainThread());
276 "DoOnMessageAvailable%s\n",
277 aBinary
? ((mBinaryType
== DC_BINARY_TYPE_BLOB
) ? " (blob)" : " (binary)")
280 nsresult rv
= CheckCurrentGlobalCorrectness();
286 if (NS_WARN_IF(!jsapi
.Init(GetOwner()))) {
287 return NS_ERROR_FAILURE
;
289 JSContext
* cx
= jsapi
.cx();
291 JS::Rooted
<JS::Value
> jsData(cx
);
294 if (mBinaryType
== DC_BINARY_TYPE_BLOB
) {
296 Blob::CreateStringBlob(GetOwnerGlobal(), aData
, u
""_ns
);
297 if (NS_WARN_IF(!blob
)) {
298 return NS_ERROR_FAILURE
;
301 if (!ToJSValue(cx
, blob
, &jsData
)) {
302 return NS_ERROR_FAILURE
;
304 } else if (mBinaryType
== DC_BINARY_TYPE_ARRAYBUFFER
) {
305 JS::Rooted
<JSObject
*> arrayBuf(cx
);
306 rv
= nsContentUtils::CreateArrayBuffer(cx
, aData
, arrayBuf
.address());
307 NS_ENSURE_SUCCESS(rv
, rv
);
308 jsData
.setObject(*arrayBuf
);
310 MOZ_CRASH("Unknown binary type!");
311 return NS_ERROR_UNEXPECTED
;
314 NS_ConvertUTF8toUTF16
utf16data(aData
);
316 JS_NewUCStringCopyN(cx
, utf16data
.get(), utf16data
.Length());
317 NS_ENSURE_TRUE(jsString
, NS_ERROR_FAILURE
);
319 jsData
.setString(jsString
);
322 RefPtr
<MessageEvent
> event
= new MessageEvent(this, nullptr, nullptr);
324 event
->InitMessageEvent(nullptr, u
"message"_ns
, CanBubble::eNo
,
325 Cancelable::eNo
, jsData
, mOrigin
, u
""_ns
, nullptr,
326 Sequence
<OwningNonNull
<MessagePort
>>());
327 event
->SetTrusted(true);
330 ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel
, __FUNCTION__
));
332 DispatchEvent(*event
, err
);
334 DC_ERROR(("%p(%p): %s - Failed to dispatch message", this,
335 (void*)mDataChannel
, __FUNCTION__
));
336 NS_WARNING("Failed to dispatch the message event!!!");
338 return err
.StealNSResult();
341 nsresult
nsDOMDataChannel::OnMessageAvailable(nsISupports
* aContext
,
342 const nsACString
& aMessage
) {
343 MOZ_ASSERT(NS_IsMainThread());
344 return DoOnMessageAvailable(aMessage
, false);
347 nsresult
nsDOMDataChannel::OnBinaryMessageAvailable(
348 nsISupports
* aContext
, const nsACString
& aMessage
) {
349 MOZ_ASSERT(NS_IsMainThread());
350 return DoOnMessageAvailable(aMessage
, true);
353 nsresult
nsDOMDataChannel::OnSimpleEvent(nsISupports
* aContext
,
354 const nsAString
& aName
) {
355 MOZ_ASSERT(NS_IsMainThread());
357 nsresult rv
= CheckCurrentGlobalCorrectness();
362 RefPtr
<Event
> event
= NS_NewDOMEvent(this, nullptr, nullptr);
364 event
->InitEvent(aName
, CanBubble::eNo
, Cancelable::eNo
);
365 event
->SetTrusted(true);
368 DispatchEvent(*event
, err
);
369 return err
.StealNSResult();
372 nsresult
nsDOMDataChannel::OnChannelConnected(nsISupports
* aContext
) {
374 ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel
, __FUNCTION__
));
376 return OnSimpleEvent(aContext
, u
"open"_ns
);
379 nsresult
nsDOMDataChannel::OnChannelClosed(nsISupports
* aContext
) {
381 // so we don't have to worry if we're notified from different paths in
382 // the underlying code
384 // Ok, we're done with it.
385 mDataChannel
->ReleaseConnection();
386 DC_DEBUG(("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel
,
389 rv
= OnSimpleEvent(aContext
, u
"close"_ns
);
390 // no more events can happen
395 DontKeepAliveAnyMore();
399 nsresult
nsDOMDataChannel::OnBufferLow(nsISupports
* aContext
) {
401 ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel
, __FUNCTION__
));
403 return OnSimpleEvent(aContext
, u
"bufferedamountlow"_ns
);
406 nsresult
nsDOMDataChannel::NotBuffered(nsISupports
* aContext
) {
407 // In the rare case that we held off GC to let the buffer drain
408 UpdateMustKeepAlive();
412 //-----------------------------------------------------------------------------
413 // Methods that keep alive the DataChannel object when:
414 // 1. the object has registered event listeners that can be triggered
415 // ("strong event listeners");
416 // 2. there are outgoing not sent messages.
417 //-----------------------------------------------------------------------------
419 void nsDOMDataChannel::UpdateMustKeepAlive() {
420 MOZ_ASSERT(NS_IsMainThread());
422 if (!mCheckMustKeepAlive
) {
426 bool shouldKeepAlive
= false;
427 uint16_t readyState
= mDataChannel
->GetReadyState();
429 switch (readyState
) {
430 case DataChannel::CONNECTING
: {
431 if (mListenerManager
&&
432 (mListenerManager
->HasListenersFor(nsGkAtoms::onopen
) ||
433 mListenerManager
->HasListenersFor(nsGkAtoms::onmessage
) ||
434 mListenerManager
->HasListenersFor(nsGkAtoms::onerror
) ||
435 mListenerManager
->HasListenersFor(nsGkAtoms::onbufferedamountlow
) ||
436 mListenerManager
->HasListenersFor(nsGkAtoms::onclose
))) {
437 shouldKeepAlive
= true;
441 case DataChannel::OPEN
:
442 case DataChannel::CLOSING
: {
443 if (mDataChannel
->GetBufferedAmount() != 0 ||
445 (mListenerManager
->HasListenersFor(nsGkAtoms::onmessage
) ||
446 mListenerManager
->HasListenersFor(nsGkAtoms::onerror
) ||
447 mListenerManager
->HasListenersFor(nsGkAtoms::onbufferedamountlow
) ||
448 mListenerManager
->HasListenersFor(nsGkAtoms::onclose
)))) {
449 shouldKeepAlive
= true;
453 case DataChannel::CLOSED
: {
454 shouldKeepAlive
= false;
458 if (mSelfRef
&& !shouldKeepAlive
) {
460 } else if (!mSelfRef
&& shouldKeepAlive
) {
465 void nsDOMDataChannel::DontKeepAliveAnyMore() {
466 MOZ_ASSERT(NS_IsMainThread());
469 // Since we're on MainThread, force an eventloop trip to avoid deleting
474 mCheckMustKeepAlive
= false;
477 void nsDOMDataChannel::ReleaseSelf() {
478 // release our self-reference (safely) by putting it in an event (always)
479 NS_ReleaseOnMainThread("nsDOMDataChannel::mSelfRef", mSelfRef
.forget(), true);
482 void nsDOMDataChannel::EventListenerAdded(nsAtom
* aType
) {
483 MOZ_ASSERT(NS_IsMainThread());
484 UpdateMustKeepAlive();
487 void nsDOMDataChannel::EventListenerRemoved(nsAtom
* aType
) {
488 MOZ_ASSERT(NS_IsMainThread());
489 UpdateMustKeepAlive();
493 nsresult
NS_NewDOMDataChannel(
494 already_AddRefed
<mozilla::DataChannel
>&& aDataChannel
,
495 nsPIDOMWindowInner
* aWindow
, nsDOMDataChannel
** aDomDataChannel
) {
496 RefPtr
<nsDOMDataChannel
> domdc
= new nsDOMDataChannel(aDataChannel
, aWindow
);
498 nsresult rv
= domdc
->Init(aWindow
);
499 NS_ENSURE_SUCCESS(rv
, rv
);
501 domdc
.forget(aDomDataChannel
);