/* -*- 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 "nsIEventTarget.h"
#include "nsIInterfaceRequestor.h"
#include "nsIRequest.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIWebSocketChannel.h"
#include "nsIWebSocketListener.h"
#include "nsProxyRelease.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 nsIWebSocketImpl,
public GlobalTeardownObserver,
public GlobalFreezeObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIWEBSOCKETIMPL
explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) {
MOZ_ASSERT(NS_IsMainThread());
}
void Disconnect() {
MOZ_ASSERT(NS_IsMainThread());
mOwner = nullptr;
}
void BindToOwner(nsIGlobalObject* aOwner) {
GlobalTeardownObserver::BindToOwner(aOwner);
GlobalFreezeObserver::BindToOwner(aOwner);
}
void DisconnectFromOwner() override;
void FrozenCallback(nsIGlobalObject* aGlobal) override;
private:
~WebSocketImplProxy() =
default;
RefPtr<WebSocketImpl> mOwner;
};
class WebSocketImpl final :
public nsIInterfaceRequestor,
public nsIWebSocketListener,
public nsIRequest,
public nsISerialEventTarget,
public nsIWebSocketImpl,
public GlobalTeardownObserver,
public GlobalFreezeObserver {
public:
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIWEBSOCKETLISTENER
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(nsIGlobalObject* aWindowGlobal, 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);
void DisconnectFromOwner() override {
RefPtr<WebSocketImpl> self(
this);
CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
void FrozenCallback(nsIGlobalObject* aGlobal) override {
RefPtr<WebSocketImpl> self(
this);
CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
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, nsIWebSocketImpl)
void WebSocketImplProxy::DisconnectFromOwner() {
if (!mOwner) {
return;
}
mOwner->DisconnectFromOwner();
GlobalTeardownObserver::DisconnectFromOwner();
}
void WebSocketImplProxy::FrozenCallback(nsIGlobalObject* aGlobal) {
if (!mOwner) {
return;
}
mOwner->FrozenCallback(aGlobal);
}
NS_IMETHODIMP
WebSocketImplProxy::SendMessage(
const nsAString& aMessage) {
if (!mOwner) {
return NS_OK;
}
return mOwner->SendMessage(aMessage);
}
NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
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(mWorkerRef->
Private(), 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, mScriptFile, mScriptLine,
mScriptColumn, nsIScriptError::errorFlag,
"Web Socket"_ns, mInnerWindowID);
}
else {
rv = errorObject->Init(message, mScriptFile, 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 (nsGlobalWindowInner* win = mWebSocket->GetOwnerWindow()) {
win->UpdateWebSocketCount(-1);
}
}
else {
RefPtr<DisconnectInternalRunnable> runnable =
new DisconnectInternalRunnable(
this);
ErrorResult rv;
runnable->Dispatch(GetCurrentThreadWorkerPrivate(), 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) {
GlobalTeardownObserver::DisconnectFromOwner();
DisconnectFreezeObserver();
}
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();
MOZ_ASSERT(mWorkerRef);
// Walk up to our containing page
WorkerPrivate* wp = mWorkerRef->
Private()->GetTopLevelWorker();
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) {
aWorkerPrivate->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;
}
MOZ_ASSERT(mWorkerRef);
nsIPrincipal* principal = mWorkerRef->
Private()->GetPrincipal();
mErrorCode = mImpl->Init(
nullptr, jsapi.cx(), principal->SchemeIs(
"https"), principal,
mClientInfo, mWorkerRef->
Private()->CSPEventListener(), mIsServerSide,
mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
return true;
}
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
MOZ_ASSERT(mWorkerRef);
WorkerPrivate* workerPrivate = mWorkerRef->
Private();
mErrorCode = mImpl->Init(
nullptr, nullptr, workerPrivate->GetPrincipal()->SchemeIs(
"https"),
aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo,
workerPrivate->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(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
}
bool ConnectionFailed()
const {
return mConnectionFailed; }
protected:
virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
MOZ_ASSERT(mWorkerRef);
Document* doc = aWindow->GetExtantDoc();
if (!doc) {
return true;
}
mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
doc->NodePrincipal(), mWorkerRef->
Private()->CookieJarSettings()));
return true;
}
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
MOZ_ASSERT(mWorkerRef);
mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
aTopLevelWorkerPrivate->GetPrincipal(),
mWorkerRef->
Private()->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(aImpl->mWorkerRef);
aImpl->mWorkerRef->
Private()->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.
nsCOMPtr<nsIGlobalObject> global;
if (nsGlobalWindowInner* win = webSocket->GetOwnerWindow()) {
win->UpdateWebSocketCount(1);
global = win->AsGlobal();
}
bool isSecure = principal->SchemeIs(
"https");
aRv = webSocketImpl->IsSecure(&isSecure);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
aRv = webSocketImpl->Init(global, 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(&file, aGlobal.Context(), &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(workerPrivate, 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(workerPrivate, 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(webSocket->mImpl->mWorkerRef->
Private(), 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 &&
GetOwnerWindow()) {
GetOwnerWindow()->UpdateWebSocketCount(-1);
}
DOMEventTargetHelper::DisconnectFromOwner();
if (mImpl) {
RefPtr<WebSocketImpl> pin(mImpl);
pin->CloseConnection(pin, nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
DontKeepAliveAnyMore();
}
//-----------------------------------------------------------------------------
// WebSocketImpl:: initialization
//-----------------------------------------------------------------------------
nsresult WebSocketImpl::Init(nsIGlobalObject* aWindowGlobal, 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);
// 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);
}
// Shut down websocket if window is frozen or destroyed (only needed for
// "ghost" websockets--see bug 696085)
RefPtr<WebSocketImplProxy> proxy;
if (mIsMainThread) {
proxy =
new WebSocketImplProxy(
this);
proxy->BindToOwner(aWindowGlobal);
}
if (!mIsMainThread) {
mScriptFile = aScriptFile;
mScriptLine = aScriptLine;
mScriptColumn = aScriptColumn;
}
else {
MOZ_ASSERT(aCx);
uint32_t lineno;
JS::ColumnNumberOneOrigin column;
JS::AutoFilename file;
if (JS::DescribeScriptedCaller(&file, aCx, &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->GetOwnerWindow()) {
mInnerWindowID = ownerWindow->WindowID();
}
}
mPrivateBrowsing = aPrincipal->OriginAttributesRef().IsPrivateBrowsing();
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,
""_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;
}
}
}
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));
RETURN_NSRESULT_ON_FAILURE(rv);
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;
}
--> --------------------
--> maximum size reached
--> --------------------