mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 13:23:07 +00:00
fffeb135bb
- Bug 1040668 part 2 - Parse and compute text emphasis properties. r=dbaron (0fb79d4709) - Bug 1040668 part 3 - Add helper function gfxTextRun::GetAdvanceForGlyph. r=jfkthame (f1cf02f5ff) - Bug 1216427 - Tests for backspacing over a character with variation selector, and over Regional Indicator flag symbols. r=emk (18957bfe77) - Bug 1216427 - part 1 - Ensure a character+VS sequence or a ligated Regional-Indicator flag symbol is deleted as a single unit when backspacing. r=emk (2776ff8f4a) - Bug 1216427 - part 2 - Ensure mouse selection does not split up a Regional Indicator flag symbol. r=emk (58eb82e6a1) - Bug 1040668 part 4 - Add helper function for ensuring a glyph is a complex glyph. r=jfkthame (ba17f7d0c4) - Bug 1040668 part 5 - Avoid unnecessary allocation inside EnsureComplexGlyph helper function. r=jfkthame (4968a7c68e) - Bug 1040668 part 6 - Add some specifier on gfxTextRun and gfxShapedWord so that compilers are able to reason out certain optimizations. r=jfkthame (69ca3eb959) - Bug 1040668 part 7 - Add NO_EMPHASIS_MARK flag in CompressedGlyph. r=jfkthame (34e9d8a6a6) - Bug 1227001 part 1 - Remove SetupBreakSinksFlags from BuildTextRunsScanner. r=jfkthame (520b1ba111) - Bug 1227001 part 2 - Remove no longer used mExistingTextRun from BreaSink. r=jfkthame (201782a78c) - Bug 1227001 part 3 - Remove no longer used mChangedBreaks from BreakSink. r=jfkthame (e754e2b13f) - Bug 1040668 part 8 - Setup text emphasis for text run. r=jfkthame (376377180e) - Bug 1040668 part 9 - Compute overflow from text-shadow after text decorations. r=dbaron (05c9bd08c2) - Bug 1040668 part 10 - Implement emphasis mark rendering. r=jfkthame (4d270afca8) - Bug 1040668 part 11 - Move line leadings adjusting code into a separate function in nsLineLayout. r=dholbert (fd4dd20ca5) - Bug 1040668 part 12 - Add line leadings for emphasis marks if necessary. r=dholbert (138add5ff7) - Bug 1040668 part 13 - Move first part of nsStyleFont::GetLanguage to nsPresContext::GetContentLanguage. r=dbaron (1e872d2a58) - Bug 1040668 part 14 - Add helper function nsStyleUtil::MatchesLanguagePrefix for doing simple language matching. r=dbaron (9322a02369) - Bug 1040668 part 15 - Make the default value of text-emphasis-position aware of the language. r=dbaron (6587c628da) - Bug 1040668 part 16 - Add reftests for text-emphasis. r=dbaron (9940d65182) - Bug 1040668 followup - Use monospace for text-emphasis reftests. rs=dbaron on a CLOSED TREE (fb7598c3ea) - Bug 1040668 followup 2 - Disable failing reftests of text-emphasis on Windows XP. (7c3f24ac22) - Bug 1040668 followup 3 - Wrap lang attribute mapping code in NS_STYLE_INHERIT_BIT test. r=dbaron (640e3b7b8f) - Bug 1219145 - nsRefreshDriver::IsJankCritical(). r=hiro To refine its alerts, Performance Stats API needs to be able to know whether a long-running operation is actually causing user-visible jank in the current process. This patch introduces a trivial API that lets clients ask the refresh driver whether any kind of animation is ongoing. (7c0868d7c4) - more missing XP theme stuff (637af0c6a0) - Bug 1210261. Tick root refresh driver last. r=mattwoodrow (dae1a325fa) - Bug 1221674 Part 2: Correct for negative content delay values. r=avih (9b9811c41c) - Bug 1211334 - Check if presshell is still available after dispatching transition events; r=mats (8a044a462c) - Bug 1211599 - Only allow whitelisted histograms to have > 100 buckets. r=nfroyd (d467e84130) - Bug 1219733 - Allow a 'bug_numbers' field in Histograms.json entries. r=vladan (8e63a713ec) - Bug 1222044 - Only allow lists of alert_emails r=vladan (1d5fcb009d) - Bug 1168263 - Add a flags parameter to GetResultingTransformMatrix instead of using bools. r=roc (ec5224f9af) - Bug 1168263 - Remove TransformRectOut since it's unused. r=roc (d20a79ae0e) - Bug 1215406 - Part 1: Remove NS_STYLE_ANIMATION_DIRECTION_XXX and NS_STYLE_ANIMATION_FILL_MODE_XXX. r=heycam (4bd7f8116b) - Bug 1215406 - Part 2: Change the types of direction and fillmode in StyleAnimation. r=heycam (e06323c81b) - Bug 1215406 - Part 3: Change the types of direction and fillmode in AnimationTiming. r=heycam (e5454d3ea5) - Bug 1215406 - Part 4: Add KeyframeEffectOptions. r=smaug Add KeyframeEffectOptions in KeyframeEffect.webidl (0ce3372fd1) - Bug 1215406 - Part 5: Implement KeyframeEffectOptions in KeyframeEffectReadOnly constructor. r=birtles (a472e9ac5f) - tch 2 - Use an enum class for NS_STYLE_BOX_SIZING_*. r=heycam (4897b7b8ff) - Bug 1218195, mark MutationObserver as observing in all the nested DOM mutations, r=bz (48a4aa8a91) - Bug 1172870 - Part 3 - Fix openWindow mochitest to work on e10s (574cc6fa4d) - Bug 1223265 - Fix -Wunreachable-code and -Wimplicit-fallthrough warnings in dom/bindings and dom/ipc. r=khuey (9eab632140) - Bug 1189195 - Fix PContentPermissionRequest shutdown () r=fabrice (6bc1a681f5) - Bug 1210508 - Handle null OriginAttributes from JS-implemented nsILoadContext. r=me (2f71edb9d7) - Bug 1224596 part 1. Add a version of WorkerMainThreadRunnable::Dispatch that takes an ErrorResult to report failure to dispatch on. r=khuey (e60a0fb115) - Bug 1224596 part 2. Switch Navigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d9298a4763) - Bug 1224596 part 3. Make some WebSocket(Impl) methods whose return value is totally ignored void. r=khuey (1d04b52d44) - Bug 1224596 part 4. Switch WebSocket to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (aa06e6417b) - Bug 1224596 part 5. Switch nsPerformance to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (0303b7de09) - Bug 1224596 part 6. Switch BroadcastChannel to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (feacc21f63) - Bug 1224636. Fix some code in ImageBitmap that rejects a promise with an ErrorResult, then keeps trying to use that ErrorResult. r=kaku (89b769fe07) - Bug 1224596 part 7. Switch ImageBitmap to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (e21b0a4a0a) - Bug 1183954, Don't leak nsStructuredCloneContainer (use of 'auto' is error prone), r=leak (1a9b30f2ca) - Bug 1114554 - Patch 9 - Fixed crash in b2g-desktop tests. r=nsm (6b12c6d121) - Bug 1189090 - Rework the nsISupports implementation in the ScopeCheckingGetCallback and its subclasses; r=nsm (6bea544597) - Bug 1187018 - Ensure feature is nulled out if it does not get added. r=khuey (9d7439498a) - Bug 1224596 part 8. Switch Notification to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (03f0f6877f) - Bug 1196079 - Always try to release Notification via normal WorkerRunnable first. r=wchen (c093253d6f) - Bug 1203324 - disable notifications on serviceworkers. r=ehsan,wchen (aa39310dc1) - Bug 1199901 - GetOrigin() fails cleanly instead of asserting principal. r=wchen (148c634a4f) - Bug 1199901 - Clear mObserver when WorkerNotificationObserver is destroyed. r=wchen (6d5cd99183) - Bug 1199901 - Bustage fix due to rebase. a=bustage (344bd62af5) - Bug 1225470 Report a message to the console when a service worker waitUntil() is rejected. r=baku (610da2eec6) - Bug 1217909 P1 Report service worker exceptions to controlled documents. r=catalinb (f8bd4677d5) - Bug 1216566 - Fix a bug in nsIServiceWorkerManager.getAllRegistrations;r=catalinb (ed3e14ab43) - Bug 1219205 - ServiceWorkerInfo should be an XPCOM object;r=catalinb (677a6f1ffd) - Bug 1217909 P2 Track registering documents as weak reference so SWM can report errors to them. r=catalinb (4233dc3edc) - Bug 1217909 P3 Refactor service worker register()/update() to reject only with SecurityErr or TypeErr. r=catalinb (c6891a7fae) - Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its scriptSpec property changes;r=amarchesini (5772bb5914) - Bug 1207727 - Add WPT tests for service worker update algorithm. r=bkelly (a4812571a1) - Bug 1217367 - Service workers update algorithm optimization. r=bkelly (e377debad1) - Bug 1226479. Change ErrorResult::ThrowTypeError/ThrowRangeError to take string references, not pointers. r=mccr8 (0804899666) - Bug 1224659 - Worker DataStore code should not use ErrorResult cross threads, r=bz (aef03b0fdd) - Bug 1224596 part 9. Switch DataStore to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (1eb1c427f2) - Bug 1224596 part 10. Switch DataStoreCursor to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (114af8021b) - Bug 1224596 part 11. Switch WorkerNavigator to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (b2347a0c49) - Bug 1224596 part 12. Switch ServiceWorkerRegistration to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (d1ff076836) - Bug 1224596 part 13. Switch gfxUtils to using the new WorkerMainThreadRunnable::Dispatch signature. r=khuey (a523997588) - Bug 1224007 part 1. Rename ThrowMethodFailed to MaybeSetPendingException and make it an ErrorResult instance method. r=peterv (bdf0891f54) - Bug 1224007 part 2. Make the various ErrorResult::Report* methods private, so consumers all go through MaybeSetPendingException and rename them to more clearly indicate what they're actually doing. r=peterv (48f483c153) - Bug 1203151 - Allow disabling of screen wakelocks for video elements. r=baku (6bd9ff6916) - Bug 1224007 part 4. Fix some cases in which ErrorResult instances are destroyed without doing anything useful with exceptions on them. r=peterv (e328785e5d) - Bug 1224007 part 5. Get rid of ErrorResult::StealJSException. r=peterv (db19cfb31e) - Bug 1224007 part 3. Push down WouldReportJSException into MaybeSetPendingException, since anyone calling the latter will propagate the JS exception as needed. r=peterv (1d3b7b415d) - Bug 1224007 part 6. Change MaybeSetPendingException to set the ErrorResult state to "not failed", just like SuppressException and StealNSResult already do, and assert in the destructor that the ErrorResult is not Failed(). (a028838e8d) - Bug 1213815 - Update URLSearchParams and URLUtils in webidl files, r=bz (28fb8f7de5) - Bug 1213815 - dom/webidl/HTMLHyperlinkElementUtils and URL don't need to throw exceptions as we did before, r=bz (b13dc3bcb9) - Bug 1224596 part 14. Switch URL to using the new WorkerMainThreadRunnable::Dispatch signature. r=baku (232677e50e) - fix updating the backport (9e958da5ca) - Bug 1224596 part 16. Switch Fetch to using the new WorkerMainThreadRunnable::Dispatch signature. r=bkelly (939e338f22) - Bug 1224596 part 17. Remove the old WorkerMainThreadRunnable::Dispatch signature. r=khuey (5a70429ec8) - Bug 1143575. Don't report negative frame delays. r=cpearce (7d8bc0f753) - Bug 1187371 - Get rid of dom.broadcastChannel.enabled pref, r=bz (9335b7ae90) - Bug 1196514 - remove dom.messagechannel.enabled pref, r=smaug (31e06119b4) - Bug 1166356 - Properly detect double-caching in nsXULPrototypeCache; r=ehsan (29df9ffb2d) - Bug 1168916 - Get rid of redundant pref callback in nsXULPrototypeCache; r=janv (9f37fff405) - Bug 1139099: Dispatch DOMDocElementInserted to match the document-element-inserted observer notification. r=mrbkap (6565e4b924) - Bug 1187068 - Tell the cycle collector about nsContentSink::mCSSLoader. r=heycam (4ae23eb26c) - Bug 1172189 - Fix overflow in nsXULContentSink.cpp. r=ehsan (cc6330f5de) - Bug 1126010 - XULContentSinkImpl::mParser should be an nsRefPtr. r=smaug (d6bb567692) - Bug 1147946, part 7 - Remove trailing whitespace from nsXULContentSink.cpp. r=baku (cdcadbfeeb) - Bug 1147946, part 1 - Tuck elses in nsXULContentSink.cpp. r=baku (26fd806676) - Bug 1147946, part 2 - Move body of check inside prior if in XULContentSinkImpl::OpenScript(). r=baku (b509455bdb) - Bug 1147946, part 3 - Eliminate unused case for non-JS scripting languages in XULContentSinkImpl::OpenScript(). r=baku (4136933cc2) - Bug 1147946, part 4 - Use an early return in XULContentSinkImpl::OpenScript(). r=baku (c3e293474b) - Bug 1147946, part 5 - Don't use the generic nsIProgrammingLanguage enum in XULContentSinkImpl::OpenScript(). r=baku (89a124e23f) - Bug 1147946, part 6 - Remove some useless null checks on infallible new in XULContentSinkImpl. r=baku (1ac57e8c3a) - Bug 1221351 P1 ServiceWorkerContainer and ServiceWorkerRegistration should not crash for null window owner. r=catalinb (1a72748632) - Bug 1212867 - Node.isEqualNode() should ignore internal subsets; r=bzBug (99b166ffee) - Bug 492933 - getElementsByTagName should match on localName not tagName, r=smaug (d0c6ceabf1) - Bug 912470 part 1 - Implement Encoding Standard-compliant big5 decoder. r=emk. (c680b0ae9b) - Bug 1170932: Test handling of unmapped characters in unicode-to-codepage encoders (ca36bcbd35) - fix style (95a90bfe3a) - Bug 1170794 - patch 2 - Improve the length check of the input in nsUnicode*::GetMaxLength, r=dveditz (aa864d656f) - Bug 1170794 followup: Add 'override' annotations to Convert() & Reset() methods in intl/uconv. rs=ehsan (bb3e6e492e) - Bug 1176462 - Remove nsTableDecoderSupport. r=smontagu (f4a86c44b3) - Bug 1169248 - Fix GBK/GB18030 encoders. r=smontagu (ed946e1ee1) - Bug 1155539 - Remove obsolete encoding decoder telemetry probes. r=emk. (44e15bfb40) - Bug 912470 part 2 - Implement Encoding Standard-compliant big5 encoder. r=emk. (5cca2dc4a0) - Bug 912470 addendum - Pass override static analysis. r=emk. (c163bffeb4) - Bug 1170932: Improve error handling for the gbk encoder, r=emk (30e95b34a5) - Bug 1202366 - Implement the encoder error mode "HTML" for nsFormSubmission without nsISaveAsCharset. r=NOT_emk. (ebc8b542dd) - update manifest (6cc19172cc) - Bug 1197309 - remove PR_snprintf calls in intl/; r=froydnj (79fcdfa845) - Bug 1214619 - Remove nsISaveAsCharset as much as possible without breaking extensions in popular use. r=emk. (89b71b3d87) - Bug 1214857. Store the document-is-HTML state directly in nsContentList instead of refetching from the node being matched. r=smaug (4c4fbf469e) - Bug 1221351 P2 Add a web-platform-test to check for crash when calling .register() on closed window. r=catalinb (eeb30c1bba) - Bug 1221351 P3 Fix test name in register-closed-window.https.html. a=testonly (5bfa840044) - Bug 1224436: Remove enumerator usage in ServiceWorkerManager.cpp. r=njn (b8cb094d3c) - Bug 1223716. Make HTMLCollection check for the element being HTML before checking for its name inside its named getter. r=bkelly (977e0bff5a) - Bug 1180737 - Add update-test.py and update test to latest version. r=bkelly. (59faa36d5c) - Bug 1217909 P4 Extend wpt tests to verify update() promise values for different script failures. r=catalinb (a2f7352a3a) - Bug 1217909 P5 Add wpt test case for fetch event handlers that throw. r=catalinb (56a77f611c) - Bug 1217909 P6 Fix wpt registration.https.html to expect TypeError for script evaluation errors. r=catalinb (3de8a45688) - Bug 1217909 P7 Fix mochitest to expect TypeError when serviceWorker.register() rejects. r=catalinb (6e8841c41e) - Bug 1217909 P8 Track navigation interceptions per scope in ServiceWorkerManager. r=catalinb (6705ba8337) - Bug 1217909 P9 Report exceptions to windows performing an intercepted navigation. r=catalinb (52f9fece14) - Bug 1217909 P10 Remove stale nsTArray when the last registering document for a scope is removed. r=catalinb (b739bcc3b2) - Bug 1217909 P11 Only report errors to documents that are active and not in the bfcache. r=catalinb (5ffd633af2) - Bug 1223378 Tighten service worker register() principal checks. r=baku (478785f2cc) - Bug 1189685 - Part 1: Ensure that the state of all ServiceWorker instances is up to date when dispatching statechange events; r=bkelly (2bd9b78c58) - Bug 1189685 - Part 2: Make synced-state.https.html pass; r=bkelly (029f942d8c) - Bug 1220740 - nsIServiceWorkerRegistrationInfo should emit an event when its worker properties change;r=amarchesini (8243a3debc) - Bug 1186856 ServiceWorker .register() should always stop current registration from uninstalling. r=jdm (ec7d6e0e7c) - Bug 1224941 Don't crash during ServiceWorker life cycle event dispatch if window is gone. r=baku (a3f45af3e4) - Bug 1180754 - Get serviceworkerobject-scripturl test passing. r=bkelly (c7979bef47) - Bug 1201498 - Service worker update should compare scriptURL to worker URL without fragment, r=bkelly (914f630528) - Bug 594505 - Remove obsolete comment since this bug has now been fixed. r=me DONTBUILD (44f3a15b91) - Bug 1221840. Support repeating images in 1 axis. r=seth (449ea3e97e) - const-var (5433688051) - Bug 1574573 - Disambiguate a use of Handle in XPCShellEnvironment.cpp r=Ehsan (15b44177d1) - clean up warnings (6e64313d0c)
2801 lines
73 KiB
C++
2801 lines
73 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);
|
|
NS_ReleaseOnMainThread(static_cast<nsIWebSocketEventService*>(mService.forget().take()));
|
|
|
|
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::AutoFilename 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::AutoFilename 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();
|
|
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
}
|
|
|
|
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)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aDispatchResult)
|
|
{
|
|
}
|
|
|
|
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
|