1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 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/. */
9 #include "mozilla/dom/WebSocketBinding.h"
10 #include "mozilla/net/WebSocketChannel.h"
12 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
14 #include "jsfriendapi.h"
15 #include "mozilla/Atomics.h"
16 #include "mozilla/BasePrincipal.h"
17 #include "mozilla/DOMEventTargetHelper.h"
18 #include "mozilla/dom/File.h"
19 #include "mozilla/dom/MessageEvent.h"
20 #include "mozilla/dom/MessageEventBinding.h"
21 #include "mozilla/dom/nsCSPContext.h"
22 #include "mozilla/dom/nsCSPUtils.h"
23 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
24 #include "mozilla/dom/nsMixedContentBlocker.h"
25 #include "mozilla/dom/ScriptSettings.h"
26 #include "mozilla/dom/SerializedStackHolder.h"
27 #include "mozilla/dom/TypedArray.h"
28 #include "mozilla/dom/UnionTypes.h"
29 #include "mozilla/dom/WindowContext.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRef.h"
32 #include "mozilla/dom/WorkerRunnable.h"
33 #include "mozilla/dom/WorkerScope.h"
34 #include "mozilla/StaticPrefs_dom.h"
35 #include "mozilla/LoadInfo.h"
36 #include "nsIScriptGlobalObject.h"
37 #include "mozilla/dom/Document.h"
39 #include "nsContentUtils.h"
41 #include "nsICookieJarSettings.h"
42 #include "nsIScriptObjectPrincipal.h"
44 #include "nsThreadUtils.h"
45 #include "nsIPromptFactory.h"
46 #include "nsIWindowWatcher.h"
47 #include "nsIPrompt.h"
48 #include "nsIStringBundle.h"
49 #include "nsIConsoleService.h"
50 #include "mozilla/dom/CloseEvent.h"
51 #include "mozilla/net/WebSocketEventService.h"
52 #include "nsJSUtils.h"
53 #include "nsIScriptError.h"
54 #include "nsNetUtil.h"
55 #include "nsIAuthPrompt.h"
56 #include "nsIAuthPrompt2.h"
57 #include "nsILoadGroup.h"
58 #include "mozilla/Preferences.h"
59 #include "xpcpublic.h"
60 #include "nsContentPolicyUtils.h"
61 #include "nsWrapperCacheInlines.h"
62 #include "nsIObserverService.h"
63 #include "nsIEventTarget.h"
64 #include "nsIInterfaceRequestor.h"
65 #include "nsIObserver.h"
66 #include "nsIRequest.h"
67 #include "nsIThreadRetargetableRequest.h"
68 #include "nsIWebSocketChannel.h"
69 #include "nsIWebSocketListener.h"
70 #include "nsProxyRelease.h"
71 #include "nsWeakReference.h"
72 #include "nsIWebSocketImpl.h"
73 #include "nsIURIMutator.h"
75 #define OPEN_EVENT_STRING u"open"_ns
76 #define MESSAGE_EVENT_STRING u"message"_ns
77 #define ERROR_EVENT_STRING u"error"_ns
78 #define CLOSE_EVENT_STRING u"close"_ns
80 using namespace mozilla::net
;
82 namespace mozilla::dom
{
86 // This class is responsible for proxying nsIObserver and nsIWebSocketImpl
87 // interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on
88 // main thread, so we can let it support weak reference.
89 class WebSocketImplProxy final
: public nsIObserver
,
90 public nsSupportsWeakReference
,
91 public nsIWebSocketImpl
{
95 NS_DECL_NSIWEBSOCKETIMPL
97 explicit WebSocketImplProxy(WebSocketImpl
* aOwner
) : mOwner(aOwner
) {
98 MOZ_ASSERT(NS_IsMainThread());
102 MOZ_ASSERT(NS_IsMainThread());
108 ~WebSocketImplProxy() = default;
110 RefPtr
<WebSocketImpl
> mOwner
;
113 class WebSocketImpl final
: public nsIInterfaceRequestor
,
114 public nsIWebSocketListener
,
117 public nsISerialEventTarget
,
118 public nsIWebSocketImpl
{
120 NS_DECL_NSIINTERFACEREQUESTOR
121 NS_DECL_NSIWEBSOCKETLISTENER
124 NS_DECL_THREADSAFE_ISUPPORTS
125 NS_DECL_NSIEVENTTARGET_FULL
126 NS_DECL_NSIWEBSOCKETIMPL
128 explicit WebSocketImpl(WebSocket
* aWebSocket
)
129 : mWebSocket(aWebSocket
),
130 mIsServerSide(false),
132 mOnCloseScheduled(false),
134 mDisconnectingOrDisconnected(false),
135 mCloseEventWasClean(false),
136 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL
),
141 mPrivateBrowsing(false),
142 mIsChromeContext(false),
144 mMutex("WebSocketImpl::mMutex"),
145 mWorkerShuttingDown(false) {
146 if (!NS_IsMainThread()) {
147 mIsMainThread
= false;
151 void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }
153 bool IsTargetThread() const;
155 nsresult
Init(JSContext
* aCx
, bool aIsSecure
, nsIPrincipal
* aPrincipal
,
156 const Maybe
<ClientInfo
>& aClientInfo
,
157 nsICSPEventListener
* aCSPEventListener
, bool aIsServerSide
,
158 const nsAString
& aURL
, nsTArray
<nsString
>& aProtocolArray
,
159 const nsACString
& aScriptFile
, uint32_t aScriptLine
,
160 uint32_t aScriptColumn
);
162 nsresult
AsyncOpen(nsIPrincipal
* aPrincipal
, uint64_t aInnerWindowID
,
163 nsITransportProvider
* aTransportProvider
,
164 const nsACString
& aNegotiatedExtensions
,
165 UniquePtr
<SerializedStackHolder
> aOriginStack
);
167 nsresult
ParseURL(const nsAString
& aURL
, nsIURI
* aBaseURI
);
168 nsresult
InitializeConnection(nsIPrincipal
* aPrincipal
,
169 nsICookieJarSettings
* aCookieJarSettings
);
171 // These methods when called can release the WebSocket object
172 void FailConnection(const RefPtr
<WebSocketImpl
>& aProofOfRef
,
174 const nsACString
& aReasonString
= ""_ns
);
175 nsresult
CloseConnection(const RefPtr
<WebSocketImpl
>& aProofOfRef
,
177 const nsACString
& aReasonString
= ""_ns
);
178 void Disconnect(const RefPtr
<WebSocketImpl
>& aProofOfRef
);
179 void DisconnectInternal();
181 nsresult
ConsoleError();
182 void PrintErrorOnConsole(const char* aBundleURI
, const char* aError
,
183 nsTArray
<nsString
>&& aFormatStrings
);
185 nsresult
DoOnMessageAvailable(const nsACString
& aMsg
, bool isBinary
) const;
187 // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
188 nsresult
ScheduleConnectionCloseEvents(nsISupports
* aContext
,
189 nsresult aStatusCode
);
190 // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
191 void DispatchConnectionCloseEvents(const RefPtr
<WebSocketImpl
>& aProofOfRef
);
193 nsresult
UpdateURI();
196 void ReleaseObject();
198 bool RegisterWorkerRef(WorkerPrivate
* aWorkerPrivate
);
199 void UnregisterWorkerRef();
201 nsresult
CancelInternal();
203 nsresult
IsSecure(bool* aValue
);
205 RefPtr
<WebSocket
> mWebSocket
;
207 nsCOMPtr
<nsIWebSocketChannel
> mChannel
;
209 bool mIsServerSide
; // True if we're implementing the server side of a
210 // websocket connection
212 bool mSecure
; // if true it is using SSL and the wss scheme,
213 // otherwise it is using the ws scheme with no SSL
215 bool mOnCloseScheduled
;
217 Atomic
<bool> mDisconnectingOrDisconnected
;
219 // Set attributes of DOM 'onclose' message
220 bool mCloseEventWasClean
;
221 nsString mCloseEventReason
;
222 uint16_t mCloseEventCode
;
224 nsCString mAsciiHost
; // hostname
226 nsCString mResource
; // [filepath[?query]]
227 nsString mUTF16Origin
;
230 nsCString mRequestedProtocolList
;
232 WeakPtr
<Document
> mOriginDocument
;
234 // Web Socket owner information:
235 // - the script file name, UTF8 encoded.
236 // - source code line number and 1-origin column number where the Web Socket
237 // object was constructed.
238 // - the ID of the Web Socket owner window. Note that this may not
239 // be the same as the inner window where the script lives.
240 // e.g within iframes
241 // These attributes are used for error reporting.
242 nsCString mScriptFile
;
243 uint32_t mScriptLine
;
244 uint32_t mScriptColumn
;
245 uint64_t mInnerWindowID
;
246 bool mPrivateBrowsing
;
247 bool mIsChromeContext
;
249 RefPtr
<ThreadSafeWorkerRef
> mWorkerRef
;
251 nsWeakPtr mWeakLoadGroup
;
255 // This mutex protects mWorkerShuttingDown.
256 mozilla::Mutex mMutex
;
257 bool mWorkerShuttingDown
MOZ_GUARDED_BY(mMutex
);
259 RefPtr
<WebSocketEventService
> mService
;
260 nsCOMPtr
<nsIPrincipal
> mLoadingPrincipal
;
262 RefPtr
<WebSocketImplProxy
> mImplProxy
;
266 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread
||
267 mDisconnectingOrDisconnected
);
269 // If we threw during Init we never called disconnect
270 if (!mDisconnectingOrDisconnected
) {
271 RefPtr
<WebSocketImpl
> self(this);
277 NS_IMPL_ISUPPORTS(WebSocketImplProxy
, nsIObserver
, nsISupportsWeakReference
,
281 WebSocketImplProxy::Observe(nsISupports
* aSubject
, const char* aTopic
,
282 const char16_t
* aData
) {
287 return mOwner
->Observe(aSubject
, aTopic
, aData
);
291 WebSocketImplProxy::SendMessage(const nsAString
& aMessage
) {
296 return mOwner
->SendMessage(aMessage
);
299 NS_IMPL_ISUPPORTS(WebSocketImpl
, nsIInterfaceRequestor
, nsIWebSocketListener
,
300 nsIObserver
, nsIRequest
, nsIEventTarget
, nsISerialEventTarget
,
303 class CallDispatchConnectionCloseEvents final
: public DiscardableRunnable
{
305 explicit CallDispatchConnectionCloseEvents(WebSocketImpl
* aWebSocketImpl
)
306 : DiscardableRunnable("dom::CallDispatchConnectionCloseEvents"),
307 mWebSocketImpl(aWebSocketImpl
) {
308 aWebSocketImpl
->AssertIsOnTargetThread();
311 NS_IMETHOD
Run() override
{
312 mWebSocketImpl
->AssertIsOnTargetThread();
313 mWebSocketImpl
->DispatchConnectionCloseEvents(mWebSocketImpl
);
318 RefPtr
<WebSocketImpl
> mWebSocketImpl
;
321 //-----------------------------------------------------------------------------
323 //-----------------------------------------------------------------------------
327 class PrintErrorOnConsoleRunnable final
: public WorkerMainThreadRunnable
{
329 PrintErrorOnConsoleRunnable(WebSocketImpl
* aImpl
, const char* aBundleURI
,
331 nsTArray
<nsString
>&& aFormatStrings
)
332 : WorkerMainThreadRunnable(aImpl
->mWorkerRef
->Private(),
333 "WebSocket :: print error on console"_ns
),
335 mBundleURI(aBundleURI
),
337 mFormatStrings(std::move(aFormatStrings
)) {}
339 bool MainThreadRun() override
{
340 mImpl
->PrintErrorOnConsole(mBundleURI
, mError
, std::move(mFormatStrings
));
345 // Raw pointer because this runnable is sync.
346 WebSocketImpl
* mImpl
;
348 const char* mBundleURI
;
350 nsTArray
<nsString
> mFormatStrings
;
355 void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI
,
357 nsTArray
<nsString
>&& aFormatStrings
) {
358 // This method must run on the main thread.
360 if (!NS_IsMainThread()) {
361 MOZ_ASSERT(mWorkerRef
);
363 RefPtr
<PrintErrorOnConsoleRunnable
> runnable
=
364 new PrintErrorOnConsoleRunnable(this, aBundleURI
, aError
,
365 std::move(aFormatStrings
));
367 runnable
->Dispatch(Killing
, rv
);
368 // XXXbz this seems totally broken. We should be propagating this out, but
369 // none of our callers really propagate anything usefully. Come to think of
370 // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget
372 rv
.SuppressException();
377 nsCOMPtr
<nsIStringBundleService
> bundleService
=
378 do_GetService(NS_STRINGBUNDLE_CONTRACTID
, &rv
);
379 NS_ENSURE_SUCCESS_VOID(rv
);
381 nsCOMPtr
<nsIStringBundle
> strBundle
;
382 rv
= bundleService
->CreateBundle(aBundleURI
, getter_AddRefs(strBundle
));
383 NS_ENSURE_SUCCESS_VOID(rv
);
385 nsCOMPtr
<nsIConsoleService
> console(
386 do_GetService(NS_CONSOLESERVICE_CONTRACTID
, &rv
));
387 NS_ENSURE_SUCCESS_VOID(rv
);
389 nsCOMPtr
<nsIScriptError
> errorObject(
390 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
, &rv
));
391 NS_ENSURE_SUCCESS_VOID(rv
);
393 // Localize the error message
394 nsAutoString message
;
395 if (!aFormatStrings
.IsEmpty()) {
396 rv
= strBundle
->FormatStringFromName(aError
, aFormatStrings
, message
);
398 rv
= strBundle
->GetStringFromName(aError
, message
);
400 NS_ENSURE_SUCCESS_VOID(rv
);
402 if (mInnerWindowID
) {
403 rv
= errorObject
->InitWithWindowID(
404 message
, NS_ConvertUTF8toUTF16(mScriptFile
), u
""_ns
, mScriptLine
,
405 mScriptColumn
, nsIScriptError::errorFlag
, "Web Socket"_ns
,
409 errorObject
->Init(message
, NS_ConvertUTF8toUTF16(mScriptFile
), u
""_ns
,
410 mScriptLine
, mScriptColumn
, nsIScriptError::errorFlag
,
411 "Web Socket"_ns
, mPrivateBrowsing
, mIsChromeContext
);
414 NS_ENSURE_SUCCESS_VOID(rv
);
416 // print the error message directly to the JS console
417 rv
= console
->LogMessage(errorObject
);
418 NS_ENSURE_SUCCESS_VOID(rv
);
423 class CancelWebSocketRunnable final
: public Runnable
{
425 CancelWebSocketRunnable(nsIWebSocketChannel
* aChannel
, uint16_t aReasonCode
,
426 const nsACString
& aReasonString
)
427 : Runnable("dom::CancelWebSocketRunnable"),
429 mReasonCode(aReasonCode
),
430 mReasonString(aReasonString
) {}
432 NS_IMETHOD
Run() override
{
433 nsresult rv
= mChannel
->Close(mReasonCode
, mReasonString
);
435 NS_WARNING("Failed to dispatch the close message");
441 nsCOMPtr
<nsIWebSocketChannel
> mChannel
;
442 uint16_t mReasonCode
;
443 nsCString mReasonString
;
446 class MOZ_STACK_CLASS MaybeDisconnect
{
448 explicit MaybeDisconnect(WebSocketImpl
* aImpl
) : mImpl(aImpl
) {}
451 bool toDisconnect
= false;
454 MutexAutoLock
lock(mImpl
->mMutex
);
455 toDisconnect
= mImpl
->mWorkerShuttingDown
;
459 mImpl
->Disconnect(mImpl
);
464 RefPtr
<WebSocketImpl
> mImpl
;
467 class CloseConnectionRunnable final
: public Runnable
{
469 CloseConnectionRunnable(WebSocketImpl
* aImpl
, uint16_t aReasonCode
,
470 const nsACString
& aReasonString
)
471 : Runnable("dom::CloseConnectionRunnable"),
473 mReasonCode(aReasonCode
),
474 mReasonString(aReasonString
) {}
476 NS_IMETHOD
Run() override
{
477 return mImpl
->CloseConnection(mImpl
, mReasonCode
, mReasonString
);
481 RefPtr
<WebSocketImpl
> mImpl
;
482 uint16_t mReasonCode
;
483 const nsCString mReasonString
;
488 nsresult
WebSocketImpl::CloseConnection(
489 const RefPtr
<WebSocketImpl
>& aProofOfRef
, uint16_t aReasonCode
,
490 const nsACString
& aReasonString
) {
491 if (!IsTargetThread()) {
492 nsCOMPtr
<nsIRunnable
> runnable
=
493 new CloseConnectionRunnable(this, aReasonCode
, aReasonString
);
494 return Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
497 AssertIsOnTargetThread();
499 if (mDisconnectingOrDisconnected
) {
503 // If this method is called because the worker is going away, we will not
504 // receive the OnStop() method and we have to disconnect the WebSocket and
505 // release the ThreadSafeWorkerRef.
506 MaybeDisconnect
md(this);
508 uint16_t readyState
= mWebSocket
->ReadyState();
509 if (readyState
== WebSocket::CLOSING
|| readyState
== WebSocket::CLOSED
) {
513 // The common case...
515 mWebSocket
->SetReadyState(WebSocket::CLOSING
);
517 // The channel has to be closed on the main-thread.
519 if (NS_IsMainThread()) {
520 return mChannel
->Close(aReasonCode
, aReasonString
);
523 RefPtr
<CancelWebSocketRunnable
> runnable
=
524 new CancelWebSocketRunnable(mChannel
, aReasonCode
, aReasonString
);
525 return NS_DispatchToMainThread(runnable
);
528 // No channel, but not disconnected: canceled or failed early
529 MOZ_ASSERT(readyState
== WebSocket::CONNECTING
,
530 "Should only get here for early websocket cancel/error");
532 // Server won't be sending us a close code, so use what's passed in here.
533 mCloseEventCode
= aReasonCode
;
534 CopyUTF8toUTF16(aReasonString
, mCloseEventReason
);
536 mWebSocket
->SetReadyState(WebSocket::CLOSING
);
538 ScheduleConnectionCloseEvents(
539 nullptr, (aReasonCode
== nsIWebSocketChannel::CLOSE_NORMAL
||
540 aReasonCode
== nsIWebSocketChannel::CLOSE_GOING_AWAY
)
547 nsresult
WebSocketImpl::ConsoleError() {
548 AssertIsOnTargetThread();
551 MutexAutoLock
lock(mMutex
);
552 if (mWorkerShuttingDown
) {
553 // Too late to report anything, bail out.
558 nsTArray
<nsString
> formatStrings
;
559 CopyUTF8toUTF16(mURI
, *formatStrings
.AppendElement());
561 if (mWebSocket
->ReadyState() < WebSocket::OPEN
) {
562 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
563 "connectionFailure", std::move(formatStrings
));
565 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
566 "netInterrupt", std::move(formatStrings
));
568 /// todo some specific errors - like for message too large
572 void WebSocketImpl::FailConnection(const RefPtr
<WebSocketImpl
>& aProofOfRef
,
573 uint16_t aReasonCode
,
574 const nsACString
& aReasonString
) {
575 AssertIsOnTargetThread();
577 if (mDisconnectingOrDisconnected
) {
583 CloseConnection(aProofOfRef
, aReasonCode
, aReasonString
);
585 if (NS_IsMainThread() && mImplProxy
) {
586 mImplProxy
->Disconnect();
587 mImplProxy
= nullptr;
593 class DisconnectInternalRunnable final
: public WorkerMainThreadRunnable
{
595 explicit DisconnectInternalRunnable(WebSocketImpl
* aImpl
)
596 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
597 "WebSocket :: disconnect"_ns
),
600 bool MainThreadRun() override
{
601 mImpl
->DisconnectInternal();
606 // NOTE: WebSocketImpl may be it the middle of being destroyed.
607 // We can't just hold this as a RefPtr, since after the runnable ends
608 // the sync caller will be released, and can finish destroying WebSocketImpl
609 // before a ref here could be dropped.
610 WebSocketImpl
* mImpl
;
615 void WebSocketImpl::Disconnect(const RefPtr
<WebSocketImpl
>& aProofOfRef
) {
616 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread
);
618 if (mDisconnectingOrDisconnected
) {
622 // DontKeepAliveAnyMore() and DisconnectInternal() can release the
623 // object. aProofOfRef ensures we're holding a reference to this until
624 // the end of the method.
626 // Disconnect can be called from some control event (such as a callback from
627 // StrongWorkerRef). This will be scheduled before any other sync/async
628 // runnable. In order to prevent some double Disconnect() calls, we use this
630 mDisconnectingOrDisconnected
= true;
632 // DisconnectInternal touches observers and nsILoadGroup and it must run on
635 if (NS_IsMainThread()) {
636 DisconnectInternal();
638 // If we haven't called WebSocket::DisconnectFromOwner yet, update
639 // web socket count here.
640 if (mWebSocket
->GetOwner()) {
641 mWebSocket
->GetOwner()->UpdateWebSocketCount(-1);
644 RefPtr
<DisconnectInternalRunnable
> runnable
=
645 new DisconnectInternalRunnable(this);
647 runnable
->Dispatch(Killing
, rv
);
648 // XXXbz this seems totally broken. We should be propagating this out, but
649 // where to, exactly?
650 rv
.SuppressException();
653 NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel
.forget());
654 NS_ReleaseOnMainThread("WebSocketImpl::mService", mService
.forget());
656 mWebSocket
->DontKeepAliveAnyMore();
657 mWebSocket
->mImpl
= nullptr;
660 UnregisterWorkerRef();
663 // We want to release the WebSocket in the correct thread.
664 mWebSocket
= nullptr;
667 void WebSocketImpl::DisconnectInternal() {
668 AssertIsOnMainThread();
670 nsCOMPtr
<nsILoadGroup
> loadGroup
= do_QueryReferent(mWeakLoadGroup
);
672 loadGroup
->RemoveRequest(this, nullptr, NS_OK
);
673 // mWeakLoadGroup has to be released on main-thread because WeakReferences
674 // are not thread-safe.
675 mWeakLoadGroup
= nullptr;
679 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
681 os
->RemoveObserver(mImplProxy
, DOM_WINDOW_DESTROYED_TOPIC
);
682 os
->RemoveObserver(mImplProxy
, DOM_WINDOW_FROZEN_TOPIC
);
687 mImplProxy
->Disconnect();
688 mImplProxy
= nullptr;
692 //-----------------------------------------------------------------------------
693 // WebSocketImpl::nsIWebSocketImpl
694 //-----------------------------------------------------------------------------
697 WebSocketImpl::SendMessage(const nsAString
& aMessage
) {
698 nsString
message(aMessage
);
699 nsCOMPtr
<nsIRunnable
> runnable
= NS_NewRunnableFunction(
700 "WebSocketImpl::SendMessage",
701 [self
= RefPtr
<WebSocketImpl
>(this), message
= std::move(message
)]() {
702 ErrorResult IgnoredErrorResult
;
703 self
->mWebSocket
->Send(message
, IgnoredErrorResult
);
705 return Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
708 //-----------------------------------------------------------------------------
709 // WebSocketImpl::nsIWebSocketListener methods:
710 //-----------------------------------------------------------------------------
712 nsresult
WebSocketImpl::DoOnMessageAvailable(const nsACString
& aMsg
,
713 bool isBinary
) const {
714 AssertIsOnTargetThread();
716 if (mDisconnectingOrDisconnected
) {
720 int16_t readyState
= mWebSocket
->ReadyState();
721 if (readyState
== WebSocket::CLOSED
) {
722 NS_ERROR("Received message after CLOSED");
723 return NS_ERROR_UNEXPECTED
;
726 if (readyState
== WebSocket::OPEN
) {
727 // Dispatch New Message
728 nsresult rv
= mWebSocket
->CreateAndDispatchMessageEvent(aMsg
, isBinary
);
730 NS_WARNING("Failed to dispatch the message event");
736 // CLOSING should be the only other state where it's possible to get msgs
737 // from channel: Spec says to drop them.
738 MOZ_ASSERT(readyState
== WebSocket::CLOSING
,
739 "Received message while CONNECTING or CLOSED");
744 WebSocketImpl::OnMessageAvailable(nsISupports
* aContext
,
745 const nsACString
& aMsg
) {
746 AssertIsOnTargetThread();
748 if (mDisconnectingOrDisconnected
) {
752 return DoOnMessageAvailable(aMsg
, false);
756 WebSocketImpl::OnBinaryMessageAvailable(nsISupports
* aContext
,
757 const nsACString
& aMsg
) {
758 AssertIsOnTargetThread();
760 if (mDisconnectingOrDisconnected
) {
764 return DoOnMessageAvailable(aMsg
, true);
768 WebSocketImpl::OnStart(nsISupports
* aContext
) {
769 AssertIsOnTargetThread();
771 if (mDisconnectingOrDisconnected
) {
775 int16_t readyState
= mWebSocket
->ReadyState();
777 // This is the only function that sets OPEN, and should be called only once
778 MOZ_ASSERT(readyState
!= WebSocket::OPEN
,
779 "readyState already OPEN! OnStart called twice?");
781 // Nothing to do if we've already closed/closing
782 if (readyState
!= WebSocket::CONNECTING
) {
786 // Attempt to kill "ghost" websocket: but usually too early for check to fail
787 nsresult rv
= mWebSocket
->CheckCurrentGlobalCorrectness();
789 RefPtr
<WebSocketImpl
> self(this);
790 CloseConnection(self
, nsIWebSocketChannel::CLOSE_GOING_AWAY
);
794 if (!mRequestedProtocolList
.IsEmpty()) {
795 rv
= mChannel
->GetProtocol(mWebSocket
->mEstablishedProtocol
);
796 MOZ_ASSERT(NS_SUCCEEDED(rv
));
799 rv
= mChannel
->GetExtensions(mWebSocket
->mEstablishedExtensions
);
800 MOZ_ASSERT(NS_SUCCEEDED(rv
));
803 mWebSocket
->SetReadyState(WebSocket::OPEN
);
805 mService
->WebSocketOpened(
806 mChannel
->Serial(), mInnerWindowID
, mWebSocket
->mEffectiveURL
,
807 mWebSocket
->mEstablishedProtocol
, mWebSocket
->mEstablishedExtensions
,
808 mChannel
->HttpChannelId());
810 // Let's keep the object alive because the webSocket can be CCed in the
812 RefPtr
<WebSocket
> webSocket
= mWebSocket
;
815 rv
= webSocket
->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING
);
817 NS_WARNING("Failed to dispatch the open event");
820 webSocket
->UpdateMustKeepAlive();
825 WebSocketImpl::OnStop(nsISupports
* aContext
, nsresult aStatusCode
) {
826 AssertIsOnTargetThread();
828 if (mDisconnectingOrDisconnected
) {
832 // We can be CONNECTING here if connection failed.
833 // We can be OPEN if we have encountered a fatal protocol error
834 // We can be CLOSING if close() was called and/or server initiated close.
835 MOZ_ASSERT(mWebSocket
->ReadyState() != WebSocket::CLOSED
,
836 "Shouldn't already be CLOSED when OnStop called");
838 return ScheduleConnectionCloseEvents(aContext
, aStatusCode
);
841 nsresult
WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports
* aContext
,
842 nsresult aStatusCode
) {
843 AssertIsOnTargetThread();
845 // no-op if some other code has already initiated close event
846 if (!mOnCloseScheduled
) {
847 mCloseEventWasClean
= NS_SUCCEEDED(aStatusCode
);
849 if (aStatusCode
== NS_BASE_STREAM_CLOSED
) {
850 // don't generate an error event just because of an unclean close
854 if (aStatusCode
== NS_ERROR_NET_INADEQUATE_SECURITY
) {
855 // TLS negotiation failed so we need to set status code to 1015.
856 mCloseEventCode
= 1015;
859 if (NS_FAILED(aStatusCode
)) {
864 mOnCloseScheduled
= true;
866 NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
873 WebSocketImpl::OnAcknowledge(nsISupports
* aContext
, uint32_t aSize
) {
874 AssertIsOnTargetThread();
876 if (mDisconnectingOrDisconnected
) {
880 MOZ_RELEASE_ASSERT(mWebSocket
->mOutgoingBufferedAmount
.isValid());
881 if (aSize
> mWebSocket
->mOutgoingBufferedAmount
.value()) {
882 return NS_ERROR_UNEXPECTED
;
885 CheckedUint64 outgoingBufferedAmount
= mWebSocket
->mOutgoingBufferedAmount
;
886 outgoingBufferedAmount
-= aSize
;
887 if (!outgoingBufferedAmount
.isValid()) {
888 return NS_ERROR_UNEXPECTED
;
891 mWebSocket
->mOutgoingBufferedAmount
= outgoingBufferedAmount
;
892 MOZ_RELEASE_ASSERT(mWebSocket
->mOutgoingBufferedAmount
.isValid());
898 WebSocketImpl::OnServerClose(nsISupports
* aContext
, uint16_t aCode
,
899 const nsACString
& aReason
) {
900 AssertIsOnTargetThread();
902 if (mDisconnectingOrDisconnected
) {
906 int16_t readyState
= mWebSocket
->ReadyState();
908 MOZ_ASSERT(readyState
!= WebSocket::CONNECTING
,
909 "Received server close before connected?");
910 MOZ_ASSERT(readyState
!= WebSocket::CLOSED
,
911 "Received server close after already closed!");
913 // store code/string for onclose DOM event
914 mCloseEventCode
= aCode
;
915 CopyUTF8toUTF16(aReason
, mCloseEventReason
);
917 if (readyState
== WebSocket::OPEN
) {
918 // Server initiating close.
919 // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
920 // typically echos the status code it received".
921 // But never send certain codes, per section 7.4.1
922 RefPtr
<WebSocketImpl
> self(this);
923 if (aCode
== 1005 || aCode
== 1006 || aCode
== 1015) {
924 CloseConnection(self
, 0, ""_ns
);
926 CloseConnection(self
, aCode
, aReason
);
929 // We initiated close, and server has replied: OnStop does rest of the work.
930 MOZ_ASSERT(readyState
== WebSocket::CLOSING
, "unknown state");
937 WebSocketImpl::OnError() {
938 if (!IsTargetThread()) {
940 NS_NewRunnableFunction("dom::FailConnectionRunnable",
941 [self
= RefPtr
{this}]() {
942 self
->FailConnection(
943 self
, nsIWebSocketChannel::CLOSE_ABNORMAL
);
948 AssertIsOnTargetThread();
949 RefPtr
<WebSocketImpl
> self(this);
950 FailConnection(self
, nsIWebSocketChannel::CLOSE_ABNORMAL
);
954 //-----------------------------------------------------------------------------
955 // WebSocketImpl::nsIInterfaceRequestor
956 //-----------------------------------------------------------------------------
959 WebSocketImpl::GetInterface(const nsIID
& aIID
, void** aResult
) {
960 AssertIsOnMainThread();
962 if (!mWebSocket
|| mWebSocket
->ReadyState() == WebSocket::CLOSED
) {
963 return NS_ERROR_FAILURE
;
966 if (aIID
.Equals(NS_GET_IID(nsIAuthPrompt
)) ||
967 aIID
.Equals(NS_GET_IID(nsIAuthPrompt2
))) {
968 nsCOMPtr
<nsPIDOMWindowInner
> win
= mWebSocket
->GetWindowIfCurrent();
970 return NS_ERROR_NOT_AVAILABLE
;
974 nsCOMPtr
<nsIPromptFactory
> wwatch
=
975 do_GetService(NS_WINDOWWATCHER_CONTRACTID
, &rv
);
976 NS_ENSURE_SUCCESS(rv
, rv
);
978 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= win
->GetOuterWindow();
979 return wwatch
->GetPrompt(outerWindow
, aIID
, aResult
);
982 return QueryInterface(aIID
, aResult
);
985 ////////////////////////////////////////////////////////////////////////////////
987 ////////////////////////////////////////////////////////////////////////////////
989 WebSocket::WebSocket(nsIGlobalObject
* aGlobal
)
990 : DOMEventTargetHelper(aGlobal
),
992 mKeepingAlive(false),
993 mCheckMustKeepAlive(true),
994 mOutgoingBufferedAmount(0),
995 mBinaryType(dom::BinaryType::Blob
),
996 mMutex("WebSocket::mMutex"),
997 mReadyState(CONNECTING
) {
1000 mImpl
= new WebSocketImpl(this);
1001 mIsMainThread
= mImpl
->mIsMainThread
;
1004 WebSocket::~WebSocket() = default;
1006 mozilla::Maybe
<EventCallbackDebuggerNotificationType
>
1007 WebSocket::GetDebuggerNotificationType() const {
1008 return mozilla::Some(EventCallbackDebuggerNotificationType::Websocket
);
1011 JSObject
* WebSocket::WrapObject(JSContext
* cx
,
1012 JS::Handle
<JSObject
*> aGivenProto
) {
1013 return WebSocket_Binding::Wrap(cx
, this, aGivenProto
);
1016 //---------------------------------------------------------------------------
1018 //---------------------------------------------------------------------------
1021 already_AddRefed
<WebSocket
> WebSocket::Constructor(
1022 const GlobalObject
& aGlobal
, const nsAString
& aUrl
,
1023 const StringOrStringSequence
& aProtocols
, ErrorResult
& aRv
) {
1024 if (aProtocols
.IsStringSequence()) {
1025 return WebSocket::ConstructorCommon(
1026 aGlobal
, aUrl
, aProtocols
.GetAsStringSequence(), nullptr, ""_ns
, aRv
);
1029 Sequence
<nsString
> protocols
;
1030 if (!protocols
.AppendElement(aProtocols
.GetAsString(), fallible
)) {
1031 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1035 return WebSocket::ConstructorCommon(aGlobal
, aUrl
, protocols
, nullptr, ""_ns
,
1039 already_AddRefed
<WebSocket
> WebSocket::CreateServerWebSocket(
1040 const GlobalObject
& aGlobal
, const nsAString
& aUrl
,
1041 const Sequence
<nsString
>& aProtocols
,
1042 nsITransportProvider
* aTransportProvider
,
1043 const nsAString
& aNegotiatedExtensions
, ErrorResult
& aRv
) {
1044 return WebSocket::ConstructorCommon(
1045 aGlobal
, aUrl
, aProtocols
, aTransportProvider
,
1046 NS_ConvertUTF16toUTF8(aNegotiatedExtensions
), aRv
);
1051 // This class is used to clear any exception.
1052 class MOZ_STACK_CLASS ClearException
{
1054 explicit ClearException(JSContext
* aCx
) : mCx(aCx
) {}
1056 ~ClearException() { JS_ClearPendingException(mCx
); }
1062 class WebSocketMainThreadRunnable
: public WorkerMainThreadRunnable
{
1064 WebSocketMainThreadRunnable(WorkerPrivate
* aWorkerPrivate
,
1065 const nsACString
& aTelemetryKey
)
1066 : WorkerMainThreadRunnable(aWorkerPrivate
, aTelemetryKey
) {
1067 MOZ_ASSERT(aWorkerPrivate
);
1068 aWorkerPrivate
->AssertIsOnWorkerThread();
1071 bool MainThreadRun() override
{
1072 AssertIsOnMainThread();
1074 // Walk up to our containing page
1075 WorkerPrivate
* wp
= mWorkerPrivate
;
1076 while (wp
->GetParent()) {
1077 wp
= wp
->GetParent();
1080 nsPIDOMWindowInner
* window
= wp
->GetWindow();
1082 return InitWithWindow(window
);
1085 return InitWindowless(wp
);
1089 virtual bool InitWithWindow(nsPIDOMWindowInner
* aWindow
) = 0;
1091 virtual bool InitWindowless(WorkerPrivate
* aTopLevelWorkerPrivate
) = 0;
1094 class InitRunnable final
: public WebSocketMainThreadRunnable
{
1096 InitRunnable(WorkerPrivate
* aWorkerPrivate
, WebSocketImpl
* aImpl
,
1097 const Maybe
<mozilla::dom::ClientInfo
>& aClientInfo
,
1098 bool aIsServerSide
, const nsAString
& aURL
,
1099 nsTArray
<nsString
>& aProtocolArray
,
1100 const nsACString
& aScriptFile
, uint32_t aScriptLine
,
1101 uint32_t aScriptColumn
)
1102 : WebSocketMainThreadRunnable(aWorkerPrivate
, "WebSocket :: init"_ns
),
1104 mClientInfo(aClientInfo
),
1105 mIsServerSide(aIsServerSide
),
1107 mProtocolArray(aProtocolArray
),
1108 mScriptFile(aScriptFile
),
1109 mScriptLine(aScriptLine
),
1110 mScriptColumn(aScriptColumn
),
1112 MOZ_ASSERT(mWorkerPrivate
);
1113 mWorkerPrivate
->AssertIsOnWorkerThread();
1116 nsresult
ErrorCode() const { return mErrorCode
; }
1119 virtual bool InitWithWindow(nsPIDOMWindowInner
* aWindow
) override
{
1121 if (NS_WARN_IF(!jsapi
.Init(aWindow
))) {
1122 mErrorCode
= NS_ERROR_FAILURE
;
1126 ClearException
ce(jsapi
.cx());
1128 Document
* doc
= aWindow
->GetExtantDoc();
1130 mErrorCode
= NS_ERROR_FAILURE
;
1134 nsIPrincipal
* principal
= mWorkerPrivate
->GetPrincipal();
1135 mErrorCode
= mImpl
->Init(
1136 jsapi
.cx(), principal
->SchemeIs("https"), principal
, mClientInfo
,
1137 mWorkerPrivate
->CSPEventListener(), mIsServerSide
, mURL
, mProtocolArray
,
1138 mScriptFile
, mScriptLine
, mScriptColumn
);
1142 virtual bool InitWindowless(WorkerPrivate
* aTopLevelWorkerPrivate
) override
{
1143 MOZ_ASSERT(NS_IsMainThread());
1144 MOZ_ASSERT(aTopLevelWorkerPrivate
&& !aTopLevelWorkerPrivate
->GetWindow());
1147 mImpl
->Init(nullptr, mWorkerPrivate
->GetPrincipal()->SchemeIs("https"),
1148 aTopLevelWorkerPrivate
->GetPrincipal(), mClientInfo
,
1149 mWorkerPrivate
->CSPEventListener(), mIsServerSide
, mURL
,
1150 mProtocolArray
, mScriptFile
, mScriptLine
, mScriptColumn
);
1154 // Raw pointer. This worker runnable runs synchronously.
1155 WebSocketImpl
* mImpl
;
1157 Maybe
<ClientInfo
> mClientInfo
;
1159 const nsAString
& mURL
;
1160 nsTArray
<nsString
>& mProtocolArray
;
1161 nsCString mScriptFile
;
1162 uint32_t mScriptLine
;
1163 uint32_t mScriptColumn
;
1164 nsresult mErrorCode
;
1167 class ConnectRunnable final
: public WebSocketMainThreadRunnable
{
1169 ConnectRunnable(WorkerPrivate
* aWorkerPrivate
, WebSocketImpl
* aImpl
)
1170 : WebSocketMainThreadRunnable(aWorkerPrivate
, "WebSocket :: init"_ns
),
1172 mConnectionFailed(true) {
1173 MOZ_ASSERT(mWorkerPrivate
);
1174 mWorkerPrivate
->AssertIsOnWorkerThread();
1177 bool ConnectionFailed() const { return mConnectionFailed
; }
1180 virtual bool InitWithWindow(nsPIDOMWindowInner
* aWindow
) override
{
1181 Document
* doc
= aWindow
->GetExtantDoc();
1186 mConnectionFailed
= NS_FAILED(mImpl
->InitializeConnection(
1187 doc
->NodePrincipal(), mWorkerPrivate
->CookieJarSettings()));
1191 virtual bool InitWindowless(WorkerPrivate
* aTopLevelWorkerPrivate
) override
{
1192 MOZ_ASSERT(NS_IsMainThread());
1193 MOZ_ASSERT(aTopLevelWorkerPrivate
&& !aTopLevelWorkerPrivate
->GetWindow());
1195 mConnectionFailed
= NS_FAILED(
1196 mImpl
->InitializeConnection(aTopLevelWorkerPrivate
->GetPrincipal(),
1197 mWorkerPrivate
->CookieJarSettings()));
1201 // Raw pointer. This worker runnable runs synchronously.
1202 WebSocketImpl
* mImpl
;
1204 bool mConnectionFailed
;
1207 class AsyncOpenRunnable final
: public WebSocketMainThreadRunnable
{
1209 explicit AsyncOpenRunnable(WebSocketImpl
* aImpl
,
1210 UniquePtr
<SerializedStackHolder
> aOriginStack
)
1211 : WebSocketMainThreadRunnable(aImpl
->mWorkerRef
->Private(),
1212 "WebSocket :: AsyncOpen"_ns
),
1214 mOriginStack(std::move(aOriginStack
)),
1216 MOZ_ASSERT(mWorkerPrivate
);
1217 mWorkerPrivate
->AssertIsOnWorkerThread();
1220 nsresult
ErrorCode() const { return mErrorCode
; }
1223 virtual bool InitWithWindow(nsPIDOMWindowInner
* aWindow
) override
{
1224 AssertIsOnMainThread();
1225 MOZ_ASSERT(aWindow
);
1227 Document
* doc
= aWindow
->GetExtantDoc();
1229 mErrorCode
= NS_ERROR_FAILURE
;
1233 nsCOMPtr
<nsIPrincipal
> principal
= doc
->PartitionedPrincipal();
1235 mErrorCode
= NS_ERROR_FAILURE
;
1239 uint64_t windowID
= 0;
1240 if (WindowContext
* wc
= aWindow
->GetWindowContext()) {
1241 windowID
= wc
->InnerWindowId();
1244 mErrorCode
= mImpl
->AsyncOpen(principal
, windowID
, nullptr, ""_ns
,
1245 std::move(mOriginStack
));
1249 virtual bool InitWindowless(WorkerPrivate
* aTopLevelWorkerPrivate
) override
{
1250 MOZ_ASSERT(NS_IsMainThread());
1251 MOZ_ASSERT(aTopLevelWorkerPrivate
&& !aTopLevelWorkerPrivate
->GetWindow());
1254 mImpl
->AsyncOpen(aTopLevelWorkerPrivate
->GetPartitionedPrincipal(), 0,
1255 nullptr, ""_ns
, nullptr);
1260 // Raw pointer. This worker runs synchronously.
1261 WebSocketImpl
* mImpl
;
1263 UniquePtr
<SerializedStackHolder
> mOriginStack
;
1265 nsresult mErrorCode
;
1270 // Check a protocol entry contains only valid characters
1271 bool WebSocket::IsValidProtocolString(const nsString
& aValue
) {
1272 // RFC 6455 (4.1): "not including separator characters as defined in RFC 2616"
1273 const char16_t illegalCharacters
[] = {0x28, 0x29, 0x3C, 0x3E, 0x40, 0x2C,
1274 0x3B, 0x3A, 0x5C, 0x22, 0x2F, 0x5B,
1275 0x5D, 0x3F, 0x3D, 0x7B, 0x7D};
1277 // Cannot be empty string
1278 if (aValue
.IsEmpty()) {
1282 const auto* start
= aValue
.BeginReading();
1283 const auto* end
= aValue
.EndReading();
1285 auto charFilter
= [&](char16_t c
) {
1286 // RFC 6455 (4.1 P18): "in the range U+0021 to U+007E"
1287 if (c
< 0x21 || c
> 0x7E) {
1291 return std::find(std::begin(illegalCharacters
), std::end(illegalCharacters
),
1292 c
) != std::end(illegalCharacters
);
1295 return std::find_if(start
, end
, charFilter
) == end
;
1298 already_AddRefed
<WebSocket
> WebSocket::ConstructorCommon(
1299 const GlobalObject
& aGlobal
, const nsAString
& aUrl
,
1300 const Sequence
<nsString
>& aProtocols
,
1301 nsITransportProvider
* aTransportProvider
,
1302 const nsACString
& aNegotiatedExtensions
, ErrorResult
& aRv
) {
1303 MOZ_ASSERT_IF(!aTransportProvider
, aNegotiatedExtensions
.IsEmpty());
1304 nsCOMPtr
<nsIPrincipal
> principal
;
1305 nsCOMPtr
<nsIPrincipal
> partitionedPrincipal
;
1307 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1308 if (NS_WARN_IF(!global
)) {
1309 aRv
.Throw(NS_ERROR_FAILURE
);
1313 if (NS_IsMainThread()) {
1314 nsCOMPtr
<nsIScriptObjectPrincipal
> scriptPrincipal
=
1315 do_QueryInterface(aGlobal
.GetAsSupports());
1316 if (!scriptPrincipal
) {
1317 aRv
.Throw(NS_ERROR_FAILURE
);
1321 principal
= scriptPrincipal
->GetPrincipal();
1322 partitionedPrincipal
= scriptPrincipal
->PartitionedPrincipal();
1323 if (!principal
|| !partitionedPrincipal
) {
1324 aRv
.Throw(NS_ERROR_FAILURE
);
1329 nsTArray
<nsString
> protocolArray
;
1331 for (uint32_t index
= 0, len
= aProtocols
.Length(); index
< len
; ++index
) {
1332 const nsString
& protocolElement
= aProtocols
[index
];
1334 // Repeated protocols are not allowed
1335 if (protocolArray
.Contains(protocolElement
)) {
1336 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
1340 // Protocol string value must match constraints
1341 if (!IsValidProtocolString(protocolElement
)) {
1342 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
1346 protocolArray
.AppendElement(protocolElement
);
1349 RefPtr
<WebSocket
> webSocket
= new WebSocket(global
);
1350 RefPtr
<WebSocketImpl
> webSocketImpl
= webSocket
->mImpl
;
1352 bool connectionFailed
= true;
1354 if (NS_IsMainThread()) {
1355 // We're keeping track of all main thread web sockets to be able to
1356 // avoid throttling timeouts when we have active web sockets.
1357 if (webSocket
->GetOwner()) {
1358 webSocket
->GetOwner()->UpdateWebSocketCount(1);
1361 bool isSecure
= principal
->SchemeIs("https");
1362 aRv
= webSocketImpl
->IsSecure(&isSecure
);
1363 if (NS_WARN_IF(aRv
.Failed())) {
1367 aRv
= webSocketImpl
->Init(aGlobal
.Context(), isSecure
, principal
, Nothing(),
1368 nullptr, !!aTransportProvider
, aUrl
,
1369 protocolArray
, ""_ns
, 0, 0);
1371 if (NS_WARN_IF(aRv
.Failed())) {
1375 nsCOMPtr
<Document
> doc
= webSocket
->GetDocumentIfCurrent();
1377 // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
1378 // url parameter, so don't throw if InitializeConnection fails, and call
1379 // onerror/onclose asynchronously
1380 connectionFailed
= NS_FAILED(webSocketImpl
->InitializeConnection(
1381 principal
, doc
? doc
->CookieJarSettings() : nullptr));
1383 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
1384 MOZ_ASSERT(workerPrivate
);
1387 JS::ColumnNumberOneOrigin column
;
1388 JS::AutoFilename file
;
1389 if (!JS::DescribeScriptedCaller(aGlobal
.Context(), &file
, &lineno
,
1391 NS_WARNING("Failed to get line number and filename in workers.");
1394 RefPtr
<InitRunnable
> runnable
= new InitRunnable(
1395 workerPrivate
, webSocketImpl
,
1396 workerPrivate
->GlobalScope()->GetClientInfo(), !!aTransportProvider
,
1397 aUrl
, protocolArray
, nsDependentCString(file
.get()), lineno
,
1398 column
.oneOriginValue());
1399 runnable
->Dispatch(Canceling
, aRv
);
1400 if (NS_WARN_IF(aRv
.Failed())) {
1404 aRv
= runnable
->ErrorCode();
1405 if (NS_WARN_IF(aRv
.Failed())) {
1409 if (NS_WARN_IF(!webSocketImpl
->RegisterWorkerRef(workerPrivate
))) {
1410 // The worker is shutting down.
1411 aRv
.Throw(NS_ERROR_FAILURE
);
1415 RefPtr
<ConnectRunnable
> connectRunnable
=
1416 new ConnectRunnable(workerPrivate
, webSocketImpl
);
1417 connectRunnable
->Dispatch(Canceling
, aRv
);
1418 if (NS_WARN_IF(aRv
.Failed())) {
1422 connectionFailed
= connectRunnable
->ConnectionFailed();
1425 // It can be that we have been already disconnected because the WebSocket is
1426 // gone away while we where initializing the webSocket.
1427 if (!webSocket
->mImpl
) {
1428 aRv
.Throw(NS_ERROR_FAILURE
);
1432 // We don't return an error if the connection just failed. Instead we dispatch
1434 if (connectionFailed
) {
1435 webSocketImpl
->FailConnection(webSocketImpl
,
1436 nsIWebSocketChannel::CLOSE_ABNORMAL
);
1439 // If we don't have a channel, the connection is failed and onerror() will be
1440 // called asynchrounsly.
1441 if (!webSocket
->mImpl
->mChannel
) {
1442 return webSocket
.forget();
1445 class MOZ_STACK_CLASS ClearWebSocket
{
1447 explicit ClearWebSocket(WebSocketImpl
* aWebSocketImpl
)
1448 : mWebSocketImpl(aWebSocketImpl
), mDone(false) {}
1450 void Done() { mDone
= true; }
1454 mWebSocketImpl
->mChannel
= nullptr;
1455 mWebSocketImpl
->FailConnection(mWebSocketImpl
,
1456 nsIWebSocketChannel::CLOSE_ABNORMAL
);
1460 RefPtr
<WebSocketImpl
> mWebSocketImpl
;
1464 ClearWebSocket
cws(webSocket
->mImpl
);
1466 // This operation must be done on the correct thread. The rest must run on the
1468 aRv
= webSocket
->mImpl
->mChannel
->SetNotificationCallbacks(webSocket
->mImpl
);
1469 if (NS_WARN_IF(aRv
.Failed())) {
1473 if (NS_IsMainThread()) {
1474 MOZ_ASSERT(principal
);
1475 MOZ_ASSERT(partitionedPrincipal
);
1477 nsCOMPtr
<nsPIDOMWindowInner
> ownerWindow
= do_QueryInterface(global
);
1479 UniquePtr
<SerializedStackHolder
> stack
;
1480 uint64_t windowID
= 0;
1483 BrowsingContext
* browsingContext
= ownerWindow
->GetBrowsingContext();
1484 if (browsingContext
&& browsingContext
->WatchedByDevTools()) {
1485 stack
= GetCurrentStackForNetMonitor(aGlobal
.Context());
1488 if (WindowContext
* wc
= ownerWindow
->GetWindowContext()) {
1489 windowID
= wc
->InnerWindowId();
1493 aRv
= webSocket
->mImpl
->AsyncOpen(partitionedPrincipal
, windowID
,
1494 aTransportProvider
, aNegotiatedExtensions
,
1497 MOZ_ASSERT(!aTransportProvider
&& aNegotiatedExtensions
.IsEmpty(),
1498 "not yet implemented");
1500 UniquePtr
<SerializedStackHolder
> stack
;
1501 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
1502 if (workerPrivate
->IsWatchedByDevTools()) {
1503 stack
= GetCurrentStackForNetMonitor(aGlobal
.Context());
1506 RefPtr
<AsyncOpenRunnable
> runnable
=
1507 new AsyncOpenRunnable(webSocket
->mImpl
, std::move(stack
));
1508 runnable
->Dispatch(Canceling
, aRv
);
1509 if (NS_WARN_IF(aRv
.Failed())) {
1513 aRv
= runnable
->ErrorCode();
1516 if (NS_WARN_IF(aRv
.Failed())) {
1520 // It can be that we have been already disconnected because the WebSocket is
1521 // gone away while we where initializing the webSocket.
1522 if (!webSocket
->mImpl
) {
1523 aRv
.Throw(NS_ERROR_FAILURE
);
1527 // Let's inform devtools about this new active WebSocket.
1528 webSocket
->mImpl
->mService
->WebSocketCreated(
1529 webSocket
->mImpl
->mChannel
->Serial(), webSocket
->mImpl
->mInnerWindowID
,
1530 webSocket
->mURI
, webSocket
->mImpl
->mRequestedProtocolList
);
1533 return webSocket
.forget();
1536 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket
)
1538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket
,
1539 DOMEventTargetHelper
)
1541 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl
->mChannel
)
1543 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1545 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket
, DOMEventTargetHelper
)
1547 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl
->mChannel
)
1548 RefPtr
<WebSocketImpl
> pin(tmp
->mImpl
);
1549 pin
->Disconnect(pin
);
1550 MOZ_ASSERT(!tmp
->mImpl
);
1552 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1554 bool WebSocket::IsCertainlyAliveForCC() const { return mKeepingAlive
; }
1556 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebSocket
)
1557 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
1559 NS_IMPL_ADDREF_INHERITED(WebSocket
, DOMEventTargetHelper
)
1560 NS_IMPL_RELEASE_INHERITED(WebSocket
, DOMEventTargetHelper
)
1562 void WebSocket::DisconnectFromOwner() {
1563 // If we haven't called WebSocketImpl::Disconnect yet, update web
1564 // socket count here.
1565 if (NS_IsMainThread() && mImpl
&& !mImpl
->mDisconnectingOrDisconnected
&&
1567 GetOwner()->UpdateWebSocketCount(-1);
1570 DOMEventTargetHelper::DisconnectFromOwner();
1573 RefPtr
<WebSocketImpl
> pin(mImpl
);
1574 pin
->CloseConnection(pin
, nsIWebSocketChannel::CLOSE_GOING_AWAY
);
1577 DontKeepAliveAnyMore();
1580 //-----------------------------------------------------------------------------
1581 // WebSocketImpl:: initialization
1582 //-----------------------------------------------------------------------------
1584 nsresult
WebSocketImpl::Init(JSContext
* aCx
, bool aIsSecure
,
1585 nsIPrincipal
* aPrincipal
,
1586 const Maybe
<ClientInfo
>& aClientInfo
,
1587 nsICSPEventListener
* aCSPEventListener
,
1588 bool aIsServerSide
, const nsAString
& aURL
,
1589 nsTArray
<nsString
>& aProtocolArray
,
1590 const nsACString
& aScriptFile
,
1591 uint32_t aScriptLine
, uint32_t aScriptColumn
) {
1592 AssertIsOnMainThread();
1593 MOZ_ASSERT(aPrincipal
);
1595 mService
= WebSocketEventService::GetOrCreate();
1597 // We need to keep the implementation alive in case the init disconnects it
1598 // because of some error.
1599 RefPtr
<WebSocketImpl
> kungfuDeathGrip
= this;
1601 // Attempt to kill "ghost" websocket: but usually too early for check to fail
1602 nsresult rv
= mWebSocket
->CheckCurrentGlobalCorrectness();
1603 NS_ENSURE_SUCCESS(rv
, rv
);
1605 // Shut down websocket if window is frozen or destroyed (only needed for
1606 // "ghost" websockets--see bug 696085)
1607 RefPtr
<WebSocketImplProxy
> proxy
;
1608 if (mIsMainThread
) {
1609 nsCOMPtr
<nsIObserverService
> os
= mozilla::services::GetObserverService();
1610 if (NS_WARN_IF(!os
)) {
1611 return NS_ERROR_FAILURE
;
1614 proxy
= new WebSocketImplProxy(this);
1615 rv
= os
->AddObserver(proxy
, DOM_WINDOW_DESTROYED_TOPIC
, true);
1616 NS_ENSURE_SUCCESS(rv
, rv
);
1618 rv
= os
->AddObserver(proxy
, DOM_WINDOW_FROZEN_TOPIC
, true);
1619 NS_ENSURE_SUCCESS(rv
, rv
);
1622 if (!mIsMainThread
) {
1623 mScriptFile
= aScriptFile
;
1624 mScriptLine
= aScriptLine
;
1625 mScriptColumn
= aScriptColumn
;
1630 JS::ColumnNumberOneOrigin column
;
1631 JS::AutoFilename file
;
1632 if (JS::DescribeScriptedCaller(aCx
, &file
, &lineno
, &column
)) {
1633 mScriptFile
= file
.get();
1634 mScriptLine
= lineno
;
1635 mScriptColumn
= column
.oneOriginValue();
1639 mIsServerSide
= aIsServerSide
;
1641 // If we don't have aCx, we are window-less, so we don't have a
1642 // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
1643 // DedicateWorkers created by JSM.
1645 if (nsPIDOMWindowInner
* ownerWindow
= mWebSocket
->GetOwner()) {
1646 mInnerWindowID
= ownerWindow
->WindowID();
1650 mPrivateBrowsing
= !!aPrincipal
->OriginAttributesRef().mPrivateBrowsingId
;
1651 mIsChromeContext
= aPrincipal
->IsSystemPrincipal();
1654 nsCOMPtr
<nsIURI
> baseURI
= aPrincipal
->GetURI();
1655 rv
= ParseURL(aURL
, baseURI
);
1656 NS_ENSURE_SUCCESS(rv
, rv
);
1658 nsCOMPtr
<Document
> originDoc
= mWebSocket
->GetDocumentIfCurrent();
1660 rv
= mWebSocket
->CheckCurrentGlobalCorrectness();
1661 NS_ENSURE_SUCCESS(rv
, rv
);
1663 mOriginDocument
= originDoc
;
1665 if (!mIsServerSide
) {
1666 nsCOMPtr
<nsIURI
> uri
;
1668 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), mURI
);
1670 // We crash here because we are sure that mURI is a valid URI, so either
1671 // we are OOM'ing or something else bad is happening.
1672 if (NS_WARN_IF(NS_FAILED(rv
))) {
1677 // The 'real' nsHttpChannel of the websocket gets opened in the parent.
1678 // Since we don't serialize the CSP within child and parent and also not
1679 // the context, we have to perform content policy checks here instead of
1681 // Please note that websockets can't follow redirects, hence there is no
1682 // need to perform a CSP check after redirects.
1683 nsCOMPtr
<nsILoadInfo
> secCheckLoadInfo
= new net::LoadInfo(
1684 aPrincipal
, // loading principal
1685 aPrincipal
, // triggering principal
1686 originDoc
, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
,
1687 nsIContentPolicy::TYPE_WEBSOCKET
, aClientInfo
);
1689 if (aCSPEventListener
) {
1690 secCheckLoadInfo
->SetCspEventListener(aCSPEventListener
);
1693 int16_t shouldLoad
= nsIContentPolicy::ACCEPT
;
1694 rv
= NS_CheckContentLoadPolicy(uri
, secCheckLoadInfo
, &shouldLoad
,
1695 nsContentUtils::GetContentPolicy());
1696 NS_ENSURE_SUCCESS(rv
, rv
);
1698 if (NS_CP_REJECTED(shouldLoad
)) {
1699 // Disallowed by content policy
1700 return NS_ERROR_CONTENT_BLOCKED
;
1703 // If the HTTPS-Only mode is enabled, we need to upgrade the websocket
1704 // connection from ws:// to wss:// and mark it as secure.
1705 if (!mSecure
&& originDoc
&&
1706 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
1707 originDoc
->GetDocumentURI())) {
1708 nsCOMPtr
<nsIURI
> uri
;
1709 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), mURI
);
1710 NS_ENSURE_SUCCESS(rv
, rv
);
1712 // secCheckLoadInfo is only used for the triggering principal, so this
1714 if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri
, secCheckLoadInfo
)) {
1715 mURI
.ReplaceSubstring("ws://", "wss://");
1716 if (NS_WARN_IF(mURI
.Find("wss://") != 0)) {
1724 // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
1725 // In such a case we have to upgrade ws: to wss: and also update mSecure
1726 // to reflect that upgrade. Please note that we can not upgrade from ws:
1727 // to wss: before performing content policy checks because CSP needs to
1728 // send reports in case the scheme is about to be upgraded.
1729 if (!mIsServerSide
&& !mSecure
&& originDoc
&&
1730 originDoc
->GetUpgradeInsecureRequests(false) &&
1731 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
1732 originDoc
->GetDocumentURI())) {
1733 // let's use the old specification before the upgrade for logging
1734 AutoTArray
<nsString
, 2> params
;
1735 CopyUTF8toUTF16(mURI
, *params
.AppendElement());
1737 // upgrade the request from ws:// to wss:// and mark as secure
1738 mURI
.ReplaceSubstring("ws://", "wss://");
1739 if (NS_WARN_IF(mURI
.Find("wss://") != 0)) {
1744 params
.AppendElement(u
"wss"_ns
);
1745 CSP_LogLocalizedStr("upgradeInsecureRequest", params
,
1746 u
""_ns
, // aSourceFile
1747 u
""_ns
, // aScriptSample
1750 nsIScriptError::warningFlag
,
1751 "upgradeInsecureRequest"_ns
, mInnerWindowID
,
1755 // Don't allow https:// to open ws://
1756 // Check that we aren't a server side websocket or set to be upgraded to wss
1757 // or allowing ws from https or a local websocket
1758 if (!mIsServerSide
&& !mSecure
&&
1759 !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
1761 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
1763 // If aIsSecure is true then disallow loading ws
1765 return NS_ERROR_DOM_SECURITY_ERR
;
1768 // Obtain the precursor's URI for the loading principal if it exists
1769 // otherwise use the loading principal's URI
1770 nsCOMPtr
<nsIPrincipal
> precursorPrincipal
=
1771 aPrincipal
->GetPrecursorPrincipal();
1772 nsCOMPtr
<nsIURI
> precursorOrLoadingURI
= precursorPrincipal
1773 ? precursorPrincipal
->GetURI()
1774 : aPrincipal
->GetURI();
1776 // Check if the parent was loaded securely if we have one
1777 if (precursorOrLoadingURI
) {
1778 nsCOMPtr
<nsIURI
> precursorOrLoadingInnermostURI
=
1779 NS_GetInnermostURI(precursorOrLoadingURI
);
1780 // If the parent was loaded securely then disallow loading ws
1781 if (precursorOrLoadingInnermostURI
&&
1782 precursorOrLoadingInnermostURI
->SchemeIs("https")) {
1783 return NS_ERROR_DOM_SECURITY_ERR
;
1788 // Assign the sub protocol list and scan it for illegal values
1789 for (uint32_t index
= 0; index
< aProtocolArray
.Length(); ++index
) {
1790 if (!WebSocket::IsValidProtocolString(aProtocolArray
[index
])) {
1791 return NS_ERROR_DOM_SYNTAX_ERR
;
1794 if (!mRequestedProtocolList
.IsEmpty()) {
1795 mRequestedProtocolList
.AppendLiteral(", ");
1798 AppendUTF16toUTF8(aProtocolArray
[index
], mRequestedProtocolList
);
1801 if (mIsMainThread
) {
1802 mImplProxy
= std::move(proxy
);
1807 nsresult
WebSocketImpl::AsyncOpen(
1808 nsIPrincipal
* aPrincipal
, uint64_t aInnerWindowID
,
1809 nsITransportProvider
* aTransportProvider
,
1810 const nsACString
& aNegotiatedExtensions
,
1811 UniquePtr
<SerializedStackHolder
> aOriginStack
) {
1812 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1813 MOZ_ASSERT_IF(!aTransportProvider
, aNegotiatedExtensions
.IsEmpty());
1815 nsCString webExposedOriginSerialization
;
1816 nsresult rv
= aPrincipal
->GetWebExposedOriginSerialization(
1817 webExposedOriginSerialization
);
1818 if (NS_FAILED(rv
)) {
1819 webExposedOriginSerialization
.AssignLiteral("null");
1822 if (aTransportProvider
) {
1823 rv
= mChannel
->SetServerParameters(aTransportProvider
,
1824 aNegotiatedExtensions
);
1825 NS_ENSURE_SUCCESS(rv
, rv
);
1828 ToLowerCase(webExposedOriginSerialization
);
1830 nsCOMPtr
<nsIURI
> uri
;
1831 if (!aTransportProvider
) {
1832 rv
= NS_NewURI(getter_AddRefs(uri
), mURI
);
1833 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1836 rv
= mChannel
->AsyncOpenNative(uri
, webExposedOriginSerialization
,
1837 aPrincipal
->OriginAttributesRef(),
1838 aInnerWindowID
, this, nullptr);
1839 if (NS_WARN_IF(NS_FAILED(rv
))) {
1840 return NS_ERROR_CONTENT_BLOCKED
;
1843 NotifyNetworkMonitorAlternateStack(mChannel
, std::move(aOriginStack
));
1845 mInnerWindowID
= aInnerWindowID
;
1850 //-----------------------------------------------------------------------------
1851 // WebSocketImpl methods:
1852 //-----------------------------------------------------------------------------
1854 class nsAutoCloseWS final
{
1856 explicit nsAutoCloseWS(WebSocketImpl
* aWebSocketImpl
)
1857 : mWebSocketImpl(aWebSocketImpl
) {}
1860 if (!mWebSocketImpl
->mChannel
) {
1861 mWebSocketImpl
->CloseConnection(
1862 mWebSocketImpl
, nsIWebSocketChannel::CLOSE_INTERNAL_ERROR
);
1867 RefPtr
<WebSocketImpl
> mWebSocketImpl
;
1870 nsresult
WebSocketImpl::InitializeConnection(
1871 nsIPrincipal
* aPrincipal
, nsICookieJarSettings
* aCookieJarSettings
) {
1872 AssertIsOnMainThread();
1873 MOZ_ASSERT(!mChannel
, "mChannel should be null");
1875 nsCOMPtr
<nsIWebSocketChannel
> wsChannel
;
1876 nsAutoCloseWS
autoClose(this);
1881 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv
);
1884 do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv
);
1886 NS_ENSURE_SUCCESS(rv
, rv
);
1888 // add ourselves to the document's load group and
1889 // provide the http stack the loadgroup info too
1890 nsCOMPtr
<nsILoadGroup
> loadGroup
;
1891 rv
= GetLoadGroup(getter_AddRefs(loadGroup
));
1893 rv
= wsChannel
->SetLoadGroup(loadGroup
);
1894 NS_ENSURE_SUCCESS(rv
, rv
);
1895 rv
= loadGroup
->AddRequest(this, nullptr);
1896 NS_ENSURE_SUCCESS(rv
, rv
);
1898 mWeakLoadGroup
= do_GetWeakReference(loadGroup
);
1901 // manually adding loadinfo to the channel since it
1902 // was not set during channel creation.
1903 nsCOMPtr
<Document
> doc(mOriginDocument
);
1905 // mOriginDocument has to be release on main-thread because WeakReferences
1906 // are not thread-safe.
1907 mOriginDocument
= nullptr;
1909 // The TriggeringPrincipal for websockets must always be a script.
1910 // Let's make sure that the doc's principal (if a doc exists)
1911 // and aPrincipal are same origin.
1912 MOZ_ASSERT(!doc
|| doc
->NodePrincipal()->Equals(aPrincipal
));
1914 rv
= wsChannel
->InitLoadInfoNative(
1915 doc
, doc
? doc
->NodePrincipal() : aPrincipal
, aPrincipal
,
1917 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL
,
1918 nsIContentPolicy::TYPE_WEBSOCKET
, 0);
1919 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1921 if (!mRequestedProtocolList
.IsEmpty()) {
1922 rv
= wsChannel
->SetProtocol(mRequestedProtocolList
);
1923 NS_ENSURE_SUCCESS(rv
, rv
);
1926 nsCOMPtr
<nsIThreadRetargetableRequest
> rr
= do_QueryInterface(wsChannel
);
1927 NS_ENSURE_TRUE(rr
, NS_ERROR_FAILURE
);
1929 rv
= rr
->RetargetDeliveryTo(this);
1930 NS_ENSURE_SUCCESS(rv
, rv
);
1932 mChannel
= wsChannel
;
1934 if (mIsMainThread
) {
1935 MOZ_ASSERT(mImplProxy
);
1936 mService
->AssociateWebSocketImplWithSerialID(mImplProxy
,
1937 mChannel
->Serial());
1943 void WebSocketImpl::DispatchConnectionCloseEvents(
1944 const RefPtr
<WebSocketImpl
>& aProofOfRef
) {
1945 AssertIsOnTargetThread();
1947 if (mDisconnectingOrDisconnected
) {
1951 mWebSocket
->SetReadyState(WebSocket::CLOSED
);
1953 // Let's keep the object alive because the webSocket can be CCed in the
1954 // onerror or in the onclose callback
1955 RefPtr
<WebSocket
> webSocket
= mWebSocket
;
1957 // Call 'onerror' if needed
1959 nsresult rv
= webSocket
->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING
);
1960 if (NS_FAILED(rv
)) {
1961 NS_WARNING("Failed to dispatch the error event");
1965 nsresult rv
= webSocket
->CreateAndDispatchCloseEvent(
1966 mCloseEventWasClean
, mCloseEventCode
, mCloseEventReason
);
1967 if (NS_FAILED(rv
)) {
1968 NS_WARNING("Failed to dispatch the close event");
1971 webSocket
->UpdateMustKeepAlive();
1972 Disconnect(aProofOfRef
);
1975 nsresult
WebSocket::CreateAndDispatchSimpleEvent(const nsAString
& aName
) {
1977 AssertIsOnTargetThread();
1979 nsresult rv
= CheckCurrentGlobalCorrectness();
1980 if (NS_FAILED(rv
)) {
1984 RefPtr
<Event
> event
= NS_NewDOMEvent(this, nullptr, nullptr);
1986 // it doesn't bubble, and it isn't cancelable
1987 event
->InitEvent(aName
, false, false);
1988 event
->SetTrusted(true);
1991 DispatchEvent(*event
, err
);
1992 return err
.StealNSResult();
1995 nsresult
WebSocket::CreateAndDispatchMessageEvent(const nsACString
& aData
,
1998 AssertIsOnTargetThread();
2001 if (NS_WARN_IF(!jsapi
.Init(GetOwnerGlobal()))) {
2002 return NS_ERROR_FAILURE
;
2005 JSContext
* cx
= jsapi
.cx();
2007 nsresult rv
= CheckCurrentGlobalCorrectness();
2008 if (NS_FAILED(rv
)) {
2012 uint16_t messageType
= nsIWebSocketEventListener::TYPE_STRING
;
2014 // Create appropriate JS object for message
2015 JS::Rooted
<JS::Value
> jsData(cx
);
2017 if (mBinaryType
== dom::BinaryType::Blob
) {
2018 messageType
= nsIWebSocketEventListener::TYPE_BLOB
;
2021 Blob::CreateStringBlob(GetOwnerGlobal(), aData
, u
""_ns
);
2022 if (NS_WARN_IF(!blob
)) {
2023 return NS_ERROR_FAILURE
;
2026 if (!ToJSValue(cx
, blob
, &jsData
)) {
2027 return NS_ERROR_FAILURE
;
2030 } else if (mBinaryType
== dom::BinaryType::Arraybuffer
) {
2031 messageType
= nsIWebSocketEventListener::TYPE_ARRAYBUFFER
;
2034 JS::Rooted
<JSObject
*> arrayBuf(cx
, ArrayBuffer::Create(cx
, aData
, rv
));
2035 ENSURE_SUCCESS(rv
, rv
.StealNSResult());
2036 jsData
.setObject(*arrayBuf
);
2038 MOZ_CRASH("Unknown binary type!");
2039 return NS_ERROR_UNEXPECTED
;
2043 nsAutoString utf16Data
;
2044 if (!AppendUTF8toUTF16(aData
, utf16Data
, mozilla::fallible
)) {
2045 return NS_ERROR_OUT_OF_MEMORY
;
2048 jsString
= JS_NewUCStringCopyN(cx
, utf16Data
.get(), utf16Data
.Length());
2049 NS_ENSURE_TRUE(jsString
, NS_ERROR_FAILURE
);
2051 jsData
.setString(jsString
);
2054 mImpl
->mService
->WebSocketMessageAvailable(
2055 mImpl
->mChannel
->Serial(), mImpl
->mInnerWindowID
, aData
, messageType
);
2057 // create an event that uses the MessageEvent interface,
2058 // which does not bubble, is not cancelable, and has no default action
2060 RefPtr
<MessageEvent
> event
= new MessageEvent(this, nullptr, nullptr);
2062 event
->InitMessageEvent(nullptr, MESSAGE_EVENT_STRING
, CanBubble::eNo
,
2063 Cancelable::eNo
, jsData
, mImpl
->mUTF16Origin
, u
""_ns
,
2064 nullptr, Sequence
<OwningNonNull
<MessagePort
>>());
2065 event
->SetTrusted(true);
2068 DispatchEvent(*event
, err
);
2069 return err
.StealNSResult();
2072 nsresult
WebSocket::CreateAndDispatchCloseEvent(bool aWasClean
, uint16_t aCode
,
2073 const nsAString
& aReason
) {
2074 AssertIsOnTargetThread();
2076 // This method is called by a runnable and it can happen that, in the
2077 // meantime, GC unlinked this object, so mImpl could be null.
2078 if (mImpl
&& mImpl
->mChannel
) {
2079 mImpl
->mService
->WebSocketClosed(mImpl
->mChannel
->Serial(),
2080 mImpl
->mInnerWindowID
, aWasClean
, aCode
,
2084 nsresult rv
= CheckCurrentGlobalCorrectness();
2085 if (NS_FAILED(rv
)) {
2089 CloseEventInit init
;
2090 init
.mBubbles
= false;
2091 init
.mCancelable
= false;
2092 init
.mWasClean
= aWasClean
;
2094 init
.mReason
= aReason
;
2096 RefPtr
<CloseEvent
> event
=
2097 CloseEvent::Constructor(this, CLOSE_EVENT_STRING
, init
);
2098 event
->SetTrusted(true);
2101 DispatchEvent(*event
, err
);
2102 return err
.StealNSResult();
2105 nsresult
WebSocketImpl::ParseURL(const nsAString
& aURL
, nsIURI
* aBaseURI
) {
2106 AssertIsOnMainThread();
2107 NS_ENSURE_TRUE(!aURL
.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR
);
2109 if (mIsServerSide
) {
2110 mWebSocket
->mURI
= aURL
;
2111 CopyUTF16toUTF8(mWebSocket
->mURI
, mURI
);
2116 nsCOMPtr
<nsIURI
> uri
;
2117 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aURL
, nullptr, aBaseURI
);
2118 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2120 nsCOMPtr
<nsIURL
> parsedURL
= do_QueryInterface(uri
, &rv
);
2121 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2123 nsAutoCString scheme
;
2124 rv
= parsedURL
->GetScheme(scheme
);
2125 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && !scheme
.IsEmpty(),
2126 NS_ERROR_DOM_SYNTAX_ERR
);
2128 // If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s
2129 // [=url/scheme=] to "`ws`". Otherwise, if |urlRecord|'s [=url/scheme=] is
2130 // "`https`", set |urlRecord|'s [=url/scheme=] to "`wss`".
2131 // https://websockets.spec.whatwg.org/#dom-websocket-websocket
2133 if (scheme
== "http" || scheme
== "https") {
2134 scheme
= scheme
== "https" ? "wss"_ns
: "ws"_ns
;
2136 NS_MutateURI
mutator(parsedURL
);
2137 mutator
.SetScheme(scheme
);
2138 rv
= mutator
.Finalize(parsedURL
);
2139 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2143 rv
= parsedURL
->GetHasRef(&hasRef
);
2144 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && !hasRef
, NS_ERROR_DOM_SYNTAX_ERR
);
2147 rv
= parsedURL
->GetAsciiHost(host
);
2148 NS_ENSURE_TRUE(NS_SUCCEEDED(rv
) && !host
.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR
);
2151 rv
= parsedURL
->GetPort(&port
);
2152 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2154 nsAutoCString filePath
;
2155 rv
= parsedURL
->GetFilePath(filePath
);
2156 if (filePath
.IsEmpty()) {
2157 filePath
.Assign('/');
2159 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2161 nsAutoCString query
;
2162 rv
= parsedURL
->GetQuery(query
);
2163 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2165 if (scheme
.LowerCaseEqualsLiteral("ws")) {
2167 mPort
= (port
== -1) ? DEFAULT_WS_SCHEME_PORT
: port
;
2168 } else if (scheme
.LowerCaseEqualsLiteral("wss")) {
2170 mPort
= (port
== -1) ? DEFAULT_WSS_SCHEME_PORT
: port
;
2172 return NS_ERROR_DOM_SYNTAX_ERR
;
2176 nsContentUtils::GetWebExposedOriginSerialization(parsedURL
, mUTF16Origin
);
2177 NS_ENSURE_SUCCESS(rv
, NS_ERROR_DOM_SYNTAX_ERR
);
2180 ToLowerCase(mAsciiHost
);
2182 mResource
= filePath
;
2183 if (!query
.IsEmpty()) {
2184 mResource
.Append('?');
2185 mResource
.Append(query
);
2187 uint32_t length
= mResource
.Length();
2189 for (i
= 0; i
< length
; ++i
) {
2190 if (mResource
[i
] < static_cast<char16_t
>(0x0021) ||
2191 mResource
[i
] > static_cast<char16_t
>(0x007E)) {
2192 return NS_ERROR_DOM_SYNTAX_ERR
;
2196 rv
= parsedURL
->GetSpec(mURI
);
2197 MOZ_ASSERT(NS_SUCCEEDED(rv
));
2199 CopyUTF8toUTF16(mURI
, mWebSocket
->mURI
);
2203 //-----------------------------------------------------------------------------
2204 // Methods that keep alive the WebSocket object when:
2205 // 1. the object has registered event listeners that can be triggered
2206 // ("strong event listeners");
2207 // 2. there are outgoing not sent messages.
2208 //-----------------------------------------------------------------------------
2210 void WebSocket::UpdateMustKeepAlive() {
2211 // Here we could not have mImpl.
2212 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread
);
2214 if (!mCheckMustKeepAlive
|| !mImpl
) {
2218 bool shouldKeepAlive
= false;
2219 uint16_t readyState
= ReadyState();
2221 if (mListenerManager
) {
2222 switch (readyState
) {
2224 if (mListenerManager
->HasListenersFor(OPEN_EVENT_STRING
) ||
2225 mListenerManager
->HasListenersFor(MESSAGE_EVENT_STRING
) ||
2226 mListenerManager
->HasListenersFor(ERROR_EVENT_STRING
) ||
2227 mListenerManager
->HasListenersFor(CLOSE_EVENT_STRING
)) {
2228 shouldKeepAlive
= true;
2234 if (mListenerManager
->HasListenersFor(MESSAGE_EVENT_STRING
) ||
2235 mListenerManager
->HasListenersFor(ERROR_EVENT_STRING
) ||
2236 mListenerManager
->HasListenersFor(CLOSE_EVENT_STRING
) ||
2237 mOutgoingBufferedAmount
.value() != 0) {
2238 shouldKeepAlive
= true;
2243 shouldKeepAlive
= false;
2248 if (mKeepingAlive
&& !shouldKeepAlive
) {
2249 mKeepingAlive
= false;
2250 mImpl
->ReleaseObject();
2251 // Note that this could be made 'alive' again if another listener is
2253 } else if (!mKeepingAlive
&& shouldKeepAlive
) {
2254 mKeepingAlive
= true;
2255 mImpl
->AddRefObject();
2259 void WebSocket::DontKeepAliveAnyMore() {
2260 // Here we could not have mImpl.
2261 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread
);
2263 if (mKeepingAlive
) {
2266 mKeepingAlive
= false;
2267 mImpl
->ReleaseObject();
2270 mCheckMustKeepAlive
= false;
2273 void WebSocketImpl::AddRefObject() {
2274 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread
);
2278 void WebSocketImpl::ReleaseObject() {
2279 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread
);
2283 bool WebSocketImpl::RegisterWorkerRef(WorkerPrivate
* aWorkerPrivate
) {
2284 MOZ_ASSERT(aWorkerPrivate
);
2286 RefPtr
<WebSocketImpl
> self(this);
2288 // In workers we have to keep the worker alive using a strong reference in
2289 // order to dispatch messages correctly.
2290 RefPtr
<StrongWorkerRef
> workerRef
=
2291 StrongWorkerRef::Create(aWorkerPrivate
, "WebSocketImpl", [self
]() {
2293 MutexAutoLock
lock(self
->mMutex
);
2294 self
->mWorkerShuttingDown
= true;
2297 self
->CloseConnection(self
, nsIWebSocketChannel::CLOSE_GOING_AWAY
,
2300 if (NS_WARN_IF(!workerRef
)) {
2304 mWorkerRef
= new ThreadSafeWorkerRef(workerRef
);
2305 MOZ_ASSERT(mWorkerRef
);
2310 void WebSocketImpl::UnregisterWorkerRef() {
2311 MOZ_ASSERT(mDisconnectingOrDisconnected
);
2312 MOZ_ASSERT(mWorkerRef
);
2313 mWorkerRef
->Private()->AssertIsOnWorkerThread();
2316 MutexAutoLock
lock(mMutex
);
2317 mWorkerShuttingDown
= true;
2320 // The DTOR of this StrongWorkerRef will release the worker for us.
2321 mWorkerRef
= nullptr;
2324 nsresult
WebSocketImpl::UpdateURI() {
2325 AssertIsOnTargetThread();
2327 // Check for Redirections
2328 RefPtr
<BaseWebSocketChannel
> channel
;
2329 channel
= static_cast<BaseWebSocketChannel
*>(mChannel
.get());
2330 MOZ_ASSERT(channel
);
2332 channel
->GetEffectiveURL(mWebSocket
->mEffectiveURL
);
2333 mSecure
= channel
->IsEncrypted();
2338 void WebSocket::EventListenerAdded(nsAtom
* aType
) {
2339 AssertIsOnTargetThread();
2340 UpdateMustKeepAlive();
2343 void WebSocket::EventListenerRemoved(nsAtom
* aType
) {
2344 AssertIsOnTargetThread();
2345 UpdateMustKeepAlive();
2348 //-----------------------------------------------------------------------------
2349 // WebSocket - methods
2350 //-----------------------------------------------------------------------------
2352 // webIDL: readonly attribute unsigned short readyState;
2353 uint16_t WebSocket::ReadyState() {
2354 MutexAutoLock
lock(mMutex
);
2358 void WebSocket::SetReadyState(uint16_t aReadyState
) {
2359 MutexAutoLock
lock(mMutex
);
2360 mReadyState
= aReadyState
;
2363 // webIDL: readonly attribute unsigned long long bufferedAmount;
2364 uint64_t WebSocket::BufferedAmount() const {
2365 AssertIsOnTargetThread();
2366 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount
.isValid());
2367 return mOutgoingBufferedAmount
.value();
2370 // webIDL: attribute BinaryType binaryType;
2371 dom::BinaryType
WebSocket::BinaryType() const {
2372 AssertIsOnTargetThread();
2376 // webIDL: attribute BinaryType binaryType;
2377 void WebSocket::SetBinaryType(dom::BinaryType aData
) {
2378 AssertIsOnTargetThread();
2379 mBinaryType
= aData
;
2382 // webIDL: readonly attribute DOMString url
2383 void WebSocket::GetUrl(nsAString
& aURL
) {
2384 AssertIsOnTargetThread();
2386 if (mEffectiveURL
.IsEmpty()) {
2389 aURL
= mEffectiveURL
;
2393 // webIDL: readonly attribute DOMString extensions;
2394 void WebSocket::GetExtensions(nsAString
& aExtensions
) {
2395 AssertIsOnTargetThread();
2396 CopyUTF8toUTF16(mEstablishedExtensions
, aExtensions
);
2399 // webIDL: readonly attribute DOMString protocol;
2400 void WebSocket::GetProtocol(nsAString
& aProtocol
) {
2401 AssertIsOnTargetThread();
2402 CopyUTF8toUTF16(mEstablishedProtocol
, aProtocol
);
2405 // webIDL: void send(DOMString data);
2406 void WebSocket::Send(const nsAString
& aData
, ErrorResult
& aRv
) {
2407 AssertIsOnTargetThread();
2409 nsAutoCString msgString
;
2410 if (!AppendUTF16toUTF8(aData
, msgString
, mozilla::fallible_t())) {
2411 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
2414 Send(nullptr, msgString
, msgString
.Length(), false, aRv
);
2417 void WebSocket::Send(Blob
& aData
, ErrorResult
& aRv
) {
2418 AssertIsOnTargetThread();
2420 nsCOMPtr
<nsIInputStream
> msgStream
;
2421 aData
.CreateInputStream(getter_AddRefs(msgStream
), aRv
);
2422 if (NS_WARN_IF(aRv
.Failed())) {
2426 uint64_t msgLength
= aData
.GetSize(aRv
);
2427 if (NS_WARN_IF(aRv
.Failed())) {
2431 if (msgLength
> UINT32_MAX
) {
2432 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
2436 Send(msgStream
, ""_ns
, msgLength
, true, aRv
);
2439 void WebSocket::Send(const ArrayBuffer
& aData
, ErrorResult
& aRv
) {
2440 AssertIsOnTargetThread();
2443 sizeof(std::remove_reference_t
<decltype(aData
)>::element_type
) == 1,
2444 "byte-sized data required");
2446 nsCString msgString
;
2447 if (!aData
.AppendDataTo(msgString
)) {
2448 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
2451 Send(nullptr, msgString
, msgString
.Length(), true, aRv
);
2454 void WebSocket::Send(const ArrayBufferView
& aData
, ErrorResult
& aRv
) {
2455 AssertIsOnTargetThread();
2458 sizeof(std::remove_reference_t
<decltype(aData
)>::element_type
) == 1,
2459 "byte-sized data required");
2461 nsCString msgString
;
2462 if (!aData
.AppendDataTo(msgString
)) {
2463 aRv
.Throw(NS_ERROR_FILE_TOO_BIG
);
2466 Send(nullptr, msgString
, msgString
.Length(), true, aRv
);
2469 void WebSocket::Send(nsIInputStream
* aMsgStream
, const nsACString
& aMsgString
,
2470 uint32_t aMsgLength
, bool aIsBinary
, ErrorResult
& aRv
) {
2471 AssertIsOnTargetThread();
2473 int64_t readyState
= ReadyState();
2474 if (readyState
== CONNECTING
) {
2475 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2479 CheckedUint64 outgoingBufferedAmount
= mOutgoingBufferedAmount
;
2480 outgoingBufferedAmount
+= aMsgLength
;
2481 if (!outgoingBufferedAmount
.isValid()) {
2482 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
2486 // Always increment outgoing buffer len, even if closed
2487 mOutgoingBufferedAmount
= outgoingBufferedAmount
;
2488 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount
.isValid());
2490 if (readyState
== CLOSING
|| readyState
== CLOSED
) {
2494 // We must have mImpl when connected.
2496 MOZ_ASSERT(readyState
== OPEN
, "Unknown state in WebSocket::Send");
2500 rv
= mImpl
->mChannel
->SendBinaryStream(aMsgStream
, aMsgLength
);
2503 rv
= mImpl
->mChannel
->SendBinaryMsg(aMsgString
);
2505 rv
= mImpl
->mChannel
->SendMsg(aMsgString
);
2509 if (NS_FAILED(rv
)) {
2514 UpdateMustKeepAlive();
2517 // webIDL: void close(optional unsigned short code, optional DOMString reason):
2518 void WebSocket::Close(const Optional
<uint16_t>& aCode
,
2519 const Optional
<nsAString
>& aReason
, ErrorResult
& aRv
) {
2520 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread
);
2522 // the reason code is optional, but if provided it must be in a specific range
2523 uint16_t closeCode
= 0;
2524 if (aCode
.WasPassed()) {
2525 if (aCode
.Value() != 1000 &&
2526 (aCode
.Value() < 3000 || aCode
.Value() > 4999)) {
2527 aRv
.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR
);
2530 closeCode
= aCode
.Value();
2533 nsCString closeReason
;
2534 if (aReason
.WasPassed()) {
2535 CopyUTF16toUTF8(aReason
.Value(), closeReason
);
2537 // The API requires the UTF-8 string to be 123 or less bytes
2538 if (closeReason
.Length() > 123) {
2539 aRv
.Throw(NS_ERROR_DOM_SYNTAX_ERR
);
2544 int64_t readyState
= ReadyState();
2545 if (readyState
== CLOSING
|| readyState
== CLOSED
) {
2549 // If we don't have mImpl, we are in a shutting down worker where we are still
2550 // in CONNECTING state, but already disconnected internally.
2552 MOZ_ASSERT(readyState
== CONNECTING
);
2553 SetReadyState(CLOSING
);
2557 // These could cause the mImpl to be released (and so this to be
2558 // released); make sure it stays valid through the call
2559 RefPtr
<WebSocketImpl
> pin(mImpl
);
2561 if (readyState
== CONNECTING
) {
2562 pin
->FailConnection(pin
, closeCode
, closeReason
);
2566 MOZ_ASSERT(readyState
== OPEN
);
2567 pin
->CloseConnection(pin
, closeCode
, closeReason
);
2570 //-----------------------------------------------------------------------------
2571 // WebSocketImpl::nsIObserver
2572 //-----------------------------------------------------------------------------
2575 WebSocketImpl::Observe(nsISupports
* aSubject
, const char* aTopic
,
2576 const char16_t
* aData
) {
2577 AssertIsOnMainThread();
2579 int64_t readyState
= mWebSocket
->ReadyState();
2580 if ((readyState
== WebSocket::CLOSING
) || (readyState
== WebSocket::CLOSED
)) {
2584 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(aSubject
);
2585 if (!mWebSocket
->GetOwner() || window
!= mWebSocket
->GetOwner()) {
2589 if ((strcmp(aTopic
, DOM_WINDOW_FROZEN_TOPIC
) == 0) ||
2590 (strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
) == 0)) {
2591 RefPtr
<WebSocketImpl
> self(this);
2592 CloseConnection(self
, nsIWebSocketChannel::CLOSE_GOING_AWAY
);
2598 //-----------------------------------------------------------------------------
2599 // WebSocketImpl::nsIRequest
2600 //-----------------------------------------------------------------------------
2603 WebSocketImpl::GetName(nsACString
& aName
) {
2604 AssertIsOnMainThread();
2606 CopyUTF16toUTF8(mWebSocket
->mURI
, aName
);
2611 WebSocketImpl::IsPending(bool* aValue
) {
2612 AssertIsOnTargetThread();
2614 int64_t readyState
= mWebSocket
->ReadyState();
2615 *aValue
= (readyState
!= WebSocket::CLOSED
);
2620 WebSocketImpl::GetStatus(nsresult
* aStatus
) {
2621 AssertIsOnTargetThread();
2629 class CancelRunnable final
: public MainThreadWorkerRunnable
{
2631 CancelRunnable(ThreadSafeWorkerRef
* aWorkerRef
, WebSocketImpl
* aImpl
)
2632 : MainThreadWorkerRunnable(aWorkerRef
->Private()), mImpl(aImpl
) {}
2634 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
2635 aWorkerPrivate
->AssertIsOnWorkerThread();
2636 return !NS_FAILED(mImpl
->CancelInternal());
2640 RefPtr
<WebSocketImpl
> mImpl
;
2645 NS_IMETHODIMP
WebSocketImpl::SetCanceledReason(const nsACString
& aReason
) {
2646 return SetCanceledReasonImpl(aReason
);
2649 NS_IMETHODIMP
WebSocketImpl::GetCanceledReason(nsACString
& aReason
) {
2650 return GetCanceledReasonImpl(aReason
);
2653 NS_IMETHODIMP
WebSocketImpl::CancelWithReason(nsresult aStatus
,
2654 const nsACString
& aReason
) {
2655 return CancelWithReasonImpl(aStatus
, aReason
);
2658 // Window closed, stop/reload button pressed, user navigated away from page,
2661 WebSocketImpl::Cancel(nsresult aStatus
) {
2662 AssertIsOnMainThread();
2664 if (!mIsMainThread
) {
2665 MOZ_ASSERT(mWorkerRef
);
2666 RefPtr
<CancelRunnable
> runnable
= new CancelRunnable(mWorkerRef
, this);
2667 if (!runnable
->Dispatch()) {
2668 return NS_ERROR_FAILURE
;
2674 return CancelInternal();
2677 nsresult
WebSocketImpl::CancelInternal() {
2678 AssertIsOnTargetThread();
2680 // If CancelInternal is called by a runnable, we may already be disconnected
2681 // by the time it runs.
2682 if (mDisconnectingOrDisconnected
) {
2686 int64_t readyState
= mWebSocket
->ReadyState();
2687 if (readyState
== WebSocket::CLOSING
|| readyState
== WebSocket::CLOSED
) {
2691 RefPtr
<WebSocketImpl
> self(this);
2692 return CloseConnection(self
, nsIWebSocketChannel::CLOSE_GOING_AWAY
);
2696 WebSocketImpl::Suspend() {
2697 AssertIsOnMainThread();
2698 return NS_ERROR_NOT_IMPLEMENTED
;
2702 WebSocketImpl::Resume() {
2703 AssertIsOnMainThread();
2704 return NS_ERROR_NOT_IMPLEMENTED
;
2708 WebSocketImpl::GetLoadGroup(nsILoadGroup
** aLoadGroup
) {
2709 AssertIsOnMainThread();
2711 *aLoadGroup
= nullptr;
2713 if (mIsMainThread
) {
2714 nsCOMPtr
<Document
> doc
= mWebSocket
->GetDocumentIfCurrent();
2716 *aLoadGroup
= doc
->GetDocumentLoadGroup().take();
2722 MOZ_ASSERT(mWorkerRef
);
2724 // Walk up to our containing page
2725 WorkerPrivate
* wp
= mWorkerRef
->Private();
2726 while (wp
->GetParent()) {
2727 wp
= wp
->GetParent();
2730 nsPIDOMWindowInner
* window
= wp
->GetWindow();
2735 Document
* doc
= window
->GetExtantDoc();
2737 *aLoadGroup
= doc
->GetDocumentLoadGroup().take();
2744 WebSocketImpl::SetLoadGroup(nsILoadGroup
* aLoadGroup
) {
2745 AssertIsOnMainThread();
2746 return NS_ERROR_UNEXPECTED
;
2750 WebSocketImpl::GetLoadFlags(nsLoadFlags
* aLoadFlags
) {
2751 AssertIsOnMainThread();
2753 *aLoadFlags
= nsIRequest::LOAD_BACKGROUND
;
2758 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags
) {
2759 AssertIsOnMainThread();
2761 // we won't change the load flags at all.
2766 WebSocketImpl::GetTRRMode(nsIRequest::TRRMode
* aTRRMode
) {
2767 return GetTRRModeImpl(aTRRMode
);
2771 WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode
) {
2772 return SetTRRModeImpl(aTRRMode
);
2777 class WorkerRunnableDispatcher final
: public WorkerRunnable
{
2778 RefPtr
<WebSocketImpl
> mWebSocketImpl
;
2781 WorkerRunnableDispatcher(WebSocketImpl
* aImpl
,
2782 ThreadSafeWorkerRef
* aWorkerRef
,
2783 already_AddRefed
<nsIRunnable
> aEvent
)
2784 : WorkerRunnable(aWorkerRef
->Private(), "WorkerRunnableDispatcher",
2786 mWebSocketImpl(aImpl
),
2787 mEvent(std::move(aEvent
)) {}
2789 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
2790 aWorkerPrivate
->AssertIsOnWorkerThread();
2792 // No messages when disconnected.
2793 if (mWebSocketImpl
->mDisconnectingOrDisconnected
) {
2794 NS_WARNING("Dispatching a WebSocket event after the disconnection!");
2798 return !NS_FAILED(mEvent
->Run());
2801 void PostRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
2802 bool aRunResult
) override
{}
2804 bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{
2805 // We don't call WorkerRunnable::PreDispatch because it would assert the
2806 // wrong thing about which thread we're on. We're on whichever thread the
2807 // channel implementation is running on (probably the main thread or socket
2808 // transport thread).
2812 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
2813 bool aDispatchResult
) override
{
2814 // We don't call WorkerRunnable::PreDispatch because it would assert the
2815 // wrong thing about which thread we're on. We're on whichever thread the
2816 // channel implementation is running on (probably the main thread or socket
2817 // transport thread).
2821 nsCOMPtr
<nsIRunnable
> mEvent
;
2827 WebSocketImpl::DispatchFromScript(nsIRunnable
* aEvent
, uint32_t aFlags
) {
2828 nsCOMPtr
<nsIRunnable
> event(aEvent
);
2829 return Dispatch(event
.forget(), aFlags
);
2833 WebSocketImpl::Dispatch(already_AddRefed
<nsIRunnable
> aEvent
, uint32_t aFlags
) {
2834 nsCOMPtr
<nsIRunnable
> event_ref(aEvent
);
2835 if (mIsMainThread
) {
2836 nsISerialEventTarget
* target
= GetMainThreadSerialEventTarget();
2837 NS_ENSURE_TRUE(target
, NS_ERROR_FAILURE
);
2838 return target
->Dispatch(event_ref
.forget());
2841 MutexAutoLock
lock(mMutex
);
2842 if (mWorkerShuttingDown
) {
2846 MOZ_DIAGNOSTIC_ASSERT(mWorkerRef
);
2848 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
2850 RefPtr
<WorkerRunnableDispatcher
> event
=
2851 new WorkerRunnableDispatcher(this, mWorkerRef
, event_ref
.forget());
2853 if (!event
->Dispatch()) {
2854 return NS_ERROR_FAILURE
;
2861 WebSocketImpl::DelayedDispatch(already_AddRefed
<nsIRunnable
>, uint32_t) {
2862 return NS_ERROR_NOT_IMPLEMENTED
;
2866 WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask
*) {
2867 return NS_ERROR_NOT_IMPLEMENTED
;
2871 WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask
*) {
2872 return NS_ERROR_NOT_IMPLEMENTED
;
2876 WebSocketImpl::IsOnCurrentThread(bool* aResult
) {
2877 *aResult
= IsTargetThread();
2881 NS_IMETHODIMP_(bool)
2882 WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
2884 bool WebSocketImpl::IsTargetThread() const {
2885 // FIXME: This should also check if we're on the worker thread. Code using
2886 // `IsOnCurrentThread` could easily misbehave here!
2887 return NS_IsMainThread() == mIsMainThread
;
2890 void WebSocket::AssertIsOnTargetThread() const {
2891 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread
);
2894 nsresult
WebSocketImpl::IsSecure(bool* aValue
) {
2895 MOZ_ASSERT(NS_IsMainThread());
2896 MOZ_ASSERT(mIsMainThread
);
2898 // Check the principal's uri to determine if we were loaded from https.
2899 nsCOMPtr
<nsIGlobalObject
> globalObject(GetEntryGlobal());
2900 nsCOMPtr
<nsIPrincipal
> principal
;
2903 principal
= globalObject
->PrincipalOrNull();
2906 nsCOMPtr
<nsPIDOMWindowInner
> innerWindow
= do_QueryInterface(globalObject
);
2908 // If we are in a XPConnect sandbox or in a JS component,
2909 // innerWindow will be null. There is nothing on top of this to be
2911 if (NS_WARN_IF(!principal
)) {
2914 *aValue
= principal
->SchemeIs("https");
2918 RefPtr
<WindowContext
> windowContext
= innerWindow
->GetWindowContext();
2919 if (NS_WARN_IF(!windowContext
)) {
2920 return NS_ERROR_DOM_SECURITY_ERR
;
2924 if (windowContext
->GetIsSecure()) {
2929 if (windowContext
->IsTop()) {
2932 // If we're not a top window get the parent window context instead.
2933 windowContext
= windowContext
->GetParentWindowContext();
2936 if (NS_WARN_IF(!windowContext
)) {
2937 return NS_ERROR_DOM_SECURITY_ERR
;
2941 *aValue
= windowContext
->GetIsSecure();
2945 } // namespace mozilla::dom