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