Backed out 22 changesets (bug 1839396) for causing build bustages on js/Printer.h...
[gecko.git] / dom / websocket / WebSocket.cpp
blob82bdf77e42352a338134e1ea5d3fb4440c3c7c41
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 "mozilla/dom/WebSocketBinding.h"
9 #include "mozilla/net/WebSocketChannel.h"
11 #include "js/ColumnNumber.h" // JS::ColumnNumberZeroOrigin
12 #include "jsapi.h"
13 #include "jsfriendapi.h"
14 #include "mozilla/Atomics.h"
15 #include "mozilla/BasePrincipal.h"
16 #include "mozilla/DOMEventTargetHelper.h"
17 #include "mozilla/dom/File.h"
18 #include "mozilla/dom/MessageEvent.h"
19 #include "mozilla/dom/MessageEventBinding.h"
20 #include "mozilla/dom/nsCSPContext.h"
21 #include "mozilla/dom/nsCSPUtils.h"
22 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
23 #include "mozilla/dom/nsMixedContentBlocker.h"
24 #include "mozilla/dom/ScriptSettings.h"
25 #include "mozilla/dom/SerializedStackHolder.h"
26 #include "mozilla/dom/UnionTypes.h"
27 #include "mozilla/dom/WindowContext.h"
28 #include "mozilla/dom/WorkerPrivate.h"
29 #include "mozilla/dom/WorkerRef.h"
30 #include "mozilla/dom/WorkerRunnable.h"
31 #include "mozilla/dom/WorkerScope.h"
32 #include "mozilla/StaticPrefs_dom.h"
33 #include "mozilla/LoadInfo.h"
34 #include "nsIScriptGlobalObject.h"
35 #include "mozilla/dom/Document.h"
36 #include "nsXPCOM.h"
37 #include "nsContentUtils.h"
38 #include "nsError.h"
39 #include "nsICookieJarSettings.h"
40 #include "nsIScriptObjectPrincipal.h"
41 #include "nsIURL.h"
42 #include "nsThreadUtils.h"
43 #include "nsIPromptFactory.h"
44 #include "nsIWindowWatcher.h"
45 #include "nsIPrompt.h"
46 #include "nsIStringBundle.h"
47 #include "nsIConsoleService.h"
48 #include "mozilla/dom/CloseEvent.h"
49 #include "mozilla/net/WebSocketEventService.h"
50 #include "nsJSUtils.h"
51 #include "nsIScriptError.h"
52 #include "nsNetUtil.h"
53 #include "nsIAuthPrompt.h"
54 #include "nsIAuthPrompt2.h"
55 #include "nsILoadGroup.h"
56 #include "mozilla/Preferences.h"
57 #include "xpcpublic.h"
58 #include "nsContentPolicyUtils.h"
59 #include "nsWrapperCacheInlines.h"
60 #include "nsIObserverService.h"
61 #include "nsIEventTarget.h"
62 #include "nsIInterfaceRequestor.h"
63 #include "nsIObserver.h"
64 #include "nsIRequest.h"
65 #include "nsIThreadRetargetableRequest.h"
66 #include "nsIWebSocketChannel.h"
67 #include "nsIWebSocketListener.h"
68 #include "nsProxyRelease.h"
69 #include "nsWeakReference.h"
70 #include "nsIWebSocketImpl.h"
72 #define OPEN_EVENT_STRING u"open"_ns
73 #define MESSAGE_EVENT_STRING u"message"_ns
74 #define ERROR_EVENT_STRING u"error"_ns
75 #define CLOSE_EVENT_STRING u"close"_ns
77 using namespace mozilla::net;
79 namespace mozilla::dom {
81 class WebSocketImpl;
83 // This class is responsible for proxying nsIObserver and nsIWebSocketImpl
84 // interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on
85 // main thread, so we can let it support weak reference.
86 class WebSocketImplProxy final : public nsIObserver,
87 public nsSupportsWeakReference,
88 public nsIWebSocketImpl {
89 public:
90 NS_DECL_ISUPPORTS
91 NS_DECL_NSIOBSERVER
92 NS_DECL_NSIWEBSOCKETIMPL
94 explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) {
95 MOZ_ASSERT(NS_IsMainThread());
98 void Disconnect() {
99 MOZ_ASSERT(NS_IsMainThread());
101 mOwner = nullptr;
104 private:
105 ~WebSocketImplProxy() = default;
107 RefPtr<WebSocketImpl> mOwner;
110 class WebSocketImpl final : public nsIInterfaceRequestor,
111 public nsIWebSocketListener,
112 public nsIObserver,
113 public nsIRequest,
114 public nsISerialEventTarget,
115 public nsIWebSocketImpl {
116 public:
117 NS_DECL_NSIINTERFACEREQUESTOR
118 NS_DECL_NSIWEBSOCKETLISTENER
119 NS_DECL_NSIOBSERVER
120 NS_DECL_NSIREQUEST
121 NS_DECL_THREADSAFE_ISUPPORTS
122 NS_DECL_NSIEVENTTARGET_FULL
123 NS_DECL_NSIWEBSOCKETIMPL
125 explicit WebSocketImpl(WebSocket* aWebSocket)
126 : mWebSocket(aWebSocket),
127 mIsServerSide(false),
128 mSecure(false),
129 mOnCloseScheduled(false),
130 mFailed(false),
131 mDisconnectingOrDisconnected(false),
132 mCloseEventWasClean(false),
133 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
134 mPort(0),
135 mScriptLine(0),
136 mScriptColumn(0),
137 mInnerWindowID(0),
138 mPrivateBrowsing(false),
139 mIsChromeContext(false),
140 mIsMainThread(true),
141 mMutex("WebSocketImpl::mMutex"),
142 mWorkerShuttingDown(false) {
143 if (!NS_IsMainThread()) {
144 mIsMainThread = false;
148 void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }
150 bool IsTargetThread() const;
152 nsresult Init(JSContext* aCx, bool aIsSecure, nsIPrincipal* aPrincipal,
153 const Maybe<ClientInfo>& aClientInfo,
154 nsICSPEventListener* aCSPEventListener, bool aIsServerSide,
155 const nsAString& aURL, nsTArray<nsString>& aProtocolArray,
156 const nsACString& aScriptFile, uint32_t aScriptLine,
157 uint32_t aScriptColumn);
159 nsresult AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
160 nsITransportProvider* aTransportProvider,
161 const nsACString& aNegotiatedExtensions,
162 UniquePtr<SerializedStackHolder> aOriginStack);
164 nsresult ParseURL(const nsAString& aURL);
165 nsresult InitializeConnection(nsIPrincipal* aPrincipal,
166 nsICookieJarSettings* aCookieJarSettings);
168 // These methods when called can release the WebSocket object
169 void FailConnection(uint16_t reasonCode,
170 const nsACString& aReasonString = ""_ns);
171 nsresult CloseConnection(uint16_t reasonCode,
172 const nsACString& aReasonString = ""_ns);
173 void Disconnect();
174 void DisconnectInternal();
176 nsresult ConsoleError();
177 void PrintErrorOnConsole(const char* aBundleURI, const char* aError,
178 nsTArray<nsString>&& aFormatStrings);
180 nsresult DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) const;
182 // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
183 nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
184 nsresult aStatusCode);
185 // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
186 void DispatchConnectionCloseEvents();
188 nsresult UpdateURI();
190 void AddRefObject();
191 void ReleaseObject();
193 bool RegisterWorkerRef(WorkerPrivate* aWorkerPrivate);
194 void UnregisterWorkerRef();
196 nsresult CancelInternal();
198 nsresult IsSecure(bool* aValue);
200 RefPtr<WebSocket> mWebSocket;
202 nsCOMPtr<nsIWebSocketChannel> mChannel;
204 bool mIsServerSide; // True if we're implementing the server side of a
205 // websocket connection
207 bool mSecure; // if true it is using SSL and the wss scheme,
208 // otherwise it is using the ws scheme with no SSL
210 bool mOnCloseScheduled;
211 bool mFailed;
212 Atomic<bool> mDisconnectingOrDisconnected;
214 // Set attributes of DOM 'onclose' message
215 bool mCloseEventWasClean;
216 nsString mCloseEventReason;
217 uint16_t mCloseEventCode;
219 nsCString mAsciiHost; // hostname
220 uint32_t mPort;
221 nsCString mResource; // [filepath[?query]]
222 nsString mUTF16Origin;
224 nsCString mURI;
225 nsCString mRequestedProtocolList;
227 nsWeakPtr mOriginDocument;
229 // Web Socket owner information:
230 // - the script file name, UTF8 encoded.
231 // - source code line number and column number where the Web Socket object
232 // was constructed.
233 // - the ID of the Web Socket owner window. Note that this may not
234 // be the same as the inner window where the script lives.
235 // e.g within iframes
236 // These attributes are used for error reporting.
237 nsCString mScriptFile;
238 uint32_t mScriptLine;
239 uint32_t mScriptColumn;
240 uint64_t mInnerWindowID;
241 bool mPrivateBrowsing;
242 bool mIsChromeContext;
244 RefPtr<ThreadSafeWorkerRef> mWorkerRef;
246 nsWeakPtr mWeakLoadGroup;
248 bool mIsMainThread;
250 // This mutex protects mWorkerShuttingDown.
251 mozilla::Mutex mMutex;
252 bool mWorkerShuttingDown MOZ_GUARDED_BY(mMutex);
254 RefPtr<WebSocketEventService> mService;
255 nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
257 // For dispatching runnables to main thread.
258 nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
260 RefPtr<WebSocketImplProxy> mImplProxy;
262 private:
263 ~WebSocketImpl() {
264 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread ||
265 mDisconnectingOrDisconnected);
267 // If we threw during Init we never called disconnect
268 if (!mDisconnectingOrDisconnected) {
269 Disconnect();
274 NS_IMPL_ISUPPORTS(WebSocketImplProxy, nsIObserver, nsISupportsWeakReference,
275 nsIWebSocketImpl)
277 NS_IMETHODIMP
278 WebSocketImplProxy::Observe(nsISupports* aSubject, const char* aTopic,
279 const char16_t* aData) {
280 if (!mOwner) {
281 return NS_OK;
284 return mOwner->Observe(aSubject, aTopic, aData);
287 NS_IMETHODIMP
288 WebSocketImplProxy::SendMessage(const nsAString& aMessage) {
289 if (!mOwner) {
290 return NS_OK;
293 return mOwner->SendMessage(aMessage);
296 NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener,
297 nsIObserver, nsIRequest, nsIEventTarget, nsISerialEventTarget,
298 nsIWebSocketImpl)
300 class CallDispatchConnectionCloseEvents final : public DiscardableRunnable {
301 public:
302 explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
303 : DiscardableRunnable("dom::CallDispatchConnectionCloseEvents"),
304 mWebSocketImpl(aWebSocketImpl) {
305 aWebSocketImpl->AssertIsOnTargetThread();
308 NS_IMETHOD Run() override {
309 mWebSocketImpl->AssertIsOnTargetThread();
310 mWebSocketImpl->DispatchConnectionCloseEvents();
311 return NS_OK;
314 private:
315 RefPtr<WebSocketImpl> mWebSocketImpl;
318 //-----------------------------------------------------------------------------
319 // WebSocketImpl
320 //-----------------------------------------------------------------------------
322 namespace {
324 class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable {
325 public:
326 PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI,
327 const char* aError,
328 nsTArray<nsString>&& aFormatStrings)
329 : WorkerMainThreadRunnable(aImpl->mWorkerRef->Private(),
330 "WebSocket :: print error on console"_ns),
331 mImpl(aImpl),
332 mBundleURI(aBundleURI),
333 mError(aError),
334 mFormatStrings(std::move(aFormatStrings)) {}
336 bool MainThreadRun() override {
337 mImpl->PrintErrorOnConsole(mBundleURI, mError, std::move(mFormatStrings));
338 return true;
341 private:
342 // Raw pointer because this runnable is sync.
343 WebSocketImpl* mImpl;
345 const char* mBundleURI;
346 const char* mError;
347 nsTArray<nsString> mFormatStrings;
350 } // namespace
352 void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI,
353 const char* aError,
354 nsTArray<nsString>&& aFormatStrings) {
355 // This method must run on the main thread.
357 if (!NS_IsMainThread()) {
358 MOZ_ASSERT(mWorkerRef);
360 RefPtr<PrintErrorOnConsoleRunnable> runnable =
361 new PrintErrorOnConsoleRunnable(this, aBundleURI, aError,
362 std::move(aFormatStrings));
363 ErrorResult rv;
364 runnable->Dispatch(Killing, rv);
365 // XXXbz this seems totally broken. We should be propagating this out, but
366 // none of our callers really propagate anything usefully. Come to think of
367 // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget
368 // runnable??
369 rv.SuppressException();
370 return;
373 nsresult rv;
374 nsCOMPtr<nsIStringBundleService> bundleService =
375 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
376 NS_ENSURE_SUCCESS_VOID(rv);
378 nsCOMPtr<nsIStringBundle> strBundle;
379 rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
380 NS_ENSURE_SUCCESS_VOID(rv);
382 nsCOMPtr<nsIConsoleService> console(
383 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
384 NS_ENSURE_SUCCESS_VOID(rv);
386 nsCOMPtr<nsIScriptError> errorObject(
387 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
388 NS_ENSURE_SUCCESS_VOID(rv);
390 // Localize the error message
391 nsAutoString message;
392 if (!aFormatStrings.IsEmpty()) {
393 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
394 } else {
395 rv = strBundle->GetStringFromName(aError, message);
397 NS_ENSURE_SUCCESS_VOID(rv);
399 if (mInnerWindowID) {
400 rv = errorObject->InitWithWindowID(
401 message, NS_ConvertUTF8toUTF16(mScriptFile), u""_ns, mScriptLine,
402 mScriptColumn, nsIScriptError::errorFlag, "Web Socket"_ns,
403 mInnerWindowID);
404 } else {
405 rv =
406 errorObject->Init(message, NS_ConvertUTF8toUTF16(mScriptFile), u""_ns,
407 mScriptLine, mScriptColumn, nsIScriptError::errorFlag,
408 "Web Socket"_ns, mPrivateBrowsing, mIsChromeContext);
411 NS_ENSURE_SUCCESS_VOID(rv);
413 // print the error message directly to the JS console
414 rv = console->LogMessage(errorObject);
415 NS_ENSURE_SUCCESS_VOID(rv);
418 namespace {
420 class CancelWebSocketRunnable final : public Runnable {
421 public:
422 CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
423 const nsACString& aReasonString)
424 : Runnable("dom::CancelWebSocketRunnable"),
425 mChannel(aChannel),
426 mReasonCode(aReasonCode),
427 mReasonString(aReasonString) {}
429 NS_IMETHOD Run() override {
430 nsresult rv = mChannel->Close(mReasonCode, mReasonString);
431 if (NS_FAILED(rv)) {
432 NS_WARNING("Failed to dispatch the close message");
434 return NS_OK;
437 private:
438 nsCOMPtr<nsIWebSocketChannel> mChannel;
439 uint16_t mReasonCode;
440 nsCString mReasonString;
443 class MOZ_STACK_CLASS MaybeDisconnect {
444 public:
445 explicit MaybeDisconnect(WebSocketImpl* aImpl) : mImpl(aImpl) {}
447 ~MaybeDisconnect() {
448 bool toDisconnect = false;
451 MutexAutoLock lock(mImpl->mMutex);
452 toDisconnect = mImpl->mWorkerShuttingDown;
455 if (toDisconnect) {
456 mImpl->Disconnect();
460 private:
461 WebSocketImpl* mImpl;
464 class CloseConnectionRunnable final : public Runnable {
465 public:
466 CloseConnectionRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode,
467 const nsACString& aReasonString)
468 : Runnable("dom::CloseConnectionRunnable"),
469 mImpl(aImpl),
470 mReasonCode(aReasonCode),
471 mReasonString(aReasonString) {}
473 NS_IMETHOD Run() override {
474 return mImpl->CloseConnection(mReasonCode, mReasonString);
477 private:
478 RefPtr<WebSocketImpl> mImpl;
479 uint16_t mReasonCode;
480 const nsCString mReasonString;
483 } // namespace
485 nsresult WebSocketImpl::CloseConnection(uint16_t aReasonCode,
486 const nsACString& aReasonString) {
487 if (!IsTargetThread()) {
488 nsCOMPtr<nsIRunnable> runnable =
489 new CloseConnectionRunnable(this, aReasonCode, aReasonString);
490 return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
493 AssertIsOnTargetThread();
495 if (mDisconnectingOrDisconnected) {
496 return NS_OK;
499 // If this method is called because the worker is going away, we will not
500 // receive the OnStop() method and we have to disconnect the WebSocket and
501 // release the ThreadSafeWorkerRef.
502 MaybeDisconnect md(this);
504 uint16_t readyState = mWebSocket->ReadyState();
505 if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
506 return NS_OK;
509 // The common case...
510 if (mChannel) {
511 mWebSocket->SetReadyState(WebSocket::CLOSING);
513 // The channel has to be closed on the main-thread.
515 if (NS_IsMainThread()) {
516 return mChannel->Close(aReasonCode, aReasonString);
519 RefPtr<CancelWebSocketRunnable> runnable =
520 new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
521 return NS_DispatchToMainThread(runnable);
524 // No channel, but not disconnected: canceled or failed early
525 MOZ_ASSERT(readyState == WebSocket::CONNECTING,
526 "Should only get here for early websocket cancel/error");
528 // Server won't be sending us a close code, so use what's passed in here.
529 mCloseEventCode = aReasonCode;
530 CopyUTF8toUTF16(aReasonString, mCloseEventReason);
532 mWebSocket->SetReadyState(WebSocket::CLOSING);
534 ScheduleConnectionCloseEvents(
535 nullptr, (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
536 aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY)
537 ? NS_OK
538 : NS_ERROR_FAILURE);
540 return NS_OK;
543 nsresult WebSocketImpl::ConsoleError() {
544 AssertIsOnTargetThread();
547 MutexAutoLock lock(mMutex);
548 if (mWorkerShuttingDown) {
549 // Too late to report anything, bail out.
550 return NS_OK;
554 nsTArray<nsString> formatStrings;
555 CopyUTF8toUTF16(mURI, *formatStrings.AppendElement());
557 if (mWebSocket->ReadyState() < WebSocket::OPEN) {
558 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
559 "connectionFailure", std::move(formatStrings));
560 } else {
561 PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
562 "netInterrupt", std::move(formatStrings));
564 /// todo some specific errors - like for message too large
565 return NS_OK;
568 void WebSocketImpl::FailConnection(uint16_t aReasonCode,
569 const nsACString& aReasonString) {
570 AssertIsOnTargetThread();
572 if (mDisconnectingOrDisconnected) {
573 return;
576 ConsoleError();
577 mFailed = true;
578 CloseConnection(aReasonCode, aReasonString);
580 if (NS_IsMainThread() && mImplProxy) {
581 mImplProxy->Disconnect();
582 mImplProxy = nullptr;
586 namespace {
588 class DisconnectInternalRunnable final : public WorkerMainThreadRunnable {
589 public:
590 explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
591 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
592 "WebSocket :: disconnect"_ns),
593 mImpl(aImpl) {}
595 bool MainThreadRun() override {
596 mImpl->DisconnectInternal();
597 return true;
600 private:
601 // A raw pointer because this runnable is sync.
602 WebSocketImpl* mImpl;
605 } // namespace
607 void WebSocketImpl::Disconnect() {
608 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
610 if (mDisconnectingOrDisconnected) {
611 return;
614 // DontKeepAliveAnyMore() and DisconnectInternal() can release the object. So
615 // hold a reference to this until the end of the method.
616 RefPtr<WebSocketImpl> kungfuDeathGrip = this;
618 // Disconnect can be called from some control event (such as a callback from
619 // StrongWorkerRef). This will be schedulated before any other sync/async
620 // runnable. In order to prevent some double Disconnect() calls, we use this
621 // boolean.
622 mDisconnectingOrDisconnected = true;
624 // DisconnectInternal touches observers and nsILoadGroup and it must run on
625 // the main thread.
627 if (NS_IsMainThread()) {
628 DisconnectInternal();
630 // If we haven't called WebSocket::DisconnectFromOwner yet, update
631 // web socket count here.
632 if (mWebSocket->GetOwner()) {
633 mWebSocket->GetOwner()->UpdateWebSocketCount(-1);
635 } else {
636 RefPtr<DisconnectInternalRunnable> runnable =
637 new DisconnectInternalRunnable(this);
638 ErrorResult rv;
639 runnable->Dispatch(Killing, rv);
640 // XXXbz this seems totally broken. We should be propagating this out, but
641 // where to, exactly?
642 rv.SuppressException();
645 NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget());
646 NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget());
648 mWebSocket->DontKeepAliveAnyMore();
649 mWebSocket->mImpl = nullptr;
651 if (mWorkerRef) {
652 UnregisterWorkerRef();
655 // We want to release the WebSocket in the correct thread.
656 mWebSocket = nullptr;
659 void WebSocketImpl::DisconnectInternal() {
660 AssertIsOnMainThread();
662 nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
663 if (loadGroup) {
664 loadGroup->RemoveRequest(this, nullptr, NS_OK);
665 // mWeakLoadGroup has to be release on main-thread because WeakReferences
666 // are not thread-safe.
667 mWeakLoadGroup = nullptr;
670 if (!mWorkerRef) {
671 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
672 if (os) {
673 os->RemoveObserver(mImplProxy, DOM_WINDOW_DESTROYED_TOPIC);
674 os->RemoveObserver(mImplProxy, DOM_WINDOW_FROZEN_TOPIC);
678 if (mImplProxy) {
679 mImplProxy->Disconnect();
680 mImplProxy = nullptr;
684 //-----------------------------------------------------------------------------
685 // WebSocketImpl::nsIWebSocketImpl
686 //-----------------------------------------------------------------------------
688 NS_IMETHODIMP
689 WebSocketImpl::SendMessage(const nsAString& aMessage) {
690 nsString message(aMessage);
691 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
692 "WebSocketImpl::SendMessage",
693 [self = RefPtr<WebSocketImpl>(this), message = std::move(message)]() {
694 ErrorResult IgnoredErrorResult;
695 self->mWebSocket->Send(message, IgnoredErrorResult);
697 return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
700 //-----------------------------------------------------------------------------
701 // WebSocketImpl::nsIWebSocketListener methods:
702 //-----------------------------------------------------------------------------
704 nsresult WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg,
705 bool isBinary) const {
706 AssertIsOnTargetThread();
708 if (mDisconnectingOrDisconnected) {
709 return NS_OK;
712 int16_t readyState = mWebSocket->ReadyState();
713 if (readyState == WebSocket::CLOSED) {
714 NS_ERROR("Received message after CLOSED");
715 return NS_ERROR_UNEXPECTED;
718 if (readyState == WebSocket::OPEN) {
719 // Dispatch New Message
720 nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
721 if (NS_FAILED(rv)) {
722 NS_WARNING("Failed to dispatch the message event");
725 return NS_OK;
728 // CLOSING should be the only other state where it's possible to get msgs
729 // from channel: Spec says to drop them.
730 MOZ_ASSERT(readyState == WebSocket::CLOSING,
731 "Received message while CONNECTING or CLOSED");
732 return NS_OK;
735 NS_IMETHODIMP
736 WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
737 const nsACString& aMsg) {
738 AssertIsOnTargetThread();
740 if (mDisconnectingOrDisconnected) {
741 return NS_OK;
744 return DoOnMessageAvailable(aMsg, false);
747 NS_IMETHODIMP
748 WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
749 const nsACString& aMsg) {
750 AssertIsOnTargetThread();
752 if (mDisconnectingOrDisconnected) {
753 return NS_OK;
756 return DoOnMessageAvailable(aMsg, true);
759 NS_IMETHODIMP
760 WebSocketImpl::OnStart(nsISupports* aContext) {
761 AssertIsOnTargetThread();
763 if (mDisconnectingOrDisconnected) {
764 return NS_OK;
767 int16_t readyState = mWebSocket->ReadyState();
769 // This is the only function that sets OPEN, and should be called only once
770 MOZ_ASSERT(readyState != WebSocket::OPEN,
771 "readyState already OPEN! OnStart called twice?");
773 // Nothing to do if we've already closed/closing
774 if (readyState != WebSocket::CONNECTING) {
775 return NS_OK;
778 // Attempt to kill "ghost" websocket: but usually too early for check to fail
779 nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
780 if (NS_FAILED(rv)) {
781 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
782 return rv;
785 if (!mRequestedProtocolList.IsEmpty()) {
786 rv = mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
787 MOZ_ASSERT(NS_SUCCEEDED(rv));
790 rv = mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
791 MOZ_ASSERT(NS_SUCCEEDED(rv));
792 UpdateURI();
794 mWebSocket->SetReadyState(WebSocket::OPEN);
796 mService->WebSocketOpened(
797 mChannel->Serial(), mInnerWindowID, mWebSocket->mEffectiveURL,
798 mWebSocket->mEstablishedProtocol, mWebSocket->mEstablishedExtensions,
799 mChannel->HttpChannelId());
801 // Let's keep the object alive because the webSocket can be CCed in the
802 // onopen callback.
803 RefPtr<WebSocket> webSocket = mWebSocket;
805 // Call 'onopen'
806 rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING);
807 if (NS_FAILED(rv)) {
808 NS_WARNING("Failed to dispatch the open event");
811 webSocket->UpdateMustKeepAlive();
812 return NS_OK;
815 NS_IMETHODIMP
816 WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode) {
817 AssertIsOnTargetThread();
819 if (mDisconnectingOrDisconnected) {
820 return NS_OK;
823 // We can be CONNECTING here if connection failed.
824 // We can be OPEN if we have encountered a fatal protocol error
825 // We can be CLOSING if close() was called and/or server initiated close.
826 MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
827 "Shouldn't already be CLOSED when OnStop called");
829 return ScheduleConnectionCloseEvents(aContext, aStatusCode);
832 nsresult WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
833 nsresult aStatusCode) {
834 AssertIsOnTargetThread();
836 // no-op if some other code has already initiated close event
837 if (!mOnCloseScheduled) {
838 mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
840 if (aStatusCode == NS_BASE_STREAM_CLOSED) {
841 // don't generate an error event just because of an unclean close
842 aStatusCode = NS_OK;
845 if (aStatusCode == NS_ERROR_NET_INADEQUATE_SECURITY) {
846 // TLS negotiation failed so we need to set status code to 1015.
847 mCloseEventCode = 1015;
850 if (NS_FAILED(aStatusCode)) {
851 ConsoleError();
852 mFailed = true;
855 mOnCloseScheduled = true;
857 NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
860 return NS_OK;
863 NS_IMETHODIMP
864 WebSocketImpl::OnAcknowledge(nsISupports* aContext, uint32_t aSize) {
865 AssertIsOnTargetThread();
867 if (mDisconnectingOrDisconnected) {
868 return NS_OK;
871 MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());
872 if (aSize > mWebSocket->mOutgoingBufferedAmount.value()) {
873 return NS_ERROR_UNEXPECTED;
876 CheckedUint64 outgoingBufferedAmount = mWebSocket->mOutgoingBufferedAmount;
877 outgoingBufferedAmount -= aSize;
878 if (!outgoingBufferedAmount.isValid()) {
879 return NS_ERROR_UNEXPECTED;
882 mWebSocket->mOutgoingBufferedAmount = outgoingBufferedAmount;
883 MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid());
885 return NS_OK;
888 NS_IMETHODIMP
889 WebSocketImpl::OnServerClose(nsISupports* aContext, uint16_t aCode,
890 const nsACString& aReason) {
891 AssertIsOnTargetThread();
893 if (mDisconnectingOrDisconnected) {
894 return NS_OK;
897 int16_t readyState = mWebSocket->ReadyState();
899 MOZ_ASSERT(readyState != WebSocket::CONNECTING,
900 "Received server close before connected?");
901 MOZ_ASSERT(readyState != WebSocket::CLOSED,
902 "Received server close after already closed!");
904 // store code/string for onclose DOM event
905 mCloseEventCode = aCode;
906 CopyUTF8toUTF16(aReason, mCloseEventReason);
908 if (readyState == WebSocket::OPEN) {
909 // Server initiating close.
910 // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
911 // typically echos the status code it received".
912 // But never send certain codes, per section 7.4.1
913 if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
914 CloseConnection(0, ""_ns);
915 } else {
916 CloseConnection(aCode, aReason);
918 } else {
919 // We initiated close, and server has replied: OnStop does rest of the work.
920 MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
923 return NS_OK;
926 NS_IMETHODIMP
927 WebSocketImpl::OnError() {
928 if (!IsTargetThread()) {
929 return Dispatch(
930 NS_NewRunnableFunction("dom::FailConnectionRunnable",
931 [self = RefPtr{this}]() {
932 self->FailConnection(
933 nsIWebSocketChannel::CLOSE_ABNORMAL);
935 NS_DISPATCH_NORMAL);
938 AssertIsOnTargetThread();
939 FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
940 return NS_OK;
943 //-----------------------------------------------------------------------------
944 // WebSocketImpl::nsIInterfaceRequestor
945 //-----------------------------------------------------------------------------
947 NS_IMETHODIMP
948 WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult) {
949 AssertIsOnMainThread();
951 if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
952 return NS_ERROR_FAILURE;
955 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
956 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
957 nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent();
958 if (!win) {
959 return NS_ERROR_NOT_AVAILABLE;
962 nsresult rv;
963 nsCOMPtr<nsIPromptFactory> wwatch =
964 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
965 NS_ENSURE_SUCCESS(rv, rv);
967 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow();
968 return wwatch->GetPrompt(outerWindow, aIID, aResult);
971 return QueryInterface(aIID, aResult);
974 ////////////////////////////////////////////////////////////////////////////////
975 // WebSocket
976 ////////////////////////////////////////////////////////////////////////////////
978 WebSocket::WebSocket(nsIGlobalObject* aGlobal)
979 : DOMEventTargetHelper(aGlobal),
980 mIsMainThread(true),
981 mKeepingAlive(false),
982 mCheckMustKeepAlive(true),
983 mOutgoingBufferedAmount(0),
984 mBinaryType(dom::BinaryType::Blob),
985 mMutex("WebSocket::mMutex"),
986 mReadyState(CONNECTING) {
987 MOZ_ASSERT(aGlobal);
989 mImpl = new WebSocketImpl(this);
990 mIsMainThread = mImpl->mIsMainThread;
993 WebSocket::~WebSocket() = default;
995 mozilla::Maybe<EventCallbackDebuggerNotificationType>
996 WebSocket::GetDebuggerNotificationType() const {
997 return mozilla::Some(EventCallbackDebuggerNotificationType::Websocket);
1000 JSObject* WebSocket::WrapObject(JSContext* cx,
1001 JS::Handle<JSObject*> aGivenProto) {
1002 return WebSocket_Binding::Wrap(cx, this, aGivenProto);
1005 //---------------------------------------------------------------------------
1006 // WebIDL
1007 //---------------------------------------------------------------------------
1009 // Constructor:
1010 already_AddRefed<WebSocket> WebSocket::Constructor(
1011 const GlobalObject& aGlobal, const nsAString& aUrl,
1012 const StringOrStringSequence& aProtocols, ErrorResult& aRv) {
1013 if (aProtocols.IsStringSequence()) {
1014 return WebSocket::ConstructorCommon(
1015 aGlobal, aUrl, aProtocols.GetAsStringSequence(), nullptr, ""_ns, aRv);
1018 Sequence<nsString> protocols;
1019 if (!protocols.AppendElement(aProtocols.GetAsString(), fallible)) {
1020 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1021 return nullptr;
1024 return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, ""_ns,
1025 aRv);
1028 already_AddRefed<WebSocket> WebSocket::CreateServerWebSocket(
1029 const GlobalObject& aGlobal, const nsAString& aUrl,
1030 const Sequence<nsString>& aProtocols,
1031 nsITransportProvider* aTransportProvider,
1032 const nsAString& aNegotiatedExtensions, ErrorResult& aRv) {
1033 return WebSocket::ConstructorCommon(
1034 aGlobal, aUrl, aProtocols, aTransportProvider,
1035 NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
1038 namespace {
1040 // This class is used to clear any exception.
1041 class MOZ_STACK_CLASS ClearException {
1042 public:
1043 explicit ClearException(JSContext* aCx) : mCx(aCx) {}
1045 ~ClearException() { JS_ClearPendingException(mCx); }
1047 private:
1048 JSContext* mCx;
1051 class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable {
1052 public:
1053 WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
1054 const nsACString& aTelemetryKey)
1055 : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey) {
1056 MOZ_ASSERT(aWorkerPrivate);
1057 aWorkerPrivate->AssertIsOnWorkerThread();
1060 bool MainThreadRun() override {
1061 AssertIsOnMainThread();
1063 // Walk up to our containing page
1064 WorkerPrivate* wp = mWorkerPrivate;
1065 while (wp->GetParent()) {
1066 wp = wp->GetParent();
1069 nsPIDOMWindowInner* window = wp->GetWindow();
1070 if (window) {
1071 return InitWithWindow(window);
1074 return InitWindowless(wp);
1077 protected:
1078 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;
1080 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
1083 class InitRunnable final : public WebSocketMainThreadRunnable {
1084 public:
1085 InitRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl,
1086 const Maybe<mozilla::dom::ClientInfo>& aClientInfo,
1087 bool aIsServerSide, const nsAString& aURL,
1088 nsTArray<nsString>& aProtocolArray,
1089 const nsACString& aScriptFile, uint32_t aScriptLine,
1090 uint32_t aScriptColumn)
1091 : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
1092 mImpl(aImpl),
1093 mClientInfo(aClientInfo),
1094 mIsServerSide(aIsServerSide),
1095 mURL(aURL),
1096 mProtocolArray(aProtocolArray),
1097 mScriptFile(aScriptFile),
1098 mScriptLine(aScriptLine),
1099 mScriptColumn(aScriptColumn),
1100 mErrorCode(NS_OK) {
1101 MOZ_ASSERT(mWorkerPrivate);
1102 mWorkerPrivate->AssertIsOnWorkerThread();
1105 nsresult ErrorCode() const { return mErrorCode; }
1107 protected:
1108 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1109 AutoJSAPI jsapi;
1110 if (NS_WARN_IF(!jsapi.Init(aWindow))) {
1111 mErrorCode = NS_ERROR_FAILURE;
1112 return true;
1115 ClearException ce(jsapi.cx());
1117 Document* doc = aWindow->GetExtantDoc();
1118 if (!doc) {
1119 mErrorCode = NS_ERROR_FAILURE;
1120 return true;
1123 mErrorCode = mImpl->Init(
1124 jsapi.cx(), mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
1125 doc->NodePrincipal(), mClientInfo, mWorkerPrivate->CSPEventListener(),
1126 mIsServerSide, mURL, mProtocolArray, mScriptFile, mScriptLine,
1127 mScriptColumn);
1128 return true;
1131 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
1132 MOZ_ASSERT(NS_IsMainThread());
1133 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1135 mErrorCode =
1136 mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
1137 aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo,
1138 mWorkerPrivate->CSPEventListener(), mIsServerSide, mURL,
1139 mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
1140 return true;
1143 // Raw pointer. This worker runnable runs synchronously.
1144 WebSocketImpl* mImpl;
1146 Maybe<ClientInfo> mClientInfo;
1147 bool mIsServerSide;
1148 const nsAString& mURL;
1149 nsTArray<nsString>& mProtocolArray;
1150 nsCString mScriptFile;
1151 uint32_t mScriptLine;
1152 uint32_t mScriptColumn;
1153 nsresult mErrorCode;
1156 class ConnectRunnable final : public WebSocketMainThreadRunnable {
1157 public:
1158 ConnectRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
1159 : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns),
1160 mImpl(aImpl),
1161 mConnectionFailed(true) {
1162 MOZ_ASSERT(mWorkerPrivate);
1163 mWorkerPrivate->AssertIsOnWorkerThread();
1166 bool ConnectionFailed() const { return mConnectionFailed; }
1168 protected:
1169 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1170 Document* doc = aWindow->GetExtantDoc();
1171 if (!doc) {
1172 return true;
1175 mConnectionFailed = NS_FAILED(mImpl->InitializeConnection(
1176 doc->NodePrincipal(), mWorkerPrivate->CookieJarSettings()));
1177 return true;
1180 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
1181 MOZ_ASSERT(NS_IsMainThread());
1182 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1184 mConnectionFailed = NS_FAILED(
1185 mImpl->InitializeConnection(aTopLevelWorkerPrivate->GetPrincipal(),
1186 mWorkerPrivate->CookieJarSettings()));
1187 return true;
1190 // Raw pointer. This worker runnable runs synchronously.
1191 WebSocketImpl* mImpl;
1193 bool mConnectionFailed;
1196 class AsyncOpenRunnable final : public WebSocketMainThreadRunnable {
1197 public:
1198 explicit AsyncOpenRunnable(WebSocketImpl* aImpl,
1199 UniquePtr<SerializedStackHolder> aOriginStack)
1200 : WebSocketMainThreadRunnable(aImpl->mWorkerRef->Private(),
1201 "WebSocket :: AsyncOpen"_ns),
1202 mImpl(aImpl),
1203 mOriginStack(std::move(aOriginStack)),
1204 mErrorCode(NS_OK) {
1205 MOZ_ASSERT(mWorkerPrivate);
1206 mWorkerPrivate->AssertIsOnWorkerThread();
1209 nsresult ErrorCode() const { return mErrorCode; }
1211 protected:
1212 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override {
1213 AssertIsOnMainThread();
1214 MOZ_ASSERT(aWindow);
1216 Document* doc = aWindow->GetExtantDoc();
1217 if (!doc) {
1218 mErrorCode = NS_ERROR_FAILURE;
1219 return true;
1222 nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal();
1223 if (!principal) {
1224 mErrorCode = NS_ERROR_FAILURE;
1225 return true;
1228 uint64_t windowID = 0;
1229 if (WindowContext* wc = aWindow->GetWindowContext()) {
1230 windowID = wc->InnerWindowId();
1233 mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, ""_ns,
1234 std::move(mOriginStack));
1235 return true;
1238 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override {
1239 MOZ_ASSERT(NS_IsMainThread());
1240 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1242 mErrorCode =
1243 mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0,
1244 nullptr, ""_ns, nullptr);
1245 return true;
1248 private:
1249 // Raw pointer. This worker runs synchronously.
1250 WebSocketImpl* mImpl;
1252 UniquePtr<SerializedStackHolder> mOriginStack;
1254 nsresult mErrorCode;
1257 } // namespace
1259 // Check a protocol entry contains only valid characters
1260 bool WebSocket::IsValidProtocolString(const nsString& aValue) {
1261 // RFC 6455 (4.1): "not including separator characters as defined in RFC 2616"
1262 const char16_t illegalCharacters[] = {0x28, 0x29, 0x3C, 0x3E, 0x40, 0x2C,
1263 0x3B, 0x3A, 0x5C, 0x22, 0x2F, 0x5B,
1264 0x5D, 0x3F, 0x3D, 0x7B, 0x7D};
1266 // Cannot be empty string
1267 if (aValue.IsEmpty()) {
1268 return false;
1271 const auto* start = aValue.BeginReading();
1272 const auto* end = aValue.EndReading();
1274 auto charFilter = [&](char16_t c) {
1275 // RFC 6455 (4.1 P18): "in the range U+0021 to U+007E"
1276 if (c < 0x21 || c > 0x7E) {
1277 return true;
1280 return std::find(std::begin(illegalCharacters), std::end(illegalCharacters),
1281 c) != std::end(illegalCharacters);
1284 return std::find_if(start, end, charFilter) == end;
1287 already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
1288 const GlobalObject& aGlobal, const nsAString& aUrl,
1289 const Sequence<nsString>& aProtocols,
1290 nsITransportProvider* aTransportProvider,
1291 const nsACString& aNegotiatedExtensions, ErrorResult& aRv) {
1292 MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
1293 nsCOMPtr<nsIPrincipal> principal;
1294 nsCOMPtr<nsIPrincipal> partitionedPrincipal;
1296 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1297 if (NS_WARN_IF(!global)) {
1298 aRv.Throw(NS_ERROR_FAILURE);
1299 return nullptr;
1302 if (NS_IsMainThread()) {
1303 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1304 do_QueryInterface(aGlobal.GetAsSupports());
1305 if (!scriptPrincipal) {
1306 aRv.Throw(NS_ERROR_FAILURE);
1307 return nullptr;
1310 principal = scriptPrincipal->GetPrincipal();
1311 partitionedPrincipal = scriptPrincipal->PartitionedPrincipal();
1312 if (!principal || !partitionedPrincipal) {
1313 aRv.Throw(NS_ERROR_FAILURE);
1314 return nullptr;
1318 nsTArray<nsString> protocolArray;
1320 for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
1321 const nsString& protocolElement = aProtocols[index];
1323 // Repeated protocols are not allowed
1324 if (protocolArray.Contains(protocolElement)) {
1325 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1326 return nullptr;
1329 // Protocol string value must match constraints
1330 if (!IsValidProtocolString(protocolElement)) {
1331 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1332 return nullptr;
1335 protocolArray.AppendElement(protocolElement);
1338 RefPtr<WebSocket> webSocket = new WebSocket(global);
1339 RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;
1341 bool connectionFailed = true;
1343 if (NS_IsMainThread()) {
1344 // We're keeping track of all main thread web sockets to be able to
1345 // avoid throttling timeouts when we have active web sockets.
1346 if (webSocket->GetOwner()) {
1347 webSocket->GetOwner()->UpdateWebSocketCount(1);
1350 bool isSecure = principal->SchemeIs("https");
1351 aRv = webSocketImpl->IsSecure(&isSecure);
1352 if (NS_WARN_IF(aRv.Failed())) {
1353 return nullptr;
1356 aRv = webSocketImpl->Init(aGlobal.Context(), isSecure, principal, Nothing(),
1357 nullptr, !!aTransportProvider, aUrl,
1358 protocolArray, ""_ns, 0, 0);
1360 if (NS_WARN_IF(aRv.Failed())) {
1361 return nullptr;
1364 nsCOMPtr<Document> doc = webSocket->GetDocumentIfCurrent();
1366 // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
1367 // url parameter, so don't throw if InitializeConnection fails, and call
1368 // onerror/onclose asynchronously
1369 connectionFailed = NS_FAILED(webSocketImpl->InitializeConnection(
1370 principal, doc ? doc->CookieJarSettings() : nullptr));
1371 } else {
1372 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1373 MOZ_ASSERT(workerPrivate);
1375 uint32_t lineno;
1376 JS::ColumnNumberZeroOrigin column;
1377 JS::AutoFilename file;
1378 if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
1379 &column)) {
1380 NS_WARNING("Failed to get line number and filename in workers.");
1383 RefPtr<InitRunnable> runnable = new InitRunnable(
1384 workerPrivate, webSocketImpl,
1385 workerPrivate->GlobalScope()->GetClientInfo(), !!aTransportProvider,
1386 aUrl, protocolArray, nsDependentCString(file.get()), lineno,
1387 column.zeroOriginValue());
1388 runnable->Dispatch(Canceling, aRv);
1389 if (NS_WARN_IF(aRv.Failed())) {
1390 return nullptr;
1393 aRv = runnable->ErrorCode();
1394 if (NS_WARN_IF(aRv.Failed())) {
1395 return nullptr;
1398 if (NS_WARN_IF(!webSocketImpl->RegisterWorkerRef(workerPrivate))) {
1399 // The worker is shutting down.
1400 aRv.Throw(NS_ERROR_FAILURE);
1401 return nullptr;
1404 RefPtr<ConnectRunnable> connectRunnable =
1405 new ConnectRunnable(workerPrivate, webSocketImpl);
1406 connectRunnable->Dispatch(Canceling, aRv);
1407 if (NS_WARN_IF(aRv.Failed())) {
1408 return nullptr;
1411 connectionFailed = connectRunnable->ConnectionFailed();
1414 // It can be that we have been already disconnected because the WebSocket is
1415 // gone away while we where initializing the webSocket.
1416 if (!webSocket->mImpl) {
1417 aRv.Throw(NS_ERROR_FAILURE);
1418 return nullptr;
1421 // We don't return an error if the connection just failed. Instead we dispatch
1422 // an event.
1423 if (connectionFailed) {
1424 webSocket->mImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
1427 // If we don't have a channel, the connection is failed and onerror() will be
1428 // called asynchrounsly.
1429 if (!webSocket->mImpl->mChannel) {
1430 return webSocket.forget();
1433 class MOZ_STACK_CLASS ClearWebSocket {
1434 public:
1435 explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
1436 : mWebSocketImpl(aWebSocketImpl), mDone(false) {}
1438 void Done() { mDone = true; }
1440 ~ClearWebSocket() {
1441 if (!mDone) {
1442 mWebSocketImpl->mChannel = nullptr;
1443 mWebSocketImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
1447 WebSocketImpl* mWebSocketImpl;
1448 bool mDone;
1451 ClearWebSocket cws(webSocket->mImpl);
1453 // This operation must be done on the correct thread. The rest must run on the
1454 // main-thread.
1455 aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
1456 if (NS_WARN_IF(aRv.Failed())) {
1457 return nullptr;
1460 if (NS_IsMainThread()) {
1461 MOZ_ASSERT(principal);
1462 MOZ_ASSERT(partitionedPrincipal);
1464 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
1466 UniquePtr<SerializedStackHolder> stack;
1467 uint64_t windowID = 0;
1469 if (ownerWindow) {
1470 BrowsingContext* browsingContext = ownerWindow->GetBrowsingContext();
1471 if (browsingContext && browsingContext->WatchedByDevTools()) {
1472 stack = GetCurrentStackForNetMonitor(aGlobal.Context());
1475 if (WindowContext* wc = ownerWindow->GetWindowContext()) {
1476 windowID = wc->InnerWindowId();
1480 aRv = webSocket->mImpl->AsyncOpen(partitionedPrincipal, windowID,
1481 aTransportProvider, aNegotiatedExtensions,
1482 std::move(stack));
1483 } else {
1484 MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
1485 "not yet implemented");
1487 UniquePtr<SerializedStackHolder> stack;
1488 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1489 if (workerPrivate->IsWatchedByDevTools()) {
1490 stack = GetCurrentStackForNetMonitor(aGlobal.Context());
1493 RefPtr<AsyncOpenRunnable> runnable =
1494 new AsyncOpenRunnable(webSocket->mImpl, std::move(stack));
1495 runnable->Dispatch(Canceling, aRv);
1496 if (NS_WARN_IF(aRv.Failed())) {
1497 return nullptr;
1500 aRv = runnable->ErrorCode();
1503 if (NS_WARN_IF(aRv.Failed())) {
1504 return nullptr;
1507 // It can be that we have been already disconnected because the WebSocket is
1508 // gone away while we where initializing the webSocket.
1509 if (!webSocket->mImpl) {
1510 aRv.Throw(NS_ERROR_FAILURE);
1511 return nullptr;
1514 // Let's inform devtools about this new active WebSocket.
1515 webSocket->mImpl->mService->WebSocketCreated(
1516 webSocket->mImpl->mChannel->Serial(), webSocket->mImpl->mInnerWindowID,
1517 webSocket->mURI, webSocket->mImpl->mRequestedProtocolList);
1518 cws.Done();
1520 return webSocket.forget();
1523 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
1525 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
1526 DOMEventTargetHelper)
1527 if (tmp->mImpl) {
1528 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
1530 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1532 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, DOMEventTargetHelper)
1533 if (tmp->mImpl) {
1534 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
1535 tmp->mImpl->Disconnect();
1536 MOZ_ASSERT(!tmp->mImpl);
1538 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1540 bool WebSocket::IsCertainlyAliveForCC() const { return mKeepingAlive; }
1542 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebSocket)
1543 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
1545 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
1546 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
1548 void WebSocket::DisconnectFromOwner() {
1549 // If we haven't called WebSocketImpl::Disconnect yet, update web
1550 // socket count here.
1551 if (NS_IsMainThread() && mImpl && !mImpl->mDisconnectingOrDisconnected &&
1552 GetOwner()) {
1553 GetOwner()->UpdateWebSocketCount(-1);
1556 DOMEventTargetHelper::DisconnectFromOwner();
1558 if (mImpl) {
1559 mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
1562 DontKeepAliveAnyMore();
1565 //-----------------------------------------------------------------------------
1566 // WebSocketImpl:: initialization
1567 //-----------------------------------------------------------------------------
1569 nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
1570 nsIPrincipal* aPrincipal,
1571 const Maybe<ClientInfo>& aClientInfo,
1572 nsICSPEventListener* aCSPEventListener,
1573 bool aIsServerSide, const nsAString& aURL,
1574 nsTArray<nsString>& aProtocolArray,
1575 const nsACString& aScriptFile,
1576 uint32_t aScriptLine, uint32_t aScriptColumn) {
1577 AssertIsOnMainThread();
1578 MOZ_ASSERT(aPrincipal);
1580 mService = WebSocketEventService::GetOrCreate();
1582 // We need to keep the implementation alive in case the init disconnects it
1583 // because of some error.
1584 RefPtr<WebSocketImpl> kungfuDeathGrip = this;
1586 // Attempt to kill "ghost" websocket: but usually too early for check to fail
1587 nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness();
1588 NS_ENSURE_SUCCESS(rv, rv);
1590 // Shut down websocket if window is frozen or destroyed (only needed for
1591 // "ghost" websockets--see bug 696085)
1592 RefPtr<WebSocketImplProxy> proxy;
1593 if (mIsMainThread) {
1594 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1595 if (NS_WARN_IF(!os)) {
1596 return NS_ERROR_FAILURE;
1599 proxy = new WebSocketImplProxy(this);
1600 rv = os->AddObserver(proxy, DOM_WINDOW_DESTROYED_TOPIC, true);
1601 NS_ENSURE_SUCCESS(rv, rv);
1603 rv = os->AddObserver(proxy, DOM_WINDOW_FROZEN_TOPIC, true);
1604 NS_ENSURE_SUCCESS(rv, rv);
1607 if (!mIsMainThread) {
1608 mScriptFile = aScriptFile;
1609 mScriptLine = aScriptLine;
1610 mScriptColumn = aScriptColumn;
1611 } else {
1612 MOZ_ASSERT(aCx);
1614 uint32_t lineno;
1615 JS::ColumnNumberZeroOrigin column;
1616 JS::AutoFilename file;
1617 if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
1618 mScriptFile = file.get();
1619 mScriptLine = lineno;
1620 mScriptColumn = column.zeroOriginValue();
1624 mIsServerSide = aIsServerSide;
1626 // If we don't have aCx, we are window-less, so we don't have a
1627 // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
1628 // DedicateWorkers created by JSM.
1629 if (aCx) {
1630 if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwner()) {
1631 mInnerWindowID = ownerWindow->WindowID();
1635 mPrivateBrowsing = !!aPrincipal->OriginAttributesRef().mPrivateBrowsingId;
1636 mIsChromeContext = aPrincipal->IsSystemPrincipal();
1638 // parses the url
1639 rv = ParseURL(aURL);
1640 NS_ENSURE_SUCCESS(rv, rv);
1642 nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent();
1643 if (!originDoc) {
1644 rv = mWebSocket->CheckCurrentGlobalCorrectness();
1645 NS_ENSURE_SUCCESS(rv, rv);
1647 mOriginDocument = do_GetWeakReference(originDoc);
1649 if (!mIsServerSide) {
1650 nsCOMPtr<nsIURI> uri;
1652 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
1654 // We crash here because we are sure that mURI is a valid URI, so either
1655 // we are OOM'ing or something else bad is happening.
1656 if (NS_WARN_IF(NS_FAILED(rv))) {
1657 MOZ_CRASH();
1661 // The 'real' nsHttpChannel of the websocket gets opened in the parent.
1662 // Since we don't serialize the CSP within child and parent and also not
1663 // the context, we have to perform content policy checks here instead of
1664 // AsyncOpen().
1665 // Please note that websockets can't follow redirects, hence there is no
1666 // need to perform a CSP check after redirects.
1667 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new net::LoadInfo(
1668 aPrincipal, // loading principal
1669 aPrincipal, // triggering principal
1670 originDoc, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
1671 nsIContentPolicy::TYPE_WEBSOCKET, aClientInfo);
1673 if (aCSPEventListener) {
1674 secCheckLoadInfo->SetCspEventListener(aCSPEventListener);
1677 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
1678 rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, ""_ns, &shouldLoad,
1679 nsContentUtils::GetContentPolicy());
1680 NS_ENSURE_SUCCESS(rv, rv);
1682 if (NS_CP_REJECTED(shouldLoad)) {
1683 // Disallowed by content policy
1684 return NS_ERROR_CONTENT_BLOCKED;
1687 // If the HTTPS-Only mode is enabled, we need to upgrade the websocket
1688 // connection from ws:// to wss:// and mark it as secure.
1689 if (!mSecure && originDoc &&
1690 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
1691 originDoc->GetDocumentURI())) {
1692 nsCOMPtr<nsIURI> uri;
1693 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
1694 NS_ENSURE_SUCCESS(rv, rv);
1696 // secCheckLoadInfo is only used for the triggering principal, so this
1697 // is okay.
1698 if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) {
1699 mURI.ReplaceSubstring("ws://", "wss://");
1700 if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
1701 return NS_OK;
1703 mSecure = true;
1708 // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
1709 // In such a case we have to upgrade ws: to wss: and also update mSecure
1710 // to reflect that upgrade. Please note that we can not upgrade from ws:
1711 // to wss: before performing content policy checks because CSP needs to
1712 // send reports in case the scheme is about to be upgraded.
1713 if (!mIsServerSide && !mSecure && originDoc &&
1714 originDoc->GetUpgradeInsecureRequests(false) &&
1715 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(
1716 originDoc->GetDocumentURI())) {
1717 // let's use the old specification before the upgrade for logging
1718 AutoTArray<nsString, 2> params;
1719 CopyUTF8toUTF16(mURI, *params.AppendElement());
1721 // upgrade the request from ws:// to wss:// and mark as secure
1722 mURI.ReplaceSubstring("ws://", "wss://");
1723 if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
1724 return NS_OK;
1726 mSecure = true;
1728 params.AppendElement(u"wss"_ns);
1729 CSP_LogLocalizedStr("upgradeInsecureRequest", params,
1730 u""_ns, // aSourceFile
1731 u""_ns, // aScriptSample
1732 0, // aLineNumber
1733 0, // aColumnNumber
1734 nsIScriptError::warningFlag,
1735 "upgradeInsecureRequest"_ns, mInnerWindowID,
1736 mPrivateBrowsing);
1739 // Don't allow https:// to open ws://
1740 if (!mIsServerSide && !mSecure &&
1741 !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
1742 false) &&
1743 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
1744 mAsciiHost)) {
1745 if (aIsSecure) {
1746 return NS_ERROR_DOM_SECURITY_ERR;
1750 // Assign the sub protocol list and scan it for illegal values
1751 for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
1752 if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) {
1753 return NS_ERROR_DOM_SYNTAX_ERR;
1756 if (!mRequestedProtocolList.IsEmpty()) {
1757 mRequestedProtocolList.AppendLiteral(", ");
1760 AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
1763 if (mIsMainThread) {
1764 mImplProxy = std::move(proxy);
1766 return NS_OK;
1769 nsresult WebSocketImpl::AsyncOpen(
1770 nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
1771 nsITransportProvider* aTransportProvider,
1772 const nsACString& aNegotiatedExtensions,
1773 UniquePtr<SerializedStackHolder> aOriginStack) {
1774 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1775 MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
1777 nsCString webExposedOriginSerialization;
1778 nsresult rv = aPrincipal->GetWebExposedOriginSerialization(
1779 webExposedOriginSerialization);
1780 if (NS_FAILED(rv)) {
1781 webExposedOriginSerialization.AssignLiteral("null");
1784 if (aTransportProvider) {
1785 rv = mChannel->SetServerParameters(aTransportProvider,
1786 aNegotiatedExtensions);
1787 NS_ENSURE_SUCCESS(rv, rv);
1790 ToLowerCase(webExposedOriginSerialization);
1792 nsCOMPtr<nsIURI> uri;
1793 if (!aTransportProvider) {
1794 rv = NS_NewURI(getter_AddRefs(uri), mURI);
1795 MOZ_ASSERT(NS_SUCCEEDED(rv));
1798 rv = mChannel->AsyncOpenNative(uri, webExposedOriginSerialization,
1799 aPrincipal->OriginAttributesRef(),
1800 aInnerWindowID, this, nullptr);
1801 if (NS_WARN_IF(NS_FAILED(rv))) {
1802 return NS_ERROR_CONTENT_BLOCKED;
1805 NotifyNetworkMonitorAlternateStack(mChannel, std::move(aOriginStack));
1807 mInnerWindowID = aInnerWindowID;
1809 return NS_OK;
1812 //-----------------------------------------------------------------------------
1813 // WebSocketImpl methods:
1814 //-----------------------------------------------------------------------------
1816 class nsAutoCloseWS final {
1817 public:
1818 explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
1819 : mWebSocketImpl(aWebSocketImpl) {}
1821 ~nsAutoCloseWS() {
1822 if (!mWebSocketImpl->mChannel) {
1823 mWebSocketImpl->CloseConnection(
1824 nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
1828 private:
1829 RefPtr<WebSocketImpl> mWebSocketImpl;
1832 nsresult WebSocketImpl::InitializeConnection(
1833 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings) {
1834 AssertIsOnMainThread();
1835 MOZ_ASSERT(!mChannel, "mChannel should be null");
1837 nsCOMPtr<nsIWebSocketChannel> wsChannel;
1838 nsAutoCloseWS autoClose(this);
1839 nsresult rv;
1841 if (mSecure) {
1842 wsChannel =
1843 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
1844 } else {
1845 wsChannel =
1846 do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
1848 NS_ENSURE_SUCCESS(rv, rv);
1850 // add ourselves to the document's load group and
1851 // provide the http stack the loadgroup info too
1852 nsCOMPtr<nsILoadGroup> loadGroup;
1853 rv = GetLoadGroup(getter_AddRefs(loadGroup));
1854 if (loadGroup) {
1855 rv = wsChannel->SetLoadGroup(loadGroup);
1856 NS_ENSURE_SUCCESS(rv, rv);
1857 rv = loadGroup->AddRequest(this, nullptr);
1858 NS_ENSURE_SUCCESS(rv, rv);
1860 mWeakLoadGroup = do_GetWeakReference(loadGroup);
1863 // manually adding loadinfo to the channel since it
1864 // was not set during channel creation.
1865 nsCOMPtr<Document> doc = do_QueryReferent(mOriginDocument);
1867 // mOriginDocument has to be release on main-thread because WeakReferences
1868 // are not thread-safe.
1869 mOriginDocument = nullptr;
1871 // The TriggeringPrincipal for websockets must always be a script.
1872 // Let's make sure that the doc's principal (if a doc exists)
1873 // and aPrincipal are same origin.
1874 MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
1876 rv = wsChannel->InitLoadInfoNative(
1877 doc, doc ? doc->NodePrincipal() : aPrincipal, aPrincipal,
1878 aCookieJarSettings,
1879 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
1880 nsIContentPolicy::TYPE_WEBSOCKET, 0);
1881 MOZ_ASSERT(NS_SUCCEEDED(rv));
1883 if (!mRequestedProtocolList.IsEmpty()) {
1884 rv = wsChannel->SetProtocol(mRequestedProtocolList);
1885 NS_ENSURE_SUCCESS(rv, rv);
1888 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
1889 NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
1891 rv = rr->RetargetDeliveryTo(this);
1892 NS_ENSURE_SUCCESS(rv, rv);
1894 mChannel = wsChannel;
1896 if (mIsMainThread) {
1897 MOZ_ASSERT(mImplProxy);
1898 mService->AssociateWebSocketImplWithSerialID(mImplProxy,
1899 mChannel->Serial());
1902 if (mIsMainThread && doc) {
1903 mMainThreadEventTarget = doc->EventTargetFor(TaskCategory::Other);
1906 return NS_OK;
1909 void WebSocketImpl::DispatchConnectionCloseEvents() {
1910 AssertIsOnTargetThread();
1912 if (mDisconnectingOrDisconnected) {
1913 return;
1916 mWebSocket->SetReadyState(WebSocket::CLOSED);
1918 // Let's keep the object alive because the webSocket can be CCed in the
1919 // onerror or in the onclose callback.
1920 RefPtr<WebSocket> webSocket = mWebSocket;
1922 // Call 'onerror' if needed
1923 if (mFailed) {
1924 nsresult rv = webSocket->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING);
1925 if (NS_FAILED(rv)) {
1926 NS_WARNING("Failed to dispatch the error event");
1930 nsresult rv = webSocket->CreateAndDispatchCloseEvent(
1931 mCloseEventWasClean, mCloseEventCode, mCloseEventReason);
1932 if (NS_FAILED(rv)) {
1933 NS_WARNING("Failed to dispatch the close event");
1936 webSocket->UpdateMustKeepAlive();
1937 Disconnect();
1940 nsresult WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1941 MOZ_ASSERT(mImpl);
1942 AssertIsOnTargetThread();
1944 nsresult rv = CheckCurrentGlobalCorrectness();
1945 if (NS_FAILED(rv)) {
1946 return NS_OK;
1949 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1951 // it doesn't bubble, and it isn't cancelable
1952 event->InitEvent(aName, false, false);
1953 event->SetTrusted(true);
1955 ErrorResult err;
1956 DispatchEvent(*event, err);
1957 return err.StealNSResult();
1960 nsresult WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
1961 bool aIsBinary) {
1962 MOZ_ASSERT(mImpl);
1963 AssertIsOnTargetThread();
1965 AutoJSAPI jsapi;
1966 if (NS_WARN_IF(!jsapi.Init(GetOwnerGlobal()))) {
1967 return NS_ERROR_FAILURE;
1970 JSContext* cx = jsapi.cx();
1972 nsresult rv = CheckCurrentGlobalCorrectness();
1973 if (NS_FAILED(rv)) {
1974 return NS_OK;
1977 uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
1979 // Create appropriate JS object for message
1980 JS::Rooted<JS::Value> jsData(cx);
1981 if (aIsBinary) {
1982 if (mBinaryType == dom::BinaryType::Blob) {
1983 messageType = nsIWebSocketEventListener::TYPE_BLOB;
1985 RefPtr<Blob> blob =
1986 Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns);
1987 if (NS_WARN_IF(!blob)) {
1988 return NS_ERROR_FAILURE;
1991 if (!ToJSValue(cx, blob, &jsData)) {
1992 return NS_ERROR_FAILURE;
1995 } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
1996 messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;
1998 JS::Rooted<JSObject*> arrayBuf(cx);
1999 nsresult rv =
2000 nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
2001 NS_ENSURE_SUCCESS(rv, rv);
2002 jsData.setObject(*arrayBuf);
2003 } else {
2004 MOZ_CRASH("Unknown binary type!");
2005 return NS_ERROR_UNEXPECTED;
2007 } else {
2008 // JS string
2009 nsAutoString utf16Data;
2010 if (!AppendUTF8toUTF16(aData, utf16Data, mozilla::fallible)) {
2011 return NS_ERROR_OUT_OF_MEMORY;
2013 JSString* jsString;
2014 jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
2015 NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
2017 jsData.setString(jsString);
2020 mImpl->mService->WebSocketMessageAvailable(
2021 mImpl->mChannel->Serial(), mImpl->mInnerWindowID, aData, messageType);
2023 // create an event that uses the MessageEvent interface,
2024 // which does not bubble, is not cancelable, and has no default action
2026 RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
2028 event->InitMessageEvent(nullptr, MESSAGE_EVENT_STRING, CanBubble::eNo,
2029 Cancelable::eNo, jsData, mImpl->mUTF16Origin, u""_ns,
2030 nullptr, Sequence<OwningNonNull<MessagePort>>());
2031 event->SetTrusted(true);
2033 ErrorResult err;
2034 DispatchEvent(*event, err);
2035 return err.StealNSResult();
2038 nsresult WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, uint16_t aCode,
2039 const nsAString& aReason) {
2040 AssertIsOnTargetThread();
2042 // This method is called by a runnable and it can happen that, in the
2043 // meantime, GC unlinked this object, so mImpl could be null.
2044 if (mImpl && mImpl->mChannel) {
2045 mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
2046 mImpl->mInnerWindowID, aWasClean, aCode,
2047 aReason);
2050 nsresult rv = CheckCurrentGlobalCorrectness();
2051 if (NS_FAILED(rv)) {
2052 return NS_OK;
2055 CloseEventInit init;
2056 init.mBubbles = false;
2057 init.mCancelable = false;
2058 init.mWasClean = aWasClean;
2059 init.mCode = aCode;
2060 init.mReason = aReason;
2062 RefPtr<CloseEvent> event =
2063 CloseEvent::Constructor(this, CLOSE_EVENT_STRING, init);
2064 event->SetTrusted(true);
2066 ErrorResult err;
2067 DispatchEvent(*event, err);
2068 return err.StealNSResult();
2071 nsresult WebSocketImpl::ParseURL(const nsAString& aURL) {
2072 AssertIsOnMainThread();
2073 NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
2075 if (mIsServerSide) {
2076 mWebSocket->mURI = aURL;
2077 CopyUTF16toUTF8(mWebSocket->mURI, mURI);
2079 return NS_OK;
2082 nsCOMPtr<nsIURI> uri;
2083 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
2084 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2086 nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
2087 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2089 bool hasRef;
2090 rv = parsedURL->GetHasRef(&hasRef);
2091 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR);
2093 nsAutoCString scheme;
2094 rv = parsedURL->GetScheme(scheme);
2095 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
2096 NS_ERROR_DOM_SYNTAX_ERR);
2098 nsAutoCString host;
2099 rv = parsedURL->GetAsciiHost(host);
2100 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
2102 int32_t port;
2103 rv = parsedURL->GetPort(&port);
2104 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2106 nsAutoCString filePath;
2107 rv = parsedURL->GetFilePath(filePath);
2108 if (filePath.IsEmpty()) {
2109 filePath.Assign('/');
2111 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2113 nsAutoCString query;
2114 rv = parsedURL->GetQuery(query);
2115 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2117 if (scheme.LowerCaseEqualsLiteral("ws")) {
2118 mSecure = false;
2119 mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
2120 } else if (scheme.LowerCaseEqualsLiteral("wss")) {
2121 mSecure = true;
2122 mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
2123 } else {
2124 return NS_ERROR_DOM_SYNTAX_ERR;
2127 rv =
2128 nsContentUtils::GetWebExposedOriginSerialization(parsedURL, mUTF16Origin);
2129 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2131 mAsciiHost = host;
2132 ToLowerCase(mAsciiHost);
2134 mResource = filePath;
2135 if (!query.IsEmpty()) {
2136 mResource.Append('?');
2137 mResource.Append(query);
2139 uint32_t length = mResource.Length();
2140 uint32_t i;
2141 for (i = 0; i < length; ++i) {
2142 if (mResource[i] < static_cast<char16_t>(0x0021) ||
2143 mResource[i] > static_cast<char16_t>(0x007E)) {
2144 return NS_ERROR_DOM_SYNTAX_ERR;
2148 rv = parsedURL->GetSpec(mURI);
2149 MOZ_ASSERT(NS_SUCCEEDED(rv));
2151 CopyUTF8toUTF16(mURI, mWebSocket->mURI);
2152 return NS_OK;
2155 //-----------------------------------------------------------------------------
2156 // Methods that keep alive the WebSocket object when:
2157 // 1. the object has registered event listeners that can be triggered
2158 // ("strong event listeners");
2159 // 2. there are outgoing not sent messages.
2160 //-----------------------------------------------------------------------------
2162 void WebSocket::UpdateMustKeepAlive() {
2163 // Here we could not have mImpl.
2164 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2166 if (!mCheckMustKeepAlive || !mImpl) {
2167 return;
2170 bool shouldKeepAlive = false;
2171 uint16_t readyState = ReadyState();
2173 if (mListenerManager) {
2174 switch (readyState) {
2175 case CONNECTING: {
2176 if (mListenerManager->HasListenersFor(OPEN_EVENT_STRING) ||
2177 mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
2178 mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
2179 mListenerManager->HasListenersFor(CLOSE_EVENT_STRING)) {
2180 shouldKeepAlive = true;
2182 } break;
2184 case OPEN:
2185 case CLOSING: {
2186 if (mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) ||
2187 mListenerManager->HasListenersFor(ERROR_EVENT_STRING) ||
2188 mListenerManager->HasListenersFor(CLOSE_EVENT_STRING) ||
2189 mOutgoingBufferedAmount.value() != 0) {
2190 shouldKeepAlive = true;
2192 } break;
2194 case CLOSED: {
2195 shouldKeepAlive = false;
2200 if (mKeepingAlive && !shouldKeepAlive) {
2201 mKeepingAlive = false;
2202 mImpl->ReleaseObject();
2203 } else if (!mKeepingAlive && shouldKeepAlive) {
2204 mKeepingAlive = true;
2205 mImpl->AddRefObject();
2209 void WebSocket::DontKeepAliveAnyMore() {
2210 // Here we could not have mImpl.
2211 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2213 if (mKeepingAlive) {
2214 MOZ_ASSERT(mImpl);
2216 mKeepingAlive = false;
2217 mImpl->ReleaseObject();
2220 mCheckMustKeepAlive = false;
2223 void WebSocketImpl::AddRefObject() {
2224 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
2225 AddRef();
2228 void WebSocketImpl::ReleaseObject() {
2229 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
2230 Release();
2233 bool WebSocketImpl::RegisterWorkerRef(WorkerPrivate* aWorkerPrivate) {
2234 MOZ_ASSERT(aWorkerPrivate);
2236 RefPtr<WebSocketImpl> self = this;
2238 // In workers we have to keep the worker alive using a strong reference in
2239 // order to dispatch messages correctly.
2240 RefPtr<StrongWorkerRef> workerRef =
2241 StrongWorkerRef::Create(aWorkerPrivate, "WebSocketImpl", [self]() {
2243 MutexAutoLock lock(self->mMutex);
2244 self->mWorkerShuttingDown = true;
2247 self->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY, ""_ns);
2249 if (NS_WARN_IF(!workerRef)) {
2250 return false;
2253 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
2254 MOZ_ASSERT(mWorkerRef);
2256 return true;
2259 void WebSocketImpl::UnregisterWorkerRef() {
2260 MOZ_ASSERT(mDisconnectingOrDisconnected);
2261 MOZ_ASSERT(mWorkerRef);
2262 mWorkerRef->Private()->AssertIsOnWorkerThread();
2265 MutexAutoLock lock(mMutex);
2266 mWorkerShuttingDown = true;
2269 // The DTOR of this StrongWorkerRef will release the worker for us.
2270 mWorkerRef = nullptr;
2273 nsresult WebSocketImpl::UpdateURI() {
2274 AssertIsOnTargetThread();
2276 // Check for Redirections
2277 RefPtr<BaseWebSocketChannel> channel;
2278 channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
2279 MOZ_ASSERT(channel);
2281 channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
2282 mSecure = channel->IsEncrypted();
2284 return NS_OK;
2287 void WebSocket::EventListenerAdded(nsAtom* aType) {
2288 AssertIsOnTargetThread();
2289 UpdateMustKeepAlive();
2292 void WebSocket::EventListenerRemoved(nsAtom* aType) {
2293 AssertIsOnTargetThread();
2294 UpdateMustKeepAlive();
2297 //-----------------------------------------------------------------------------
2298 // WebSocket - methods
2299 //-----------------------------------------------------------------------------
2301 // webIDL: readonly attribute unsigned short readyState;
2302 uint16_t WebSocket::ReadyState() {
2303 MutexAutoLock lock(mMutex);
2304 return mReadyState;
2307 void WebSocket::SetReadyState(uint16_t aReadyState) {
2308 MutexAutoLock lock(mMutex);
2309 mReadyState = aReadyState;
2312 // webIDL: readonly attribute unsigned long long bufferedAmount;
2313 uint64_t WebSocket::BufferedAmount() const {
2314 AssertIsOnTargetThread();
2315 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
2316 return mOutgoingBufferedAmount.value();
2319 // webIDL: attribute BinaryType binaryType;
2320 dom::BinaryType WebSocket::BinaryType() const {
2321 AssertIsOnTargetThread();
2322 return mBinaryType;
2325 // webIDL: attribute BinaryType binaryType;
2326 void WebSocket::SetBinaryType(dom::BinaryType aData) {
2327 AssertIsOnTargetThread();
2328 mBinaryType = aData;
2331 // webIDL: readonly attribute DOMString url
2332 void WebSocket::GetUrl(nsAString& aURL) {
2333 AssertIsOnTargetThread();
2335 if (mEffectiveURL.IsEmpty()) {
2336 aURL = mURI;
2337 } else {
2338 aURL = mEffectiveURL;
2342 // webIDL: readonly attribute DOMString extensions;
2343 void WebSocket::GetExtensions(nsAString& aExtensions) {
2344 AssertIsOnTargetThread();
2345 CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
2348 // webIDL: readonly attribute DOMString protocol;
2349 void WebSocket::GetProtocol(nsAString& aProtocol) {
2350 AssertIsOnTargetThread();
2351 CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
2354 // webIDL: void send(DOMString data);
2355 void WebSocket::Send(const nsAString& aData, ErrorResult& aRv) {
2356 AssertIsOnTargetThread();
2358 nsAutoCString msgString;
2359 if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) {
2360 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2361 return;
2363 Send(nullptr, msgString, msgString.Length(), false, aRv);
2366 void WebSocket::Send(Blob& aData, ErrorResult& aRv) {
2367 AssertIsOnTargetThread();
2369 nsCOMPtr<nsIInputStream> msgStream;
2370 aData.CreateInputStream(getter_AddRefs(msgStream), aRv);
2371 if (NS_WARN_IF(aRv.Failed())) {
2372 return;
2375 uint64_t msgLength = aData.GetSize(aRv);
2376 if (NS_WARN_IF(aRv.Failed())) {
2377 return;
2380 if (msgLength > UINT32_MAX) {
2381 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2382 return;
2385 Send(msgStream, ""_ns, msgLength, true, aRv);
2388 void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) {
2389 AssertIsOnTargetThread();
2391 aData.ComputeState();
2393 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
2395 uint32_t len = aData.Length();
2396 char* data = reinterpret_cast<char*>(aData.Data());
2398 nsDependentCSubstring msgString;
2399 if (!msgString.Assign(data, len, mozilla::fallible_t())) {
2400 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2401 return;
2403 Send(nullptr, msgString, len, true, aRv);
2406 void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) {
2407 AssertIsOnTargetThread();
2409 aData.ComputeState();
2411 static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
2413 uint32_t len = aData.Length();
2414 char* data = reinterpret_cast<char*>(aData.Data());
2416 nsDependentCSubstring msgString;
2417 if (!msgString.Assign(data, len, mozilla::fallible_t())) {
2418 aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2419 return;
2421 Send(nullptr, msgString, len, true, aRv);
2424 void WebSocket::Send(nsIInputStream* aMsgStream, const nsACString& aMsgString,
2425 uint32_t aMsgLength, bool aIsBinary, ErrorResult& aRv) {
2426 AssertIsOnTargetThread();
2428 int64_t readyState = ReadyState();
2429 if (readyState == CONNECTING) {
2430 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2431 return;
2434 CheckedUint64 outgoingBufferedAmount = mOutgoingBufferedAmount;
2435 outgoingBufferedAmount += aMsgLength;
2436 if (!outgoingBufferedAmount.isValid()) {
2437 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2438 return;
2441 // Always increment outgoing buffer len, even if closed
2442 mOutgoingBufferedAmount = outgoingBufferedAmount;
2443 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid());
2445 if (readyState == CLOSING || readyState == CLOSED) {
2446 return;
2449 // We must have mImpl when connected.
2450 MOZ_ASSERT(mImpl);
2451 MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
2453 nsresult rv;
2454 if (aMsgStream) {
2455 rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
2456 } else {
2457 if (aIsBinary) {
2458 rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
2459 } else {
2460 rv = mImpl->mChannel->SendMsg(aMsgString);
2464 if (NS_FAILED(rv)) {
2465 aRv.Throw(rv);
2466 return;
2469 UpdateMustKeepAlive();
2472 // webIDL: void close(optional unsigned short code, optional DOMString reason):
2473 void WebSocket::Close(const Optional<uint16_t>& aCode,
2474 const Optional<nsAString>& aReason, ErrorResult& aRv) {
2475 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread);
2477 // the reason code is optional, but if provided it must be in a specific range
2478 uint16_t closeCode = 0;
2479 if (aCode.WasPassed()) {
2480 if (aCode.Value() != 1000 &&
2481 (aCode.Value() < 3000 || aCode.Value() > 4999)) {
2482 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
2483 return;
2485 closeCode = aCode.Value();
2488 nsCString closeReason;
2489 if (aReason.WasPassed()) {
2490 CopyUTF16toUTF8(aReason.Value(), closeReason);
2492 // The API requires the UTF-8 string to be 123 or less bytes
2493 if (closeReason.Length() > 123) {
2494 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
2495 return;
2499 int64_t readyState = ReadyState();
2500 if (readyState == CLOSING || readyState == CLOSED) {
2501 return;
2504 // If we don't have mImpl, we are in a shutting down worker where we are still
2505 // in CONNECTING state, but already disconnected internally.
2506 if (!mImpl) {
2507 MOZ_ASSERT(readyState == CONNECTING);
2508 SetReadyState(CLOSING);
2509 return;
2512 if (readyState == CONNECTING) {
2513 mImpl->FailConnection(closeCode, closeReason);
2514 return;
2517 MOZ_ASSERT(readyState == OPEN);
2518 mImpl->CloseConnection(closeCode, closeReason);
2521 //-----------------------------------------------------------------------------
2522 // WebSocketImpl::nsIObserver
2523 //-----------------------------------------------------------------------------
2525 NS_IMETHODIMP
2526 WebSocketImpl::Observe(nsISupports* aSubject, const char* aTopic,
2527 const char16_t* aData) {
2528 AssertIsOnMainThread();
2530 int64_t readyState = mWebSocket->ReadyState();
2531 if ((readyState == WebSocket::CLOSING) || (readyState == WebSocket::CLOSED)) {
2532 return NS_OK;
2535 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
2536 if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
2537 return NS_OK;
2540 if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
2541 (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)) {
2542 CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
2545 return NS_OK;
2548 //-----------------------------------------------------------------------------
2549 // WebSocketImpl::nsIRequest
2550 //-----------------------------------------------------------------------------
2552 NS_IMETHODIMP
2553 WebSocketImpl::GetName(nsACString& aName) {
2554 AssertIsOnMainThread();
2556 CopyUTF16toUTF8(mWebSocket->mURI, aName);
2557 return NS_OK;
2560 NS_IMETHODIMP
2561 WebSocketImpl::IsPending(bool* aValue) {
2562 AssertIsOnTargetThread();
2564 int64_t readyState = mWebSocket->ReadyState();
2565 *aValue = (readyState != WebSocket::CLOSED);
2566 return NS_OK;
2569 NS_IMETHODIMP
2570 WebSocketImpl::GetStatus(nsresult* aStatus) {
2571 AssertIsOnTargetThread();
2573 *aStatus = NS_OK;
2574 return NS_OK;
2577 namespace {
2579 class CancelRunnable final : public MainThreadWorkerRunnable {
2580 public:
2581 CancelRunnable(ThreadSafeWorkerRef* aWorkerRef, WebSocketImpl* aImpl)
2582 : MainThreadWorkerRunnable(aWorkerRef->Private()), mImpl(aImpl) {}
2584 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
2585 aWorkerPrivate->AssertIsOnWorkerThread();
2586 return !NS_FAILED(mImpl->CancelInternal());
2589 private:
2590 RefPtr<WebSocketImpl> mImpl;
2593 } // namespace
2595 NS_IMETHODIMP WebSocketImpl::SetCanceledReason(const nsACString& aReason) {
2596 return SetCanceledReasonImpl(aReason);
2599 NS_IMETHODIMP WebSocketImpl::GetCanceledReason(nsACString& aReason) {
2600 return GetCanceledReasonImpl(aReason);
2603 NS_IMETHODIMP WebSocketImpl::CancelWithReason(nsresult aStatus,
2604 const nsACString& aReason) {
2605 return CancelWithReasonImpl(aStatus, aReason);
2608 // Window closed, stop/reload button pressed, user navigated away from page,
2609 // etc.
2610 NS_IMETHODIMP
2611 WebSocketImpl::Cancel(nsresult aStatus) {
2612 AssertIsOnMainThread();
2614 if (!mIsMainThread) {
2615 MOZ_ASSERT(mWorkerRef);
2616 RefPtr<CancelRunnable> runnable = new CancelRunnable(mWorkerRef, this);
2617 if (!runnable->Dispatch()) {
2618 return NS_ERROR_FAILURE;
2621 return NS_OK;
2624 return CancelInternal();
2627 nsresult WebSocketImpl::CancelInternal() {
2628 AssertIsOnTargetThread();
2630 // If CancelInternal is called by a runnable, we may already be disconnected
2631 // by the time it runs.
2632 if (mDisconnectingOrDisconnected) {
2633 return NS_OK;
2636 int64_t readyState = mWebSocket->ReadyState();
2637 if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
2638 return NS_OK;
2641 return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
2644 NS_IMETHODIMP
2645 WebSocketImpl::Suspend() {
2646 AssertIsOnMainThread();
2647 return NS_ERROR_NOT_IMPLEMENTED;
2650 NS_IMETHODIMP
2651 WebSocketImpl::Resume() {
2652 AssertIsOnMainThread();
2653 return NS_ERROR_NOT_IMPLEMENTED;
2656 NS_IMETHODIMP
2657 WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup) {
2658 AssertIsOnMainThread();
2660 *aLoadGroup = nullptr;
2662 if (mIsMainThread) {
2663 nsCOMPtr<Document> doc = mWebSocket->GetDocumentIfCurrent();
2664 if (doc) {
2665 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2668 return NS_OK;
2671 MOZ_ASSERT(mWorkerRef);
2673 // Walk up to our containing page
2674 WorkerPrivate* wp = mWorkerRef->Private();
2675 while (wp->GetParent()) {
2676 wp = wp->GetParent();
2679 nsPIDOMWindowInner* window = wp->GetWindow();
2680 if (!window) {
2681 return NS_OK;
2684 Document* doc = window->GetExtantDoc();
2685 if (doc) {
2686 *aLoadGroup = doc->GetDocumentLoadGroup().take();
2689 return NS_OK;
2692 NS_IMETHODIMP
2693 WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup) {
2694 AssertIsOnMainThread();
2695 return NS_ERROR_UNEXPECTED;
2698 NS_IMETHODIMP
2699 WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags) {
2700 AssertIsOnMainThread();
2702 *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
2703 return NS_OK;
2706 NS_IMETHODIMP
2707 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags) {
2708 AssertIsOnMainThread();
2710 // we won't change the load flags at all.
2711 return NS_OK;
2714 NS_IMETHODIMP
2715 WebSocketImpl::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
2716 return GetTRRModeImpl(aTRRMode);
2719 NS_IMETHODIMP
2720 WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
2721 return SetTRRModeImpl(aTRRMode);
2724 namespace {
2726 class WorkerRunnableDispatcher final : public WorkerRunnable {
2727 RefPtr<WebSocketImpl> mWebSocketImpl;
2729 public:
2730 WorkerRunnableDispatcher(WebSocketImpl* aImpl,
2731 ThreadSafeWorkerRef* aWorkerRef,
2732 already_AddRefed<nsIRunnable> aEvent)
2733 : WorkerRunnable(aWorkerRef->Private(), WorkerThreadUnchangedBusyCount),
2734 mWebSocketImpl(aImpl),
2735 mEvent(std::move(aEvent)) {}
2737 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
2738 aWorkerPrivate->AssertIsOnWorkerThread();
2740 // No messages when disconnected.
2741 if (mWebSocketImpl->mDisconnectingOrDisconnected) {
2742 NS_WARNING("Dispatching a WebSocket event after the disconnection!");
2743 return true;
2746 return !NS_FAILED(mEvent->Run());
2749 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
2750 bool aRunResult) override {}
2752 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
2753 // We don't call WorkerRunnable::PreDispatch because it would assert the
2754 // wrong thing about which thread we're on. We're on whichever thread the
2755 // channel implementation is running on (probably the main thread or socket
2756 // transport thread).
2757 return true;
2760 void PostDispatch(WorkerPrivate* aWorkerPrivate,
2761 bool aDispatchResult) override {
2762 // We don't call WorkerRunnable::PreDispatch because it would assert the
2763 // wrong thing about which thread we're on. We're on whichever thread the
2764 // channel implementation is running on (probably the main thread or socket
2765 // transport thread).
2768 private:
2769 nsCOMPtr<nsIRunnable> mEvent;
2772 } // namespace
2774 NS_IMETHODIMP
2775 WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
2776 nsCOMPtr<nsIRunnable> event(aEvent);
2777 return Dispatch(event.forget(), aFlags);
2780 NS_IMETHODIMP
2781 WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags) {
2782 nsCOMPtr<nsIRunnable> event_ref(aEvent);
2783 // If the target is the main-thread, we should try to dispatch the runnable
2784 // to a labeled event target.
2785 if (mIsMainThread) {
2786 return mMainThreadEventTarget
2787 ? mMainThreadEventTarget->Dispatch(event_ref.forget())
2788 : GetMainThreadSerialEventTarget()->Dispatch(event_ref.forget());
2791 MutexAutoLock lock(mMutex);
2792 if (mWorkerShuttingDown) {
2793 return NS_OK;
2796 MOZ_DIAGNOSTIC_ASSERT(mWorkerRef);
2798 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
2799 // runnable.
2800 RefPtr<WorkerRunnableDispatcher> event =
2801 new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget());
2803 if (!event->Dispatch()) {
2804 return NS_ERROR_FAILURE;
2807 return NS_OK;
2810 NS_IMETHODIMP
2811 WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
2812 return NS_ERROR_NOT_IMPLEMENTED;
2815 NS_IMETHODIMP
2816 WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
2817 return NS_ERROR_NOT_IMPLEMENTED;
2820 NS_IMETHODIMP
2821 WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
2822 return NS_ERROR_NOT_IMPLEMENTED;
2825 NS_IMETHODIMP
2826 WebSocketImpl::IsOnCurrentThread(bool* aResult) {
2827 *aResult = IsTargetThread();
2828 return NS_OK;
2831 NS_IMETHODIMP_(bool)
2832 WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
2834 bool WebSocketImpl::IsTargetThread() const {
2835 // FIXME: This should also check if we're on the worker thread. Code using
2836 // `IsOnCurrentThread` could easily misbehave here!
2837 return NS_IsMainThread() == mIsMainThread;
2840 void WebSocket::AssertIsOnTargetThread() const {
2841 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2844 nsresult WebSocketImpl::IsSecure(bool* aValue) {
2845 MOZ_ASSERT(NS_IsMainThread());
2846 MOZ_ASSERT(mIsMainThread);
2848 // Check the principal's uri to determine if we were loaded from https.
2849 nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
2850 nsCOMPtr<nsIPrincipal> principal;
2852 if (globalObject) {
2853 principal = globalObject->PrincipalOrNull();
2856 nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(globalObject);
2857 if (!innerWindow) {
2858 // If we are in a XPConnect sandbox or in a JS component,
2859 // innerWindow will be null. There is nothing on top of this to be
2860 // considered.
2861 if (NS_WARN_IF(!principal)) {
2862 return NS_OK;
2864 *aValue = principal->SchemeIs("https");
2865 return NS_OK;
2868 RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
2869 if (NS_WARN_IF(!windowContext)) {
2870 return NS_ERROR_DOM_SECURITY_ERR;
2873 while (true) {
2874 if (windowContext->GetIsSecure()) {
2875 *aValue = true;
2876 return NS_OK;
2879 if (windowContext->IsTop()) {
2880 break;
2881 } else {
2882 // If we're not a top window get the parent window context instead.
2883 windowContext = windowContext->GetParentWindowContext();
2886 if (NS_WARN_IF(!windowContext)) {
2887 return NS_ERROR_DOM_SECURITY_ERR;
2891 *aValue = windowContext->GetIsSecure();
2892 return NS_OK;
2895 } // namespace mozilla::dom