2928 lines
87 KiB
C++
2928 lines
87 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 "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();
|
|
} 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();
|
|
}
|
|
|
|
// If we haven't called WebSocket::DisconnectFromOwner yet, update
|
|
// web socket count here.
|
|
if (nsIGlobalObject* global = mWebSocket->GetOwnerGlobal()) {
|
|
global->UpdateWebSocketCount(-1);
|
|
}
|
|
|
|
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;
|
|
|
|
global->UpdateWebSocketCount(1);
|
|
|
|
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.
|
|
|
|
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 (mImpl && !mImpl->mDisconnectingOrDisconnected) {
|
|
GetOwnerGlobal()->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 = MOZ_TRY(net::LoadInfo::Create(
|
|
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;
|
|
}
|
|
JSString* jsString;
|
|
jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
|
|
NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
|
|
|
|
jsData.setString(jsString);
|
|
}
|
|
|
|
mImpl->mService->WebSocketMessageAvailable(
|
|
mImpl->mChannel->Serial(), mImpl->mInnerWindowID, aData, messageType);
|
|
|
|
// create an event that uses the MessageEvent interface,
|
|
// which does not bubble, is not cancelable, and has no default action
|
|
|
|
RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
|
|
|
|
event->InitMessageEvent(nullptr, MESSAGE_EVENT_STRING, CanBubble::eNo,
|
|
Cancelable::eNo, jsData, mImpl->mUTF16Origin, u""_ns,
|
|
nullptr, Sequence<OwningNonNull<MessagePort>>());
|
|
event->SetTrusted(true);
|
|
|
|
ErrorResult err;
|
|
DispatchEvent(*event, err);
|
|
return err.StealNSResult();
|
|
}
|
|
|
|
nsresult WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, uint16_t aCode,
|
|
const nsAString& aReason) {
|
|
AssertIsOnTargetThread();
|
|
|
|
// This method is called by a runnable and it can happen that, in the
|
|
// meantime, GC unlinked this object, so mImpl could be null.
|
|
if (mImpl && mImpl->mChannel) {
|
|
mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
|
|
mImpl->mInnerWindowID, aWasClean, aCode,
|
|
aReason);
|
|
}
|
|
|
|
nsresult rv = CheckCurrentGlobalCorrectness();
|
|
if (NS_FAILED(rv)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
CloseEventInit init;
|
|
init.mBubbles = false;
|
|
init.mCancelable = false;
|
|
init.mWasClean = aWasClean;
|
|
init.mCode = aCode;
|
|
init.mReason = aReason;
|
|
|
|
RefPtr<CloseEvent> event =
|
|
CloseEvent::Constructor(this, CLOSE_EVENT_STRING, init);
|
|
event->SetTrusted(true);
|
|
|
|
ErrorResult err;
|
|
DispatchEvent(*event, err);
|
|
return err.StealNSResult();
|
|
}
|
|
|
|
nsresult WebSocketImpl::ParseURL(const nsAString& aURL, nsIURI* aBaseURI) {
|
|
AssertIsOnMainThread();
|
|
NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
if (mIsServerSide) {
|
|
mWebSocket->mURI = aURL;
|
|
CopyUTF16toUTF8(mWebSocket->mURI, mURI);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBaseURI);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
nsAutoCString scheme;
|
|
rv = parsedURL->GetScheme(scheme);
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
|
|
NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
// If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s
|
|
// [=url/scheme=] to "`ws`". Otherwise, if |urlRecord|'s [=url/scheme=] is
|
|
// "`https`", set |urlRecord|'s [=url/scheme=] to "`wss`".
|
|
// https://websockets.spec.whatwg.org/#dom-websocket-websocket
|
|
|
|
if (scheme == "http" || scheme == "https") {
|
|
scheme = scheme == "https" ? "wss"_ns : "ws"_ns;
|
|
|
|
NS_MutateURI mutator(parsedURL);
|
|
mutator.SetScheme(scheme);
|
|
rv = mutator.Finalize(parsedURL);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
}
|
|
|
|
bool hasRef;
|
|
rv = parsedURL->GetHasRef(&hasRef);
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
nsAutoCString host;
|
|
rv = parsedURL->GetAsciiHost(host);
|
|
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
int32_t port;
|
|
rv = parsedURL->GetPort(&port);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
nsAutoCString filePath;
|
|
rv = parsedURL->GetFilePath(filePath);
|
|
if (filePath.IsEmpty()) {
|
|
filePath.Assign('/');
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
nsAutoCString query;
|
|
rv = parsedURL->GetQuery(query);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
if (scheme.LowerCaseEqualsLiteral("ws")) {
|
|
mSecure = false;
|
|
mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
|
|
} else if (scheme.LowerCaseEqualsLiteral("wss")) {
|
|
mSecure = true;
|
|
mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
|
|
} else {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
rv =
|
|
nsContentUtils::GetWebExposedOriginSerialization(parsedURL, mUTF16Origin);
|
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
|
|
|
|
mAsciiHost = host;
|
|
ToLowerCase(mAsciiHost);
|
|
|
|
mResource = filePath;
|
|
if (!query.IsEmpty()) {
|
|
mResource.Append('?');
|
|
mResource.Append(query);
|
|
}
|
|
uint32_t length = mResource.Length();
|
|
uint32_t i;
|
|
for (i = 0; i < length; ++i) {
|
|
if (mResource[i] < static_cast<char16_t>(0x0021) ||
|
|
mResource[i] > static_cast<char16_t>(0x007E)) {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
}
|
|
|
|
rv = parsedURL->GetSpec(mURI);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
CopyUTF8toUTF16(mURI, mWebSocket->mURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Methods that keep alive the WebSocket object when:
|
|
// 1. the object has registered event listeners that can be triggered
|
|
// ("strong event listeners");
|
|
// 2. there are outgoing not sent messages.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void WebSocket::UpdateMustKeepAlive() {
|
|
// Here we could not have mImpl.
|
|
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
|
|
if (!mCheckMustKeepAlive || !mImpl) {
|
|
return;
|
|
}
|
|
|
|
bool shouldKeepAlive = false;
|
|
uint16_t readyState = ReadyState();
|
|
|
|
if (mListenerManager) {
|
|
switch (readyState) {
|
|
case CONNECTING: {
|
|
if (mListenerManager->HasListenersFor(OPEN_EVENT_STRING) ||
|
|
mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
|
|
mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
|
|
mListenerManager->HasListenersFor(CLOSE_EVENT_STRING)) {
|
|
shouldKeepAlive = true;
|
|
}
|
|
} break;
|
|
|
|
case OPEN:
|
|
case CLOSING: {
|
|
if (mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
|
|
mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
|
|
mListenerManager->HasListenersFor(CLOSE_EVENT_STRING) ||
|
|
mOutgoingBufferedAmount.value() != 0) {
|
|
shouldKeepAlive = true;
|
|
}
|
|
} break;
|
|
|
|
case CLOSED: {
|
|
shouldKeepAlive = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mKeepingAlive && !shouldKeepAlive) {
|
|
mKeepingAlive = false;
|
|
mImpl->ReleaseObject();
|
|
// Note that this could be made 'alive' again if another listener is
|
|
// added.
|
|
} else if (!mKeepingAlive && shouldKeepAlive) {
|
|
mKeepingAlive = true;
|
|
mImpl->AddRefObject();
|
|
}
|
|
}
|
|
|
|
void WebSocket::DontKeepAliveAnyMore() {
|
|
// Here we could not have mImpl.
|
|
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
|
|
if (mKeepingAlive) {
|
|
MOZ_ASSERT(mImpl);
|
|
|
|
mKeepingAlive = false;
|
|
mImpl->ReleaseObject();
|
|
}
|
|
|
|
mCheckMustKeepAlive = false;
|
|
}
|
|
|
|
void WebSocketImpl::AddRefObject() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
AddRef();
|
|
}
|
|
|
|
void WebSocketImpl::ReleaseObject() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
Release();
|
|
}
|
|
|
|
bool WebSocketImpl::RegisterWorkerRef(WorkerPrivate* aWorkerPrivate) {
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
|
|
RefPtr<WebSocketImpl> self(this);
|
|
|
|
// In workers we have to keep the worker alive using a strong reference in
|
|
// order to dispatch messages correctly.
|
|
RefPtr<StrongWorkerRef> workerRef =
|
|
StrongWorkerRef::Create(aWorkerPrivate, "WebSocketImpl", [self]() {
|
|
{
|
|
MutexAutoLock lock(self->mMutex);
|
|
self->mWorkerShuttingDown = true;
|
|
}
|
|
|
|
self->CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY,
|
|
""_ns);
|
|
});
|
|
if (NS_WARN_IF(!workerRef)) {
|
|
return false;
|
|
}
|
|
|
|
mWorkerRef = new ThreadSafeWorkerRef(workerRef);
|
|
MOZ_ASSERT(mWorkerRef);
|
|
|
|
return true;
|
|
}
|
|
|
|
void WebSocketImpl::UnregisterWorkerRef() {
|
|
MOZ_ASSERT(mDisconnectingOrDisconnected);
|
|
MOZ_ASSERT(mWorkerRef);
|
|
mWorkerRef->Private()->AssertIsOnWorkerThread();
|
|
|
|
{
|
|
MutexAutoLock lock(mMutex);
|
|
mWorkerShuttingDown = true;
|
|
}
|
|
|
|
// The DTOR of this StrongWorkerRef will release the worker for us.
|
|
mWorkerRef = nullptr;
|
|
}
|
|
|
|
nsresult WebSocketImpl::UpdateURI() {
|
|
AssertIsOnTargetThread();
|
|
|
|
// Check for Redirections
|
|
RefPtr<BaseWebSocketChannel> channel;
|
|
channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
|
|
MOZ_ASSERT(channel);
|
|
|
|
channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
|
|
mSecure = channel->IsEncrypted();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void WebSocket::EventListenerAdded(nsAtom* aType) {
|
|
AssertIsOnTargetThread();
|
|
UpdateMustKeepAlive();
|
|
}
|
|
|
|
void WebSocket::EventListenerRemoved(nsAtom* aType) {
|
|
AssertIsOnTargetThread();
|
|
UpdateMustKeepAlive();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// WebSocket - methods
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// webIDL: readonly attribute unsigned short readyState;
|
|
uint16_t WebSocket::ReadyState() {
|
|
MutexAutoLock lock(mMutex);
|
|
return mReadyState;
|
|
}
|
|
|
|
void WebSocket::SetReadyState(uint16_t aReadyState) {
|
|
MutexAutoLock lock(mMutex);
|
|
mReadyState = aReadyState;
|
|
}
|
|
|
|
// webIDL: readonly attribute unsigned long long bufferedAmount;
|
|
uint64_t WebSocket::BufferedAmount() const {
|
|
AssertIsOnTargetThread();
|
|
MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
|
|
return mOutgoingBufferedAmount.value();
|
|
}
|
|
|
|
// webIDL: attribute BinaryType binaryType;
|
|
dom::BinaryType WebSocket::BinaryType() const {
|
|
AssertIsOnTargetThread();
|
|
return mBinaryType;
|
|
}
|
|
|
|
// webIDL: attribute BinaryType binaryType;
|
|
void WebSocket::SetBinaryType(dom::BinaryType aData) {
|
|
AssertIsOnTargetThread();
|
|
mBinaryType = aData;
|
|
}
|
|
|
|
// webIDL: readonly attribute DOMString url
|
|
void WebSocket::GetUrl(nsAString& aURL) {
|
|
AssertIsOnTargetThread();
|
|
|
|
if (mEffectiveURL.IsEmpty()) {
|
|
aURL = mURI;
|
|
} else {
|
|
aURL = mEffectiveURL;
|
|
}
|
|
}
|
|
|
|
// webIDL: readonly attribute DOMString extensions;
|
|
void WebSocket::GetExtensions(nsAString& aExtensions) {
|
|
AssertIsOnTargetThread();
|
|
CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
|
|
}
|
|
|
|
// webIDL: readonly attribute DOMString protocol;
|
|
void WebSocket::GetProtocol(nsAString& aProtocol) {
|
|
AssertIsOnTargetThread();
|
|
CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
|
|
}
|
|
|
|
// webIDL: void send(DOMString data);
|
|
void WebSocket::Send(const nsAString& aData, ErrorResult& aRv) {
|
|
AssertIsOnTargetThread();
|
|
|
|
nsAutoCString msgString;
|
|
if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
|
|
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
|
|
return;
|
|
}
|
|
Send(nullptr, msgString, msgString.Length(), false, aRv);
|
|
}
|
|
|
|
void WebSocket::Send(Blob& aData, ErrorResult& aRv) {
|
|
AssertIsOnTargetThread();
|
|
|
|
nsCOMPtr<nsIInputStream> msgStream;
|
|
aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
|
|
uint64_t msgLength = aData.GetSize(aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
|
|
if (msgLength > UINT32_MAX) {
|
|
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
|
|
return;
|
|
}
|
|
|
|
Send(msgStream, ""_ns, msgLength, true, aRv);
|
|
}
|
|
|
|
void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
|
|
AssertIsOnTargetThread();
|
|
|
|
static_assert(
|
|
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
|
|
"byte-sized data required");
|
|
|
|
nsCString msgString;
|
|
if (!aData.AppendDataTo(msgString)) {
|
|
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
|
|
return;
|
|
}
|
|
Send(nullptr, msgString, msgString.Length(), true, aRv);
|
|
}
|
|
|
|
void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
|
|
AssertIsOnTargetThread();
|
|
|
|
static_assert(
|
|
sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1,
|
|
"byte-sized data required");
|
|
|
|
nsCString msgString;
|
|
if (!aData.AppendDataTo(msgString)) {
|
|
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
|
|
return;
|
|
}
|
|
Send(nullptr, msgString, msgString.Length(), true, aRv);
|
|
}
|
|
|
|
void WebSocket::Send(nsIInputStream* aMsgStream, const nsACString& aMsgString,
|
|
uint32_t aMsgLength, bool aIsBinary, ErrorResult& aRv) {
|
|
AssertIsOnTargetThread();
|
|
|
|
int64_t readyState = ReadyState();
|
|
if (readyState == CONNECTING) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
CheckedUint64 outgoingBufferedAmount = mOutgoingBufferedAmount;
|
|
outgoingBufferedAmount += aMsgLength;
|
|
if (!outgoingBufferedAmount.isValid()) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
// Always increment outgoing buffer len, even if closed
|
|
mOutgoingBufferedAmount = outgoingBufferedAmount;
|
|
MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
|
|
|
|
if (readyState == CLOSING || readyState == CLOSED) {
|
|
return;
|
|
}
|
|
|
|
// We must have mImpl when connected.
|
|
MOZ_ASSERT(mImpl);
|
|
MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
|
|
|
|
nsresult rv;
|
|
if (aMsgStream) {
|
|
rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
|
|
} else {
|
|
if (aIsBinary) {
|
|
rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
|
|
} else {
|
|
rv = mImpl->mChannel->SendMsg(aMsgString);
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
aRv.Throw(rv);
|
|
return;
|
|
}
|
|
|
|
UpdateMustKeepAlive();
|
|
}
|
|
|
|
// webIDL: void close(optional unsigned short code, optional DOMString reason):
|
|
void WebSocket::Close(const Optional<uint16_t>& aCode,
|
|
const Optional<nsAString>& aReason, ErrorResult& aRv) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
|
|
// the reason code is optional, but if provided it must be in a specific range
|
|
uint16_t closeCode = 0;
|
|
if (aCode.WasPassed()) {
|
|
if (aCode.Value() != 1000 &&
|
|
(aCode.Value() < 3000 || aCode.Value() > 4999)) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
|
return;
|
|
}
|
|
closeCode = aCode.Value();
|
|
}
|
|
|
|
nsCString closeReason;
|
|
if (aReason.WasPassed()) {
|
|
CopyUTF16toUTF8(aReason.Value(), closeReason);
|
|
|
|
// The API requires the UTF-8 string to be 123 or less bytes
|
|
if (closeReason.Length() > 123) {
|
|
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int64_t readyState = ReadyState();
|
|
if (readyState == CLOSING || readyState == CLOSED) {
|
|
return;
|
|
}
|
|
|
|
// If we don't have mImpl, we are in a shutting down worker where we are still
|
|
// in CONNECTING state, but already disconnected internally.
|
|
if (!mImpl) {
|
|
MOZ_ASSERT(readyState == CONNECTING);
|
|
SetReadyState(CLOSING);
|
|
return;
|
|
}
|
|
|
|
// These could cause the mImpl to be released (and so this to be
|
|
// released); make sure it stays valid through the call
|
|
RefPtr<WebSocketImpl> pin(mImpl);
|
|
|
|
if (readyState == CONNECTING) {
|
|
pin->FailConnection(pin, closeCode, closeReason);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(readyState == OPEN);
|
|
pin->CloseConnection(pin, closeCode, closeReason);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// WebSocketImpl::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("CancelRunnable"), mImpl(aImpl) {}
|
|
|
|
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
return !NS_FAILED(mImpl->CancelInternal());
|
|
}
|
|
|
|
private:
|
|
RefPtr<WebSocketImpl> mImpl;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
NS_IMETHODIMP WebSocketImpl::SetCanceledReason(const nsACString& aReason) {
|
|
return SetCanceledReasonImpl(aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP WebSocketImpl::GetCanceledReason(nsACString& aReason) {
|
|
return GetCanceledReasonImpl(aReason);
|
|
}
|
|
|
|
NS_IMETHODIMP WebSocketImpl::CancelWithReason(nsresult aStatus,
|
|
const nsACString& aReason) {
|
|
return CancelWithReasonImpl(aStatus, aReason);
|
|
}
|
|
|
|
// Window closed, stop/reload button pressed, user navigated away from page,
|
|
// etc.
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::Cancel(nsresult aStatus) {
|
|
AssertIsOnMainThread();
|
|
|
|
if (!mIsMainThread) {
|
|
MOZ_ASSERT(mWorkerRef);
|
|
RefPtr<CancelRunnable> runnable = new CancelRunnable(mWorkerRef, this);
|
|
if (!runnable->Dispatch(mWorkerRef->Private())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
return CancelInternal();
|
|
}
|
|
|
|
nsresult WebSocketImpl::CancelInternal() {
|
|
AssertIsOnTargetThread();
|
|
|
|
// If CancelInternal is called by a runnable, we may already be disconnected
|
|
// by the time it runs.
|
|
if (mDisconnectingOrDisconnected) {
|
|
return NS_OK;
|
|
}
|
|
|
|
int64_t readyState = mWebSocket->ReadyState();
|
|
if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<WebSocketImpl> self(this);
|
|
return CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::Suspend() {
|
|
AssertIsOnMainThread();
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::Resume() {
|
|
AssertIsOnMainThread();
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup) {
|
|
AssertIsOnMainThread();
|
|
|
|
*aLoadGroup = nullptr;
|
|
|
|
if (mIsMainThread) {
|
|
nsCOMPtr<Document> doc = mWebSocket->GetDocumentIfCurrent();
|
|
if (doc) {
|
|
*aLoadGroup = doc->GetDocumentLoadGroup().take();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mWorkerRef);
|
|
|
|
nsPIDOMWindowInner* window = mWorkerRef->Private()->GetAncestorWindow();
|
|
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 WorkerThreadRunnable {
|
|
RefPtr<WebSocketImpl> mWebSocketImpl;
|
|
|
|
public:
|
|
WorkerRunnableDispatcher(WebSocketImpl* aImpl,
|
|
ThreadSafeWorkerRef* aWorkerRef,
|
|
already_AddRefed<nsIRunnable> aEvent)
|
|
: WorkerThreadRunnable("WorkerRunnableDispatcher"),
|
|
mWebSocketImpl(aImpl),
|
|
mEvent(std::move(aEvent)) {}
|
|
|
|
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
// No messages when disconnected.
|
|
if (mWebSocketImpl->mDisconnectingOrDisconnected) {
|
|
NS_WARNING("Dispatching a WebSocket event after the disconnection!");
|
|
return true;
|
|
}
|
|
|
|
return !NS_FAILED(mEvent->Run());
|
|
}
|
|
|
|
void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aRunResult) override {}
|
|
|
|
bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
|
|
// We don't call WorkerRunnable::PreDispatch because it would assert the
|
|
// wrong thing about which thread we're on. We're on whichever thread the
|
|
// channel implementation is running on (probably the main thread or socket
|
|
// transport thread).
|
|
return true;
|
|
}
|
|
|
|
void PostDispatch(WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult) override {
|
|
// We don't call WorkerRunnable::PreDispatch because it would assert the
|
|
// wrong thing about which thread we're on. We're on whichever thread the
|
|
// channel implementation is running on (probably the main thread or socket
|
|
// transport thread).
|
|
}
|
|
|
|
private:
|
|
nsCOMPtr<nsIRunnable> mEvent;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
|
|
nsCOMPtr<nsIRunnable> event(aEvent);
|
|
return Dispatch(event.forget(), aFlags);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
|
|
nsCOMPtr<nsIRunnable> event_ref(aEvent);
|
|
if (mIsMainThread) {
|
|
nsISerialEventTarget* target = GetMainThreadSerialEventTarget();
|
|
NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
|
|
return target->Dispatch(event_ref.forget());
|
|
}
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
if (mWorkerShuttingDown) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_DIAGNOSTIC_ASSERT(mWorkerRef);
|
|
|
|
// If the target is a worker, we have to use a custom WorkerRunnableDispatcher
|
|
// runnable.
|
|
RefPtr<WorkerRunnableDispatcher> event =
|
|
new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget());
|
|
|
|
if (!event->Dispatch(mWorkerRef->Private())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
WebSocketImpl::IsOnCurrentThread(bool* aResult) {
|
|
*aResult = IsTargetThread();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP_(bool)
|
|
WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
|
|
|
|
bool WebSocketImpl::IsTargetThread() const {
|
|
// FIXME: This should also check if we're on the worker thread. Code using
|
|
// `IsOnCurrentThread` could easily misbehave here!
|
|
return NS_IsMainThread() == mIsMainThread;
|
|
}
|
|
|
|
void WebSocket::AssertIsOnTargetThread() const {
|
|
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
|
|
}
|
|
|
|
nsresult WebSocketImpl::IsSecure(bool* aValue) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mIsMainThread);
|
|
|
|
// Check the principal's uri to determine if we were loaded from https.
|
|
nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
|
|
if (globalObject) {
|
|
principal = globalObject->PrincipalOrNull();
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(globalObject);
|
|
if (!innerWindow) {
|
|
// If we are in a XPConnect sandbox or in a JS component,
|
|
// innerWindow will be null. There is nothing on top of this to be
|
|
// considered.
|
|
if (NS_WARN_IF(!principal)) {
|
|
return NS_OK;
|
|
}
|
|
*aValue = principal->SchemeIs("https");
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
|
|
if (NS_WARN_IF(!windowContext)) {
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
}
|
|
|
|
while (true) {
|
|
if (windowContext->GetIsSecure()) {
|
|
*aValue = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (windowContext->IsTop()) {
|
|
break;
|
|
} else {
|
|
// If we're not a top window get the parent window context instead.
|
|
windowContext = windowContext->GetParentWindowContext();
|
|
}
|
|
|
|
if (NS_WARN_IF(!windowContext)) {
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
}
|
|
}
|
|
|
|
*aValue = windowContext->GetIsSecure();
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::dom
|