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/. */
6 
7 #include "WebSocket.h"
8 #include "mozilla/dom/WebSocketBinding.h"
9 #include "mozilla/net/WebSocketChannel.h"
10 
11 #include "jsapi.h"
12 #include "jsfriendapi.h"
13 #include "mozilla/CheckedInt.h"
14 #include "mozilla/DOMEventTargetHelper.h"
15 #include "mozilla/net/WebSocketChannel.h"
16 #include "mozilla/dom/File.h"
17 #include "mozilla/dom/MessageEvent.h"
18 #include "mozilla/dom/MessageEventBinding.h"
19 #include "mozilla/dom/nsCSPContext.h"
20 #include "mozilla/dom/nsCSPUtils.h"
21 #include "mozilla/dom/ScriptSettings.h"
22 #include "mozilla/dom/WorkerPrivate.h"
23 #include "mozilla/dom/WorkerRunnable.h"
24 #include "mozilla/dom/WorkerScope.h"
25 #include "nsAutoPtr.h"
26 #include "nsGlobalWindow.h"
27 #include "nsIScriptGlobalObject.h"
28 #include "nsIDOMWindow.h"
29 #include "nsIDocument.h"
30 #include "nsXPCOM.h"
31 #include "nsIXPConnect.h"
32 #include "nsContentUtils.h"
33 #include "nsError.h"
34 #include "nsIScriptObjectPrincipal.h"
35 #include "nsIURL.h"
36 #include "nsIUnicodeEncoder.h"
37 #include "nsThreadUtils.h"
38 #include "nsIPromptFactory.h"
39 #include "nsIWindowWatcher.h"
40 #include "nsIPrompt.h"
41 #include "nsIStringBundle.h"
42 #include "nsIConsoleService.h"
43 #include "mozilla/dom/CloseEvent.h"
44 #include "mozilla/net/WebSocketEventService.h"
45 #include "nsICryptoHash.h"
46 #include "nsJSUtils.h"
47 #include "nsIScriptError.h"
48 #include "nsNetUtil.h"
49 #include "nsIAuthPrompt.h"
50 #include "nsIAuthPrompt2.h"
51 #include "nsILoadGroup.h"
52 #include "mozilla/Preferences.h"
53 #include "xpcpublic.h"
54 #include "nsContentPolicyUtils.h"
55 #include "nsWrapperCacheInlines.h"
56 #include "nsIObserverService.h"
57 #include "nsIEventTarget.h"
58 #include "nsIInterfaceRequestor.h"
59 #include "nsIObserver.h"
60 #include "nsIRequest.h"
61 #include "nsIThreadRetargetableRequest.h"
62 #include "nsIWebSocketChannel.h"
63 #include "nsIWebSocketListener.h"
64 #include "nsProxyRelease.h"
65 #include "nsWeakReference.h"
66 
67 using namespace mozilla::net;
68 using namespace mozilla::dom::workers;
69 
70 namespace mozilla {
71 namespace dom {
72 
73 class WebSocketImpl final : public nsIInterfaceRequestor
74                           , public nsIWebSocketListener
75                           , public nsIObserver
76                           , public nsSupportsWeakReference
77                           , public nsIRequest
78                           , public nsIEventTarget
79 {
80 public:
81   NS_DECL_NSIINTERFACEREQUESTOR
82   NS_DECL_NSIWEBSOCKETLISTENER
83   NS_DECL_NSIOBSERVER
84   NS_DECL_NSIREQUEST
85   NS_DECL_THREADSAFE_ISUPPORTS
86   NS_DECL_NSIEVENTTARGET
87   using nsIEventTarget::Dispatch;
88 
WebSocketImpl(WebSocket * aWebSocket)89   explicit WebSocketImpl(WebSocket* aWebSocket)
90   : mWebSocket(aWebSocket)
91   , mIsServerSide(false)
92   , mSecure(false)
93   , mOnCloseScheduled(false)
94   , mFailed(false)
95   , mDisconnectingOrDisconnected(false)
96   , mCloseEventWasClean(false)
97   , mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL)
98   , mScriptLine(0)
99   , mScriptColumn(0)
100   , mInnerWindowID(0)
101   , mWorkerPrivate(nullptr)
102 #ifdef DEBUG
103   , mHasWorkerHolderRegistered(false)
104 #endif
105   , mIsMainThread(true)
106   , mMutex("WebSocketImpl::mMutex")
107   , mWorkerShuttingDown(false)
108   {
109     if (!NS_IsMainThread()) {
110       mWorkerPrivate = GetCurrentThreadWorkerPrivate();
111       MOZ_ASSERT(mWorkerPrivate);
112       mIsMainThread = false;
113     }
114   }
115 
AssertIsOnTargetThread() const116   void AssertIsOnTargetThread() const
117   {
118     MOZ_ASSERT(IsTargetThread());
119   }
120 
121   bool IsTargetThread() const;
122 
123   void Init(JSContext* aCx,
124             nsIPrincipal* aPrincipal,
125             bool aIsServerSide,
126             const nsAString& aURL,
127             nsTArray<nsString>& aProtocolArray,
128             const nsACString& aScriptFile,
129             uint32_t aScriptLine,
130             uint32_t aScriptColumn,
131             ErrorResult& aRv,
132             bool* aConnectionFailed);
133 
134   void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
135                  nsITransportProvider* aTransportProvider,
136                  const nsACString& aNegotiatedExtensions,
137                  ErrorResult& aRv);
138 
139   nsresult ParseURL(const nsAString& aURL);
140   nsresult InitializeConnection(nsIPrincipal* aPrincipal);
141 
142   // These methods when called can release the WebSocket object
143   void FailConnection(uint16_t reasonCode,
144                       const nsACString& aReasonString = EmptyCString());
145   nsresult CloseConnection(uint16_t reasonCode,
146                            const nsACString& aReasonString = EmptyCString());
147   void Disconnect();
148   void DisconnectInternal();
149 
150   nsresult ConsoleError();
151   void PrintErrorOnConsole(const char* aBundleURI,
152                            const char16_t* aError,
153                            const char16_t** aFormatStrings,
154                            uint32_t aFormatStringsLen);
155 
156   nsresult DoOnMessageAvailable(const nsACString& aMsg,
157                                 bool isBinary);
158 
159   // ConnectionCloseEvents: 'error' event if needed, then 'close' event.
160   nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
161                                          nsresult aStatusCode);
162   // 2nd half of ScheduleConnectionCloseEvents, run in its own event.
163   void DispatchConnectionCloseEvents();
164 
165   nsresult UpdateURI();
166 
167   void AddRefObject();
168   void ReleaseObject();
169 
170   bool RegisterWorkerHolder();
171   void UnregisterWorkerHolder();
172 
173   nsresult CancelInternal();
174 
175   RefPtr<WebSocket> mWebSocket;
176 
177   nsCOMPtr<nsIWebSocketChannel> mChannel;
178 
179   bool mIsServerSide; // True if we're implementing the server side of a
180                       // websocket connection
181 
182   bool mSecure; // if true it is using SSL and the wss scheme,
183                 // otherwise it is using the ws scheme with no SSL
184 
185   bool mOnCloseScheduled;
186   bool mFailed;
187   bool mDisconnectingOrDisconnected;
188 
189   // Set attributes of DOM 'onclose' message
190   bool      mCloseEventWasClean;
191   nsString  mCloseEventReason;
192   uint16_t  mCloseEventCode;
193 
194   nsCString mAsciiHost;  // hostname
195   uint32_t  mPort;
196   nsCString mResource; // [filepath[?query]]
197   nsString  mUTF16Origin;
198 
199   nsCString mURI;
200   nsCString mRequestedProtocolList;
201 
202   nsWeakPtr mOriginDocument;
203 
204   // Web Socket owner information:
205   // - the script file name, UTF8 encoded.
206   // - source code line number and column number where the Web Socket object
207   //   was constructed.
208   // - the ID of the inner window where the script lives. Note that this may not
209   //   be the same as the Web Socket owner window.
210   // These attributes are used for error reporting.
211   nsCString mScriptFile;
212   uint32_t mScriptLine;
213   uint32_t mScriptColumn;
214   uint64_t mInnerWindowID;
215 
216   WorkerPrivate* mWorkerPrivate;
217   nsAutoPtr<WorkerHolder> mWorkerHolder;
218 
219 #ifdef DEBUG
220   // This is protected by mutex.
221   bool mHasWorkerHolderRegistered;
222 
HasWorkerHolderRegistered()223   bool HasWorkerHolderRegistered()
224   {
225     MOZ_ASSERT(mWebSocket);
226     MutexAutoLock lock(mWebSocket->mMutex);
227     return mHasWorkerHolderRegistered;
228   }
229 
SetHasWorkerHolderRegistered(bool aValue)230   void SetHasWorkerHolderRegistered(bool aValue)
231   {
232     MOZ_ASSERT(mWebSocket);
233     MutexAutoLock lock(mWebSocket->mMutex);
234     mHasWorkerHolderRegistered = aValue;
235   }
236 #endif
237 
238   nsWeakPtr mWeakLoadGroup;
239 
240   bool mIsMainThread;
241 
242   // This mutex protects mWorkerShuttingDown.
243   mozilla::Mutex mMutex;
244   bool mWorkerShuttingDown;
245 
246   RefPtr<WebSocketEventService> mService;
247 
248 private:
~WebSocketImpl()249   ~WebSocketImpl()
250   {
251     // If we threw during Init we never called disconnect
252     if (!mDisconnectingOrDisconnected) {
253       Disconnect();
254     }
255   }
256 };
257 
258 NS_IMPL_ISUPPORTS(WebSocketImpl,
259                   nsIInterfaceRequestor,
260                   nsIWebSocketListener,
261                   nsIObserver,
262                   nsISupportsWeakReference,
263                   nsIRequest,
264                   nsIEventTarget)
265 
266 class CallDispatchConnectionCloseEvents final : public CancelableRunnable
267 {
268 public:
CallDispatchConnectionCloseEvents(WebSocketImpl * aWebSocketImpl)269   explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
270     : mWebSocketImpl(aWebSocketImpl)
271   {
272     aWebSocketImpl->AssertIsOnTargetThread();
273   }
274 
Run()275   NS_IMETHOD Run() override
276   {
277     mWebSocketImpl->AssertIsOnTargetThread();
278     mWebSocketImpl->DispatchConnectionCloseEvents();
279     return NS_OK;
280   }
281 
282 private:
283   RefPtr<WebSocketImpl> mWebSocketImpl;
284 };
285 
286 //-----------------------------------------------------------------------------
287 // WebSocketImpl
288 //-----------------------------------------------------------------------------
289 
290 namespace {
291 
292 class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable
293 {
294 public:
PrintErrorOnConsoleRunnable(WebSocketImpl * aImpl,const char * aBundleURI,const char16_t * aError,const char16_t ** aFormatStrings,uint32_t aFormatStringsLen)295   PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl,
296                               const char* aBundleURI,
297                               const char16_t* aError,
298                               const char16_t** aFormatStrings,
299                               uint32_t aFormatStringsLen)
300     : WorkerMainThreadRunnable(aImpl->mWorkerPrivate,
301                                NS_LITERAL_CSTRING("WebSocket :: print error on console"))
302     , mImpl(aImpl)
303     , mBundleURI(aBundleURI)
304     , mError(aError)
305     , mFormatStrings(aFormatStrings)
306     , mFormatStringsLen(aFormatStringsLen)
307   { }
308 
MainThreadRun()309   bool MainThreadRun() override
310   {
311     mImpl->PrintErrorOnConsole(mBundleURI, mError, mFormatStrings,
312                                mFormatStringsLen);
313     return true;
314   }
315 
316 private:
317   // Raw pointer because this runnable is sync.
318   WebSocketImpl* mImpl;
319 
320   const char* mBundleURI;
321   const char16_t* mError;
322   const char16_t** mFormatStrings;
323   uint32_t mFormatStringsLen;
324 };
325 
326 } // namespace
327 
328 void
PrintErrorOnConsole(const char * aBundleURI,const char16_t * aError,const char16_t ** aFormatStrings,uint32_t aFormatStringsLen)329 WebSocketImpl::PrintErrorOnConsole(const char *aBundleURI,
330                                    const char16_t *aError,
331                                    const char16_t **aFormatStrings,
332                                    uint32_t aFormatStringsLen)
333 {
334   // This method must run on the main thread.
335 
336   if (!NS_IsMainThread()) {
337     MOZ_ASSERT(mWorkerPrivate);
338 
339     RefPtr<PrintErrorOnConsoleRunnable> runnable =
340       new PrintErrorOnConsoleRunnable(this, aBundleURI, aError, aFormatStrings,
341                                       aFormatStringsLen);
342     ErrorResult rv;
343     runnable->Dispatch(rv);
344     // XXXbz this seems totally broken.  We should be propagating this out, but
345     // none of our callers really propagate anything usefully.  Come to think of
346     // it, why is this a syncrunnable anyway?  Can't this be a fire-and-forget
347     // runnable??
348     rv.SuppressException();
349     return;
350   }
351 
352   nsresult rv;
353   nsCOMPtr<nsIStringBundleService> bundleService =
354     do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
355   NS_ENSURE_SUCCESS_VOID(rv);
356 
357   nsCOMPtr<nsIStringBundle> strBundle;
358   rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
359   NS_ENSURE_SUCCESS_VOID(rv);
360 
361   nsCOMPtr<nsIConsoleService> console(
362     do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
363   NS_ENSURE_SUCCESS_VOID(rv);
364 
365   nsCOMPtr<nsIScriptError> errorObject(
366     do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
367   NS_ENSURE_SUCCESS_VOID(rv);
368 
369   // Localize the error message
370   nsXPIDLString message;
371   if (aFormatStrings) {
372     rv = strBundle->FormatStringFromName(aError, aFormatStrings,
373                                          aFormatStringsLen,
374                                          getter_Copies(message));
375   } else {
376     rv = strBundle->GetStringFromName(aError, getter_Copies(message));
377   }
378   NS_ENSURE_SUCCESS_VOID(rv);
379 
380   if (mInnerWindowID) {
381     rv = errorObject->InitWithWindowID(message,
382                                        NS_ConvertUTF8toUTF16(mScriptFile),
383                                        EmptyString(), mScriptLine,
384                                        mScriptColumn,
385                                        nsIScriptError::errorFlag, "Web Socket",
386                                        mInnerWindowID);
387   } else {
388     rv = errorObject->Init(message,
389                            NS_ConvertUTF8toUTF16(mScriptFile),
390                            EmptyString(), mScriptLine, mScriptColumn,
391                            nsIScriptError::errorFlag, "Web Socket");
392   }
393 
394   NS_ENSURE_SUCCESS_VOID(rv);
395 
396   // print the error message directly to the JS console
397   rv = console->LogMessage(errorObject);
398   NS_ENSURE_SUCCESS_VOID(rv);
399 }
400 
401 namespace {
402 
403 class CancelWebSocketRunnable final : public Runnable
404 {
405 public:
CancelWebSocketRunnable(nsIWebSocketChannel * aChannel,uint16_t aReasonCode,const nsACString & aReasonString)406   CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
407                           const nsACString& aReasonString)
408     : mChannel(aChannel)
409     , mReasonCode(aReasonCode)
410     , mReasonString(aReasonString)
411   {}
412 
Run()413   NS_IMETHOD Run() override
414   {
415     mChannel->Close(mReasonCode, mReasonString);
416     return NS_OK;
417   }
418 
419 private:
420   nsCOMPtr<nsIWebSocketChannel> mChannel;
421   uint16_t mReasonCode;
422   nsCString mReasonString;
423 };
424 
425 class MOZ_STACK_CLASS MaybeDisconnect
426 {
427 public:
MaybeDisconnect(WebSocketImpl * aImpl)428   explicit MaybeDisconnect(WebSocketImpl* aImpl)
429     : mImpl(aImpl)
430   {
431   }
432 
~MaybeDisconnect()433   ~MaybeDisconnect()
434   {
435     bool toDisconnect = false;
436 
437     {
438       MutexAutoLock lock(mImpl->mMutex);
439       toDisconnect = mImpl->mWorkerShuttingDown;
440     }
441 
442     if (toDisconnect) {
443       mImpl->Disconnect();
444     }
445   }
446 
447 private:
448   WebSocketImpl* mImpl;
449 };
450 
451 class CloseConnectionRunnable final : public Runnable
452 {
453 public:
CloseConnectionRunnable(WebSocketImpl * aImpl,uint16_t aReasonCode,const nsACString & aReasonString)454   CloseConnectionRunnable(WebSocketImpl* aImpl,
455                           uint16_t aReasonCode,
456                           const nsACString& aReasonString)
457     : mImpl(aImpl)
458     , mReasonCode(aReasonCode)
459     , mReasonString(aReasonString)
460   {}
461 
Run()462   NS_IMETHOD Run() override
463   {
464     return mImpl->CloseConnection(mReasonCode, mReasonString);
465   }
466 
467 private:
468   RefPtr<WebSocketImpl> mImpl;
469   uint16_t mReasonCode;
470   const nsCString mReasonString;
471 };
472 
473 } // namespace
474 
475 nsresult
CloseConnection(uint16_t aReasonCode,const nsACString & aReasonString)476 WebSocketImpl::CloseConnection(uint16_t aReasonCode,
477                                const nsACString& aReasonString)
478 {
479   if (!IsTargetThread()) {
480     nsCOMPtr<nsIRunnable> runnable =
481       new CloseConnectionRunnable(this, aReasonCode, aReasonString);
482     return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
483   }
484 
485   AssertIsOnTargetThread();
486 
487   if (mDisconnectingOrDisconnected) {
488     return NS_OK;
489   }
490 
491   // If this method is called because the worker is going away, we will not
492   // receive the OnStop() method and we have to disconnect the WebSocket and
493   // release the WorkerHolder.
494   MaybeDisconnect md(this);
495 
496   uint16_t readyState = mWebSocket->ReadyState();
497   if (readyState == WebSocket::CLOSING ||
498       readyState == WebSocket::CLOSED) {
499     return NS_OK;
500   }
501 
502   // The common case...
503   if (mChannel) {
504     mWebSocket->SetReadyState(WebSocket::CLOSING);
505 
506     // The channel has to be closed on the main-thread.
507 
508     if (NS_IsMainThread()) {
509       return mChannel->Close(aReasonCode, aReasonString);
510     }
511 
512     RefPtr<CancelWebSocketRunnable> runnable =
513       new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
514     return NS_DispatchToMainThread(runnable);
515   }
516 
517   // No channel, but not disconnected: canceled or failed early
518   MOZ_ASSERT(readyState == WebSocket::CONNECTING,
519              "Should only get here for early websocket cancel/error");
520 
521   // Server won't be sending us a close code, so use what's passed in here.
522   mCloseEventCode = aReasonCode;
523   CopyUTF8toUTF16(aReasonString, mCloseEventReason);
524 
525   mWebSocket->SetReadyState(WebSocket::CLOSING);
526 
527   ScheduleConnectionCloseEvents(
528                     nullptr,
529                     (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
530                      aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
531                      NS_OK : NS_ERROR_FAILURE);
532 
533   return NS_OK;
534 }
535 
536 nsresult
ConsoleError()537 WebSocketImpl::ConsoleError()
538 {
539   AssertIsOnTargetThread();
540 
541   {
542     MutexAutoLock lock(mMutex);
543     if (mWorkerShuttingDown) {
544       // Too late to report anything, bail out.
545       return NS_OK;
546     }
547   }
548 
549   NS_ConvertUTF8toUTF16 specUTF16(mURI);
550   const char16_t* formatStrings[] = { specUTF16.get() };
551 
552   if (mWebSocket->ReadyState() < WebSocket::OPEN) {
553     PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
554                         u"connectionFailure",
555                         formatStrings, ArrayLength(formatStrings));
556   } else {
557     PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
558                         u"netInterrupt",
559                         formatStrings, ArrayLength(formatStrings));
560   }
561   /// todo some specific errors - like for message too large
562   return NS_OK;
563 }
564 
565 void
FailConnection(uint16_t aReasonCode,const nsACString & aReasonString)566 WebSocketImpl::FailConnection(uint16_t aReasonCode,
567                               const nsACString& aReasonString)
568 {
569   AssertIsOnTargetThread();
570 
571   if (mDisconnectingOrDisconnected) {
572     return;
573   }
574 
575   ConsoleError();
576   mFailed = true;
577   CloseConnection(aReasonCode, aReasonString);
578 }
579 
580 namespace {
581 
582 class DisconnectInternalRunnable final : public WorkerMainThreadRunnable
583 {
584 public:
DisconnectInternalRunnable(WebSocketImpl * aImpl)585   explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
586     : WorkerMainThreadRunnable(aImpl->mWorkerPrivate,
587                                NS_LITERAL_CSTRING("WebSocket :: disconnect"))
588     , mImpl(aImpl)
589   { }
590 
MainThreadRun()591   bool MainThreadRun() override
592   {
593     mImpl->DisconnectInternal();
594     return true;
595   }
596 
597 private:
598   // A raw pointer because this runnable is sync.
599   WebSocketImpl* mImpl;
600 };
601 
602 } // namespace
603 
604 void
Disconnect()605 WebSocketImpl::Disconnect()
606 {
607   if (mDisconnectingOrDisconnected) {
608     return;
609   }
610 
611   AssertIsOnTargetThread();
612 
613   // DontKeepAliveAnyMore() and DisconnectInternal() can release the object. So
614   // hold a reference to this until the end of the method.
615   RefPtr<WebSocketImpl> kungfuDeathGrip = this;
616 
617   // Disconnect can be called from some control event (such as Notify() of
618   // WorkerHolder). This will be schedulated before any other sync/async
619   // runnable. In order to prevent some double Disconnect() calls, we use this
620   // boolean.
621   mDisconnectingOrDisconnected = true;
622 
623   // DisconnectInternal touches observers and nsILoadGroup and it must run on
624   // the main thread.
625 
626   if (NS_IsMainThread()) {
627     DisconnectInternal();
628   } else {
629     RefPtr<DisconnectInternalRunnable> runnable =
630       new DisconnectInternalRunnable(this);
631     ErrorResult rv;
632     runnable->Dispatch(rv);
633     // XXXbz this seems totally broken.  We should be propagating this out, but
634     // where to, exactly?
635     rv.SuppressException();
636   }
637 
638   NS_ReleaseOnMainThread(mChannel.forget());
639   NS_ReleaseOnMainThread(mService.forget());
640 
641   mWebSocket->DontKeepAliveAnyMore();
642   mWebSocket->mImpl = nullptr;
643 
644   if (mWorkerPrivate && mWorkerHolder) {
645     UnregisterWorkerHolder();
646   }
647 
648   // We want to release the WebSocket in the correct thread.
649   mWebSocket = nullptr;
650 }
651 
652 void
DisconnectInternal()653 WebSocketImpl::DisconnectInternal()
654 {
655   AssertIsOnMainThread();
656 
657   nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
658   if (loadGroup) {
659     loadGroup->RemoveRequest(this, nullptr, NS_OK);
660     // mWeakLoadGroup has to be release on main-thread because WeakReferences
661     // are not thread-safe.
662     mWeakLoadGroup = nullptr;
663   }
664 
665   if (!mWorkerPrivate) {
666     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
667     if (os) {
668       os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
669       os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
670     }
671   }
672 }
673 
674 //-----------------------------------------------------------------------------
675 // WebSocketImpl::nsIWebSocketListener methods:
676 //-----------------------------------------------------------------------------
677 
678 nsresult
DoOnMessageAvailable(const nsACString & aMsg,bool isBinary)679 WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
680 {
681   AssertIsOnTargetThread();
682 
683   if (mDisconnectingOrDisconnected) {
684     return NS_OK;
685   }
686 
687   int16_t readyState = mWebSocket->ReadyState();
688   if (readyState == WebSocket::CLOSED) {
689     NS_ERROR("Received message after CLOSED");
690     return NS_ERROR_UNEXPECTED;
691   }
692 
693   if (readyState == WebSocket::OPEN) {
694     // Dispatch New Message
695     nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
696     if (NS_FAILED(rv)) {
697       NS_WARNING("Failed to dispatch the message event");
698     }
699 
700     return NS_OK;
701   }
702 
703   // CLOSING should be the only other state where it's possible to get msgs
704   // from channel: Spec says to drop them.
705   MOZ_ASSERT(readyState == WebSocket::CLOSING,
706              "Received message while CONNECTING or CLOSED");
707   return NS_OK;
708 }
709 
710 NS_IMETHODIMP
OnMessageAvailable(nsISupports * aContext,const nsACString & aMsg)711 WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
712                                   const nsACString& aMsg)
713 {
714   AssertIsOnTargetThread();
715 
716   if (mDisconnectingOrDisconnected) {
717     return NS_OK;
718   }
719 
720   return DoOnMessageAvailable(aMsg, false);
721 }
722 
723 NS_IMETHODIMP
OnBinaryMessageAvailable(nsISupports * aContext,const nsACString & aMsg)724 WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
725                                         const nsACString& aMsg)
726 {
727   AssertIsOnTargetThread();
728 
729   if (mDisconnectingOrDisconnected) {
730     return NS_OK;
731   }
732 
733   return DoOnMessageAvailable(aMsg, true);
734 }
735 
736 NS_IMETHODIMP
OnStart(nsISupports * aContext)737 WebSocketImpl::OnStart(nsISupports* aContext)
738 {
739   AssertIsOnTargetThread();
740 
741   if (mDisconnectingOrDisconnected) {
742     return NS_OK;
743   }
744 
745   int16_t readyState = mWebSocket->ReadyState();
746 
747   // This is the only function that sets OPEN, and should be called only once
748   MOZ_ASSERT(readyState != WebSocket::OPEN,
749              "readyState already OPEN! OnStart called twice?");
750 
751   // Nothing to do if we've already closed/closing
752   if (readyState != WebSocket::CONNECTING) {
753     return NS_OK;
754   }
755 
756   // Attempt to kill "ghost" websocket: but usually too early for check to fail
757   nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
758   if (NS_FAILED(rv)) {
759     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
760     return rv;
761   }
762 
763   if (!mRequestedProtocolList.IsEmpty()) {
764     mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
765   }
766 
767   mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
768   UpdateURI();
769 
770   mWebSocket->SetReadyState(WebSocket::OPEN);
771 
772   mService->WebSocketOpened(mChannel->Serial(),mInnerWindowID,
773                             mWebSocket->mEffectiveURL,
774                             mWebSocket->mEstablishedProtocol,
775                             mWebSocket->mEstablishedExtensions);
776 
777   // Let's keep the object alive because the webSocket can be CCed in the
778   // onopen callback.
779   RefPtr<WebSocket> webSocket = mWebSocket;
780 
781   // Call 'onopen'
782   rv = webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
783   if (NS_FAILED(rv)) {
784     NS_WARNING("Failed to dispatch the open event");
785   }
786 
787   webSocket->UpdateMustKeepAlive();
788   return NS_OK;
789 }
790 
791 NS_IMETHODIMP
OnStop(nsISupports * aContext,nsresult aStatusCode)792 WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode)
793 {
794   AssertIsOnTargetThread();
795 
796   if (mDisconnectingOrDisconnected) {
797     return NS_OK;
798   }
799 
800   // We can be CONNECTING here if connection failed.
801   // We can be OPEN if we have encountered a fatal protocol error
802   // We can be CLOSING if close() was called and/or server initiated close.
803   MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
804              "Shouldn't already be CLOSED when OnStop called");
805 
806   return ScheduleConnectionCloseEvents(aContext, aStatusCode);
807 }
808 
809 nsresult
ScheduleConnectionCloseEvents(nsISupports * aContext,nsresult aStatusCode)810 WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
811                                              nsresult aStatusCode)
812 {
813   AssertIsOnTargetThread();
814 
815   // no-op if some other code has already initiated close event
816   if (!mOnCloseScheduled) {
817     mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
818 
819     if (aStatusCode == NS_BASE_STREAM_CLOSED) {
820       // don't generate an error event just because of an unclean close
821       aStatusCode = NS_OK;
822     }
823 
824     if (NS_FAILED(aStatusCode)) {
825       ConsoleError();
826       mFailed = true;
827     }
828 
829     mOnCloseScheduled = true;
830 
831     NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
832   }
833 
834   return NS_OK;
835 }
836 
837 NS_IMETHODIMP
OnAcknowledge(nsISupports * aContext,uint32_t aSize)838 WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
839 {
840   AssertIsOnTargetThread();
841 
842   if (mDisconnectingOrDisconnected) {
843     return NS_OK;
844   }
845 
846   if (aSize > mWebSocket->mOutgoingBufferedAmount) {
847     return NS_ERROR_UNEXPECTED;
848   }
849 
850   mWebSocket->mOutgoingBufferedAmount -= aSize;
851   return NS_OK;
852 }
853 
854 NS_IMETHODIMP
OnServerClose(nsISupports * aContext,uint16_t aCode,const nsACString & aReason)855 WebSocketImpl::OnServerClose(nsISupports *aContext, uint16_t aCode,
856                              const nsACString &aReason)
857 {
858   AssertIsOnTargetThread();
859 
860   if (mDisconnectingOrDisconnected) {
861     return NS_OK;
862   }
863 
864   int16_t readyState = mWebSocket->ReadyState();
865 
866   MOZ_ASSERT(readyState != WebSocket::CONNECTING,
867              "Received server close before connected?");
868   MOZ_ASSERT(readyState != WebSocket::CLOSED,
869              "Received server close after already closed!");
870 
871   // store code/string for onclose DOM event
872   mCloseEventCode = aCode;
873   CopyUTF8toUTF16(aReason, mCloseEventReason);
874 
875   if (readyState == WebSocket::OPEN) {
876     // Server initiating close.
877     // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
878     // typically echos the status code it received".
879     // But never send certain codes, per section 7.4.1
880     if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
881       CloseConnection(0, EmptyCString());
882     } else {
883       CloseConnection(aCode, aReason);
884     }
885   } else {
886     // We initiated close, and server has replied: OnStop does rest of the work.
887     MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
888   }
889 
890   return NS_OK;
891 }
892 
893 //-----------------------------------------------------------------------------
894 // WebSocketImpl::nsIInterfaceRequestor
895 //-----------------------------------------------------------------------------
896 
897 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)898 WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
899 {
900   AssertIsOnMainThread();
901 
902   if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
903     return NS_ERROR_FAILURE;
904   }
905 
906   if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
907       aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
908     nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent();
909     if (!win) {
910       return NS_ERROR_NOT_AVAILABLE;
911     }
912 
913     nsresult rv;
914     nsCOMPtr<nsIPromptFactory> wwatch =
915       do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
916     NS_ENSURE_SUCCESS(rv, rv);
917 
918     nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow();
919     return wwatch->GetPrompt(outerWindow, aIID, aResult);
920   }
921 
922   return QueryInterface(aIID, aResult);
923 }
924 
925 ////////////////////////////////////////////////////////////////////////////////
926 // WebSocket
927 ////////////////////////////////////////////////////////////////////////////////
928 
WebSocket(nsPIDOMWindowInner * aOwnerWindow)929 WebSocket::WebSocket(nsPIDOMWindowInner* aOwnerWindow)
930   : DOMEventTargetHelper(aOwnerWindow)
931   , mIsMainThread(true)
932   , mKeepingAlive(false)
933   , mCheckMustKeepAlive(true)
934   , mOutgoingBufferedAmount(0)
935   , mBinaryType(dom::BinaryType::Blob)
936   , mMutex("WebSocket::mMutex")
937   , mReadyState(CONNECTING)
938 {
939   mImpl = new WebSocketImpl(this);
940   mIsMainThread = mImpl->mIsMainThread;
941 }
942 
~WebSocket()943 WebSocket::~WebSocket()
944 {
945 }
946 
947 JSObject*
WrapObject(JSContext * cx,JS::Handle<JSObject * > aGivenProto)948 WebSocket::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
949 {
950   return WebSocketBinding::Wrap(cx, this, aGivenProto);
951 }
952 
953 //---------------------------------------------------------------------------
954 // WebIDL
955 //---------------------------------------------------------------------------
956 
957 // Constructor:
958 already_AddRefed<WebSocket>
Constructor(const GlobalObject & aGlobal,const nsAString & aUrl,ErrorResult & aRv)959 WebSocket::Constructor(const GlobalObject& aGlobal,
960                        const nsAString& aUrl,
961                        ErrorResult& aRv)
962 {
963   Sequence<nsString> protocols;
964   return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
965                                       EmptyCString(), aRv);
966 }
967 
968 already_AddRefed<WebSocket>
Constructor(const GlobalObject & aGlobal,const nsAString & aUrl,const nsAString & aProtocol,ErrorResult & aRv)969 WebSocket::Constructor(const GlobalObject& aGlobal,
970                        const nsAString& aUrl,
971                        const nsAString& aProtocol,
972                        ErrorResult& aRv)
973 {
974   Sequence<nsString> protocols;
975   if (!protocols.AppendElement(aProtocol, fallible)) {
976     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
977     return nullptr;
978   }
979 
980   return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
981                                       EmptyCString(), aRv);
982 }
983 
984 already_AddRefed<WebSocket>
Constructor(const GlobalObject & aGlobal,const nsAString & aUrl,const Sequence<nsString> & aProtocols,ErrorResult & aRv)985 WebSocket::Constructor(const GlobalObject& aGlobal,
986                        const nsAString& aUrl,
987                        const Sequence<nsString>& aProtocols,
988                        ErrorResult& aRv)
989 {
990   return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
991                                       EmptyCString(), aRv);
992 }
993 
994 already_AddRefed<WebSocket>
CreateServerWebSocket(const GlobalObject & aGlobal,const nsAString & aUrl,const Sequence<nsString> & aProtocols,nsITransportProvider * aTransportProvider,const nsAString & aNegotiatedExtensions,ErrorResult & aRv)995 WebSocket::CreateServerWebSocket(const GlobalObject& aGlobal,
996                                  const nsAString& aUrl,
997                                  const Sequence<nsString>& aProtocols,
998                                  nsITransportProvider* aTransportProvider,
999                                  const nsAString& aNegotiatedExtensions,
1000                                  ErrorResult& aRv)
1001 {
1002   return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, aTransportProvider,
1003                                       NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
1004 }
1005 
1006 namespace {
1007 
1008 // This class is used to clear any exception.
1009 class MOZ_STACK_CLASS ClearException
1010 {
1011 public:
ClearException(JSContext * aCx)1012   explicit ClearException(JSContext* aCx)
1013     : mCx(aCx)
1014   {
1015   }
1016 
~ClearException()1017   ~ClearException()
1018   {
1019     JS_ClearPendingException(mCx);
1020   }
1021 
1022 private:
1023   JSContext* mCx;
1024 };
1025 
1026 class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable
1027 {
1028 public:
WebSocketMainThreadRunnable(WorkerPrivate * aWorkerPrivate,const nsACString & aTelemetryKey)1029   WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate,
1030                               const nsACString& aTelemetryKey)
1031     : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey)
1032   {
1033     MOZ_ASSERT(aWorkerPrivate);
1034     aWorkerPrivate->AssertIsOnWorkerThread();
1035   }
1036 
MainThreadRun()1037   bool MainThreadRun() override
1038   {
1039     AssertIsOnMainThread();
1040 
1041     // Walk up to our containing page
1042     WorkerPrivate* wp = mWorkerPrivate;
1043     while (wp->GetParent()) {
1044       wp = wp->GetParent();
1045     }
1046 
1047     nsPIDOMWindowInner* window = wp->GetWindow();
1048     if (window) {
1049       return InitWithWindow(window);
1050     }
1051 
1052     return InitWindowless(wp);
1053   }
1054 
1055 protected:
1056   virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0;
1057 
1058   virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
1059 };
1060 
1061 class InitRunnable final : public WebSocketMainThreadRunnable
1062 {
1063 public:
InitRunnable(WebSocketImpl * aImpl,bool aIsServerSide,const nsAString & aURL,nsTArray<nsString> & aProtocolArray,const nsACString & aScriptFile,uint32_t aScriptLine,uint32_t aScriptColumn,ErrorResult & aRv,bool * aConnectionFailed)1064   InitRunnable(WebSocketImpl* aImpl, bool aIsServerSide,
1065                const nsAString& aURL,
1066                nsTArray<nsString>& aProtocolArray,
1067                const nsACString& aScriptFile, uint32_t aScriptLine,
1068                uint32_t aScriptColumn,
1069                ErrorResult& aRv, bool* aConnectionFailed)
1070     : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
1071                                   NS_LITERAL_CSTRING("WebSocket :: init"))
1072     , mImpl(aImpl)
1073     , mIsServerSide(aIsServerSide)
1074     , mURL(aURL)
1075     , mProtocolArray(aProtocolArray)
1076     , mScriptFile(aScriptFile)
1077     , mScriptLine(aScriptLine)
1078     , mScriptColumn(aScriptColumn)
1079     , mRv(aRv)
1080     , mConnectionFailed(aConnectionFailed)
1081   {
1082     MOZ_ASSERT(mWorkerPrivate);
1083     mWorkerPrivate->AssertIsOnWorkerThread();
1084   }
1085 
1086 protected:
InitWithWindow(nsPIDOMWindowInner * aWindow)1087   virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override
1088   {
1089     AutoJSAPI jsapi;
1090     if (NS_WARN_IF(!jsapi.Init(aWindow))) {
1091       mRv.Throw(NS_ERROR_FAILURE);
1092       return true;
1093     }
1094 
1095     ClearException ce(jsapi.cx());
1096 
1097     nsIDocument* doc = aWindow->GetExtantDoc();
1098     if (!doc) {
1099       mRv.Throw(NS_ERROR_FAILURE);
1100       return true;
1101     }
1102 
1103     nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
1104     if (!principal) {
1105       mRv.Throw(NS_ERROR_FAILURE);
1106       return true;
1107     }
1108 
1109     mImpl->Init(jsapi.cx(), principal, mIsServerSide, mURL, mProtocolArray,
1110                 mScriptFile, mScriptLine, mScriptColumn, mRv,
1111                 mConnectionFailed);
1112     return true;
1113   }
1114 
InitWindowless(WorkerPrivate * aTopLevelWorkerPrivate)1115   virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
1116   {
1117     MOZ_ASSERT(NS_IsMainThread());
1118     MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1119 
1120     mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide,
1121                 mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn,
1122                 mRv, mConnectionFailed);
1123     return true;
1124   }
1125 
1126   // Raw pointer. This worker runs synchronously.
1127   WebSocketImpl* mImpl;
1128 
1129   bool mIsServerSide;
1130   const nsAString& mURL;
1131   nsTArray<nsString>& mProtocolArray;
1132   nsCString mScriptFile;
1133   uint32_t mScriptLine;
1134   uint32_t mScriptColumn;
1135   ErrorResult& mRv;
1136   bool* mConnectionFailed;
1137 };
1138 
1139 class AsyncOpenRunnable final : public WebSocketMainThreadRunnable
1140 {
1141 public:
AsyncOpenRunnable(WebSocketImpl * aImpl,ErrorResult & aRv)1142   AsyncOpenRunnable(WebSocketImpl* aImpl, ErrorResult& aRv)
1143     : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
1144                                   NS_LITERAL_CSTRING("WebSocket :: AsyncOpen"))
1145     , mImpl(aImpl)
1146     , mRv(aRv)
1147   {
1148     MOZ_ASSERT(mWorkerPrivate);
1149     mWorkerPrivate->AssertIsOnWorkerThread();
1150   }
1151 
1152 protected:
InitWithWindow(nsPIDOMWindowInner * aWindow)1153   virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override
1154   {
1155     AssertIsOnMainThread();
1156     MOZ_ASSERT(aWindow);
1157 
1158     nsIDocument* doc = aWindow->GetExtantDoc();
1159     if (!doc) {
1160       mRv.Throw(NS_ERROR_FAILURE);
1161       return true;
1162     }
1163 
1164     nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
1165     if (!principal) {
1166       mRv.Throw(NS_ERROR_FAILURE);
1167       return true;
1168     }
1169 
1170     uint64_t windowID = 0;
1171     nsCOMPtr<nsPIDOMWindowOuter> topWindow = aWindow->GetScriptableTop();
1172     nsCOMPtr<nsPIDOMWindowInner> topInner;
1173     if (topWindow) {
1174       topInner = topWindow->GetCurrentInnerWindow();
1175     }
1176 
1177     if (topInner) {
1178       windowID = topInner->WindowID();
1179     }
1180 
1181     mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), mRv);
1182     return true;
1183   }
1184 
InitWindowless(WorkerPrivate * aTopLevelWorkerPrivate)1185   virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
1186   {
1187     MOZ_ASSERT(NS_IsMainThread());
1188     MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
1189 
1190     mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, nullptr,
1191                      EmptyCString(), mRv);
1192     return true;
1193   }
1194 
1195 private:
1196   // Raw pointer. This worker runs synchronously.
1197   WebSocketImpl* mImpl;
1198 
1199   ErrorResult& mRv;
1200 };
1201 
1202 } // namespace
1203 
1204 already_AddRefed<WebSocket>
ConstructorCommon(const GlobalObject & aGlobal,const nsAString & aUrl,const Sequence<nsString> & aProtocols,nsITransportProvider * aTransportProvider,const nsACString & aNegotiatedExtensions,ErrorResult & aRv)1205 WebSocket::ConstructorCommon(const GlobalObject& aGlobal,
1206                              const nsAString& aUrl,
1207                              const Sequence<nsString>& aProtocols,
1208                              nsITransportProvider* aTransportProvider,
1209                              const nsACString& aNegotiatedExtensions,
1210                              ErrorResult& aRv)
1211 {
1212   MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
1213   nsCOMPtr<nsIPrincipal> principal;
1214   nsCOMPtr<nsPIDOMWindowInner> ownerWindow;
1215 
1216   if (NS_IsMainThread()) {
1217     nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1218       do_QueryInterface(aGlobal.GetAsSupports());
1219     if (!scriptPrincipal) {
1220       aRv.Throw(NS_ERROR_FAILURE);
1221       return nullptr;
1222     }
1223 
1224     principal = scriptPrincipal->GetPrincipal();
1225     if (!principal) {
1226       aRv.Throw(NS_ERROR_FAILURE);
1227       return nullptr;
1228     }
1229 
1230     nsCOMPtr<nsIScriptGlobalObject> sgo =
1231       do_QueryInterface(aGlobal.GetAsSupports());
1232     if (!sgo) {
1233       aRv.Throw(NS_ERROR_FAILURE);
1234       return nullptr;
1235     }
1236 
1237     ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
1238     if (!ownerWindow) {
1239       aRv.Throw(NS_ERROR_FAILURE);
1240       return nullptr;
1241     }
1242   }
1243 
1244   MOZ_ASSERT_IF(ownerWindow, ownerWindow->IsInnerWindow());
1245 
1246   nsTArray<nsString> protocolArray;
1247 
1248   for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
1249 
1250     const nsString& protocolElement = aProtocols[index];
1251 
1252     if (protocolElement.IsEmpty()) {
1253       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1254       return nullptr;
1255     }
1256     if (protocolArray.Contains(protocolElement)) {
1257       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1258       return nullptr;
1259     }
1260     if (protocolElement.FindChar(',') != -1)  /* interferes w/list */ {
1261       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1262       return nullptr;
1263     }
1264 
1265     protocolArray.AppendElement(protocolElement);
1266   }
1267 
1268   RefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
1269   RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl;
1270 
1271   bool connectionFailed = true;
1272 
1273   if (NS_IsMainThread()) {
1274     webSocketImpl->Init(aGlobal.Context(), principal, !!aTransportProvider,
1275                         aUrl, protocolArray, EmptyCString(),
1276                         0, 0, aRv, &connectionFailed);
1277   } else {
1278     // In workers we have to keep the worker alive using a workerHolder in order
1279     // to dispatch messages correctly.
1280     if (!webSocketImpl->RegisterWorkerHolder()) {
1281       aRv.Throw(NS_ERROR_FAILURE);
1282       return nullptr;
1283     }
1284 
1285     unsigned lineno, column;
1286     JS::AutoFilename file;
1287     if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
1288                                     &column)) {
1289       NS_WARNING("Failed to get line number and filename in workers.");
1290     }
1291 
1292     RefPtr<InitRunnable> runnable =
1293       new InitRunnable(webSocketImpl, !!aTransportProvider, aUrl,
1294                        protocolArray, nsDependentCString(file.get()), lineno,
1295                        column, aRv, &connectionFailed);
1296     runnable->Dispatch(aRv);
1297   }
1298 
1299   if (NS_WARN_IF(aRv.Failed())) {
1300     return nullptr;
1301   }
1302 
1303   // It can be that we have been already disconnected because the WebSocket is
1304   // gone away while we where initializing the webSocket.
1305   if (!webSocket->mImpl) {
1306     aRv.Throw(NS_ERROR_FAILURE);
1307     return nullptr;
1308   }
1309 
1310   // We don't return an error if the connection just failed. Instead we dispatch
1311   // an event.
1312   if (connectionFailed) {
1313     webSocket->mImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
1314   }
1315 
1316   // If we don't have a channel, the connection is failed and onerror() will be
1317   // called asynchrounsly.
1318   if (!webSocket->mImpl->mChannel) {
1319     return webSocket.forget();
1320   }
1321 
1322   class MOZ_STACK_CLASS ClearWebSocket
1323   {
1324   public:
1325     explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
1326       : mWebSocketImpl(aWebSocketImpl)
1327       , mDone(false)
1328     {
1329     }
1330 
1331     void Done()
1332     {
1333        mDone = true;
1334     }
1335 
1336     ~ClearWebSocket()
1337     {
1338       if (!mDone) {
1339         mWebSocketImpl->mChannel = nullptr;
1340         mWebSocketImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
1341       }
1342     }
1343 
1344     WebSocketImpl* mWebSocketImpl;
1345     bool mDone;
1346   };
1347 
1348   ClearWebSocket cws(webSocket->mImpl);
1349 
1350   // This operation must be done on the correct thread. The rest must run on the
1351   // main-thread.
1352   aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
1353   if (NS_WARN_IF(aRv.Failed())) {
1354     return nullptr;
1355   }
1356 
1357   if (NS_IsMainThread()) {
1358     MOZ_ASSERT(principal);
1359 
1360     nsPIDOMWindowOuter* outerWindow = ownerWindow->GetOuterWindow();
1361 
1362     uint64_t windowID = 0;
1363     nsCOMPtr<nsPIDOMWindowOuter> topWindow = outerWindow->GetScriptableTop();
1364     nsCOMPtr<nsPIDOMWindowInner> topInner;
1365     if (topWindow) {
1366       topInner = topWindow->GetCurrentInnerWindow();
1367     }
1368 
1369     if (topInner) {
1370       windowID = topInner->WindowID();
1371     }
1372 
1373     webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
1374                                 aNegotiatedExtensions, aRv);
1375   } else {
1376     MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
1377                "not yet implemented");
1378     RefPtr<AsyncOpenRunnable> runnable =
1379       new AsyncOpenRunnable(webSocket->mImpl, aRv);
1380     runnable->Dispatch(aRv);
1381   }
1382 
1383   if (NS_WARN_IF(aRv.Failed())) {
1384     return nullptr;
1385   }
1386 
1387   // It can be that we have been already disconnected because the WebSocket is
1388   // gone away while we where initializing the webSocket.
1389   if (!webSocket->mImpl) {
1390     aRv.Throw(NS_ERROR_FAILURE);
1391     return nullptr;
1392   }
1393 
1394   // Let's inform devtools about this new active WebSocket.
1395   webSocket->mImpl->mService->WebSocketCreated(webSocket->mImpl->mChannel->Serial(),
1396                                                webSocket->mImpl->mInnerWindowID,
1397                                                webSocket->mURI,
1398                                                webSocket->mImpl->mRequestedProtocolList);
1399 
1400   cws.Done();
1401   return webSocket.forget();
1402 }
1403 
1404 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
1405 
1406 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
1407   bool isBlack = tmp->IsBlack();
1408   if (isBlack || tmp->mKeepingAlive) {
1409     if (tmp->mListenerManager) {
1410       tmp->mListenerManager->MarkForCC();
1411     }
1412     if (!isBlack && tmp->PreservingWrapper()) {
1413       // This marks the wrapper black.
1414       tmp->GetWrapper();
1415     }
1416     return true;
1417   }
1418 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
1419 
1420 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket)
1421   return tmp->IsBlack();
1422 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
1423 
1424 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket)
1425   return tmp->IsBlack();
1426 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
1427 
1428 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
1429                                                DOMEventTargetHelper)
1430 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1431 
1432 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
1433                                                   DOMEventTargetHelper)
1434   if (tmp->mImpl) {
1435     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
1436   }
1437 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1438 
1439 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
1440                                                 DOMEventTargetHelper)
1441   if (tmp->mImpl) {
1442     NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
1443     tmp->mImpl->Disconnect();
1444     MOZ_ASSERT(!tmp->mImpl);
1445   }
1446 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1447 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)1448 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
1449 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
1450 
1451 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
1452 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
1453 
1454 void
1455 WebSocket::DisconnectFromOwner()
1456 {
1457   AssertIsOnMainThread();
1458   DOMEventTargetHelper::DisconnectFromOwner();
1459 
1460   if (mImpl) {
1461     mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
1462   }
1463 
1464   DontKeepAliveAnyMore();
1465 }
1466 
1467 //-----------------------------------------------------------------------------
1468 // WebSocketImpl:: initialization
1469 //-----------------------------------------------------------------------------
1470 
1471 void
Init(JSContext * aCx,nsIPrincipal * aPrincipal,bool aIsServerSide,const nsAString & aURL,nsTArray<nsString> & aProtocolArray,const nsACString & aScriptFile,uint32_t aScriptLine,uint32_t aScriptColumn,ErrorResult & aRv,bool * aConnectionFailed)1472 WebSocketImpl::Init(JSContext* aCx,
1473                     nsIPrincipal* aPrincipal,
1474                     bool aIsServerSide,
1475                     const nsAString& aURL,
1476                     nsTArray<nsString>& aProtocolArray,
1477                     const nsACString& aScriptFile,
1478                     uint32_t aScriptLine,
1479                     uint32_t aScriptColumn,
1480                     ErrorResult& aRv,
1481                     bool* aConnectionFailed)
1482 {
1483   AssertIsOnMainThread();
1484   MOZ_ASSERT(aPrincipal);
1485 
1486   mService = WebSocketEventService::GetOrCreate();
1487 
1488   // We need to keep the implementation alive in case the init disconnects it
1489   // because of some error.
1490   RefPtr<WebSocketImpl> kungfuDeathGrip = this;
1491 
1492   // Attempt to kill "ghost" websocket: but usually too early for check to fail
1493   aRv = mWebSocket->CheckInnerWindowCorrectness();
1494   if (NS_WARN_IF(aRv.Failed())) {
1495     return;
1496   }
1497 
1498   // Shut down websocket if window is frozen or destroyed (only needed for
1499   // "ghost" websockets--see bug 696085)
1500   if (!mWorkerPrivate) {
1501     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1502     if (NS_WARN_IF(!os)) {
1503       aRv.Throw(NS_ERROR_FAILURE);
1504       return;
1505     }
1506 
1507     aRv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
1508     if (NS_WARN_IF(aRv.Failed())) {
1509       return;
1510     }
1511 
1512     aRv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
1513     if (NS_WARN_IF(aRv.Failed())) {
1514       return;
1515     }
1516   }
1517 
1518   if (mWorkerPrivate) {
1519     mScriptFile = aScriptFile;
1520     mScriptLine = aScriptLine;
1521     mScriptColumn = aScriptColumn;
1522   } else {
1523     MOZ_ASSERT(aCx);
1524 
1525     unsigned lineno, column;
1526     JS::AutoFilename file;
1527     if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
1528       mScriptFile = file.get();
1529       mScriptLine = lineno;
1530       mScriptColumn = column;
1531     }
1532   }
1533 
1534   mIsServerSide = aIsServerSide;
1535 
1536   // If we don't have aCx, we are window-less, so we don't have a
1537   // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
1538   // DedicateWorkers created by JSM.
1539   if (aCx) {
1540     mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
1541   }
1542 
1543   // parses the url
1544   aRv = ParseURL(PromiseFlatString(aURL));
1545   if (NS_WARN_IF(aRv.Failed())) {
1546     return;
1547   }
1548 
1549   nsCOMPtr<nsIDocument> originDoc = mWebSocket->GetDocumentIfCurrent();
1550   if (!originDoc) {
1551     nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
1552     if (NS_WARN_IF(NS_FAILED(rv))) {
1553       aRv.Throw(rv);
1554       return;
1555     }
1556   }
1557   mOriginDocument = do_GetWeakReference(originDoc);
1558 
1559   if (!mIsServerSide) {
1560     nsCOMPtr<nsIURI> uri;
1561     {
1562       nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
1563 
1564       // We crash here because we are sure that mURI is a valid URI, so either we
1565       // are OOM'ing or something else bad is happening.
1566       if (NS_WARN_IF(NS_FAILED(rv))) {
1567         MOZ_CRASH();
1568       }
1569     }
1570 
1571     // The 'real' nsHttpChannel of the websocket gets opened in the parent.
1572     // Since we don't serialize the CSP within child and parent and also not
1573     // the context, we have to perform content policy checks here instead of
1574     // AsyncOpen2().
1575     // Please note that websockets can't follow redirects, hence there is no
1576     // need to perform a CSP check after redirects.
1577     int16_t shouldLoad = nsIContentPolicy::ACCEPT;
1578     aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
1579                                     uri,
1580                                     aPrincipal,
1581                                     originDoc,
1582                                     EmptyCString(),
1583                                     nullptr,
1584                                     &shouldLoad,
1585                                     nsContentUtils::GetContentPolicy(),
1586                                     nsContentUtils::GetSecurityManager());
1587 
1588     if (NS_WARN_IF(aRv.Failed())) {
1589       return;
1590     }
1591 
1592     if (NS_CP_REJECTED(shouldLoad)) {
1593       // Disallowed by content policy
1594       aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
1595       return;
1596     }
1597   }
1598 
1599   // Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
1600   // In such a case we have to upgrade ws: to wss: and also update mSecure
1601   // to reflect that upgrade. Please note that we can not upgrade from ws:
1602   // to wss: before performing content policy checks because CSP needs to
1603   // send reports in case the scheme is about to be upgraded.
1604   if (!mIsServerSide && !mSecure && originDoc &&
1605       originDoc->GetUpgradeInsecureRequests(false)) {
1606     // let's use the old specification before the upgrade for logging
1607     NS_ConvertUTF8toUTF16 reportSpec(mURI);
1608 
1609     // upgrade the request from ws:// to wss:// and mark as secure
1610     mURI.ReplaceSubstring("ws://", "wss://");
1611     if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
1612       return;
1613     }
1614     mSecure = true;
1615 
1616     const char16_t* params[] = { reportSpec.get(), u"wss" };
1617     CSP_LogLocalizedStr(u"upgradeInsecureRequest",
1618                         params, ArrayLength(params),
1619                         EmptyString(), // aSourceFile
1620                         EmptyString(), // aScriptSample
1621                         0, // aLineNumber
1622                         0, // aColumnNumber
1623                         nsIScriptError::warningFlag, "CSP",
1624                         mInnerWindowID);
1625   }
1626 
1627   // Don't allow https:// to open ws://
1628   if (!mIsServerSide && !mSecure &&
1629       !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
1630                             false)) {
1631     // Confirmed we are opening plain ws:// and want to prevent this from a
1632     // secure context (e.g. https).
1633     nsCOMPtr<nsIPrincipal> principal;
1634     nsCOMPtr<nsIURI> originURI;
1635     if (mWorkerPrivate) {
1636       // For workers, retrieve the URI from the WorkerPrivate
1637       principal = mWorkerPrivate->GetPrincipal();
1638     } else {
1639       // Check the principal's uri to determine if we were loaded from https.
1640       nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
1641       if (globalObject) {
1642         principal = globalObject->PrincipalOrNull();
1643       }
1644 
1645       nsCOMPtr<nsPIDOMWindowInner> innerWindow;
1646 
1647       while (true) {
1648         if (principal && !principal->GetIsNullPrincipal()) {
1649           break;
1650         }
1651 
1652         if (!innerWindow) {
1653           innerWindow = do_QueryInterface(globalObject);
1654           if (!innerWindow) {
1655             // If we are in a XPConnect sandbox or in a JS component,
1656             // innerWindow will be null. There is nothing on top of this to be
1657             // considered.
1658             break;
1659           }
1660         }
1661 
1662         nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
1663           innerWindow->GetScriptableParent();
1664         if (NS_WARN_IF(!parentWindow)) {
1665           aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1666           return;
1667         }
1668 
1669         nsCOMPtr<nsPIDOMWindowInner> currentInnerWindow =
1670           parentWindow->GetCurrentInnerWindow();
1671         if (NS_WARN_IF(!currentInnerWindow)) {
1672           aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1673           return;
1674         }
1675 
1676         // We are at the top. Let's see if we have an opener window.
1677         if (innerWindow == currentInnerWindow) {
1678           ErrorResult error;
1679           parentWindow =
1680             nsGlobalWindow::Cast(innerWindow)->GetOpenerWindow(error);
1681           if (NS_WARN_IF(error.Failed())) {
1682             error.SuppressException();
1683             aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1684             return;
1685           }
1686 
1687           if (!parentWindow) {
1688             break;
1689           }
1690 
1691           currentInnerWindow = parentWindow->GetCurrentInnerWindow();
1692           if (NS_WARN_IF(!currentInnerWindow)) {
1693             aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1694             return;
1695           }
1696 
1697           MOZ_ASSERT(currentInnerWindow != innerWindow);
1698         }
1699 
1700         innerWindow = currentInnerWindow;
1701 
1702         nsCOMPtr<nsIDocument> document = innerWindow->GetExtantDoc();
1703         if (NS_WARN_IF(!document)) {
1704           aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1705           return;
1706         }
1707 
1708         principal = document->NodePrincipal();
1709       }
1710     }
1711 
1712     if (principal) {
1713       principal->GetURI(getter_AddRefs(originURI));
1714     }
1715 
1716     if (originURI) {
1717       bool originIsHttps = false;
1718       aRv = originURI->SchemeIs("https", &originIsHttps);
1719       if (NS_WARN_IF(aRv.Failed())) {
1720         return;
1721       }
1722       if (originIsHttps) {
1723         aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1724         return;
1725       }
1726     }
1727   }
1728 
1729   // Assign the sub protocol list and scan it for illegal values
1730   for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
1731     for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
1732       if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
1733           aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) {
1734         aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1735         return;
1736       }
1737     }
1738 
1739     if (!mRequestedProtocolList.IsEmpty()) {
1740       mRequestedProtocolList.AppendLiteral(", ");
1741     }
1742 
1743     AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
1744   }
1745 
1746   // the constructor should throw a SYNTAX_ERROR only if it fails to parse the
1747   // url parameter, so don't throw if InitializeConnection fails, and call
1748   // onerror/onclose asynchronously
1749   if (NS_FAILED(InitializeConnection(aPrincipal))) {
1750     *aConnectionFailed = true;
1751   } else {
1752     *aConnectionFailed = false;
1753   }
1754 }
1755 
1756 void
AsyncOpen(nsIPrincipal * aPrincipal,uint64_t aInnerWindowID,nsITransportProvider * aTransportProvider,const nsACString & aNegotiatedExtensions,ErrorResult & aRv)1757 WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
1758                          nsITransportProvider* aTransportProvider,
1759                          const nsACString& aNegotiatedExtensions,
1760                          ErrorResult& aRv)
1761 {
1762   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
1763   MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
1764 
1765   nsCString asciiOrigin;
1766   aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
1767   if (NS_WARN_IF(aRv.Failed())) {
1768     return;
1769   }
1770 
1771   if (aTransportProvider) {
1772     aRv = mChannel->SetServerParameters(aTransportProvider, aNegotiatedExtensions);
1773     if (NS_WARN_IF(aRv.Failed())) {
1774       return;
1775     }
1776   }
1777 
1778   ToLowerCase(asciiOrigin);
1779 
1780   nsCOMPtr<nsIURI> uri;
1781   if (!aTransportProvider) {
1782     aRv = NS_NewURI(getter_AddRefs(uri), mURI);
1783     MOZ_ASSERT(!aRv.Failed());
1784   }
1785 
1786   aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
1787   if (NS_WARN_IF(aRv.Failed())) {
1788     aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
1789     return;
1790   }
1791 
1792   mInnerWindowID = aInnerWindowID;
1793 }
1794 
1795 //-----------------------------------------------------------------------------
1796 // WebSocketImpl methods:
1797 //-----------------------------------------------------------------------------
1798 
1799 class nsAutoCloseWS final
1800 {
1801 public:
nsAutoCloseWS(WebSocketImpl * aWebSocketImpl)1802   explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
1803     : mWebSocketImpl(aWebSocketImpl)
1804   {}
1805 
~nsAutoCloseWS()1806   ~nsAutoCloseWS()
1807   {
1808     if (!mWebSocketImpl->mChannel) {
1809       mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
1810     }
1811   }
1812 private:
1813   RefPtr<WebSocketImpl> mWebSocketImpl;
1814 };
1815 
1816 nsresult
InitializeConnection(nsIPrincipal * aPrincipal)1817 WebSocketImpl::InitializeConnection(nsIPrincipal* aPrincipal)
1818 {
1819   AssertIsOnMainThread();
1820   MOZ_ASSERT(!mChannel, "mChannel should be null");
1821 
1822   nsCOMPtr<nsIWebSocketChannel> wsChannel;
1823   nsAutoCloseWS autoClose(this);
1824   nsresult rv;
1825 
1826   if (mSecure) {
1827     wsChannel =
1828       do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
1829   } else {
1830     wsChannel =
1831       do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
1832   }
1833   NS_ENSURE_SUCCESS(rv, rv);
1834 
1835   // add ourselves to the document's load group and
1836   // provide the http stack the loadgroup info too
1837   nsCOMPtr<nsILoadGroup> loadGroup;
1838   rv = GetLoadGroup(getter_AddRefs(loadGroup));
1839   if (loadGroup) {
1840     rv = wsChannel->SetLoadGroup(loadGroup);
1841     NS_ENSURE_SUCCESS(rv, rv);
1842     rv = loadGroup->AddRequest(this, nullptr);
1843     NS_ENSURE_SUCCESS(rv, rv);
1844 
1845     mWeakLoadGroup = do_GetWeakReference(loadGroup);
1846   }
1847 
1848   // manually adding loadinfo to the channel since it
1849   // was not set during channel creation.
1850   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mOriginDocument);
1851 
1852   // mOriginDocument has to be release on main-thread because WeakReferences
1853   // are not thread-safe.
1854   mOriginDocument = nullptr;
1855 
1856 
1857   // The TriggeringPrincipal for websockets must always be a script.
1858   // Let's make sure that the doc's principal (if a doc exists)
1859   // and aPrincipal are same origin.
1860   MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal));
1861 
1862   wsChannel->InitLoadInfo(doc ? doc->AsDOMNode() : nullptr,
1863                           doc ? doc->NodePrincipal() : aPrincipal,
1864                           aPrincipal,
1865                           nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
1866                           nsIContentPolicy::TYPE_WEBSOCKET);
1867 
1868   if (!mRequestedProtocolList.IsEmpty()) {
1869     rv = wsChannel->SetProtocol(mRequestedProtocolList);
1870     NS_ENSURE_SUCCESS(rv, rv);
1871   }
1872 
1873   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
1874   NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
1875 
1876   rv = rr->RetargetDeliveryTo(this);
1877   NS_ENSURE_SUCCESS(rv, rv);
1878 
1879   mChannel = wsChannel;
1880 
1881   return NS_OK;
1882 }
1883 
1884 void
DispatchConnectionCloseEvents()1885 WebSocketImpl::DispatchConnectionCloseEvents()
1886 {
1887   AssertIsOnTargetThread();
1888 
1889   if (mDisconnectingOrDisconnected) {
1890     return;
1891   }
1892 
1893   mWebSocket->SetReadyState(WebSocket::CLOSED);
1894 
1895   // Let's keep the object alive because the webSocket can be CCed in the
1896   // onerror or in the onclose callback.
1897   RefPtr<WebSocket> webSocket = mWebSocket;
1898 
1899   // Call 'onerror' if needed
1900   if (mFailed) {
1901     nsresult rv =
1902       webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
1903     if (NS_FAILED(rv)) {
1904       NS_WARNING("Failed to dispatch the error event");
1905     }
1906   }
1907 
1908   nsresult rv = webSocket->CreateAndDispatchCloseEvent(mCloseEventWasClean,
1909                                                        mCloseEventCode,
1910                                                        mCloseEventReason);
1911   if (NS_FAILED(rv)) {
1912     NS_WARNING("Failed to dispatch the close event");
1913   }
1914 
1915   webSocket->UpdateMustKeepAlive();
1916   Disconnect();
1917 }
1918 
1919 nsresult
CreateAndDispatchSimpleEvent(const nsAString & aName)1920 WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
1921 {
1922   MOZ_ASSERT(mImpl);
1923   AssertIsOnTargetThread();
1924 
1925   nsresult rv = CheckInnerWindowCorrectness();
1926   if (NS_FAILED(rv)) {
1927     return NS_OK;
1928   }
1929 
1930   RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1931 
1932   // it doesn't bubble, and it isn't cancelable
1933   event->InitEvent(aName, false, false);
1934   event->SetTrusted(true);
1935 
1936   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
1937 }
1938 
1939 nsresult
CreateAndDispatchMessageEvent(const nsACString & aData,bool aIsBinary)1940 WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
1941                                          bool aIsBinary)
1942 {
1943   MOZ_ASSERT(mImpl);
1944   AssertIsOnTargetThread();
1945 
1946   AutoJSAPI jsapi;
1947 
1948   if (NS_IsMainThread()) {
1949     if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
1950       return NS_ERROR_FAILURE;
1951     }
1952   } else {
1953     MOZ_ASSERT(!mIsMainThread);
1954     MOZ_ASSERT(mImpl->mWorkerPrivate);
1955     if (NS_WARN_IF(!jsapi.Init(mImpl->mWorkerPrivate->GlobalScope()))) {
1956       return NS_ERROR_FAILURE;
1957     }
1958   }
1959 
1960   JSContext* cx = jsapi.cx();
1961 
1962   nsresult rv = CheckInnerWindowCorrectness();
1963   if (NS_FAILED(rv)) {
1964     return NS_OK;
1965   }
1966 
1967   uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
1968 
1969   // Create appropriate JS object for message
1970   JS::Rooted<JS::Value> jsData(cx);
1971   if (aIsBinary) {
1972     if (mBinaryType == dom::BinaryType::Blob) {
1973       messageType = nsIWebSocketEventListener::TYPE_BLOB;
1974 
1975       RefPtr<Blob> blob =
1976         Blob::CreateStringBlob(GetOwner(), aData, EmptyString());
1977       MOZ_ASSERT(blob);
1978 
1979       if (!ToJSValue(cx, blob, &jsData)) {
1980         return NS_ERROR_FAILURE;
1981       }
1982 
1983     } else if (mBinaryType == dom::BinaryType::Arraybuffer) {
1984       messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;
1985 
1986       JS::Rooted<JSObject*> arrayBuf(cx);
1987       nsresult rv = nsContentUtils::CreateArrayBuffer(cx, aData,
1988                                                       arrayBuf.address());
1989       NS_ENSURE_SUCCESS(rv, rv);
1990       jsData.setObject(*arrayBuf);
1991     } else {
1992       NS_RUNTIMEABORT("Unknown binary type!");
1993       return NS_ERROR_UNEXPECTED;
1994     }
1995   } else {
1996     // JS string
1997     NS_ConvertUTF8toUTF16 utf16Data(aData);
1998     JSString* jsString;
1999     jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
2000     NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
2001 
2002     jsData.setString(jsString);
2003   }
2004 
2005   mImpl->mService->WebSocketMessageAvailable(mImpl->mChannel->Serial(),
2006                                              mImpl->mInnerWindowID,
2007                                              aData, messageType);
2008 
2009   // create an event that uses the MessageEvent interface,
2010   // which does not bubble, is not cancelable, and has no default action
2011 
2012   RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
2013 
2014   event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"), false, false,
2015                           jsData, mImpl->mUTF16Origin, EmptyString(), nullptr,
2016                           Sequence<OwningNonNull<MessagePort>>());
2017   event->SetTrusted(true);
2018 
2019   return DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
2020                           nullptr);
2021 }
2022 
2023 nsresult
CreateAndDispatchCloseEvent(bool aWasClean,uint16_t aCode,const nsAString & aReason)2024 WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
2025                                        uint16_t aCode,
2026                                        const nsAString& aReason)
2027 {
2028   AssertIsOnTargetThread();
2029 
2030   // This method is called by a runnable and it can happen that, in the
2031   // meantime, GC unlinked this object, so mImpl could be null.
2032   if (mImpl && mImpl->mChannel) {
2033     mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
2034                                      mImpl->mInnerWindowID,
2035                                      aWasClean, aCode, aReason);
2036   }
2037 
2038   nsresult rv = CheckInnerWindowCorrectness();
2039   if (NS_FAILED(rv)) {
2040     return NS_OK;
2041   }
2042 
2043   CloseEventInit init;
2044   init.mBubbles = false;
2045   init.mCancelable = false;
2046   init.mWasClean = aWasClean;
2047   init.mCode = aCode;
2048   init.mReason = aReason;
2049 
2050   RefPtr<CloseEvent> event =
2051     CloseEvent::Constructor(this, NS_LITERAL_STRING("close"), init);
2052   event->SetTrusted(true);
2053 
2054   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
2055 }
2056 
2057 nsresult
ParseURL(const nsAString & aURL)2058 WebSocketImpl::ParseURL(const nsAString& aURL)
2059 {
2060   AssertIsOnMainThread();
2061   NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
2062 
2063   if (mIsServerSide) {
2064     mWebSocket->mURI = aURL;
2065     CopyUTF16toUTF8(mWebSocket->mURI, mURI);
2066 
2067     return NS_OK;
2068   }
2069 
2070   nsCOMPtr<nsIURI> uri;
2071   nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
2072   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2073 
2074   nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
2075   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2076 
2077   bool hasRef;
2078   rv = parsedURL->GetHasRef(&hasRef);
2079   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef,
2080                  NS_ERROR_DOM_SYNTAX_ERR);
2081 
2082   nsAutoCString scheme;
2083   rv = parsedURL->GetScheme(scheme);
2084   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
2085                  NS_ERROR_DOM_SYNTAX_ERR);
2086 
2087   nsAutoCString host;
2088   rv = parsedURL->GetAsciiHost(host);
2089   NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
2090 
2091   int32_t port;
2092   rv = parsedURL->GetPort(&port);
2093   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2094 
2095   rv = NS_CheckPortSafety(port, scheme.get());
2096   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
2097 
2098   nsAutoCString filePath;
2099   rv = parsedURL->GetFilePath(filePath);
2100   if (filePath.IsEmpty()) {
2101     filePath.Assign('/');
2102   }
2103   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2104 
2105   nsAutoCString query;
2106   rv = parsedURL->GetQuery(query);
2107   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2108 
2109   if (scheme.LowerCaseEqualsLiteral("ws")) {
2110      mSecure = false;
2111      mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
2112   } else if (scheme.LowerCaseEqualsLiteral("wss")) {
2113     mSecure = true;
2114     mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
2115   } else {
2116     return NS_ERROR_DOM_SYNTAX_ERR;
2117   }
2118 
2119   rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
2120   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
2121 
2122   mAsciiHost = host;
2123   ToLowerCase(mAsciiHost);
2124 
2125   mResource = filePath;
2126   if (!query.IsEmpty()) {
2127     mResource.Append('?');
2128     mResource.Append(query);
2129   }
2130   uint32_t length = mResource.Length();
2131   uint32_t i;
2132   for (i = 0; i < length; ++i) {
2133     if (mResource[i] < static_cast<char16_t>(0x0021) ||
2134         mResource[i] > static_cast<char16_t>(0x007E)) {
2135       return NS_ERROR_DOM_SYNTAX_ERR;
2136     }
2137   }
2138 
2139   rv = parsedURL->GetSpec(mURI);
2140   MOZ_ASSERT(NS_SUCCEEDED(rv));
2141 
2142   CopyUTF8toUTF16(mURI, mWebSocket->mURI);
2143   return NS_OK;
2144 }
2145 
2146 //-----------------------------------------------------------------------------
2147 // Methods that keep alive the WebSocket object when:
2148 //   1. the object has registered event listeners that can be triggered
2149 //      ("strong event listeners");
2150 //   2. there are outgoing not sent messages.
2151 //-----------------------------------------------------------------------------
2152 
2153 void
UpdateMustKeepAlive()2154 WebSocket::UpdateMustKeepAlive()
2155 {
2156   // Here we could not have mImpl.
2157   MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2158 
2159   if (!mCheckMustKeepAlive || !mImpl) {
2160     return;
2161   }
2162 
2163   bool shouldKeepAlive = false;
2164   uint16_t readyState = ReadyState();
2165 
2166   if (mListenerManager) {
2167     switch (readyState)
2168     {
2169       case CONNECTING:
2170       {
2171         if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
2172             mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
2173             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
2174             mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
2175           shouldKeepAlive = true;
2176         }
2177       }
2178       break;
2179 
2180       case OPEN:
2181       case CLOSING:
2182       {
2183         if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
2184             mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
2185             mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
2186             mOutgoingBufferedAmount != 0) {
2187           shouldKeepAlive = true;
2188         }
2189       }
2190       break;
2191 
2192       case CLOSED:
2193       {
2194         shouldKeepAlive = false;
2195       }
2196     }
2197   }
2198 
2199   if (mKeepingAlive && !shouldKeepAlive) {
2200     mKeepingAlive = false;
2201     mImpl->ReleaseObject();
2202   } else if (!mKeepingAlive && shouldKeepAlive) {
2203     mKeepingAlive = true;
2204     mImpl->AddRefObject();
2205   }
2206 }
2207 
2208 void
DontKeepAliveAnyMore()2209 WebSocket::DontKeepAliveAnyMore()
2210 {
2211   // Here we could not have mImpl.
2212   MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2213 
2214   if (mKeepingAlive) {
2215     MOZ_ASSERT(mImpl);
2216 
2217     mKeepingAlive = false;
2218     mImpl->ReleaseObject();
2219   }
2220 
2221   mCheckMustKeepAlive = false;
2222 }
2223 
2224 namespace {
2225 
2226 class WebSocketWorkerHolder final : public WorkerHolder
2227 {
2228 public:
WebSocketWorkerHolder(WebSocketImpl * aWebSocketImpl)2229   explicit WebSocketWorkerHolder(WebSocketImpl* aWebSocketImpl)
2230     : mWebSocketImpl(aWebSocketImpl)
2231   {
2232   }
2233 
Notify(Status aStatus)2234   bool Notify(Status aStatus) override
2235   {
2236     MOZ_ASSERT(aStatus > workers::Running);
2237 
2238     if (aStatus >= Canceling) {
2239       {
2240         MutexAutoLock lock(mWebSocketImpl->mMutex);
2241         mWebSocketImpl->mWorkerShuttingDown = true;
2242       }
2243 
2244       mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY,
2245                                       EmptyCString());
2246     }
2247 
2248     return true;
2249   }
2250 
2251 private:
2252   WebSocketImpl* mWebSocketImpl;
2253 };
2254 
2255 } // namespace
2256 
2257 void
AddRefObject()2258 WebSocketImpl::AddRefObject()
2259 {
2260   AssertIsOnTargetThread();
2261   AddRef();
2262 }
2263 
2264 void
ReleaseObject()2265 WebSocketImpl::ReleaseObject()
2266 {
2267   AssertIsOnTargetThread();
2268   Release();
2269 }
2270 
2271 bool
RegisterWorkerHolder()2272 WebSocketImpl::RegisterWorkerHolder()
2273 {
2274   mWorkerPrivate->AssertIsOnWorkerThread();
2275   MOZ_ASSERT(!mWorkerHolder);
2276   mWorkerHolder = new WebSocketWorkerHolder(this);
2277 
2278   if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
2279     mWorkerHolder = nullptr;
2280     return false;
2281   }
2282 
2283 #ifdef DEBUG
2284   SetHasWorkerHolderRegistered(true);
2285 #endif
2286 
2287   return true;
2288 }
2289 
2290 void
UnregisterWorkerHolder()2291 WebSocketImpl::UnregisterWorkerHolder()
2292 {
2293   MOZ_ASSERT(mDisconnectingOrDisconnected);
2294   MOZ_ASSERT(mWorkerPrivate);
2295   mWorkerPrivate->AssertIsOnWorkerThread();
2296   MOZ_ASSERT(mWorkerHolder);
2297 
2298   {
2299     MutexAutoLock lock(mMutex);
2300     mWorkerShuttingDown = true;
2301   }
2302 
2303   // The DTOR of this WorkerHolder will release the worker for us.
2304   mWorkerHolder = nullptr;
2305 
2306   mWorkerPrivate = nullptr;
2307 
2308 #ifdef DEBUG
2309   SetHasWorkerHolderRegistered(false);
2310 #endif
2311 }
2312 
2313 nsresult
UpdateURI()2314 WebSocketImpl::UpdateURI()
2315 {
2316   AssertIsOnTargetThread();
2317 
2318   // Check for Redirections
2319   RefPtr<BaseWebSocketChannel> channel;
2320   channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
2321   MOZ_ASSERT(channel);
2322 
2323   channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
2324   mSecure = channel->IsEncrypted();
2325 
2326   return NS_OK;
2327 }
2328 
2329 void
EventListenerAdded(nsIAtom * aType)2330 WebSocket::EventListenerAdded(nsIAtom* aType)
2331 {
2332   AssertIsOnMainThread();
2333   UpdateMustKeepAlive();
2334 }
2335 
2336 void
EventListenerRemoved(nsIAtom * aType)2337 WebSocket::EventListenerRemoved(nsIAtom* aType)
2338 {
2339   AssertIsOnMainThread();
2340   UpdateMustKeepAlive();
2341 }
2342 
2343 //-----------------------------------------------------------------------------
2344 // WebSocket - methods
2345 //-----------------------------------------------------------------------------
2346 
2347 // webIDL: readonly attribute unsigned short readyState;
2348 uint16_t
ReadyState()2349 WebSocket::ReadyState()
2350 {
2351   MutexAutoLock lock(mMutex);
2352   return mReadyState;
2353 }
2354 
2355 void
SetReadyState(uint16_t aReadyState)2356 WebSocket::SetReadyState(uint16_t aReadyState)
2357 {
2358   MutexAutoLock lock(mMutex);
2359   mReadyState = aReadyState;
2360 }
2361 
2362 // webIDL: readonly attribute unsigned long bufferedAmount;
2363 uint32_t
BufferedAmount() const2364 WebSocket::BufferedAmount() const
2365 {
2366   AssertIsOnTargetThread();
2367   return mOutgoingBufferedAmount;
2368 }
2369 
2370 // webIDL: attribute BinaryType binaryType;
2371 dom::BinaryType
BinaryType() const2372 WebSocket::BinaryType() const
2373 {
2374   AssertIsOnTargetThread();
2375   return mBinaryType;
2376 }
2377 
2378 // webIDL: attribute BinaryType binaryType;
2379 void
SetBinaryType(dom::BinaryType aData)2380 WebSocket::SetBinaryType(dom::BinaryType aData)
2381 {
2382   AssertIsOnTargetThread();
2383   mBinaryType = aData;
2384 }
2385 
2386 // webIDL: readonly attribute DOMString url
2387 void
GetUrl(nsAString & aURL)2388 WebSocket::GetUrl(nsAString& aURL)
2389 {
2390   AssertIsOnTargetThread();
2391 
2392   if (mEffectiveURL.IsEmpty()) {
2393     aURL = mURI;
2394   } else {
2395     aURL = mEffectiveURL;
2396   }
2397 }
2398 
2399 // webIDL: readonly attribute DOMString extensions;
2400 void
GetExtensions(nsAString & aExtensions)2401 WebSocket::GetExtensions(nsAString& aExtensions)
2402 {
2403   AssertIsOnTargetThread();
2404   CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
2405 }
2406 
2407 // webIDL: readonly attribute DOMString protocol;
2408 void
GetProtocol(nsAString & aProtocol)2409 WebSocket::GetProtocol(nsAString& aProtocol)
2410 {
2411   AssertIsOnTargetThread();
2412   CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
2413 }
2414 
2415 // webIDL: void send(DOMString data);
2416 void
Send(const nsAString & aData,ErrorResult & aRv)2417 WebSocket::Send(const nsAString& aData,
2418                 ErrorResult& aRv)
2419 {
2420   AssertIsOnTargetThread();
2421 
2422   NS_ConvertUTF16toUTF8 msgString(aData);
2423   Send(nullptr, msgString, msgString.Length(), false, aRv);
2424 }
2425 
2426 void
Send(Blob & aData,ErrorResult & aRv)2427 WebSocket::Send(Blob& aData, ErrorResult& aRv)
2428 {
2429   AssertIsOnTargetThread();
2430 
2431   nsCOMPtr<nsIInputStream> msgStream;
2432   aData.GetInternalStream(getter_AddRefs(msgStream), aRv);
2433   if (NS_WARN_IF(aRv.Failed())){
2434     return;
2435   }
2436 
2437   uint64_t msgLength = aData.GetSize(aRv);
2438   if (NS_WARN_IF(aRv.Failed())){
2439     return;
2440   }
2441 
2442   if (msgLength > UINT32_MAX) {
2443     aRv.Throw(NS_ERROR_FILE_TOO_BIG);
2444     return;
2445   }
2446 
2447   Send(msgStream, EmptyCString(), msgLength, true, aRv);
2448 }
2449 
2450 void
Send(const ArrayBuffer & aData,ErrorResult & aRv)2451 WebSocket::Send(const ArrayBuffer& aData,
2452                 ErrorResult& aRv)
2453 {
2454   AssertIsOnTargetThread();
2455 
2456   aData.ComputeLengthAndData();
2457 
2458   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
2459 
2460   uint32_t len = aData.Length();
2461   char* data = reinterpret_cast<char*>(aData.Data());
2462 
2463   nsDependentCSubstring msgString(data, len);
2464   Send(nullptr, msgString, len, true, aRv);
2465 }
2466 
2467 void
Send(const ArrayBufferView & aData,ErrorResult & aRv)2468 WebSocket::Send(const ArrayBufferView& aData,
2469                 ErrorResult& aRv)
2470 {
2471   AssertIsOnTargetThread();
2472 
2473   aData.ComputeLengthAndData();
2474 
2475   static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
2476 
2477   uint32_t len = aData.Length();
2478   char* data = reinterpret_cast<char*>(aData.Data());
2479 
2480   nsDependentCSubstring msgString(data, len);
2481   Send(nullptr, msgString, len, true, aRv);
2482 }
2483 
2484 void
Send(nsIInputStream * aMsgStream,const nsACString & aMsgString,uint32_t aMsgLength,bool aIsBinary,ErrorResult & aRv)2485 WebSocket::Send(nsIInputStream* aMsgStream,
2486                 const nsACString& aMsgString,
2487                 uint32_t aMsgLength,
2488                 bool aIsBinary,
2489                 ErrorResult& aRv)
2490 {
2491   AssertIsOnTargetThread();
2492 
2493   int64_t readyState = ReadyState();
2494   if (readyState == CONNECTING) {
2495     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2496     return;
2497   }
2498 
2499   // Always increment outgoing buffer len, even if closed
2500   CheckedUint32 size = mOutgoingBufferedAmount;
2501   size += aMsgLength;
2502   if (!size.isValid()) {
2503     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
2504     return;
2505   }
2506 
2507   mOutgoingBufferedAmount = size.value();
2508 
2509   if (readyState == CLOSING ||
2510       readyState == CLOSED) {
2511     return;
2512   }
2513 
2514   // We must have mImpl when connected.
2515   MOZ_ASSERT(mImpl);
2516   MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
2517 
2518   nsresult rv;
2519   if (aMsgStream) {
2520     rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
2521   } else {
2522     if (aIsBinary) {
2523       rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
2524     } else {
2525       rv = mImpl->mChannel->SendMsg(aMsgString);
2526     }
2527   }
2528 
2529   if (NS_FAILED(rv)) {
2530     aRv.Throw(rv);
2531     return;
2532   }
2533 
2534   UpdateMustKeepAlive();
2535 }
2536 
2537 // webIDL: void close(optional unsigned short code, optional DOMString reason):
2538 void
Close(const Optional<uint16_t> & aCode,const Optional<nsAString> & aReason,ErrorResult & aRv)2539 WebSocket::Close(const Optional<uint16_t>& aCode,
2540                  const Optional<nsAString>& aReason,
2541                  ErrorResult& aRv)
2542 {
2543   AssertIsOnTargetThread();
2544 
2545   // the reason code is optional, but if provided it must be in a specific range
2546   uint16_t closeCode = 0;
2547   if (aCode.WasPassed()) {
2548     if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
2549       aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
2550       return;
2551     }
2552     closeCode = aCode.Value();
2553   }
2554 
2555   nsCString closeReason;
2556   if (aReason.WasPassed()) {
2557     CopyUTF16toUTF8(aReason.Value(), closeReason);
2558 
2559     // The API requires the UTF-8 string to be 123 or less bytes
2560     if (closeReason.Length() > 123) {
2561       aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
2562       return;
2563     }
2564   }
2565 
2566   int64_t readyState = ReadyState();
2567   if (readyState == CLOSING ||
2568       readyState == CLOSED) {
2569     return;
2570   }
2571 
2572   // If the webSocket is not closed we MUST have a mImpl.
2573   MOZ_ASSERT(mImpl);
2574 
2575   if (readyState == CONNECTING) {
2576     mImpl->FailConnection(closeCode, closeReason);
2577     return;
2578   }
2579 
2580   MOZ_ASSERT(readyState == OPEN);
2581   mImpl->CloseConnection(closeCode, closeReason);
2582 }
2583 
2584 //-----------------------------------------------------------------------------
2585 // WebSocketImpl::nsIObserver
2586 //-----------------------------------------------------------------------------
2587 
2588 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)2589 WebSocketImpl::Observe(nsISupports* aSubject,
2590                        const char* aTopic,
2591                        const char16_t* aData)
2592 {
2593   AssertIsOnMainThread();
2594 
2595   int64_t readyState = mWebSocket->ReadyState();
2596   if ((readyState == WebSocket::CLOSING) ||
2597       (readyState == WebSocket::CLOSED)) {
2598     return NS_OK;
2599   }
2600 
2601   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
2602   if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
2603     return NS_OK;
2604   }
2605 
2606   if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
2607       (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
2608   {
2609     CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
2610   }
2611 
2612   return NS_OK;
2613 }
2614 
2615 //-----------------------------------------------------------------------------
2616 // WebSocketImpl::nsIRequest
2617 //-----------------------------------------------------------------------------
2618 
2619 NS_IMETHODIMP
GetName(nsACString & aName)2620 WebSocketImpl::GetName(nsACString& aName)
2621 {
2622   AssertIsOnMainThread();
2623 
2624   CopyUTF16toUTF8(mWebSocket->mURI, aName);
2625   return NS_OK;
2626 }
2627 
2628 NS_IMETHODIMP
IsPending(bool * aValue)2629 WebSocketImpl::IsPending(bool* aValue)
2630 {
2631   AssertIsOnTargetThread();
2632 
2633   int64_t readyState = mWebSocket->ReadyState();
2634   *aValue = (readyState != WebSocket::CLOSED);
2635   return NS_OK;
2636 }
2637 
2638 NS_IMETHODIMP
GetStatus(nsresult * aStatus)2639 WebSocketImpl::GetStatus(nsresult* aStatus)
2640 {
2641   AssertIsOnTargetThread();
2642 
2643   *aStatus = NS_OK;
2644   return NS_OK;
2645 }
2646 
2647 namespace {
2648 
2649 class CancelRunnable final : public MainThreadWorkerRunnable
2650 {
2651 public:
CancelRunnable(WorkerPrivate * aWorkerPrivate,WebSocketImpl * aImpl)2652   CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
2653     : MainThreadWorkerRunnable(aWorkerPrivate)
2654     , mImpl(aImpl)
2655   {
2656   }
2657 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)2658   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
2659   {
2660     aWorkerPrivate->AssertIsOnWorkerThread();
2661     return !NS_FAILED(mImpl->CancelInternal());
2662   }
2663 
2664 private:
2665   RefPtr<WebSocketImpl> mImpl;
2666 };
2667 
2668 } // namespace
2669 
2670 // Window closed, stop/reload button pressed, user navigated away from page, etc.
2671 NS_IMETHODIMP
Cancel(nsresult aStatus)2672 WebSocketImpl::Cancel(nsresult aStatus)
2673 {
2674   AssertIsOnMainThread();
2675 
2676   if (!mIsMainThread) {
2677     MOZ_ASSERT(mWorkerPrivate);
2678     RefPtr<CancelRunnable> runnable =
2679       new CancelRunnable(mWorkerPrivate, this);
2680     if (!runnable->Dispatch()) {
2681       return NS_ERROR_FAILURE;
2682     }
2683 
2684     return NS_OK;
2685   }
2686 
2687   return CancelInternal();
2688 }
2689 
2690 nsresult
CancelInternal()2691 WebSocketImpl::CancelInternal()
2692 {
2693   AssertIsOnTargetThread();
2694 
2695    // If CancelInternal is called by a runnable, we may already be disconnected
2696    // by the time it runs.
2697   if (mDisconnectingOrDisconnected) {
2698     return NS_OK;
2699   }
2700 
2701   int64_t readyState = mWebSocket->ReadyState();
2702   if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
2703     return NS_OK;
2704   }
2705 
2706   return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
2707 }
2708 
2709 NS_IMETHODIMP
Suspend()2710 WebSocketImpl::Suspend()
2711 {
2712   AssertIsOnMainThread();
2713   return NS_ERROR_NOT_IMPLEMENTED;
2714 }
2715 
2716 NS_IMETHODIMP
Resume()2717 WebSocketImpl::Resume()
2718 {
2719   AssertIsOnMainThread();
2720   return NS_ERROR_NOT_IMPLEMENTED;
2721 }
2722 
2723 NS_IMETHODIMP
GetLoadGroup(nsILoadGroup ** aLoadGroup)2724 WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup)
2725 {
2726   AssertIsOnMainThread();
2727 
2728   *aLoadGroup = nullptr;
2729 
2730   if (mIsMainThread) {
2731     nsCOMPtr<nsIDocument> doc = mWebSocket->GetDocumentIfCurrent();
2732     if (doc) {
2733       *aLoadGroup = doc->GetDocumentLoadGroup().take();
2734     }
2735 
2736     return NS_OK;
2737   }
2738 
2739   MOZ_ASSERT(mWorkerPrivate);
2740 
2741   // Walk up to our containing page
2742   WorkerPrivate* wp = mWorkerPrivate;
2743   while (wp->GetParent()) {
2744     wp = wp->GetParent();
2745   }
2746 
2747   nsPIDOMWindowInner* window = wp->GetWindow();
2748   if (!window) {
2749     return NS_OK;
2750   }
2751 
2752   nsIDocument* doc = window->GetExtantDoc();
2753   if (doc) {
2754     *aLoadGroup = doc->GetDocumentLoadGroup().take();
2755   }
2756 
2757   return NS_OK;
2758 }
2759 
2760 NS_IMETHODIMP
SetLoadGroup(nsILoadGroup * aLoadGroup)2761 WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup)
2762 {
2763   AssertIsOnMainThread();
2764   return NS_ERROR_UNEXPECTED;
2765 }
2766 
2767 NS_IMETHODIMP
GetLoadFlags(nsLoadFlags * aLoadFlags)2768 WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags)
2769 {
2770   AssertIsOnMainThread();
2771 
2772   *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
2773   return NS_OK;
2774 }
2775 
2776 NS_IMETHODIMP
SetLoadFlags(nsLoadFlags aLoadFlags)2777 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags)
2778 {
2779   AssertIsOnMainThread();
2780 
2781   // we won't change the load flags at all.
2782   return NS_OK;
2783 }
2784 
2785 namespace {
2786 
2787 class WorkerRunnableDispatcher final : public WorkerRunnable
2788 {
2789   RefPtr<WebSocketImpl> mWebSocketImpl;
2790 
2791 public:
WorkerRunnableDispatcher(WebSocketImpl * aImpl,WorkerPrivate * aWorkerPrivate,already_AddRefed<nsIRunnable> aEvent)2792   WorkerRunnableDispatcher(WebSocketImpl* aImpl, WorkerPrivate* aWorkerPrivate,
2793                            already_AddRefed<nsIRunnable> aEvent)
2794     : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
2795     , mWebSocketImpl(aImpl)
2796     , mEvent(Move(aEvent))
2797   {
2798   }
2799 
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)2800   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
2801   {
2802     aWorkerPrivate->AssertIsOnWorkerThread();
2803 
2804     // No messages when disconnected.
2805     if (mWebSocketImpl->mDisconnectingOrDisconnected) {
2806       NS_WARNING("Dispatching a WebSocket event after the disconnection!");
2807       return true;
2808     }
2809 
2810     return !NS_FAILED(mEvent->Run());
2811   }
2812 
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)2813   void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
2814                bool aRunResult) override
2815   {
2816   }
2817 
2818   bool
PreDispatch(WorkerPrivate * aWorkerPrivate)2819   PreDispatch(WorkerPrivate* aWorkerPrivate) override
2820   {
2821     // We don't call WorkerRunnable::PreDispatch because it would assert the
2822     // wrong thing about which thread we're on.  We're on whichever thread the
2823     // channel implementation is running on (probably the main thread or socket
2824     // transport thread).
2825     return true;
2826   }
2827 
2828   void
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)2829   PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
2830   {
2831     // We don't call WorkerRunnable::PreDispatch because it would assert the
2832     // wrong thing about which thread we're on.  We're on whichever thread the
2833     // channel implementation is running on (probably the main thread or socket
2834     // transport thread).
2835   }
2836 
2837 private:
2838   nsCOMPtr<nsIRunnable> mEvent;
2839 };
2840 
2841 } // namespace
2842 
2843 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aEvent,uint32_t aFlags)2844 WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
2845 {
2846   nsCOMPtr<nsIRunnable> event(aEvent);
2847   return Dispatch(event.forget(), aFlags);
2848 }
2849 
2850 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aFlags)2851 WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
2852 {
2853   nsCOMPtr<nsIRunnable> event_ref(aEvent);
2854   // If the target is the main-thread we can just dispatch the runnable.
2855   if (mIsMainThread) {
2856     return NS_DispatchToMainThread(event_ref.forget());
2857   }
2858 
2859   MutexAutoLock lock(mMutex);
2860   if (mWorkerShuttingDown) {
2861     return NS_OK;
2862   }
2863 
2864   MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate);
2865 
2866 #ifdef DEBUG
2867   MOZ_ASSERT(HasWorkerHolderRegistered());
2868 #endif
2869 
2870   // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
2871   // runnable.
2872   RefPtr<WorkerRunnableDispatcher> event =
2873     new WorkerRunnableDispatcher(this, mWorkerPrivate, event_ref.forget());
2874 
2875   if (!event->Dispatch()) {
2876     return NS_ERROR_FAILURE;
2877   }
2878 
2879   return NS_OK;
2880 }
2881 
2882 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable>,uint32_t)2883 WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t)
2884 {
2885   return NS_ERROR_NOT_IMPLEMENTED;
2886 }
2887 
2888 NS_IMETHODIMP
IsOnCurrentThread(bool * aResult)2889 WebSocketImpl::IsOnCurrentThread(bool* aResult)
2890 {
2891   *aResult = IsTargetThread();
2892   return NS_OK;
2893 }
2894 
2895 bool
IsTargetThread() const2896 WebSocketImpl::IsTargetThread() const
2897 {
2898   return NS_IsMainThread() == mIsMainThread;
2899 }
2900 
2901 void
AssertIsOnTargetThread() const2902 WebSocket::AssertIsOnTargetThread() const
2903 {
2904   MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
2905 }
2906 
2907 } // namespace dom
2908 } // namespace mozilla
2909