summaryrefslogtreecommitdiffstats
path: root/dom/websocket/WebSocket.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/websocket/WebSocket.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/websocket/WebSocket.cpp')
-rw-r--r--dom/websocket/WebSocket.cpp2956
1 files changed, 2956 insertions, 0 deletions
diff --git a/dom/websocket/WebSocket.cpp b/dom/websocket/WebSocket.cpp
new file mode 100644
index 0000000000..d0be6e1778
--- /dev/null
+++ b/dom/websocket/WebSocket.cpp
@@ -0,0 +1,2956 @@
+/* -*- 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 "WebSocket.h"
+#include "ErrorList.h"
+#include "mozilla/dom/WebSocketBinding.h"
+#include "mozilla/net/WebSocketChannel.h"
+
+#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Unused.h"
+#include "nsIScriptGlobalObject.h"
+#include "mozilla/dom/Document.h"
+#include "nsXPCOM.h"
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsICookieJarSettings.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURL.h"
+#include "nsThreadUtils.h"
+#include "nsIPromptFactory.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIStringBundle.h"
+#include "nsIConsoleService.h"
+#include "mozilla/dom/CloseEvent.h"
+#include "mozilla/net/WebSocketEventService.h"
+#include "nsJSUtils.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsILoadGroup.h"
+#include "mozilla/Preferences.h"
+#include "xpcpublic.h"
+#include "nsContentPolicyUtils.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsIObserverService.h"
+#include "nsIEventTarget.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsIRequest.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIWebSocketChannel.h"
+#include "nsIWebSocketListener.h"
+#include "nsProxyRelease.h"
+#include "nsWeakReference.h"
+#include "nsIWebSocketImpl.h"
+#include "nsIURIMutator.h"
+
+#define OPEN_EVENT_STRING u"open"_ns
+#define MESSAGE_EVENT_STRING u"message"_ns
+#define ERROR_EVENT_STRING u"error"_ns
+#define CLOSE_EVENT_STRING u"close"_ns
+
+using namespace mozilla::net;
+
+namespace mozilla::dom {
+
+class WebSocketImpl;
+
+// This class is responsible for proxying nsIObserver and nsIWebSocketImpl
+// interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on
+// main thread, so we can let it support weak reference.
+class WebSocketImplProxy final : public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIWebSocketImpl {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWEBSOCKETIMPL
+
+ explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void Disconnect() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mOwner = nullptr;
+ }
+
+ private:
+ ~WebSocketImplProxy() = default;
+
+ RefPtr<WebSocketImpl> mOwner;
+};
+
+class WebSocketImpl final : public nsIInterfaceRequestor,
+ public nsIWebSocketListener,
+ public nsIObserver,
+ public nsIRequest,
+ public nsISerialEventTarget,
+ public nsIWebSocketImpl {
+ public:
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBSOCKETLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIREQUEST
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSIWEBSOCKETIMPL
+
+ explicit WebSocketImpl(WebSocket* aWebSocket)
+ : mWebSocket(aWebSocket),
+ mIsServerSide(false),
+ mSecure(false),
+ mOnCloseScheduled(false),
+ mFailed(false),
+ mDisconnectingOrDisconnected(false),
+ mCloseEventWasClean(false),
+ mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
+ mPort(0),
+ mScriptLine(0),
+ mScriptColumn(1),
+ mInnerWindowID(0),
+ mPrivateBrowsing(false),
+ mIsChromeContext(false),
+ mIsMainThread(true),
+ mMutex("WebSocketImpl::mMutex"),
+ mWorkerShuttingDown(false) {
+ if (!NS_IsMainThread()) {
+ mIsMainThread = false;
+ }
+ }
+
+ void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }
+
+ bool IsTargetThread() const;
+
+ nsresult Init(JSContext* aCx, bool aIsSecure, nsIPrincipal* aPrincipal,
+ const Maybe<ClientInfo>& aClientInfo,
+ nsICSPEventListener* aCSPEventListener, bool aIsServerSide,
+ const nsAString& aURL, nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile, uint32_t aScriptLine,
+ uint32_t aScriptColumn);
+
+ nsresult AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ UniquePtr<SerializedStackHolder> aOriginStack);
+
+ nsresult ParseURL(const nsAString& aURL, nsIURI* aBaseURI);
+ nsresult InitializeConnection(nsIPrincipal* aPrincipal,
+ nsICookieJarSettings* aCookieJarSettings);
+
+ // These methods when called can release the WebSocket object
+ void FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
+ uint16_t reasonCode,
+ const nsACString& aReasonString = ""_ns);
+ nsresult CloseConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
+ uint16_t reasonCode,
+ const nsACString& aReasonString = ""_ns);
+ void Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef);
+ void DisconnectInternal();
+
+ nsresult ConsoleError();
+ void PrintErrorOnConsole(const char* aBundleURI, const char* aError,
+ nsTArray<nsString>&& aFormatStrings);
+
+ nsresult DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) const;
+
+ // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
+ nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
+ nsresult aStatusCode);
+ // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
+ void DispatchConnectionCloseEvents(const RefPtr<WebSocketImpl>& aProofOfRef);
+
+ nsresult UpdateURI();
+
+ void AddRefObject();
+ void ReleaseObject();
+
+ bool RegisterWorkerRef(WorkerPrivate* aWorkerPrivate);
+ void UnregisterWorkerRef();
+
+ nsresult CancelInternal();
+
+ nsresult IsSecure(bool* aValue);
+
+ RefPtr<WebSocket> mWebSocket;
+
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+
+ bool mIsServerSide; // True if we're implementing the server side of a
+ // websocket connection
+
+ bool mSecure; // if true it is using SSL and the wss scheme,
+ // otherwise it is using the ws scheme with no SSL
+
+ bool mOnCloseScheduled;
+ bool mFailed;
+ Atomic<bool> mDisconnectingOrDisconnected;
+
+ // Set attributes of DOM 'onclose' message
+ bool mCloseEventWasClean;
+ nsString mCloseEventReason;
+ uint16_t mCloseEventCode;
+
+ nsCString mAsciiHost; // hostname
+ uint32_t mPort;
+ nsCString mResource; // [filepath[?query]]
+ nsString mUTF16Origin;
+
+ nsCString mURI;
+ nsCString mRequestedProtocolList;
+
+ WeakPtr<Document> mOriginDocument;
+
+ // Web Socket owner information:
+ // - the script file name, UTF8 encoded.
+ // - source code line number and 1-origin column number where the Web Socket
+ // object was constructed.
+ // - the ID of the Web Socket owner window. Note that this may not
+ // be the same as the inner window where the script lives.
+ // e.g within iframes
+ // These attributes are used for error reporting.
+ nsCString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ uint64_t mInnerWindowID;
+ bool mPrivateBrowsing;
+ bool mIsChromeContext;
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+ nsWeakPtr mWeakLoadGroup;
+
+ bool mIsMainThread;
+
+ // This mutex protects mWorkerShuttingDown.
+ mozilla::Mutex mMutex;
+ bool mWorkerShuttingDown MOZ_GUARDED_BY(mMutex);
+
+ RefPtr<WebSocketEventService> mService;
+ nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
+
+ RefPtr<WebSocketImplProxy> mImplProxy;
+
+ private:
+ ~WebSocketImpl() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread ||
+ mDisconnectingOrDisconnected);
+
+ // If we threw during Init we never called disconnect
+ if (!mDisconnectingOrDisconnected) {
+ RefPtr<WebSocketImpl> self(this);
+ Disconnect(self);
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(WebSocketImplProxy, nsIObserver, nsISupportsWeakReference,
+ nsIWebSocketImpl)
+
+NS_IMETHODIMP
+WebSocketImplProxy::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!mOwner) {
+ return NS_OK;
+ }
+
+ return mOwner->Observe(aSubject, aTopic, aData);
+}
+
+NS_IMETHODIMP
+WebSocketImplProxy::SendMessage(const nsAString& aMessage) {
+ if (!mOwner) {
+ return NS_OK;
+ }
+
+ return mOwner->SendMessage(aMessage);
+}
+
+NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
+ nsIObserver, nsIRequest, nsIEventTarget, nsISerialEventTarget,
+ nsIWebSocketImpl)
+
+class CallDispatchConnectionCloseEvents final : public DiscardableRunnable {
+ public:
+ explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
+ : DiscardableRunnable("dom::CallDispatchConnectionCloseEvents"),
+ mWebSocketImpl(aWebSocketImpl) {
+ aWebSocketImpl->AssertIsOnTargetThread();
+ }
+
+ NS_IMETHOD Run() override {
+ mWebSocketImpl->AssertIsOnTargetThread();
+ mWebSocketImpl->DispatchConnectionCloseEvents(mWebSocketImpl);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+};
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable {
+ public:
+ PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI,
+ const char* aError,
+ nsTArray<nsString>&& aFormatStrings)
+ : WorkerMainThreadRunnable(aImpl->mWorkerRef->Private(),
+ "WebSocket :: print error on console"_ns),
+ mImpl(aImpl),
+ mBundleURI(aBundleURI),
+ mError(aError),
+ mFormatStrings(std::move(aFormatStrings)) {}
+
+ bool MainThreadRun() override {
+ mImpl->PrintErrorOnConsole(mBundleURI, mError, std::move(mFormatStrings));
+ return true;
+ }
+
+ private:
+ // Raw pointer because this runnable is sync.
+ WebSocketImpl* mImpl;
+
+ const char* mBundleURI;
+ const char* mError;
+ nsTArray<nsString> mFormatStrings;
+};
+
+} // namespace
+
+void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI,
+ const char* aError,
+ nsTArray<nsString>&& aFormatStrings) {
+ // This method must run on the main thread.
+
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(mWorkerRef);
+
+ RefPtr<PrintErrorOnConsoleRunnable> runnable =
+ new PrintErrorOnConsoleRunnable(this, aBundleURI, aError,
+ std::move(aFormatStrings));
+ ErrorResult rv;
+ runnable->Dispatch(Killing, rv);
+ // XXXbz this seems totally broken. We should be propagating this out, but
+ // none of our callers really propagate anything usefully. Come to think of
+ // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget
+ // runnable??
+ rv.SuppressException();
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIStringBundle> strBundle;
+ rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIScriptError> errorObject(
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Localize the error message
+ nsAutoString message;
+ if (!aFormatStrings.IsEmpty()) {
+ rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
+ } else {
+ rv = strBundle->GetStringFromName(aError, message);
+ }
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (mInnerWindowID) {
+ rv = errorObject->InitWithWindowID(
+ message, NS_ConvertUTF8toUTF16(mScriptFile), u""_ns, mScriptLine,
+ mScriptColumn, nsIScriptError::errorFlag, "Web Socket"_ns,
+ mInnerWindowID);
+ } else {
+ rv =
+ errorObject->Init(message, NS_ConvertUTF8toUTF16(mScriptFile), u""_ns,
+ mScriptLine, mScriptColumn, nsIScriptError::errorFlag,
+ "Web Socket"_ns, mPrivateBrowsing, mIsChromeContext);
+ }
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // print the error message directly to the JS console
+ rv = console->LogMessage(errorObject);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+namespace {
+
+class CancelWebSocketRunnable final : public Runnable {
+ public:
+ CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
+ const nsACString& aReasonString)
+ : Runnable("dom::CancelWebSocketRunnable"),
+ mChannel(aChannel),
+ mReasonCode(aReasonCode),
+ mReasonString(aReasonString) {}
+
+ NS_IMETHOD Run() override {
+ nsresult rv = mChannel->Close(mReasonCode, mReasonString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the close message");
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIWebSocketChannel> mChannel;
+ uint16_t mReasonCode;
+ nsCString mReasonString;
+};
+
+class MOZ_STACK_CLASS MaybeDisconnect {
+ public:
+ explicit MaybeDisconnect(WebSocketImpl* aImpl) : mImpl(aImpl) {}
+
+ ~MaybeDisconnect() {
+ bool toDisconnect = false;
+
+ {
+ MutexAutoLock lock(mImpl->mMutex);
+ toDisconnect = mImpl->mWorkerShuttingDown;
+ }
+
+ if (toDisconnect) {
+ mImpl->Disconnect(mImpl);
+ }
+ }
+
+ private:
+ RefPtr<WebSocketImpl> mImpl;
+};
+
+class CloseConnectionRunnable final : public Runnable {
+ public:
+ CloseConnectionRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode,
+ const nsACString& aReasonString)
+ : Runnable("dom::CloseConnectionRunnable"),
+ mImpl(aImpl),
+ mReasonCode(aReasonCode),
+ mReasonString(aReasonString) {}
+
+ NS_IMETHOD Run() override {
+ return mImpl->CloseConnection(mImpl, mReasonCode, mReasonString);
+ }
+
+ private:
+ RefPtr<WebSocketImpl> mImpl;
+ uint16_t mReasonCode;
+ const nsCString mReasonString;
+};
+
+} // namespace
+
+nsresult WebSocketImpl::CloseConnection(
+ const RefPtr<WebSocketImpl>& aProofOfRef, uint16_t aReasonCode,
+ const nsACString& aReasonString) {
+ if (!IsTargetThread()) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new CloseConnectionRunnable(this, aReasonCode, aReasonString);
+ return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ }
+
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ // If this method is called because the worker is going away, we will not
+ // receive the OnStop() method and we have to disconnect the WebSocket and
+ // release the ThreadSafeWorkerRef.
+ MaybeDisconnect md(this);
+
+ uint16_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
+ return NS_OK;
+ }
+
+ // The common case...
+ if (mChannel) {
+ mWebSocket->SetReadyState(WebSocket::CLOSING);
+
+ // The channel has to be closed on the main-thread.
+
+ if (NS_IsMainThread()) {
+ return mChannel->Close(aReasonCode, aReasonString);
+ }
+
+ RefPtr<CancelWebSocketRunnable> runnable =
+ new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
+ return NS_DispatchToMainThread(runnable);
+ }
+
+ // No channel, but not disconnected: canceled or failed early
+ MOZ_ASSERT(readyState == WebSocket::CONNECTING,
+ "Should only get here for early websocket cancel/error");
+
+ // Server won't be sending us a close code, so use what's passed in here.
+ mCloseEventCode = aReasonCode;
+ CopyUTF8toUTF16(aReasonString, mCloseEventReason);
+
+ mWebSocket->SetReadyState(WebSocket::CLOSING);
+
+ ScheduleConnectionCloseEvents(
+ nullptr, (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
+ aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY)
+ ? NS_OK
+ : NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsresult WebSocketImpl::ConsoleError() {
+ AssertIsOnTargetThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mWorkerShuttingDown) {
+ // Too late to report anything, bail out.
+ return NS_OK;
+ }
+ }
+
+ nsTArray<nsString> formatStrings;
+ CopyUTF8toUTF16(mURI, *formatStrings.AppendElement());
+
+ if (mWebSocket->ReadyState() < WebSocket::OPEN) {
+ PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ "connectionFailure", std::move(formatStrings));
+ } else {
+ PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
+ "netInterrupt", std::move(formatStrings));
+ }
+ /// todo some specific errors - like for message too large
+ return NS_OK;
+}
+
+void WebSocketImpl::FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
+ uint16_t aReasonCode,
+ const nsACString& aReasonString) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ ConsoleError();
+ mFailed = true;
+ CloseConnection(aProofOfRef, aReasonCode, aReasonString);
+
+ if (NS_IsMainThread() && mImplProxy) {
+ mImplProxy->Disconnect();
+ mImplProxy = nullptr;
+ }
+}
+
+namespace {
+
+class DisconnectInternalRunnable final : public WorkerMainThreadRunnable {
+ public:
+ explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
+ : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
+ "WebSocket :: disconnect"_ns),
+ mImpl(aImpl) {}
+
+ bool MainThreadRun() override {
+ mImpl->DisconnectInternal();
+ return true;
+ }
+
+ private:
+ // NOTE: WebSocketImpl may be it the middle of being destroyed.
+ // We can't just hold this as a RefPtr, since after the runnable ends
+ // the sync caller will be released, and can finish destroying WebSocketImpl
+ // before a ref here could be dropped.
+ WebSocketImpl* mImpl;
+};
+
+} // namespace
+
+void WebSocketImpl::Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ // DontKeepAliveAnyMore() and DisconnectInternal() can release the
+ // object. aProofOfRef ensures we're holding a reference to this until
+ // the end of the method.
+
+ // Disconnect can be called from some control event (such as a callback from
+ // StrongWorkerRef). This will be scheduled before any other sync/async
+ // runnable. In order to prevent some double Disconnect() calls, we use this
+ // boolean.
+ mDisconnectingOrDisconnected = true;
+
+ // DisconnectInternal touches observers and nsILoadGroup and it must run on
+ // the main thread.
+
+ if (NS_IsMainThread()) {
+ DisconnectInternal();
+
+ // If we haven't called WebSocket::DisconnectFromOwner yet, update
+ // web socket count here.
+ if (mWebSocket->GetOwner()) {
+ mWebSocket->GetOwner()->UpdateWebSocketCount(-1);
+ }
+ } else {
+ RefPtr<DisconnectInternalRunnable> runnable =
+ new DisconnectInternalRunnable(this);
+ ErrorResult rv;
+ runnable->Dispatch(Killing, rv);
+ // XXXbz this seems totally broken. We should be propagating this out, but
+ // where to, exactly?
+ rv.SuppressException();
+ }
+
+ NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget());
+ NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget());
+
+ mWebSocket->DontKeepAliveAnyMore();
+ mWebSocket->mImpl = nullptr;
+
+ if (mWorkerRef) {
+ UnregisterWorkerRef();
+ }
+
+ // We want to release the WebSocket in the correct thread.
+ mWebSocket = nullptr;
+}
+
+void WebSocketImpl::DisconnectInternal() {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
+ if (loadGroup) {
+ loadGroup->RemoveRequest(this, nullptr, NS_OK);
+ // mWeakLoadGroup has to be released on main-thread because WeakReferences
+ // are not thread-safe.
+ mWeakLoadGroup = nullptr;
+ }
+
+ if (!mWorkerRef) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(mImplProxy, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(mImplProxy, DOM_WINDOW_FROZEN_TOPIC);
+ }
+ }
+
+ if (mImplProxy) {
+ mImplProxy->Disconnect();
+ mImplProxy = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIWebSocketImpl
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::SendMessage(const nsAString& aMessage) {
+ nsString message(aMessage);
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "WebSocketImpl::SendMessage",
+ [self = RefPtr<WebSocketImpl>(this), message = std::move(message)]() {
+ ErrorResult IgnoredErrorResult;
+ self->mWebSocket->Send(message, IgnoredErrorResult);
+ });
+ return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIWebSocketListener methods:
+//-----------------------------------------------------------------------------
+
+nsresult WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg,
+ bool isBinary) const {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSED) {
+ NS_ERROR("Received message after CLOSED");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (readyState == WebSocket::OPEN) {
+ // Dispatch New Message
+ nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the message event");
+ }
+
+ return NS_OK;
+ }
+
+ // CLOSING should be the only other state where it's possible to get msgs
+ // from channel: Spec says to drop them.
+ MOZ_ASSERT(readyState == WebSocket::CLOSING,
+ "Received message while CONNECTING or CLOSED");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ return DoOnMessageAvailable(aMsg, false);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
+ const nsACString& aMsg) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ return DoOnMessageAvailable(aMsg, true);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnStart(nsISupports* aContext) {
+ if (!IsTargetThread()) {
+ nsCOMPtr<nsISupports> context = aContext;
+ return Dispatch(NS_NewRunnableFunction("WebSocketImpl::OnStart",
+ [self = RefPtr{this}, context]() {
+ Unused << self->OnStart(context);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+
+ // This is the only function that sets OPEN, and should be called only once
+ MOZ_ASSERT(readyState != WebSocket::OPEN,
+ "readyState already OPEN! OnStart called twice?");
+
+ // Nothing to do if we've already closed/closing
+ if (readyState != WebSocket::CONNECTING) {
+ return NS_OK;
+ }
+
+ // Attempt to kill "ghost" websocket: but usually too early for check to fail
+ nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ RefPtr<WebSocketImpl> self(this);
+ CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ return rv;
+ }
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ rv = mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ UpdateURI();
+
+ mWebSocket->SetReadyState(WebSocket::OPEN);
+
+ mService->WebSocketOpened(
+ mChannel->Serial(), mInnerWindowID, mWebSocket->mEffectiveURL,
+ mWebSocket->mEstablishedProtocol, mWebSocket->mEstablishedExtensions,
+ mChannel->HttpChannelId());
+
+ // Let's keep the object alive because the webSocket can be CCed in the
+ // onopen callback
+ RefPtr<WebSocket> webSocket = mWebSocket;
+
+ // Call 'onopen'
+ rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the open event");
+ }
+
+ webSocket->UpdateMustKeepAlive();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ // We can be CONNECTING here if connection failed.
+ // We can be OPEN if we have encountered a fatal protocol error
+ // We can be CLOSING if close() was called and/or server initiated close.
+ MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
+ "Shouldn't already be CLOSED when OnStop called");
+
+ return ScheduleConnectionCloseEvents(aContext, aStatusCode);
+}
+
+nsresult WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
+ nsresult aStatusCode) {
+ AssertIsOnTargetThread();
+
+ // no-op if some other code has already initiated close event
+ if (!mOnCloseScheduled) {
+ mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
+
+ if (aStatusCode == NS_BASE_STREAM_CLOSED) {
+ // don't generate an error event just because of an unclean close
+ aStatusCode = NS_OK;
+ }
+
+ if (aStatusCode == NS_ERROR_NET_INADEQUATE_SECURITY) {
+ // TLS negotiation failed so we need to set status code to 1015.
+ mCloseEventCode = 1015;
+ }
+
+ if (NS_FAILED(aStatusCode)) {
+ ConsoleError();
+ mFailed = true;
+ }
+
+ mOnCloseScheduled = true;
+
+ NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());
+ if (aSize > mWebSocket->mOutgoingBufferedAmount.value()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ CheckedUint64 outgoingBufferedAmount = mWebSocket->mOutgoingBufferedAmount;
+ outgoingBufferedAmount -= aSize;
+ if (!outgoingBufferedAmount.isValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWebSocket->mOutgoingBufferedAmount = outgoingBufferedAmount;
+ MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnServerClose(nsISupports* aContext, uint16_t aCode,
+ const nsACString& aReason) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int16_t readyState = mWebSocket->ReadyState();
+
+ MOZ_ASSERT(readyState != WebSocket::CONNECTING,
+ "Received server close before connected?");
+ MOZ_ASSERT(readyState != WebSocket::CLOSED,
+ "Received server close after already closed!");
+
+ // store code/string for onclose DOM event
+ mCloseEventCode = aCode;
+ CopyUTF8toUTF16(aReason, mCloseEventReason);
+
+ if (readyState == WebSocket::OPEN) {
+ // Server initiating close.
+ // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
+ // typically echos the status code it received".
+ // But never send certain codes, per section 7.4.1
+ RefPtr<WebSocketImpl> self(this);
+ if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
+ CloseConnection(self, 0, ""_ns);
+ } else {
+ CloseConnection(self, aCode, aReason);
+ }
+ } else {
+ // We initiated close, and server has replied: OnStop does rest of the work.
+ MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::OnError() {
+ if (!IsTargetThread()) {
+ return Dispatch(
+ NS_NewRunnableFunction("dom::FailConnectionRunnable",
+ [self = RefPtr{this}]() {
+ self->FailConnection(
+ self, nsIWebSocketChannel::CLOSE_ABNORMAL);
+ }),
+ NS_DISPATCH_NORMAL);
+ }
+
+ AssertIsOnTargetThread();
+ RefPtr<WebSocketImpl> self(this);
+ FailConnection(self, nsIWebSocketChannel::CLOSE_ABNORMAL);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult) {
+ AssertIsOnMainThread();
+
+ if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent();
+ if (!win) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow();
+ return wwatch->GetPrompt(outerWindow, aIID, aResult);
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebSocket
+////////////////////////////////////////////////////////////////////////////////
+
+WebSocket::WebSocket(nsIGlobalObject* aGlobal)
+ : DOMEventTargetHelper(aGlobal),
+ mIsMainThread(true),
+ mKeepingAlive(false),
+ mCheckMustKeepAlive(true),
+ mOutgoingBufferedAmount(0),
+ mBinaryType(dom::BinaryType::Blob),
+ mMutex("WebSocket::mMutex"),
+ mReadyState(CONNECTING) {
+ MOZ_ASSERT(aGlobal);
+
+ mImpl = new WebSocketImpl(this);
+ mIsMainThread = mImpl->mIsMainThread;
+}
+
+WebSocket::~WebSocket() = default;
+
+mozilla::Maybe<EventCallbackDebuggerNotificationType>
+WebSocket::GetDebuggerNotificationType() const {
+ return mozilla::Some(EventCallbackDebuggerNotificationType::Websocket);
+}
+
+JSObject* WebSocket::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WebSocket_Binding::Wrap(cx, this, aGivenProto);
+}
+
+//---------------------------------------------------------------------------
+// WebIDL
+//---------------------------------------------------------------------------
+
+// Constructor:
+already_AddRefed<WebSocket> WebSocket::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aUrl,
+ const StringOrStringSequence& aProtocols, ErrorResult& aRv) {
+ if (aProtocols.IsStringSequence()) {
+ return WebSocket::ConstructorCommon(
+ aGlobal, aUrl, aProtocols.GetAsStringSequence(), nullptr, ""_ns, aRv);
+ }
+
+ Sequence<nsString> protocols;
+ if (!protocols.AppendElement(aProtocols.GetAsString(), fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, ""_ns,
+ aRv);
+}
+
+already_AddRefed<WebSocket> WebSocket::CreateServerWebSocket(
+ const GlobalObject& aGlobal, const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsAString& aNegotiatedExtensions, ErrorResult& aRv) {
+ return WebSocket::ConstructorCommon(
+ aGlobal, aUrl, aProtocols, aTransportProvider,
+ NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
+}
+
+namespace {
+
+// This class is used to clear any exception.
+class MOZ_STACK_CLASS ClearException {
+ public:
+ explicit ClearException(JSContext* aCx) : mCx(aCx) {}
+
+ ~ClearException() { JS_ClearPendingException(mCx); }
+
+ private:
+ JSContext* mCx;
+};
+
+class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable {
+ public:
+ WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsACString& aTelemetryKey)
+ : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ AssertIsOnMainThread();
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (window) {
+ return InitWithWindow(window);
+ }
+
+ return InitWindowless(wp);
+ }
+
+ protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
+};
+
+class InitRunnable final : public WebSocketMainThreadRunnable {
+ public:
+ InitRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl,
+ const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
+ bool aIsServerSide, const nsAString& aURL,
+ nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile, uint32_t aScriptLine,
+ uint32_t aScriptColumn)
+ : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
+ mImpl(aImpl),
+ mClientInfo(aClientInfo),
+ mIsServerSide(aIsServerSide),
+ mURL(aURL),
+ mProtocolArray(aProtocolArray),
+ mScriptFile(aScriptFile),
+ mScriptLine(aScriptLine),
+ mScriptColumn(aScriptColumn),
+ mErrorCode(NS_OK) {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ nsresult ErrorCode() const { return mErrorCode; }
+
+ protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(aWindow))) {
+ mErrorCode = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ ClearException ce(jsapi.cx());
+
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ mErrorCode = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ mErrorCode = mImpl->Init(
+ jsapi.cx(), principal->SchemeIs("https"), principal, mClientInfo,
+ mWorkerPrivate->CSPEventListener(), mIsServerSide, mURL, mProtocolArray,
+ mScriptFile, mScriptLine, mScriptColumn);
+ return true;
+ }
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
+
+ mErrorCode =
+ mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
+ aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo,
+ mWorkerPrivate->CSPEventListener(), mIsServerSide, mURL,
+ mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
+ return true;
+ }
+
+ // Raw pointer. This worker runnable runs synchronously.
+ WebSocketImpl* mImpl;
+
+ Maybe<ClientInfo> mClientInfo;
+ bool mIsServerSide;
+ const nsAString& mURL;
+ nsTArray<nsString>& mProtocolArray;
+ nsCString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ nsresult mErrorCode;
+};
+
+class ConnectRunnable final : public WebSocketMainThreadRunnable {
+ public:
+ ConnectRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
+ : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
+ mImpl(aImpl),
+ mConnectionFailed(true) {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool ConnectionFailed() const { return mConnectionFailed; }
+
+ protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return true;
+ }
+
+ mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
+ doc->NodePrincipal(), mWorkerPrivate->CookieJarSettings()));
+ return true;
+ }
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
+
+ mConnectionFailed = NS_FAILED(
+ mImpl->InitializeConnection(aTopLevelWorkerPrivate->GetPrincipal(),
+ mWorkerPrivate->CookieJarSettings()));
+ return true;
+ }
+
+ // Raw pointer. This worker runnable runs synchronously.
+ WebSocketImpl* mImpl;
+
+ bool mConnectionFailed;
+};
+
+class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
+ public:
+ explicit AsyncOpenRunnable(WebSocketImpl* aImpl,
+ UniquePtr<SerializedStackHolder> aOriginStack)
+ : WebSocketMainThreadRunnable(aImpl->mWorkerRef->Private(),
+ "WebSocket :: AsyncOpen"_ns),
+ mImpl(aImpl),
+ mOriginStack(std::move(aOriginStack)),
+ mErrorCode(NS_OK) {
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ nsresult ErrorCode() const { return mErrorCode; }
+
+ protected:
+ virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aWindow);
+
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ mErrorCode = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal();
+ if (!principal) {
+ mErrorCode = NS_ERROR_FAILURE;
+ return true;
+ }
+
+ uint64_t windowID = 0;
+ if (WindowContext* wc = aWindow->GetWindowContext()) {
+ windowID = wc->InnerWindowId();
+ }
+
+ mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, ""_ns,
+ std::move(mOriginStack));
+ return true;
+ }
+
+ virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
+
+ mErrorCode =
+ mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0,
+ nullptr, ""_ns, nullptr);
+ return true;
+ }
+
+ private:
+ // Raw pointer. This worker runs synchronously.
+ WebSocketImpl* mImpl;
+
+ UniquePtr<SerializedStackHolder> mOriginStack;
+
+ nsresult mErrorCode;
+};
+
+} // namespace
+
+// Check a protocol entry contains only valid characters
+bool WebSocket::IsValidProtocolString(const nsString& aValue) {
+ // RFC 6455 (4.1): "not including separator characters as defined in RFC 2616"
+ const char16_t illegalCharacters[] = {0x28, 0x29, 0x3C, 0x3E, 0x40, 0x2C,
+ 0x3B, 0x3A, 0x5C, 0x22, 0x2F, 0x5B,
+ 0x5D, 0x3F, 0x3D, 0x7B, 0x7D};
+
+ // Cannot be empty string
+ if (aValue.IsEmpty()) {
+ return false;
+ }
+
+ const auto* start = aValue.BeginReading();
+ const auto* end = aValue.EndReading();
+
+ auto charFilter = [&](char16_t c) {
+ // RFC 6455 (4.1 P18): "in the range U+0021 to U+007E"
+ if (c < 0x21 || c > 0x7E) {
+ return true;
+ }
+
+ return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
+ c) != std::end(illegalCharacters);
+ };
+
+ return std::find_if(start, end, charFilter) == end;
+}
+
+already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
+ const GlobalObject& aGlobal, const nsAString& aUrl,
+ const Sequence<nsString>& aProtocols,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions, ErrorResult& aRv) {
+ MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
+ nsCOMPtr<nsIPrincipal> principal;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipal;
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (NS_WARN_IF(!global)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!scriptPrincipal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ principal = scriptPrincipal->GetPrincipal();
+ partitionedPrincipal = scriptPrincipal->PartitionedPrincipal();
+ if (!principal || !partitionedPrincipal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ nsTArray<nsString> protocolArray;
+
+ for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
+ const nsString& protocolElement = aProtocols[index];
+
+ // Repeated protocols are not allowed
+ if (protocolArray.Contains(protocolElement)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ // Protocol string value must match constraints
+ if (!IsValidProtocolString(protocolElement)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ protocolArray.AppendElement(protocolElement);
+ }
+
+ RefPtr<WebSocket> webSocket = new WebSocket(global);
+ RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;
+
+ bool connectionFailed = true;
+
+ if (NS_IsMainThread()) {
+ // We're keeping track of all main thread web sockets to be able to
+ // avoid throttling timeouts when we have active web sockets.
+ if (webSocket->GetOwner()) {
+ webSocket->GetOwner()->UpdateWebSocketCount(1);
+ }
+
+ bool isSecure = principal->SchemeIs("https");
+ aRv = webSocketImpl->IsSecure(&isSecure);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = webSocketImpl->Init(aGlobal.Context(), isSecure, principal, Nothing(),
+ nullptr, !!aTransportProvider, aUrl,
+ protocolArray, ""_ns, 0, 0);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Document> doc = webSocket->GetDocumentIfCurrent();
+
+ // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
+ // url parameter, so don't throw if InitializeConnection fails, and call
+ // onerror/onclose asynchronously
+ connectionFailed = NS_FAILED(webSocketImpl->InitializeConnection(
+ principal, doc ? doc->CookieJarSettings() : nullptr));
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ uint32_t lineno;
+ JS::ColumnNumberOneOrigin column;
+ JS::AutoFilename file;
+ if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
+ &column)) {
+ NS_WARNING("Failed to get line number and filename in workers.");
+ }
+
+ RefPtr<InitRunnable> runnable = new InitRunnable(
+ workerPrivate, webSocketImpl,
+ workerPrivate->GlobalScope()->GetClientInfo(), !!aTransportProvider,
+ aUrl, protocolArray, nsDependentCString(file.get()), lineno,
+ column.oneOriginValue());
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = runnable->ErrorCode();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!webSocketImpl->RegisterWorkerRef(workerPrivate))) {
+ // The worker is shutting down.
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<ConnectRunnable> connectRunnable =
+ new ConnectRunnable(workerPrivate, webSocketImpl);
+ connectRunnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ connectionFailed = connectRunnable->ConnectionFailed();
+ }
+
+ // It can be that we have been already disconnected because the WebSocket is
+ // gone away while we where initializing the webSocket.
+ if (!webSocket->mImpl) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // We don't return an error if the connection just failed. Instead we dispatch
+ // an event.
+ if (connectionFailed) {
+ webSocketImpl->FailConnection(webSocketImpl,
+ nsIWebSocketChannel::CLOSE_ABNORMAL);
+ }
+
+ // If we don't have a channel, the connection is failed and onerror() will be
+ // called asynchrounsly.
+ if (!webSocket->mImpl->mChannel) {
+ return webSocket.forget();
+ }
+
+ class MOZ_STACK_CLASS ClearWebSocket {
+ public:
+ explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl), mDone(false) {}
+
+ void Done() { mDone = true; }
+
+ ~ClearWebSocket() {
+ if (!mDone) {
+ mWebSocketImpl->mChannel = nullptr;
+ mWebSocketImpl->FailConnection(mWebSocketImpl,
+ nsIWebSocketChannel::CLOSE_ABNORMAL);
+ }
+ }
+
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+ bool mDone;
+ };
+
+ ClearWebSocket cws(webSocket->mImpl);
+
+ // This operation must be done on the correct thread. The rest must run on the
+ // main-thread.
+ aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (NS_IsMainThread()) {
+ MOZ_ASSERT(principal);
+ MOZ_ASSERT(partitionedPrincipal);
+
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
+
+ UniquePtr<SerializedStackHolder> stack;
+ uint64_t windowID = 0;
+
+ if (ownerWindow) {
+ BrowsingContext* browsingContext = ownerWindow->GetBrowsingContext();
+ if (browsingContext && browsingContext->WatchedByDevTools()) {
+ stack = GetCurrentStackForNetMonitor(aGlobal.Context());
+ }
+
+ if (WindowContext* wc = ownerWindow->GetWindowContext()) {
+ windowID = wc->InnerWindowId();
+ }
+ }
+
+ aRv = webSocket->mImpl->AsyncOpen(partitionedPrincipal, windowID,
+ aTransportProvider, aNegotiatedExtensions,
+ std::move(stack));
+ } else {
+ MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
+ "not yet implemented");
+
+ UniquePtr<SerializedStackHolder> stack;
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ if (workerPrivate->IsWatchedByDevTools()) {
+ stack = GetCurrentStackForNetMonitor(aGlobal.Context());
+ }
+
+ RefPtr<AsyncOpenRunnable> runnable =
+ new AsyncOpenRunnable(webSocket->mImpl, std::move(stack));
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ aRv = runnable->ErrorCode();
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // It can be that we have been already disconnected because the WebSocket is
+ // gone away while we where initializing the webSocket.
+ if (!webSocket->mImpl) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Let's inform devtools about this new active WebSocket.
+ webSocket->mImpl->mService->WebSocketCreated(
+ webSocket->mImpl->mChannel->Serial(), webSocket->mImpl->mInnerWindowID,
+ webSocket->mURI, webSocket->mImpl->mRequestedProtocolList);
+ cws.Done();
+
+ return webSocket.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
+ DOMEventTargetHelper)
+ if (tmp->mImpl) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, DOMEventTargetHelper)
+ if (tmp->mImpl) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
+ RefPtr<WebSocketImpl> pin(tmp->mImpl);
+ pin->Disconnect(pin);
+ MOZ_ASSERT(!tmp->mImpl);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+bool WebSocket::IsCertainlyAliveForCC() const { return mKeepingAlive; }
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebSocket)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
+
+void WebSocket::DisconnectFromOwner() {
+ // If we haven't called WebSocketImpl::Disconnect yet, update web
+ // socket count here.
+ if (NS_IsMainThread() && mImpl && !mImpl->mDisconnectingOrDisconnected &&
+ GetOwner()) {
+ GetOwner()->UpdateWebSocketCount(-1);
+ }
+
+ DOMEventTargetHelper::DisconnectFromOwner();
+
+ if (mImpl) {
+ RefPtr<WebSocketImpl> pin(mImpl);
+ pin->CloseConnection(pin, nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ }
+
+ DontKeepAliveAnyMore();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl:: initialization
+//-----------------------------------------------------------------------------
+
+nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
+ nsIPrincipal* aPrincipal,
+ const Maybe<ClientInfo>& aClientInfo,
+ nsICSPEventListener* aCSPEventListener,
+ bool aIsServerSide, const nsAString& aURL,
+ nsTArray<nsString>& aProtocolArray,
+ const nsACString& aScriptFile,
+ uint32_t aScriptLine, uint32_t aScriptColumn) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+
+ mService = WebSocketEventService::GetOrCreate();
+
+ // We need to keep the implementation alive in case the init disconnects it
+ // because of some error.
+ RefPtr<WebSocketImpl> kungfuDeathGrip = this;
+
+ // Attempt to kill "ghost" websocket: but usually too early for check to fail
+ nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Shut down websocket if window is frozen or destroyed (only needed for
+ // "ghost" websockets--see bug 696085)
+ RefPtr<WebSocketImplProxy> proxy;
+ if (mIsMainThread) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!os)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ proxy = new WebSocketImplProxy(this);
+ rv = os->AddObserver(proxy, DOM_WINDOW_DESTROYED_TOPIC, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = os->AddObserver(proxy, DOM_WINDOW_FROZEN_TOPIC, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!mIsMainThread) {
+ mScriptFile = aScriptFile;
+ mScriptLine = aScriptLine;
+ mScriptColumn = aScriptColumn;
+ } else {
+ MOZ_ASSERT(aCx);
+
+ uint32_t lineno;
+ JS::ColumnNumberOneOrigin column;
+ JS::AutoFilename file;
+ if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
+ mScriptFile = file.get();
+ mScriptLine = lineno;
+ mScriptColumn = column.oneOriginValue();
+ }
+ }
+
+ mIsServerSide = aIsServerSide;
+
+ // If we don't have aCx, we are window-less, so we don't have a
+ // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
+ // DedicateWorkers created by JSM.
+ if (aCx) {
+ if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwner()) {
+ mInnerWindowID = ownerWindow->WindowID();
+ }
+ }
+
+ mPrivateBrowsing = !!aPrincipal->OriginAttributesRef().mPrivateBrowsingId;
+ mIsChromeContext = aPrincipal->IsSystemPrincipal();
+
+ // parses the url
+ nsCOMPtr<nsIURI> baseURI = aPrincipal->GetURI();
+ rv = ParseURL(aURL, baseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent();
+ if (!originDoc) {
+ rv = mWebSocket->CheckCurrentGlobalCorrectness();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mOriginDocument = originDoc;
+
+ if (!mIsServerSide) {
+ nsCOMPtr<nsIURI> uri;
+ {
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
+
+ // We crash here because we are sure that mURI is a valid URI, so either
+ // we are OOM'ing or something else bad is happening.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_CRASH();
+ }
+ }
+
+ // The 'real' nsHttpChannel of the websocket gets opened in the parent.
+ // Since we don't serialize the CSP within child and parent and also not
+ // the context, we have to perform content policy checks here instead of
+ // AsyncOpen().
+ // Please note that websockets can't follow redirects, hence there is no
+ // need to perform a CSP check after redirects.
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
+ aPrincipal, // loading principal
+ aPrincipal, // triggering principal
+ originDoc, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ nsIContentPolicy::TYPE_WEBSOCKET, aClientInfo);
+
+ if (aCSPEventListener) {
+ secCheckLoadInfo->SetCspEventListener(aCSPEventListener);
+ }
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, &shouldLoad,
+ nsContentUtils::GetContentPolicy());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_CP_REJECTED(shouldLoad)) {
+ // Disallowed by content policy
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ // If the HTTPS-Only mode is enabled, we need to upgrade the websocket
+ // connection from ws:// to wss:// and mark it as secure.
+ if (!mSecure && originDoc &&
+ !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
+ originDoc->GetDocumentURI())) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // secCheckLoadInfo is only used for the triggering principal, so this
+ // is okay.
+ if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) {
+ mURI.ReplaceSubstring("ws://", "wss://");
+ if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
+ return NS_OK;
+ }
+ mSecure = true;
+ }
+ }
+ }
+
+ // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
+ // In such a case we have to upgrade ws: to wss: and also update mSecure
+ // to reflect that upgrade. Please note that we can not upgrade from ws:
+ // to wss: before performing content policy checks because CSP needs to
+ // send reports in case the scheme is about to be upgraded.
+ if (!mIsServerSide && !mSecure && originDoc &&
+ originDoc->GetUpgradeInsecureRequests(false) &&
+ !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
+ originDoc->GetDocumentURI())) {
+ // let's use the old specification before the upgrade for logging
+ AutoTArray<nsString, 2> params;
+ CopyUTF8toUTF16(mURI, *params.AppendElement());
+
+ // upgrade the request from ws:// to wss:// and mark as secure
+ mURI.ReplaceSubstring("ws://", "wss://");
+ if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
+ return NS_OK;
+ }
+ mSecure = true;
+
+ params.AppendElement(u"wss"_ns);
+ CSP_LogLocalizedStr("upgradeInsecureRequest", params,
+ u""_ns, // aSourceFile
+ u""_ns, // aScriptSample
+ 0, // aLineNumber
+ 1, // aColumnNumber
+ nsIScriptError::warningFlag,
+ "upgradeInsecureRequest"_ns, mInnerWindowID,
+ mPrivateBrowsing);
+ }
+
+ // Don't allow https:// to open ws://
+ // Check that we aren't a server side websocket or set to be upgraded to wss
+ // or allowing ws from https or a local websocket
+ if (!mIsServerSide && !mSecure &&
+ !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
+ false) &&
+ !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
+ mAsciiHost)) {
+ // If aIsSecure is true then disallow loading ws
+ if (aIsSecure) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Obtain the precursor's URI for the loading principal if it exists
+ // otherwise use the loading principal's URI
+ nsCOMPtr<nsIPrincipal> precursorPrincipal =
+ aPrincipal->GetPrecursorPrincipal();
+ nsCOMPtr<nsIURI> precursorOrLoadingURI = precursorPrincipal
+ ? precursorPrincipal->GetURI()
+ : aPrincipal->GetURI();
+
+ // Check if the parent was loaded securely if we have one
+ if (precursorOrLoadingURI) {
+ nsCOMPtr<nsIURI> precursorOrLoadingInnermostURI =
+ NS_GetInnermostURI(precursorOrLoadingURI);
+ // If the parent was loaded securely then disallow loading ws
+ if (precursorOrLoadingInnermostURI &&
+ precursorOrLoadingInnermostURI->SchemeIs("https")) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+ }
+
+ // Assign the sub protocol list and scan it for illegal values
+ for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
+ if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ mRequestedProtocolList.AppendLiteral(", ");
+ }
+
+ AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
+ }
+
+ if (mIsMainThread) {
+ mImplProxy = std::move(proxy);
+ }
+ return NS_OK;
+}
+
+nsresult WebSocketImpl::AsyncOpen(
+ nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
+ nsITransportProvider* aTransportProvider,
+ const nsACString& aNegotiatedExtensions,
+ UniquePtr<SerializedStackHolder> aOriginStack) {
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+ MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
+
+ nsCString webExposedOriginSerialization;
+ nsresult rv = aPrincipal->GetWebExposedOriginSerialization(
+ webExposedOriginSerialization);
+ if (NS_FAILED(rv)) {
+ webExposedOriginSerialization.AssignLiteral("null");
+ }
+
+ if (aTransportProvider) {
+ rv = mChannel->SetServerParameters(aTransportProvider,
+ aNegotiatedExtensions);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ ToLowerCase(webExposedOriginSerialization);
+
+ nsCOMPtr<nsIURI> uri;
+ if (!aTransportProvider) {
+ rv = NS_NewURI(getter_AddRefs(uri), mURI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = mChannel->AsyncOpenNative(uri, webExposedOriginSerialization,
+ aPrincipal->OriginAttributesRef(),
+ aInnerWindowID, this, nullptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ NotifyNetworkMonitorAlternateStack(mChannel, std::move(aOriginStack));
+
+ mInnerWindowID = aInnerWindowID;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl methods:
+//-----------------------------------------------------------------------------
+
+class nsAutoCloseWS final {
+ public:
+ explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
+ : mWebSocketImpl(aWebSocketImpl) {}
+
+ ~nsAutoCloseWS() {
+ if (!mWebSocketImpl->mChannel) {
+ mWebSocketImpl->CloseConnection(
+ mWebSocketImpl, nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
+ }
+ }
+
+ private:
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+};
+
+nsresult WebSocketImpl::InitializeConnection(
+ nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mChannel, "mChannel should be null");
+
+ nsCOMPtr<nsIWebSocketChannel> wsChannel;
+ nsAutoCloseWS autoClose(this);
+ nsresult rv;
+
+ if (mSecure) {
+ wsChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
+ } else {
+ wsChannel =
+ do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // add ourselves to the document's load group and
+ // provide the http stack the loadgroup info too
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ rv = GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ rv = wsChannel->SetLoadGroup(loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = loadGroup->AddRequest(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWeakLoadGroup = do_GetWeakReference(loadGroup);
+ }
+
+ // manually adding loadinfo to the channel since it
+ // was not set during channel creation.
+ nsCOMPtr<Document> doc(mOriginDocument);
+
+ // mOriginDocument has to be release on main-thread because WeakReferences
+ // are not thread-safe.
+ mOriginDocument = nullptr;
+
+ // The TriggeringPrincipal for websockets must always be a script.
+ // Let's make sure that the doc's principal (if a doc exists)
+ // and aPrincipal are same origin.
+ MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
+
+ rv = wsChannel->InitLoadInfoNative(
+ doc, doc ? doc->NodePrincipal() : aPrincipal, aPrincipal,
+ aCookieJarSettings,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_WEBSOCKET, 0);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!mRequestedProtocolList.IsEmpty()) {
+ rv = wsChannel->SetProtocol(mRequestedProtocolList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
+ NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
+
+ rv = rr->RetargetDeliveryTo(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mChannel = wsChannel;
+
+ if (mIsMainThread) {
+ MOZ_ASSERT(mImplProxy);
+ mService->AssociateWebSocketImplWithSerialID(mImplProxy,
+ mChannel->Serial());
+ }
+
+ return NS_OK;
+}
+
+void WebSocketImpl::DispatchConnectionCloseEvents(
+ const RefPtr<WebSocketImpl>& aProofOfRef) {
+ AssertIsOnTargetThread();
+
+ if (mDisconnectingOrDisconnected) {
+ return;
+ }
+
+ mWebSocket->SetReadyState(WebSocket::CLOSED);
+
+ // Let's keep the object alive because the webSocket can be CCed in the
+ // onerror or in the onclose callback
+ RefPtr<WebSocket> webSocket = mWebSocket;
+
+ // Call 'onerror' if needed
+ if (mFailed) {
+ nsresult rv = webSocket->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event");
+ }
+ }
+
+ nsresult rv = webSocket->CreateAndDispatchCloseEvent(
+ mCloseEventWasClean, mCloseEventCode, mCloseEventReason);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the close event");
+ }
+
+ webSocket->UpdateMustKeepAlive();
+ Disconnect(aProofOfRef);
+}
+
+nsresult WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName) {
+ MOZ_ASSERT(mImpl);
+ AssertIsOnTargetThread();
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+
+ // it doesn't bubble, and it isn't cancelable
+ event->InitEvent(aName, false, false);
+ event->SetTrusted(true);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+nsresult WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
+ bool aIsBinary) {
+ MOZ_ASSERT(mImpl);
+ AssertIsOnTargetThread();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetOwnerGlobal()))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
+
+ // Create appropriate JS object for message
+ JS::Rooted<JS::Value> jsData(cx);
+ if (aIsBinary) {
+ if (mBinaryType == dom::BinaryType::Blob) {
+ messageType = nsIWebSocketEventListener::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 == dom::BinaryType::Arraybuffer) {
+ messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;
+
+ ErrorResult rv;
+ JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aData, rv));
+ ENSURE_SUCCESS(rv, rv.StealNSResult());
+ jsData.setObject(*arrayBuf);
+ } else {
+ MOZ_CRASH("Unknown binary type!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ } else {
+ // JS string
+ nsAutoString utf16Data;
+ if (!AppendUTF8toUTF16(aData, utf16Data, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JSString* jsString;
+ jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
+ NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
+
+ jsData.setString(jsString);
+ }
+
+ mImpl->mService->WebSocketMessageAvailable(
+ mImpl->mChannel->Serial(), mImpl->mInnerWindowID, aData, messageType);
+
+ // create an event that uses the MessageEvent interface,
+ // which does not bubble, is not cancelable, and has no default action
+
+ RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+
+ event->InitMessageEvent(nullptr, MESSAGE_EVENT_STRING, CanBubble::eNo,
+ Cancelable::eNo, jsData, mImpl->mUTF16Origin, u""_ns,
+ nullptr, Sequence<OwningNonNull<MessagePort>>());
+ event->SetTrusted(true);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+nsresult WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, uint16_t aCode,
+ const nsAString& aReason) {
+ AssertIsOnTargetThread();
+
+ // This method is called by a runnable and it can happen that, in the
+ // meantime, GC unlinked this object, so mImpl could be null.
+ if (mImpl && mImpl->mChannel) {
+ mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
+ mImpl->mInnerWindowID, aWasClean, aCode,
+ aReason);
+ }
+
+ nsresult rv = CheckCurrentGlobalCorrectness();
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ CloseEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mWasClean = aWasClean;
+ init.mCode = aCode;
+ init.mReason = aReason;
+
+ RefPtr<CloseEvent> event =
+ CloseEvent::Constructor(this, CLOSE_EVENT_STRING, init);
+ event->SetTrusted(true);
+
+ ErrorResult err;
+ DispatchEvent(*event, err);
+ return err.StealNSResult();
+}
+
+nsresult WebSocketImpl::ParseURL(const nsAString& aURL, nsIURI* aBaseURI) {
+ AssertIsOnMainThread();
+ NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
+
+ if (mIsServerSide) {
+ mWebSocket->mURI = aURL;
+ CopyUTF16toUTF8(mWebSocket->mURI, mURI);
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBaseURI);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString scheme;
+ rv = parsedURL->GetScheme(scheme);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
+ NS_ERROR_DOM_SYNTAX_ERR);
+
+ // If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s
+ // [=url/scheme=] to "`ws`". Otherwise, if |urlRecord|'s [=url/scheme=] is
+ // "`https`", set |urlRecord|'s [=url/scheme=] to "`wss`".
+ // https://websockets.spec.whatwg.org/#dom-websocket-websocket
+
+ if (scheme == "http" || scheme == "https") {
+ scheme = scheme == "https" ? "wss"_ns : "ws"_ns;
+
+ NS_MutateURI mutator(parsedURL);
+ mutator.SetScheme(scheme);
+ rv = mutator.Finalize(parsedURL);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+ }
+
+ bool hasRef;
+ rv = parsedURL->GetHasRef(&hasRef);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString host;
+ rv = parsedURL->GetAsciiHost(host);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
+
+ int32_t port;
+ rv = parsedURL->GetPort(&port);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString filePath;
+ rv = parsedURL->GetFilePath(filePath);
+ if (filePath.IsEmpty()) {
+ filePath.Assign('/');
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ nsAutoCString query;
+ rv = parsedURL->GetQuery(query);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ if (scheme.LowerCaseEqualsLiteral("ws")) {
+ mSecure = false;
+ mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
+ } else if (scheme.LowerCaseEqualsLiteral("wss")) {
+ mSecure = true;
+ mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
+ } else {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ rv =
+ nsContentUtils::GetWebExposedOriginSerialization(parsedURL, mUTF16Origin);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
+
+ mAsciiHost = host;
+ ToLowerCase(mAsciiHost);
+
+ mResource = filePath;
+ if (!query.IsEmpty()) {
+ mResource.Append('?');
+ mResource.Append(query);
+ }
+ uint32_t length = mResource.Length();
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (mResource[i] < static_cast<char16_t>(0x0021) ||
+ mResource[i] > static_cast<char16_t>(0x007E)) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+ }
+
+ rv = parsedURL->GetSpec(mURI);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ CopyUTF8toUTF16(mURI, mWebSocket->mURI);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Methods that keep alive the WebSocket object when:
+// 1. the object has registered event listeners that can be triggered
+// ("strong event listeners");
+// 2. there are outgoing not sent messages.
+//-----------------------------------------------------------------------------
+
+void WebSocket::UpdateMustKeepAlive() {
+ // Here we could not have mImpl.
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ if (!mCheckMustKeepAlive || !mImpl) {
+ return;
+ }
+
+ bool shouldKeepAlive = false;
+ uint16_t readyState = ReadyState();
+
+ if (mListenerManager) {
+ switch (readyState) {
+ case CONNECTING: {
+ if (mListenerManager->HasListenersFor(OPEN_EVENT_STRING) ||
+ mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
+ mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
+ mListenerManager->HasListenersFor(CLOSE_EVENT_STRING)) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case OPEN:
+ case CLOSING: {
+ if (mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
+ mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
+ mListenerManager->HasListenersFor(CLOSE_EVENT_STRING) ||
+ mOutgoingBufferedAmount.value() != 0) {
+ shouldKeepAlive = true;
+ }
+ } break;
+
+ case CLOSED: {
+ shouldKeepAlive = false;
+ }
+ }
+ }
+
+ if (mKeepingAlive && !shouldKeepAlive) {
+ mKeepingAlive = false;
+ mImpl->ReleaseObject();
+ // Note that this could be made 'alive' again if another listener is
+ // added.
+ } else if (!mKeepingAlive && shouldKeepAlive) {
+ mKeepingAlive = true;
+ mImpl->AddRefObject();
+ }
+}
+
+void WebSocket::DontKeepAliveAnyMore() {
+ // Here we could not have mImpl.
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ if (mKeepingAlive) {
+ MOZ_ASSERT(mImpl);
+
+ mKeepingAlive = false;
+ mImpl->ReleaseObject();
+ }
+
+ mCheckMustKeepAlive = false;
+}
+
+void WebSocketImpl::AddRefObject() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
+ AddRef();
+}
+
+void WebSocketImpl::ReleaseObject() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
+ Release();
+}
+
+bool WebSocketImpl::RegisterWorkerRef(WorkerPrivate* aWorkerPrivate) {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ RefPtr<WebSocketImpl> self(this);
+
+ // In workers we have to keep the worker alive using a strong reference in
+ // order to dispatch messages correctly.
+ RefPtr<StrongWorkerRef> workerRef =
+ StrongWorkerRef::Create(aWorkerPrivate, "WebSocketImpl", [self]() {
+ {
+ MutexAutoLock lock(self->mMutex);
+ self->mWorkerShuttingDown = true;
+ }
+
+ self->CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY,
+ ""_ns);
+ });
+ if (NS_WARN_IF(!workerRef)) {
+ return false;
+ }
+
+ mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ MOZ_ASSERT(mWorkerRef);
+
+ return true;
+}
+
+void WebSocketImpl::UnregisterWorkerRef() {
+ MOZ_ASSERT(mDisconnectingOrDisconnected);
+ MOZ_ASSERT(mWorkerRef);
+ mWorkerRef->Private()->AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+ mWorkerShuttingDown = true;
+ }
+
+ // The DTOR of this StrongWorkerRef will release the worker for us.
+ mWorkerRef = nullptr;
+}
+
+nsresult WebSocketImpl::UpdateURI() {
+ AssertIsOnTargetThread();
+
+ // Check for Redirections
+ RefPtr<BaseWebSocketChannel> channel;
+ channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
+ MOZ_ASSERT(channel);
+
+ channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
+ mSecure = channel->IsEncrypted();
+
+ return NS_OK;
+}
+
+void WebSocket::EventListenerAdded(nsAtom* aType) {
+ AssertIsOnTargetThread();
+ UpdateMustKeepAlive();
+}
+
+void WebSocket::EventListenerRemoved(nsAtom* aType) {
+ AssertIsOnTargetThread();
+ UpdateMustKeepAlive();
+}
+
+//-----------------------------------------------------------------------------
+// WebSocket - methods
+//-----------------------------------------------------------------------------
+
+// webIDL: readonly attribute unsigned short readyState;
+uint16_t WebSocket::ReadyState() {
+ MutexAutoLock lock(mMutex);
+ return mReadyState;
+}
+
+void WebSocket::SetReadyState(uint16_t aReadyState) {
+ MutexAutoLock lock(mMutex);
+ mReadyState = aReadyState;
+}
+
+// webIDL: readonly attribute unsigned long long bufferedAmount;
+uint64_t WebSocket::BufferedAmount() const {
+ AssertIsOnTargetThread();
+ MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
+ return mOutgoingBufferedAmount.value();
+}
+
+// webIDL: attribute BinaryType binaryType;
+dom::BinaryType WebSocket::BinaryType() const {
+ AssertIsOnTargetThread();
+ return mBinaryType;
+}
+
+// webIDL: attribute BinaryType binaryType;
+void WebSocket::SetBinaryType(dom::BinaryType aData) {
+ AssertIsOnTargetThread();
+ mBinaryType = aData;
+}
+
+// webIDL: readonly attribute DOMString url
+void WebSocket::GetUrl(nsAString& aURL) {
+ AssertIsOnTargetThread();
+
+ if (mEffectiveURL.IsEmpty()) {
+ aURL = mURI;
+ } else {
+ aURL = mEffectiveURL;
+ }
+}
+
+// webIDL: readonly attribute DOMString extensions;
+void WebSocket::GetExtensions(nsAString& aExtensions) {
+ AssertIsOnTargetThread();
+ CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
+}
+
+// webIDL: readonly attribute DOMString protocol;
+void WebSocket::GetProtocol(nsAString& aProtocol) {
+ AssertIsOnTargetThread();
+ CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
+}
+
+// webIDL: void send(DOMString data);
+void WebSocket::Send(const nsAString& aData, ErrorResult& aRv) {
+ AssertIsOnTargetThread();
+
+ nsAutoCString msgString;
+ if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ Send(nullptr, msgString, msgString.Length(), false, aRv);
+}
+
+void WebSocket::Send(Blob& aData, ErrorResult& aRv) {
+ AssertIsOnTargetThread();
+
+ 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(msgStream, ""_ns, msgLength, true, aRv);
+}
+
+void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
+ AssertIsOnTargetThread();
+
+ static_assert(
+ sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
+ "byte-sized data required");
+
+ nsCString msgString;
+ if (!aData.AppendDataTo(msgString)) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ Send(nullptr, msgString, msgString.Length(), true, aRv);
+}
+
+void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
+ AssertIsOnTargetThread();
+
+ static_assert(
+ sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
+ "byte-sized data required");
+
+ nsCString msgString;
+ if (!aData.AppendDataTo(msgString)) {
+ aRv.Throw(NS_ERROR_FILE_TOO_BIG);
+ return;
+ }
+ Send(nullptr, msgString, msgString.Length(), true, aRv);
+}
+
+void WebSocket::Send(nsIInputStream* aMsgStream, const nsACString& aMsgString,
+ uint32_t aMsgLength, bool aIsBinary, ErrorResult& aRv) {
+ AssertIsOnTargetThread();
+
+ int64_t readyState = ReadyState();
+ if (readyState == CONNECTING) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ CheckedUint64 outgoingBufferedAmount = mOutgoingBufferedAmount;
+ outgoingBufferedAmount += aMsgLength;
+ if (!outgoingBufferedAmount.isValid()) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ // Always increment outgoing buffer len, even if closed
+ mOutgoingBufferedAmount = outgoingBufferedAmount;
+ MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
+
+ if (readyState == CLOSING || readyState == CLOSED) {
+ return;
+ }
+
+ // We must have mImpl when connected.
+ MOZ_ASSERT(mImpl);
+ MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
+
+ nsresult rv;
+ if (aMsgStream) {
+ rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
+ } else {
+ if (aIsBinary) {
+ rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
+ } else {
+ rv = mImpl->mChannel->SendMsg(aMsgString);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+// webIDL: void close(optional unsigned short code, optional DOMString reason):
+void WebSocket::Close(const Optional<uint16_t>& aCode,
+ const Optional<nsAString>& aReason, ErrorResult& aRv) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
+
+ // the reason code is optional, but if provided it must be in a specific range
+ uint16_t closeCode = 0;
+ if (aCode.WasPassed()) {
+ if (aCode.Value() != 1000 &&
+ (aCode.Value() < 3000 || aCode.Value() > 4999)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return;
+ }
+ closeCode = aCode.Value();
+ }
+
+ nsCString closeReason;
+ if (aReason.WasPassed()) {
+ CopyUTF16toUTF8(aReason.Value(), closeReason);
+
+ // The API requires the UTF-8 string to be 123 or less bytes
+ if (closeReason.Length() > 123) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return;
+ }
+ }
+
+ int64_t readyState = ReadyState();
+ if (readyState == CLOSING || readyState == CLOSED) {
+ return;
+ }
+
+ // If we don't have mImpl, we are in a shutting down worker where we are still
+ // in CONNECTING state, but already disconnected internally.
+ if (!mImpl) {
+ MOZ_ASSERT(readyState == CONNECTING);
+ SetReadyState(CLOSING);
+ return;
+ }
+
+ // These could cause the mImpl to be released (and so this to be
+ // released); make sure it stays valid through the call
+ RefPtr<WebSocketImpl> pin(mImpl);
+
+ if (readyState == CONNECTING) {
+ pin->FailConnection(pin, closeCode, closeReason);
+ return;
+ }
+
+ MOZ_ASSERT(readyState == OPEN);
+ pin->CloseConnection(pin, closeCode, closeReason);
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ AssertIsOnMainThread();
+
+ int64_t readyState = mWebSocket->ReadyState();
+ if ((readyState == WebSocket::CLOSING) || (readyState == WebSocket::CLOSED)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
+ if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
+ return NS_OK;
+ }
+
+ if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
+ (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)) {
+ RefPtr<WebSocketImpl> self(this);
+ CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// WebSocketImpl::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+WebSocketImpl::GetName(nsACString& aName) {
+ AssertIsOnMainThread();
+
+ CopyUTF16toUTF8(mWebSocket->mURI, aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::IsPending(bool* aValue) {
+ AssertIsOnTargetThread();
+
+ int64_t readyState = mWebSocket->ReadyState();
+ *aValue = (readyState != WebSocket::CLOSED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetStatus(nsresult* aStatus) {
+ AssertIsOnTargetThread();
+
+ *aStatus = NS_OK;
+ return NS_OK;
+}
+
+namespace {
+
+class CancelRunnable final : public MainThreadWorkerRunnable {
+ public:
+ CancelRunnable(ThreadSafeWorkerRef* aWorkerRef, WebSocketImpl* aImpl)
+ : MainThreadWorkerRunnable(aWorkerRef->Private(), "CancelRunnable"),
+ mImpl(aImpl) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return !NS_FAILED(mImpl->CancelInternal());
+ }
+
+ private:
+ RefPtr<WebSocketImpl> mImpl;
+};
+
+} // namespace
+
+NS_IMETHODIMP WebSocketImpl::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP WebSocketImpl::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP WebSocketImpl::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+// Window closed, stop/reload button pressed, user navigated away from page,
+// etc.
+NS_IMETHODIMP
+WebSocketImpl::Cancel(nsresult aStatus) {
+ AssertIsOnMainThread();
+
+ if (!mIsMainThread) {
+ MOZ_ASSERT(mWorkerRef);
+ RefPtr<CancelRunnable> runnable = new CancelRunnable(mWorkerRef, this);
+ if (!runnable->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ return CancelInternal();
+}
+
+nsresult WebSocketImpl::CancelInternal() {
+ AssertIsOnTargetThread();
+
+ // If CancelInternal is called by a runnable, we may already be disconnected
+ // by the time it runs.
+ if (mDisconnectingOrDisconnected) {
+ return NS_OK;
+ }
+
+ int64_t readyState = mWebSocket->ReadyState();
+ if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
+ return NS_OK;
+ }
+
+ RefPtr<WebSocketImpl> self(this);
+ return CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Suspend() {
+ AssertIsOnMainThread();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Resume() {
+ AssertIsOnMainThread();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ AssertIsOnMainThread();
+
+ *aLoadGroup = nullptr;
+
+ if (mIsMainThread) {
+ nsCOMPtr<Document> doc = mWebSocket->GetDocumentIfCurrent();
+ if (doc) {
+ *aLoadGroup = doc->GetDocumentLoadGroup().take();
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mWorkerRef);
+
+ // Walk up to our containing page
+ WorkerPrivate* wp = mWorkerRef->Private();
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ if (!window) {
+ return NS_OK;
+ }
+
+ Document* doc = window->GetExtantDoc();
+ if (doc) {
+ *aLoadGroup = doc->GetDocumentLoadGroup().take();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ AssertIsOnMainThread();
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ AssertIsOnMainThread();
+
+ *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ AssertIsOnMainThread();
+
+ // we won't change the load flags at all.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+namespace {
+
+class WorkerRunnableDispatcher final : public WorkerRunnable {
+ RefPtr<WebSocketImpl> mWebSocketImpl;
+
+ public:
+ WorkerRunnableDispatcher(WebSocketImpl* aImpl,
+ ThreadSafeWorkerRef* aWorkerRef,
+ already_AddRefed<nsIRunnable> aEvent)
+ : WorkerRunnable(aWorkerRef->Private(), "WorkerRunnableDispatcher",
+ WorkerThread),
+ mWebSocketImpl(aImpl),
+ mEvent(std::move(aEvent)) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ // No messages when disconnected.
+ if (mWebSocketImpl->mDisconnectingOrDisconnected) {
+ NS_WARNING("Dispatching a WebSocket event after the disconnection!");
+ return true;
+ }
+
+ return !NS_FAILED(mEvent->Run());
+ }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {}
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ // We don't call WorkerRunnable::PreDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or socket
+ // transport thread).
+ return true;
+ }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ // We don't call WorkerRunnable::PreDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or socket
+ // transport thread).
+ }
+
+ private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
+ nsCOMPtr<nsIRunnable> event_ref(aEvent);
+ if (mIsMainThread) {
+ nsISerialEventTarget* target = GetMainThreadSerialEventTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+ return target->Dispatch(event_ref.forget());
+ }
+
+ MutexAutoLock lock(mMutex);
+ if (mWorkerShuttingDown) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mWorkerRef);
+
+ // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
+ // runnable.
+ RefPtr<WorkerRunnableDispatcher> event =
+ new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget());
+
+ if (!event->Dispatch()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebSocketImpl::IsOnCurrentThread(bool* aResult) {
+ *aResult = IsTargetThread();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
+
+bool WebSocketImpl::IsTargetThread() const {
+ // FIXME: This should also check if we're on the worker thread. Code using
+ // `IsOnCurrentThread` could easily misbehave here!
+ return NS_IsMainThread() == mIsMainThread;
+}
+
+void WebSocket::AssertIsOnTargetThread() const {
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+}
+
+nsresult WebSocketImpl::IsSecure(bool* aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mIsMainThread);
+
+ // Check the principal's uri to determine if we were loaded from https.
+ nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
+ nsCOMPtr<nsIPrincipal> principal;
+
+ if (globalObject) {
+ principal = globalObject->PrincipalOrNull();
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(globalObject);
+ if (!innerWindow) {
+ // If we are in a XPConnect sandbox or in a JS component,
+ // innerWindow will be null. There is nothing on top of this to be
+ // considered.
+ if (NS_WARN_IF(!principal)) {
+ return NS_OK;
+ }
+ *aValue = principal->SchemeIs("https");
+ return NS_OK;
+ }
+
+ RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
+ if (NS_WARN_IF(!windowContext)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ while (true) {
+ if (windowContext->GetIsSecure()) {
+ *aValue = true;
+ return NS_OK;
+ }
+
+ if (windowContext->IsTop()) {
+ break;
+ } else {
+ // If we're not a top window get the parent window context instead.
+ windowContext = windowContext->GetParentWindowContext();
+ }
+
+ if (NS_WARN_IF(!windowContext)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ *aValue = windowContext->GetIsSecure();
+ return NS_OK;
+}
+
+} // namespace mozilla::dom