diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/websocket/WebSocketEventService.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/netwerk/protocol/websocket/WebSocketEventService.cpp b/netwerk/protocol/websocket/WebSocketEventService.cpp new file mode 100644 index 0000000000..6e9a89a005 --- /dev/null +++ b/netwerk/protocol/websocket/WebSocketEventService.cpp @@ -0,0 +1,566 @@ +/* -*- 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 "WebSocketEventListenerChild.h" +#include "WebSocketEventService.h" +#include "WebSocketFrame.h" + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/StaticPtr.h" +#include "nsISupportsPrimitives.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include "nsSocketTransportService2.h" +#include "nsThreadUtils.h" +#include "mozilla/Services.h" +#include "nsIWebSocketImpl.h" + +namespace mozilla { +namespace net { + +namespace { + +StaticRefPtr<WebSocketEventService> gWebSocketEventService; + +bool IsChildProcess() { + return XRE_GetProcessType() != GeckoProcessType_Default; +} + +} // anonymous namespace + +class WebSocketBaseRunnable : public Runnable { + public: + WebSocketBaseRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID) + : Runnable("net::WebSocketBaseRunnable"), + mWebSocketSerialID(aWebSocketSerialID), + mInnerWindowID(aInnerWindowID) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<WebSocketEventService> service = + WebSocketEventService::GetOrCreate(); + MOZ_ASSERT(service); + + WebSocketEventService::WindowListeners listeners; + service->GetListeners(mInnerWindowID, listeners); + + for (uint32_t i = 0; i < listeners.Length(); ++i) { + DoWork(listeners[i]); + } + + return NS_OK; + } + + protected: + ~WebSocketBaseRunnable() = default; + + virtual void DoWork(nsIWebSocketEventListener* aListener) = 0; + + uint32_t mWebSocketSerialID; + uint64_t mInnerWindowID; +}; + +class WebSocketFrameRunnable final : public WebSocketBaseRunnable { + public: + WebSocketFrameRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + already_AddRefed<WebSocketFrame> aFrame, + bool aFrameSent) + : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID), + mFrame(std::move(aFrame)), + mFrameSent(aFrameSent) {} + + private: + virtual void DoWork(nsIWebSocketEventListener* aListener) override { + DebugOnly<nsresult> rv{}; + if (mFrameSent) { + rv = aListener->FrameSent(mWebSocketSerialID, mFrame); + } else { + rv = aListener->FrameReceived(mWebSocketSerialID, mFrame); + } + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Frame op failed"); + } + + RefPtr<WebSocketFrame> mFrame; + bool mFrameSent; +}; + +class WebSocketCreatedRunnable final : public WebSocketBaseRunnable { + public: + WebSocketCreatedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + const nsAString& aURI, const nsACString& aProtocols) + : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID), + mURI(aURI), + mProtocols(aProtocols) {} + + private: + virtual void DoWork(nsIWebSocketEventListener* aListener) override { + DebugOnly<nsresult> rv = + aListener->WebSocketCreated(mWebSocketSerialID, mURI, mProtocols); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketCreated failed"); + } + + const nsString mURI; + const nsCString mProtocols; +}; + +class WebSocketOpenedRunnable final : public WebSocketBaseRunnable { + public: + WebSocketOpenedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + const nsAString& aEffectiveURI, + const nsACString& aProtocols, + const nsACString& aExtensions, + uint64_t aHttpChannelId) + : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID), + mEffectiveURI(aEffectiveURI), + mProtocols(aProtocols), + mExtensions(aExtensions), + mHttpChannelId(aHttpChannelId) {} + + private: + virtual void DoWork(nsIWebSocketEventListener* aListener) override { + DebugOnly<nsresult> rv = + aListener->WebSocketOpened(mWebSocketSerialID, mEffectiveURI, + mProtocols, mExtensions, mHttpChannelId); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketOpened failed"); + } + + const nsString mEffectiveURI; + const nsCString mProtocols; + const nsCString mExtensions; + uint64_t mHttpChannelId; +}; + +class WebSocketMessageAvailableRunnable final : public WebSocketBaseRunnable { + public: + WebSocketMessageAvailableRunnable(uint32_t aWebSocketSerialID, + uint64_t aInnerWindowID, + const nsACString& aData, + uint16_t aMessageType) + : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID), + mData(aData), + mMessageType(aMessageType) {} + + private: + virtual void DoWork(nsIWebSocketEventListener* aListener) override { + DebugOnly<nsresult> rv = aListener->WebSocketMessageAvailable( + mWebSocketSerialID, mData, mMessageType); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketMessageAvailable failed"); + } + + const nsCString mData; + uint16_t mMessageType; +}; + +class WebSocketClosedRunnable final : public WebSocketBaseRunnable { + public: + WebSocketClosedRunnable(uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + bool aWasClean, uint16_t aCode, + const nsAString& aReason) + : WebSocketBaseRunnable(aWebSocketSerialID, aInnerWindowID), + mWasClean(aWasClean), + mCode(aCode), + mReason(aReason) {} + + private: + virtual void DoWork(nsIWebSocketEventListener* aListener) override { + DebugOnly<nsresult> rv = aListener->WebSocketClosed( + mWebSocketSerialID, mWasClean, mCode, mReason); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WebSocketClosed failed"); + } + + bool mWasClean; + uint16_t mCode; + const nsString mReason; +}; + +/* static */ +already_AddRefed<WebSocketEventService> WebSocketEventService::Get() { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<WebSocketEventService> service = gWebSocketEventService.get(); + return service.forget(); +} + +/* static */ +already_AddRefed<WebSocketEventService> WebSocketEventService::GetOrCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!gWebSocketEventService) { + gWebSocketEventService = new WebSocketEventService(); + } + + RefPtr<WebSocketEventService> service = gWebSocketEventService.get(); + return service.forget(); +} + +NS_INTERFACE_MAP_BEGIN(WebSocketEventService) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketEventService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIWebSocketEventService) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(WebSocketEventService) +NS_IMPL_RELEASE(WebSocketEventService) + +WebSocketEventService::WebSocketEventService() : mCountListeners(0) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "xpcom-shutdown", false); + obs->AddObserver(this, "inner-window-destroyed", false); + } +} + +WebSocketEventService::~WebSocketEventService() { + MOZ_ASSERT(NS_IsMainThread()); +} + +void WebSocketEventService::WebSocketCreated(uint32_t aWebSocketSerialID, + uint64_t aInnerWindowID, + const nsAString& aURI, + const nsACString& aProtocols, + nsIEventTarget* aTarget) { + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketCreatedRunnable> runnable = new WebSocketCreatedRunnable( + aWebSocketSerialID, aInnerWindowID, aURI, aProtocols); + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::WebSocketOpened(uint32_t aWebSocketSerialID, + uint64_t aInnerWindowID, + const nsAString& aEffectiveURI, + const nsACString& aProtocols, + const nsACString& aExtensions, + uint64_t aHttpChannelId, + nsIEventTarget* aTarget) { + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketOpenedRunnable> runnable = new WebSocketOpenedRunnable( + aWebSocketSerialID, aInnerWindowID, aEffectiveURI, aProtocols, + aExtensions, aHttpChannelId); + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::WebSocketMessageAvailable( + uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + const nsACString& aData, uint16_t aMessageType, nsIEventTarget* aTarget) { + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketMessageAvailableRunnable> runnable = + new WebSocketMessageAvailableRunnable(aWebSocketSerialID, aInnerWindowID, + aData, aMessageType); + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::WebSocketClosed(uint32_t aWebSocketSerialID, + uint64_t aInnerWindowID, + bool aWasClean, uint16_t aCode, + const nsAString& aReason, + nsIEventTarget* aTarget) { + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketClosedRunnable> runnable = new WebSocketClosedRunnable( + aWebSocketSerialID, aInnerWindowID, aWasClean, aCode, aReason); + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::FrameReceived( + uint32_t aWebSocketSerialID, uint64_t aInnerWindowID, + already_AddRefed<WebSocketFrame> aFrame, nsIEventTarget* aTarget) { + RefPtr<WebSocketFrame> frame(std::move(aFrame)); + MOZ_ASSERT(frame); + + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketFrameRunnable> runnable = + new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID, + frame.forget(), false /* frameSent */); + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::FrameSent(uint32_t aWebSocketSerialID, + uint64_t aInnerWindowID, + already_AddRefed<WebSocketFrame> aFrame, + nsIEventTarget* aTarget) { + RefPtr<WebSocketFrame> frame(std::move(aFrame)); + MOZ_ASSERT(frame); + + // Let's continue only if we have some listeners. + if (!HasListeners()) { + return; + } + + RefPtr<WebSocketFrameRunnable> runnable = new WebSocketFrameRunnable( + aWebSocketSerialID, aInnerWindowID, frame.forget(), true /* frameSent */); + + DebugOnly<nsresult> rv = aTarget + ? aTarget->Dispatch(runnable, NS_DISPATCH_NORMAL) + : NS_DispatchToMainThread(runnable); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed"); +} + +void WebSocketEventService::AssociateWebSocketImplWithSerialID( + nsIWebSocketImpl* aWebSocketImpl, uint32_t aWebSocketSerialID) { + MOZ_ASSERT(NS_IsMainThread()); + + mWebSocketImplMap.InsertOrUpdate(aWebSocketSerialID, + do_GetWeakReference(aWebSocketImpl)); +} + +NS_IMETHODIMP +WebSocketEventService::SendMessage(uint32_t aWebSocketSerialID, + const nsAString& aMessage) { + MOZ_ASSERT(NS_IsMainThread()); + + nsWeakPtr weakPtr = mWebSocketImplMap.Get(aWebSocketSerialID); + nsCOMPtr<nsIWebSocketImpl> webSocketImpl = do_QueryReferent(weakPtr); + + if (!webSocketImpl) { + return NS_ERROR_NOT_AVAILABLE; + } + + return webSocketImpl->SendMessage(aMessage); +} + +NS_IMETHODIMP +WebSocketEventService::AddListener(uint64_t aInnerWindowID, + nsIWebSocketEventListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aListener) { + return NS_ERROR_FAILURE; + } + + ++mCountListeners; + + mWindows + .LookupOrInsertWith( + aInnerWindowID, + [&] { + auto listener = MakeUnique<WindowListener>(); + + if (IsChildProcess()) { + PWebSocketEventListenerChild* actor = + gNeckoChild->SendPWebSocketEventListenerConstructor( + aInnerWindowID); + + listener->mActor = + static_cast<WebSocketEventListenerChild*>(actor); + MOZ_ASSERT(listener->mActor); + } + + return listener; + }) + ->mListeners.AppendElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +WebSocketEventService::RemoveListener(uint64_t aInnerWindowID, + nsIWebSocketEventListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!aListener) { + return NS_ERROR_FAILURE; + } + + WindowListener* listener = mWindows.Get(aInnerWindowID); + if (!listener) { + return NS_ERROR_FAILURE; + } + + if (!listener->mListeners.RemoveElement(aListener)) { + return NS_ERROR_FAILURE; + } + + // The last listener for this window. + if (listener->mListeners.IsEmpty()) { + if (IsChildProcess()) { + ShutdownActorListener(listener); + } + + mWindows.Remove(aInnerWindowID); + } + + MOZ_ASSERT(mCountListeners); + --mCountListeners; + + return NS_OK; +} + +NS_IMETHODIMP +WebSocketEventService::HasListenerFor(uint64_t aInnerWindowID, bool* aResult) { + MOZ_ASSERT(NS_IsMainThread()); + + *aResult = mWindows.Get(aInnerWindowID); + + return NS_OK; +} + +NS_IMETHODIMP +WebSocketEventService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!strcmp(aTopic, "xpcom-shutdown")) { + Shutdown(); + return NS_OK; + } + + if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) { + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); + + uint64_t innerID; + nsresult rv = wrapper->GetData(&innerID); + NS_ENSURE_SUCCESS(rv, rv); + + WindowListener* listener = mWindows.Get(innerID); + if (!listener) { + return NS_OK; + } + + MOZ_ASSERT(mCountListeners >= listener->mListeners.Length()); + mCountListeners -= listener->mListeners.Length(); + + if (IsChildProcess()) { + ShutdownActorListener(listener); + } + + mWindows.Remove(innerID); + } + + // This should not happen. + return NS_ERROR_FAILURE; +} + +void WebSocketEventService::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (gWebSocketEventService) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(gWebSocketEventService, "xpcom-shutdown"); + obs->RemoveObserver(gWebSocketEventService, "inner-window-destroyed"); + } + + mWindows.Clear(); + gWebSocketEventService = nullptr; + } +} + +bool WebSocketEventService::HasListeners() const { return !!mCountListeners; } + +void WebSocketEventService::GetListeners( + uint64_t aInnerWindowID, + WebSocketEventService::WindowListeners& aListeners) const { + aListeners.Clear(); + + WindowListener* listener = mWindows.Get(aInnerWindowID); + if (!listener) { + return; + } + + aListeners.AppendElements(listener->mListeners); +} + +void WebSocketEventService::ShutdownActorListener(WindowListener* aListener) { + MOZ_ASSERT(aListener); + MOZ_ASSERT(aListener->mActor); + aListener->mActor->Close(); + aListener->mActor = nullptr; +} + +already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded( + bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode, + bool aMaskBit, uint32_t aMask, const nsCString& aPayload) { + if (!HasListeners()) { + return nullptr; + } + + return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, + aOpCode, aMaskBit, aMask, aPayload); +} + +already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded( + bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode, + bool aMaskBit, uint32_t aMask, uint8_t* aPayload, uint32_t aPayloadLength) { + if (!HasListeners()) { + return nullptr; + } + + nsAutoCString payloadStr; + if (NS_WARN_IF(!(payloadStr.Assign((const char*)aPayload, aPayloadLength, + mozilla::fallible)))) { + return nullptr; + } + + return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, + aOpCode, aMaskBit, aMask, payloadStr); +} + +already_AddRefed<WebSocketFrame> WebSocketEventService::CreateFrameIfNeeded( + bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3, uint8_t aOpCode, + bool aMaskBit, uint32_t aMask, uint8_t* aPayloadInHdr, + uint32_t aPayloadInHdrLength, uint8_t* aPayload, uint32_t aPayloadLength) { + if (!HasListeners()) { + return nullptr; + } + + uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength; + + nsAutoCString payload; + if (NS_WARN_IF(!payload.SetLength(payloadLength, fallible))) { + return nullptr; + } + + char* payloadPtr = payload.BeginWriting(); + if (aPayloadInHdrLength) { + memcpy(payloadPtr, aPayloadInHdr, aPayloadInHdrLength); + } + + memcpy(payloadPtr + aPayloadInHdrLength, aPayload, aPayloadLength); + + return MakeAndAddRef<WebSocketFrame>(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, + aOpCode, aMaskBit, aMask, payload); +} + +} // namespace net +} // namespace mozilla |