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