gecko-dev/dom/websocket/WebSocket.cpp
Stefan Hindli 91f4c18048 Backed out 4 changesets (bug 1622042) for causing AddonInstallException: Could not install add-on: c:\users\task_1584560140\appdata\local\temp\tmpbg9_h7.zip: ERROR_FILE_ACCESS: There was an error accessing the filesystem. CLOSED TREE
Backed out changeset 929043330599 (bug 1622042)
Backed out changeset b08cdd075e20 (bug 1622042)
Backed out changeset ca87f1c6b90b (bug 1622042)
Backed out changeset 27dbbb7cd590 (bug 1622042)
2020-03-18 22:49:23 +02:00

2769 lines
81 KiB
C++

/* -*- 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 "mozilla/dom/WebSocketBinding.h"
#include "mozilla/net/WebSocketChannel.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "mozilla/Atomics.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/net/WebSocketChannel.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/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsAutoPtr.h"
#include "mozilla/LoadInfo.h"
#include "nsGlobalWindow.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"
#define OPEN_EVENT_STRING NS_LITERAL_STRING("open")
#define MESSAGE_EVENT_STRING NS_LITERAL_STRING("message")
#define ERROR_EVENT_STRING NS_LITERAL_STRING("error")
#define CLOSE_EVENT_STRING NS_LITERAL_STRING("close")
using namespace mozilla::net;
namespace mozilla {
namespace dom {
class WebSocketImpl final : public nsIInterfaceRequestor,
public nsIWebSocketListener,
public nsIObserver,
public nsSupportsWeakReference,
public nsIRequest,
public nsIEventTarget {
public:
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIWEBSOCKETLISTENER
NS_DECL_NSIOBSERVER
NS_DECL_NSIREQUEST
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET_FULL
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(0),
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, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aPrincipal, 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);
nsresult InitializeConnection(nsIPrincipal* aPrincipal,
nsICookieJarSettings* aCookieJarSettings);
// These methods when called can release the WebSocket object
void FailConnection(uint16_t reasonCode,
const nsACString& aReasonString = EmptyCString());
nsresult CloseConnection(uint16_t reasonCode,
const nsACString& aReasonString = EmptyCString());
void Disconnect();
void DisconnectInternal();
nsresult ConsoleError();
void PrintErrorOnConsole(const char* aBundleURI, const char* aError,
nsTArray<nsString>&& aFormatStrings);
nsresult DoOnMessageAvailable(const nsACString& aMsg, bool isBinary);
// 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();
nsresult UpdateURI();
void AddRefObject();
void ReleaseObject();
bool RegisterWorkerRef(WorkerPrivate* aWorkerPrivate);
void UnregisterWorkerRef();
nsresult CancelInternal();
nsresult GetLoadingPrincipal(nsIPrincipal** aPrincipal);
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;
nsWeakPtr mOriginDocument;
// Web Socket owner information:
// - the script file name, UTF8 encoded.
// - source code line number and column number where the Web Socket object
// was constructed.
// - the ID of the inner window where the script lives. Note that this may not
// be the same as the Web Socket owner window.
// 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;
RefPtr<WebSocketEventService> mService;
// For dispatching runnables to main thread.
nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
private:
~WebSocketImpl() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread() == mIsMainThread ||
mDisconnectingOrDisconnected);
// If we threw during Init we never called disconnect
if (!mDisconnectingOrDisconnected) {
Disconnect();
}
}
};
NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
nsIObserver, nsISupportsWeakReference, nsIRequest,
nsIEventTarget)
class CallDispatchConnectionCloseEvents final : public CancelableRunnable {
public:
explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
: CancelableRunnable("dom::CallDispatchConnectionCloseEvents"),
mWebSocketImpl(aWebSocketImpl) {
aWebSocketImpl->AssertIsOnTargetThread();
}
NS_IMETHOD Run() override {
mWebSocketImpl->AssertIsOnTargetThread();
mWebSocketImpl->DispatchConnectionCloseEvents();
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(),
NS_LITERAL_CSTRING("WebSocket :: print error on console")),
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), EmptyString(), mScriptLine,
mScriptColumn, nsIScriptError::errorFlag, "Web Socket", mInnerWindowID);
} else {
rv = errorObject->Init(message, NS_ConvertUTF8toUTF16(mScriptFile),
EmptyString(), mScriptLine, mScriptColumn,
nsIScriptError::errorFlag, "Web Socket",
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();
}
}
private:
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(mReasonCode, mReasonString);
}
private:
RefPtr<WebSocketImpl> mImpl;
uint16_t mReasonCode;
const nsCString mReasonString;
};
} // namespace
nsresult WebSocketImpl::CloseConnection(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(uint16_t aReasonCode,
const nsACString& aReasonString) {
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return;
}
ConsoleError();
mFailed = true;
CloseConnection(aReasonCode, aReasonString);
}
namespace {
class DisconnectInternalRunnable final : public WorkerMainThreadRunnable {
public:
explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
: WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
NS_LITERAL_CSTRING("WebSocket :: disconnect")),
mImpl(aImpl) {}
bool MainThreadRun() override {
mImpl->DisconnectInternal();
return true;
}
private:
// A raw pointer because this runnable is sync.
WebSocketImpl* mImpl;
};
} // namespace
void WebSocketImpl::Disconnect() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread() == mIsMainThread);
if (mDisconnectingOrDisconnected) {
return;
}
// DontKeepAliveAnyMore() and DisconnectInternal() can release the object. So
// hold a reference to this until the end of the method.
RefPtr<WebSocketImpl> kungfuDeathGrip = this;
// Disconnect can be called from some control event (such as a callback from
// StrongWorkerRef). This will be schedulated 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_ReleaseOnMainThreadSystemGroup("WebSocketImpl::mChannel",
mChannel.forget());
NS_ReleaseOnMainThreadSystemGroup("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 release on main-thread because WeakReferences
// are not thread-safe.
mWeakLoadGroup = nullptr;
}
if (!mWorkerRef) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
}
}
}
//-----------------------------------------------------------------------------
// WebSocketImpl::nsIWebSocketListener methods:
//-----------------------------------------------------------------------------
nsresult WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg,
bool isBinary) {
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) {
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)) {
CloseConnection(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;
}
mWebSocket->mOutgoingBufferedAmount -= aSize;
if (!mWebSocket->mOutgoingBufferedAmount.isValid()) {
return NS_ERROR_UNEXPECTED;
}
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
if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
CloseConnection(0, EmptyCString());
} else {
CloseConnection(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;
}
//-----------------------------------------------------------------------------
// 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;
JSObject* WebSocket::WrapObject(JSContext* cx,
JS::Handle<JSObject*> aGivenProto) {
return WebSocket_Binding::Wrap(cx, this, aGivenProto);
}
void WebSocket::BindToOwner(nsIGlobalObject* aNew) {
auto scopeExit =
MakeScopeExit([&] { DOMEventTargetHelper::BindToOwner(aNew); });
// If we're disconnected, then there is no state to update.
if (!mImpl || mImpl->mDisconnectingOrDisconnected) {
return;
}
// Update state on the old window.
if (GetOwner()) {
GetOwner()->UpdateWebSocketCount(-1);
}
// Update state on the new window
nsCOMPtr<nsPIDOMWindowInner> newWindow = do_QueryInterface(aNew);
if (newWindow) {
newWindow->UpdateWebSocketCount(1);
}
}
//---------------------------------------------------------------------------
// WebIDL
//---------------------------------------------------------------------------
// Constructor:
already_AddRefed<WebSocket> WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv) {
Sequence<nsString> protocols;
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
EmptyCString(), aRv);
}
already_AddRefed<WebSocket> WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
const nsAString& aProtocol,
ErrorResult& aRv) {
Sequence<nsString> protocols;
if (!protocols.AppendElement(aProtocol, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
EmptyCString(), aRv);
}
already_AddRefed<WebSocket> WebSocket::Constructor(
const GlobalObject& aGlobal, const nsAString& aUrl,
const Sequence<nsString>& aProtocols, ErrorResult& aRv) {
return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
EmptyCString(), 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,
bool aIsServerSide, const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile, uint32_t aScriptLine,
uint32_t aScriptColumn)
: WebSocketMainThreadRunnable(aWorkerPrivate,
NS_LITERAL_CSTRING("WebSocket :: init")),
mImpl(aImpl),
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;
}
mErrorCode =
mImpl->Init(jsapi.cx(), mWorkerPrivate->GetPrincipal(),
doc->NodePrincipal(), 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(),
aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide, mURL,
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
return true;
}
// Raw pointer. This worker runnable runs synchronously.
WebSocketImpl* mImpl;
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,
NS_LITERAL_CSTRING("WebSocket :: init")),
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(),
NS_LITERAL_CSTRING("WebSocket :: AsyncOpen")),
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->NodePrincipal();
if (!principal) {
mErrorCode = NS_ERROR_FAILURE;
return true;
}
uint64_t windowID = 0;
nsCOMPtr<nsPIDOMWindowOuter> topWindow =
aWindow->GetInProcessScriptableTop();
nsCOMPtr<nsPIDOMWindowInner> topInner;
if (topWindow) {
topInner = topWindow->GetCurrentInnerWindow();
}
if (topInner) {
windowID = topInner->WindowID();
}
mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(),
std::move(mOriginStack));
return true;
}
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mErrorCode = mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0,
nullptr, EmptyCString(), nullptr);
return true;
}
private:
// Raw pointer. This worker runs synchronously.
WebSocketImpl* mImpl;
UniquePtr<SerializedStackHolder> mOriginStack;
nsresult mErrorCode;
};
} // namespace
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<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();
if (!principal) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsCOMPtr<nsIScriptGlobalObject> sgo =
do_QueryInterface(aGlobal.GetAsSupports());
if (!sgo) {
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];
if (protocolElement.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
if (protocolArray.Contains(protocolElement)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
if (protocolElement.FindChar(',') != -1) /* interferes w/list */ {
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.
webSocket->GetOwner()->UpdateWebSocketCount(1);
nsCOMPtr<nsIPrincipal> loadingPrincipal;
aRv = webSocketImpl->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
aRv = webSocketImpl->Init(aGlobal.Context(), loadingPrincipal, principal,
!!aTransportProvider, aUrl, protocolArray,
EmptyCString(), 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);
unsigned lineno, 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, !!aTransportProvider, aUrl, protocolArray,
nsDependentCString(file.get()), lineno, column);
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) {
webSocket->mImpl->FailConnection(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(nsIWebSocketChannel::CLOSE_ABNORMAL);
}
}
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);
nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
nsPIDOMWindowOuter* outerWindow = ownerWindow->GetOuterWindow();
UniquePtr<SerializedStackHolder> stack;
nsIDocShell* docShell = outerWindow->GetDocShell();
if (docShell && docShell->GetWatchedByDevtools()) {
stack = GetCurrentStackForNetMonitor(aGlobal.Context());
}
uint64_t windowID = 0;
nsCOMPtr<nsPIDOMWindowOuter> topWindow =
outerWindow->GetInProcessScriptableTop();
nsCOMPtr<nsPIDOMWindowInner> topInner;
if (topWindow) {
topInner = topWindow->GetCurrentInnerWindow();
}
if (topInner) {
windowID = topInner->WindowID();
}
aRv = webSocket->mImpl->AsyncOpen(principal, 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)
tmp->mImpl->Disconnect();
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) {
mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
DontKeepAliveAnyMore();
}
//-----------------------------------------------------------------------------
// WebSocketImpl:: initialization
//-----------------------------------------------------------------------------
nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aPrincipal, 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)
if (mIsMainThread) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
return NS_ERROR_FAILURE;
}
rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
}
if (!mIsMainThread) {
mScriptFile = aScriptFile;
mScriptLine = aScriptLine;
mScriptColumn = aScriptColumn;
} else {
MOZ_ASSERT(aCx);
unsigned lineno, column;
JS::AutoFilename file;
if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
mScriptFile = file.get();
mScriptLine = lineno;
mScriptColumn = column;
}
}
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) {
mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
}
mPrivateBrowsing = !!aPrincipal->OriginAttributesRef().mPrivateBrowsingId;
mIsChromeContext = aPrincipal->IsSystemPrincipal();
// parses the url
rv = ParseURL(aURL);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent();
if (!originDoc) {
rv = mWebSocket->CheckCurrentGlobalCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
}
mOriginDocument = do_GetWeakReference(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);
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, EmptyCString(),
&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 (!mIsServerSide && !mSecure &&
StaticPrefs::dom_security_https_only_mode()) {
// let's use the old specification before the upgrade for logging
AutoTArray<nsString, 2> params;
CopyUTF8toUTF16(mURI, *params.AppendElement());
mURI.ReplaceSubstring("ws://", "wss://");
if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
return NS_OK;
}
mSecure = true;
params.AppendElement(NS_LITERAL_STRING("wss"));
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeInsecureRequest",
params, nsIScriptError::warningFlag,
mInnerWindowID, mPrivateBrowsing);
}
// 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)) {
// 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(NS_LITERAL_STRING("wss"));
CSP_LogLocalizedStr("upgradeInsecureRequest", params,
EmptyString(), // aSourceFile
EmptyString(), // aScriptSample
0, // aLineNumber
0, // aColumnNumber
nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("upgradeInsecureRequest"),
mInnerWindowID, mPrivateBrowsing);
}
// Don't allow https:// to open ws://
if (!mIsServerSide && !mSecure &&
!Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
false) &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
mAsciiHost)) {
if (aLoadingPrincipal->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) {
for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
}
if (!mRequestedProtocolList.IsEmpty()) {
mRequestedProtocolList.AppendLiteral(", ");
}
AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
}
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 asciiOrigin;
nsresult rv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
NS_ENSURE_SUCCESS(rv, rv);
if (aTransportProvider) {
rv = mChannel->SetServerParameters(aTransportProvider,
aNegotiatedExtensions);
NS_ENSURE_SUCCESS(rv, rv);
}
ToLowerCase(asciiOrigin);
nsCOMPtr<nsIURI> uri;
if (!aTransportProvider) {
rv = NS_NewURI(getter_AddRefs(uri), mURI);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
rv = mChannel->AsyncOpen(uri, asciiOrigin, 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(
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 = do_QueryReferent(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_DATA_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 && doc) {
mMainThreadEventTarget = doc->EventTargetFor(TaskCategory::Other);
}
return NS_OK;
}
void WebSocketImpl::DispatchConnectionCloseEvents() {
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();
}
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, EmptyString());
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;
JS::Rooted<JSObject*> arrayBuf(cx);
nsresult rv =
nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
NS_ENSURE_SUCCESS(rv, 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;
}
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,
EmptyString(), 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) {
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);
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);
bool hasRef;
rv = parsedURL->GetHasRef(&hasRef);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR);
nsAutoCString scheme;
rv = parsedURL->GetScheme(scheme);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
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);
rv = NS_CheckPortSafety(port, scheme.get());
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_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::GetUTFOrigin(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();
} 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() {
AssertIsOnTargetThread();
AddRef();
}
void WebSocketImpl::ReleaseObject() {
AssertIsOnTargetThread();
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(nsIWebSocketChannel::CLOSE_GOING_AWAY,
EmptyCString());
});
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 bufferedAmount;
uint32_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();
NS_ConvertUTF16toUTF8 msgString(aData);
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, EmptyCString(), msgLength, true, aRv);
}
void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
aData.ComputeState();
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString(data, len);
Send(nullptr, msgString, len, true, aRv);
}
void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
AssertIsOnTargetThread();
aData.ComputeState();
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString(data, len);
Send(nullptr, msgString, len, 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;
}
// Always increment outgoing buffer len, even if closed
mOutgoingBufferedAmount += aMsgLength;
if (!mOutgoingBufferedAmount.isValid()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
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) {
AssertIsOnTargetThread();
// 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;
}
if (readyState == CONNECTING) {
mImpl->FailConnection(closeCode, closeReason);
return;
}
MOZ_ASSERT(readyState == OPEN);
mImpl->CloseConnection(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)) {
CloseConnection(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()), mImpl(aImpl) {}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
aWorkerPrivate->AssertIsOnWorkerThread();
return !NS_FAILED(mImpl->CancelInternal());
}
private:
RefPtr<WebSocketImpl> mImpl;
};
} // namespace
// 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;
}
return CloseConnection(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(), WorkerThreadUnchangedBusyCount),
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 the target is the main-thread, we should try to dispatch the runnable
// to a labeled event target.
if (mIsMainThread) {
return mMainThreadEventTarget
? mMainThreadEventTarget->Dispatch(event_ref.forget())
: GetMainThreadEventTarget()->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::IsOnCurrentThread(bool* aResult) {
*aResult = IsTargetThread();
return NS_OK;
}
NS_IMETHODIMP_(bool)
WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
bool WebSocketImpl::IsTargetThread() const {
return NS_IsMainThread() == mIsMainThread;
}
void WebSocket::AssertIsOnTargetThread() const {
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
}
nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
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;
while (true) {
if (principal && !principal->GetIsNullPrincipal()) {
break;
}
if (!innerWindow) {
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.
break;
}
}
nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
innerWindow->GetInProcessScriptableParent();
if (NS_WARN_IF(!parentWindow)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsPIDOMWindowInner> currentInnerWindow =
parentWindow->GetCurrentInnerWindow();
if (NS_WARN_IF(!currentInnerWindow)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// We are at the top. Let's see if we have an opener window.
if (innerWindow == currentInnerWindow) {
parentWindow = nsGlobalWindowOuter::Cast(innerWindow->GetOuterWindow())
->GetSameProcessOpener();
if (!parentWindow) {
break;
}
if (parentWindow->GetInProcessScriptableTop() ==
innerWindow->GetInProcessScriptableTop()) {
break;
}
currentInnerWindow = parentWindow->GetCurrentInnerWindow();
if (NS_WARN_IF(!currentInnerWindow)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
if (currentInnerWindow == innerWindow) {
// The opener may be the same outer window as the parent.
break;
}
}
innerWindow = currentInnerWindow;
nsCOMPtr<Document> document = innerWindow->GetExtantDoc();
if (NS_WARN_IF(!document)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
principal = document->NodePrincipal();
}
principal.forget(aPrincipal);
return NS_OK;
}
} // namespace dom
} // namespace mozilla