Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/dom/websocket/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 86 kB image not shown  

Quelle  WebSocket.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "WebSocket.h"
#include "ErrorList.h"
#include "mozilla/dom/WebSocketBinding.h"
#include "mozilla/net/WebSocketChannel.h"

#include "js/ColumnNumber.h"  // JS::ColumnNumberOneOrigin
#include "jsapi.h"
#include "jsfriendapi.h"
#include "mozilla/Atomics.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SerializedStackHolder.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/Unused.h"
#include "nsIScriptGlobalObject.h"
#include "mozilla/dom/Document.h"
#include "nsXPCOM.h"
#include "nsContentUtils.h"
#include "nsError.h"
#include "nsICookieJarSettings.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURL.h"
#include "nsThreadUtils.h"
#include "nsIPromptFactory.h"
#include "nsIWindowWatcher.h"
#include "nsIPrompt.h"
#include "nsIStringBundle.h"
#include "nsIConsoleService.h"
#include "mozilla/dom/CloseEvent.h"
#include "mozilla/net/WebSocketEventService.h"
#include "nsJSUtils.h"
#include "nsIScriptError.h"
#include "nsNetUtil.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsILoadGroup.h"
#include "mozilla/Preferences.h"
#include "xpcpublic.h"
#include "nsContentPolicyUtils.h"
#include "nsWrapperCacheInlines.h"
#include "nsIEventTarget.h"
#include "nsIInterfaceRequestor.h"
#include "nsIRequest.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIWebSocketChannel.h"
#include "nsIWebSocketListener.h"
#include "nsProxyRelease.h"
#include "nsIWebSocketImpl.h"
#include "nsIURIMutator.h"

#define OPEN_EVENT_STRING u"open"_ns
#define MESSAGE_EVENT_STRING u"message"_ns
#define ERROR_EVENT_STRING u"error"_ns
#define CLOSE_EVENT_STRING u"close"_ns

using namespace mozilla::net;

namespace mozilla::dom {

class WebSocketImpl;

// This class is responsible for proxying nsIObserver and nsIWebSocketImpl
// interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on
// main thread, so we can let it support weak reference.
class WebSocketImplProxy final : public nsIWebSocketImpl,
                                 public GlobalTeardownObserver,
                                 public GlobalFreezeObserver {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIWEBSOCKETIMPL

  explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  void Disconnect() {
    MOZ_ASSERT(NS_IsMainThread());

    mOwner = nullptr;
  }

  void BindToOwner(nsIGlobalObject* aOwner) {
    GlobalTeardownObserver::BindToOwner(aOwner);
    GlobalFreezeObserver::BindToOwner(aOwner);
  }

  void DisconnectFromOwner() override;
  void FrozenCallback(nsIGlobalObject* aGlobal) override;

 private:
  ~WebSocketImplProxy() = default;

  RefPtr<WebSocketImpl> mOwner;
};

class WebSocketImpl final : public nsIInterfaceRequestor,
                            public nsIWebSocketListener,
                            public nsIRequest,
                            public nsISerialEventTarget,
                            public nsIWebSocketImpl,
                            public GlobalTeardownObserver,
                            public GlobalFreezeObserver {
 public:
  NS_DECL_NSIINTERFACEREQUESTOR
  NS_DECL_NSIWEBSOCKETLISTENER
  NS_DECL_NSIREQUEST
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIEVENTTARGET_FULL
  NS_DECL_NSIWEBSOCKETIMPL

  explicit WebSocketImpl(WebSocket* aWebSocket)
      : mWebSocket(aWebSocket),
        mIsServerSide(false),
        mSecure(false),
        mOnCloseScheduled(false),
        mFailed(false),
        mDisconnectingOrDisconnected(false),
        mCloseEventWasClean(false),
        mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
        mPort(0),
        mScriptLine(0),
        mScriptColumn(1),
        mInnerWindowID(0),
        mPrivateBrowsing(false),
        mIsChromeContext(false),
        mIsMainThread(true),
        mMutex("WebSocketImpl::mMutex"),
        mWorkerShuttingDown(false) {
    if (!NS_IsMainThread()) {
      mIsMainThread = false;
    }
  }

  void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }

  bool IsTargetThread() const;

  nsresult Init(nsIGlobalObject* aWindowGlobal, JSContext* aCx, bool aIsSecure,
                nsIPrincipal* aPrincipal, const Maybe<ClientInfo>& aClientInfo,
                nsICSPEventListener* aCSPEventListener, bool aIsServerSide,
                const nsAString& aURL, nsTArray<nsString>& aProtocolArray,
                const nsACString& aScriptFile, uint32_t aScriptLine,
                uint32_t aScriptColumn);

  nsresult AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
                     nsITransportProvider* aTransportProvider,
                     const nsACString& aNegotiatedExtensions,
                     UniquePtr<SerializedStackHolder> aOriginStack);

  nsresult ParseURL(const nsAString& aURL, nsIURI* aBaseURI);
  nsresult InitializeConnection(nsIPrincipal* aPrincipal,
                                nsICookieJarSettings* aCookieJarSettings);

  // These methods when called can release the WebSocket object
  void FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
                      uint16_t reasonCode,
                      const nsACString& aReasonString = ""_ns);
  nsresult CloseConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
                           uint16_t reasonCode,
                           const nsACString& aReasonString = ""_ns);
  void Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef);
  void DisconnectInternal();

  nsresult ConsoleError();
  void PrintErrorOnConsole(const char* aBundleURI, const char* aError,
                           nsTArray<nsString>&& aFormatStrings);

  nsresult DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) const;

  // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
  nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
                                         nsresult aStatusCode);
  // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
  void DispatchConnectionCloseEvents(const RefPtr<WebSocketImpl>& aProofOfRef);

  nsresult UpdateURI();

  void AddRefObject();
  void ReleaseObject();

  bool RegisterWorkerRef(WorkerPrivate* aWorkerPrivate);
  void UnregisterWorkerRef();

  nsresult CancelInternal();

  nsresult IsSecure(bool* aValue);

  void DisconnectFromOwner() override {
    RefPtr<WebSocketImpl> self(this);
    CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
  }
  void FrozenCallback(nsIGlobalObject* aGlobal) override {
    RefPtr<WebSocketImpl> self(this);
    CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
  }

  RefPtr<WebSocket> mWebSocket;

  nsCOMPtr<nsIWebSocketChannel> mChannel;

  bool mIsServerSide;  // True if we're implementing the server side of a
                       // websocket connection

  bool mSecure;  // if true it is using SSL and the wss scheme,
                 // otherwise it is using the ws scheme with no SSL

  bool mOnCloseScheduled;
  bool mFailed;
  Atomic<bool> mDisconnectingOrDisconnected;

  // Set attributes of DOM 'onclose' message
  bool mCloseEventWasClean;
  nsString mCloseEventReason;
  uint16_t mCloseEventCode;

  nsCString mAsciiHost;  // hostname
  uint32_t mPort;
  nsCString mResource;  // [filepath[?query]]
  nsString mUTF16Origin;

  nsCString mURI;
  nsCString mRequestedProtocolList;

  WeakPtr<Document> mOriginDocument;

  // Web Socket owner information:
  // - the script file name, UTF8 encoded.
  // - source code line number and 1-origin column number where the Web Socket
  //   object was constructed.
  // - the ID of the Web Socket owner window. Note that this may not
  //   be the same as the inner window where the script lives.
  //   e.g within iframes
  // These attributes are used for error reporting.
  nsCString mScriptFile;
  uint32_t mScriptLine;
  uint32_t mScriptColumn;
  uint64_t mInnerWindowID;
  bool mPrivateBrowsing;
  bool mIsChromeContext;

  RefPtr<ThreadSafeWorkerRef> mWorkerRef;

  nsWeakPtr mWeakLoadGroup;

  bool mIsMainThread;

  // This mutex protects mWorkerShuttingDown.
  mozilla::Mutex mMutex;
  bool mWorkerShuttingDown MOZ_GUARDED_BY(mMutex);

  RefPtr<WebSocketEventService> mService;
  nsCOMPtr<nsIPrincipal> mLoadingPrincipal;

  RefPtr<WebSocketImplProxy> mImplProxy;

 private:
  ~WebSocketImpl() {
    MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread ||
                       mDisconnectingOrDisconnected);

    // If we threw during Init we never called disconnect
    if (!mDisconnectingOrDisconnected) {
      RefPtr<WebSocketImpl> self(this);
      Disconnect(self);
    }
  }
};

