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