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