summaryrefslogtreecommitdiffstats
path: root/dom/base/nsDOMDataChannel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsDOMDataChannel.cpp')
-rw-r--r--dom/base/nsDOMDataChannel.cpp510
1 files changed, 510 insertions, 0 deletions
diff --git a/dom/base/nsDOMDataChannel.cpp b/dom/base/nsDOMDataChannel.cpp
new file mode 100644
index 0000000000..6f1e475168
--- /dev/null
+++ b/dom/base/nsDOMDataChannel.cpp
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDOMDataChannel.h"
+
+#include "base/basictypes.h"
+#include "mozilla/Logging.h"
+
+#include "nsDOMDataChannelDeclarations.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/Blob.h"
+
+#include "nsError.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsProxyRelease.h"
+
+#include "DataChannel.h"
+#include "DataChannelLog.h"
+
+// Since we've moved the windows.h include down here, we have to explicitly
+// undef GetBinaryType, otherwise we'll get really odd conflicts
+#ifdef GetBinaryType
+# undef GetBinaryType
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDOMDataChannel::~nsDOMDataChannel() {
+ // Don't call us anymore! Likely isn't an issue (or maybe just less of
+ // one) once we block GC until all the (appropriate) onXxxx handlers
+ // are dropped. (See WebRTC spec)
+ DC_DEBUG(("%p: Close()ing %p", this, mDataChannel.get()));
+ mDataChannel->SetListener(nullptr, nullptr);
+ mDataChannel->Close();
+}
+
+/* virtual */
+JSObject* nsDOMDataChannel::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDataChannel_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMDataChannel)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMDataChannel,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMDataChannel,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMDataChannel)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+nsDOMDataChannel::nsDOMDataChannel(
+ already_AddRefed<mozilla::DataChannel>& aDataChannel,
+ nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow),
+ mDataChannel(aDataChannel),
+ mBinaryType(DC_BINARY_TYPE_BLOB),
+ mCheckMustKeepAlive(true),
+ mSentClose(false) {}
+
+nsresult nsDOMDataChannel::Init(nsPIDOMWindowInner* aDOMWindow) {
+ nsresult rv;
+ nsAutoString urlParam;
+
+ MOZ_ASSERT(mDataChannel);
+ mDataChannel->SetListener(this, nullptr);
+
+ // Now grovel through the objects to get a usable origin for onMessage
+ nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aDOMWindow);
+ NS_ENSURE_STATE(sgo);
+ nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
+ NS_ENSURE_STATE(scriptContext);
+
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal(
+ do_QueryInterface(aDOMWindow));
+ NS_ENSURE_STATE(scriptPrincipal);
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ NS_ENSURE_STATE(principal);
+
+ // Attempt to kill "ghost" DataChannel (if one can happen): but usually too
+ // early for check to fail
+ rv = CheckCurrentGlobalCorrectness();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
+ DC_DEBUG(("%s: origin = %s\n", __FUNCTION__,
+ NS_LossyConvertUTF16toASCII(mOrigin).get()));
+ return rv;
+}
+
+// Most of the GetFoo()/SetFoo()s don't need to touch shared resources and
+// are safe after Close()
+void nsDOMDataChannel::GetLabel(nsAString& aLabel) {
+ mDataChannel->GetLabel(aLabel);
+}
+
+void nsDOMDataChannel::GetProtocol(nsAString& aProtocol) {
+ mDataChannel->GetProtocol(aProtocol);
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetId() const {
+ mozilla::dom::Nullable<uint16_t> result = mDataChannel->GetStream();
+ if (result.Value() == 65535) {
+ result.SetNull();
+ }
+ return result;
+}
+
+// XXX should be GetType()? Open question for the spec
+bool nsDOMDataChannel::Reliable() const {
+ return mDataChannel->GetType() == mozilla::DataChannelConnection::RELIABLE;
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetMaxPacketLifeTime()
+ const {
+ return mDataChannel->GetMaxPacketLifeTime();
+}
+
+mozilla::dom::Nullable<uint16_t> nsDOMDataChannel::GetMaxRetransmits() const {
+ return mDataChannel->GetMaxRetransmits();
+}
+
+bool nsDOMDataChannel::Negotiated() const {
+ return mDataChannel->GetNegotiated();
+}
+
+bool nsDOMDataChannel::Ordered() const { return mDataChannel->GetOrdered(); }
+
+RTCDataChannelState nsDOMDataChannel::ReadyState() const {
+ return static_cast<RTCDataChannelState>(mDataChannel->GetReadyState());
+}
+
+uint32_t nsDOMDataChannel::BufferedAmount() const {
+ if (!mSentClose) {
+ return mDataChannel->GetBufferedAmount();
+ }
+ return 0;
+}
+
+uint32_t nsDOMDataChannel::BufferedAmountLowThreshold() const {
+ return mDataChannel->GetBufferedAmountLowThreshold();
+}
+
+void nsDOMDataChannel::SetBufferedAmountLowThreshold(uint32_t aThreshold) {
+ mDataChannel->SetBufferedAmountLowThreshold(aThreshold);
+}
+
+void nsDOMDataChannel::Close() {
+ mDataChannel->Close();
+ UpdateMustKeepAlive();
+}
+
+// All of the following is copy/pasted from WebSocket.cpp.
+void nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv) {
+ nsAutoCString msgString;
+ if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ Send(nullptr, &msgString, false, aRv);
+}
+
+void nsDOMDataChannel::Send(Blob& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ nsCOMPtr<nsIInputStream> msgStream;
+ aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ uint64_t msgLength = aData.GetSize(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (msgLength > UINT32_MAX) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(&aData, nullptr, true, aRv);
+}
+
+void nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeState();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString;
+ if (!msgString.Assign(data, len, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(nullptr, &msgString, true, aRv);
+}
+
+void nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ aData.ComputeState();
+
+ static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+ uint32_t len = aData.Length();
+ char* data = reinterpret_cast<char*>(aData.Data());
+
+ nsDependentCSubstring msgString;
+ if (!msgString.Assign(data, len, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+
+ Send(nullptr, &msgString, true, aRv);
+}
+
+void nsDOMDataChannel::Send(mozilla::dom::Blob* aMsgBlob,
+ const nsACString* aMsgString, bool aIsBinary,
+ mozilla::ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ uint16_t state = mozilla::DataChannel::CLOSED;
+ if (!mSentClose) {
+ state = mDataChannel->GetReadyState();
+ }
+
+ // In reality, the DataChannel protocol allows this, but we want it to
+ // look like WebSockets
+ if (state == mozilla::DataChannel::CONNECTING) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (state == mozilla::DataChannel::CLOSING ||
+ state == mozilla::DataChannel::CLOSED) {
+ return;
+ }
+
+ MOZ_ASSERT(state == mozilla::DataChannel::OPEN,
+ "Unknown state in nsDOMDataChannel::Send");
+
+ if (aMsgBlob) {
+ mDataChannel->SendBinaryBlob(*aMsgBlob, aRv);
+ } else {
+ if (aIsBinary) {
+ mDataChannel->SendBinaryMsg(*aMsgString, aRv);
+ } else {
+ mDataChannel->SendMsg(*aMsgString, aRv);
+ }
+ }
+}
+
+nsresult nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData,
+ bool aBinary) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DC_VERBOSE((
+ "DoOnMessageAvailable%s\n",
+ aBinary ? ((mBinaryType == DC_BINARY_TYPE_BLOB) ? " (blob)" : " (binary)")
+ : ""));
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> jsData(cx);
+
+ if (aBinary) {
+ if (mBinaryType == DC_BINARY_TYPE_BLOB) {
+ RefPtr<Blob> blob =
+ Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns);
+ if (NS_WARN_IF(!blob)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!ToJSValue(cx, blob, &jsData)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (mBinaryType == DC_BINARY_TYPE_ARRAYBUFFER) {
+ JS::Rooted<JSObject*> arrayBuf(cx);
+ rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ jsData.setObject(*arrayBuf);
+ } else {
+ MOZ_CRASH("Unknown binary type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ NS_ConvertUTF8toUTF16 utf16data(aData);
+ JSString* jsString =
+ JS_NewUCStringCopyN(cx, utf16data.get(), utf16data.Length());
+ NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
+
+ jsData.setString(jsString);
+ }
+
+ RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, jsData, mOrigin, u""_ns, nullptr,
+ Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ if (err.Failed()) {
+ DC_ERROR(("%p(%p): %s - Failed to dispatch message", this,
+ (void*)mDataChannel, __FUNCTION__));
+ NS_WARNING("Failed to dispatch the message event!!!");
+ }
+ return err.StealNSResult();
+}
+
+nsresult nsDOMDataChannel::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return DoOnMessageAvailable(aMessage, false);
+}
+
+nsresult nsDOMDataChannel::OnBinaryMessageAvailable(
+ nsISupports* aContext, const nsACString& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return DoOnMessageAvailable(aMessage, true);
+}
+
+nsresult nsDOMDataChannel::OnSimpleEvent(nsISupports* aContext,
+ const nsAString& aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ event->InitEvent(aName, CanBubble::eNo, Cancelable::eNo);
+ event->SetTrusted(true);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+nsresult nsDOMDataChannel::OnChannelConnected(nsISupports* aContext) {
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+
+ return OnSimpleEvent(aContext, u"open"_ns);
+}
+
+nsresult nsDOMDataChannel::OnChannelClosed(nsISupports* aContext) {
+ nsresult rv;
+ // so we don't have to worry if we're notified from different paths in
+ // the underlying code
+ if (!mSentClose) {
+ // Ok, we're done with it.
+ mDataChannel->ReleaseConnection();
+ DC_DEBUG(("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel,
+ __FUNCTION__));
+
+ rv = OnSimpleEvent(aContext, u"close"_ns);
+ // no more events can happen
+ mSentClose = true;
+ } else {
+ rv = NS_OK;
+ }
+ DontKeepAliveAnyMore();
+ return rv;
+}
+
+nsresult nsDOMDataChannel::OnBufferLow(nsISupports* aContext) {
+ DC_DEBUG(
+ ("%p(%p): %s - Dispatching\n", this, (void*)mDataChannel, __FUNCTION__));
+
+ return OnSimpleEvent(aContext, u"bufferedamountlow"_ns);
+}
+
+nsresult nsDOMDataChannel::NotBuffered(nsISupports* aContext) {
+ // In the rare case that we held off GC to let the buffer drain
+ UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Methods that keep alive the DataChannel object when:
+// 1. the object has registered event listeners that can be triggered
+// ("strong event listeners");
+// 2. there are outgoing not sent messages.
+//-----------------------------------------------------------------------------
+
+void nsDOMDataChannel::UpdateMustKeepAlive() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCheckMustKeepAlive) {
+ return;
+ }
+
+ bool shouldKeepAlive = false;
+ uint16_t readyState = mDataChannel->GetReadyState();
+
+ switch (readyState) {
+ case DataChannel::CONNECTING: {
+ if (mListenerManager &&
+ (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose))) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case DataChannel::OPEN:
+ case DataChannel::CLOSING: {
+ if (mDataChannel->GetBufferedAmount() != 0 ||
+ (mListenerManager &&
+ (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onbufferedamountlow) ||
+ mListenerManager->HasListenersFor(nsGkAtoms::onclose)))) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case DataChannel::CLOSED: {
+ shouldKeepAlive = false;
+ }
+ }
+
+ if (mSelfRef && !shouldKeepAlive) {
+ ReleaseSelf();
+ } else if (!mSelfRef && shouldKeepAlive) {
+ mSelfRef = this;
+ }
+}
+
+void nsDOMDataChannel::DontKeepAliveAnyMore() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSelfRef) {
+ // Since we're on MainThread, force an eventloop trip to avoid deleting
+ // ourselves.
+ ReleaseSelf();
+ }
+
+ mCheckMustKeepAlive = false;
+}
+
+void nsDOMDataChannel::ReleaseSelf() {
+ // release our self-reference (safely) by putting it in an event (always)
+ NS_ReleaseOnMainThread("nsDOMDataChannel::mSelfRef", mSelfRef.forget(), true);
+}
+
+void nsDOMDataChannel::EventListenerAdded(nsAtom* aType) {
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+void nsDOMDataChannel::EventListenerRemoved(nsAtom* aType) {
+ MOZ_ASSERT(NS_IsMainThread());
+ UpdateMustKeepAlive();
+}
+
+/* static */
+nsresult NS_NewDOMDataChannel(
+ already_AddRefed<mozilla::DataChannel>&& aDataChannel,
+ nsPIDOMWindowInner* aWindow, nsDOMDataChannel** aDomDataChannel) {
+ RefPtr<nsDOMDataChannel> domdc = new nsDOMDataChannel(aDataChannel, aWindow);
+
+ nsresult rv = domdc->Init(aWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ domdc.forget(aDomDataChannel);
+ return NS_OK;
+}