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