NS_IMPL_ISUPPORTS(WebSocketImplProxy, nsIWebSocketImpl)

void WebSocketImplProxy::DisconnectFromOwner() {
  if (!mOwner) {
    return;
  }

  mOwner->DisconnectFromOwner();
  GlobalTeardownObserver::DisconnectFromOwner();
}

void WebSocketImplProxy::FrozenCallback(nsIGlobalObject* aGlobal) {
  if (!mOwner) {
    return;
  }

  mOwner->FrozenCallback(aGlobal);
}

NS_IMETHODIMP
WebSocketImplProxy::SendMessage(const nsAString& aMessage) {
  if (!mOwner) {
    return NS_OK;
  }

  return mOwner->SendMessage(aMessage);
}

NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
                  nsIRequest, nsIEventTarget, nsISerialEventTarget,
                  nsIWebSocketImpl)

class CallDispatchConnectionCloseEvents final : public DiscardableRunnable {
 public:
  explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
      : DiscardableRunnable("dom::CallDispatchConnectionCloseEvents"),
        mWebSocketImpl(aWebSocketImpl) {
    aWebSocketImpl->AssertIsOnTargetThread();
  }

  NS_IMETHOD Run() override {
    mWebSocketImpl->AssertIsOnTargetThread();
    mWebSocketImpl->DispatchConnectionCloseEvents(mWebSocketImpl);
    return NS_OK;
  }

 private:
  RefPtr<WebSocketImpl> mWebSocketImpl;
};

//-----------------------------------------------------------------------------
// WebSocketImpl
//-----------------------------------------------------------------------------

namespace {

class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable {
 public:
  PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI,
                              const char* aError,
                              nsTArray<nsString>&& aFormatStrings)
      : WorkerMainThreadRunnable(aImpl->mWorkerRef->Private(),
                                 "WebSocket :: print error on console"_ns),
        mImpl(aImpl),
        mBundleURI(aBundleURI),
        mError(aError),
        mFormatStrings(std::move(aFormatStrings)) {}

  bool MainThreadRun() override {
    mImpl->PrintErrorOnConsole(mBundleURI, mError, std::move(mFormatStrings));
    return true;
  }

 private:
  // Raw pointer because this runnable is sync.
  WebSocketImpl* mImpl;

  const char* mBundleURI;
  const char* mError;
  nsTArray<nsString> mFormatStrings;
};

}  // namespace

void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI,
                                        const char* aError,
                                        nsTArray<nsString>&& aFormatStrings) {
  // This method must run on the main thread.

  if (!NS_IsMainThread()) {
    MOZ_ASSERT(mWorkerRef);

    RefPtr<PrintErrorOnConsoleRunnable> runnable =
        new PrintErrorOnConsoleRunnable(this, aBundleURI, aError,
                                        std::move(aFormatStrings));
    ErrorResult rv;
    runnable->Dispatch(mWorkerRef->Private(), Killing, rv);
    // XXXbz this seems totally broken.  We should be propagating this out, but
    // none of our callers really propagate anything usefully.  Come to think of
    // it, why is this a syncrunnable anyway?  Can't this be a fire-and-forget
    // runnable??
    rv.SuppressException();
    return;
  }

  nsresult rv;
  nsCOMPtr<nsIStringBundleService> bundleService =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS_VOID(rv);

  nsCOMPtr<nsIStringBundle> strBundle;
  rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
  NS_ENSURE_SUCCESS_VOID(rv);

  nsCOMPtr<nsIConsoleService> console(
      do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS_VOID(rv);

  nsCOMPtr<nsIScriptError> errorObject(
      do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS_VOID(rv);

  // Localize the error message
  nsAutoString message;
  if (!aFormatStrings.IsEmpty()) {
    rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
  } else {
    rv = strBundle->GetStringFromName(aError, message);
  }
  NS_ENSURE_SUCCESS_VOID(rv);

  if (mInnerWindowID) {
    rv = errorObject->InitWithWindowID(message, mScriptFile, mScriptLine,
                                       mScriptColumn, nsIScriptError::errorFlag,
                                       "Web Socket"_ns, mInnerWindowID);
  } else {
    rv = errorObject->Init(message, mScriptFile, mScriptLine, mScriptColumn,
                           nsIScriptError::errorFlag, "Web Socket"_ns,
                           mPrivateBrowsing, mIsChromeContext);
  }

  NS_ENSURE_SUCCESS_VOID(rv);

  // print the error message directly to the JS console
  rv = console->LogMessage(errorObject);
  NS_ENSURE_SUCCESS_VOID(rv);
}

namespace {

class CancelWebSocketRunnable final : public Runnable {
 public:
  CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
                          const nsACString& aReasonString)
      : Runnable("dom::CancelWebSocketRunnable"),
        mChannel(aChannel),
        mReasonCode(aReasonCode),
        mReasonString(aReasonString) {}

  NS_IMETHOD Run() override {
    nsresult rv = mChannel->Close(mReasonCode, mReasonString);
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to dispatch the close message");
    }
    return NS_OK;
  }

 private:
  nsCOMPtr<nsIWebSocketChannel> mChannel;
  uint16_t mReasonCode;
  nsCString mReasonString;
};

class MOZ_STACK_CLASS MaybeDisconnect {
 public:
  explicit MaybeDisconnect(WebSocketImpl* aImpl) : mImpl(aImpl) {}

  ~MaybeDisconnect() {
    bool toDisconnect = false;

    {
      MutexAutoLock lock(mImpl->mMutex);
      toDisconnect = mImpl->mWorkerShuttingDown;
    }

    if (toDisconnect) {
      mImpl->Disconnect(mImpl);
    }
  }

 private:
  RefPtr<WebSocketImpl> mImpl;
};

class CloseConnectionRunnable final : public Runnable {
 public:
  CloseConnectionRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode,
                          const nsACString& aReasonString)
      : Runnable("dom::CloseConnectionRunnable"),
        mImpl(aImpl),
        mReasonCode(aReasonCode),
        mReasonString(aReasonString) {}

  NS_IMETHOD Run() override {
    return mImpl->CloseConnection(mImpl, mReasonCode, mReasonString);
  }

 private:
  RefPtr<WebSocketImpl> mImpl;
  uint16_t mReasonCode;
  const nsCString mReasonString;
};

}  // namespace

