Files
palemoon27/dom/base/WebSocket.cpp
T
roytam1 e4c3e62beb import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1216751 part 1. Restrict value iterators to interfaces that have indexed properties and pair iterators to interfaces that do not have indexed properties. r=qdot (6519f3f8c5)
- Bug 1216751 part 2. For value iterators, "entries", "keys", and "values" must just come from Array.prototype. r=qdot (c0859f945c)
- Bug 1216751 part 3. For pair iterators, @@iterator should be an alias for "entries". Similarly for maplikes and "entries" and setlikes and "values". r=qdot (bbe7c04782)
- Bug 1216751 part 4. Implement forEach for iterable interfaces. r=qdot (8fdba677a4)
- Bug 1216751 part 5. Remove the now-unnecessary value iterator infrastructure, since it's entirely handled via the %ArrayPrototype% methods now. r=qdot (88d3911694)
- Bug 1231333 - part 1, JS engine: only allow futexWait in workers. r=luke (28e16fd2f9)
- Bug 1231333 - part 2, DOM: only allow futexWait in workers. r=khuey (6c4dc98037)
- Bug 1148990 - Don't ship bagheeraclient.js or tokenserverclient.js on Android. r=gps (aa9b22699a)
- Bug 1216749 - Land the Firefox Kinto.js client (r=rnewman) (ea8c74e2ea)
- Bug 1230221 - Convert JS callsites to use asyncOpen2 within services/ (r=sicking) (07ac8751f1)
- Bug 1242965 - Make services/common eslintable. r=rnewman (0c84562750)
- Bug 1055616 - Skip addons addons without a sourceURI or from a non-secure domain rather than treating them as errors. r=rnewman (7b8b738be0)
- Bug 1229986 - get Sync tps tests starting again. r=whimboo (8cd0bf4f7f)
- Bug 1003204: Removed CommonUtils.exceptionStr() in services/sync r=makh r=gfritzsche (830c106a29)
- Bug 1003204: Removed CommonUtils.exceptionStr() in services/common/ r=gfritzsche (2c7bd4f8b5)
- Bug 1234734 - Replace CommonUtils.stackTrace() with Log.stackTrace(). r=markh (3f0e88f192)
- Bug 1241715 - get Sync TPS tests working locally by tweaking observers listened for and the authentication setup. r=whimboo (529b2f3d44)
- Bug 1203736 - Convert H264::DecodeSPS assert to error return. r=jya (41c8c34c42)
- Bug 1186716: Error if SPS NAL parsing failed. r=rillian (6c158be51e)
- Bug 1187076 - Warn at end of SPS buffers. r=jya (2a49671261)
- fix broken files (a090aad200)
- Bug 1218217: avoid buffersize overflow even if codec is unbounded in dimensions r=pkerr (356140c947)
- Bug 1218217: bustage fix for static assert r=bustage (e86dc5bf3a)
- Bug 1041882 - Remove Froyo-specific support from libcubeb. r=snorp, r=padenot (e1f2d5283f)
- Bug 1073319 - Enable AVX2 for libvpx on linux (update.py). r=rillian (934fd0a896)
- Bug 1245027 - Move LOCAL_INCLUDES to moz.build in media/libvpx. r=mshal (7e56797d0e)
- parts of Bug 1151175 - Update libvpx update.py for 1.4.0. (0e3f4a470f)
- bits of 1178215 (bab7592703)
- Bug 1218124 - Add vpx_once patch to update script. r=gerald (7b72a43382)
- Bug 1225221 - vpx: Allow 8k video in update.sh. r=kinetik (9ec59f7737)
- Bug 1224363 - Upstream update patch - r=rillian (4772921a5f)
- Bug 1224361 - Upstream update patch - r=rillian (36ad6f1de4)
- Bug 1233983 - Make libvpx build with clang-cl; r=rillian (5d98a8d888)
- Bug 1224371 - Upstream update patch. r=jya (25164ba856)
- Bug 1237848 - Updated update.py patch - r=rillian (69646eb6dc)
- Bug 1184226 - Suppressing received packets when disabled, r=ekr (c8dfdb1a56)
- Bug 1184226 - Disabling write on shutdown, r=ekr (d5a810dbe5)
- Bug 1184226 - Updating transportlayerdtls logging levels, r=ekr (f3bc4a9889)
- Bug 1137932: Unwind the stack before starting the DTLS handshake. r=mt (69dce8243a)
- Bug 1214269 - read multiple DTLS packets from NSS if present. r=mt rjesup (e57b1628f5)
- Bug 1235235 - Fix -Wimplicit-fallthrough warning in media/mtransport/. r=ekr (d56c9d1244)
- Bug 1115483 - Accept a match on any a=fingerprint value. r=ekr (4a58378c09)
- Bug 1167274 - Do the right thing when accessing the proxyinfo fails for some reason. r=mt (3ea23173ea)
- Bug 1125292 - Sending ALPN header field for WebRTC calls, r=bwc (16fda60c39)
- Bug 1167443 - Fix verification of end-of-candidates in mochitests. r=mt (8d74546e68)
- Bug 1192813 - update the default candidate as new candidates arrive.  r=bwc (490ac80af2)
- Bug 1206981 - prevent ICE TCP from being turned off under e10s. r=jesup (a38afd56b8)
- Bug 1234578 - Assert if PCM is destroyed improperly. r=rjesup (f1aa0d7cbc)
- Bug 1164564 - WorkerDebugger.initialize should not return failure when called more than once;r=khuey (c316c83af7)
- Bug 1211903 - WorkerDebugger should live on the main thread;r=khuey (5586888e77)
- Bug 1164581 - Adding an overload for NS_ProxyRelease that accepts already_AddRefed, and removing all the others. r=bobbyholley (bc70230689)
- Bug 1186750 part 1 - Inlinize trivial constructors and destructors of events in DeviceStorageRequestParent. r=dhylands (0fc6b594b1)
- Bug 1186750 part 2 - Remove some unused member fields in events in DeviceStorageRequestParent. r=dhylands (d4be7e7031)
- Bug 1186750 part 3 - Abstract CancelableFileEvent in DeviceStorageReqeustParent and use already_AddRefed&& for passing DeviceStorageFile parameter. r=dhylands (cea4df4465)
- Bug 1186750 part 4 - Clear runnable list in DeviceStorageRequestParent when being destroyed. r=dhylands (a4d6018ce6)
- Bug 1196315 - Ensure MIME service is only accessed on the main thread. r=dhylands (20c07f4baf)
- Bug 1186750 part 5 - Convert nsDOMDeviceStorage::CheckPermission to take already_AddRefed&&. r=dhylands (7b2d0b415e)
- Bug 1186750 part 6 - Remove unused and unimplemented method nsDOMDeviceStorage::StorePermission. r=dhylands (e6772e7b51)
- Bug 1186750 part 7 - Convert DispatchToOwningThread and DispatchOrAbandon to take already_AddRefed&&. r=dhylands (5925568a22)
- Bug 1186750 part 8 - Convert DeviceStorageUsedSpaceCache::Dispatch to use already_AddRef&&. r=dhylands (660b44eec7)
- Bug 1186750 part 9 - Use already_AddRefed&& to initialize mFile of device storage requests. r=dhylands (c94464f412)
- Bug 1186750 part 10 - Simplify code in DeviceStorageRequestParent::Dispatch. r=dhylands (debcc219ca)
- Bug 1186750 part 11 - Convert all usage of Dispatch/NS_DispatchToMainThread in dom/devicestorage to pass in either already_AddRefed or raw pointer. r=dhylands (753694d0b5)
- Bug 1059469: Part 1 - Add a log module for dump() calls. r=bent (d94c677e49)
- Bug 1059469: Part 2 - When rescheduling the interval timer, cancel it first, and refactor things so that actually does something. r=bent (1edc485b0f)
- Bug 1243881 - patch 1 - unship performance.translateTime, r=bz (5a4afeea67)
- Bug 1243881 - patch 2 - unship performance.translateTime, r=bz (5bf9557cd4)
- Bug 1165722 - Replace JS_GetPropertyDescriptor usage in Xray code. r=bholley (e277cbcc78)
- Bug 1243824. Add support for static functions and attributes on JSXrays. r=bholley (498d6c6034)
- Bug 1228456 - SharedWorker should close the MessagePort in case the connecting runnable is not dispatched, r=smaug (c14a3e212f)
- Bug 779707 - Add crashtest. (e86caca48e)
- Bug 1228456 - add 'override' to the Cancel() method of a nsICancelableRunnable, rs=me (48db3b97e9)
- Bug 1131323 - Enable SharedWorker loads to be intercepted through service workers; r=nsm (b2d972c5e3)
- Bug 1173002 - Set worker system principal flag correctly when created from chrome, r=bz, a=kwierso. (ac9fc2980d)
- bits of 1113429 backout (a862f16bb7)
- bug 1206312 - add IndexedDatabaseManager include to IDBKeyRange. r=bz (bd6663f976)
- Bug 1247117: De-namespace much of IndexedDB. r=baku (a996e3b443)
- Bug 1196841: Update getAll/getAllKeys to match the spec and expose them. r=baku (7365769e04)
- Bug 1196840: Make IDBTransaction::ObjectStoreNames const. r=baku (e7af2b0510)
- Bug 1176165 - Fix the exception codes returned from functions that modify the IndexedDB schema, r=janv. (efa4e818d0)
- Bug 935753 - Firefox displays the "This is a secure Firefox page" indicator on pages served by addons. r=MattN (77dced27ad)
- Bug 925681 - Show identity block and reload icon in awesomebar in Australis' customization mode. ui-r=shorlander, r=Gijs (ffd1b2f6a4)
- Bug 970382 - Add about:accounts to the list of chrome UIs with a special identity mode r=gavin (6d2817d087)
- Bug 1051847 - Add trusted identity block to about:license and about:rights. r=dao (aa8dfe4d1d)
- Bug 1094947 - The trusted identity block is not displayed for the about:downloads page. r=jaws (1c51faa077)
- Bug 686281 - Implement CSS mask style; r=dbaron. (2f823c4a49)
- Bug 686281 - Mask CSS parsing and Mask DOM API. r=dbaron (f9cc291131)
- Bug 686281 - Mask CSS rendering; r=mstange (b26ba7ba7e)
- Bug 686281 - Mask CSS animation; r=dbaron. (4ce1ba671e)
- Bug 686281 - Mask CSS webkit-alias; r=dbaron. (c27f4023d6)
- Bug 686281 - Mask mochitest; r=dbaron. (010fcdfd04)
- Bug 686281 - Expands will-change of a shorthand prop to longhand ones; r=dbaron. (f8e4a6dcfd)
- Bug 686281 - A static assertion to keep value correctness of NS_RULE_NODE_IS_ANIMATION_RULE; r=dbaron. (5ae87b576b)
- Bug 686281 - Remove nsStyleSVGReset::mMask; r=dbaron (1e7a0dfb45)
- Bug 686281 - mask-composite reftests; r=dbaron (7f769e196a)
- Bug 686281 - Rename nsStyleSVGReset::mLayers to nsStyleSVGReset::mMask; Rename nsStyleBackground::mLayers to nsStyleBackground::mImage. r=dbaron (3bd4fc6e3b)
- Bug 1241275 - Change the way -moz-window-dragging works. r=heycam,roc (5691f2dbf5)
- Bug 1246892 - pass aCTF as a reference instead of value. r=roc (98b0e45063)
- Bug 1234800 - Reinstate code that adjusts dirty rects for fixed-position frames in display ports. r=tn (44e55ebacb)
- Bug 1234800 - Move this line to the right place. r=tn (1a86a7fc72)
- Bug 1216832 - Handle preserve-3d visible regions during display list building by always transforming from the preserve-3d root each time. r=roc (1887af1172)
- Bug 1231243 - In nsDisplayBackgroundImage::GetBoundsInternal(), take the union of the image bounds and the viewport bounds if APZ is enabled. r=mstange (87a1fa0ab4)
- Bug 1246622 - Handle nested preserve-3d contexts when hit testing. r=roc (6eed51c734)
- Bug 1235945 - Fix assertion error in some cases when running szip when debug flags are enabled for host tools. r=froydnj (3a0aa4f728)
- Bug 1224798: Do not produce a clip mask if our context is entirely clipped out anyway. r=jrmuizel (3926a4ef7d)
- Bug 1223604 - Disentangle nsSVGClipPathFrame::ApplyClipOrPaintClipMask and make the code easier to understand. r=Bas (c8c19a1b0d)
- Bug 1204405: Don't access prefs off main thread in testing ProcessLink::Open(). r=khuey (301aa7259d)
- Bug 1248896 - don't conditional compile on config ENABLE_TESTS in Nuwa. r=khuey (4f2fd275fd)
- Bug 1232458 - use UniquePtr<T[]> instead of nsAutoArrayPtr<T> in WindowsDllBlocklist.cpp; r=aklotz (292071bdb5)
- Bug 1247741 - Additional checks for pointer validity in LdrLoadDLL detour. r=aklotz (8ee48e8cf3)
- Bug 1113930 - Move __libc_stack_end related code block from StackWalk.cpp in a non-OSX section. r=froydnj (4f0f9e2e66)
- Bug 1113930 - Use the actual stack end address on x86 OSX and Android for the stack walker. r=froydnj (7371d9a508)
- missing bit of Bug 1216681 (fdf69e362f)
- Bug 1193593 - Test fingerprinting resistance for media queries in picture elements. r=heycam (6155b73c26)
- Bug 1232829 - Detach obsolete DocumentTimeline from refresh driver when the document is reset; r=smaug (564680e2a0)
- Bug 1075457, part 1 - Implement rendering for |clip-path:polygon()|. r=mstange, r=jwatt (76056caacd)
- Bug 1075457, part 2 - Implement circle() and ellipse() for the |clip-path| property. r=mstange, r=jwatt (4b8b39c682)
- Bug 1094571 - add unicode-range load tests. r=heycam (3358555411)
- Bug 1216695 - Remove the Request.context specific bits from fetch-request-resources.https.html; r=bkelly (2315e50b97)
- Bug 1193133 - Disable broken service worker wpt tests. r=bkelly (8f0205d5e7)
- Bug 1199831: Fix a bunch of mixed-content violations in imported ServiceWorker WPTs. r=jdm (33f261ce91)
- bit of Bug 603201 (325170577f)
- Bug 1184798 - same origin, cors and no-cors load tests. r=bkelly (f8549dd0bb)
- Bug 1210581: Test controlled worker loads (XHR, fetch, importScripts). r=ehsan (41a436df47)
- Bug 1215196 - Fix web-platform-tests iframe scripts to avoid pulling in testharness.js in them; r=bkelly (a2edb0784c)
- Bug 1242798 - Don't OSR into Ion on debuggee frames. (r=jandem) (21e17bdd9d)
- Bug 1238658 - Allow setElem-accessor optimizations only for native baseHolder objects; r=efaust (12c9766a53)
- Bug 1144630 - Follup: Fix review nit. (rs=evilpie) (67b5cc2c7f)
- Bug 1182866 - Fix Baseline GETNAME stubs to check for uninitialized lexicals. (r=jandem) (dd47d2025a)
- Bug 1189536 - Make fetch-request-xhr.https.html pass; r=bkelly (ce177226bf)
- Bug 1188822 - Make service-workers/service-worker/fetch-request-resources.https.html pass. r=bkelly (3a5f3a6660)
2023-11-14 15:08:43 +08:00

