Bug 1842773 - Part 18: Update TypedArray length, byteLength, and byteOffset accesses...
[gecko.git] / dom / websocket / WebSocket.cpp
bloba1c497ba20bea3b480854771d4e643f9679927ba
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/. */
7 #include "WebSocket.h"
8 #include "ErrorList.h"
9 #include "mozilla/dom/WebSocketBinding.h"
10 #include "mozilla/net/WebSocketChannel.h"
12 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
13 #include "jsapi.h"
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"
38 #include "nsXPCOM.h"
39 #include "nsContentUtils.h"
40 #include "nsError.h"
41 #include "nsICookieJarSettings.h"
42 #include "nsIScriptObjectPrincipal.h"
43 #include "nsIURL.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 {
84 class WebSocketImpl;
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 {
92 public:
93 NS_DECL_ISUPPORTS
94 NS_DECL_NSIOBSERVER
95 NS_DECL_NSIWEBSOCKETIMPL
97 explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) {
98 MOZ_ASSERT(NS_IsMainThread());
101 void Disconnect() {
102 MOZ_ASSERT(NS_IsMainThread());
104 mOwner = nullptr;
107 private:
108 ~WebSocketImplProxy() = default;
110 RefPtr<WebSocketImpl> mOwner;
113 class WebSocketImpl final : public nsIInterfaceRequestor,
114 public nsIWebSocketListener,
115 public nsIObserver,
116 public nsIRequest,
117 public nsISerialEventTarget,
118 public nsIWebSocketImpl {
119 public:
120 NS_DECL_NSIINTERFACEREQUESTOR
121 NS_DECL_NSIWEBSOCKETLISTENER
122 NS_DECL_NSIOBSERVER
123 NS_DECL_NSIREQUEST
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),
131 mSecure(false),
132 mOnCloseScheduled(false),
133 mFailed(false),
134 mDisconnectingOrDisconnected(false),
135 mCloseEventWasClean(false),
136 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
137 mPort(0),
138 mScriptLine(0),
139 mScriptColumn(1),
140 mInnerWindowID(0),
141 mPrivateBrowsing(false),
142 mIsChromeContext(false),
143 mIsMainThread(true),
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,
173 uint16_t reasonCode,
174 const nsACString& aReasonString = ""_ns);
175 nsresult CloseConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
176 uint16_t reasonCode,
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();
195 void AddRefObject();
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;
216 bool mFailed;
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
225 uint32_t mPort;
226 nsCString mResource; // [filepath[?query]]
227 nsString mUTF16Origin;
229 nsCString mURI;
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;
253 bool mIsMainThread;
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;
264 private:
265 ~WebSocketImpl() {
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);
272 Disconnect(self);
277 NS_IMPL_ISUPPORTS(WebSocketImplProxy, nsIObserver, nsISupportsWeakReference,
278 nsIWebSocketImpl)
280 NS_IMETHODIMP
281 WebSocketImplProxy::Observe(nsISupports* aSubject, const char* aTopic,
282 const char16_t* aData) {
283 if (!mOwner) {
284 return NS_OK;
287 return mOwner->Observe(aSubject, aTopic, aData);
290 NS_IMETHODIMP
291 WebSocketImplProxy::SendMessage(const nsAString& aMessage) {
292 if (!mOwner) {
293 return NS_OK;
296 return mOwner->SendMessage(aMessage);
299 NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
300 nsIObserver, nsIRequest, nsIEventTarget, nsISerialEventTarget,
301 nsIWebSocketImpl)
303 class CallDispatchConnectionCloseEvents final : public DiscardableRunnable {
304 public:
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);
314 return NS_OK;
317 private:
318 RefPtr<WebSocketImpl> mWebSocketImpl;
321 //-----------------------------------------------------------------------------
322 // WebSocketImpl
323 //-----------------------------------------------------------------------------
325 namespace {
327 class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable {
328 public:
329 PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI,
330 const char* aError,
331 nsTArray<nsString>&& aFormatStrings)
332 : WorkerMainThreadRunnable(aImpl->mWorkerRef->Private(),
333 "WebSocket :: print error on console"_ns),
334 mImpl(aImpl),
335 mBundleURI(aBundleURI),
336 mError(aError),
337 mFormatStrings(std::move(aFormatStrings)) {}
339 bool MainThreadRun() override {
340 mImpl->PrintErrorOnConsole(mBundleURI, mError, std::move(mFormatStrings));
341 return true;
344 private:
345 // Raw pointer because this runnable is sync.
346 WebSocketImpl* mImpl;
348 const char* mBundleURI;
349 const char* mError;
350 nsTArray<nsString> mFormatStrings;
353 } // namespace
355 void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI,
356 const char* aError,
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));
366 ErrorResult rv;
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
371 // runnable??
372 rv.SuppressException();
373 return;
376 nsresult rv;
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);
397 } else {
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,
406 mInnerWindowID);
407 } else {
408 rv =
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);
421 namespace {
423 class CancelWebSocketRunnable final : public Runnable {
424 public:
425 CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
426 const nsACString& aReasonString)
427 : Runnable("dom::CancelWebSocketRunnable"),
428 mChannel(aChannel),
429 mReasonCode(aReasonCode),
430 mReasonString(aReasonString) {}
432 NS_IMETHOD Run() override {
433 nsresult rv = mChannel->Close(mReasonCode, mReasonString);
434 if (NS_FAILED(rv)) {
435 NS_WARNING("Failed to dispatch the close message");
437 return NS_OK;
440 private:
441 nsCOMPtr<nsIWebSocketChannel> mChannel;
442 uint16_t mReasonCode;
443 nsCString mReasonString;
446 class MOZ_STACK_CLASS MaybeDisconnect {
447 public:
448 explicit MaybeDisconnect(WebSocketImpl* aImpl) : mImpl(aImpl) {}
450 ~MaybeDisconnect() {
451 bool toDisconnect = false;
454 MutexAutoLock lock(mImpl->mMutex);
455 toDisconnect = mImpl->mWorkerShuttingDown;
458 if (toDisconnect) {
459 mImpl->Disconnect(mImpl);
463 private:
464 RefPtr<WebSocketImpl> mImpl;
467 class CloseConnectionRunnable final : public Runnable {
468 public:
469 CloseConnectionRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode,
470 const nsACString& aReasonString)
471 : Runnable("dom::CloseConnectionRunnable"),
472 mImpl(aImpl),
473 mReasonCode(aReasonCode),
474 mReasonString(aReasonString) {}
476 NS_IMETHOD Run() override {
477 return mImpl->CloseConnection(mImpl, mReasonCode, mReasonString);
480 private:
481 RefPtr<WebSocketImpl> mImpl;
482 uint16_t mReasonCode;
483 const nsCString mReasonString;
486 } // namespace
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) {
500 return NS_OK;
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) {
510 return NS_OK;
513 // The common case...
514 if (mChannel) {
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)
541 ? NS_OK
542 : NS_ERROR_FAILURE);
544 return NS_OK;
547 nsresult WebSocketImpl::ConsoleError() {
548 AssertIsOnTargetThread();
551 MutexAutoLock lock(mMutex);
552 if (mWorkerShuttingDown) {
553 // Too late to report anything, bail out.
554 return NS_OK;
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));
564 } else {
565 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
566 "netInterrupt", std::move(formatStrings));
568 /// todo some specific errors - like for message too large
569 return NS_OK;
572 void WebSocketImpl::FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef,
573 uint16_t aReasonCode,
574 const nsACString& aReasonString) {
575 AssertIsOnTargetThread();
577 if (mDisconnectingOrDisconnected) {
578 return;
581 ConsoleError();
582 mFailed = true;
583 CloseConnection(aProofOfRef, aReasonCode, aReasonString);
585 if (NS_IsMainThread() && mImplProxy) {
586 mImplProxy->Disconnect();
587 mImplProxy = nullptr;
591 namespace {
593 class DisconnectInternalRunnable final : public WorkerMainThreadRunnable {
594 public:
595 explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
596 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
597 "WebSocket :: disconnect"_ns),
598 mImpl(aImpl) {}
600 bool MainThreadRun() override {
601 mImpl->DisconnectInternal();
602 return true;
605 private:
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;
613 } // namespace
615 void WebSocketImpl::Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef) {
616 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
618 if (mDisconnectingOrDisconnected) {
619 return;
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
629 // boolean.
630 mDisconnectingOrDisconnected = true;
632 // DisconnectInternal touches observers and nsILoadGroup and it must run on
633 // the main thread.
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);
643 } else {
644 RefPtr<DisconnectInternalRunnable> runnable =
645 new DisconnectInternalRunnable(this);
646 ErrorResult rv;
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;
659 if (mWorkerRef) {
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);
671 if (loadGroup) {
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;
678 if (!mWorkerRef) {
679 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
680 if (os) {
681 os->RemoveObserver(mImplProxy, DOM_WINDOW_DESTROYED_TOPIC);
682 os->RemoveObserver(mImplProxy, DOM_WINDOW_FROZEN_TOPIC);
686 if (mImplProxy) {
687 mImplProxy->Disconnect();
688 mImplProxy = nullptr;
692 //-----------------------------------------------------------------------------
693 // WebSocketImpl::nsIWebSocketImpl
694 //-----------------------------------------------------------------------------
696 NS_IMETHODIMP
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) {
717 return NS_OK;
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);
729 if (NS_FAILED(rv)) {
730 NS_WARNING("Failed to dispatch the message event");
733 return NS_OK;
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");
740 return NS_OK;
743 NS_IMETHODIMP
744 WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
745 const nsACString& aMsg) {
746 AssertIsOnTargetThread();
748 if (mDisconnectingOrDisconnected) {
749 return NS_OK;
752 return DoOnMessageAvailable(aMsg, false);
755 NS_IMETHODIMP
756 WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
757 const nsACString& aMsg) {
758 AssertIsOnTargetThread();
760 if (mDisconnectingOrDisconnected) {
761 return NS_OK;
764 return DoOnMessageAvailable(aMsg, true);
767 NS_IMETHODIMP
768 WebSocketImpl::OnStart(nsISupports* aContext) {
769 AssertIsOnTargetThread();
771 if (mDisconnectingOrDisconnected) {
772 return NS_OK;
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) {
783 return NS_OK;
786 // Attempt to kill "ghost" websocket: but usually too early for check to fail
787 nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
788 if (NS_FAILED(rv)) {
789 RefPtr<WebSocketImpl> self(this);
790 CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
791 return rv;
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));
801 UpdateURI();
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
811 // onopen callback
812 RefPtr<WebSocket> webSocket = mWebSocket;
814 // Call 'onopen'
815 rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING);
816 if (NS_FAILED(rv)) {
817 NS_WARNING("Failed to dispatch the open event");
820 webSocket->UpdateMustKeepAlive();
821 return NS_OK;
824 NS_IMETHODIMP
825 WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode) {
826 AssertIsOnTargetThread();
828 if (mDisconnectingOrDisconnected) {
829 return NS_OK;
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
851 aStatusCode = NS_OK;
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)) {
860 ConsoleError();
861 mFailed = true;
864 mOnCloseScheduled = true;
866 NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
869 return NS_OK;
872 NS_IMETHODIMP
873 WebSocketImpl::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
874 AssertIsOnTargetThread();
876 if (mDisconnectingOrDisconnected) {
877 return NS_OK;
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());
894 return NS_OK;
897 NS_IMETHODIMP
898 WebSocketImpl::OnServerClose(nsISupports* aContext, uint16_t aCode,
899 const nsACString& aReason) {
900 AssertIsOnTargetThread();
902 if (mDisconnectingOrDisconnected) {
903 return NS_OK;
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);
925 } else {
926 CloseConnection(self, aCode, aReason);
928 } else {
929 // We initiated close, and server has replied: OnStop does rest of the work.
930 MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
933 return NS_OK;
936 NS_IMETHODIMP
937 WebSocketImpl::OnError() {
938 if (!IsTargetThread()) {
939 return Dispatch(
940 NS_NewRunnableFunction("dom::FailConnectionRunnable",
941 [self = RefPtr{this}]() {
942 self->FailConnection(
943 self, nsIWebSocketChannel::CLOSE_ABNORMAL);
945 NS_DISPATCH_NORMAL);
948 AssertIsOnTargetThread();
949 RefPtr<WebSocketImpl> self(this);
950 FailConnection(self, nsIWebSocketChannel::CLOSE_ABNORMAL);
951 return NS_OK;
954 //-----------------------------------------------------------------------------
955 // WebSocketImpl::nsIInterfaceRequestor
956 //-----------------------------------------------------------------------------
958 NS_IMETHODIMP
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();
969 if (!win) {
970 return NS_ERROR_NOT_AVAILABLE;
973 nsresult rv;
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 ////////////////////////////////////////////////////////////////////////////////
986 // WebSocket
987 ////////////////////////////////////////////////////////////////////////////////
989 WebSocket::WebSocket(nsIGlobalObject* aGlobal)
990 : DOMEventTargetHelper(aGlobal),
991 mIsMainThread(true),
992 mKeepingAlive(false),
993 mCheckMustKeepAlive(true),
994 mOutgoingBufferedAmount(0),
995 mBinaryType(dom::BinaryType::Blob),
996 mMutex("WebSocket::mMutex"),
997 mReadyState(CONNECTING) {
998 MOZ_ASSERT(aGlobal);
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 //---------------------------------------------------------------------------
1017 // WebIDL
1018 //---------------------------------------------------------------------------
1020 // Constructor:
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);
1032 return nullptr;
1035 return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, ""_ns,
1036 aRv);
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);
1049 namespace {
1051 // This class is used to clear any exception.
1052 class MOZ_STACK_CLASS ClearException {
1053 public:
1054 explicit ClearException(JSContext* aCx) : mCx(aCx) {}
1056 ~ClearException() { JS_ClearPendingException(mCx); }
1058 private:
1059 JSContext* mCx;
1062 class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable {
1063 public:
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();
1081 if (window) {
1082 return InitWithWindow(window);
1085 return InitWindowless(wp);
1088 protected:
1089 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;
1091 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
1094 class InitRunnable final : public WebSocketMainThreadRunnable {
1095 public:
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),
1103 mImpl(aImpl),
1104 mClientInfo(aClientInfo),
1105 mIsServerSide(aIsServerSide),
1106 mURL(aURL),
1107 mProtocolArray(aProtocolArray),
1108 mScriptFile(aScriptFile),
1109 mScriptLine(aScriptLine),
1110 mScriptColumn(aScriptColumn),
1111 mErrorCode(NS_OK) {
1112 MOZ_ASSERT(mWorkerPrivate);
1113 mWorkerPrivate->AssertIsOnWorkerThread();
1116 nsresult ErrorCode() const { return mErrorCode; }
1118 protected:
1119 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1120 AutoJSAPI jsapi;
1121 if (NS_WARN_IF(!jsapi.Init(aWindow))) {
1122 mErrorCode = NS_ERROR_FAILURE;
1123 return true;
1126 ClearException ce(jsapi.cx());
1128 Document* doc = aWindow->GetExtantDoc();
1129 if (!doc) {
1130 mErrorCode = NS_ERROR_FAILURE;
1131 return true;
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);
1139 return true;
1142 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
1143 MOZ_ASSERT(NS_IsMainThread());
1144 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1146 mErrorCode =
1147 mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
1148 aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo,
1149 mWorkerPrivate->CSPEventListener(), mIsServerSide, mURL,
1150 mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
1151 return true;
1154 // Raw pointer. This worker runnable runs synchronously.
1155 WebSocketImpl* mImpl;
1157 Maybe<ClientInfo> mClientInfo;
1158 bool mIsServerSide;
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 {
1168 public:
1169 ConnectRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
1170 : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
1171 mImpl(aImpl),
1172 mConnectionFailed(true) {
1173 MOZ_ASSERT(mWorkerPrivate);
1174 mWorkerPrivate->AssertIsOnWorkerThread();
1177 bool ConnectionFailed() const { return mConnectionFailed; }
1179 protected:
1180 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1181 Document* doc = aWindow->GetExtantDoc();
1182 if (!doc) {
1183 return true;
1186 mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
1187 doc->NodePrincipal(), mWorkerPrivate->CookieJarSettings()));
1188 return true;
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()));
1198 return true;
1201 // Raw pointer. This worker runnable runs synchronously.
1202 WebSocketImpl* mImpl;
1204 bool mConnectionFailed;
1207 class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
1208 public:
1209 explicit AsyncOpenRunnable(WebSocketImpl* aImpl,
1210 UniquePtr<SerializedStackHolder> aOriginStack)
1211 : WebSocketMainThreadRunnable(aImpl->mWorkerRef->Private(),
1212 "WebSocket :: AsyncOpen"_ns),
1213 mImpl(aImpl),
1214 mOriginStack(std::move(aOriginStack)),
1215 mErrorCode(NS_OK) {
1216 MOZ_ASSERT(mWorkerPrivate);
1217 mWorkerPrivate->AssertIsOnWorkerThread();
1220 nsresult ErrorCode() const { return mErrorCode; }
1222 protected:
1223 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1224 AssertIsOnMainThread();
1225 MOZ_ASSERT(aWindow);
1227 Document* doc = aWindow->GetExtantDoc();
1228 if (!doc) {
1229 mErrorCode = NS_ERROR_FAILURE;
1230 return true;
1233 nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal();
1234 if (!principal) {
1235 mErrorCode = NS_ERROR_FAILURE;
1236 return true;
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));
1246 return true;
1249 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
1250 MOZ_ASSERT(NS_IsMainThread());
1251 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1253 mErrorCode =
1254 mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0,
1255 nullptr, ""_ns, nullptr);
1256 return true;
1259 private:
1260 // Raw pointer. This worker runs synchronously.
1261 WebSocketImpl* mImpl;
1263 UniquePtr<SerializedStackHolder> mOriginStack;
1265 nsresult mErrorCode;
1268 } // namespace
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()) {
1279 return false;
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) {
1288 return true;
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);
1310 return nullptr;
1313 if (NS_IsMainThread()) {
1314 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1315 do_QueryInterface(aGlobal.GetAsSupports());
1316 if (!scriptPrincipal) {
1317 aRv.Throw(NS_ERROR_FAILURE);
1318 return nullptr;
1321 principal = scriptPrincipal->GetPrincipal();
1322 partitionedPrincipal = scriptPrincipal->PartitionedPrincipal();
1323 if (!principal || !partitionedPrincipal) {
1324 aRv.Throw(NS_ERROR_FAILURE);
1325 return nullptr;
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);
1337 return nullptr;
1340 // Protocol string value must match constraints
1341 if (!IsValidProtocolString(protocolElement)) {
1342 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1343 return nullptr;
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())) {
1364 return nullptr;
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())) {
1372 return nullptr;
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));
1382 } else {
1383 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1384 MOZ_ASSERT(workerPrivate);
1386 uint32_t lineno;
1387 JS::ColumnNumberOneOrigin column;
1388 JS::AutoFilename file;
1389 if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
1390 &column)) {
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())) {
1401 return nullptr;
1404 aRv = runnable->ErrorCode();
1405 if (NS_WARN_IF(aRv.Failed())) {
1406 return nullptr;
1409 if (NS_WARN_IF(!webSocketImpl->RegisterWorkerRef(workerPrivate))) {
1410 // The worker is shutting down.
1411 aRv.Throw(NS_ERROR_FAILURE);
1412 return nullptr;
1415 RefPtr<ConnectRunnable> connectRunnable =
1416 new ConnectRunnable(workerPrivate, webSocketImpl);
1417 connectRunnable->Dispatch(Canceling, aRv);
1418 if (NS_WARN_IF(aRv.Failed())) {
1419 return nullptr;
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);
1429 return nullptr;
1432 // We don't return an error if the connection just failed. Instead we dispatch
1433 // an event.
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 {
1446 public:
1447 explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
1448 : mWebSocketImpl(aWebSocketImpl), mDone(false) {}
1450 void Done() { mDone = true; }
1452 ~ClearWebSocket() {
1453 if (!mDone) {
1454 mWebSocketImpl->mChannel = nullptr;
1455 mWebSocketImpl->FailConnection(mWebSocketImpl,
1456 nsIWebSocketChannel::CLOSE_ABNORMAL);
1460 RefPtr<WebSocketImpl> mWebSocketImpl;
1461 bool mDone;
1464 ClearWebSocket cws(webSocket->mImpl);
1466 // This operation must be done on the correct thread. The rest must run on the
1467 // main-thread.
1468 aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
1469 if (NS_WARN_IF(aRv.Failed())) {
1470 return nullptr;
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;
1482 if (ownerWindow) {
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,
1495 std::move(stack));
1496 } else {
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())) {
1510 return nullptr;
1513 aRv = runnable->ErrorCode();
1516 if (NS_WARN_IF(aRv.Failed())) {
1517 return nullptr;
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);
1524 return nullptr;
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);
1531 cws.Done();
1533 return webSocket.forget();
1536 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
1538 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
1539 DOMEventTargetHelper)
1540 if (tmp->mImpl) {
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)
1546 if (tmp->mImpl) {
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 &&
1566 GetOwner()) {
1567 GetOwner()->UpdateWebSocketCount(-1);
1570 DOMEventTargetHelper::DisconnectFromOwner();
1572 if (mImpl) {
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;
1626 } else {
1627 MOZ_ASSERT(aCx);
1629 uint32_t lineno;
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.
1644 if (aCx) {
1645 if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwner()) {
1646 mInnerWindowID = ownerWindow->WindowID();
1650 mPrivateBrowsing = !!aPrincipal->OriginAttributesRef().mPrivateBrowsingId;
1651 mIsChromeContext = aPrincipal->IsSystemPrincipal();
1653 // parses the url
1654 nsCOMPtr<nsIURI> baseURI = aPrincipal->GetURI();
1655 rv = ParseURL(aURL, baseURI);
1656 NS_ENSURE_SUCCESS(rv, rv);
1658 nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent();
1659 if (!originDoc) {
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))) {
1673 MOZ_CRASH();
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
1680 // AsyncOpen().
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
1713 // is okay.
1714 if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) {
1715 mURI.ReplaceSubstring("ws://", "wss://");
1716 if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
1717 return NS_OK;
1719 mSecure = true;
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)) {
1740 return NS_OK;
1742 mSecure = true;
1744 params.AppendElement(u"wss"_ns);
1745 CSP_LogLocalizedStr("upgradeInsecureRequest", params,
1746 u""_ns, // aSourceFile
1747 u""_ns, // aScriptSample
1748 0, // aLineNumber
1749 1, // aColumnNumber
1750 nsIScriptError::warningFlag,
1751 "upgradeInsecureRequest"_ns, mInnerWindowID,
1752 mPrivateBrowsing);
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",
1760 false) &&
1761 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
1762 mAsciiHost)) {
1763 // If aIsSecure is true then disallow loading ws
1764 if (aIsSecure) {
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);
1804 return NS_OK;
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;
1847 return NS_OK;
1850 //-----------------------------------------------------------------------------
1851 // WebSocketImpl methods:
1852 //-----------------------------------------------------------------------------
1854 class nsAutoCloseWS final {
1855 public:
1856 explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
1857 : mWebSocketImpl(aWebSocketImpl) {}
1859 ~nsAutoCloseWS() {
1860 if (!mWebSocketImpl->mChannel) {
1861 mWebSocketImpl->CloseConnection(
1862 mWebSocketImpl, nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
1866 private:
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);
1877 nsresult rv;
1879 if (mSecure) {
1880 wsChannel =
1881 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
1882 } else {
1883 wsChannel =
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));
1892 if (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,
1916 aCookieJarSettings,
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());
1940 return NS_OK;
1943 void WebSocketImpl::DispatchConnectionCloseEvents(
1944 const RefPtr<WebSocketImpl>& aProofOfRef) {
1945 AssertIsOnTargetThread();
1947 if (mDisconnectingOrDisconnected) {
1948 return;
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
1958 if (mFailed) {
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) {
1976 MOZ_ASSERT(mImpl);
1977 AssertIsOnTargetThread();
1979 nsresult rv = CheckCurrentGlobalCorrectness();
1980 if (NS_FAILED(rv)) {
1981 return NS_OK;
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);
1990 ErrorResult err;
1991 DispatchEvent(*event, err);
1992 return err.StealNSResult();
1995 nsresult WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
1996 bool aIsBinary) {
1997 MOZ_ASSERT(mImpl);
1998 AssertIsOnTargetThread();
2000 AutoJSAPI jsapi;
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)) {
2009 return NS_OK;
2012 uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
2014 // Create appropriate JS object for message
2015 JS::Rooted<JS::Value> jsData(cx);
2016 if (aIsBinary) {
2017 if (mBinaryType == dom::BinaryType::Blob) {
2018 messageType = nsIWebSocketEventListener::TYPE_BLOB;
2020 RefPtr<Blob> 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;
2033 ErrorResult rv;
2034 JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aData, rv));
2035 ENSURE_SUCCESS(rv, rv.StealNSResult());
2036 jsData.setObject(*arrayBuf);
2037 } else {
2038 MOZ_CRASH("Unknown binary type!");
2039 return NS_ERROR_UNEXPECTED;
2041 } else {
2042 // JS string
2043 nsAutoString utf16Data;
2044 if (!AppendUTF8toUTF16(aData, utf16Data, mozilla::fallible)) {
2045 return NS_ERROR_OUT_OF_MEMORY;
2047 JSString* jsString;
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);
2067 ErrorResult err;
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,
2081 aReason);
2084 nsresult rv = CheckCurrentGlobalCorrectness();
2085 if (NS_FAILED(rv)) {
2086 return NS_OK;
2089 CloseEventInit init;
2090 init.mBubbles = false;
2091 init.mCancelable = false;
2092 init.mWasClean = aWasClean;
2093 init.mCode = aCode;
2094 init.mReason = aReason;
2096 RefPtr<CloseEvent> event =
2097 CloseEvent::Constructor(this, CLOSE_EVENT_STRING, init);
2098 event->SetTrusted(true);
2100 ErrorResult err;
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);
2113 return NS_OK;
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);
2142 bool hasRef;
2143 rv = parsedURL->GetHasRef(&hasRef);
2144 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR);
2146 nsAutoCString host;
2147 rv = parsedURL->GetAsciiHost(host);
2148 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
2150 int32_t port;
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")) {
2166 mSecure = false;
2167 mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
2168 } else if (scheme.LowerCaseEqualsLiteral("wss")) {
2169 mSecure = true;
2170 mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
2171 } else {
2172 return NS_ERROR_DOM_SYNTAX_ERR;
2175 rv =
2176 nsContentUtils::GetWebExposedOriginSerialization(parsedURL, mUTF16Origin);
2177 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2179 mAsciiHost = host;
2180 ToLowerCase(mAsciiHost);
2182 mResource = filePath;
2183 if (!query.IsEmpty()) {
2184 mResource.Append('?');
2185 mResource.Append(query);
2187 uint32_t length = mResource.Length();
2188 uint32_t i;
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);
2200 return NS_OK;
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) {
2215 return;
2218 bool shouldKeepAlive = false;
2219 uint16_t readyState = ReadyState();
2221 if (mListenerManager) {
2222 switch (readyState) {
2223 case CONNECTING: {
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;
2230 } break;
2232 case OPEN:
2233 case CLOSING: {
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;
2240 } break;
2242 case CLOSED: {
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
2252 // added.
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) {
2264 MOZ_ASSERT(mImpl);
2266 mKeepingAlive = false;
2267 mImpl->ReleaseObject();
2270 mCheckMustKeepAlive = false;
2273 void WebSocketImpl::AddRefObject() {
2274 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
2275 AddRef();
2278 void WebSocketImpl::ReleaseObject() {
2279 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
2280 Release();
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,
2298 ""_ns);
2300 if (NS_WARN_IF(!workerRef)) {
2301 return false;
2304 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
2305 MOZ_ASSERT(mWorkerRef);
2307 return true;
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();
2335 return NS_OK;
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);
2355 return mReadyState;
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();
2373 return mBinaryType;
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()) {
2387 aURL = mURI;
2388 } else {
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);
2412 return;
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())) {
2423 return;
2426 uint64_t msgLength = aData.GetSize(aRv);
2427 if (NS_WARN_IF(aRv.Failed())) {
2428 return;
2431 if (msgLength > UINT32_MAX) {
2432 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2433 return;
2436 Send(msgStream, ""_ns, msgLength, true, aRv);
2439 void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
2440 AssertIsOnTargetThread();
2442 static_assert(
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);
2449 return;
2451 Send(nullptr, msgString, msgString.Length(), true, aRv);
2454 void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
2455 AssertIsOnTargetThread();
2457 static_assert(
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);
2464 return;
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);
2476 return;
2479 CheckedUint64 outgoingBufferedAmount = mOutgoingBufferedAmount;
2480 outgoingBufferedAmount += aMsgLength;
2481 if (!outgoingBufferedAmount.isValid()) {
2482 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2483 return;
2486 // Always increment outgoing buffer len, even if closed
2487 mOutgoingBufferedAmount = outgoingBufferedAmount;
2488 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
2490 if (readyState == CLOSING || readyState == CLOSED) {
2491 return;
2494 // We must have mImpl when connected.
2495 MOZ_ASSERT(mImpl);
2496 MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
2498 nsresult rv;
2499 if (aMsgStream) {
2500 rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
2501 } else {
2502 if (aIsBinary) {
2503 rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
2504 } else {
2505 rv = mImpl->mChannel->SendMsg(aMsgString);
2509 if (NS_FAILED(rv)) {
2510 aRv.Throw(rv);
2511 return;
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);
2528 return;
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);
2540 return;
2544 int64_t readyState = ReadyState();
2545 if (readyState == CLOSING || readyState == CLOSED) {
2546 return;
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.
2551 if (!mImpl) {
2552 MOZ_ASSERT(readyState == CONNECTING);
2553 SetReadyState(CLOSING);
2554 return;
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);
2563 return;
2566 MOZ_ASSERT(readyState == OPEN);
2567 pin->CloseConnection(pin, closeCode, closeReason);
2570 //-----------------------------------------------------------------------------
2571 // WebSocketImpl::nsIObserver
2572 //-----------------------------------------------------------------------------
2574 NS_IMETHODIMP
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)) {
2581 return NS_OK;
2584 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
2585 if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
2586 return NS_OK;
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);
2595 return NS_OK;
2598 //-----------------------------------------------------------------------------
2599 // WebSocketImpl::nsIRequest
2600 //-----------------------------------------------------------------------------
2602 NS_IMETHODIMP
2603 WebSocketImpl::GetName(nsACString& aName) {
2604 AssertIsOnMainThread();
2606 CopyUTF16toUTF8(mWebSocket->mURI, aName);
2607 return NS_OK;
2610 NS_IMETHODIMP
2611 WebSocketImpl::IsPending(bool* aValue) {
2612 AssertIsOnTargetThread();
2614 int64_t readyState = mWebSocket->ReadyState();
2615 *aValue = (readyState != WebSocket::CLOSED);
2616 return NS_OK;
2619 NS_IMETHODIMP
2620 WebSocketImpl::GetStatus(nsresult* aStatus) {
2621 AssertIsOnTargetThread();
2623 *aStatus = NS_OK;
2624 return NS_OK;
2627 namespace {
2629 class CancelRunnable final : public MainThreadWorkerRunnable {
2630 public:
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());
2639 private:
2640 RefPtr<WebSocketImpl> mImpl;
2643 } // namespace
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,
2659 // etc.
2660 NS_IMETHODIMP
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;
2671 return NS_OK;
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) {
2683 return NS_OK;
2686 int64_t readyState = mWebSocket->ReadyState();
2687 if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
2688 return NS_OK;
2691 RefPtr<WebSocketImpl> self(this);
2692 return CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY);
2695 NS_IMETHODIMP
2696 WebSocketImpl::Suspend() {
2697 AssertIsOnMainThread();
2698 return NS_ERROR_NOT_IMPLEMENTED;
2701 NS_IMETHODIMP
2702 WebSocketImpl::Resume() {
2703 AssertIsOnMainThread();
2704 return NS_ERROR_NOT_IMPLEMENTED;
2707 NS_IMETHODIMP
2708 WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup) {
2709 AssertIsOnMainThread();
2711 *aLoadGroup = nullptr;
2713 if (mIsMainThread) {
2714 nsCOMPtr<Document> doc = mWebSocket->GetDocumentIfCurrent();
2715 if (doc) {
2716 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2719 return NS_OK;
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();
2731 if (!window) {
2732 return NS_OK;
2735 Document* doc = window->GetExtantDoc();
2736 if (doc) {
2737 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2740 return NS_OK;
2743 NS_IMETHODIMP
2744 WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup) {
2745 AssertIsOnMainThread();
2746 return NS_ERROR_UNEXPECTED;
2749 NS_IMETHODIMP
2750 WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags) {
2751 AssertIsOnMainThread();
2753 *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
2754 return NS_OK;
2757 NS_IMETHODIMP
2758 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags) {
2759 AssertIsOnMainThread();
2761 // we won't change the load flags at all.
2762 return NS_OK;
2765 NS_IMETHODIMP
2766 WebSocketImpl::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
2767 return GetTRRModeImpl(aTRRMode);
2770 NS_IMETHODIMP
2771 WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
2772 return SetTRRModeImpl(aTRRMode);
2775 namespace {
2777 class WorkerRunnableDispatcher final : public WorkerRunnable {
2778 RefPtr<WebSocketImpl> mWebSocketImpl;
2780 public:
2781 WorkerRunnableDispatcher(WebSocketImpl* aImpl,
2782 ThreadSafeWorkerRef* aWorkerRef,
2783 already_AddRefed<nsIRunnable> aEvent)
2784 : WorkerRunnable(aWorkerRef->Private(), "WorkerRunnableDispatcher",
2785 WorkerThread),
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!");
2795 return true;
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).
2809 return true;
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).
2820 private:
2821 nsCOMPtr<nsIRunnable> mEvent;
2824 } // namespace
2826 NS_IMETHODIMP
2827 WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
2828 nsCOMPtr<nsIRunnable> event(aEvent);
2829 return Dispatch(event.forget(), aFlags);
2832 NS_IMETHODIMP
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) {
2843 return NS_OK;
2846 MOZ_DIAGNOSTIC_ASSERT(mWorkerRef);
2848 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
2849 // runnable.
2850 RefPtr<WorkerRunnableDispatcher> event =
2851 new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget());
2853 if (!event->Dispatch()) {
2854 return NS_ERROR_FAILURE;
2857 return NS_OK;
2860 NS_IMETHODIMP
2861 WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
2862 return NS_ERROR_NOT_IMPLEMENTED;
2865 NS_IMETHODIMP
2866 WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
2867 return NS_ERROR_NOT_IMPLEMENTED;
2870 NS_IMETHODIMP
2871 WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
2872 return NS_ERROR_NOT_IMPLEMENTED;
2875 NS_IMETHODIMP
2876 WebSocketImpl::IsOnCurrentThread(bool* aResult) {
2877 *aResult = IsTargetThread();
2878 return NS_OK;
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;
2902 if (globalObject) {
2903 principal = globalObject->PrincipalOrNull();
2906 nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(globalObject);
2907 if (!innerWindow) {
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
2910 // considered.
2911 if (NS_WARN_IF(!principal)) {
2912 return NS_OK;
2914 *aValue = principal->SchemeIs("https");
2915 return NS_OK;
2918 RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
2919 if (NS_WARN_IF(!windowContext)) {
2920 return NS_ERROR_DOM_SECURITY_ERR;
2923 while (true) {
2924 if (windowContext->GetIsSecure()) {
2925 *aValue = true;
2926 return NS_OK;
2929 if (windowContext->IsTop()) {
2930 break;
2931 } else {
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();
2942 return NS_OK;
2945 } // namespace mozilla::dom