nsresult WebSocketImpl::CloseConnection(
    const RefPtr<WebSocketImpl>& aProofOfRef, uint16_t aReasonCode,
    const nsACString& aReasonString) {
  if (!IsTargetThread()) {
    nsCOMPtr<nsIRunnable> runnable =
        new CloseConnectionRunnable(this, aReasonCode, aReasonString);
    return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
  }

  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  // If this method is called because the worker is going away, we will not
  // receive the OnStop() method and we have to disconnect the WebSocket and
  // release the ThreadSafeWorkerRef.
  MaybeDisconnect md(this);

  uint16_t readyState = mWebSocket->ReadyState();
  if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
    return NS_OK;
  }

  // The common case...
  if (mChannel) {
    mWebSocket->SetReadyState(WebSocket::CLOSING);

    // The channel has to be closed on the main-thread.

    if (NS_IsMainThread()) {
      return mChannel->Close(aReasonCode, aReasonString);
    }

    RefPtr<CancelWebSocketRunnable> runnable =
        new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
    return NS_DispatchToMainThread(runnable);
  }

  // No channel, but not disconnected: canceled or failed early
  MOZ_ASSERT(readyState == WebSocket::CONNECTING,
             "Should only get here for early websocket cancel/error");

  // Server won't be sending us a close code, so use what's passed in here.
  mCloseEventCode = aReasonCode;
  CopyUTF8toUTF16(aReasonString, mCloseEventReason);

  mWebSocket->SetReadyState(WebSocket::CLOSING);

  ScheduleConnectionCloseEvents(
      nullptr, (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
                aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY)
                   ? NS_OK
                   : NS_ERROR_FAILURE);

  return NS_OK;
}

nsresult WebSocketImpl::ConsoleError() {
  AssertIsOnTargetThread();

  {
    MutexAutoLock lock(mMutex);
    if (mWorkerShuttingDown) {
      // Too late to report anything, bail out.
      return NS_OK;
    }
  }

  nsTArray<nsString> formatStrings;
  CopyUTF8toUTF16(mURI, *formatStrings.AppendElement());

  if (mWebSocket->ReadyState() < WebSocket::OPEN) {
    PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
                        "connectionFailure", std::move(formatStrings));
  } else {
    PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
                        "netInterrupt", std::move(formatStrings));
  }
  /// todo some specific errors - like for message too large
  return NS_OK;
}

void WebSocketImpl::FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
                                   uint16_t aReasonCode,
                                   const nsACString& aReasonString) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return;
  }

  ConsoleError();
  mFailed = true;
  CloseConnection(aProofOfRef, aReasonCode, aReasonString);

  if (NS_IsMainThread() && mImplProxy) {
    mImplProxy->Disconnect();
    mImplProxy = nullptr;
  }
}

namespace {

class DisconnectInternalRunnable final : public WorkerMainThreadRunnable {
 public:
  explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
      : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
                                 "WebSocket :: disconnect"_ns),
        mImpl(aImpl) {}

  bool MainThreadRun() override {
    mImpl->DisconnectInternal();
    return true;
  }

 private:
  // NOTE: WebSocketImpl may be it the middle of being destroyed.
  // We can't just hold this as a RefPtr, since after the runnable ends
  // the sync caller will be released, and can finish destroying WebSocketImpl
  // before a ref here could be dropped.
  WebSocketImpl* mImpl;
};

}  // namespace

void WebSocketImpl::Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);

  if (mDisconnectingOrDisconnected) {
    return;
  }

  // DontKeepAliveAnyMore() and DisconnectInternal() can release the
  // object.  aProofOfRef ensures we're holding a reference to this until
  // the end of the method.

  // Disconnect can be called from some control event (such as a callback from
  // StrongWorkerRef). This will be scheduled before any other sync/async
  // runnable. In order to prevent some double Disconnect() calls, we use this
  // boolean.
  mDisconnectingOrDisconnected = true;

  // DisconnectInternal touches observers and nsILoadGroup and it must run on
  // the main thread.

  if (NS_IsMainThread()) {
    DisconnectInternal();

    // If we haven't called WebSocket::DisconnectFromOwner yet, update
    // web socket count here.
    if (nsGlobalWindowInner* win = mWebSocket->GetOwnerWindow()) {
      win->UpdateWebSocketCount(-1);
    }
  } else {
    RefPtr<DisconnectInternalRunnable> runnable =
        new DisconnectInternalRunnable(this);
    ErrorResult rv;
    runnable->Dispatch(GetCurrentThreadWorkerPrivate(), Killing, rv);
    // XXXbz this seems totally broken.  We should be propagating this out, but
    // where to, exactly?
    rv.SuppressException();
  }

  NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget());
  NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget());

  mWebSocket->DontKeepAliveAnyMore();
  mWebSocket->mImpl = nullptr;

  if (mWorkerRef) {
    UnregisterWorkerRef();
  }

  // We want to release the WebSocket in the correct thread.
  mWebSocket = nullptr;
}

void WebSocketImpl::DisconnectInternal() {
  AssertIsOnMainThread();

  nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
  if (loadGroup) {
    loadGroup->RemoveRequest(this, nullptr, NS_OK);
    // mWeakLoadGroup has to be released on main-thread because WeakReferences
    // are not thread-safe.
    mWeakLoadGroup = nullptr;
  }

  if (!mWorkerRef) {
    GlobalTeardownObserver::DisconnectFromOwner();
    DisconnectFreezeObserver();
  }

  if (mImplProxy) {
    mImplProxy->Disconnect();
    mImplProxy = nullptr;
  }
}

//-----------------------------------------------------------------------------
// WebSocketImpl::nsIWebSocketImpl
//-----------------------------------------------------------------------------

NS_IMETHODIMP
WebSocketImpl::SendMessage(const nsAString& aMessage) {
  nsString message(aMessage);
  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
      "WebSocketImpl::SendMessage",
      [self = RefPtr<WebSocketImpl>(this), message = std::move(message)]() {
        ErrorResult IgnoredErrorResult;
        self->mWebSocket->Send(message, IgnoredErrorResult);
      });
  return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
}

//-----------------------------------------------------------------------------
// WebSocketImpl::nsIWebSocketListener methods:
//-----------------------------------------------------------------------------

nsresult WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg,
                                             bool isBinary) const {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  int16_t readyState = mWebSocket->ReadyState();
  if (readyState == WebSocket::CLOSED) {
    NS_ERROR("Received message after CLOSED");
    return NS_ERROR_UNEXPECTED;
  }

  if (readyState == WebSocket::OPEN) {
    // Dispatch New Message
    nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to dispatch the message event");
    }

    return NS_OK;
  }

  // CLOSING should be the only other state where it's possible to get msgs
  // from channel: Spec says to drop them.
  MOZ_ASSERT(readyState == WebSocket::CLOSING,
             "Received message while CONNECTING or CLOSED");
  return NS_OK;
}

NS_IMETHODIMP
WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
                                  const nsACString& aMsg) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  return DoOnMessageAvailable(aMsg, false);
}

NS_IMETHODIMP
WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
                                        const nsACString& aMsg) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  return DoOnMessageAvailable(aMsg, true);
}