2817 lines
74 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebSocket.h"
#include "mozilla/dom/WebSocketBinding.h"
#include "mozilla/net/WebSocketChannel.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/net/WebSocketChannel.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "nsIScriptGlobalObject.h"
#include "nsIDOMWindow.h"
#include "nsIDocument.h"
#include "nsXPCOM.h"
#include "nsIXPConnect.h"
#include "nsContentUtils.h"
#include "nsError.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIURL.h"
#include "nsIUnicodeEncoder.h"
#include "nsThreadUtils.h"
#include "nsIPromptFactory.h"
#include "nsIWindowWatcher.h"
#include "nsIPrompt.h"
#include "nsIStringBundle.h"
#include "nsIConsoleService.h"
#include "mozilla/dom/CloseEvent.h"
#include "mozilla/net/WebSocketEventService.h"
#include "nsICryptoHash.h"
#include "nsJSUtils.h"
#include "nsIScriptError.h"
#include "nsNetUtil.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsILoadGroup.h"
#include "mozilla/Preferences.h"
#include "xpcpublic.h"
#include "nsContentPolicyUtils.h"
#include "nsWrapperCacheInlines.h"
#include "nsIObserverService.h"
#include "nsIEventTarget.h"
#include "nsIInterfaceRequestor.h"
#include "nsIObserver.h"
#include "nsIRequest.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIWebSocketChannel.h"
#include "nsIWebSocketListener.h"
#include "nsProxyRelease.h"
#include "nsWeakReference.h"
using namespace mozilla::net;
using namespace mozilla::dom::workers;
namespace mozilla {
namespace dom {
class WebSocketImpl final : public nsIInterfaceRequestor
, public nsIWebSocketListener
, public nsIObserver
, public nsSupportsWeakReference
, public nsIRequest
, public nsIEventTarget
{
public:
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSIWEBSOCKETLISTENER
NS_DECL_NSIOBSERVER
NS_DECL_NSIREQUEST
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIEVENTTARGET
// missing from NS_DECL_NSIEVENTTARGET because MSVC
nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) {
return Dispatch(nsCOMPtr<nsIRunnable>(aEvent).forget(), aFlags);
}
explicit WebSocketImpl(WebSocket* aWebSocket)
: mWebSocket(aWebSocket)
, mSecure(false)
, mOnCloseScheduled(false)
, mFailed(false)
, mDisconnectingOrDisconnected(false)
, mCloseEventWasClean(false)
, mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL)
, mScriptLine(0)
, mScriptColumn(0)
, mInnerWindowID(0)
, mWorkerPrivate(nullptr)
#ifdef DEBUG
, mHasFeatureRegistered(false)
#endif
, mIsMainThread(true)
, mMutex("WebSocketImpl::mMutex")
, mWorkerShuttingDown(false)
{
if (!NS_IsMainThread()) {
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(mWorkerPrivate);
mIsMainThread = false;
}
}
void AssertIsOnTargetThread() const
{
MOZ_ASSERT(IsTargetThread());
}
bool IsTargetThread() const;
void Init(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile,
uint32_t aScriptLine,
uint32_t aScriptColumn,
ErrorResult& aRv,
bool* aConnectionFailed);
void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
ErrorResult& aRv);
nsresult ParseURL(const nsAString& aURL);
nsresult InitializeConnection(nsIPrincipal* aPrincipal);
// These methods when called can release the WebSocket object
void FailConnection(uint16_t reasonCode,
const nsACString& aReasonString = EmptyCString());
nsresult CloseConnection(uint16_t reasonCode,
const nsACString& aReasonString = EmptyCString());
void Disconnect();
void DisconnectInternal();
nsresult ConsoleError();
void PrintErrorOnConsole(const char* aBundleURI,
const char16_t* aError,
const char16_t** aFormatStrings,
uint32_t aFormatStringsLen);
nsresult DoOnMessageAvailable(const nsACString& aMsg,
bool isBinary);
// ConnectionCloseEvents: 'error' event if needed, then 'close' event.
// - These must not be dispatched while we are still within an incoming call
// from JS (ex: close()). Set 'sync' to false in that case to dispatch in a
// separate new event.
nsresult ScheduleConnectionCloseEvents(nsISupports* aContext,
nsresult aStatusCode,
bool sync);
// 2nd half of ScheduleConnectionCloseEvents, sometimes run in its own event.
void DispatchConnectionCloseEvents();
// Dispatch a runnable to the right thread.
nsresult DispatchRunnable(nsIRunnable* aRunnable);
nsresult UpdateURI();
void AddRefObject();
void ReleaseObject();
bool RegisterFeature();
void UnregisterFeature();
nsresult CancelInternal();
RefPtr<WebSocket> mWebSocket;
nsCOMPtr<nsIWebSocketChannel> mChannel;
bool mSecure; // if true it is using SSL and the wss scheme,
// otherwise it is using the ws scheme with no SSL
bool mOnCloseScheduled;
bool mFailed;
bool mDisconnectingOrDisconnected;
// Set attributes of DOM 'onclose' message
bool mCloseEventWasClean;
nsString mCloseEventReason;
uint16_t mCloseEventCode;
nsCString mAsciiHost; // hostname
uint32_t mPort;
nsCString mResource; // [filepath[?query]]
nsString mUTF16Origin;
nsCString mURI;
nsCString mRequestedProtocolList;
nsWeakPtr mOriginDocument;
// Web Socket owner information:
// - the script file name, UTF8 encoded.
// - source code line number and column number where the Web Socket object
// was constructed.
// - the ID of the inner window where the script lives. Note that this may not
// be the same as the Web Socket owner window.
// These attributes are used for error reporting.
nsCString mScriptFile;
uint32_t mScriptLine;
uint32_t mScriptColumn;
uint64_t mInnerWindowID;
WorkerPrivate* mWorkerPrivate;
nsAutoPtr<WorkerFeature> mWorkerFeature;
#ifdef DEBUG
// This is protected by mutex.
bool mHasFeatureRegistered;
bool HasFeatureRegistered()
{
MOZ_ASSERT(mWebSocket);
MutexAutoLock lock(mWebSocket->mMutex);
return mHasFeatureRegistered;
}
void SetHasFeatureRegistered(bool aValue)
{
MOZ_ASSERT(mWebSocket);
MutexAutoLock lock(mWebSocket->mMutex);
mHasFeatureRegistered = aValue;
}
#endif
nsWeakPtr mWeakLoadGroup;
bool mIsMainThread;
// This mutex protects mWorkerShuttingDown.
mozilla::Mutex mMutex;
bool mWorkerShuttingDown;
RefPtr<WebSocketEventService> mService;
private:
~WebSocketImpl()
{
// If we threw during Init we never called disconnect
if (!mDisconnectingOrDisconnected) {
Disconnect();
}
}
};
NS_IMPL_ISUPPORTS(WebSocketImpl,
nsIInterfaceRequestor,
nsIWebSocketListener,
nsIObserver,
nsISupportsWeakReference,
nsIRequest,
nsIEventTarget)
class CallDispatchConnectionCloseEvents final : public nsCancelableRunnable
{
public:
explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl)
: mWebSocketImpl(aWebSocketImpl)
{
aWebSocketImpl->AssertIsOnTargetThread();
}
NS_IMETHOD Run()
{
mWebSocketImpl->AssertIsOnTargetThread();
mWebSocketImpl->DispatchConnectionCloseEvents();
return NS_OK;
}
private:
RefPtr<WebSocketImpl> mWebSocketImpl;
};
//-----------------------------------------------------------------------------
// WebSocketImpl
//-----------------------------------------------------------------------------
namespace {
class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable
{
public:
PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl,
const char* aBundleURI,
const char16_t* aError,
const char16_t** aFormatStrings,
uint32_t aFormatStringsLen)
: WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
, mImpl(aImpl)
, mBundleURI(aBundleURI)
, mError(aError)
, mFormatStrings(aFormatStrings)
, mFormatStringsLen(aFormatStringsLen)
{ }
bool MainThreadRun() override
{
mImpl->PrintErrorOnConsole(mBundleURI, mError, mFormatStrings,
mFormatStringsLen);
return true;
}
private:
// Raw pointer because this runnable is sync.
WebSocketImpl* mImpl;
const char* mBundleURI;
const char16_t* mError;
const char16_t** mFormatStrings;
uint32_t mFormatStringsLen;
};
} // namespace
void
WebSocketImpl::PrintErrorOnConsole(const char *aBundleURI,
const char16_t *aError,
const char16_t **aFormatStrings,
uint32_t aFormatStringsLen)
{
// This method must run on the main thread.
if (!NS_IsMainThread()) {
MOZ_ASSERT(mWorkerPrivate);
RefPtr<PrintErrorOnConsoleRunnable> runnable =
new PrintErrorOnConsoleRunnable(this, aBundleURI, aError, aFormatStrings,
aFormatStringsLen);
ErrorResult rv;
runnable->Dispatch(rv);
// XXXbz this seems totally broken. We should be propagating this out, but
// none of our callers really propagate anything usefully. Come to think of
// it, why is this a syncrunnable anyway? Can't this be a fire-and-forget
// runnable??
rv.SuppressException();
return;
}
nsresult rv;
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS_VOID(rv);
nsCOMPtr<nsIStringBundle> strBundle;
rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
NS_ENSURE_SUCCESS_VOID(rv);
nsCOMPtr<nsIConsoleService> console(
do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS_VOID(rv);
nsCOMPtr<nsIScriptError> errorObject(
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
NS_ENSURE_SUCCESS_VOID(rv);
// Localize the error message
nsXPIDLString message;
if (aFormatStrings) {
rv = strBundle->FormatStringFromName(aError, aFormatStrings,
aFormatStringsLen,
getter_Copies(message));
} else {
rv = strBundle->GetStringFromName(aError, getter_Copies(message));
}
NS_ENSURE_SUCCESS_VOID(rv);
if (mInnerWindowID) {
rv = errorObject->InitWithWindowID(message,
NS_ConvertUTF8toUTF16(mScriptFile),
EmptyString(), mScriptLine,
mScriptColumn,
nsIScriptError::errorFlag, "Web Socket",
mInnerWindowID);
} else {
rv = errorObject->Init(message,
NS_ConvertUTF8toUTF16(mScriptFile),
EmptyString(), mScriptLine, mScriptColumn,
nsIScriptError::errorFlag, "Web Socket");
}
NS_ENSURE_SUCCESS_VOID(rv);
// print the error message directly to the JS console
rv = console->LogMessage(errorObject);
NS_ENSURE_SUCCESS_VOID(rv);
}
namespace {
class CancelWebSocketRunnable final : public nsRunnable
{
public:
CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode,
const nsACString& aReasonString)
: mChannel(aChannel)
, mReasonCode(aReasonCode)
, mReasonString(aReasonString)
{}
NS_IMETHOD Run() override
{
mChannel->Close(mReasonCode, mReasonString);
return NS_OK;
}
private:
nsCOMPtr<nsIWebSocketChannel> mChannel;
uint16_t mReasonCode;
nsCString mReasonString;
};
class MOZ_STACK_CLASS MaybeDisconnect
{
public:
explicit MaybeDisconnect(WebSocketImpl* aImpl)
: mImpl(aImpl)
{
}
~MaybeDisconnect()
{
bool toDisconnect = false;
{
MutexAutoLock lock(mImpl->mMutex);
toDisconnect = mImpl->mWorkerShuttingDown;
}
if (toDisconnect) {
mImpl->Disconnect();
}
}
private:
WebSocketImpl* mImpl;
};
class CloseConnectionRunnable final : public nsRunnable
{
public:
CloseConnectionRunnable(WebSocketImpl* aImpl,
uint16_t aReasonCode,
const nsACString& aReasonString)
: mImpl(aImpl)
, mReasonCode(aReasonCode)
, mReasonString(aReasonString)
{}
NS_IMETHOD Run() override
{
return mImpl->CloseConnection(mReasonCode, mReasonString);
}
private:
RefPtr<WebSocketImpl> mImpl;
uint16_t mReasonCode;
const nsCString mReasonString;
};
} // namespace
nsresult
WebSocketImpl::CloseConnection(uint16_t aReasonCode,
const nsACString& aReasonString)
{
if (!IsTargetThread()) {
RefPtr<nsRunnable> runnable =
new CloseConnectionRunnable(this, aReasonCode, aReasonString);
return Dispatch(runnable, NS_DISPATCH_NORMAL);
}
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
// If this method is called because the worker is going away, we will not
// receive the OnStop() method and we have to disconnect the WebSocket and
// release the WorkerFeature.
MaybeDisconnect md(this);
uint16_t readyState = mWebSocket->ReadyState();
if (readyState == WebSocket::CLOSING ||
readyState == WebSocket::CLOSED) {
return NS_OK;
}
// The common case...
if (mChannel) {
mWebSocket->SetReadyState(WebSocket::CLOSING);
// The channel has to be closed on the main-thread.
if (NS_IsMainThread()) {
return mChannel->Close(aReasonCode, aReasonString);
}
RefPtr<CancelWebSocketRunnable> runnable =
new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString);
return NS_DispatchToMainThread(runnable);
}
// No channel, but not disconnected: canceled or failed early
MOZ_ASSERT(readyState == WebSocket::CONNECTING,
"Should only get here for early websocket cancel/error");
// Server won't be sending us a close code, so use what's passed in here.
mCloseEventCode = aReasonCode;
CopyUTF8toUTF16(aReasonString, mCloseEventReason);
mWebSocket->SetReadyState(WebSocket::CLOSING);
// Can be called from Cancel() or Init() codepaths, so need to dispatch
// onerror/onclose asynchronously
ScheduleConnectionCloseEvents(
nullptr,
(aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL ||
aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) ?
NS_OK : NS_ERROR_FAILURE,
false);
return NS_OK;
}
nsresult
WebSocketImpl::ConsoleError()
{
AssertIsOnTargetThread();
{
MutexAutoLock lock(mMutex);
if (mWorkerShuttingDown) {
// Too late to report anything, bail out.
return NS_OK;
}
}
NS_ConvertUTF8toUTF16 specUTF16(mURI);
const char16_t* formatStrings[] = { specUTF16.get() };
if (mWebSocket->ReadyState() < WebSocket::OPEN) {
PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
MOZ_UTF16("connectionFailure"),
formatStrings, ArrayLength(formatStrings));
} else {
PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
MOZ_UTF16("netInterrupt"),
formatStrings, ArrayLength(formatStrings));
}
/// todo some specific errors - like for message too large
return NS_OK;
}
void
WebSocketImpl::FailConnection(uint16_t aReasonCode,
const nsACString& aReasonString)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return;
}
ConsoleError();
mFailed = true;
CloseConnection(aReasonCode, aReasonString);
}
namespace {
class DisconnectInternalRunnable final : public WorkerMainThreadRunnable
{
public:
explicit DisconnectInternalRunnable(WebSocketImpl* aImpl)
: WorkerMainThreadRunnable(aImpl->mWorkerPrivate)
, mImpl(aImpl)
{ }
bool MainThreadRun() override
{
mImpl->DisconnectInternal();
return true;
}
private:
// A raw pointer because this runnable is sync.
WebSocketImpl* mImpl;
};
} // namespace
void
WebSocketImpl::Disconnect()
{
if (mDisconnectingOrDisconnected) {
return;
}
AssertIsOnTargetThread();
// Disconnect can be called from some control event (such as Notify() of
// WorkerFeature). This will be schedulated before any other sync/async
// runnable. In order to prevent some double Disconnect() calls, we use this
// boolean.
mDisconnectingOrDisconnected = true;
// DisconnectInternal touches observers and nsILoadGroup and it must run on
// the main thread.
if (NS_IsMainThread()) {
DisconnectInternal();
} else {
RefPtr<DisconnectInternalRunnable> runnable =
new DisconnectInternalRunnable(this);
ErrorResult rv;
runnable->Dispatch(rv);
// XXXbz this seems totally broken. We should be propagating this out, but
// where to, exactly?
rv.SuppressException();
}
// DontKeepAliveAnyMore() can release the object. So hold a reference to this
// until the end of the method.
RefPtr<WebSocketImpl> kungfuDeathGrip = this;
NS_ReleaseOnMainThread(mChannel.forget());
NS_ReleaseOnMainThread(mService.forget());
mWebSocket->DontKeepAliveAnyMore();
mWebSocket->mImpl = nullptr;
if (mWorkerPrivate && mWorkerFeature) {
UnregisterFeature();
}
// We want to release the WebSocket in the correct thread.
mWebSocket = nullptr;
}
void
WebSocketImpl::DisconnectInternal()
{
AssertIsOnMainThread();
nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
if (loadGroup) {
loadGroup->RemoveRequest(this, nullptr, NS_OK);
// mWeakLoadGroup has to be release on main-thread because WeakReferences
// are not thread-safe.
mWeakLoadGroup = nullptr;
}
if (!mWorkerPrivate) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
}
}
}
//-----------------------------------------------------------------------------
// WebSocketImpl::nsIWebSocketListener methods:
//-----------------------------------------------------------------------------
nsresult
WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, bool isBinary)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState();
if (readyState == WebSocket::CLOSED) {
NS_ERROR("Received message after CLOSED");
return NS_ERROR_UNEXPECTED;
}
if (readyState == WebSocket::OPEN) {
// Dispatch New Message
nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the message event");
}
return NS_OK;
}
// CLOSING should be the only other state where it's possible to get msgs
// from channel: Spec says to drop them.
MOZ_ASSERT(readyState == WebSocket::CLOSING,
"Received message while CONNECTING or CLOSED");
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::OnMessageAvailable(nsISupports* aContext,
const nsACString& aMsg)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
return DoOnMessageAvailable(aMsg, false);
}
NS_IMETHODIMP
WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext,
const nsACString& aMsg)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
return DoOnMessageAvailable(aMsg, true);
}
NS_IMETHODIMP
WebSocketImpl::OnStart(nsISupports* aContext)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState();
// This is the only function that sets OPEN, and should be called only once
MOZ_ASSERT(readyState != WebSocket::OPEN,
"readyState already OPEN! OnStart called twice?");
// Nothing to do if we've already closed/closing
if (readyState != WebSocket::CONNECTING) {
return NS_OK;
}
// Attempt to kill "ghost" websocket: but usually too early for check to fail
nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
return rv;
}
if (!mRequestedProtocolList.IsEmpty()) {
mChannel->GetProtocol(mWebSocket->mEstablishedProtocol);
}
mChannel->GetExtensions(mWebSocket->mEstablishedExtensions);
UpdateURI();
mWebSocket->SetReadyState(WebSocket::OPEN);
mService->WebSocketOpened(mChannel->Serial(),mInnerWindowID,
mWebSocket->mEffectiveURL,
mWebSocket->mEstablishedProtocol,
mWebSocket->mEstablishedExtensions);
// Let's keep the object alive because the webSocket can be CCed in the
// onopen callback.
RefPtr<WebSocket> webSocket = mWebSocket;
// Call 'onopen'
rv = webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the open event");
}
webSocket->UpdateMustKeepAlive();
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
// We can be CONNECTING here if connection failed.
// We can be OPEN if we have encountered a fatal protocol error
// We can be CLOSING if close() was called and/or server initiated close.
MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED,
"Shouldn't already be CLOSED when OnStop called");
// called by network stack, not JS, so can dispatch JS events synchronously
return ScheduleConnectionCloseEvents(aContext, aStatusCode, true);
}
nsresult
WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext,
nsresult aStatusCode,
bool sync)
{
AssertIsOnTargetThread();
// no-op if some other code has already initiated close event
if (!mOnCloseScheduled) {
mCloseEventWasClean = NS_SUCCEEDED(aStatusCode);
if (aStatusCode == NS_BASE_STREAM_CLOSED) {
// don't generate an error event just because of an unclean close
aStatusCode = NS_OK;
}
if (NS_FAILED(aStatusCode)) {
ConsoleError();
mFailed = true;
}
mOnCloseScheduled = true;
if (sync) {
DispatchConnectionCloseEvents();
} else {
NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this));
}
}
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::OnAcknowledge(nsISupports *aContext, uint32_t aSize)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
if (aSize > mWebSocket->mOutgoingBufferedAmount) {
return NS_ERROR_UNEXPECTED;
}
mWebSocket->mOutgoingBufferedAmount -= aSize;
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::OnServerClose(nsISupports *aContext, uint16_t aCode,
const nsACString &aReason)
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
int16_t readyState = mWebSocket->ReadyState();
MOZ_ASSERT(readyState != WebSocket::CONNECTING,
"Received server close before connected?");
MOZ_ASSERT(readyState != WebSocket::CLOSED,
"Received server close after already closed!");
// store code/string for onclose DOM event
mCloseEventCode = aCode;
CopyUTF8toUTF16(aReason, mCloseEventReason);
if (readyState == WebSocket::OPEN) {
// Server initiating close.
// RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint
// typically echos the status code it received".
// But never send certain codes, per section 7.4.1
if (aCode == 1005 || aCode == 1006 || aCode == 1015) {
CloseConnection(0, EmptyCString());
} else {
CloseConnection(aCode, aReason);
}
} else {
// We initiated close, and server has replied: OnStop does rest of the work.
MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state");
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// WebSocketImpl::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult)
{
AssertIsOnMainThread();
if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) {
return NS_ERROR_FAILURE;
}
if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
nsresult rv;
nsIScriptContext* sc = mWebSocket->GetContextForEventHandlers(&rv);
nsCOMPtr<nsIDocument> doc =
nsContentUtils::GetDocumentFromScriptContext(sc);
if (!doc) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIPromptFactory> wwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsPIDOMWindow> outerWindow = doc->GetWindow();
return wwatch->GetPrompt(outerWindow, aIID, aResult);
}
return QueryInterface(aIID, aResult);
}
////////////////////////////////////////////////////////////////////////////////
// WebSocket
////////////////////////////////////////////////////////////////////////////////
WebSocket::WebSocket(nsPIDOMWindow* aOwnerWindow)
: DOMEventTargetHelper(aOwnerWindow)
, mIsMainThread(true)
, mKeepingAlive(false)
, mCheckMustKeepAlive(true)
, mOutgoingBufferedAmount(0)
, mBinaryType(dom::BinaryType::Blob)
, mMutex("WebSocket::mMutex")
, mReadyState(CONNECTING)
{
mImpl = new WebSocketImpl(this);
mIsMainThread = mImpl->mIsMainThread;
}
WebSocket::~WebSocket()
{
}
JSObject*
WebSocket::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
{
return WebSocketBinding::Wrap(cx, this, aGivenProto);
}
//---------------------------------------------------------------------------
// WebIDL
//---------------------------------------------------------------------------
// Constructor:
already_AddRefed<WebSocket>
WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
ErrorResult& aRv)
{
Sequence<nsString> protocols;
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
}
already_AddRefed<WebSocket>
WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
const nsAString& aProtocol,
ErrorResult& aRv)
{
Sequence<nsString> protocols;
if (!protocols.AppendElement(aProtocol, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return nullptr;
}
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
}
namespace {
// This class is used to clear any exception.
class MOZ_STACK_CLASS ClearException
{
public:
explicit ClearException(JSContext* aCx)
: mCx(aCx)
{
}
~ClearException()
{
JS_ClearPendingException(mCx);
}
private:
JSContext* mCx;
};
class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable
{
public:
WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerMainThreadRunnable(aWorkerPrivate)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
}
bool MainThreadRun() override
{
AssertIsOnMainThread();
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindow* window = wp->GetWindow();
if (window) {
return InitWithWindow(window);
}
return InitWindowless(wp);
}
protected:
virtual bool InitWithWindow(nsPIDOMWindow* aWindow) = 0;
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0;
};
class InitRunnable final : public WebSocketMainThreadRunnable
{
public:
InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile, uint32_t aScriptLine,
uint32_t aScriptColumn,
ErrorResult& aRv, bool* aConnectionFailed)
: WebSocketMainThreadRunnable(aImpl->mWorkerPrivate)
, mImpl(aImpl)
, mURL(aURL)
, mProtocolArray(aProtocolArray)
, mScriptFile(aScriptFile)
, mScriptLine(aScriptLine)
, mScriptColumn(aScriptColumn)
, mRv(aRv)
, mConnectionFailed(aConnectionFailed)
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
}
protected:
virtual bool InitWithWindow(nsPIDOMWindow* aWindow) override
{
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(aWindow))) {
mRv.Throw(NS_ERROR_FAILURE);
return true;
}
ClearException ce(jsapi.cx());
nsIDocument* doc = aWindow->GetExtantDoc();
if (!doc) {
mRv.Throw(NS_ERROR_FAILURE);
return true;
}
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
if (!principal) {
mRv.Throw(NS_ERROR_FAILURE);
return true;
}
mImpl->Init(jsapi.cx(), principal, mURL, mProtocolArray, mScriptFile,
mScriptLine, mScriptColumn, mRv, mConnectionFailed);
return true;
}
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mURL,
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn, mRv,
mConnectionFailed);
return true;
}
// Raw pointer. This worker runs synchronously.
WebSocketImpl* mImpl;
const nsAString& mURL;
nsTArray<nsString>& mProtocolArray;
nsCString mScriptFile;
uint32_t mScriptLine;
uint32_t mScriptColumn;
ErrorResult& mRv;
bool* mConnectionFailed;
};
class AsyncOpenRunnable final : public WebSocketMainThreadRunnable
{
public:
AsyncOpenRunnable(WebSocketImpl* aImpl, ErrorResult& aRv)
: WebSocketMainThreadRunnable(aImpl->mWorkerPrivate)
, mImpl(aImpl)
, mRv(aRv)
{
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
}
protected:
virtual bool InitWithWindow(nsPIDOMWindow* aWindow) override
{
AssertIsOnMainThread();
MOZ_ASSERT(aWindow);
nsIDocument* doc = aWindow->GetExtantDoc();
if (!doc) {
mRv.Throw(NS_ERROR_FAILURE);
return true;
}
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
if (!principal) {
mRv.Throw(NS_ERROR_FAILURE);
return true;
}
if (aWindow->IsOuterWindow()) {
aWindow = aWindow->GetCurrentInnerWindow();
}
MOZ_ASSERT(aWindow);
uint64_t windowID = 0;
nsCOMPtr<nsPIDOMWindow> topWindow = aWindow->GetScriptableTop();
if (topWindow) {
topWindow = topWindow->GetCurrentInnerWindow();
}
if (topWindow) {
windowID = topWindow->WindowID();
}
mImpl->AsyncOpen(principal, windowID, mRv);
return true;
}
virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv);
return true;
}
private:
// Raw pointer. This worker runs synchronously.
WebSocketImpl* mImpl;
ErrorResult& mRv;
};
} // namespace
already_AddRefed<WebSocket>
WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
const Sequence<nsString>& aProtocols,
ErrorResult& aRv)
{
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsPIDOMWindow> ownerWindow;
if (NS_IsMainThread()) {
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
do_QueryInterface(aGlobal.GetAsSupports());
if (!scriptPrincipal) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
principal = scriptPrincipal->GetPrincipal();
if (!principal) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsCOMPtr<nsIScriptGlobalObject> sgo =
do_QueryInterface(aGlobal.GetAsSupports());
if (!sgo) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
if (!ownerWindow) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
}
MOZ_ASSERT_IF(ownerWindow, ownerWindow->IsInnerWindow());
nsTArray<nsString> protocolArray;
for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
const nsString& protocolElement = aProtocols[index];
if (protocolElement.IsEmpty()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
if (protocolArray.Contains(protocolElement)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
if (protocolElement.FindChar(',') != -1) /* interferes w/list */ {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return nullptr;
}
protocolArray.AppendElement(protocolElement);
}
RefPtr<WebSocket> webSocket = new WebSocket(ownerWindow);
RefPtr<WebSocketImpl> kungfuDeathGrip = webSocket->mImpl;
bool connectionFailed = true;
if (NS_IsMainThread()) {
webSocket->mImpl->Init(aGlobal.Context(), principal, aUrl, protocolArray,
EmptyCString(), 0, 0, aRv, &connectionFailed);
} else {
// In workers we have to keep the worker alive using a feature in order to
// dispatch messages correctly.
if (!webSocket->mImpl->RegisterFeature()) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
unsigned lineno, column;
JS::UniqueChars file;
if (!JS::DescribeScriptedCaller(aGlobal.Context(), &file, &lineno,
&column)) {
NS_WARNING("Failed to get line number and filename in workers.");
}
RefPtr<InitRunnable> runnable =
new InitRunnable(webSocket->mImpl, aUrl, protocolArray,
nsAutoCString(file.get()), lineno, column, aRv,
&connectionFailed);
runnable->Dispatch(aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// It can be that we have been already disconnected because the WebSocket is
// gone away while we where initializing the webSocket.
if (!webSocket->mImpl) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// We don't return an error if the connection just failed. Instead we dispatch
// an event.
if (connectionFailed) {
webSocket->mImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
}
// If we don't have a channel, the connection is failed and onerror() will be
// called asynchrounsly.
if (!webSocket->mImpl->mChannel) {
return webSocket.forget();
}
class MOZ_STACK_CLASS ClearWebSocket
{
public:
explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl)
: mWebSocketImpl(aWebSocketImpl)
, mDone(false)
{
}
void Done()
{
mDone = true;
}
~ClearWebSocket()
{
if (!mDone) {
mWebSocketImpl->mChannel = nullptr;
mWebSocketImpl->FailConnection(nsIWebSocketChannel::CLOSE_ABNORMAL);
}
}
WebSocketImpl* mWebSocketImpl;
bool mDone;
};
ClearWebSocket cws(webSocket->mImpl);
// This operation must be done on the correct thread. The rest must run on the
// main-thread.
aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (NS_IsMainThread()) {
MOZ_ASSERT(principal);
nsPIDOMWindow* outerWindow = ownerWindow->GetOuterWindow();
uint64_t windowID = 0;
nsCOMPtr<nsPIDOMWindow> topWindow = outerWindow->GetScriptableTop();
if (topWindow) {
topWindow = topWindow->GetCurrentInnerWindow();
}
if (topWindow) {
windowID = topWindow->WindowID();
}
webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
} else {
RefPtr<AsyncOpenRunnable> runnable =
new AsyncOpenRunnable(webSocket->mImpl, aRv);
runnable->Dispatch(aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// It can be that we have been already disconnected because the WebSocket is
// gone away while we where initializing the webSocket.
if (!webSocket->mImpl) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// Let's inform devtools about this new active WebSocket.
webSocket->mImpl->mService->WebSocketCreated(webSocket->mImpl->mChannel->Serial(),
webSocket->mImpl->mInnerWindowID,
webSocket->mURI,
webSocket->mImpl->mRequestedProtocolList);
cws.Done();
return webSocket.forget();
}
NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket)
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(WebSocket)
bool isBlack = tmp->IsBlack();
if (isBlack || tmp->mKeepingAlive) {
if (tmp->mListenerManager) {
tmp->mListenerManager->MarkForCC();
}
if (!isBlack && tmp->PreservingWrapper()) {
// This marks the wrapper black.
tmp->GetWrapper();
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(WebSocket)
return tmp->IsBlack();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(WebSocket)
return tmp->IsBlack();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WebSocket,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket,
DOMEventTargetHelper)
if (tmp->mImpl) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel)
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket,
DOMEventTargetHelper)
if (tmp->mImpl) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel)
tmp->mImpl->Disconnect();
MOZ_ASSERT(!tmp->mImpl);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WebSocket)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper)
void
WebSocket::DisconnectFromOwner()
{
AssertIsOnMainThread();
DOMEventTargetHelper::DisconnectFromOwner();
if (mImpl) {
mImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
DontKeepAliveAnyMore();
}
//-----------------------------------------------------------------------------
// WebSocketImpl:: initialization
//-----------------------------------------------------------------------------
void
WebSocketImpl::Init(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile,
uint32_t aScriptLine,
uint32_t aScriptColumn,
ErrorResult& aRv,
bool* aConnectionFailed)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
mService = WebSocketEventService::GetOrCreate();
// We need to keep the implementation alive in case the init disconnects it
// because of some error.
RefPtr<WebSocketImpl> kungfuDeathGrip = this;
// Attempt to kill "ghost" websocket: but usually too early for check to fail
aRv = mWebSocket->CheckInnerWindowCorrectness();
if (NS_WARN_IF(aRv.Failed())) {
return;
}
// Shut down websocket if window is frozen or destroyed (only needed for
// "ghost" websockets--see bug 696085)
if (!mWorkerPrivate) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
aRv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
if (mWorkerPrivate) {
mScriptFile = aScriptFile;
mScriptLine = aScriptLine;
mScriptColumn = aScriptColumn;
} else {
MOZ_ASSERT(aCx);
unsigned lineno, column;
JS::UniqueChars file;
if (JS::DescribeScriptedCaller(aCx, &file, &lineno, &column)) {
mScriptFile = file.get();
mScriptLine = lineno;
mScriptColumn = column;
}
}
// If we don't have aCx, we are window-less, so we don't have a
// inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
// DedicateWorkers created by JSM.
if (aCx) {
mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(aCx);
}
// parses the url
aRv = ParseURL(PromiseFlatString(aURL));
if (NS_WARN_IF(aRv.Failed())) {
return;
}
nsIScriptContext* sc = nullptr;
{
nsresult rv;
sc = mWebSocket->GetContextForEventHandlers(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
}
nsCOMPtr<nsIURI> uri;
{
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
// We crash here because we are sure that mURI is a valid URI, so either we
// are OOM'ing or something else bad is happening.
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_CRASH();
}
}
// Check content policy.
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsCOMPtr<nsIDocument> originDoc = nsContentUtils::GetDocumentFromScriptContext(sc);
mOriginDocument = do_GetWeakReference(originDoc);
aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
uri,
aPrincipal,
originDoc,
EmptyCString(),
nullptr,
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (NS_CP_REJECTED(shouldLoad)) {
// Disallowed by content policy.
aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
return;
}
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
// In such a case we have to upgrade ws: to wss: and also update mSecure
// to reflect that upgrade. Please note that we can not upgrade from ws:
// to wss: before performing content policy checks because CSP needs to
// send reports in case the scheme is about to be upgraded.
if (!mSecure && originDoc && originDoc->GetUpgradeInsecureRequests()) {
// let's use the old specification before the upgrade for logging
NS_ConvertUTF8toUTF16 reportSpec(mURI);
// upgrade the request from ws:// to wss:// and mark as secure
mURI.ReplaceSubstring("ws://", "wss://");
if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
return;
}
mSecure = true;
const char16_t* params[] = { reportSpec.get(), MOZ_UTF16("wss") };
CSP_LogLocalizedStr(MOZ_UTF16("upgradeInsecureRequest"),
params, ArrayLength(params),
EmptyString(), // aSourceFile
EmptyString(), // aScriptSample
0, // aLineNumber
0, // aColumnNumber
nsIScriptError::warningFlag, "CSP",
mInnerWindowID);
}
// Don't allow https:// to open ws://
if (!mSecure &&
!Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
false)) {
// Confirmed we are opening plain ws:// and want to prevent this from a
// secure context (e.g. https).
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIURI> originURI;
if (mWorkerPrivate) {
// For workers, retrieve the URI from the WorkerPrivate
principal = mWorkerPrivate->GetPrincipal();
} else {
// Check the principal's uri to determine if we were loaded from https.
nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal());
if (globalObject) {
principal = globalObject->PrincipalOrNull();
}
}
if (principal) {
principal->GetURI(getter_AddRefs(originURI));
}
if (originURI) {
bool originIsHttps = false;
aRv = originURI->SchemeIs("https", &originIsHttps);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (originIsHttps) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
}
}
// Assign the sub protocol list and scan it for illegal values
for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) {
for (uint32_t i = 0; i < aProtocolArray[index].Length(); ++i) {
if (aProtocolArray[index][i] < static_cast<char16_t>(0x0021) ||
aProtocolArray[index][i] > static_cast<char16_t>(0x007E)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
}
if (!mRequestedProtocolList.IsEmpty()) {
mRequestedProtocolList.AppendLiteral(", ");
}
AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList);
}
// the constructor should throw a SYNTAX_ERROR only if it fails to parse the
// url parameter, so don't throw if InitializeConnection fails, and call
// onerror/onclose asynchronously
if (NS_FAILED(InitializeConnection(aPrincipal))) {
*aConnectionFailed = true;
} else {
*aConnectionFailed = false;
}
}
void
WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
nsCString asciiOrigin;
aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
ToLowerCase(asciiOrigin);
nsCOMPtr<nsIURI> uri;
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
MOZ_ASSERT(!aRv.Failed());
aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mInnerWindowID = aInnerWindowID;
}
//-----------------------------------------------------------------------------
// WebSocketImpl methods:
//-----------------------------------------------------------------------------
class nsAutoCloseWS final
{
public:
explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl)
: mWebSocketImpl(aWebSocketImpl)
{}
~nsAutoCloseWS()
{
if (!mWebSocketImpl->mChannel) {
mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_INTERNAL_ERROR);
}
}
private:
RefPtr<WebSocketImpl> mWebSocketImpl;
};
nsresult
WebSocketImpl::InitializeConnection(nsIPrincipal* aPrincipal)
{
AssertIsOnMainThread();
MOZ_ASSERT(!mChannel, "mChannel should be null");
nsCOMPtr<nsIWebSocketChannel> wsChannel;
nsAutoCloseWS autoClose(this);
nsresult rv;
if (mSecure) {
wsChannel =
do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv);
} else {
wsChannel =
do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv);
}
NS_ENSURE_SUCCESS(rv, rv);
// add ourselves to the document's load group and
// provide the http stack the loadgroup info too
nsCOMPtr<nsILoadGroup> loadGroup;
rv = GetLoadGroup(getter_AddRefs(loadGroup));
if (loadGroup) {
rv = wsChannel->SetLoadGroup(loadGroup);
NS_ENSURE_SUCCESS(rv, rv);
rv = loadGroup->AddRequest(this, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
mWeakLoadGroup = do_GetWeakReference(loadGroup);
}
// manually adding loadinfo to the channel since it
// was not set during channel creation.
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mOriginDocument);
// mOriginDocument has to be release on main-thread because WeakReferences
// are not thread-safe.
mOriginDocument = nullptr;
wsChannel->InitLoadInfo(doc ? doc->AsDOMNode() : nullptr,
doc ? doc->NodePrincipal() : aPrincipal,
aPrincipal,
nsILoadInfo::SEC_NORMAL,
nsIContentPolicy::TYPE_WEBSOCKET);
if (!mRequestedProtocolList.IsEmpty()) {
rv = wsChannel->SetProtocol(mRequestedProtocolList);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel);
NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE);
rv = rr->RetargetDeliveryTo(this);
NS_ENSURE_SUCCESS(rv, rv);
mChannel = wsChannel;
return NS_OK;
}
void
WebSocketImpl::DispatchConnectionCloseEvents()
{
AssertIsOnTargetThread();
if (mDisconnectingOrDisconnected) {
return;
}
mWebSocket->SetReadyState(WebSocket::CLOSED);
// Let's keep the object alive because the webSocket can be CCed in the
// onerror or in the onclose callback.
RefPtr<WebSocket> webSocket = mWebSocket;
// Call 'onerror' if needed
if (mFailed) {
nsresult rv =
webSocket->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the error event");
}
}
nsresult rv = webSocket->CreateAndDispatchCloseEvent(mCloseEventWasClean,
mCloseEventCode,
mCloseEventReason);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the close event");
}
webSocket->UpdateMustKeepAlive();
Disconnect();
}
nsresult
WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName)
{
MOZ_ASSERT(mImpl);
AssertIsOnTargetThread();
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return NS_OK;
}
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
// it doesn't bubble, and it isn't cancelable
event->InitEvent(aName, false, false);
event->SetTrusted(true);
return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
}
nsresult
WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
bool aIsBinary)
{
MOZ_ASSERT(mImpl);
AssertIsOnTargetThread();
AutoJSAPI jsapi;
if (NS_IsMainThread()) {
if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
return NS_ERROR_FAILURE;
}
} else {
MOZ_ASSERT(!mIsMainThread);
MOZ_ASSERT(mImpl->mWorkerPrivate);
if (NS_WARN_IF(!jsapi.Init(mImpl->mWorkerPrivate->GlobalScope()))) {
return NS_ERROR_FAILURE;
}
}
return CreateAndDispatchMessageEvent(jsapi.cx(), aData, aIsBinary);
}
nsresult
WebSocket::CreateAndDispatchMessageEvent(JSContext* aCx,
const nsACString& aData,
bool aIsBinary)
{
MOZ_ASSERT(mImpl);
AssertIsOnTargetThread();
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return NS_OK;
}
uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
// Create appropriate JS object for message
JS::Rooted<JS::Value> jsData(aCx);
if (aIsBinary) {
if (mBinaryType == dom::BinaryType::Blob) {
messageType = nsIWebSocketEventListener::TYPE_BLOB;
nsresult rv = nsContentUtils::CreateBlobBuffer(aCx, GetOwner(), aData,
&jsData);
NS_ENSURE_SUCCESS(rv, rv);
} else if (mBinaryType == dom::BinaryType::Arraybuffer) {
messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER;
JS::Rooted<JSObject*> arrayBuf(aCx);
nsresult rv = nsContentUtils::CreateArrayBuffer(aCx, aData,
arrayBuf.address());
NS_ENSURE_SUCCESS(rv, rv);
jsData.setObject(*arrayBuf);
} else {
NS_RUNTIMEABORT("Unknown binary type!");
return NS_ERROR_UNEXPECTED;
}
} else {
// JS string
NS_ConvertUTF8toUTF16 utf16Data(aData);
JSString* jsString;
jsString = JS_NewUCStringCopyN(aCx, utf16Data.get(), utf16Data.Length());
NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
jsData.setString(jsString);
}
mImpl->mService->WebSocketMessageAvailable(mImpl->mChannel->Serial(),
mImpl->mInnerWindowID,
aData, messageType);
// create an event that uses the MessageEvent interface,
// which does not bubble, is not cancelable, and has no default action
RefPtr<MessageEvent> event = NS_NewDOMMessageEvent(this, nullptr, nullptr);
rv = event->InitMessageEvent(NS_LITERAL_STRING("message"), false, false,
jsData, mImpl->mUTF16Origin, EmptyString(),
nullptr);
NS_ENSURE_SUCCESS(rv, rv);
event->SetTrusted(true);
return DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
nullptr);
}
nsresult
WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
uint16_t aCode,
const nsAString& aReason)
{
MOZ_ASSERT(mImpl);
AssertIsOnTargetThread();
if (mImpl->mChannel) {
mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
mImpl->mInnerWindowID,
aWasClean, aCode, aReason);
}
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return NS_OK;
}
CloseEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mWasClean = aWasClean;
init.mCode = aCode;
init.mReason = aReason;
RefPtr<CloseEvent> event =
CloseEvent::Constructor(this, NS_LITERAL_STRING("close"), init);
event->SetTrusted(true);
return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
}
nsresult
WebSocketImpl::ParseURL(const nsAString& aURL)
{
AssertIsOnMainThread();
NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
bool hasRef;
rv = parsedURL->GetHasRef(&hasRef);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef,
NS_ERROR_DOM_SYNTAX_ERR);
nsAutoCString scheme;
rv = parsedURL->GetScheme(scheme);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(),
NS_ERROR_DOM_SYNTAX_ERR);
nsAutoCString host;
rv = parsedURL->GetAsciiHost(host);
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
int32_t port;
rv = parsedURL->GetPort(&port);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
rv = NS_CheckPortSafety(port, scheme.get());
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
nsAutoCString filePath;
rv = parsedURL->GetFilePath(filePath);
if (filePath.IsEmpty()) {
filePath.Assign('/');
}
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
nsAutoCString query;
rv = parsedURL->GetQuery(query);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
if (scheme.LowerCaseEqualsLiteral("ws")) {
mSecure = false;
mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port;
} else if (scheme.LowerCaseEqualsLiteral("wss")) {
mSecure = true;
mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port;
} else {
return NS_ERROR_DOM_SYNTAX_ERR;
}
rv = nsContentUtils::GetUTFOrigin(parsedURL, mUTF16Origin);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
mAsciiHost = host;
ToLowerCase(mAsciiHost);
mResource = filePath;
if (!query.IsEmpty()) {
mResource.Append('?');
mResource.Append(query);
}
uint32_t length = mResource.Length();
uint32_t i;
for (i = 0; i < length; ++i) {
if (mResource[i] < static_cast<char16_t>(0x0021) ||
mResource[i] > static_cast<char16_t>(0x007E)) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
}
rv = parsedURL->GetSpec(mURI);
MOZ_ASSERT(NS_SUCCEEDED(rv));
CopyUTF8toUTF16(mURI, mWebSocket->mURI);
return NS_OK;
}
//-----------------------------------------------------------------------------
// Methods that keep alive the WebSocket object when:
// 1. the object has registered event listeners that can be triggered
// ("strong event listeners");
// 2. there are outgoing not sent messages.
//-----------------------------------------------------------------------------
void
WebSocket::UpdateMustKeepAlive()
{
// Here we could not have mImpl.
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
if (!mCheckMustKeepAlive || !mImpl) {
return;
}
bool shouldKeepAlive = false;
uint16_t readyState = ReadyState();
if (mListenerManager) {
switch (readyState)
{
case CONNECTING:
{
if (mListenerManager->HasListenersFor(nsGkAtoms::onopen) ||
mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
mListenerManager->HasListenersFor(nsGkAtoms::onclose)) {
shouldKeepAlive = true;
}
}
break;
case OPEN:
case CLOSING:
{
if (mListenerManager->HasListenersFor(nsGkAtoms::onmessage) ||
mListenerManager->HasListenersFor(nsGkAtoms::onerror) ||
mListenerManager->HasListenersFor(nsGkAtoms::onclose) ||
mOutgoingBufferedAmount != 0) {
shouldKeepAlive = true;
}
}
break;
case CLOSED:
{
shouldKeepAlive = false;
}
}
}
if (mKeepingAlive && !shouldKeepAlive) {
mKeepingAlive = false;
mImpl->ReleaseObject();
} else if (!mKeepingAlive && shouldKeepAlive) {
mKeepingAlive = true;
mImpl->AddRefObject();
}
}
void
WebSocket::DontKeepAliveAnyMore()
{
// Here we could not have mImpl.
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
if (mKeepingAlive) {
MOZ_ASSERT(mImpl);
mKeepingAlive = false;
mImpl->ReleaseObject();
}
mCheckMustKeepAlive = false;
}
namespace {
class WebSocketWorkerFeature final : public WorkerFeature
{
public:
explicit WebSocketWorkerFeature(WebSocketImpl* aWebSocketImpl)
: mWebSocketImpl(aWebSocketImpl)
{
}
bool Notify(JSContext* aCx, Status aStatus) override
{
MOZ_ASSERT(aStatus > workers::Running);
if (aStatus >= Canceling) {
{
MutexAutoLock lock(mWebSocketImpl->mMutex);
mWebSocketImpl->mWorkerShuttingDown = true;
}
mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY,
EmptyCString());
}
return true;
}
bool Suspend(JSContext* aCx) override
{
{
MutexAutoLock lock(mWebSocketImpl->mMutex);
mWebSocketImpl->mWorkerShuttingDown = true;
}
mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
return true;
}
private:
WebSocketImpl* mWebSocketImpl;
};
} // namespace
void
WebSocketImpl::AddRefObject()
{
AssertIsOnTargetThread();
AddRef();
}
void
WebSocketImpl::ReleaseObject()
{
AssertIsOnTargetThread();
Release();
}
bool
WebSocketImpl::RegisterFeature()
{
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(!mWorkerFeature);
mWorkerFeature = new WebSocketWorkerFeature(this);
JSContext* cx = GetCurrentThreadJSContext();
if (!mWorkerPrivate->AddFeature(cx, mWorkerFeature)) {
NS_WARNING("Failed to register a feature.");
mWorkerFeature = nullptr;
return false;
}
#ifdef DEBUG
SetHasFeatureRegistered(true);
#endif
return true;
}
void
WebSocketImpl::UnregisterFeature()
{
MOZ_ASSERT(mDisconnectingOrDisconnected);
MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate->AssertIsOnWorkerThread();
MOZ_ASSERT(mWorkerFeature);
JSContext* cx = GetCurrentThreadJSContext();
mWorkerPrivate->RemoveFeature(cx, mWorkerFeature);
mWorkerFeature = nullptr;
mWorkerPrivate = nullptr;
#ifdef DEBUG
SetHasFeatureRegistered(false);
#endif
}
nsresult
WebSocketImpl::UpdateURI()
{
AssertIsOnTargetThread();
// Check for Redirections
RefPtr<BaseWebSocketChannel> channel;
channel = static_cast<BaseWebSocketChannel*>(mChannel.get());
MOZ_ASSERT(channel);
channel->GetEffectiveURL(mWebSocket->mEffectiveURL);
mSecure = channel->IsEncrypted();
return NS_OK;
}
void
WebSocket::EventListenerAdded(nsIAtom* aType)
{
AssertIsOnMainThread();
UpdateMustKeepAlive();
}
void
WebSocket::EventListenerRemoved(nsIAtom* aType)
{
AssertIsOnMainThread();
UpdateMustKeepAlive();
}
//-----------------------------------------------------------------------------
// WebSocket - methods
//-----------------------------------------------------------------------------
// webIDL: readonly attribute unsigned short readyState;
uint16_t
WebSocket::ReadyState()
{
MutexAutoLock lock(mMutex);
return mReadyState;
}
void
WebSocket::SetReadyState(uint16_t aReadyState)
{
MutexAutoLock lock(mMutex);
mReadyState = aReadyState;
}
// webIDL: readonly attribute unsigned long bufferedAmount;
uint32_t
WebSocket::BufferedAmount() const
{
AssertIsOnTargetThread();
return mOutgoingBufferedAmount;
}
// webIDL: attribute BinaryType binaryType;
dom::BinaryType
WebSocket::BinaryType() const
{
AssertIsOnTargetThread();
return mBinaryType;
}
// webIDL: attribute BinaryType binaryType;
void
WebSocket::SetBinaryType(dom::BinaryType aData)
{
AssertIsOnTargetThread();
mBinaryType = aData;
}
// webIDL: readonly attribute DOMString url
void
WebSocket::GetUrl(nsAString& aURL)
{
AssertIsOnTargetThread();
if (mEffectiveURL.IsEmpty()) {
aURL = mURI;
} else {
aURL = mEffectiveURL;
}
}
// webIDL: readonly attribute DOMString extensions;
void
WebSocket::GetExtensions(nsAString& aExtensions)
{
AssertIsOnTargetThread();
CopyUTF8toUTF16(mEstablishedExtensions, aExtensions);
}
// webIDL: readonly attribute DOMString protocol;
void
WebSocket::GetProtocol(nsAString& aProtocol)
{
AssertIsOnTargetThread();
CopyUTF8toUTF16(mEstablishedProtocol, aProtocol);
}
// webIDL: void send(DOMString data);
void
WebSocket::Send(const nsAString& aData,
ErrorResult& aRv)
{
AssertIsOnTargetThread();
NS_ConvertUTF16toUTF8 msgString(aData);
Send(nullptr, msgString, msgString.Length(), false, aRv);
}
void
WebSocket::Send(Blob& aData, ErrorResult& aRv)
{
AssertIsOnTargetThread();
nsCOMPtr<nsIInputStream> msgStream;
aData.GetInternalStream(getter_AddRefs(msgStream), aRv);
if (NS_WARN_IF(aRv.Failed())){
return;
}
uint64_t msgLength = aData.GetSize(aRv);
if (NS_WARN_IF(aRv.Failed())){
return;
}
if (msgLength > UINT32_MAX) {
aRv.Throw(NS_ERROR_FILE_TOO_BIG);
return;
}
Send(msgStream, EmptyCString(), msgLength, true, aRv);
}
void
WebSocket::Send(const ArrayBuffer& aData,
ErrorResult& aRv)
{
AssertIsOnTargetThread();
aData.ComputeLengthAndData();
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString(data, len);
Send(nullptr, msgString, len, true, aRv);
}
void
WebSocket::Send(const ArrayBufferView& aData,
ErrorResult& aRv)
{
AssertIsOnTargetThread();
aData.ComputeLengthAndData();
static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
uint32_t len = aData.Length();
char* data = reinterpret_cast<char*>(aData.Data());
nsDependentCSubstring msgString(data, len);
Send(nullptr, msgString, len, true, aRv);
}
void
WebSocket::Send(nsIInputStream* aMsgStream,
const nsACString& aMsgString,
uint32_t aMsgLength,
bool aIsBinary,
ErrorResult& aRv)
{
AssertIsOnTargetThread();
int64_t readyState = ReadyState();
if (readyState == CONNECTING) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Always increment outgoing buffer len, even if closed
CheckedUint32 size = mOutgoingBufferedAmount;
size += aMsgLength;
if (!size.isValid()) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
mOutgoingBufferedAmount = size.value();
if (readyState == CLOSING ||
readyState == CLOSED) {
return;
}
// We must have mImpl when connected.
MOZ_ASSERT(mImpl);
MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send");
nsresult rv;
if (aMsgStream) {
rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength);
} else {
if (aIsBinary) {
rv = mImpl->mChannel->SendBinaryMsg(aMsgString);
} else {
rv = mImpl->mChannel->SendMsg(aMsgString);
}
}
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
UpdateMustKeepAlive();
}
// webIDL: void close(optional unsigned short code, optional DOMString reason):
void
WebSocket::Close(const Optional<uint16_t>& aCode,
const Optional<nsAString>& aReason,
ErrorResult& aRv)
{
AssertIsOnTargetThread();
// the reason code is optional, but if provided it must be in a specific range
uint16_t closeCode = 0;
if (aCode.WasPassed()) {
if (aCode.Value() != 1000 && (aCode.Value() < 3000 || aCode.Value() > 4999)) {
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
return;
}
closeCode = aCode.Value();
}
nsCString closeReason;
if (aReason.WasPassed()) {
CopyUTF16toUTF8(aReason.Value(), closeReason);
// The API requires the UTF-8 string to be 123 or less bytes
if (closeReason.Length() > 123) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
}
int64_t readyState = ReadyState();
if (readyState == CLOSING ||
readyState == CLOSED) {
return;
}
// If the webSocket is not closed we MUST have a mImpl.
MOZ_ASSERT(mImpl);
if (readyState == CONNECTING) {
mImpl->FailConnection(closeCode, closeReason);
return;
}
MOZ_ASSERT(readyState == OPEN);
mImpl->CloseConnection(closeCode, closeReason);
}
//-----------------------------------------------------------------------------
// WebSocketImpl::nsIObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebSocketImpl::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
AssertIsOnMainThread();
int64_t readyState = mWebSocket->ReadyState();
if ((readyState == WebSocket::CLOSING) ||
(readyState == WebSocket::CLOSED)) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aSubject);
if (!mWebSocket->GetOwner() || window != mWebSocket->GetOwner()) {
return NS_OK;
}
if ((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) ||
(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0))
{
CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// WebSocketImpl::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebSocketImpl::GetName(nsACString& aName)
{
AssertIsOnMainThread();
CopyUTF16toUTF8(mWebSocket->mURI, aName);
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::IsPending(bool* aValue)
{
AssertIsOnTargetThread();
int64_t readyState = mWebSocket->ReadyState();
*aValue = (readyState != WebSocket::CLOSED);
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::GetStatus(nsresult* aStatus)
{
AssertIsOnTargetThread();
*aStatus = NS_OK;
return NS_OK;
}
namespace {
class CancelRunnable final : public WorkerRunnable
{
public:
CancelRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mImpl(aImpl)
{
}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnWorkerThread();
aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
return !NS_FAILED(mImpl->CancelInternal());
}
void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
{
aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
}
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
// We don't call WorkerRunnable::PreDispatch because it would assert the
// wrong thing about which thread we're on.
AssertIsOnMainThread();
return true;
}
void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
// We don't call WorkerRunnable::PostDispatch because it would assert the
// wrong thing about which thread we're on.
AssertIsOnMainThread();
}
private:
RefPtr<WebSocketImpl> mImpl;
};
} // namespace
// Window closed, stop/reload button pressed, user navigated away from page, etc.
NS_IMETHODIMP
WebSocketImpl::Cancel(nsresult aStatus)
{
AssertIsOnMainThread();
if (!mIsMainThread) {
MOZ_ASSERT(mWorkerPrivate);
RefPtr<CancelRunnable> runnable =
new CancelRunnable(mWorkerPrivate, this);
if (!runnable->Dispatch(nullptr)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
return CancelInternal();
}
nsresult
WebSocketImpl::CancelInternal()
{
AssertIsOnTargetThread();
// If CancelInternal is called by a runnable, we may already be disconnected
// by the time it runs.
if (mDisconnectingOrDisconnected) {
return NS_OK;
}
int64_t readyState = mWebSocket->ReadyState();
if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
return NS_OK;
}
return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
}
NS_IMETHODIMP
WebSocketImpl::Suspend()
{
AssertIsOnMainThread();
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WebSocketImpl::Resume()
{
AssertIsOnMainThread();
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup)
{
AssertIsOnMainThread();
*aLoadGroup = nullptr;
if (mIsMainThread) {
nsresult rv;
nsIScriptContext* sc = mWebSocket->GetContextForEventHandlers(&rv);
nsCOMPtr<nsIDocument> doc =
nsContentUtils::GetDocumentFromScriptContext(sc);
if (doc) {
*aLoadGroup = doc->GetDocumentLoadGroup().take();
}
return NS_OK;
}
MOZ_ASSERT(mWorkerPrivate);
// Walk up to our containing page
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
wp = wp->GetParent();
}
nsPIDOMWindow* window = wp->GetWindow();
if (!window) {
return NS_OK;
}
nsIDocument* doc = window->GetExtantDoc();
if (doc) {
*aLoadGroup = doc->GetDocumentLoadGroup().take();
}
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
AssertIsOnMainThread();
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags)
{
AssertIsOnMainThread();
*aLoadFlags = nsIRequest::LOAD_BACKGROUND;
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags)
{
AssertIsOnMainThread();
// we won't change the load flags at all.
return NS_OK;
}
namespace {
class WorkerRunnableDispatcher final : public WorkerRunnable
{
RefPtr<WebSocketImpl> mWebSocketImpl;
public:
WorkerRunnableDispatcher(WebSocketImpl* aImpl, WorkerPrivate* aWorkerPrivate,
already_AddRefed<nsIRunnable>&& aEvent)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mWebSocketImpl(aImpl)
, mEvent(aEvent)
{
}
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
aWorkerPrivate->AssertIsOnWorkerThread();
aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
// No messages when disconnected.
if (mWebSocketImpl->mDisconnectingOrDisconnected) {
NS_WARNING("Dispatching a WebSocket event after the disconnection!");
return true;
}
return !NS_FAILED(mEvent->Run());
}
void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
{
aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
}
bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
// We don't call WorkerRunnable::PreDispatch because it would assert the
// wrong thing about which thread we're on. We're on whichever thread the
// channel implementation is running on (probably the main thread or socket
// transport thread).
return true;
}
void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
// We don't call WorkerRunnable::PreDispatch because it would assert the
// wrong thing about which thread we're on. We're on whichever thread the
// channel implementation is running on (probably the main thread or socket
// transport thread).
}
private:
nsCOMPtr<nsIRunnable> mEvent;
};
} // anonymous namespace
NS_IMETHODIMP
WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
{
nsCOMPtr<nsIRunnable> event(aEvent);
return Dispatch(event.forget(), aFlags);
}
NS_IMETHODIMP
WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags)
{
nsCOMPtr<nsIRunnable> event_ref(aEvent);
// If the target is the main-thread we can just dispatch the runnable.
if (mIsMainThread) {
return NS_DispatchToMainThread(event_ref.forget());
}
MutexAutoLock lock(mMutex);
if (mWorkerShuttingDown) {
return NS_OK;
}
MOZ_ASSERT(mWorkerPrivate);
#ifdef DEBUG
MOZ_ASSERT(HasFeatureRegistered());
#endif
// If the target is a worker, we have to use a custom WorkerRunnableDispatcher
// runnable.
RefPtr<WorkerRunnableDispatcher> event =
new WorkerRunnableDispatcher(this, mWorkerPrivate, event_ref.forget());
if (!event->Dispatch(nullptr)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
WebSocketImpl::IsOnCurrentThread(bool* aResult)
{
*aResult = IsTargetThread();
return NS_OK;
}
bool
WebSocketImpl::IsTargetThread() const
{
return NS_IsMainThread() == mIsMainThread;
}
void
WebSocket::AssertIsOnTargetThread() const
{
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
}
} // namespace dom
} // namespace mozilla