michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDOMDataChannel.h" michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "prlog.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* GetDataChannelLog(); michael@0: #endif michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(GetDataChannelLog(), PR_LOG_DEBUG, args) michael@0: michael@0: michael@0: #include "nsDOMDataChannelDeclarations.h" michael@0: #include "nsDOMDataChannel.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIDOMDataChannel.h" michael@0: #include "nsIDOMMessageEvent.h" michael@0: #include "mozilla/DOMEventTargetHelper.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsDOMFile.h" michael@0: michael@0: #include "DataChannel.h" michael@0: michael@0: // Since we've moved the windows.h include down here, we have to explicitly michael@0: // undef GetBinaryType, otherwise we'll get really odd conflicts michael@0: #ifdef GetBinaryType michael@0: #undef GetBinaryType michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsDOMDataChannel::~nsDOMDataChannel() michael@0: { michael@0: // Don't call us anymore! Likely isn't an issue (or maybe just less of michael@0: // one) once we block GC until all the (appropriate) onXxxx handlers michael@0: // are dropped. (See WebRTC spec) michael@0: LOG(("Close()ing %p", mDataChannel.get())); michael@0: mDataChannel->SetListener(nullptr, nullptr); michael@0: mDataChannel->Close(); michael@0: } michael@0: michael@0: /* virtual */ JSObject* michael@0: nsDOMDataChannel::WrapObject(JSContext* aCx) michael@0: { michael@0: return DataChannelBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMDataChannel) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMDataChannel, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMDataChannel, michael@0: DOMEventTargetHelper) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel, DOMEventTargetHelper) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDataChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMDataChannel) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: nsDOMDataChannel::nsDOMDataChannel(already_AddRefed& aDataChannel, michael@0: nsPIDOMWindow* aWindow) michael@0: : DOMEventTargetHelper(aWindow && aWindow->IsOuterWindow() ? michael@0: aWindow->GetCurrentInnerWindow() : aWindow) michael@0: , mDataChannel(aDataChannel) michael@0: , mBinaryType(DC_BINARY_TYPE_BLOB) michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::Init(nsPIDOMWindow* aDOMWindow) michael@0: { michael@0: nsresult rv; michael@0: nsAutoString urlParam; michael@0: michael@0: MOZ_ASSERT(mDataChannel); michael@0: mDataChannel->SetListener(this, nullptr); michael@0: michael@0: // Now grovel through the objects to get a usable origin for onMessage michael@0: nsCOMPtr sgo = do_QueryInterface(aDOMWindow); michael@0: NS_ENSURE_STATE(sgo); michael@0: nsCOMPtr scriptContext = sgo->GetContext(); michael@0: NS_ENSURE_STATE(scriptContext); michael@0: michael@0: nsCOMPtr scriptPrincipal(do_QueryInterface(aDOMWindow)); michael@0: NS_ENSURE_STATE(scriptPrincipal); michael@0: nsCOMPtr principal = scriptPrincipal->GetPrincipal(); michael@0: NS_ENSURE_STATE(principal); michael@0: michael@0: // Attempt to kill "ghost" DataChannel (if one can happen): but usually too early for check to fail michael@0: rv = CheckInnerWindowCorrectness(); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: rv = nsContentUtils::GetUTFOrigin(principal,mOrigin); michael@0: LOG(("%s: origin = %s\n",__FUNCTION__,NS_LossyConvertUTF16toASCII(mOrigin).get())); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, open) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, error) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, close) michael@0: NS_IMPL_EVENT_HANDLER(nsDOMDataChannel, message) michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetLabel(nsAString& aLabel) michael@0: { michael@0: mDataChannel->GetLabel(aLabel); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetProtocol(nsAString& aProtocol) michael@0: { michael@0: mDataChannel->GetProtocol(aProtocol); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint16_t michael@0: nsDOMDataChannel::Id() const michael@0: { michael@0: return mDataChannel->GetStream(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetId(uint16_t *aId) michael@0: { michael@0: *aId = Id(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint16_t michael@0: nsDOMDataChannel::Stream() const michael@0: { michael@0: return mDataChannel->GetStream(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetStream(uint16_t *aStream) michael@0: { michael@0: *aStream = Stream(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX should be GetType()? Open question for the spec michael@0: bool michael@0: nsDOMDataChannel::Reliable() const michael@0: { michael@0: return mDataChannel->GetType() == mozilla::DataChannelConnection::RELIABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetReliable(bool* aReliable) michael@0: { michael@0: *aReliable = Reliable(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsDOMDataChannel::Ordered() const michael@0: { michael@0: return mDataChannel->GetOrdered(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetOrdered(bool* aOrdered) michael@0: { michael@0: *aOrdered = Ordered(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: RTCDataChannelState michael@0: nsDOMDataChannel::ReadyState() const michael@0: { michael@0: return static_cast(mDataChannel->GetReadyState()); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetReadyState(nsAString& aReadyState) michael@0: { michael@0: uint16_t readyState = mDataChannel->GetReadyState(); michael@0: // From the WebRTC spec michael@0: const char * stateName[] = { michael@0: "connecting", michael@0: "open", michael@0: "closing", michael@0: "closed" michael@0: }; michael@0: MOZ_ASSERT(/*readyState >= mozilla::DataChannel::CONNECTING && */ // Always true due to datatypes michael@0: readyState <= mozilla::DataChannel::CLOSED); michael@0: aReadyState.AssignASCII(stateName[readyState]); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsDOMDataChannel::BufferedAmount() const michael@0: { michael@0: return mDataChannel->GetBufferedAmount(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::GetBufferedAmount(uint32_t* aBufferedAmount) michael@0: { michael@0: *aBufferedAmount = BufferedAmount(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsDOMDataChannel::GetBinaryType(nsAString & aBinaryType) michael@0: { michael@0: switch (mBinaryType) { michael@0: case DC_BINARY_TYPE_ARRAYBUFFER: michael@0: aBinaryType.AssignLiteral("arraybuffer"); michael@0: break; michael@0: case DC_BINARY_TYPE_BLOB: michael@0: aBinaryType.AssignLiteral("blob"); michael@0: break; michael@0: default: michael@0: NS_ERROR("Should not happen"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::SetBinaryType(const nsAString& aBinaryType) michael@0: { michael@0: if (aBinaryType.EqualsLiteral("arraybuffer")) { michael@0: mBinaryType = DC_BINARY_TYPE_ARRAYBUFFER; michael@0: } else if (aBinaryType.EqualsLiteral("blob")) { michael@0: mBinaryType = DC_BINARY_TYPE_BLOB; michael@0: } else { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMDataChannel::Close() michael@0: { michael@0: mDataChannel->Close(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // All of the following is copy/pasted from WebSocket.cpp. michael@0: void michael@0: nsDOMDataChannel::Send(const nsAString& aData, ErrorResult& aRv) michael@0: { michael@0: NS_ConvertUTF16toUTF8 msgString(aData); michael@0: Send(nullptr, msgString, msgString.Length(), false, aRv); michael@0: } michael@0: michael@0: void michael@0: nsDOMDataChannel::Send(nsIDOMBlob* aData, ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: nsCOMPtr msgStream; michael@0: nsresult rv = aData->GetInternalStream(getter_AddRefs(msgStream)); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: uint64_t msgLength; michael@0: rv = aData->GetSize(&msgLength); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return; michael@0: } michael@0: michael@0: if (msgLength > UINT32_MAX) { michael@0: aRv.Throw(NS_ERROR_FILE_TOO_BIG); michael@0: return; michael@0: } michael@0: michael@0: Send(msgStream, EmptyCString(), msgLength, true, aRv); michael@0: } michael@0: michael@0: void michael@0: nsDOMDataChannel::Send(const ArrayBuffer& aData, ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); michael@0: michael@0: uint32_t len = aData.Length(); michael@0: char* data = reinterpret_cast(aData.Data()); michael@0: michael@0: nsDependentCSubstring msgString(data, len); michael@0: Send(nullptr, msgString, len, true, aRv); michael@0: } michael@0: michael@0: void michael@0: nsDOMDataChannel::Send(const ArrayBufferView& aData, ErrorResult& aRv) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread"); michael@0: michael@0: aData.ComputeLengthAndData(); michael@0: michael@0: static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required"); michael@0: michael@0: uint32_t len = aData.Length(); michael@0: char* data = reinterpret_cast(aData.Data()); michael@0: michael@0: nsDependentCSubstring msgString(data, len); michael@0: Send(nullptr, msgString, len, true, aRv); michael@0: } michael@0: michael@0: void michael@0: nsDOMDataChannel::Send(nsIInputStream* aMsgStream, michael@0: const nsACString& aMsgString, michael@0: uint32_t aMsgLength, michael@0: bool aIsBinary, michael@0: ErrorResult& aRv) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: uint16_t state = mDataChannel->GetReadyState(); michael@0: michael@0: // In reality, the DataChannel protocol allows this, but we want it to michael@0: // look like WebSockets michael@0: if (state == mozilla::DataChannel::CONNECTING) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (state == mozilla::DataChannel::CLOSING || michael@0: state == mozilla::DataChannel::CLOSED) { michael@0: return; michael@0: } michael@0: michael@0: MOZ_ASSERT(state == mozilla::DataChannel::OPEN, michael@0: "Unknown state in nsDOMDataChannel::Send"); michael@0: michael@0: int32_t sent; michael@0: if (aMsgStream) { michael@0: sent = mDataChannel->SendBinaryStream(aMsgStream, aMsgLength); michael@0: } else { michael@0: if (aIsBinary) { michael@0: sent = mDataChannel->SendBinaryMsg(aMsgString); michael@0: } else { michael@0: sent = mDataChannel->SendMsg(aMsgString); michael@0: } michael@0: } michael@0: if (sent < 0) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::DoOnMessageAvailable(const nsACString& aData, michael@0: bool aBinary) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: LOG(("DoOnMessageAvailable%s\n",aBinary ? ((mBinaryType == DC_BINARY_TYPE_BLOB) ? " (blob)" : " (binary)") : "")); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr sgo = do_QueryInterface(GetOwner()); michael@0: NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE); michael@0: michael@0: nsIScriptContext* sc = sgo->GetContext(); michael@0: NS_ENSURE_TRUE(sc, NS_ERROR_FAILURE); michael@0: michael@0: AutoPushJSContext cx(sc->GetNativeContext()); michael@0: NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); michael@0: michael@0: JS::Rooted jsData(cx); michael@0: michael@0: if (aBinary) { michael@0: if (mBinaryType == DC_BINARY_TYPE_BLOB) { michael@0: rv = nsContentUtils::CreateBlobBuffer(cx, aData, &jsData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else if (mBinaryType == DC_BINARY_TYPE_ARRAYBUFFER) { michael@0: JS::Rooted arrayBuf(cx); michael@0: rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: jsData = OBJECT_TO_JSVAL(arrayBuf); michael@0: } else { michael@0: NS_RUNTIMEABORT("Unknown binary type!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } else { michael@0: NS_ConvertUTF8toUTF16 utf16data(aData); michael@0: JSString* jsString = JS_NewUCStringCopyN(cx, utf16data.get(), utf16data.Length()); michael@0: NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE); michael@0: michael@0: jsData = STRING_TO_JSVAL(jsString); michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: nsCOMPtr messageEvent = do_QueryInterface(event); michael@0: rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"), michael@0: false, false, michael@0: jsData, mOrigin, EmptyString(), michael@0: nullptr); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: event->SetTrusted(true); michael@0: michael@0: LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__)); michael@0: rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to dispatch the message event!!!"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::OnMessageAvailable(nsISupports* aContext, michael@0: const nsACString& aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return DoOnMessageAvailable(aMessage, false); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::OnBinaryMessageAvailable(nsISupports* aContext, michael@0: const nsACString& aMessage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: return DoOnMessageAvailable(aMessage, true); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::OnSimpleEvent(nsISupports* aContext, const nsAString& aName) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv = CheckInnerWindowCorrectness(); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: rv = event->InitEvent(aName, false, false); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: return DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::OnChannelConnected(nsISupports* aContext) michael@0: { michael@0: LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__)); michael@0: michael@0: return OnSimpleEvent(aContext, NS_LITERAL_STRING("open")); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMDataChannel::OnChannelClosed(nsISupports* aContext) michael@0: { michael@0: LOG(("%p(%p): %s - Dispatching\n",this,(void*)mDataChannel,__FUNCTION__)); michael@0: michael@0: return OnSimpleEvent(aContext, NS_LITERAL_STRING("close")); michael@0: } michael@0: michael@0: void michael@0: nsDOMDataChannel::AppReady() michael@0: { michael@0: mDataChannel->AppReady(); michael@0: } michael@0: michael@0: /* static */ michael@0: nsresult michael@0: NS_NewDOMDataChannel(already_AddRefed&& aDataChannel, michael@0: nsPIDOMWindow* aWindow, michael@0: nsIDOMDataChannel** aDomDataChannel) michael@0: { michael@0: nsRefPtr domdc = michael@0: new nsDOMDataChannel(aDataChannel, aWindow); michael@0: michael@0: nsresult rv = domdc->Init(aWindow); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: return CallQueryInterface(domdc, aDomDataChannel); michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: NS_DataChannelAppReady(nsIDOMDataChannel* aDomDataChannel) michael@0: { michael@0: ((nsDOMDataChannel *)aDomDataChannel)->AppReady(); michael@0: }