NS_IMETHODIMP
WebSocketImpl::OnStart(nsISupports* aContext) {
  if (!IsTargetThread()) {
    nsCOMPtr<nsISupports> context = aContext;
    return Dispatch(NS_NewRunnableFunction("WebSocketImpl::OnStart",
                                           [self = RefPtr{this}, context]() {
                                             Unused << self->OnStart(context);
                                           }),
                    NS_DISPATCH_NORMAL);
  }

  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  int16_t readyState = mWebSocket->ReadyState();

  // This is the only function that sets OPEN, and should be called only once
  MOZ_ASSERT(readyState != WebSocket::OPEN,
             "readyState already OPEN! OnStart called twice?");

  // Nothing to do if we've already closed/closing
  if (readyState != WebSocket::CONNECTING) {
    return NS_OK;
  }

  // Attempt to kill "ghost" websocket: but usually too early for check to fail
  nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
  if (NS_FAILED(rv)) {
    RefPtr<WebSocketImpl> self(this);
    CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
    return rv;
  }

  if (!mRequestedProtocolList.IsEmpty()) {
    rv = mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  rv = mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  UpdateURI();

  mWebSocket->SetReadyState(WebSocket::OPEN);

  mService->WebSocketOpened(
      mChannel->Serial(), mInnerWindowID, mWebSocket->mEffectiveURL,
      mWebSocket->mEstablishedProtocol, mWebSocket->mEstablishedExtensions,
      mChannel->HttpChannelId());

  // Let's keep the object alive because the webSocket can be CCed in the
  // onopen callback
  RefPtr<WebSocket> webSocket = mWebSocket;

  // Call 'onopen'
  rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING);
  if (NS_FAILED(rv)) {
    NS_WARNING("Failed to dispatch the open event");
  }

  webSocket->UpdateMustKeepAlive();
  return NS_OK;
}

NS_IMETHODIMP
WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  // We can be CONNECTING here if connection failed.
  // We can be OPEN if we have encountered a fatal protocol error
  // We can be CLOSING if close() was called and/or server initiated close.
  MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
             "Shouldn't already be CLOSED when OnStop called");

  return ScheduleConnectionCloseEvents(aContext, aStatusCode);
}

nsresult WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
                                                      nsresult aStatusCode) {
  AssertIsOnTargetThread();

  // no-op if some other code has already initiated close event
  if (!mOnCloseScheduled) {
    mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);

    if (aStatusCode == NS_BASE_STREAM_CLOSED) {
      // don't generate an error event just because of an unclean close
      aStatusCode = NS_OK;
    }

    if (aStatusCode == NS_ERROR_NET_INADEQUATE_SECURITY) {
      // TLS negotiation failed so we need to set status code to 1015.
      mCloseEventCode = 1015;
    }

    if (NS_FAILED(aStatusCode)) {
      ConsoleError();
      mFailed = true;
    }

    mOnCloseScheduled = true;

    NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
  }

  return NS_OK;
}

NS_IMETHODIMP
WebSocketImpl::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());
  if (aSize > mWebSocket->mOutgoingBufferedAmount.value()) {
    return NS_ERROR_UNEXPECTED;
  }

  CheckedUint64 outgoingBufferedAmount = mWebSocket->mOutgoingBufferedAmount;
  outgoingBufferedAmount -= aSize;
  if (!outgoingBufferedAmount.isValid()) {
    return NS_ERROR_UNEXPECTED;
  }

  mWebSocket->mOutgoingBufferedAmount = outgoingBufferedAmount;
  MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());

  return NS_OK;
}

NS_IMETHODIMP
WebSocketImpl::OnServerClose(nsISupports* aContext, uint16_t aCode,
                             const nsACString& aReason) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return NS_OK;
  }

  int16_t readyState = mWebSocket->ReadyState();

  MOZ_ASSERT(readyState != WebSocket::CONNECTING,
             "Received server close before connected?");
  MOZ_ASSERT(readyState != WebSocket::CLOSED,
             "Received server close after already closed!");

  // store code/string for onclose DOM event
  mCloseEventCode = aCode;
  CopyUTF8toUTF16(aReason, mCloseEventReason);

  if (readyState == WebSocket::OPEN) {
    // Server initiating close.
    // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
    // typically echos the status code it received".
    // But never send certain codes, per section 7.4.1
    RefPtr<WebSocketImpl> self(this);
    if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
      CloseConnection(self, 0, ""_ns);
    } else {
      CloseConnection(self, aCode, aReason);
    }
  } else {
    // We initiated close, and server has replied: OnStop does rest of the work.
    MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
  }

  return NS_OK;
}

NS_IMETHODIMP
WebSocketImpl::OnError() {
  if (!IsTargetThread()) {
    return Dispatch(
        NS_NewRunnableFunction("dom::FailConnectionRunnable",
                               [self = RefPtr{this}]() {
                                 self->FailConnection(
                                     self, nsIWebSocketChannel::CLOSE_ABNORMAL);
                               }),
        NS_DISPATCH_NORMAL);
  }

  AssertIsOnTargetThread();
  RefPtr<WebSocketImpl> self(this);
  FailConnection(self, nsIWebSocketChannel::CLOSE_ABNORMAL);
  return NS_OK;
}

//-----------------------------------------------------------------------------
// WebSocketImpl::nsIInterfaceRequestor
//-----------------------------------------------------------------------------

NS_IMETHODIMP
WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult) {
  AssertIsOnMainThread();

  if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
    return NS_ERROR_FAILURE;
  }

  if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
      aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
    nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent();
    if (!win) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    nsresult rv;
    nsCOMPtr<nsIPromptFactory> wwatch =
        do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow();
    return wwatch->GetPrompt(outerWindow, aIID, aResult);
  }

  return QueryInterface(aIID, aResult);
}

////////////////////////////////////////////////////////////////////////////////
// WebSocket
////////////////////////////////////////////////////////////////////////////////

WebSocket::WebSocket(nsIGlobalObject* aGlobal)
    : DOMEventTargetHelper(aGlobal),
      mIsMainThread(true),
      mKeepingAlive(false),
      mCheckMustKeepAlive(true),
      mOutgoingBufferedAmount(0),
      mBinaryType(dom::BinaryType::Blob),
      mMutex("WebSocket::mMutex"),
      mReadyState(CONNECTING) {
  MOZ_ASSERT(aGlobal);

  mImpl = new WebSocketImpl(this);
  mIsMainThread = mImpl->mIsMainThread;
}

WebSocket::~WebSocket() = default;

mozilla::Maybe<EventCallbackDebuggerNotificationType>
WebSocket::GetDebuggerNotificationType() const {
  return mozilla::Some(EventCallbackDebuggerNotificationType::Websocket);
}

JSObject* WebSocket::WrapObject(JSContext* cx,
                                JS::Handle<JSObject*> aGivenProto) {
  return WebSocket_Binding::Wrap(cx, this, aGivenProto);
}

//---------------------------------------------------------------------------
// WebIDL
//---------------------------------------------------------------------------

// Constructor:
already_AddRefed<WebSocket> WebSocket::Constructor(
    const GlobalObject& aGlobal, const nsAString& aUrl,
    const StringOrStringSequence& aProtocols, ErrorResult& aRv) {
  if (aProtocols.IsStringSequence()) {
    return WebSocket::ConstructorCommon(
        aGlobal, aUrl, aProtocols.GetAsStringSequence(), nullptr, ""_ns, aRv);
  }

  Sequence<nsString> protocols;
  if (!protocols.AppendElement(aProtocols.GetAsString(), fallible)) {
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return nullptr;
  }

  return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, ""_ns,
                                      aRv);
}

