content/base/src/WebSocket.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set sw=2 ts=8 et tw=80 : */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "WebSocket.h"
michael@0 8 #include "mozilla/dom/WebSocketBinding.h"
michael@0 9
michael@0 10 #include "jsapi.h"
michael@0 11 #include "jsfriendapi.h"
michael@0 12 #include "js/OldDebugAPI.h"
michael@0 13 #include "mozilla/DOMEventTargetHelper.h"
michael@0 14 #include "nsIScriptGlobalObject.h"
michael@0 15 #include "nsIDOMWindow.h"
michael@0 16 #include "nsIDocument.h"
michael@0 17 #include "nsXPCOM.h"
michael@0 18 #include "nsIXPConnect.h"
michael@0 19 #include "nsContentUtils.h"
michael@0 20 #include "nsCxPusher.h"
michael@0 21 #include "nsError.h"
michael@0 22 #include "nsIScriptObjectPrincipal.h"
michael@0 23 #include "nsIURL.h"
michael@0 24 #include "nsIUnicodeEncoder.h"
michael@0 25 #include "nsThreadUtils.h"
michael@0 26 #include "nsIDOMMessageEvent.h"
michael@0 27 #include "nsIPromptFactory.h"
michael@0 28 #include "nsIWindowWatcher.h"
michael@0 29 #include "nsIPrompt.h"
michael@0 30 #include "nsIStringBundle.h"
michael@0 31 #include "nsIConsoleService.h"
michael@0 32 #include "nsIDOMCloseEvent.h"
michael@0 33 #include "nsICryptoHash.h"
michael@0 34 #include "nsJSUtils.h"
michael@0 35 #include "nsIScriptError.h"
michael@0 36 #include "nsNetUtil.h"
michael@0 37 #include "nsILoadGroup.h"
michael@0 38 #include "mozilla/Preferences.h"
michael@0 39 #include "xpcpublic.h"
michael@0 40 #include "nsContentPolicyUtils.h"
michael@0 41 #include "nsDOMFile.h"
michael@0 42 #include "nsWrapperCacheInlines.h"
michael@0 43 #include "nsIObserverService.h"
michael@0 44 #include "nsIWebSocketChannel.h"
michael@0 45 #include "GeneratedEvents.h"
michael@0 46
michael@0 47 namespace mozilla {
michael@0 48 namespace dom {
michael@0 49
michael@0 50 #define UTF_8_REPLACEMENT_CHAR static_cast<char16_t>(0xFFFD)
michael@0 51
michael@0 52 class CallDispatchConnectionCloseEvents: public nsRunnable
michael@0 53 {
michael@0 54 public:
michael@0 55 CallDispatchConnectionCloseEvents(WebSocket* aWebSocket)
michael@0 56 : mWebSocket(aWebSocket)
michael@0 57 {}
michael@0 58
michael@0 59 NS_IMETHOD Run()
michael@0 60 {
michael@0 61 mWebSocket->DispatchConnectionCloseEvents();
michael@0 62 return NS_OK;
michael@0 63 }
michael@0 64
michael@0 65 private:
michael@0 66 nsRefPtr<WebSocket> mWebSocket;
michael@0 67 };
michael@0 68
michael@0 69 //-----------------------------------------------------------------------------
michael@0 70 // WebSocket
michael@0 71 //-----------------------------------------------------------------------------
michael@0 72
michael@0 73 nsresult
michael@0 74 WebSocket::PrintErrorOnConsole(const char *aBundleURI,
michael@0 75 const char16_t *aError,
michael@0 76 const char16_t **aFormatStrings,
michael@0 77 uint32_t aFormatStringsLen)
michael@0 78 {
michael@0 79 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 80
michael@0 81 nsresult rv;
michael@0 82 nsCOMPtr<nsIStringBundleService> bundleService =
michael@0 83 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
michael@0 84 NS_ENSURE_SUCCESS(rv, rv);
michael@0 85
michael@0 86 nsCOMPtr<nsIStringBundle> strBundle;
michael@0 87 rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
michael@0 88 NS_ENSURE_SUCCESS(rv, rv);
michael@0 89
michael@0 90 nsCOMPtr<nsIConsoleService> console(
michael@0 91 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
michael@0 92 NS_ENSURE_SUCCESS(rv, rv);
michael@0 93
michael@0 94 nsCOMPtr<nsIScriptError> errorObject(
michael@0 95 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
michael@0 96 NS_ENSURE_SUCCESS(rv, rv);
michael@0 97
michael@0 98 // Localize the error message
michael@0 99 nsXPIDLString message;
michael@0 100 if (aFormatStrings) {
michael@0 101 rv = strBundle->FormatStringFromName(aError, aFormatStrings,
michael@0 102 aFormatStringsLen,
michael@0 103 getter_Copies(message));
michael@0 104 } else {
michael@0 105 rv = strBundle->GetStringFromName(aError, getter_Copies(message));
michael@0 106 }
michael@0 107 NS_ENSURE_SUCCESS(rv, rv);
michael@0 108
michael@0 109 rv = errorObject->InitWithWindowID(message,
michael@0 110 NS_ConvertUTF8toUTF16(mScriptFile),
michael@0 111 EmptyString(), mScriptLine, 0,
michael@0 112 nsIScriptError::errorFlag, "Web Socket",
michael@0 113 mInnerWindowID);
michael@0 114 NS_ENSURE_SUCCESS(rv, rv);
michael@0 115
michael@0 116 // print the error message directly to the JS console
michael@0 117 rv = console->LogMessage(errorObject);
michael@0 118 NS_ENSURE_SUCCESS(rv, rv);
michael@0 119
michael@0 120 return NS_OK;
michael@0 121 }
michael@0 122
michael@0 123 nsresult
michael@0 124 WebSocket::CloseConnection(uint16_t aReasonCode,
michael@0 125 const nsACString& aReasonString)
michael@0 126 {
michael@0 127 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 128 if (mReadyState == WebSocket::CLOSING ||
michael@0 129 mReadyState == WebSocket::CLOSED) {
michael@0 130 return NS_OK;
michael@0 131 }
michael@0 132
michael@0 133 // The common case...
michael@0 134 if (mChannel) {
michael@0 135 mReadyState = WebSocket::CLOSING;
michael@0 136 return mChannel->Close(aReasonCode, aReasonString);
michael@0 137 }
michael@0 138
michael@0 139 // No channel, but not disconnected: canceled or failed early
michael@0 140 //
michael@0 141 MOZ_ASSERT(mReadyState == WebSocket::CONNECTING,
michael@0 142 "Should only get here for early websocket cancel/error");
michael@0 143
michael@0 144 // Server won't be sending us a close code, so use what's passed in here.
michael@0 145 mCloseEventCode = aReasonCode;
michael@0 146 CopyUTF8toUTF16(aReasonString, mCloseEventReason);
michael@0 147
michael@0 148 mReadyState = WebSocket::CLOSING;
michael@0 149
michael@0 150 // Can be called from Cancel() or Init() codepaths, so need to dispatch
michael@0 151 // onerror/onclose asynchronously
michael@0 152 ScheduleConnectionCloseEvents(
michael@0 153 nullptr,
michael@0 154 (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
michael@0 155 aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
michael@0 156 NS_OK : NS_ERROR_FAILURE,
michael@0 157 false);
michael@0 158
michael@0 159 return NS_OK;
michael@0 160 }
michael@0 161
michael@0 162 nsresult
michael@0 163 WebSocket::ConsoleError()
michael@0 164 {
michael@0 165 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 166
michael@0 167 nsAutoCString targetSpec;
michael@0 168 nsresult rv = mURI->GetSpec(targetSpec);
michael@0 169 if (NS_FAILED(rv)) {
michael@0 170 NS_WARNING("Failed to get targetSpec");
michael@0 171 } else {
michael@0 172 NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
michael@0 173 const char16_t* formatStrings[] = { specUTF16.get() };
michael@0 174
michael@0 175 if (mReadyState < WebSocket::OPEN) {
michael@0 176 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
michael@0 177 MOZ_UTF16("connectionFailure"),
michael@0 178 formatStrings, ArrayLength(formatStrings));
michael@0 179 } else {
michael@0 180 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
michael@0 181 MOZ_UTF16("netInterrupt"),
michael@0 182 formatStrings, ArrayLength(formatStrings));
michael@0 183 }
michael@0 184 }
michael@0 185 /// todo some specific errors - like for message too large
michael@0 186 return rv;
michael@0 187 }
michael@0 188
michael@0 189
michael@0 190 void
michael@0 191 WebSocket::FailConnection(uint16_t aReasonCode,
michael@0 192 const nsACString& aReasonString)
michael@0 193 {
michael@0 194 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 195
michael@0 196 ConsoleError();
michael@0 197 mFailed = true;
michael@0 198 CloseConnection(aReasonCode, aReasonString);
michael@0 199 }
michael@0 200
michael@0 201 nsresult
michael@0 202 WebSocket::Disconnect()
michael@0 203 {
michael@0 204 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 205
michael@0 206 if (mDisconnected)
michael@0 207 return NS_OK;
michael@0 208
michael@0 209 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 210 GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 211 if (loadGroup)
michael@0 212 loadGroup->RemoveRequest(this, nullptr, NS_OK);
michael@0 213
michael@0 214 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
michael@0 215 if (os) {
michael@0 216 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
michael@0 217 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
michael@0 218 }
michael@0 219
michael@0 220 // DontKeepAliveAnyMore() can release the object. So hold a reference to this
michael@0 221 // until the end of the method.
michael@0 222 nsRefPtr<WebSocket> kungfuDeathGrip = this;
michael@0 223
michael@0 224 DontKeepAliveAnyMore();
michael@0 225 mChannel = nullptr;
michael@0 226 mDisconnected = true;
michael@0 227
michael@0 228 return NS_OK;
michael@0 229 }
michael@0 230
michael@0 231 //-----------------------------------------------------------------------------
michael@0 232 // WebSocket::nsIWebSocketListener methods:
michael@0 233 //-----------------------------------------------------------------------------
michael@0 234
michael@0 235 nsresult
michael@0 236 WebSocket::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
michael@0 237 {
michael@0 238 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 239
michael@0 240 if (mReadyState == WebSocket::CLOSED) {
michael@0 241 NS_ERROR("Received message after CLOSED");
michael@0 242 return NS_ERROR_UNEXPECTED;
michael@0 243 }
michael@0 244
michael@0 245 if (mReadyState == WebSocket::OPEN) {
michael@0 246 // Dispatch New Message
michael@0 247 nsresult rv = CreateAndDispatchMessageEvent(aMsg, isBinary);
michael@0 248 if (NS_FAILED(rv)) {
michael@0 249 NS_WARNING("Failed to dispatch the message event");
michael@0 250 }
michael@0 251 } else {
michael@0 252 // CLOSING should be the only other state where it's possible to get msgs
michael@0 253 // from channel: Spec says to drop them.
michael@0 254 MOZ_ASSERT(mReadyState == WebSocket::CLOSING,
michael@0 255 "Received message while CONNECTING or CLOSED");
michael@0 256 }
michael@0 257
michael@0 258 return NS_OK;
michael@0 259 }
michael@0 260
michael@0 261 NS_IMETHODIMP
michael@0 262 WebSocket::OnMessageAvailable(nsISupports* aContext, const nsACString& aMsg)
michael@0 263 {
michael@0 264 return DoOnMessageAvailable(aMsg, false);
michael@0 265 }
michael@0 266
michael@0 267 NS_IMETHODIMP
michael@0 268 WebSocket::OnBinaryMessageAvailable(nsISupports* aContext,
michael@0 269 const nsACString& aMsg)
michael@0 270 {
michael@0 271 return DoOnMessageAvailable(aMsg, true);
michael@0 272 }
michael@0 273
michael@0 274 NS_IMETHODIMP
michael@0 275 WebSocket::OnStart(nsISupports* aContext)
michael@0 276 {
michael@0 277 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 278
michael@0 279 // This is the only function that sets OPEN, and should be called only once
michael@0 280 MOZ_ASSERT(mReadyState != WebSocket::OPEN,
michael@0 281 "readyState already OPEN! OnStart called twice?");
michael@0 282
michael@0 283 // Nothing to do if we've already closed/closing
michael@0 284 if (mReadyState != WebSocket::CONNECTING) {
michael@0 285 return NS_OK;
michael@0 286 }
michael@0 287
michael@0 288 // Attempt to kill "ghost" websocket: but usually too early for check to fail
michael@0 289 nsresult rv = CheckInnerWindowCorrectness();
michael@0 290 if (NS_FAILED(rv)) {
michael@0 291 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
michael@0 292 return rv;
michael@0 293 }
michael@0 294
michael@0 295 if (!mRequestedProtocolList.IsEmpty()) {
michael@0 296 mChannel->GetProtocol(mEstablishedProtocol);
michael@0 297 }
michael@0 298
michael@0 299 mChannel->GetExtensions(mEstablishedExtensions);
michael@0 300 UpdateURI();
michael@0 301
michael@0 302 mReadyState = WebSocket::OPEN;
michael@0 303
michael@0 304 // Call 'onopen'
michael@0 305 rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
michael@0 306 if (NS_FAILED(rv)) {
michael@0 307 NS_WARNING("Failed to dispatch the open event");
michael@0 308 }
michael@0 309
michael@0 310 UpdateMustKeepAlive();
michael@0 311
michael@0 312 return NS_OK;
michael@0 313 }
michael@0 314
michael@0 315 NS_IMETHODIMP
michael@0 316 WebSocket::OnStop(nsISupports* aContext, nsresult aStatusCode)
michael@0 317 {
michael@0 318 // We can be CONNECTING here if connection failed.
michael@0 319 // We can be OPEN if we have encountered a fatal protocol error
michael@0 320 // We can be CLOSING if close() was called and/or server initiated close.
michael@0 321 MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
michael@0 322 "Shouldn't already be CLOSED when OnStop called");
michael@0 323
michael@0 324 // called by network stack, not JS, so can dispatch JS events synchronously
michael@0 325 return ScheduleConnectionCloseEvents(aContext, aStatusCode, true);
michael@0 326 }
michael@0 327
michael@0 328 nsresult
michael@0 329 WebSocket::ScheduleConnectionCloseEvents(nsISupports* aContext,
michael@0 330 nsresult aStatusCode,
michael@0 331 bool sync)
michael@0 332 {
michael@0 333 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 334
michael@0 335 // no-op if some other code has already initiated close event
michael@0 336 if (!mOnCloseScheduled) {
michael@0 337 mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
michael@0 338
michael@0 339 if (aStatusCode == NS_BASE_STREAM_CLOSED) {
michael@0 340 // don't generate an error event just because of an unclean close
michael@0 341 aStatusCode = NS_OK;
michael@0 342 }
michael@0 343
michael@0 344 if (NS_FAILED(aStatusCode)) {
michael@0 345 ConsoleError();
michael@0 346 mFailed = true;
michael@0 347 }
michael@0 348
michael@0 349 mOnCloseScheduled = true;
michael@0 350
michael@0 351 if (sync) {
michael@0 352 DispatchConnectionCloseEvents();
michael@0 353 } else {
michael@0 354 NS_DispatchToMainThread(new CallDispatchConnectionCloseEvents(this),
michael@0 355 NS_DISPATCH_NORMAL);
michael@0 356 }
michael@0 357 }
michael@0 358
michael@0 359 return NS_OK;
michael@0 360 }
michael@0 361
michael@0 362 NS_IMETHODIMP
michael@0 363 WebSocket::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
michael@0 364 {
michael@0 365 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 366
michael@0 367 if (aSize > mOutgoingBufferedAmount)
michael@0 368 return NS_ERROR_UNEXPECTED;
michael@0 369
michael@0 370 mOutgoingBufferedAmount -= aSize;
michael@0 371 return NS_OK;
michael@0 372 }
michael@0 373
michael@0 374 NS_IMETHODIMP
michael@0 375 WebSocket::OnServerClose(nsISupports *aContext, uint16_t aCode,
michael@0 376 const nsACString &aReason)
michael@0 377 {
michael@0 378 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 379
michael@0 380 MOZ_ASSERT(mReadyState != WebSocket::CONNECTING,
michael@0 381 "Received server close before connected?");
michael@0 382 MOZ_ASSERT(mReadyState != WebSocket::CLOSED,
michael@0 383 "Received server close after already closed!");
michael@0 384
michael@0 385 // store code/string for onclose DOM event
michael@0 386 mCloseEventCode = aCode;
michael@0 387 CopyUTF8toUTF16(aReason, mCloseEventReason);
michael@0 388
michael@0 389 if (mReadyState == WebSocket::OPEN) {
michael@0 390 // Server initiating close.
michael@0 391 // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
michael@0 392 // typically echos the status code it received".
michael@0 393 // But never send certain codes, per section 7.4.1
michael@0 394 if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
michael@0 395 CloseConnection(0, EmptyCString());
michael@0 396 } else {
michael@0 397 CloseConnection(aCode, aReason);
michael@0 398 }
michael@0 399 } else {
michael@0 400 // We initiated close, and server has replied: OnStop does rest of the work.
michael@0 401 MOZ_ASSERT(mReadyState == WebSocket::CLOSING, "unknown state");
michael@0 402 }
michael@0 403
michael@0 404 return NS_OK;
michael@0 405 }
michael@0 406
michael@0 407 //-----------------------------------------------------------------------------
michael@0 408 // WebSocket::nsIInterfaceRequestor
michael@0 409 //-----------------------------------------------------------------------------
michael@0 410
michael@0 411 NS_IMETHODIMP
michael@0 412 WebSocket::GetInterface(const nsIID& aIID, void** aResult)
michael@0 413 {
michael@0 414 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 415
michael@0 416 if (mReadyState == WebSocket::CLOSED)
michael@0 417 return NS_ERROR_FAILURE;
michael@0 418
michael@0 419 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
michael@0 420 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
michael@0 421 nsresult rv;
michael@0 422 nsIScriptContext* sc = GetContextForEventHandlers(&rv);
michael@0 423 nsCOMPtr<nsIDocument> doc =
michael@0 424 nsContentUtils::GetDocumentFromScriptContext(sc);
michael@0 425 if (!doc)
michael@0 426 return NS_ERROR_NOT_AVAILABLE;
michael@0 427
michael@0 428 nsCOMPtr<nsIPromptFactory> wwatch =
michael@0 429 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
michael@0 430 NS_ENSURE_SUCCESS(rv, rv);
michael@0 431
michael@0 432 nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow();
michael@0 433 return wwatch->GetPrompt(outerWindow, aIID, aResult);
michael@0 434 }
michael@0 435
michael@0 436 return QueryInterface(aIID, aResult);
michael@0 437 }
michael@0 438
michael@0 439 ////////////////////////////////////////////////////////////////////////////////
michael@0 440 // WebSocket
michael@0 441 ////////////////////////////////////////////////////////////////////////////////
michael@0 442
michael@0 443 WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
michael@0 444 : DOMEventTargetHelper(aOwnerWindow),
michael@0 445 mKeepingAlive(false),
michael@0 446 mCheckMustKeepAlive(true),
michael@0 447 mOnCloseScheduled(false),
michael@0 448 mFailed(false),
michael@0 449 mDisconnected(false),
michael@0 450 mCloseEventWasClean(false),
michael@0 451 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
michael@0 452 mReadyState(WebSocket::CONNECTING),
michael@0 453 mOutgoingBufferedAmount(0),
michael@0 454 mBinaryType(dom::BinaryType::Blob),
michael@0 455 mScriptLine(0),
michael@0 456 mInnerWindowID(0)
michael@0 457 {
michael@0 458 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 459 MOZ_ASSERT(aOwnerWindow);
michael@0 460 MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
michael@0 461 }
michael@0 462
michael@0 463 WebSocket::~WebSocket()
michael@0 464 {
michael@0 465 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 466
michael@0 467 // If we threw during Init we never called disconnect
michael@0 468 if (!mDisconnected) {
michael@0 469 Disconnect();
michael@0 470 }
michael@0 471 }
michael@0 472
michael@0 473 JSObject*
michael@0 474 WebSocket::WrapObject(JSContext* cx)
michael@0 475 {
michael@0 476 return WebSocketBinding::Wrap(cx, this);
michael@0 477 }
michael@0 478
michael@0 479 //---------------------------------------------------------------------------
michael@0 480 // WebIDL
michael@0 481 //---------------------------------------------------------------------------
michael@0 482
michael@0 483 // Constructor:
michael@0 484 already_AddRefed<WebSocket>
michael@0 485 WebSocket::Constructor(const GlobalObject& aGlobal,
michael@0 486 const nsAString& aUrl,
michael@0 487 ErrorResult& aRv)
michael@0 488 {
michael@0 489 Sequence<nsString> protocols;
michael@0 490 return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
michael@0 491 }
michael@0 492
michael@0 493 already_AddRefed<WebSocket>
michael@0 494 WebSocket::Constructor(const GlobalObject& aGlobal,
michael@0 495 const nsAString& aUrl,
michael@0 496 const nsAString& aProtocol,
michael@0 497 ErrorResult& aRv)
michael@0 498 {
michael@0 499 Sequence<nsString> protocols;
michael@0 500 protocols.AppendElement(aProtocol);
michael@0 501 return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
michael@0 502 }
michael@0 503
michael@0 504 already_AddRefed<WebSocket>
michael@0 505 WebSocket::Constructor(const GlobalObject& aGlobal,
michael@0 506 const nsAString& aUrl,
michael@0 507 const Sequence<nsString>& aProtocols,
michael@0 508 ErrorResult& aRv)
michael@0 509 {
michael@0 510 if (!PrefEnabled()) {
michael@0 511 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
michael@0 512 return nullptr;
michael@0 513 }
michael@0 514
michael@0 515 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
michael@0 516 do_QueryInterface(aGlobal.GetAsSupports());
michael@0 517 if (!scriptPrincipal) {
michael@0 518 aRv.Throw(NS_ERROR_FAILURE);
michael@0 519 return nullptr;
michael@0 520 }
michael@0 521
michael@0 522 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
michael@0 523 if (!principal) {
michael@0 524 aRv.Throw(NS_ERROR_FAILURE);
michael@0 525 return nullptr;
michael@0 526 }
michael@0 527
michael@0 528 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.GetAsSupports());
michael@0 529 if (!sgo) {
michael@0 530 aRv.Throw(NS_ERROR_FAILURE);
michael@0 531 return nullptr;
michael@0 532 }
michael@0 533
michael@0 534 nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
michael@0 535 if (!ownerWindow) {
michael@0 536 aRv.Throw(NS_ERROR_FAILURE);
michael@0 537 return nullptr;
michael@0 538 }
michael@0 539
michael@0 540 nsTArray<nsString> protocolArray;
michael@0 541
michael@0 542 for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
michael@0 543
michael@0 544 const nsString& protocolElement = aProtocols[index];
michael@0 545
michael@0 546 if (protocolElement.IsEmpty()) {
michael@0 547 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
michael@0 548 return nullptr;
michael@0 549 }
michael@0 550 if (protocolArray.Contains(protocolElement)) {
michael@0 551 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
michael@0 552 return nullptr;
michael@0 553 }
michael@0 554 if (protocolElement.FindChar(',') != -1) /* interferes w/list */ {
michael@0 555 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
michael@0 556 return nullptr;
michael@0 557 }
michael@0 558
michael@0 559 protocolArray.AppendElement(protocolElement);
michael@0 560 }
michael@0 561
michael@0 562 nsRefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
michael@0 563 nsresult rv = webSocket->Init(aGlobal.GetContext(), principal,
michael@0 564 aUrl, protocolArray);
michael@0 565 if (NS_FAILED(rv)) {
michael@0 566 aRv.Throw(rv);
michael@0 567 return nullptr;
michael@0 568 }
michael@0 569
michael@0 570 return webSocket.forget();
michael@0 571 }
michael@0 572
michael@0 573 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
michael@0 574
michael@0 575 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
michael@0 576 bool isBlack = tmp->IsBlack();
michael@0 577 if (isBlack|| tmp->mKeepingAlive) {
michael@0 578 if (tmp->mListenerManager) {
michael@0 579 tmp->mListenerManager->MarkForCC();
michael@0 580 }
michael@0 581 if (!isBlack && tmp->PreservingWrapper()) {
michael@0 582 // This marks the wrapper black.
michael@0 583 tmp->GetWrapper();
michael@0 584 }
michael@0 585 return true;
michael@0 586 }
michael@0 587 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
michael@0 588
michael@0 589 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket)
michael@0 590 return tmp->IsBlack();
michael@0 591 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
michael@0 592
michael@0 593 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket)
michael@0 594 return tmp->IsBlack();
michael@0 595 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
michael@0 596
michael@0 597 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
michael@0 598 DOMEventTargetHelper)
michael@0 599 NS_IMPL_CYCLE_COLLECTION_TRACE_END
michael@0 600
michael@0 601 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
michael@0 602 DOMEventTargetHelper)
michael@0 603 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
michael@0 604 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mURI)
michael@0 605 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChannel)
michael@0 606 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 607
michael@0 608 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
michael@0 609 DOMEventTargetHelper)
michael@0 610 tmp->Disconnect();
michael@0 611 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrincipal)
michael@0 612 NS_IMPL_CYCLE_COLLECTION_UNLINK(mURI)
michael@0 613 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannel)
michael@0 614 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
michael@0 615
michael@0 616 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
michael@0 617 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
michael@0 618 NS_INTERFACE_MAP_ENTRY(nsIWebSocketListener)
michael@0 619 NS_INTERFACE_MAP_ENTRY(nsIObserver)
michael@0 620 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
michael@0 621 NS_INTERFACE_MAP_ENTRY(nsIRequest)
michael@0 622 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
michael@0 623
michael@0 624 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
michael@0 625 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
michael@0 626
michael@0 627 void
michael@0 628 WebSocket::DisconnectFromOwner()
michael@0 629 {
michael@0 630 DOMEventTargetHelper::DisconnectFromOwner();
michael@0 631 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
michael@0 632 DontKeepAliveAnyMore();
michael@0 633 }
michael@0 634
michael@0 635 //-----------------------------------------------------------------------------
michael@0 636 // WebSocket:: initialization
michael@0 637 //-----------------------------------------------------------------------------
michael@0 638
michael@0 639 nsresult
michael@0 640 WebSocket::Init(JSContext* aCx,
michael@0 641 nsIPrincipal* aPrincipal,
michael@0 642 const nsAString& aURL,
michael@0 643 nsTArray<nsString>& aProtocolArray)
michael@0 644 {
michael@0 645 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 646 MOZ_ASSERT(aPrincipal);
michael@0 647
michael@0 648 if (!PrefEnabled()) {
michael@0 649 return NS_ERROR_DOM_SECURITY_ERR;
michael@0 650 }
michael@0 651
michael@0 652 mPrincipal = aPrincipal;
michael@0 653
michael@0 654 // Attempt to kill "ghost" websocket: but usually too early for check to fail
michael@0 655 nsresult rv = CheckInnerWindowCorrectness();
michael@0 656 NS_ENSURE_SUCCESS(rv, rv);
michael@0 657
michael@0 658 // Shut down websocket if window is frozen or destroyed (only needed for
michael@0 659 // "ghost" websockets--see bug 696085)
michael@0 660 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
michael@0 661 NS_ENSURE_STATE(os);
michael@0 662 rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
michael@0 663 NS_ENSURE_SUCCESS(rv, rv);
michael@0 664 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
michael@0 665 NS_ENSURE_SUCCESS(rv, rv);
michael@0 666
michael@0 667 unsigned lineno;
michael@0 668 JS::AutoFilename file;
michael@0 669 if (JS::DescribeScriptedCaller(aCx, &file, &lineno)) {
michael@0 670 mScriptFile = file.get();
michael@0 671 mScriptLine = lineno;
michael@0 672 }
michael@0 673
michael@0 674 // Get WindowID
michael@0 675 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
michael@0 676
michael@0 677 // parses the url
michael@0 678 rv = ParseURL(PromiseFlatString(aURL));
michael@0 679 NS_ENSURE_SUCCESS(rv, rv);
michael@0 680
michael@0 681 nsIScriptContext* sc = GetContextForEventHandlers(&rv);
michael@0 682 NS_ENSURE_SUCCESS(rv, rv);
michael@0 683
michael@0 684 nsCOMPtr<nsIDocument> originDoc = nsContentUtils::GetDocumentFromScriptContext(sc);
michael@0 685
michael@0 686 // Don't allow https:// to open ws://
michael@0 687 if (!mSecure &&
michael@0 688 !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
michael@0 689 false)) {
michael@0 690 // Confirmed we are opening plain ws:// and want to prevent this from a
michael@0 691 // secure context (e.g. https). Check the security context of the document
michael@0 692 // associated with this script, which is the same as associated with mOwner.
michael@0 693 if (originDoc && originDoc->GetSecurityInfo()) {
michael@0 694 return NS_ERROR_DOM_SECURITY_ERR;
michael@0 695 }
michael@0 696 }
michael@0 697
michael@0 698 // Assign the sub protocol list and scan it for illegal values
michael@0 699 for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
michael@0 700 for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
michael@0 701 if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
michael@0 702 aProtocolArray[index][i] > static_cast<char16_t>(0x007E))
michael@0 703 return NS_ERROR_DOM_SYNTAX_ERR;
michael@0 704 }
michael@0 705
michael@0 706 if (!mRequestedProtocolList.IsEmpty()) {
michael@0 707 mRequestedProtocolList.Append(NS_LITERAL_CSTRING(", "));
michael@0 708 }
michael@0 709
michael@0 710 AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
michael@0 711 }
michael@0 712
michael@0 713 // Check content policy.
michael@0 714 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
michael@0 715 rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
michael@0 716 mURI,
michael@0 717 mPrincipal,
michael@0 718 originDoc,
michael@0 719 EmptyCString(),
michael@0 720 nullptr,
michael@0 721 &shouldLoad,
michael@0 722 nsContentUtils::GetContentPolicy(),
michael@0 723 nsContentUtils::GetSecurityManager());
michael@0 724 NS_ENSURE_SUCCESS(rv, rv);
michael@0 725 if (NS_CP_REJECTED(shouldLoad)) {
michael@0 726 // Disallowed by content policy.
michael@0 727 return NS_ERROR_CONTENT_BLOCKED;
michael@0 728 }
michael@0 729
michael@0 730 // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
michael@0 731 // url parameter, so don't throw if EstablishConnection fails, and call
michael@0 732 // onerror/onclose asynchronously
michael@0 733 if (NS_FAILED(EstablishConnection())) {
michael@0 734 FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
michael@0 735 }
michael@0 736
michael@0 737 return NS_OK;
michael@0 738 }
michael@0 739
michael@0 740 //-----------------------------------------------------------------------------
michael@0 741 // WebSocket methods:
michael@0 742 //-----------------------------------------------------------------------------
michael@0 743
michael@0 744 class nsAutoCloseWS
michael@0 745 {
michael@0 746 public:
michael@0 747 nsAutoCloseWS(WebSocket* aWebSocket)
michael@0 748 : mWebSocket(aWebSocket)
michael@0 749 {}
michael@0 750
michael@0 751 ~nsAutoCloseWS()
michael@0 752 {
michael@0 753 if (!mWebSocket->mChannel) {
michael@0 754 mWebSocket->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
michael@0 755 }
michael@0 756 }
michael@0 757 private:
michael@0 758 nsRefPtr<WebSocket> mWebSocket;
michael@0 759 };
michael@0 760
michael@0 761 nsresult
michael@0 762 WebSocket::EstablishConnection()
michael@0 763 {
michael@0 764 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 765 NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null");
michael@0 766
michael@0 767 nsCOMPtr<nsIWebSocketChannel> wsChannel;
michael@0 768 nsAutoCloseWS autoClose(this);
michael@0 769 nsresult rv;
michael@0 770
michael@0 771 if (mSecure) {
michael@0 772 wsChannel =
michael@0 773 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
michael@0 774 } else {
michael@0 775 wsChannel =
michael@0 776 do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
michael@0 777 }
michael@0 778 NS_ENSURE_SUCCESS(rv, rv);
michael@0 779
michael@0 780 rv = wsChannel->SetNotificationCallbacks(this);
michael@0 781 NS_ENSURE_SUCCESS(rv, rv);
michael@0 782
michael@0 783 // add ourselves to the document's load group and
michael@0 784 // provide the http stack the loadgroup info too
michael@0 785 nsCOMPtr<nsILoadGroup> loadGroup;
michael@0 786 rv = GetLoadGroup(getter_AddRefs(loadGroup));
michael@0 787 if (loadGroup) {
michael@0 788 rv = wsChannel->SetLoadGroup(loadGroup);
michael@0 789 NS_ENSURE_SUCCESS(rv, rv);
michael@0 790 rv = loadGroup->AddRequest(this, nullptr);
michael@0 791 NS_ENSURE_SUCCESS(rv, rv);
michael@0 792 }
michael@0 793
michael@0 794 if (!mRequestedProtocolList.IsEmpty()) {
michael@0 795 rv = wsChannel->SetProtocol(mRequestedProtocolList);
michael@0 796 NS_ENSURE_SUCCESS(rv, rv);
michael@0 797 }
michael@0 798
michael@0 799 nsCString asciiOrigin;
michael@0 800 rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin);
michael@0 801 NS_ENSURE_SUCCESS(rv, rv);
michael@0 802
michael@0 803 ToLowerCase(asciiOrigin);
michael@0 804
michael@0 805 rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nullptr);
michael@0 806 NS_ENSURE_SUCCESS(rv, rv);
michael@0 807
michael@0 808 mChannel = wsChannel;
michael@0 809
michael@0 810 return NS_OK;
michael@0 811 }
michael@0 812
michael@0 813 void
michael@0 814 WebSocket::DispatchConnectionCloseEvents()
michael@0 815 {
michael@0 816 mReadyState = WebSocket::CLOSED;
michael@0 817
michael@0 818 // Call 'onerror' if needed
michael@0 819 if (mFailed) {
michael@0 820 nsresult rv = CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
michael@0 821 if (NS_FAILED(rv)) {
michael@0 822 NS_WARNING("Failed to dispatch the error event");
michael@0 823 }
michael@0 824 }
michael@0 825
michael@0 826 nsresult rv = CreateAndDispatchCloseEvent(mCloseEventWasClean, mCloseEventCode,
michael@0 827 mCloseEventReason);
michael@0 828 if (NS_FAILED(rv)) {
michael@0 829 NS_WARNING("Failed to dispatch the close event");
michael@0 830 }
michael@0 831
michael@0 832 UpdateMustKeepAlive();
michael@0 833 Disconnect();
michael@0 834 }
michael@0 835
michael@0 836 nsresult
michael@0 837 WebSocket::CreateAndDispatchSimpleEvent(const nsString& aName)
michael@0 838 {
michael@0 839 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 840
michael@0 841 nsresult rv = CheckInnerWindowCorrectness();
michael@0 842 if (NS_FAILED(rv)) {
michael@0 843 return NS_OK;
michael@0 844 }
michael@0 845
michael@0 846 nsCOMPtr<nsIDOMEvent> event;
michael@0 847 rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr);
michael@0 848 NS_ENSURE_SUCCESS(rv, rv);
michael@0 849
michael@0 850 // it doesn't bubble, and it isn't cancelable
michael@0 851 rv = event->InitEvent(aName, false, false);
michael@0 852 NS_ENSURE_SUCCESS(rv, rv);
michael@0 853
michael@0 854 event->SetTrusted(true);
michael@0 855
michael@0 856 return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
michael@0 857 }
michael@0 858
michael@0 859 nsresult
michael@0 860 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
michael@0 861 bool isBinary)
michael@0 862 {
michael@0 863 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 864
michael@0 865 nsresult rv = CheckInnerWindowCorrectness();
michael@0 866 if (NS_FAILED(rv))
michael@0 867 return NS_OK;
michael@0 868
michael@0 869 // Get the JSContext
michael@0 870 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(GetOwner());
michael@0 871 NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
michael@0 872
michael@0 873 nsIScriptContext* scriptContext = sgo->GetContext();
michael@0 874 NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE);
michael@0 875
michael@0 876 AutoPushJSContext cx(scriptContext->GetNativeContext());
michael@0 877 NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
michael@0 878
michael@0 879 // Create appropriate JS object for message
michael@0 880 JS::Rooted<JS::Value> jsData(cx);
michael@0 881 if (isBinary) {
michael@0 882 if (mBinaryType == dom::BinaryType::Blob) {
michael@0 883 rv = nsContentUtils::CreateBlobBuffer(cx, aData, &jsData);
michael@0 884 NS_ENSURE_SUCCESS(rv, rv);
michael@0 885 } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
michael@0 886 JS::Rooted<JSObject*> arrayBuf(cx);
michael@0 887 rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
michael@0 888 NS_ENSURE_SUCCESS(rv, rv);
michael@0 889 jsData = OBJECT_TO_JSVAL(arrayBuf);
michael@0 890 } else {
michael@0 891 NS_RUNTIMEABORT("Unknown binary type!");
michael@0 892 return NS_ERROR_UNEXPECTED;
michael@0 893 }
michael@0 894 } else {
michael@0 895 // JS string
michael@0 896 NS_ConvertUTF8toUTF16 utf16Data(aData);
michael@0 897 JSString* jsString;
michael@0 898 jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
michael@0 899 NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
michael@0 900
michael@0 901 jsData = STRING_TO_JSVAL(jsString);
michael@0 902 }
michael@0 903
michael@0 904 // create an event that uses the MessageEvent interface,
michael@0 905 // which does not bubble, is not cancelable, and has no default action
michael@0 906
michael@0 907 nsCOMPtr<nsIDOMEvent> event;
michael@0 908 rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr);
michael@0 909 NS_ENSURE_SUCCESS(rv, rv);
michael@0 910
michael@0 911 nsCOMPtr<nsIDOMMessageEvent> messageEvent = do_QueryInterface(event);
michael@0 912 rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"),
michael@0 913 false, false,
michael@0 914 jsData,
michael@0 915 mUTF16Origin,
michael@0 916 EmptyString(), nullptr);
michael@0 917 NS_ENSURE_SUCCESS(rv, rv);
michael@0 918
michael@0 919 event->SetTrusted(true);
michael@0 920
michael@0 921 return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
michael@0 922 }
michael@0 923
michael@0 924 nsresult
michael@0 925 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
michael@0 926 uint16_t aCode,
michael@0 927 const nsString &aReason)
michael@0 928 {
michael@0 929 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 930
michael@0 931 nsresult rv = CheckInnerWindowCorrectness();
michael@0 932 if (NS_FAILED(rv)) {
michael@0 933 return NS_OK;
michael@0 934 }
michael@0 935
michael@0 936 // create an event that uses the CloseEvent interface,
michael@0 937 // which does not bubble, is not cancelable, and has no default action
michael@0 938
michael@0 939 nsCOMPtr<nsIDOMEvent> event;
michael@0 940 rv = NS_NewDOMCloseEvent(getter_AddRefs(event), this, nullptr, nullptr);
michael@0 941 NS_ENSURE_SUCCESS(rv, rv);
michael@0 942
michael@0 943 nsCOMPtr<nsIDOMCloseEvent> closeEvent = do_QueryInterface(event);
michael@0 944 rv = closeEvent->InitCloseEvent(NS_LITERAL_STRING("close"),
michael@0 945 false, false,
michael@0 946 aWasClean, aCode, aReason);
michael@0 947 NS_ENSURE_SUCCESS(rv, rv);
michael@0 948
michael@0 949 event->SetTrusted(true);
michael@0 950
michael@0 951 return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
michael@0 952 }
michael@0 953
michael@0 954 bool
michael@0 955 WebSocket::PrefEnabled(JSContext* aCx, JSObject* aGlobal)
michael@0 956 {
michael@0 957 return Preferences::GetBool("network.websocket.enabled", true);
michael@0 958 }
michael@0 959
michael@0 960 nsresult
michael@0 961 WebSocket::ParseURL(const nsString& aURL)
michael@0 962 {
michael@0 963 NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
michael@0 964
michael@0 965 nsCOMPtr<nsIURI> uri;
michael@0 966 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
michael@0 967 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 968
michael@0 969 nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
michael@0 970 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 971
michael@0 972 nsAutoCString fragment;
michael@0 973 rv = parsedURL->GetRef(fragment);
michael@0 974 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && fragment.IsEmpty(),
michael@0 975 NS_ERROR_DOM_SYNTAX_ERR);
michael@0 976
michael@0 977 nsAutoCString scheme;
michael@0 978 rv = parsedURL->GetScheme(scheme);
michael@0 979 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
michael@0 980 NS_ERROR_DOM_SYNTAX_ERR);
michael@0 981
michael@0 982 nsAutoCString host;
michael@0 983 rv = parsedURL->GetAsciiHost(host);
michael@0 984 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
michael@0 985
michael@0 986 int32_t port;
michael@0 987 rv = parsedURL->GetPort(&port);
michael@0 988 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 989
michael@0 990 rv = NS_CheckPortSafety(port, scheme.get());
michael@0 991 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 992
michael@0 993 nsAutoCString filePath;
michael@0 994 rv = parsedURL->GetFilePath(filePath);
michael@0 995 if (filePath.IsEmpty()) {
michael@0 996 filePath.AssignLiteral("/");
michael@0 997 }
michael@0 998 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 999
michael@0 1000 nsAutoCString query;
michael@0 1001 rv = parsedURL->GetQuery(query);
michael@0 1002 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 1003
michael@0 1004 if (scheme.LowerCaseEqualsLiteral("ws")) {
michael@0 1005 mSecure = false;
michael@0 1006 mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
michael@0 1007 } else if (scheme.LowerCaseEqualsLiteral("wss")) {
michael@0 1008 mSecure = true;
michael@0 1009 mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
michael@0 1010 } else {
michael@0 1011 return NS_ERROR_DOM_SYNTAX_ERR;
michael@0 1012 }
michael@0 1013
michael@0 1014 rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
michael@0 1015 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
michael@0 1016
michael@0 1017 mAsciiHost = host;
michael@0 1018 ToLowerCase(mAsciiHost);
michael@0 1019
michael@0 1020 mResource = filePath;
michael@0 1021 if (!query.IsEmpty()) {
michael@0 1022 mResource.AppendLiteral("?");
michael@0 1023 mResource.Append(query);
michael@0 1024 }
michael@0 1025 uint32_t length = mResource.Length();
michael@0 1026 uint32_t i;
michael@0 1027 for (i = 0; i < length; ++i) {
michael@0 1028 if (mResource[i] < static_cast<char16_t>(0x0021) ||
michael@0 1029 mResource[i] > static_cast<char16_t>(0x007E)) {
michael@0 1030 return NS_ERROR_DOM_SYNTAX_ERR;
michael@0 1031 }
michael@0 1032 }
michael@0 1033
michael@0 1034 mOriginalURL = aURL;
michael@0 1035 mURI = parsedURL;
michael@0 1036 return NS_OK;
michael@0 1037 }
michael@0 1038
michael@0 1039 //-----------------------------------------------------------------------------
michael@0 1040 // Methods that keep alive the WebSocket object when:
michael@0 1041 // 1. the object has registered event listeners that can be triggered
michael@0 1042 // ("strong event listeners");
michael@0 1043 // 2. there are outgoing not sent messages.
michael@0 1044 //-----------------------------------------------------------------------------
michael@0 1045
michael@0 1046 void
michael@0 1047 WebSocket::UpdateMustKeepAlive()
michael@0 1048 {
michael@0 1049 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1050 if (!mCheckMustKeepAlive) {
michael@0 1051 return;
michael@0 1052 }
michael@0 1053
michael@0 1054 bool shouldKeepAlive = false;
michael@0 1055
michael@0 1056 if (mListenerManager) {
michael@0 1057 switch (mReadyState)
michael@0 1058 {
michael@0 1059 case WebSocket::CONNECTING:
michael@0 1060 {
michael@0 1061 if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
michael@0 1062 mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
michael@0 1063 mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
michael@0 1064 mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
michael@0 1065 shouldKeepAlive = true;
michael@0 1066 }
michael@0 1067 }
michael@0 1068 break;
michael@0 1069
michael@0 1070 case WebSocket::OPEN:
michael@0 1071 case WebSocket::CLOSING:
michael@0 1072 {
michael@0 1073 if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
michael@0 1074 mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
michael@0 1075 mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
michael@0 1076 mOutgoingBufferedAmount != 0) {
michael@0 1077 shouldKeepAlive = true;
michael@0 1078 }
michael@0 1079 }
michael@0 1080 break;
michael@0 1081
michael@0 1082 case WebSocket::CLOSED:
michael@0 1083 {
michael@0 1084 shouldKeepAlive = false;
michael@0 1085 }
michael@0 1086 }
michael@0 1087 }
michael@0 1088
michael@0 1089 if (mKeepingAlive && !shouldKeepAlive) {
michael@0 1090 mKeepingAlive = false;
michael@0 1091 static_cast<EventTarget*>(this)->Release();
michael@0 1092 } else if (!mKeepingAlive && shouldKeepAlive) {
michael@0 1093 mKeepingAlive = true;
michael@0 1094 static_cast<EventTarget*>(this)->AddRef();
michael@0 1095 }
michael@0 1096 }
michael@0 1097
michael@0 1098 void
michael@0 1099 WebSocket::DontKeepAliveAnyMore()
michael@0 1100 {
michael@0 1101 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1102 if (mKeepingAlive) {
michael@0 1103 mKeepingAlive = false;
michael@0 1104 static_cast<EventTarget*>(this)->Release();
michael@0 1105 }
michael@0 1106 mCheckMustKeepAlive = false;
michael@0 1107 }
michael@0 1108
michael@0 1109 nsresult
michael@0 1110 WebSocket::UpdateURI()
michael@0 1111 {
michael@0 1112 // Check for Redirections
michael@0 1113 nsCOMPtr<nsIURI> uri;
michael@0 1114 nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
michael@0 1115 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1116
michael@0 1117 nsAutoCString spec;
michael@0 1118 rv = uri->GetSpec(spec);
michael@0 1119 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1120 CopyUTF8toUTF16(spec, mEffectiveURL);
michael@0 1121
michael@0 1122 bool isWSS = false;
michael@0 1123 rv = uri->SchemeIs("wss", &isWSS);
michael@0 1124 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1125 mSecure = isWSS ? true : false;
michael@0 1126
michael@0 1127 return NS_OK;
michael@0 1128 }
michael@0 1129
michael@0 1130 void
michael@0 1131 WebSocket::EventListenerAdded(nsIAtom* aType)
michael@0 1132 {
michael@0 1133 UpdateMustKeepAlive();
michael@0 1134 }
michael@0 1135
michael@0 1136 void
michael@0 1137 WebSocket::EventListenerRemoved(nsIAtom* aType)
michael@0 1138 {
michael@0 1139 UpdateMustKeepAlive();
michael@0 1140 }
michael@0 1141
michael@0 1142 //-----------------------------------------------------------------------------
michael@0 1143 // WebSocket - methods
michael@0 1144 //-----------------------------------------------------------------------------
michael@0 1145
michael@0 1146 // webIDL: readonly attribute DOMString url
michael@0 1147 void
michael@0 1148 WebSocket::GetUrl(nsAString& aURL)
michael@0 1149 {
michael@0 1150 if (mEffectiveURL.IsEmpty()) {
michael@0 1151 aURL = mOriginalURL;
michael@0 1152 } else {
michael@0 1153 aURL = mEffectiveURL;
michael@0 1154 }
michael@0 1155 }
michael@0 1156
michael@0 1157 // webIDL: readonly attribute DOMString extensions;
michael@0 1158 void
michael@0 1159 WebSocket::GetExtensions(nsAString& aExtensions)
michael@0 1160 {
michael@0 1161 CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
michael@0 1162 }
michael@0 1163
michael@0 1164 // webIDL: readonly attribute DOMString protocol;
michael@0 1165 void
michael@0 1166 WebSocket::GetProtocol(nsAString& aProtocol)
michael@0 1167 {
michael@0 1168 CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
michael@0 1169 }
michael@0 1170
michael@0 1171 // webIDL: void send(DOMString data);
michael@0 1172 void
michael@0 1173 WebSocket::Send(const nsAString& aData,
michael@0 1174 ErrorResult& aRv)
michael@0 1175 {
michael@0 1176 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1177
michael@0 1178 NS_ConvertUTF16toUTF8 msgString(aData);
michael@0 1179 Send(nullptr, msgString, msgString.Length(), false, aRv);
michael@0 1180 }
michael@0 1181
michael@0 1182 void
michael@0 1183 WebSocket::Send(nsIDOMBlob* aData,
michael@0 1184 ErrorResult& aRv)
michael@0 1185 {
michael@0 1186 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1187
michael@0 1188 nsCOMPtr<nsIInputStream> msgStream;
michael@0 1189 nsresult rv = aData->GetInternalStream(getter_AddRefs(msgStream));
michael@0 1190 if (NS_FAILED(rv)) {
michael@0 1191 aRv.Throw(rv);
michael@0 1192 return;
michael@0 1193 }
michael@0 1194
michael@0 1195 uint64_t msgLength;
michael@0 1196 rv = aData->GetSize(&msgLength);
michael@0 1197 if (NS_FAILED(rv)) {
michael@0 1198 aRv.Throw(rv);
michael@0 1199 return;
michael@0 1200 }
michael@0 1201
michael@0 1202 if (msgLength > UINT32_MAX) {
michael@0 1203 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
michael@0 1204 return;
michael@0 1205 }
michael@0 1206
michael@0 1207 Send(msgStream, EmptyCString(), msgLength, true, aRv);
michael@0 1208 }
michael@0 1209
michael@0 1210 void
michael@0 1211 WebSocket::Send(const ArrayBuffer& aData,
michael@0 1212 ErrorResult& aRv)
michael@0 1213 {
michael@0 1214 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1215
michael@0 1216 aData.ComputeLengthAndData();
michael@0 1217
michael@0 1218 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
michael@0 1219
michael@0 1220 uint32_t len = aData.Length();
michael@0 1221 char* data = reinterpret_cast<char*>(aData.Data());
michael@0 1222
michael@0 1223 nsDependentCSubstring msgString(data, len);
michael@0 1224 Send(nullptr, msgString, len, true, aRv);
michael@0 1225 }
michael@0 1226
michael@0 1227 void
michael@0 1228 WebSocket::Send(const ArrayBufferView& aData,
michael@0 1229 ErrorResult& aRv)
michael@0 1230 {
michael@0 1231 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1232
michael@0 1233 aData.ComputeLengthAndData();
michael@0 1234
michael@0 1235 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
michael@0 1236
michael@0 1237 uint32_t len = aData.Length();
michael@0 1238 char* data = reinterpret_cast<char*>(aData.Data());
michael@0 1239
michael@0 1240 nsDependentCSubstring msgString(data, len);
michael@0 1241 Send(nullptr, msgString, len, true, aRv);
michael@0 1242 }
michael@0 1243
michael@0 1244 void
michael@0 1245 WebSocket::Send(nsIInputStream* aMsgStream,
michael@0 1246 const nsACString& aMsgString,
michael@0 1247 uint32_t aMsgLength,
michael@0 1248 bool aIsBinary,
michael@0 1249 ErrorResult& aRv)
michael@0 1250 {
michael@0 1251 if (mReadyState == WebSocket::CONNECTING) {
michael@0 1252 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
michael@0 1253 return;
michael@0 1254 }
michael@0 1255
michael@0 1256 // Always increment outgoing buffer len, even if closed
michael@0 1257 mOutgoingBufferedAmount += aMsgLength;
michael@0 1258
michael@0 1259 if (mReadyState == WebSocket::CLOSING ||
michael@0 1260 mReadyState == WebSocket::CLOSED) {
michael@0 1261 return;
michael@0 1262 }
michael@0 1263
michael@0 1264 MOZ_ASSERT(mReadyState == WebSocket::OPEN,
michael@0 1265 "Unknown state in WebSocket::Send");
michael@0 1266
michael@0 1267 nsresult rv;
michael@0 1268 if (aMsgStream) {
michael@0 1269 rv = mChannel->SendBinaryStream(aMsgStream, aMsgLength);
michael@0 1270 } else {
michael@0 1271 if (aIsBinary) {
michael@0 1272 rv = mChannel->SendBinaryMsg(aMsgString);
michael@0 1273 } else {
michael@0 1274 rv = mChannel->SendMsg(aMsgString);
michael@0 1275 }
michael@0 1276 }
michael@0 1277
michael@0 1278 if (NS_FAILED(rv)) {
michael@0 1279 aRv.Throw(rv);
michael@0 1280 return;
michael@0 1281 }
michael@0 1282
michael@0 1283 UpdateMustKeepAlive();
michael@0 1284 }
michael@0 1285
michael@0 1286 // webIDL: void close(optional unsigned short code, optional DOMString reason):
michael@0 1287 void
michael@0 1288 WebSocket::Close(const Optional<uint16_t>& aCode,
michael@0 1289 const Optional<nsAString>& aReason,
michael@0 1290 ErrorResult& aRv)
michael@0 1291 {
michael@0 1292 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1293
michael@0 1294 // the reason code is optional, but if provided it must be in a specific range
michael@0 1295 uint16_t closeCode = 0;
michael@0 1296 if (aCode.WasPassed()) {
michael@0 1297 if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
michael@0 1298 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
michael@0 1299 return;
michael@0 1300 }
michael@0 1301 closeCode = aCode.Value();
michael@0 1302 }
michael@0 1303
michael@0 1304 nsCString closeReason;
michael@0 1305 if (aReason.WasPassed()) {
michael@0 1306 CopyUTF16toUTF8(aReason.Value(), closeReason);
michael@0 1307
michael@0 1308 // The API requires the UTF-8 string to be 123 or less bytes
michael@0 1309 if (closeReason.Length() > 123) {
michael@0 1310 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
michael@0 1311 return;
michael@0 1312 }
michael@0 1313 }
michael@0 1314
michael@0 1315 if (mReadyState == WebSocket::CLOSING ||
michael@0 1316 mReadyState == WebSocket::CLOSED) {
michael@0 1317 return;
michael@0 1318 }
michael@0 1319
michael@0 1320 if (mReadyState == WebSocket::CONNECTING) {
michael@0 1321 FailConnection(closeCode, closeReason);
michael@0 1322 return;
michael@0 1323 }
michael@0 1324
michael@0 1325 MOZ_ASSERT(mReadyState == WebSocket::OPEN);
michael@0 1326 CloseConnection(closeCode, closeReason);
michael@0 1327 }
michael@0 1328
michael@0 1329 //-----------------------------------------------------------------------------
michael@0 1330 // WebSocket::nsIObserver
michael@0 1331 //-----------------------------------------------------------------------------
michael@0 1332
michael@0 1333 NS_IMETHODIMP
michael@0 1334 WebSocket::Observe(nsISupports* aSubject,
michael@0 1335 const char* aTopic,
michael@0 1336 const char16_t* aData)
michael@0 1337 {
michael@0 1338 if ((mReadyState == WebSocket::CLOSING) ||
michael@0 1339 (mReadyState == WebSocket::CLOSED)) {
michael@0 1340 return NS_OK;
michael@0 1341 }
michael@0 1342
michael@0 1343 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
michael@0 1344 if (!GetOwner() || window != GetOwner()) {
michael@0 1345 return NS_OK;
michael@0 1346 }
michael@0 1347
michael@0 1348 if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
michael@0 1349 (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
michael@0 1350 {
michael@0 1351 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
michael@0 1352 }
michael@0 1353
michael@0 1354 return NS_OK;
michael@0 1355 }
michael@0 1356
michael@0 1357 //-----------------------------------------------------------------------------
michael@0 1358 // WebSocket::nsIRequest
michael@0 1359 //-----------------------------------------------------------------------------
michael@0 1360
michael@0 1361 NS_IMETHODIMP
michael@0 1362 WebSocket::GetName(nsACString& aName)
michael@0 1363 {
michael@0 1364 CopyUTF16toUTF8(mOriginalURL, aName);
michael@0 1365 return NS_OK;
michael@0 1366 }
michael@0 1367
michael@0 1368 NS_IMETHODIMP
michael@0 1369 WebSocket::IsPending(bool* aValue)
michael@0 1370 {
michael@0 1371 *aValue = (mReadyState != WebSocket::CLOSED);
michael@0 1372 return NS_OK;
michael@0 1373 }
michael@0 1374
michael@0 1375 NS_IMETHODIMP
michael@0 1376 WebSocket::GetStatus(nsresult* aStatus)
michael@0 1377 {
michael@0 1378 *aStatus = NS_OK;
michael@0 1379 return NS_OK;
michael@0 1380 }
michael@0 1381
michael@0 1382 // Window closed, stop/reload button pressed, user navigated away from page, etc.
michael@0 1383 NS_IMETHODIMP
michael@0 1384 WebSocket::Cancel(nsresult aStatus)
michael@0 1385 {
michael@0 1386 NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
michael@0 1387
michael@0 1388 if (mReadyState == CLOSING || mReadyState == CLOSED) {
michael@0 1389 return NS_OK;
michael@0 1390 }
michael@0 1391
michael@0 1392 ConsoleError();
michael@0 1393
michael@0 1394 return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
michael@0 1395 }
michael@0 1396
michael@0 1397 NS_IMETHODIMP
michael@0 1398 WebSocket::Suspend()
michael@0 1399 {
michael@0 1400 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1401 }
michael@0 1402
michael@0 1403 NS_IMETHODIMP
michael@0 1404 WebSocket::Resume()
michael@0 1405 {
michael@0 1406 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1407 }
michael@0 1408
michael@0 1409 NS_IMETHODIMP
michael@0 1410 WebSocket::GetLoadGroup(nsILoadGroup** aLoadGroup)
michael@0 1411 {
michael@0 1412 *aLoadGroup = nullptr;
michael@0 1413
michael@0 1414 nsresult rv;
michael@0 1415 nsIScriptContext* sc = GetContextForEventHandlers(&rv);
michael@0 1416 nsCOMPtr<nsIDocument> doc =
michael@0 1417 nsContentUtils::GetDocumentFromScriptContext(sc);
michael@0 1418
michael@0 1419 if (doc) {
michael@0 1420 *aLoadGroup = doc->GetDocumentLoadGroup().take();
michael@0 1421 }
michael@0 1422
michael@0 1423 return NS_OK;
michael@0 1424 }
michael@0 1425
michael@0 1426 NS_IMETHODIMP
michael@0 1427 WebSocket::SetLoadGroup(nsILoadGroup* aLoadGroup)
michael@0 1428 {
michael@0 1429 return NS_ERROR_UNEXPECTED;
michael@0 1430 }
michael@0 1431
michael@0 1432 NS_IMETHODIMP
michael@0 1433 WebSocket::GetLoadFlags(nsLoadFlags* aLoadFlags)
michael@0 1434 {
michael@0 1435 *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
michael@0 1436 return NS_OK;
michael@0 1437 }
michael@0 1438
michael@0 1439 NS_IMETHODIMP
michael@0 1440 WebSocket::SetLoadFlags(nsLoadFlags aLoadFlags)
michael@0 1441 {
michael@0 1442 // we won't change the load flags at all.
michael@0 1443 return NS_OK;
michael@0 1444 }
michael@0 1445
michael@0 1446 } // dom namespace
michael@0 1447 } // mozilla namespace

mercurial