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 "WebSocketLog.h" michael@0: #include "base/compiler_specific.h" michael@0: #include "mozilla/dom/TabChild.h" michael@0: #include "mozilla/net/NeckoChild.h" michael@0: #include "WebSocketChannelChild.h" michael@0: #include "nsITabChild.h" michael@0: #include "nsNetUtil.h" michael@0: #include "mozilla/ipc/InputStreamUtils.h" michael@0: #include "mozilla/ipc/URIUtils.h" michael@0: #include "mozilla/net/ChannelEventQueue.h" michael@0: #include "SerializedLoadContext.h" michael@0: michael@0: using namespace mozilla::ipc; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: NS_IMPL_ADDREF(WebSocketChannelChild) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) WebSocketChannelChild::Release() michael@0: { michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: NS_ASSERT_OWNINGTHREAD(WebSocketChannelChild); michael@0: --mRefCnt; michael@0: NS_LOG_RELEASE(this, mRefCnt, "WebSocketChannelChild"); michael@0: michael@0: if (mRefCnt == 1 && mIPCOpen) { michael@0: SendDeleteSelf(); michael@0: return mRefCnt; michael@0: } michael@0: michael@0: if (mRefCnt == 0) { michael@0: mRefCnt = 1; /* stabilize */ michael@0: delete this; michael@0: return 0; michael@0: } michael@0: return mRefCnt; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(WebSocketChannelChild) michael@0: NS_INTERFACE_MAP_ENTRY(nsIWebSocketChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: WebSocketChannelChild::WebSocketChannelChild(bool aSecure) michael@0: : mIPCOpen(false) michael@0: { michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); michael@0: michael@0: LOG(("WebSocketChannelChild::WebSocketChannelChild() %p\n", this)); michael@0: BaseWebSocketChannel::mEncrypted = aSecure; michael@0: mEventQ = new ChannelEventQueue(static_cast(this)); michael@0: } michael@0: michael@0: WebSocketChannelChild::~WebSocketChannelChild() michael@0: { michael@0: LOG(("WebSocketChannelChild::~WebSocketChannelChild() %p\n", this)); michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::AddIPDLReference() michael@0: { michael@0: NS_ABORT_IF_FALSE(!mIPCOpen, "Attempt to retain more than one IPDL reference"); michael@0: mIPCOpen = true; michael@0: AddRef(); michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::ReleaseIPDLReference() michael@0: { michael@0: NS_ABORT_IF_FALSE(mIPCOpen, "Attempt to release nonexistent IPDL reference"); michael@0: mIPCOpen = false; michael@0: Release(); michael@0: } michael@0: michael@0: class WrappedChannelEvent : public nsRunnable michael@0: { michael@0: public: michael@0: WrappedChannelEvent(ChannelEvent *aChannelEvent) michael@0: : mChannelEvent(aChannelEvent) michael@0: { michael@0: MOZ_RELEASE_ASSERT(aChannelEvent); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: mChannelEvent->Run(); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsAutoPtr mChannelEvent; michael@0: }; michael@0: michael@0: void michael@0: WebSocketChannelChild::DispatchToTargetThread(ChannelEvent *aChannelEvent) michael@0: { michael@0: MOZ_RELEASE_ASSERT(NS_IsMainThread()); michael@0: MOZ_RELEASE_ASSERT(mTargetThread); michael@0: MOZ_RELEASE_ASSERT(aChannelEvent); michael@0: michael@0: mTargetThread->Dispatch(new WrappedChannelEvent(aChannelEvent), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: class StartEvent : public ChannelEvent michael@0: { michael@0: public: michael@0: StartEvent(WebSocketChannelChild* aChild, michael@0: const nsCString& aProtocol, michael@0: const nsCString& aExtensions) michael@0: : mChild(aChild) michael@0: , mProtocol(aProtocol) michael@0: , mExtensions(aExtensions) michael@0: {} michael@0: michael@0: void Run() michael@0: { michael@0: mChild->OnStart(mProtocol, mExtensions); michael@0: } michael@0: private: michael@0: WebSocketChannelChild* mChild; michael@0: nsCString mProtocol; michael@0: nsCString mExtensions; michael@0: }; michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnStart(const nsCString& aProtocol, michael@0: const nsCString& aExtensions) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new StartEvent(this, aProtocol, aExtensions)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new StartEvent(this, aProtocol, aExtensions)); michael@0: } else { michael@0: OnStart(aProtocol, aExtensions); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnStart(const nsCString& aProtocol, michael@0: const nsCString& aExtensions) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnStart() %p\n", this)); michael@0: SetProtocol(aProtocol); michael@0: mNegotiatedExtensions = aExtensions; michael@0: michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnStart(mContext); michael@0: } michael@0: } michael@0: michael@0: class StopEvent : public ChannelEvent michael@0: { michael@0: public: michael@0: StopEvent(WebSocketChannelChild* aChild, michael@0: const nsresult& aStatusCode) michael@0: : mChild(aChild) michael@0: , mStatusCode(aStatusCode) michael@0: {} michael@0: michael@0: void Run() michael@0: { michael@0: mChild->OnStop(mStatusCode); michael@0: } michael@0: private: michael@0: WebSocketChannelChild* mChild; michael@0: nsresult mStatusCode; michael@0: }; michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnStop(const nsresult& aStatusCode) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new StopEvent(this, aStatusCode)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new StopEvent(this, aStatusCode)); michael@0: } else { michael@0: OnStop(aStatusCode); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnStop(const nsresult& aStatusCode) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnStop() %p\n", this)); michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnStop(mContext, aStatusCode); michael@0: } michael@0: } michael@0: michael@0: class MessageEvent : public ChannelEvent michael@0: { michael@0: public: michael@0: MessageEvent(WebSocketChannelChild* aChild, michael@0: const nsCString& aMessage, michael@0: bool aBinary) michael@0: : mChild(aChild) michael@0: , mMessage(aMessage) michael@0: , mBinary(aBinary) michael@0: {} michael@0: michael@0: void Run() michael@0: { michael@0: if (!mBinary) { michael@0: mChild->OnMessageAvailable(mMessage); michael@0: } else { michael@0: mChild->OnBinaryMessageAvailable(mMessage); michael@0: } michael@0: } michael@0: private: michael@0: WebSocketChannelChild* mChild; michael@0: nsCString mMessage; michael@0: bool mBinary; michael@0: }; michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnMessageAvailable(const nsCString& aMsg) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new MessageEvent(this, aMsg, false)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new MessageEvent(this, aMsg, false)); michael@0: } else { michael@0: OnMessageAvailable(aMsg); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnMessageAvailable(const nsCString& aMsg) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnMessageAvailable() %p\n", this)); michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnMessageAvailable(mContext, aMsg); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnBinaryMessageAvailable(const nsCString& aMsg) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new MessageEvent(this, aMsg, true)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new MessageEvent(this, aMsg, true)); michael@0: } else { michael@0: OnBinaryMessageAvailable(aMsg); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnBinaryMessageAvailable(const nsCString& aMsg) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnBinaryMessageAvailable() %p\n", this)); michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnBinaryMessageAvailable(mContext, aMsg); michael@0: } michael@0: } michael@0: michael@0: class AcknowledgeEvent : public ChannelEvent michael@0: { michael@0: public: michael@0: AcknowledgeEvent(WebSocketChannelChild* aChild, michael@0: const uint32_t& aSize) michael@0: : mChild(aChild) michael@0: , mSize(aSize) michael@0: {} michael@0: michael@0: void Run() michael@0: { michael@0: mChild->OnAcknowledge(mSize); michael@0: } michael@0: private: michael@0: WebSocketChannelChild* mChild; michael@0: uint32_t mSize; michael@0: }; michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnAcknowledge(const uint32_t& aSize) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new AcknowledgeEvent(this, aSize)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new AcknowledgeEvent(this, aSize)); michael@0: } else { michael@0: OnAcknowledge(aSize); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnAcknowledge(const uint32_t& aSize) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnAcknowledge() %p\n", this)); michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnAcknowledge(mContext, aSize); michael@0: } michael@0: } michael@0: michael@0: class ServerCloseEvent : public ChannelEvent michael@0: { michael@0: public: michael@0: ServerCloseEvent(WebSocketChannelChild* aChild, michael@0: const uint16_t aCode, michael@0: const nsCString &aReason) michael@0: : mChild(aChild) michael@0: , mCode(aCode) michael@0: , mReason(aReason) michael@0: {} michael@0: michael@0: void Run() michael@0: { michael@0: mChild->OnServerClose(mCode, mReason); michael@0: } michael@0: private: michael@0: WebSocketChannelChild* mChild; michael@0: uint16_t mCode; michael@0: nsCString mReason; michael@0: }; michael@0: michael@0: bool michael@0: WebSocketChannelChild::RecvOnServerClose(const uint16_t& aCode, michael@0: const nsCString& aReason) michael@0: { michael@0: if (mEventQ->ShouldEnqueue()) { michael@0: mEventQ->Enqueue(new ServerCloseEvent(this, aCode, aReason)); michael@0: } else if (mTargetThread) { michael@0: DispatchToTargetThread(new ServerCloseEvent(this, aCode, aReason)); michael@0: } else { michael@0: OnServerClose(aCode, aReason); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WebSocketChannelChild::OnServerClose(const uint16_t& aCode, michael@0: const nsCString& aReason) michael@0: { michael@0: LOG(("WebSocketChannelChild::RecvOnServerClose() %p\n", this)); michael@0: if (mListener) { michael@0: AutoEventEnqueuer ensureSerialDispatch(mEventQ);; michael@0: mListener->OnServerClose(mContext, aCode, aReason); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::AsyncOpen(nsIURI *aURI, michael@0: const nsACString &aOrigin, michael@0: nsIWebSocketListener *aListener, michael@0: nsISupports *aContext) michael@0: { michael@0: LOG(("WebSocketChannelChild::AsyncOpen() %p\n", this)); michael@0: michael@0: NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); michael@0: NS_ABORT_IF_FALSE(aURI && aListener && !mListener, michael@0: "Invalid state for WebSocketChannelChild::AsyncOpen"); michael@0: michael@0: mozilla::dom::TabChild* tabChild = nullptr; michael@0: nsCOMPtr iTabChild; michael@0: NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, michael@0: NS_GET_IID(nsITabChild), michael@0: getter_AddRefs(iTabChild)); michael@0: if (iTabChild) { michael@0: tabChild = static_cast(iTabChild.get()); michael@0: } michael@0: if (MissingRequiredTabChild(tabChild, "websocket")) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: URIParams uri; michael@0: SerializeURI(aURI, uri); michael@0: michael@0: // Corresponding release in DeallocPWebSocket michael@0: AddIPDLReference(); michael@0: michael@0: gNeckoChild->SendPWebSocketConstructor(this, tabChild, michael@0: IPC::SerializedLoadContext(this)); michael@0: if (!SendAsyncOpen(uri, nsCString(aOrigin), mProtocol, mEncrypted, michael@0: mPingInterval, mClientSetPingInterval, michael@0: mPingResponseTimeout, mClientSetPingTimeout)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: mOriginalURI = aURI; michael@0: mURI = mOriginalURI; michael@0: mListener = aListener; michael@0: mContext = aContext; michael@0: mOrigin = aOrigin; michael@0: mWasOpened = 1; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class CloseEvent : public nsRunnable michael@0: { michael@0: public: michael@0: CloseEvent(WebSocketChannelChild *aChild, michael@0: uint16_t aCode, michael@0: const nsACString& aReason) michael@0: : mChild(aChild) michael@0: , mCode(aCode) michael@0: , mReason(aReason) michael@0: { michael@0: MOZ_RELEASE_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(aChild); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_RELEASE_ASSERT(NS_IsMainThread()); michael@0: mChild->Close(mCode, mReason); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mChild; michael@0: uint16_t mCode; michael@0: nsCString mReason; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::Close(uint16_t code, const nsACString & reason) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread); michael@0: return NS_DispatchToMainThread(new CloseEvent(this, code, reason)); michael@0: } michael@0: LOG(("WebSocketChannelChild::Close() %p\n", this)); michael@0: michael@0: if (!mIPCOpen || !SendClose(code, nsCString(reason))) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: class MsgEvent : public nsRunnable michael@0: { michael@0: public: michael@0: MsgEvent(WebSocketChannelChild *aChild, michael@0: const nsACString &aMsg, michael@0: bool aBinaryMsg) michael@0: : mChild(aChild) michael@0: , mMsg(aMsg) michael@0: , mBinaryMsg(aBinaryMsg) michael@0: { michael@0: MOZ_RELEASE_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(aChild); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_RELEASE_ASSERT(NS_IsMainThread()); michael@0: if (mBinaryMsg) { michael@0: mChild->SendBinaryMsg(mMsg); michael@0: } else { michael@0: mChild->SendMsg(mMsg); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mChild; michael@0: nsCString mMsg; michael@0: bool mBinaryMsg; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::SendMsg(const nsACString &aMsg) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread); michael@0: return NS_DispatchToMainThread(new MsgEvent(this, aMsg, false)); michael@0: } michael@0: LOG(("WebSocketChannelChild::SendMsg() %p\n", this)); michael@0: michael@0: if (!mIPCOpen || !SendSendMsg(nsCString(aMsg))) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::SendBinaryMsg(const nsACString &aMsg) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread); michael@0: return NS_DispatchToMainThread(new MsgEvent(this, aMsg, true)); michael@0: } michael@0: LOG(("WebSocketChannelChild::SendBinaryMsg() %p\n", this)); michael@0: michael@0: if (!mIPCOpen || !SendSendBinaryMsg(nsCString(aMsg))) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: class BinaryStreamEvent : public nsRunnable michael@0: { michael@0: public: michael@0: BinaryStreamEvent(WebSocketChannelChild *aChild, michael@0: OptionalInputStreamParams *aStream, michael@0: uint32_t aLength) michael@0: : mChild(aChild) michael@0: , mStream(aStream) michael@0: , mLength(aLength) michael@0: { michael@0: MOZ_RELEASE_ASSERT(!NS_IsMainThread()); michael@0: MOZ_ASSERT(aChild); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mChild->SendBinaryStream(mStream, mLength); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mChild; michael@0: nsAutoPtr mStream; michael@0: uint32_t mLength; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::SendBinaryStream(nsIInputStream *aStream, michael@0: uint32_t aLength) michael@0: { michael@0: OptionalInputStreamParams *stream = new OptionalInputStreamParams(); michael@0: nsTArray fds; michael@0: SerializeInputStream(aStream, *stream, fds); michael@0: michael@0: MOZ_ASSERT(fds.IsEmpty()); michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: MOZ_RELEASE_ASSERT(NS_GetCurrentThread() == mTargetThread); michael@0: return NS_DispatchToMainThread(new BinaryStreamEvent(this, stream, aLength), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: return SendBinaryStream(stream, aLength); michael@0: } michael@0: michael@0: nsresult michael@0: WebSocketChannelChild::SendBinaryStream(OptionalInputStreamParams *aStream, michael@0: uint32_t aLength) michael@0: { michael@0: LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this)); michael@0: michael@0: nsAutoPtr stream(aStream); michael@0: michael@0: if (!mIPCOpen || !SendSendBinaryStream(*stream, aLength)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo) michael@0: { michael@0: LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this)); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // WebSocketChannelChild::nsIThreadRetargetableRequest michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: WebSocketChannelChild::RetargetDeliveryTo(nsIEventTarget* aTargetThread) michael@0: { michael@0: nsresult rv = BaseWebSocketChannel::RetargetDeliveryTo(aTargetThread); michael@0: MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: return mEventQ->RetargetDeliveryTo(aTargetThread); michael@0: } michael@0: michael@0: } // namespace net michael@0: } // namespace mozilla