already_AddRefed<WebSocket> WebSocket::CreateServerWebSocket(
    const GlobalObject& aGlobal, const nsAString& aUrl,
    const Sequence<nsString>& aProtocols,
    nsITransportProvider* aTransportProvider,
    const nsAString& aNegotiatedExtensions, ErrorResult& aRv) {
  return WebSocket::ConstructorCommon(
      aGlobal, aUrl, aProtocols, aTransportProvider,
      NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
}

namespace {

// This class is used to clear any exception.
class MOZ_STACK_CLASS ClearException {
 public:
  explicit ClearException(JSContext* aCx) : mCx(aCx) {}

  ~ClearException() { JS_ClearPendingException(mCx); }

 private:
  JSContext* mCx;
};

class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable {
 public:
  WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
                              const nsACString& aTelemetryKey)
      : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey) {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  bool MainThreadRun() override {
    AssertIsOnMainThread();
    MOZ_ASSERT(mWorkerRef);

    // Walk up to our containing page
    WorkerPrivate* wp = mWorkerRef->Private()->GetTopLevelWorker();

    nsPIDOMWindowInner* window = wp->GetWindow();
    if (window) {
      return InitWithWindow(window);
    }

    return InitWindowless(wp);
  }

 protected:
  virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;

  virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
};

class InitRunnable final : public WebSocketMainThreadRunnable {
 public:
  InitRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl,
               const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
               bool aIsServerSide, const nsAString& aURL,
               nsTArray<nsString>& aProtocolArray,
               const nsACString& aScriptFile, uint32_t aScriptLine,
               uint32_t aScriptColumn)
      : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
        mImpl(aImpl),
        mClientInfo(aClientInfo),
        mIsServerSide(aIsServerSide),
        mURL(aURL),
        mProtocolArray(aProtocolArray),
        mScriptFile(aScriptFile),
        mScriptLine(aScriptLine),
        mScriptColumn(aScriptColumn),
        mErrorCode(NS_OK) {
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  nsresult ErrorCode() const { return mErrorCode; }

 protected:
  virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
    AutoJSAPI jsapi;
    if (NS_WARN_IF(!jsapi.Init(aWindow))) {
      mErrorCode = NS_ERROR_FAILURE;
      return true;
    }

    ClearException ce(jsapi.cx());

    Document* doc = aWindow->GetExtantDoc();
    if (!doc) {
      mErrorCode = NS_ERROR_FAILURE;
      return true;
    }

    MOZ_ASSERT(mWorkerRef);

    nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
    mErrorCode = mImpl->Init(
        nullptr, jsapi.cx(), principal->SchemeIs("https"), principal,
        mClientInfo, mWorkerRef->Private()->CSPEventListener(), mIsServerSide,
        mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
    return true;
  }

  virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
    MOZ_ASSERT(mWorkerRef);

    WorkerPrivate* workerPrivate = mWorkerRef->Private();

    mErrorCode = mImpl->Init(
        nullptr, nullptr, workerPrivate->GetPrincipal()->SchemeIs("https"),
        aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo,
        workerPrivate->CSPEventListener(), mIsServerSide, mURL, mProtocolArray,
        mScriptFile, mScriptLine, mScriptColumn);
    return true;
  }

  // Raw pointer. This worker runnable runs synchronously.
  WebSocketImpl* mImpl;

  Maybe<ClientInfo> mClientInfo;
  bool mIsServerSide;
  const nsAString& mURL;
  nsTArray<nsString>& mProtocolArray;
  nsCString mScriptFile;
  uint32_t mScriptLine;
  uint32_t mScriptColumn;
  nsresult mErrorCode;
};

class ConnectRunnable final : public WebSocketMainThreadRunnable {
 public:
  ConnectRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
      : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
        mImpl(aImpl),
        mConnectionFailed(true) {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  bool ConnectionFailed() const { return mConnectionFailed; }

 protected:
  virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
    MOZ_ASSERT(mWorkerRef);

    Document* doc = aWindow->GetExtantDoc();
    if (!doc) {
      return true;
    }

    mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
        doc->NodePrincipal(), mWorkerRef->Private()->CookieJarSettings()));
    return true;
  }

  virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
    MOZ_ASSERT(mWorkerRef);

    mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
        aTopLevelWorkerPrivate->GetPrincipal(),
        mWorkerRef->Private()->CookieJarSettings()));
    return true;
  }

  // Raw pointer. This worker runnable runs synchronously.
  WebSocketImpl* mImpl;

  bool mConnectionFailed;
};

class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
 public:
  explicit AsyncOpenRunnable(WebSocketImpl* aImpl,
                             UniquePtr<SerializedStackHolder> aOriginStack)
      : WebSocketMainThreadRunnable(aImpl->mWorkerRef->Private(),
                                    "WebSocket :: AsyncOpen"_ns),
        mImpl(aImpl),
        mOriginStack(std::move(aOriginStack)),
        mErrorCode(NS_OK) {
    MOZ_ASSERT(aImpl->mWorkerRef);
    aImpl->mWorkerRef->Private()->AssertIsOnWorkerThread();
  }

  nsresult ErrorCode() const { return mErrorCode; }

 protected:
  virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
    AssertIsOnMainThread();
    MOZ_ASSERT(aWindow);

    Document* doc = aWindow->GetExtantDoc();
    if (!doc) {
      mErrorCode = NS_ERROR_FAILURE;
      return true;
    }

    nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal();
    if (!principal) {
      mErrorCode = NS_ERROR_FAILURE;
      return true;
    }

    uint64_t windowID = 0;
    if (WindowContext* wc = aWindow->GetWindowContext()) {
      windowID = wc->InnerWindowId();
    }

    mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, ""_ns,
                                  std::move(mOriginStack));
    return true;
  }

  virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());

    mErrorCode =
        mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0,
                         nullptr, ""_ns, nullptr);
    return true;
  }

 private:
  // Raw pointer. This worker runs synchronously.
  WebSocketImpl* mImpl;

  UniquePtr<SerializedStackHolder> mOriginStack;

  nsresult mErrorCode;
};

}  // namespace

// Check a protocol entry contains only valid characters
bool WebSocket::IsValidProtocolString(const nsString& aValue) {
  // RFC 6455 (4.1): "not including separator characters as defined in RFC 2616"
  const char16_t illegalCharacters[] = {0x28, 0x29, 0x3C, 0x3E, 0x40, 0x2C,
                                        0x3B, 0x3A, 0x5C, 0x22, 0x2F, 0x5B,
                                        0x5D, 0x3F, 0x3D, 0x7B, 0x7D};

  // Cannot be empty string
  if (aValue.IsEmpty()) {
    return false;
  }

  const auto* start = aValue.BeginReading();
  const auto* end = aValue.EndReading();

  auto charFilter = [&](char16_t c) {
    // RFC 6455 (4.1 P18): "in the range U+0021 to U+007E"
    if (c < 0x21 || c > 0x7E) {
      return true;
    }

    return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
                     c) != std::end(illegalCharacters);
  };

  return std::find_if(start, end, charFilter) == end;
}

