content/base/src/WebSocket.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/base/src/WebSocket.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1447 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set sw=2 ts=8 et tw=80 : */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "WebSocket.h"
    1.11 +#include "mozilla/dom/WebSocketBinding.h"
    1.12 +
    1.13 +#include "jsapi.h"
    1.14 +#include "jsfriendapi.h"
    1.15 +#include "js/OldDebugAPI.h"
    1.16 +#include "mozilla/DOMEventTargetHelper.h"
    1.17 +#include "nsIScriptGlobalObject.h"
    1.18 +#include "nsIDOMWindow.h"
    1.19 +#include "nsIDocument.h"
    1.20 +#include "nsXPCOM.h"
    1.21 +#include "nsIXPConnect.h"
    1.22 +#include "nsContentUtils.h"
    1.23 +#include "nsCxPusher.h"
    1.24 +#include "nsError.h"
    1.25 +#include "nsIScriptObjectPrincipal.h"
    1.26 +#include "nsIURL.h"
    1.27 +#include "nsIUnicodeEncoder.h"
    1.28 +#include "nsThreadUtils.h"
    1.29 +#include "nsIDOMMessageEvent.h"
    1.30 +#include "nsIPromptFactory.h"
    1.31 +#include "nsIWindowWatcher.h"
    1.32 +#include "nsIPrompt.h"
    1.33 +#include "nsIStringBundle.h"
    1.34 +#include "nsIConsoleService.h"
    1.35 +#include "nsIDOMCloseEvent.h"
    1.36 +#include "nsICryptoHash.h"
    1.37 +#include "nsJSUtils.h"
    1.38 +#include "nsIScriptError.h"
    1.39 +#include "nsNetUtil.h"
    1.40 +#include "nsILoadGroup.h"
    1.41 +#include "mozilla/Preferences.h"
    1.42 +#include "xpcpublic.h"
    1.43 +#include "nsContentPolicyUtils.h"
    1.44 +#include "nsDOMFile.h"
    1.45 +#include "nsWrapperCacheInlines.h"
    1.46 +#include "nsIObserverService.h"
    1.47 +#include "nsIWebSocketChannel.h"
    1.48 +#include "GeneratedEvents.h"
    1.49 +
    1.50 +namespace mozilla {
    1.51 +namespace dom {
    1.52 +
    1.53 +#define UTF_8_REPLACEMENT_CHAR    static_cast<char16_t>(0xFFFD)
    1.54 +
    1.55 +class CallDispatchConnectionCloseEvents: public nsRunnable
    1.56 +{
    1.57 +public:
    1.58 +CallDispatchConnectionCloseEvents(WebSocket* aWebSocket)
    1.59 +  : mWebSocket(aWebSocket)
    1.60 +  {}
    1.61 +
    1.62 +  NS_IMETHOD Run()
    1.63 +  {
    1.64 +    mWebSocket->DispatchConnectionCloseEvents();
    1.65 +    return NS_OK;
    1.66 +  }
    1.67 +
    1.68 +private:
    1.69 +  nsRefPtr<WebSocket> mWebSocket;
    1.70 +};
    1.71 +
    1.72 +//-----------------------------------------------------------------------------
    1.73 +// WebSocket
    1.74 +//-----------------------------------------------------------------------------
    1.75 +
    1.76 +nsresult
    1.77 +WebSocket::PrintErrorOnConsole(const char *aBundleURI,
    1.78 +                               const char16_t *aError,
    1.79 +                               const char16_t **aFormatStrings,
    1.80 +                               uint32_t aFormatStringsLen)
    1.81 +{
    1.82 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
    1.83 +
    1.84 +  nsresult rv;
    1.85 +  nsCOMPtr<nsIStringBundleService> bundleService =
    1.86 +    do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
    1.87 +  NS_ENSURE_SUCCESS(rv, rv);
    1.88 +
    1.89 +  nsCOMPtr<nsIStringBundle> strBundle;
    1.90 +  rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
    1.91 +  NS_ENSURE_SUCCESS(rv, rv);
    1.92 +
    1.93 +  nsCOMPtr<nsIConsoleService> console(
    1.94 +    do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
    1.95 +  NS_ENSURE_SUCCESS(rv, rv);
    1.96 +
    1.97 +  nsCOMPtr<nsIScriptError> errorObject(
    1.98 +    do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
    1.99 +  NS_ENSURE_SUCCESS(rv, rv);
   1.100 +
   1.101 +  // Localize the error message
   1.102 +  nsXPIDLString message;
   1.103 +  if (aFormatStrings) {
   1.104 +    rv = strBundle->FormatStringFromName(aError, aFormatStrings,
   1.105 +                                         aFormatStringsLen,
   1.106 +                                         getter_Copies(message));
   1.107 +  } else {
   1.108 +    rv = strBundle->GetStringFromName(aError, getter_Copies(message));
   1.109 +  }
   1.110 +  NS_ENSURE_SUCCESS(rv, rv);
   1.111 +
   1.112 +  rv = errorObject->InitWithWindowID(message,
   1.113 +                                     NS_ConvertUTF8toUTF16(mScriptFile),
   1.114 +                                     EmptyString(), mScriptLine, 0,
   1.115 +                                     nsIScriptError::errorFlag, "Web Socket",
   1.116 +                                     mInnerWindowID);
   1.117 +  NS_ENSURE_SUCCESS(rv, rv);
   1.118 +
   1.119 +  // print the error message directly to the JS console
   1.120 +  rv = console->LogMessage(errorObject);
   1.121 +  NS_ENSURE_SUCCESS(rv, rv);
   1.122 +
   1.123 +  return NS_OK;
   1.124 +}
   1.125 +
   1.126 +nsresult
   1.127 +WebSocket::CloseConnection(uint16_t aReasonCode,
   1.128 +                             const nsACString& aReasonString)
   1.129 +{
   1.130 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.131 +  if (mReadyState == WebSocket::CLOSING ||
   1.132 +      mReadyState == WebSocket::CLOSED) {
   1.133 +    return NS_OK;
   1.134 +  }
   1.135 +
   1.136 +  // The common case...
   1.137 +  if (mChannel) {
   1.138 +    mReadyState = WebSocket::CLOSING;
   1.139 +    return mChannel->Close(aReasonCode, aReasonString);
   1.140 +  }
   1.141 +
   1.142 +  // No channel, but not disconnected: canceled or failed early
   1.143 +  //
   1.144 +  MOZ_ASSERT(mReadyState == WebSocket::CONNECTING,
   1.145 +             "Should only get here for early websocket cancel/error");
   1.146 +
   1.147 +  // Server won't be sending us a close code, so use what's passed in here.
   1.148 +  mCloseEventCode = aReasonCode;
   1.149 +  CopyUTF8toUTF16(aReasonString, mCloseEventReason);
   1.150 +
   1.151 +  mReadyState = WebSocket::CLOSING;
   1.152 +
   1.153 +  // Can be called from Cancel() or Init() codepaths, so need to dispatch
   1.154 +  // onerror/onclose asynchronously
   1.155 +  ScheduleConnectionCloseEvents(
   1.156 +                    nullptr,
   1.157 +                    (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
   1.158 +                     aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
   1.159 +                     NS_OK : NS_ERROR_FAILURE,
   1.160 +                    false);
   1.161 +
   1.162 +  return NS_OK;
   1.163 +}
   1.164 +
   1.165 +nsresult
   1.166 +WebSocket::ConsoleError()
   1.167 +{
   1.168 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.169 +
   1.170 +  nsAutoCString targetSpec;
   1.171 +  nsresult rv = mURI->GetSpec(targetSpec);
   1.172 +  if (NS_FAILED(rv)) {
   1.173 +    NS_WARNING("Failed to get targetSpec");
   1.174 +  } else {
   1.175 +    NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
   1.176 +    const char16_t* formatStrings[] = { specUTF16.get() };
   1.177 +
   1.178 +    if (mReadyState < WebSocket::OPEN) {
   1.179 +      PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
   1.180 +                          MOZ_UTF16("connectionFailure"),
   1.181 +                          formatStrings, ArrayLength(formatStrings));
   1.182 +    } else {
   1.183 +      PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
   1.184 +                          MOZ_UTF16("netInterrupt"),
   1.185 +                          formatStrings, ArrayLength(formatStrings));
   1.186 +    }
   1.187 +  }
   1.188 +  /// todo some specific errors - like for message too large
   1.189 +  return rv;
   1.190 +}
   1.191 +
   1.192 +
   1.193 +void
   1.194 +WebSocket::FailConnection(uint16_t aReasonCode,
   1.195 +                          const nsACString& aReasonString)
   1.196 +{
   1.197 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.198 +
   1.199 +  ConsoleError();
   1.200 +  mFailed = true;
   1.201 +  CloseConnection(aReasonCode, aReasonString);
   1.202 +}
   1.203 +
   1.204 +nsresult
   1.205 +WebSocket::Disconnect()
   1.206 +{
   1.207 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.208 +
   1.209 +  if (mDisconnected)
   1.210 +    return NS_OK;
   1.211 +
   1.212 +  nsCOMPtr<nsILoadGroup> loadGroup;
   1.213 +  GetLoadGroup(getter_AddRefs(loadGroup));
   1.214 +  if (loadGroup)
   1.215 +    loadGroup->RemoveRequest(this, nullptr, NS_OK);
   1.216 +
   1.217 +  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   1.218 +  if (os) {
   1.219 +    os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
   1.220 +    os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
   1.221 +  }
   1.222 +
   1.223 +  // DontKeepAliveAnyMore() can release the object. So hold a reference to this
   1.224 +  // until the end of the method.
   1.225 +  nsRefPtr<WebSocket> kungfuDeathGrip = this;
   1.226 +
   1.227 +  DontKeepAliveAnyMore();
   1.228 +  mChannel = nullptr;
   1.229 +  mDisconnected = true;
   1.230 +
   1.231 +  return NS_OK;
   1.232 +}
   1.233 +
   1.234 +//-----------------------------------------------------------------------------
   1.235 +// WebSocket::nsIWebSocketListener methods:
   1.236 +//-----------------------------------------------------------------------------
   1.237 +
   1.238 +nsresult
   1.239 +WebSocket::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
   1.240 +{
   1.241 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.242 +
   1.243 +  if (mReadyState == WebSocket::CLOSED) {
   1.244 +    NS_ERROR("Received message after CLOSED");
   1.245 +    return NS_ERROR_UNEXPECTED;
   1.246 +  }
   1.247 +
   1.248 +  if (mReadyState == WebSocket::OPEN) {
   1.249 +    // Dispatch New Message
   1.250 +    nsresult rv = CreateAndDispatchMessageEvent(aMsg, isBinary);
   1.251 +    if (NS_FAILED(rv)) {
   1.252 +      NS_WARNING("Failed to dispatch the message event");
   1.253 +    }
   1.254 +  } else {
   1.255 +    // CLOSING should be the only other state where it's possible to get msgs
   1.256 +    // from channel: Spec says to drop them.
   1.257 +    MOZ_ASSERT(mReadyState == WebSocket::CLOSING,
   1.258 +               "Received message while CONNECTING or CLOSED");
   1.259 +  }
   1.260 +
   1.261 +  return NS_OK;
   1.262 +}
   1.263 +
   1.264 +NS_IMETHODIMP
   1.265 +WebSocket::OnMessageAvailable(nsISupports* aContext, const nsACString& aMsg)
   1.266 +{
   1.267 +  return DoOnMessageAvailable(aMsg, false);
   1.268 +}
   1.269 +
   1.270 +NS_IMETHODIMP
   1.271 +WebSocket::OnBinaryMessageAvailable(nsISupports* aContext,
   1.272 +                                    const nsACString& aMsg)
   1.273 +{
   1.274 +  return DoOnMessageAvailable(aMsg, true);
   1.275 +}
   1.276 +
   1.277 +NS_IMETHODIMP
   1.278 +WebSocket::OnStart(nsISupports* aContext)
   1.279 +{
   1.280 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.281 +
   1.282 +  // This is the only function that sets OPEN, and should be called only once
   1.283 +  MOZ_ASSERT(mReadyState != WebSocket::OPEN,
   1.284 +             "readyState already OPEN! OnStart called twice?");
   1.285 +
   1.286 +  // Nothing to do if we've already closed/closing
   1.287 +  if (mReadyState != WebSocket::CONNECTING) {
   1.288 +    return NS_OK;
   1.289 +  }
   1.290 +
   1.291 +  // Attempt to kill "ghost" websocket: but usually too early for check to fail
   1.292 +  nsresult rv = CheckInnerWindowCorrectness();
   1.293 +  if (NS_FAILED(rv)) {
   1.294 +    CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
   1.295 +    return rv;
   1.296 +  }
   1.297 +
   1.298 +  if (!mRequestedProtocolList.IsEmpty()) {
   1.299 +    mChannel->GetProtocol(mEstablishedProtocol);
   1.300 +  }
   1.301 +
   1.302 +  mChannel->GetExtensions(mEstablishedExtensions);
   1.303 +  UpdateURI();
   1.304 +
   1.305 +  mReadyState = WebSocket::OPEN;
   1.306 +
   1.307 +  // Call 'onopen'
   1.308 +  rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
   1.309 +  if (NS_FAILED(rv)) {
   1.310 +    NS_WARNING("Failed to dispatch the open event");
   1.311 +  }
   1.312 +
   1.313 +  UpdateMustKeepAlive();
   1.314 +
   1.315 +  return NS_OK;
   1.316 +}
   1.317 +
   1.318 +NS_IMETHODIMP
   1.319 +WebSocket::OnStop(nsISupports* aContext, nsresult aStatusCode)
   1.320 +{
   1.321 +  // We can be CONNECTING here if connection failed.
   1.322 +  // We can be OPEN if we have encountered a fatal protocol error
   1.323 +  // We can be CLOSING if close() was called and/or server initiated close.
   1.324 +  MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
   1.325 +             "Shouldn't already be CLOSED when OnStop called");
   1.326 +
   1.327 +  // called by network stack, not JS, so can dispatch JS events synchronously
   1.328 +  return ScheduleConnectionCloseEvents(aContext, aStatusCode, true);
   1.329 +}
   1.330 +
   1.331 +nsresult
   1.332 +WebSocket::ScheduleConnectionCloseEvents(nsISupports* aContext,
   1.333 +                                         nsresult aStatusCode,
   1.334 +                                         bool sync)
   1.335 +{
   1.336 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.337 +
   1.338 +  // no-op if some other code has already initiated close event
   1.339 +  if (!mOnCloseScheduled) {
   1.340 +    mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
   1.341 +
   1.342 +    if (aStatusCode == NS_BASE_STREAM_CLOSED) {
   1.343 +      // don't generate an error event just because of an unclean close
   1.344 +      aStatusCode = NS_OK;
   1.345 +    }
   1.346 +
   1.347 +    if (NS_FAILED(aStatusCode)) {
   1.348 +      ConsoleError();
   1.349 +      mFailed = true;
   1.350 +    }
   1.351 +
   1.352 +    mOnCloseScheduled = true;
   1.353 +
   1.354 +    if (sync) {
   1.355 +      DispatchConnectionCloseEvents();
   1.356 +    } else {
   1.357 +      NS_DispatchToMainThread(new CallDispatchConnectionCloseEvents(this),
   1.358 +                              NS_DISPATCH_NORMAL);
   1.359 +    }
   1.360 +  }
   1.361 +
   1.362 +  return NS_OK;
   1.363 +}
   1.364 +
   1.365 +NS_IMETHODIMP
   1.366 +WebSocket::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
   1.367 +{
   1.368 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.369 +
   1.370 +  if (aSize > mOutgoingBufferedAmount)
   1.371 +    return NS_ERROR_UNEXPECTED;
   1.372 +
   1.373 +  mOutgoingBufferedAmount -= aSize;
   1.374 +  return NS_OK;
   1.375 +}
   1.376 +
   1.377 +NS_IMETHODIMP
   1.378 +WebSocket::OnServerClose(nsISupports *aContext, uint16_t aCode,
   1.379 +                           const nsACString &aReason)
   1.380 +{
   1.381 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.382 +
   1.383 +  MOZ_ASSERT(mReadyState != WebSocket::CONNECTING,
   1.384 +             "Received server close before connected?");
   1.385 +  MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
   1.386 +             "Received server close after already closed!");
   1.387 +
   1.388 +  // store code/string for onclose DOM event
   1.389 +  mCloseEventCode = aCode;
   1.390 +  CopyUTF8toUTF16(aReason, mCloseEventReason);
   1.391 +
   1.392 +  if (mReadyState == WebSocket::OPEN) {
   1.393 +    // Server initiating close.
   1.394 +    // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
   1.395 +    // typically echos the status code it received".
   1.396 +    // But never send certain codes, per section 7.4.1
   1.397 +    if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
   1.398 +      CloseConnection(0, EmptyCString());
   1.399 +    } else {
   1.400 +      CloseConnection(aCode, aReason);
   1.401 +    }
   1.402 +  } else {
   1.403 +    // We initiated close, and server has replied: OnStop does rest of the work.
   1.404 +    MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state");
   1.405 +  }
   1.406 +
   1.407 +  return NS_OK;
   1.408 +}
   1.409 +
   1.410 +//-----------------------------------------------------------------------------
   1.411 +// WebSocket::nsIInterfaceRequestor
   1.412 +//-----------------------------------------------------------------------------
   1.413 +
   1.414 +NS_IMETHODIMP
   1.415 +WebSocket::GetInterface(const nsIID& aIID, void** aResult)
   1.416 +{
   1.417 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.418 +
   1.419 +  if (mReadyState == WebSocket::CLOSED)
   1.420 +    return NS_ERROR_FAILURE;
   1.421 +
   1.422 +  if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
   1.423 +      aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
   1.424 +    nsresult rv;
   1.425 +    nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   1.426 +    nsCOMPtr<nsIDocument> doc =
   1.427 +      nsContentUtils::GetDocumentFromScriptContext(sc);
   1.428 +    if (!doc)
   1.429 +      return NS_ERROR_NOT_AVAILABLE;
   1.430 +
   1.431 +    nsCOMPtr<nsIPromptFactory> wwatch =
   1.432 +      do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
   1.433 +    NS_ENSURE_SUCCESS(rv, rv);
   1.434 +
   1.435 +    nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow();
   1.436 +    return wwatch->GetPrompt(outerWindow, aIID, aResult);
   1.437 +  }
   1.438 +
   1.439 +  return QueryInterface(aIID, aResult);
   1.440 +}
   1.441 +
   1.442 +////////////////////////////////////////////////////////////////////////////////
   1.443 +// WebSocket
   1.444 +////////////////////////////////////////////////////////////////////////////////
   1.445 +
   1.446 +WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
   1.447 +: DOMEventTargetHelper(aOwnerWindow),
   1.448 +  mKeepingAlive(false),
   1.449 +  mCheckMustKeepAlive(true),
   1.450 +  mOnCloseScheduled(false),
   1.451 +  mFailed(false),
   1.452 +  mDisconnected(false),
   1.453 +  mCloseEventWasClean(false),
   1.454 +  mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
   1.455 +  mReadyState(WebSocket::CONNECTING),
   1.456 +  mOutgoingBufferedAmount(0),
   1.457 +  mBinaryType(dom::BinaryType::Blob),
   1.458 +  mScriptLine(0),
   1.459 +  mInnerWindowID(0)
   1.460 +{
   1.461 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.462 +  MOZ_ASSERT(aOwnerWindow);
   1.463 +  MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
   1.464 +}
   1.465 +
   1.466 +WebSocket::~WebSocket()
   1.467 +{
   1.468 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.469 +
   1.470 +  // If we threw during Init we never called disconnect
   1.471 +  if (!mDisconnected) {
   1.472 +    Disconnect();
   1.473 +  }
   1.474 +}
   1.475 +
   1.476 +JSObject*
   1.477 +WebSocket::WrapObject(JSContext* cx)
   1.478 +{
   1.479 +  return WebSocketBinding::Wrap(cx, this);
   1.480 +}
   1.481 +
   1.482 +//---------------------------------------------------------------------------
   1.483 +// WebIDL
   1.484 +//---------------------------------------------------------------------------
   1.485 +
   1.486 +// Constructor:
   1.487 +already_AddRefed<WebSocket>
   1.488 +WebSocket::Constructor(const GlobalObject& aGlobal,
   1.489 +                       const nsAString& aUrl,
   1.490 +                       ErrorResult& aRv)
   1.491 +{
   1.492 +  Sequence<nsString> protocols;
   1.493 +  return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
   1.494 +}
   1.495 +
   1.496 +already_AddRefed<WebSocket>
   1.497 +WebSocket::Constructor(const GlobalObject& aGlobal,
   1.498 +                       const nsAString& aUrl,
   1.499 +                       const nsAString& aProtocol,
   1.500 +                       ErrorResult& aRv)
   1.501 +{
   1.502 +  Sequence<nsString> protocols;
   1.503 +  protocols.AppendElement(aProtocol);
   1.504 +  return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
   1.505 +}
   1.506 +
   1.507 +already_AddRefed<WebSocket>
   1.508 +WebSocket::Constructor(const GlobalObject& aGlobal,
   1.509 +                       const nsAString& aUrl,
   1.510 +                       const Sequence<nsString>& aProtocols,
   1.511 +                       ErrorResult& aRv)
   1.512 +{
   1.513 +  if (!PrefEnabled()) {
   1.514 +    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
   1.515 +    return nullptr;
   1.516 +  }
   1.517 +
   1.518 +  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
   1.519 +    do_QueryInterface(aGlobal.GetAsSupports());
   1.520 +  if (!scriptPrincipal) {
   1.521 +    aRv.Throw(NS_ERROR_FAILURE);
   1.522 +    return nullptr;
   1.523 +  }
   1.524 +
   1.525 +  nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
   1.526 +  if (!principal) {
   1.527 +    aRv.Throw(NS_ERROR_FAILURE);
   1.528 +    return nullptr;
   1.529 +  }
   1.530 +
   1.531 +  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
   1.532 +  if (!sgo) {
   1.533 +    aRv.Throw(NS_ERROR_FAILURE);
   1.534 +    return nullptr;
   1.535 +  }
   1.536 +
   1.537 +  nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   1.538 +  if (!ownerWindow) {
   1.539 +    aRv.Throw(NS_ERROR_FAILURE);
   1.540 +    return nullptr;
   1.541 +  }
   1.542 +
   1.543 +  nsTArray<nsString> protocolArray;
   1.544 +
   1.545 +  for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
   1.546 +
   1.547 +    const nsString& protocolElement = aProtocols[index];
   1.548 +
   1.549 +    if (protocolElement.IsEmpty()) {
   1.550 +      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   1.551 +      return nullptr;
   1.552 +    }
   1.553 +    if (protocolArray.Contains(protocolElement)) {
   1.554 +      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   1.555 +      return nullptr;
   1.556 +    }
   1.557 +    if (protocolElement.FindChar(',') != -1)  /* interferes w/list */ {
   1.558 +      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
   1.559 +      return nullptr;
   1.560 +    }
   1.561 +
   1.562 +    protocolArray.AppendElement(protocolElement);
   1.563 +  }
   1.564 +
   1.565 +  nsRefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
   1.566 +  nsresult rv = webSocket->Init(aGlobal.GetContext(), principal,
   1.567 +                                aUrl, protocolArray);
   1.568 +  if (NS_FAILED(rv)) {
   1.569 +    aRv.Throw(rv);
   1.570 +    return nullptr;
   1.571 +  }
   1.572 +
   1.573 +  return webSocket.forget();
   1.574 +}
   1.575 +
   1.576 +NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
   1.577 +
   1.578 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
   1.579 +  bool isBlack = tmp->IsBlack();
   1.580 +  if (isBlack|| tmp->mKeepingAlive) {
   1.581 +    if (tmp->mListenerManager) {
   1.582 +      tmp->mListenerManager->MarkForCC();
   1.583 +    }
   1.584 +    if (!isBlack && tmp->PreservingWrapper()) {
   1.585 +      // This marks the wrapper black.
   1.586 +      tmp->GetWrapper();
   1.587 +    }
   1.588 +    return true;
   1.589 +  }
   1.590 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
   1.591 +
   1.592 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket)
   1.593 +  return tmp->IsBlack();
   1.594 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
   1.595 +
   1.596 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket)
   1.597 +  return tmp->IsBlack();
   1.598 +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
   1.599 +
   1.600 +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
   1.601 +                                               DOMEventTargetHelper)
   1.602 +NS_IMPL_CYCLE_COLLECTION_TRACE_END
   1.603 +
   1.604 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
   1.605 +                                                  DOMEventTargetHelper)
   1.606 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   1.607 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI)
   1.608 +  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
   1.609 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   1.610 +
   1.611 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
   1.612 +                                                DOMEventTargetHelper)
   1.613 +  tmp->Disconnect();
   1.614 +  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
   1.615 +  NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI)
   1.616 +  NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
   1.617 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   1.618 +
   1.619 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
   1.620 +  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
   1.621 +  NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener)
   1.622 +  NS_INTERFACE_MAP_ENTRY(nsIObserver)
   1.623 +  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   1.624 +  NS_INTERFACE_MAP_ENTRY(nsIRequest)
   1.625 +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
   1.626 +
   1.627 +NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
   1.628 +NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
   1.629 +
   1.630 +void
   1.631 +WebSocket::DisconnectFromOwner()
   1.632 +{
   1.633 +  DOMEventTargetHelper::DisconnectFromOwner();
   1.634 +  CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
   1.635 +  DontKeepAliveAnyMore();
   1.636 +}
   1.637 +
   1.638 +//-----------------------------------------------------------------------------
   1.639 +// WebSocket:: initialization
   1.640 +//-----------------------------------------------------------------------------
   1.641 +
   1.642 +nsresult
   1.643 +WebSocket::Init(JSContext* aCx,
   1.644 +                nsIPrincipal* aPrincipal,
   1.645 +                const nsAString& aURL,
   1.646 +                nsTArray<nsString>& aProtocolArray)
   1.647 +{
   1.648 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.649 +  MOZ_ASSERT(aPrincipal);
   1.650 +
   1.651 +  if (!PrefEnabled()) {
   1.652 +    return NS_ERROR_DOM_SECURITY_ERR;
   1.653 +  }
   1.654 +
   1.655 +  mPrincipal = aPrincipal;
   1.656 +
   1.657 +  // Attempt to kill "ghost" websocket: but usually too early for check to fail
   1.658 +  nsresult rv = CheckInnerWindowCorrectness();
   1.659 +  NS_ENSURE_SUCCESS(rv, rv);
   1.660 +
   1.661 +  // Shut down websocket if window is frozen or destroyed (only needed for
   1.662 +  // "ghost" websockets--see bug 696085)
   1.663 +  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   1.664 +  NS_ENSURE_STATE(os);
   1.665 +  rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
   1.666 +  NS_ENSURE_SUCCESS(rv, rv);
   1.667 +  rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
   1.668 +  NS_ENSURE_SUCCESS(rv, rv);
   1.669 +
   1.670 +  unsigned lineno;
   1.671 +  JS::AutoFilename file;
   1.672 +  if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) {
   1.673 +    mScriptFile = file.get();
   1.674 +    mScriptLine = lineno;
   1.675 +  }
   1.676 +
   1.677 +  // Get WindowID
   1.678 +  mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
   1.679 +
   1.680 +  // parses the url
   1.681 +  rv = ParseURL(PromiseFlatString(aURL));
   1.682 +  NS_ENSURE_SUCCESS(rv, rv);
   1.683 +
   1.684 +  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
   1.685 +  NS_ENSURE_SUCCESS(rv, rv);
   1.686 +
   1.687 +  nsCOMPtr<nsIDocument> originDoc = nsContentUtils::GetDocumentFromScriptContext(sc);
   1.688 +
   1.689 +  // Don't allow https:// to open ws://
   1.690 +  if (!mSecure &&
   1.691 +      !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
   1.692 +                            false)) {
   1.693 +    // Confirmed we are opening plain ws:// and want to prevent this from a
   1.694 +    // secure context (e.g. https). Check the security context of the document
   1.695 +    // associated with this script, which is the same as associated with mOwner.
   1.696 +    if (originDoc && originDoc->GetSecurityInfo()) {
   1.697 +      return NS_ERROR_DOM_SECURITY_ERR;
   1.698 +    }
   1.699 +  }
   1.700 +
   1.701 +  // Assign the sub protocol list and scan it for illegal values
   1.702 +  for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
   1.703 +    for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
   1.704 +      if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
   1.705 +          aProtocolArray[index][i] > static_cast<char16_t>(0x007E))
   1.706 +        return NS_ERROR_DOM_SYNTAX_ERR;
   1.707 +    }
   1.708 +
   1.709 +    if (!mRequestedProtocolList.IsEmpty()) {
   1.710 +      mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", "));
   1.711 +    }
   1.712 +
   1.713 +    AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
   1.714 +  }
   1.715 +
   1.716 +  // Check content policy.
   1.717 +  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
   1.718 +  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
   1.719 +                                 mURI,
   1.720 +                                 mPrincipal,
   1.721 +                                 originDoc,
   1.722 +                                 EmptyCString(),
   1.723 +                                 nullptr,
   1.724 +                                 &shouldLoad,
   1.725 +                                 nsContentUtils::GetContentPolicy(),
   1.726 +                                 nsContentUtils::GetSecurityManager());
   1.727 +  NS_ENSURE_SUCCESS(rv, rv);
   1.728 +  if (NS_CP_REJECTED(shouldLoad)) {
   1.729 +    // Disallowed by content policy.
   1.730 +    return NS_ERROR_CONTENT_BLOCKED;
   1.731 +  }
   1.732 +
   1.733 +  // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
   1.734 +  // url parameter, so don't throw if EstablishConnection fails, and call
   1.735 +  // onerror/onclose asynchronously
   1.736 +  if (NS_FAILED(EstablishConnection())) {
   1.737 +    FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
   1.738 +  }
   1.739 +
   1.740 +  return NS_OK;
   1.741 +}
   1.742 +
   1.743 +//-----------------------------------------------------------------------------
   1.744 +// WebSocket methods:
   1.745 +//-----------------------------------------------------------------------------
   1.746 +
   1.747 +class nsAutoCloseWS
   1.748 +{
   1.749 +public:
   1.750 +  nsAutoCloseWS(WebSocket* aWebSocket)
   1.751 +    : mWebSocket(aWebSocket)
   1.752 +  {}
   1.753 +
   1.754 +  ~nsAutoCloseWS()
   1.755 +  {
   1.756 +    if (!mWebSocket->mChannel) {
   1.757 +      mWebSocket->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
   1.758 +    }
   1.759 +  }
   1.760 +private:
   1.761 +  nsRefPtr<WebSocket> mWebSocket;
   1.762 +};
   1.763 +
   1.764 +nsresult
   1.765 +WebSocket::EstablishConnection()
   1.766 +{
   1.767 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.768 +  NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null");
   1.769 +
   1.770 +  nsCOMPtr<nsIWebSocketChannel> wsChannel;
   1.771 +  nsAutoCloseWS autoClose(this);
   1.772 +  nsresult rv;
   1.773 +
   1.774 +  if (mSecure) {
   1.775 +    wsChannel =
   1.776 +      do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
   1.777 +  } else {
   1.778 +    wsChannel =
   1.779 +      do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
   1.780 +  }
   1.781 +  NS_ENSURE_SUCCESS(rv, rv);
   1.782 +
   1.783 +  rv = wsChannel->SetNotificationCallbacks(this);
   1.784 +  NS_ENSURE_SUCCESS(rv, rv);
   1.785 +
   1.786 +  // add ourselves to the document's load group and
   1.787 +  // provide the http stack the loadgroup info too
   1.788 +  nsCOMPtr<nsILoadGroup> loadGroup;
   1.789 +  rv = GetLoadGroup(getter_AddRefs(loadGroup));
   1.790 +  if (loadGroup) {
   1.791 +    rv = wsChannel->SetLoadGroup(loadGroup);
   1.792 +    NS_ENSURE_SUCCESS(rv, rv);
   1.793 +    rv = loadGroup->AddRequest(this, nullptr);
   1.794 +    NS_ENSURE_SUCCESS(rv, rv);
   1.795 +  }
   1.796 +
   1.797 +  if (!mRequestedProtocolList.IsEmpty()) {
   1.798 +    rv = wsChannel->SetProtocol(mRequestedProtocolList);
   1.799 +    NS_ENSURE_SUCCESS(rv, rv);
   1.800 +  }
   1.801 +
   1.802 +  nsCString asciiOrigin;
   1.803 +  rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin);
   1.804 +  NS_ENSURE_SUCCESS(rv, rv);
   1.805 +
   1.806 +  ToLowerCase(asciiOrigin);
   1.807 +
   1.808 +  rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nullptr);
   1.809 +  NS_ENSURE_SUCCESS(rv, rv);
   1.810 +
   1.811 +  mChannel = wsChannel;
   1.812 +
   1.813 +  return NS_OK;
   1.814 +}
   1.815 +
   1.816 +void
   1.817 +WebSocket::DispatchConnectionCloseEvents()
   1.818 +{
   1.819 +  mReadyState = WebSocket::CLOSED;
   1.820 +
   1.821 +  // Call 'onerror' if needed
   1.822 +  if (mFailed) {
   1.823 +    nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
   1.824 +    if (NS_FAILED(rv)) {
   1.825 +      NS_WARNING("Failed to dispatch the error event");
   1.826 +    }
   1.827 +  }
   1.828 +
   1.829 +  nsresult rv = CreateAndDispatchCloseEvent(mCloseEventWasClean, mCloseEventCode,
   1.830 +                                            mCloseEventReason);
   1.831 +  if (NS_FAILED(rv)) {
   1.832 +    NS_WARNING("Failed to dispatch the close event");
   1.833 +  }
   1.834 +
   1.835 +  UpdateMustKeepAlive();
   1.836 +  Disconnect();
   1.837 +}
   1.838 +
   1.839 +nsresult
   1.840 +WebSocket::CreateAndDispatchSimpleEvent(const nsString& aName)
   1.841 +{
   1.842 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.843 +
   1.844 +  nsresult rv = CheckInnerWindowCorrectness();
   1.845 +  if (NS_FAILED(rv)) {
   1.846 +    return NS_OK;
   1.847 +  }
   1.848 +
   1.849 +  nsCOMPtr<nsIDOMEvent> event;
   1.850 +  rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
   1.851 +  NS_ENSURE_SUCCESS(rv, rv);
   1.852 +
   1.853 +  // it doesn't bubble, and it isn't cancelable
   1.854 +  rv = event->InitEvent(aName, false, false);
   1.855 +  NS_ENSURE_SUCCESS(rv, rv);
   1.856 +
   1.857 +  event->SetTrusted(true);
   1.858 +
   1.859 +  return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.860 +}
   1.861 +
   1.862 +nsresult
   1.863 +WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
   1.864 +                                         bool isBinary)
   1.865 +{
   1.866 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.867 +
   1.868 +  nsresult rv = CheckInnerWindowCorrectness();
   1.869 +  if (NS_FAILED(rv))
   1.870 +    return NS_OK;
   1.871 +
   1.872 +  // Get the JSContext
   1.873 +  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(GetOwner());
   1.874 +  NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
   1.875 +
   1.876 +  nsIScriptContext* scriptContext = sgo->GetContext();
   1.877 +  NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE);
   1.878 +
   1.879 +  AutoPushJSContext cx(scriptContext->GetNativeContext());
   1.880 +  NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
   1.881 +
   1.882 +  // Create appropriate JS object for message
   1.883 +  JS::Rooted<JS::Value> jsData(cx);
   1.884 +  if (isBinary) {
   1.885 +    if (mBinaryType == dom::BinaryType::Blob) {
   1.886 +      rv = nsContentUtils::CreateBlobBuffer(cx, aData, &jsData);
   1.887 +      NS_ENSURE_SUCCESS(rv, rv);
   1.888 +    } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
   1.889 +      JS::Rooted<JSObject*> arrayBuf(cx);
   1.890 +      rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
   1.891 +      NS_ENSURE_SUCCESS(rv, rv);
   1.892 +      jsData = OBJECT_TO_JSVAL(arrayBuf);
   1.893 +    } else {
   1.894 +      NS_RUNTIMEABORT("Unknown binary type!");
   1.895 +      return NS_ERROR_UNEXPECTED;
   1.896 +    }
   1.897 +  } else {
   1.898 +    // JS string
   1.899 +    NS_ConvertUTF8toUTF16 utf16Data(aData);
   1.900 +    JSString* jsString;
   1.901 +    jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
   1.902 +    NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
   1.903 +
   1.904 +    jsData = STRING_TO_JSVAL(jsString);
   1.905 +  }
   1.906 +
   1.907 +  // create an event that uses the MessageEvent interface,
   1.908 +  // which does not bubble, is not cancelable, and has no default action
   1.909 +
   1.910 +  nsCOMPtr<nsIDOMEvent> event;
   1.911 +  rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr);
   1.912 +  NS_ENSURE_SUCCESS(rv, rv);
   1.913 +
   1.914 +  nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event);
   1.915 +  rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"),
   1.916 +                                      false, false,
   1.917 +                                      jsData,
   1.918 +                                      mUTF16Origin,
   1.919 +                                      EmptyString(), nullptr);
   1.920 +  NS_ENSURE_SUCCESS(rv, rv);
   1.921 +
   1.922 +  event->SetTrusted(true);
   1.923 +
   1.924 +  return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.925 +}
   1.926 +
   1.927 +nsresult
   1.928 +WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
   1.929 +                                       uint16_t aCode,
   1.930 +                                       const nsString &aReason)
   1.931 +{
   1.932 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   1.933 +
   1.934 +  nsresult rv = CheckInnerWindowCorrectness();
   1.935 +  if (NS_FAILED(rv)) {
   1.936 +    return NS_OK;
   1.937 +  }
   1.938 +
   1.939 +  // create an event that uses the CloseEvent interface,
   1.940 +  // which does not bubble, is not cancelable, and has no default action
   1.941 +
   1.942 +  nsCOMPtr<nsIDOMEvent> event;
   1.943 +  rv = NS_NewDOMCloseEvent(getter_AddRefs(event), this, nullptr, nullptr);
   1.944 +  NS_ENSURE_SUCCESS(rv, rv);
   1.945 +
   1.946 +  nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event);
   1.947 +  rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"),
   1.948 +                                  false, false,
   1.949 +                                  aWasClean, aCode, aReason);
   1.950 +  NS_ENSURE_SUCCESS(rv, rv);
   1.951 +
   1.952 +  event->SetTrusted(true);
   1.953 +
   1.954 +  return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   1.955 +}
   1.956 +
   1.957 +bool
   1.958 +WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
   1.959 +{
   1.960 +  return Preferences::GetBool("network.websocket.enabled", true);
   1.961 +}
   1.962 +
   1.963 +nsresult
   1.964 +WebSocket::ParseURL(const nsString& aURL)
   1.965 +{
   1.966 +  NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
   1.967 +
   1.968 +  nsCOMPtr<nsIURI> uri;
   1.969 +  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
   1.970 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
   1.971 +
   1.972 +  nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
   1.973 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
   1.974 +
   1.975 +  nsAutoCString fragment;
   1.976 +  rv = parsedURL->GetRef(fragment);
   1.977 +  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && fragment.IsEmpty(),
   1.978 +                 NS_ERROR_DOM_SYNTAX_ERR);
   1.979 +
   1.980 +  nsAutoCString scheme;
   1.981 +  rv = parsedURL->GetScheme(scheme);
   1.982 +  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
   1.983 +                 NS_ERROR_DOM_SYNTAX_ERR);
   1.984 +
   1.985 +  nsAutoCString host;
   1.986 +  rv = parsedURL->GetAsciiHost(host);
   1.987 +  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
   1.988 +
   1.989 +  int32_t port;
   1.990 +  rv = parsedURL->GetPort(&port);
   1.991 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
   1.992 +
   1.993 +  rv = NS_CheckPortSafety(port, scheme.get());
   1.994 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
   1.995 +
   1.996 +  nsAutoCString filePath;
   1.997 +  rv = parsedURL->GetFilePath(filePath);
   1.998 +  if (filePath.IsEmpty()) {
   1.999 +    filePath.AssignLiteral("/");
  1.1000 +  }
  1.1001 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
  1.1002 +
  1.1003 +  nsAutoCString query;
  1.1004 +  rv = parsedURL->GetQuery(query);
  1.1005 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
  1.1006 +
  1.1007 +  if (scheme.LowerCaseEqualsLiteral("ws")) {
  1.1008 +     mSecure = false;
  1.1009 +     mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
  1.1010 +  } else if (scheme.LowerCaseEqualsLiteral("wss")) {
  1.1011 +    mSecure = true;
  1.1012 +    mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
  1.1013 +  } else {
  1.1014 +    return NS_ERROR_DOM_SYNTAX_ERR;
  1.1015 +  }
  1.1016 +
  1.1017 +  rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
  1.1018 +  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
  1.1019 +
  1.1020 +  mAsciiHost = host;
  1.1021 +  ToLowerCase(mAsciiHost);
  1.1022 +
  1.1023 +  mResource = filePath;
  1.1024 +  if (!query.IsEmpty()) {
  1.1025 +    mResource.AppendLiteral("?");
  1.1026 +    mResource.Append(query);
  1.1027 +  }
  1.1028 +  uint32_t length = mResource.Length();
  1.1029 +  uint32_t i;
  1.1030 +  for (i = 0; i < length; ++i) {
  1.1031 +    if (mResource[i] < static_cast<char16_t>(0x0021) ||
  1.1032 +        mResource[i] > static_cast<char16_t>(0x007E)) {
  1.1033 +      return NS_ERROR_DOM_SYNTAX_ERR;
  1.1034 +    }
  1.1035 +  }
  1.1036 +
  1.1037 +  mOriginalURL = aURL;
  1.1038 +  mURI = parsedURL;
  1.1039 +  return NS_OK;
  1.1040 +}
  1.1041 +
  1.1042 +//-----------------------------------------------------------------------------
  1.1043 +// Methods that keep alive the WebSocket object when:
  1.1044 +//   1. the object has registered event listeners that can be triggered
  1.1045 +//      ("strong event listeners");
  1.1046 +//   2. there are outgoing not sent messages.
  1.1047 +//-----------------------------------------------------------------------------
  1.1048 +
  1.1049 +void
  1.1050 +WebSocket::UpdateMustKeepAlive()
  1.1051 +{
  1.1052 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1053 +  if (!mCheckMustKeepAlive) {
  1.1054 +    return;
  1.1055 +  }
  1.1056 +
  1.1057 +  bool shouldKeepAlive = false;
  1.1058 +
  1.1059 +  if (mListenerManager) {
  1.1060 +    switch (mReadyState)
  1.1061 +    {
  1.1062 +      case WebSocket::CONNECTING:
  1.1063 +      {
  1.1064 +        if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
  1.1065 +            mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
  1.1066 +            mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
  1.1067 +            mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
  1.1068 +          shouldKeepAlive = true;
  1.1069 +        }
  1.1070 +      }
  1.1071 +      break;
  1.1072 +
  1.1073 +      case WebSocket::OPEN:
  1.1074 +      case WebSocket::CLOSING:
  1.1075 +      {
  1.1076 +        if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
  1.1077 +            mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
  1.1078 +            mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
  1.1079 +            mOutgoingBufferedAmount != 0) {
  1.1080 +          shouldKeepAlive = true;
  1.1081 +        }
  1.1082 +      }
  1.1083 +      break;
  1.1084 +
  1.1085 +      case WebSocket::CLOSED:
  1.1086 +      {
  1.1087 +        shouldKeepAlive = false;
  1.1088 +      }
  1.1089 +    }
  1.1090 +  }
  1.1091 +
  1.1092 +  if (mKeepingAlive && !shouldKeepAlive) {
  1.1093 +    mKeepingAlive = false;
  1.1094 +    static_cast<EventTarget*>(this)->Release();
  1.1095 +  } else if (!mKeepingAlive && shouldKeepAlive) {
  1.1096 +    mKeepingAlive = true;
  1.1097 +    static_cast<EventTarget*>(this)->AddRef();
  1.1098 +  }
  1.1099 +}
  1.1100 +
  1.1101 +void
  1.1102 +WebSocket::DontKeepAliveAnyMore()
  1.1103 +{
  1.1104 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1105 +  if (mKeepingAlive) {
  1.1106 +    mKeepingAlive = false;
  1.1107 +    static_cast<EventTarget*>(this)->Release();
  1.1108 +  }
  1.1109 +  mCheckMustKeepAlive = false;
  1.1110 +}
  1.1111 +
  1.1112 +nsresult
  1.1113 +WebSocket::UpdateURI()
  1.1114 +{
  1.1115 +  // Check for Redirections
  1.1116 +  nsCOMPtr<nsIURI> uri;
  1.1117 +  nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
  1.1118 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1119 +
  1.1120 +  nsAutoCString spec;
  1.1121 +  rv = uri->GetSpec(spec);
  1.1122 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1123 +  CopyUTF8toUTF16(spec, mEffectiveURL);
  1.1124 +
  1.1125 +  bool isWSS = false;
  1.1126 +  rv = uri->SchemeIs("wss", &isWSS);
  1.1127 +  NS_ENSURE_SUCCESS(rv, rv);
  1.1128 +  mSecure = isWSS ? true : false;
  1.1129 +
  1.1130 +  return NS_OK;
  1.1131 +}
  1.1132 +
  1.1133 +void
  1.1134 +WebSocket::EventListenerAdded(nsIAtom* aType)
  1.1135 +{
  1.1136 +  UpdateMustKeepAlive();
  1.1137 +}
  1.1138 +
  1.1139 +void
  1.1140 +WebSocket::EventListenerRemoved(nsIAtom* aType)
  1.1141 +{
  1.1142 +  UpdateMustKeepAlive();
  1.1143 +}
  1.1144 +
  1.1145 +//-----------------------------------------------------------------------------
  1.1146 +// WebSocket - methods
  1.1147 +//-----------------------------------------------------------------------------
  1.1148 +
  1.1149 +// webIDL: readonly attribute DOMString url
  1.1150 +void
  1.1151 +WebSocket::GetUrl(nsAString& aURL)
  1.1152 +{
  1.1153 +  if (mEffectiveURL.IsEmpty()) {
  1.1154 +    aURL = mOriginalURL;
  1.1155 +  } else {
  1.1156 +    aURL = mEffectiveURL;
  1.1157 +  }
  1.1158 +}
  1.1159 +
  1.1160 +// webIDL: readonly attribute DOMString extensions;
  1.1161 +void
  1.1162 +WebSocket::GetExtensions(nsAString& aExtensions)
  1.1163 +{
  1.1164 +  CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
  1.1165 +}
  1.1166 +
  1.1167 +// webIDL: readonly attribute DOMString protocol;
  1.1168 +void
  1.1169 +WebSocket::GetProtocol(nsAString& aProtocol)
  1.1170 +{
  1.1171 +  CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
  1.1172 +}
  1.1173 +
  1.1174 +// webIDL: void send(DOMString data);
  1.1175 +void
  1.1176 +WebSocket::Send(const nsAString& aData,
  1.1177 +                ErrorResult& aRv)
  1.1178 +{
  1.1179 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1180 +
  1.1181 +  NS_ConvertUTF16toUTF8 msgString(aData);
  1.1182 +  Send(nullptr, msgString, msgString.Length(), false, aRv);
  1.1183 +}
  1.1184 +
  1.1185 +void
  1.1186 +WebSocket::Send(nsIDOMBlob* aData,
  1.1187 +                ErrorResult& aRv)
  1.1188 +{
  1.1189 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1190 +
  1.1191 +  nsCOMPtr<nsIInputStream> msgStream;
  1.1192 +  nsresult rv = aData->GetInternalStream(getter_AddRefs(msgStream));
  1.1193 +  if (NS_FAILED(rv)) {
  1.1194 +    aRv.Throw(rv);
  1.1195 +    return;
  1.1196 +  }
  1.1197 +
  1.1198 +  uint64_t msgLength;
  1.1199 +  rv = aData->GetSize(&msgLength);
  1.1200 +  if (NS_FAILED(rv)) {
  1.1201 +    aRv.Throw(rv);
  1.1202 +    return;
  1.1203 +  }
  1.1204 +
  1.1205 +  if (msgLength > UINT32_MAX) {
  1.1206 +    aRv.Throw(NS_ERROR_FILE_TOO_BIG);
  1.1207 +    return;
  1.1208 +  }
  1.1209 +
  1.1210 +  Send(msgStream, EmptyCString(), msgLength, true, aRv);
  1.1211 +}
  1.1212 +
  1.1213 +void
  1.1214 +WebSocket::Send(const ArrayBuffer& aData,
  1.1215 +                ErrorResult& aRv)
  1.1216 +{
  1.1217 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1218 +
  1.1219 +  aData.ComputeLengthAndData();
  1.1220 +
  1.1221 +  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
  1.1222 +
  1.1223 +  uint32_t len = aData.Length();
  1.1224 +  char* data = reinterpret_cast<char*>(aData.Data());
  1.1225 +
  1.1226 +  nsDependentCSubstring msgString(data, len);
  1.1227 +  Send(nullptr, msgString, len, true, aRv);
  1.1228 +}
  1.1229 +
  1.1230 +void
  1.1231 +WebSocket::Send(const ArrayBufferView& aData,
  1.1232 +                ErrorResult& aRv)
  1.1233 +{
  1.1234 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1235 +
  1.1236 +  aData.ComputeLengthAndData();
  1.1237 +
  1.1238 +  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
  1.1239 +
  1.1240 +  uint32_t len = aData.Length();
  1.1241 +  char* data = reinterpret_cast<char*>(aData.Data());
  1.1242 +
  1.1243 +  nsDependentCSubstring msgString(data, len);
  1.1244 +  Send(nullptr, msgString, len, true, aRv);
  1.1245 +}
  1.1246 +
  1.1247 +void
  1.1248 +WebSocket::Send(nsIInputStream* aMsgStream,
  1.1249 +                const nsACString& aMsgString,
  1.1250 +                uint32_t aMsgLength,
  1.1251 +                bool aIsBinary,
  1.1252 +                ErrorResult& aRv)
  1.1253 +{
  1.1254 +  if (mReadyState == WebSocket::CONNECTING) {
  1.1255 +    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
  1.1256 +    return;
  1.1257 +  }
  1.1258 +
  1.1259 +  // Always increment outgoing buffer len, even if closed
  1.1260 +  mOutgoingBufferedAmount += aMsgLength;
  1.1261 +
  1.1262 +  if (mReadyState == WebSocket::CLOSING ||
  1.1263 +      mReadyState == WebSocket::CLOSED) {
  1.1264 +    return;
  1.1265 +  }
  1.1266 +
  1.1267 +  MOZ_ASSERT(mReadyState == WebSocket::OPEN,
  1.1268 +             "Unknown state in WebSocket::Send");
  1.1269 +
  1.1270 +  nsresult rv;
  1.1271 +  if (aMsgStream) {
  1.1272 +    rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength);
  1.1273 +  } else {
  1.1274 +    if (aIsBinary) {
  1.1275 +      rv = mChannel->SendBinaryMsg(aMsgString);
  1.1276 +    } else {
  1.1277 +      rv = mChannel->SendMsg(aMsgString);
  1.1278 +    }
  1.1279 +  }
  1.1280 +
  1.1281 +  if (NS_FAILED(rv)) {
  1.1282 +    aRv.Throw(rv);
  1.1283 +    return;
  1.1284 +  }
  1.1285 +
  1.1286 +  UpdateMustKeepAlive();
  1.1287 +}
  1.1288 +
  1.1289 +// webIDL: void close(optional unsigned short code, optional DOMString reason):
  1.1290 +void
  1.1291 +WebSocket::Close(const Optional<uint16_t>& aCode,
  1.1292 +                 const Optional<nsAString>& aReason,
  1.1293 +                 ErrorResult& aRv)
  1.1294 +{
  1.1295 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1296 +
  1.1297 +  // the reason code is optional, but if provided it must be in a specific range
  1.1298 +  uint16_t closeCode = 0;
  1.1299 +  if (aCode.WasPassed()) {
  1.1300 +    if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
  1.1301 +      aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
  1.1302 +      return;
  1.1303 +    }
  1.1304 +    closeCode = aCode.Value();
  1.1305 +  }
  1.1306 +
  1.1307 +  nsCString closeReason;
  1.1308 +  if (aReason.WasPassed()) {
  1.1309 +    CopyUTF16toUTF8(aReason.Value(), closeReason);
  1.1310 +
  1.1311 +    // The API requires the UTF-8 string to be 123 or less bytes
  1.1312 +    if (closeReason.Length() > 123) {
  1.1313 +      aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
  1.1314 +      return;
  1.1315 +    }
  1.1316 +  }
  1.1317 +
  1.1318 +  if (mReadyState == WebSocket::CLOSING ||
  1.1319 +      mReadyState == WebSocket::CLOSED) {
  1.1320 +    return;
  1.1321 +  }
  1.1322 +
  1.1323 +  if (mReadyState == WebSocket::CONNECTING) {
  1.1324 +    FailConnection(closeCode, closeReason);
  1.1325 +    return;
  1.1326 +  }
  1.1327 +
  1.1328 +  MOZ_ASSERT(mReadyState == WebSocket::OPEN);
  1.1329 +  CloseConnection(closeCode, closeReason);
  1.1330 +}
  1.1331 +
  1.1332 +//-----------------------------------------------------------------------------
  1.1333 +// WebSocket::nsIObserver
  1.1334 +//-----------------------------------------------------------------------------
  1.1335 +
  1.1336 +NS_IMETHODIMP
  1.1337 +WebSocket::Observe(nsISupports* aSubject,
  1.1338 +                   const char* aTopic,
  1.1339 +                   const char16_t* aData)
  1.1340 +{
  1.1341 +  if ((mReadyState == WebSocket::CLOSING) ||
  1.1342 +      (mReadyState == WebSocket::CLOSED)) {
  1.1343 +    return NS_OK;
  1.1344 +  }
  1.1345 +
  1.1346 +  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
  1.1347 +  if (!GetOwner() || window != GetOwner()) {
  1.1348 +    return NS_OK;
  1.1349 +  }
  1.1350 +
  1.1351 +  if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
  1.1352 +      (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
  1.1353 +  {
  1.1354 +    CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
  1.1355 +  }
  1.1356 +
  1.1357 +  return NS_OK;
  1.1358 +}
  1.1359 +
  1.1360 +//-----------------------------------------------------------------------------
  1.1361 +// WebSocket::nsIRequest
  1.1362 +//-----------------------------------------------------------------------------
  1.1363 +
  1.1364 +NS_IMETHODIMP
  1.1365 +WebSocket::GetName(nsACString& aName)
  1.1366 +{
  1.1367 +  CopyUTF16toUTF8(mOriginalURL, aName);
  1.1368 +  return NS_OK;
  1.1369 +}
  1.1370 +
  1.1371 +NS_IMETHODIMP
  1.1372 +WebSocket::IsPending(bool* aValue)
  1.1373 +{
  1.1374 +  *aValue = (mReadyState != WebSocket::CLOSED);
  1.1375 +  return NS_OK;
  1.1376 +}
  1.1377 +
  1.1378 +NS_IMETHODIMP
  1.1379 +WebSocket::GetStatus(nsresult* aStatus)
  1.1380 +{
  1.1381 +  *aStatus = NS_OK;
  1.1382 +  return NS_OK;
  1.1383 +}
  1.1384 +
  1.1385 +// Window closed, stop/reload button pressed, user navigated away from page, etc.
  1.1386 +NS_IMETHODIMP
  1.1387 +WebSocket::Cancel(nsresult aStatus)
  1.1388 +{
  1.1389 +  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
  1.1390 +
  1.1391 +  if (mReadyState == CLOSING || mReadyState == CLOSED) {
  1.1392 +    return NS_OK;
  1.1393 +  }
  1.1394 +
  1.1395 +  ConsoleError();
  1.1396 +
  1.1397 +  return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
  1.1398 +}
  1.1399 +
  1.1400 +NS_IMETHODIMP
  1.1401 +WebSocket::Suspend()
  1.1402 +{
  1.1403 +  return NS_ERROR_NOT_IMPLEMENTED;
  1.1404 +}
  1.1405 +
  1.1406 +NS_IMETHODIMP
  1.1407 +WebSocket::Resume()
  1.1408 +{
  1.1409 +  return NS_ERROR_NOT_IMPLEMENTED;
  1.1410 +}
  1.1411 +
  1.1412 +NS_IMETHODIMP
  1.1413 +WebSocket::GetLoadGroup(nsILoadGroup** aLoadGroup)
  1.1414 +{
  1.1415 +  *aLoadGroup = nullptr;
  1.1416 +
  1.1417 +  nsresult rv;
  1.1418 +  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
  1.1419 +  nsCOMPtr<nsIDocument> doc =
  1.1420 +    nsContentUtils::GetDocumentFromScriptContext(sc);
  1.1421 +
  1.1422 +  if (doc) {
  1.1423 +    *aLoadGroup = doc->GetDocumentLoadGroup().take();
  1.1424 +  }
  1.1425 +
  1.1426 +  return NS_OK;
  1.1427 +}
  1.1428 +
  1.1429 +NS_IMETHODIMP
  1.1430 +WebSocket::SetLoadGroup(nsILoadGroup* aLoadGroup)
  1.1431 +{
  1.1432 +  return NS_ERROR_UNEXPECTED;
  1.1433 +}
  1.1434 +
  1.1435 +NS_IMETHODIMP
  1.1436 +WebSocket::GetLoadFlags(nsLoadFlags* aLoadFlags)
  1.1437 +{
  1.1438 +  *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
  1.1439 +  return NS_OK;
  1.1440 +}
  1.1441 +
  1.1442 +NS_IMETHODIMP
  1.1443 +WebSocket::SetLoadFlags(nsLoadFlags aLoadFlags)
  1.1444 +{
  1.1445 +  // we won't change the load flags at all.
  1.1446 +  return NS_OK;
  1.1447 +}
  1.1448 +
  1.1449 +} // dom namespace
  1.1450 +} // mozilla namespace

mercurial