Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 |