already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
    const GlobalObject& aGlobal, const nsAString& aUrl,
    const Sequence<nsString>& aProtocols,
    nsITransportProvider* aTransportProvider,
    const nsACString& aNegotiatedExtensions, ErrorResult& aRv) {
  MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
  nsCOMPtr<nsIPrincipal> principal;
  nsCOMPtr<nsIPrincipal> partitionedPrincipal;

  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
  if (NS_WARN_IF(!global)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  if (NS_IsMainThread()) {
    nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
        do_QueryInterface(aGlobal.GetAsSupports());
    if (!scriptPrincipal) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    principal = scriptPrincipal->GetPrincipal();
    partitionedPrincipal = scriptPrincipal->PartitionedPrincipal();
    if (!principal || !partitionedPrincipal) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
  }

  nsTArray<nsString> protocolArray;

  for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
    const nsString& protocolElement = aProtocols[index];

    // Repeated protocols are not allowed
    if (protocolArray.Contains(protocolElement)) {
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return nullptr;
    }

    // Protocol string value must match constraints
    if (!IsValidProtocolString(protocolElement)) {
      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
      return nullptr;
    }

    protocolArray.AppendElement(protocolElement);
  }

  RefPtr<WebSocket> webSocket = new WebSocket(global);
  RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;

  bool connectionFailed = true;

  if (NS_IsMainThread()) {
    // We're keeping track of all main thread web sockets to be able to
    // avoid throttling timeouts when we have active web sockets.
    nsCOMPtr<nsIGlobalObject> global;
    if (nsGlobalWindowInner* win = webSocket->GetOwnerWindow()) {
      win->UpdateWebSocketCount(1);
      global = win->AsGlobal();
    }

    bool isSecure = principal->SchemeIs("https");
    aRv = webSocketImpl->IsSecure(&isSecure);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    aRv = webSocketImpl->Init(global, aGlobal.Context(), isSecure, principal,
                              Nothing(), nullptr, !!aTransportProvider, aUrl,
                              protocolArray, ""_ns, 0, 0);

    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    nsCOMPtr<Document> doc = webSocket->GetDocumentIfCurrent();

    // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
    // url parameter, so don't throw if InitializeConnection fails, and call
    // onerror/onclose asynchronously
    connectionFailed = NS_FAILED(webSocketImpl->InitializeConnection(
        principal, doc ? doc->CookieJarSettings() : nullptr));
  } else {
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);

    uint32_t lineno;
    JS::ColumnNumberOneOrigin column;
    JS::AutoFilename file;
    if (!JS::DescribeScriptedCaller(&file, aGlobal.Context(), &lineno,
                                    &column)) {
      NS_WARNING("Failed to get line number and filename in workers.");
    }

    RefPtr<InitRunnable> runnable = new InitRunnable(
        workerPrivate, webSocketImpl,
        workerPrivate->GlobalScope()->GetClientInfo(), !!aTransportProvider,
        aUrl, protocolArray, nsDependentCString(file.get()), lineno,
        column.oneOriginValue());
    runnable->Dispatch(workerPrivate, Canceling, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    aRv = runnable->ErrorCode();
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    if (NS_WARN_IF(!webSocketImpl->RegisterWorkerRef(workerPrivate))) {
      // The worker is shutting down.
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    RefPtr<ConnectRunnable> connectRunnable =
        new ConnectRunnable(workerPrivate, webSocketImpl);
    connectRunnable->Dispatch(workerPrivate, Canceling, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    connectionFailed = connectRunnable->ConnectionFailed();
  }

  // It can be that we have been already disconnected because the WebSocket is
  // gone away while we where initializing the webSocket.
  if (!webSocket->mImpl) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // We don't return an error if the connection just failed. Instead we dispatch
  // an event.
  if (connectionFailed) {
    webSocketImpl->FailConnection(webSocketImpl,
                                  nsIWebSocketChannel::CLOSE_ABNORMAL);
  }

  // If we don't have a channel, the connection is failed and onerror() will be
  // called asynchrounsly.
  if (!webSocket->mImpl->mChannel) {
    return webSocket.forget();
  }

  class MOZ_STACK_CLASS ClearWebSocket {
   public:
    explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
        : mWebSocketImpl(aWebSocketImpl), mDone(false) {}

    void Done() { mDone = true; }

    ~ClearWebSocket() {
      if (!mDone) {
        mWebSocketImpl->mChannel = nullptr;
        mWebSocketImpl->FailConnection(mWebSocketImpl,
                                       nsIWebSocketChannel::CLOSE_ABNORMAL);
      }
    }

    RefPtr<WebSocketImpl> mWebSocketImpl;
    bool mDone;
  };

  ClearWebSocket cws(webSocket->mImpl);

  // This operation must be done on the correct thread. The rest must run on the
  // main-thread.
  aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  if (NS_IsMainThread()) {
    MOZ_ASSERT(principal);
    MOZ_ASSERT(partitionedPrincipal);

    nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);

    UniquePtr<SerializedStackHolder> stack;
    uint64_t windowID = 0;

    if (ownerWindow) {
      BrowsingContext* browsingContext = ownerWindow->GetBrowsingContext();
      if (browsingContext && browsingContext->WatchedByDevTools()) {
        stack = GetCurrentStackForNetMonitor(aGlobal.Context());
      }

      if (WindowContext* wc = ownerWindow->GetWindowContext()) {
        windowID = wc->InnerWindowId();
      }
    }

    aRv = webSocket->mImpl->AsyncOpen(partitionedPrincipal, windowID,
                                      aTransportProvider, aNegotiatedExtensions,
                                      std::move(stack));
  } else {
    MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
               "not yet implemented");

    UniquePtr<SerializedStackHolder> stack;
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    if (workerPrivate->IsWatchedByDevTools()) {
      stack = GetCurrentStackForNetMonitor(aGlobal.Context());
    }

    RefPtr<AsyncOpenRunnable> runnable =
        new AsyncOpenRunnable(webSocket->mImpl, std::move(stack));
    runnable->Dispatch(webSocket->mImpl->mWorkerRef->Private(), Canceling, aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }

    aRv = runnable->ErrorCode();
  }

  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  // It can be that we have been already disconnected because the WebSocket is
  // gone away while we where initializing the webSocket.
  if (!webSocket->mImpl) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }

  // Let's inform devtools about this new active WebSocket.
  webSocket->mImpl->mService->WebSocketCreated(
      webSocket->mImpl->mChannel->Serial(), webSocket->mImpl->mInnerWindowID,
      webSocket->mURI, webSocket->mImpl->mRequestedProtocolList);
  cws.Done();

  return webSocket.forget();
}

NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
                                                  DOMEventTargetHelper)
  if (tmp->mImpl) {
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, DOMEventTargetHelper)
  if (tmp->mImpl) {
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
    RefPtr<WebSocketImpl> pin(tmp->mImpl);
    pin->Disconnect(pin);
    MOZ_ASSERT(!tmp->mImpl);
  }
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

bool WebSocket::IsCertainlyAliveForCC() const { return mKeepingAlive; }

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebSocket)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)

NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)

void WebSocket::DisconnectFromOwner() {
  // If we haven't called WebSocketImpl::Disconnect yet, update web
  // socket count here.
  if (NS_IsMainThread() && mImpl && !mImpl->mDisconnectingOrDisconnected &&
      GetOwnerWindow()) {
    GetOwnerWindow()->UpdateWebSocketCount(-1);
  }

  DOMEventTargetHelper::DisconnectFromOwner();

  if (mImpl) {
    RefPtr<WebSocketImpl> pin(mImpl);
    pin->CloseConnection(pin, nsIWebSocketChannel::CLOSE_GOING_AWAY);
  }

  DontKeepAliveAnyMore();
}

//-----------------------------------------------------------------------------
// WebSocketImpl:: initialization
//-----------------------------------------------------------------------------

nsresult WebSocketImpl::Init(nsIGlobalObject* aWindowGlobal, JSContext* aCx,
                             bool aIsSecure, nsIPrincipal* aPrincipal,
                             const Maybe<ClientInfo>& aClientInfo,
                             nsICSPEventListener* aCSPEventListener,
                             bool aIsServerSide, const nsAString& aURL,
                             nsTArray<nsString>& aProtocolArray,
                             const nsACString& aScriptFile,
                             uint32_t aScriptLine, uint32_t aScriptColumn) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aPrincipal);

  mService = WebSocketEventService::GetOrCreate();

  // We need to keep the implementation alive in case the init disconnects it
  // because of some error.
  RefPtr<WebSocketImpl> kungfuDeathGrip = this;

  // Attempt to kill "ghost" websocket: but usually too early for check to fail
  nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
  NS_ENSURE_SUCCESS(rv, rv);

  // Assign the sub protocol list and scan it for illegal values
  for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
    if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) {
      return NS_ERROR_DOM_SYNTAX_ERR;
    }

    if (!mRequestedProtocolList.IsEmpty()) {
      mRequestedProtocolList.AppendLiteral(", ");
    }

    AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
  }

  // Shut down websocket if window is frozen or destroyed (only needed for
  // "ghost" websockets--see bug 696085)
  RefPtr<WebSocketImplProxy> proxy;
  if (mIsMainThread) {
    proxy = new WebSocketImplProxy(this);
    proxy->BindToOwner(aWindowGlobal);
  }

  if (!mIsMainThread) {
    mScriptFile = aScriptFile;
    mScriptLine = aScriptLine;
    mScriptColumn = aScriptColumn;
  } else {
    MOZ_ASSERT(aCx);

    uint32_t lineno;
    JS::ColumnNumberOneOrigin column;
    JS::AutoFilename file;
    if (JS::DescribeScriptedCaller(&file, aCx, &lineno, &column)) {
      mScriptFile = file.get();
      mScriptLine = lineno;
      mScriptColumn = column.oneOriginValue();
    }
  }

  mIsServerSide = aIsServerSide;

  // If we don't have aCx, we are window-less, so we don't have a
  // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
  // DedicateWorkers created by JSM.
  if (aCx) {
    if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwnerWindow()) {
      mInnerWindowID = ownerWindow->WindowID();
    }
  }

  mPrivateBrowsing = aPrincipal->OriginAttributesRef().IsPrivateBrowsing();
  mIsChromeContext = aPrincipal->IsSystemPrincipal();

  // parses the url
  nsCOMPtr<nsIURI> baseURI = aPrincipal->GetURI();
  rv = ParseURL(aURL, baseURI);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent();
  if (!originDoc) {
    rv = mWebSocket->CheckCurrentGlobalCorrectness();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  mOriginDocument = originDoc;

  if (!mIsServerSide) {
    nsCOMPtr<nsIURI> uri;
    {
      nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);

      // We crash here because we are sure that mURI is a valid URI, so either
      // we are OOM'ing or something else bad is happening.
      if (NS_WARN_IF(NS_FAILED(rv))) {
        MOZ_CRASH();
      }
    }

    // The 'real' nsHttpChannel of the websocket gets opened in the parent.
    // Since we don't serialize the CSP within child and parent and also not
    // the context, we have to perform content policy checks here instead of
    // AsyncOpen().
    // Please note that websockets can't follow redirects, hence there is no
    // need to perform a CSP check after redirects.
    nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
        aPrincipal,  // loading principal
        aPrincipal,  // triggering principal
        originDoc, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
        nsIContentPolicy::TYPE_WEBSOCKET, aClientInfo);

    if (aCSPEventListener) {
      secCheckLoadInfo->SetCspEventListener(aCSPEventListener);
    }

    int16_t shouldLoad = nsIContentPolicy::ACCEPT;
    rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, &shouldLoad,
                                   nsContentUtils::GetContentPolicy());
    NS_ENSURE_SUCCESS(rv, rv);

    if (NS_CP_REJECTED(shouldLoad)) {
      // Disallowed by content policy
      return NS_ERROR_CONTENT_BLOCKED;
    }

    // If the HTTPS-Only mode is enabled, we need to upgrade the websocket
    // connection from ws:// to wss:// and mark it as secure.
    if (!mSecure && originDoc &&
        !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
            originDoc->GetDocumentURI())) {
      nsCOMPtr<nsIURI> uri;
      nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
      NS_ENSURE_SUCCESS(rv, rv);

      // secCheckLoadInfo is only used for the triggering principal, so this
      // is okay.
      if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) {
        mURI.ReplaceSubstring("ws://", "wss://");
        if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
          return NS_OK;
        }
        mSecure = true;
      }
    }
  }

  // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
  // In such a case we have to upgrade ws: to wss: and also update mSecure
  // to reflect that upgrade. Please note that we can not upgrade from ws:
  // to wss: before performing content policy checks because CSP needs to
  // send reports in case the scheme is about to be upgraded.
  if (!mIsServerSide && !mSecure && originDoc &&
      originDoc->GetUpgradeInsecureRequests(false) &&
      !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
          originDoc->GetDocumentURI())) {
    // let's use the old specification before the upgrade for logging
    AutoTArray<nsString, 2> params;
    CopyUTF8toUTF16(mURI, *params.AppendElement());

    // upgrade the request from ws:// to wss:// and mark as secure
    mURI.ReplaceSubstring("ws://", "wss://");
    if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
      return NS_OK;
    }
    mSecure = true;

    params.AppendElement(u"wss"_ns);
    CSP_LogLocalizedStr("upgradeInsecureRequest", params,
                        ""_ns,   // aSourceFile
                        u""_ns,  // aScriptSample
                        0,       // aLineNumber
                        1,       // aColumnNumber
                        nsIScriptError::warningFlag,
                        "upgradeInsecureRequest"_ns, mInnerWindowID,
                        mPrivateBrowsing);
  }

  // Don't allow https:// to open ws://
  // Check that we aren't a server side websocket or set to be upgraded to wss
  // or allowing ws from https or a local websocket
  if (!mIsServerSide && !mSecure &&
      !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
                            false) &&
      !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
          mAsciiHost)) {
    // If aIsSecure is true then disallow loading ws
    if (aIsSecure) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }

    // Obtain the precursor's URI for the loading principal if it exists
    // otherwise use the loading principal's URI
    nsCOMPtr<nsIPrincipal> precursorPrincipal =
        aPrincipal->GetPrecursorPrincipal();
    nsCOMPtr<nsIURI> precursorOrLoadingURI = precursorPrincipal
                                                 ? precursorPrincipal->GetURI()
                                                 : aPrincipal->GetURI();

    // Check if the parent was loaded securely if we have one
    if (precursorOrLoadingURI) {
      nsCOMPtr<nsIURI> precursorOrLoadingInnermostURI =
          NS_GetInnermostURI(precursorOrLoadingURI);
      // If the parent was loaded securely then disallow loading ws
      if (precursorOrLoadingInnermostURI &&
          precursorOrLoadingInnermostURI->SchemeIs("https")) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }
    }
  }

  if (mIsMainThread) {
    mImplProxy = std::move(proxy);
  }
  return NS_OK;
}

nsresult WebSocketImpl::AsyncOpen(
    nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
    nsITransportProvider* aTransportProvider,
    const nsACString& aNegotiatedExtensions,
    UniquePtr<SerializedStackHolder> aOriginStack) {
  MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
  MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());

  nsCString webExposedOriginSerialization;
  nsresult rv = aPrincipal->GetWebExposedOriginSerialization(
      webExposedOriginSerialization);
  if (NS_FAILED(rv)) {
    webExposedOriginSerialization.AssignLiteral("null");
  }

  if (aTransportProvider) {
    rv = mChannel->SetServerParameters(aTransportProvider,
                                       aNegotiatedExtensions);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  ToLowerCase(webExposedOriginSerialization);

  nsCOMPtr<nsIURI> uri;
  if (!aTransportProvider) {
    rv = NS_NewURI(getter_AddRefs(uri), mURI);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }

  rv = mChannel->AsyncOpenNative(uri, webExposedOriginSerialization,
                                 aPrincipal->OriginAttributesRef(),
                                 aInnerWindowID, this, nullptr);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return NS_ERROR_CONTENT_BLOCKED;
  }

  NotifyNetworkMonitorAlternateStack(mChannel, std::move(aOriginStack));

  mInnerWindowID = aInnerWindowID;

  return NS_OK;
}

//-----------------------------------------------------------------------------
// WebSocketImpl methods:
//-----------------------------------------------------------------------------

class nsAutoCloseWS final {
 public:
  explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
      : mWebSocketImpl(aWebSocketImpl) {}

  ~nsAutoCloseWS() {
    if (!mWebSocketImpl->mChannel) {
      mWebSocketImpl->CloseConnection(
          mWebSocketImpl, nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
    }
  }

 private:
  RefPtr<WebSocketImpl> mWebSocketImpl;
};

nsresult WebSocketImpl::InitializeConnection(
    nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings) {
  AssertIsOnMainThread();
  MOZ_ASSERT(!mChannel, "mChannel should be null");

  nsCOMPtr<nsIWebSocketChannel> wsChannel;
  nsAutoCloseWS autoClose(this);
  nsresult rv;

  if (mSecure) {
    wsChannel =
        do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
  } else {
    wsChannel =
        do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
  }
  NS_ENSURE_SUCCESS(rv, rv);

  // add ourselves to the document's load group and
  // provide the http stack the loadgroup info too
  nsCOMPtr<nsILoadGroup> loadGroup;
  rv = GetLoadGroup(getter_AddRefs(loadGroup));
  if (loadGroup) {
    rv = wsChannel->SetLoadGroup(loadGroup);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = loadGroup->AddRequest(this, nullptr);
    NS_ENSURE_SUCCESS(rv, rv);

    mWeakLoadGroup = do_GetWeakReference(loadGroup);
  }

  // manually adding loadinfo to the channel since it
  // was not set during channel creation.
  nsCOMPtr<Document> doc(mOriginDocument);

  // mOriginDocument has to be release on main-thread because WeakReferences
  // are not thread-safe.
  mOriginDocument = nullptr;

  // The TriggeringPrincipal for websockets must always be a script.
  // Let's make sure that the doc's principal (if a doc exists)
  // and aPrincipal are same origin.
  MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));

  rv = wsChannel->InitLoadInfoNative(
      doc, doc ? doc->NodePrincipal() : aPrincipal, aPrincipal,
      aCookieJarSettings,
      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
      nsIContentPolicy::TYPE_WEBSOCKET, 0);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  if (!mRequestedProtocolList.IsEmpty()) {
    rv = wsChannel->SetProtocol(mRequestedProtocolList);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
  NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);

  rv = rr->RetargetDeliveryTo(this);
  NS_ENSURE_SUCCESS(rv, rv);

  mChannel = wsChannel;

  if (mIsMainThread) {
    MOZ_ASSERT(mImplProxy);
    mService->AssociateWebSocketImplWithSerialID(mImplProxy,
                                                 mChannel->Serial());
  }

  return NS_OK;
}

void WebSocketImpl::DispatchConnectionCloseEvents(
    const RefPtr<WebSocketImpl>& aProofOfRef) {
  AssertIsOnTargetThread();

  if (mDisconnectingOrDisconnected) {
    return;
  }

  mWebSocket->SetReadyState(WebSocket::CLOSED);

  // Let's keep the object alive because the webSocket can be CCed in the
  // onerror or in the onclose callback
  RefPtr<WebSocket> webSocket = mWebSocket;

  // Call 'onerror' if needed
  if (mFailed) {
    nsresult rv = webSocket->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING);
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to dispatch the error event");
    }
  }

  nsresult rv = webSocket->CreateAndDispatchCloseEvent(
      mCloseEventWasClean, mCloseEventCode, mCloseEventReason);
  if (NS_FAILED(rv)) {
    NS_WARNING("Failed to dispatch the close event");
  }

  webSocket->UpdateMustKeepAlive();
  Disconnect(aProofOfRef);
}

nsresult WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName) {
  MOZ_ASSERT(mImpl);
  AssertIsOnTargetThread();

  nsresult rv = CheckCurrentGlobalCorrectness();
  if (NS_FAILED(rv)) {
    return NS_OK;
  }

  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);

  // it doesn't bubble, and it isn't cancelable
  event->InitEvent(aName, falsefalse);
  event->SetTrusted(true);

  ErrorResult err;
  DispatchEvent(*event, err);
  return err.StealNSResult();
}

nsresult WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
                                                  bool aIsBinary) {
  MOZ_ASSERT(mImpl);
  AssertIsOnTargetThread();

  AutoJSAPI jsapi;
  if (NS_WARN_IF(!jsapi.Init(GetOwnerGlobal()))) {
    return NS_ERROR_FAILURE;
  }

  JSContext* cx = jsapi.cx();

  nsresult rv = CheckCurrentGlobalCorrectness();
  if (NS_FAILED(rv)) {
    return NS_OK;
  }

  uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;

  // Create appropriate JS object for message
  JS::Rooted<JS::Value> jsData(cx);
  if (aIsBinary) {
    if (mBinaryType == dom::BinaryType::Blob) {
      messageType = nsIWebSocketEventListener::TYPE_BLOB;

      RefPtr<Blob> blob =
          Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns);
      if (NS_WARN_IF(!blob)) {
        return NS_ERROR_FAILURE;
      }

      if (!ToJSValue(cx, blob, &jsData)) {
        return NS_ERROR_FAILURE;
      }

    } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
      messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;

      ErrorResult rv;
      JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aData, rv));
      RETURN_NSRESULT_ON_FAILURE(rv);
      jsData.setObject(*arrayBuf);
    } else {
      MOZ_CRASH("Unknown binary type!");
      return NS_ERROR_UNEXPECTED;
    }
  } else {
    // JS string
    nsAutoString utf16Data;
    if (!AppendUTF8toUTF16(aData, utf16Data, mozilla::fallible)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }
--> --------------------

--> maximum size reached

--> --------------------

100%


¤ Dauer der Verarbeitung: 0.56 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.