summaryrefslogtreecommitdiff
path: root/dom
diff options
context:
space:
mode:
authorBrian Smith <brian@dbsoft.org>2022-08-18 16:30:38 -0500
committerBrian Smith <brian@dbsoft.org>2022-08-18 16:30:38 -0500
commitb52731f9e00920ad9e750069bf5e2f4547cb0893 (patch)
tree65a756bf799aa70f09286b0a93fd08a8cd0edc8d /dom
parent544366e3010ea16601ff363a2f41df5f84f77d47 (diff)
downloaduxp-b52731f9e00920ad9e750069bf5e2f4547cb0893.tar.gz
Issue #1990 - Part 1 - EventSource for workers - Mozilla Bug 1267903
Diffstat (limited to 'dom')
-rw-r--r--dom/base/EventSource.cpp1457
-rw-r--r--dom/base/EventSource.h214
-rw-r--r--dom/webidl/EventSource.webidl3
3 files changed, 1139 insertions, 535 deletions
diff --git a/dom/base/EventSource.cpp b/dom/base/EventSource.cpp
index cd3672c74f..f4aad9ab49 100644
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -13,7 +13,10 @@
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/ScriptSettings.h"
-
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/UniquePtrExtensions.h"
#include "nsAutoPtr.h"
#include "nsNetUtil.h"
#include "nsIAuthPrompt.h"
@@ -30,6 +33,7 @@
#include "nsIObserverService.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsJSUtils.h"
+#include "nsIThreadRetargetableRequest.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIScriptError.h"
#include "mozilla/dom/EncodingUtils.h"
@@ -44,6 +48,8 @@
namespace mozilla {
namespace dom {
+using namespace workers;
+
#define REPLACEMENT_CHAR (char16_t)0xFFFD
#define BOM_CHAR (char16_t)0xFEFF
#define SPACE_CHAR (char16_t)0x0020
@@ -51,112 +57,379 @@ namespace dom {
#define LF_CHAR (char16_t)0x000A
#define COLON_CHAR (char16_t)0x003A
-#define DEFAULT_BUFFER_SIZE 4096
-
// Reconnection time related values in milliseconds. The default one is equal
// to the default value of the pref dom.server-events.default-reconnection-time
#define MIN_RECONNECTION_TIME_VALUE 500
#define DEFAULT_RECONNECTION_TIME_VALUE 5000
#define MAX_RECONNECTION_TIME_VALUE PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
-EventSource::EventSource(nsPIDOMWindowInner* aOwnerWindow) :
- DOMEventTargetHelper(aOwnerWindow),
- mStatus(PARSE_STATE_OFF),
- mFrozen(false),
- mErrorLoadOnRedirect(false),
- mGoingToDispatchAllMessages(false),
- mWithCredentials(false),
- mWaitingForOnStopRequest(false),
- mLastConvertionResult(NS_OK),
- mReadyState(CONNECTING),
- mScriptLine(0),
- mScriptColumn(0),
- mInnerWindowID(0)
-{
-}
-
-EventSource::~EventSource()
+class EventSourceImpl final : public nsIObserver
+ , public nsIStreamListener
+ , public nsIChannelEventSink
+ , public nsIInterfaceRequestor
+ , public nsSupportsWeakReference
+ , public nsIEventTarget
+ , public nsIThreadRetargetableStreamListener
{
- Close();
-}
-
-//-----------------------------------------------------------------------------
-// EventSource::nsISupports
-//-----------------------------------------------------------------------------
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIEVENTTARGET
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ explicit EventSourceImpl(EventSource* aEventSource);
+
+ enum {
+ CONNECTING = 0U,
+ OPEN = 1U,
+ CLOSED = 2U
+ };
+
+ void Close();
+
+ void Init(nsIPrincipal* aPrincipal, const nsAString& aURL, ErrorResult& aRv);
+
+ nsresult GetBaseURI(nsIURI** aBaseURI);
+
+ void SetupHttpChannel();
+ nsresult SetupReferrerPolicy();
+ nsresult InitChannelAndRequestEventSource();
+ nsresult ResetConnection();
+ void ResetDecoder();
+ nsresult SetReconnectionTimeout();
+
+ void AnnounceConnection();
+ void DispatchAllMessageEvents();
+ nsresult RestartConnection();
+ void ReestablishConnection();
+ void DispatchFailConnection();
+ void FailConnection();
+
+ nsresult Thaw();
+ nsresult Freeze();
+
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+
+ nsresult PrintErrorOnConsole(const char* aBundleURI,
+ const char16_t* aError,
+ const char16_t** aFormatStrings,
+ uint32_t aFormatStringsLen);
+ nsresult ConsoleError();
+
+ static nsresult StreamReaderFunc(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount);
+ void ParseSegment(const char* aBuffer, uint32_t aLength);
+ nsresult SetFieldAndClear();
+ nsresult ClearFields();
+ nsresult ResetEvent();
+ nsresult DispatchCurrentMessageEvent();
+ nsresult ParseCharacter(char16_t aChr);
+ nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
+ nsresult OnRedirectVerifyCallback(nsresult result);
+ nsresult ParseURL(const nsAString& aURL);
+ nsresult AddWindowObservers();
+ void RemoveWindowObservers();
+
+ void CloseInternal();
+ void CleanupOnMainThread();
+ void AddRefObject();
+ void ReleaseObject();
+
+ bool RegisterWorkerHolder();
+ void UnregisterWorkerHolder();
+
+ void AssertIsOnTargetThread() const
+ {
+ MOZ_ASSERT(IsTargetThread());
+ }
-NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
+ bool IsTargetThread() const
+ {
+ return NS_IsMainThread() == mIsMainThread;
+ }
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(EventSource)
- bool isBlack = tmp->IsBlack();
- if (isBlack || tmp->mWaitingForOnStopRequest) {
- if (tmp->mListenerManager) {
- tmp->mListenerManager->MarkForCC();
- }
- if (!isBlack && tmp->PreservingWrapper()) {
- // This marks the wrapper black.
- tmp->GetWrapper();
+ uint16_t ReadyState()
+ {
+ if (mEventSource) {
+ MutexAutoLock lock(mMutex);
+ return mEventSource->mReadyState;
}
- return true;
+ // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
+ // means that the EventSource has been closed.
+ return CLOSED;
}
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(EventSource)
- return tmp->IsBlack();
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+ void SetReadyState(uint16_t aReadyState)
+ {
+ MOZ_ASSERT(mEventSource);
+ MutexAutoLock lock(mMutex);
+ mEventSource->mReadyState = aReadyState;
+ }
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(EventSource)
- return tmp->IsBlack();
-NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+ bool IsFrozen()
+ {
+ MutexAutoLock lock(mMutex);
+ return mFrozen;
+ }
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EventSource,
- DOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
+ void SetFrozen(bool aFrozen)
+ {
+ MutexAutoLock lock(mMutex);
+ mFrozen = aFrozen;
+ }
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
- DOMEventTargetHelper)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrc)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadGroup)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHttpChannel)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimer)
- NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnicodeDecoder)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+ bool IsClosed()
+ {
+ return ReadyState() == CLOSED;
+ }
+
+ RefPtr<EventSource> mEventSource;
+
+ /**
+ * A simple state machine used to manage the event-source's line buffer
+ *
+ * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
+ *
+ * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_BOM_WAS_READ |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_BOM_WAS_READ -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME
+ *
+ * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE |
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
+ *
+ * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
+ * PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
+ * PARSE_STATE_COMMENT |
+ * PARSE_STATE_FIELD_NAME |
+ * PARSE_STATE_BEGIN_OF_LINE
+ *
+ * Whenever the parser find an empty line or the end-of-file
+ * it dispatches the stacked event.
+ *
+ */
+ enum ParserStatus {
+ PARSE_STATE_OFF = 0,
+ PARSE_STATE_BEGIN_OF_STREAM,
+ PARSE_STATE_BOM_WAS_READ,
+ PARSE_STATE_CR_CHAR,
+ PARSE_STATE_COMMENT,
+ PARSE_STATE_FIELD_NAME,
+ PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
+ PARSE_STATE_FIELD_VALUE,
+ PARSE_STATE_BEGIN_OF_LINE
+ };
+
+ // Connection related data members. Should only be accessed on main thread.
+ nsCOMPtr<nsIURI> mSrc;
+ uint32_t mReconnectionTime; // in ms
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsString mOrigin;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+
+ struct Message
+ {
+ nsString mEventName;
+ nsString mLastEventID;
+ nsString mData;
+ };
+
+ // Message related data members. May be set / initialized when initializing
+ // EventSourceImpl on target thread but should only be used on target thread.
+ nsString mLastEventID;
+ Message mCurrentMessage;
+ nsDeque mMessagesToDispatch;
+ ParserStatus mStatus;
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+ nsresult mLastConvertionResult;
+ nsString mLastFieldName;
+ nsString mLastFieldValue;
+
+ // EventSourceImpl internal states.
+ // The worker private where the EventSource is created. nullptr if created on
+ // main thread. (accessed on worker thread only)
+ WorkerPrivate* mWorkerPrivate;
+ // Holder to worker to keep worker alive. (accessed on worker thread only)
+ nsAutoPtr<WorkerHolder> mWorkerHolder;
+ // This mutex protects mFrozen and mEventSource->mReadyState that are used in
+ // different threads.
+ mozilla::Mutex mMutex;
+ // Whether the window is frozen. May be set on main thread and read on target
+ // thread. Use mMutex to protect it before accessing it.
+ bool mFrozen;
+ // There are some messages are going to be dispatched when thaw.
+ bool mGoingToDispatchAllMessages;
+ // Whether the EventSource is run on main thread.
+ bool mIsMainThread;
+ // Whether the EventSourceImpl is going to be destroyed.
+ bool mIsClosing;
+
+ // Event Source owner information:
+ // - the script file name
+ // - source code line number and column number where the Event Source object
+ // was constructed.
+ // - the ID of the inner window where the script lives. Note that this may not
+ // be the same as the Event Source owner window.
+ // These attributes are used for error reporting. Should only be accessed on
+ // target thread
+ nsString mScriptFile;
+ uint32_t mScriptLine;
+ uint32_t mScriptColumn;
+ uint64_t mInnerWindowID;
+
+private:
+ // prevent bad usage
+ EventSourceImpl(const EventSourceImpl& x) = delete;
+ EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
+ ~EventSourceImpl()
+ {
+ if (IsClosed()) {
+ return;
+ }
+ // If we threw during Init we never called Close
+ SetReadyState(CLOSED);
+ CloseInternal();
+ }
+};
+
+NS_IMPL_ISUPPORTS(EventSourceImpl,
+ nsIObserver,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIChannelEventSink,
+ nsIInterfaceRequestor,
+ nsISupportsWeakReference,
+ nsIEventTarget,
+ nsIThreadRetargetableStreamListener)
+
+EventSourceImpl::EventSourceImpl(EventSource* aEventSource)
+ : mEventSource(aEventSource)
+ , mReconnectionTime(0)
+ , mStatus(PARSE_STATE_OFF)
+ , mLastConvertionResult(NS_OK)
+ , mMutex("EventSourceImpl::mMutex")
+ , mFrozen(false)
+ , mGoingToDispatchAllMessages(false)
+ , mIsMainThread(NS_IsMainThread())
+ , mIsClosing(false)
+ , mScriptLine(0)
+ , mScriptColumn(0)
+ , mInnerWindowID(0)
+{
+ MOZ_ASSERT(mEventSource);
+ if (!mIsMainThread) {
+ mWorkerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(mWorkerPrivate);
+ mEventSource->mIsMainThread = false;
+ }
+ SetReadyState(CONNECTING);
+}
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
- DOMEventTargetHelper)
- tmp->Close();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+class CleanupRunnable final : public WorkerMainThreadRunnable
+{
+public:
+ explicit CleanupRunnable(EventSourceImpl* aEventSourceImpl)
+ : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("EventSource :: Cleanup"))
+ , mImpl(aEventSourceImpl)
+ {
+ mImpl->mWorkerPrivate->AssertIsOnWorkerThread();
+ }
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(EventSource)
- NS_INTERFACE_MAP_ENTRY(nsIObserver)
- NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
- NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
- NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
- NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
- NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+ bool MainThreadRun() override
+ {
+ mImpl->CleanupOnMainThread();
+ return true;
+ }
-NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
+protected:
+ // Raw pointer because this runnable is sync.
+ EventSourceImpl* mImpl;
+};
void
-EventSource::DisconnectFromOwner()
+EventSourceImpl::Close()
{
- DOMEventTargetHelper::DisconnectFromOwner();
- Close();
+ if (IsClosed()) {
+ return;
+ }
+ SetReadyState(CLOSED);
+ // Asynchronously call CloseInternal to prevent EventSourceImpl from being
+ // synchronously destoryed while dispatching DOM event.
+ DebugOnly<nsresult> rv =
+ Dispatch(NewRunnableMethod(this, &EventSourceImpl::CloseInternal),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
}
void
-EventSource::Close()
+EventSourceImpl::CloseInternal()
{
- if (mReadyState == CLOSED) {
+ AssertIsOnTargetThread();
+ MOZ_ASSERT(IsClosed());
+ if (mIsClosing) {
return;
}
+ mIsClosing = true;
+ while (mMessagesToDispatch.GetSize() != 0) {
+ delete static_cast<Message*>(mMessagesToDispatch.PopFront());
+ }
- nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
- if (os) {
- os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
- os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
- os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
+ if (NS_IsMainThread()) {
+ CleanupOnMainThread();
+ } else {
+ ErrorResult rv;
+ // run CleanupOnMainThread synchronously on main thread since it touches
+ // observers and members only can be accessed on main thread.
+ RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
+ runnable->Dispatch(Killing, rv);
+ MOZ_ASSERT(!rv.Failed());
+ UnregisterWorkerHolder();
+ }
+
+ SetFrozen(false);
+ ResetDecoder();
+ mUnicodeDecoder = nullptr;
+ // UpdateDontKeepAlive() can release the object. Don't access to any members
+ // after it.
+ mEventSource->UpdateDontKeepAlive();
+}
+
+void EventSourceImpl::CleanupOnMainThread()
+{
+ AssertIsOnMainThread();
+ if (mIsMainThread) {
+ RemoveWindowObservers();
}
if (mTimer) {
@@ -165,59 +438,57 @@ EventSource::Close()
}
ResetConnection();
-
- ClearFields();
-
- while (mMessagesToDispatch.GetSize() != 0) {
- delete static_cast<Message*>(mMessagesToDispatch.PopFront());
- }
-
+ mPrincipal = nullptr;
mSrc = nullptr;
- mFrozen = false;
-
- mUnicodeDecoder = nullptr;
-
- mReadyState = CLOSED;
}
-nsresult
-EventSource::Init(nsISupports* aOwner,
- const nsAString& aURL,
- bool aWithCredentials)
+class InitRunnable final : public WorkerMainThreadRunnable
{
- if (mReadyState != CONNECTING) {
- return NS_ERROR_DOM_SECURITY_ERR;
+public:
+ explicit InitRunnable(EventSourceImpl* aEventSourceImpl,
+ const nsAString& aURL)
+ : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("EventSource :: Init"))
+ , mImpl(aEventSourceImpl)
+ , mURL(aURL)
+ {
+ mImpl->mWorkerPrivate->AssertIsOnWorkerThread();
}
- nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner);
- NS_ENSURE_STATE(sgo);
- // XXXbz why are we checking this? This doesn't match anything in the spec.
- nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext();
- NS_ENSURE_STATE(scriptContext);
-
- nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
- do_QueryInterface(aOwner);
- NS_ENSURE_STATE(scriptPrincipal);
- nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
- NS_ENSURE_STATE(principal);
+ bool MainThreadRun() override
+ {
+ // Get principal from worker's owner document or from worker.
+ WorkerPrivate* wp = mImpl->mWorkerPrivate;
+ while (wp->GetParent()) {
+ wp = wp->GetParent();
+ }
+ nsPIDOMWindowInner* window = wp->GetWindow();
+ nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
+ nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() :
+ wp->GetPrincipal();
+ if (!principal) {
+ mRv = NS_ERROR_FAILURE;
+ return true;
+ }
+ ErrorResult rv;
+ mImpl->Init(principal, mURL, rv);
+ mRv = rv.StealNSResult();
+ return true;
+ }
- mPrincipal = principal;
- mWithCredentials = aWithCredentials;
+ nsresult ErrorCode() const { return mRv; }
- // The conditional here is historical and not necessarily sane.
- if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
- nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
- &mScriptColumn);
- mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
- }
+protected:
+ // Raw pointer because this runnable is sync.
+ EventSourceImpl* mImpl;
+ const nsAString& mURL;
+ nsresult mRv;
+};
- // Get the load group for the page. When requesting we'll add ourselves to it.
- // This way any pending requests will be automatically aborted if the user
- // leaves the page.
- nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
- if (doc) {
- mLoadGroup = doc->GetDocumentLoadGroup();
- }
+nsresult
+EventSourceImpl::ParseURL(const nsAString& aURL)
+{
+ AssertIsOnMainThread();
// get the src
nsCOMPtr<nsIURI> baseURI;
nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
@@ -227,28 +498,79 @@ EventSource::Init(nsISupports* aOwner,
rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
- // we observe when the window freezes and thaws
+ nsAutoString origin;
+ rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = srcURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
+ mSrc = srcURI;
+ mOrigin = origin;
+ return NS_OK;
+}
+
+nsresult
+EventSourceImpl::AddWindowObservers()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mIsMainThread);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
NS_ENSURE_STATE(os);
- rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
+ nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
- nsAutoString origin;
- rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
- NS_ENSURE_SUCCESS(rv, rv);
+void
+EventSourceImpl::RemoveWindowObservers()
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mIsMainThread);
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
+ os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
+ }
+}
- nsAutoCString spec;
- rv = srcURI->GetSpec(spec);
- NS_ENSURE_SUCCESS(rv, rv);
+void
+EventSourceImpl::Init(nsIPrincipal* aPrincipal,
+ const nsAString& aURL,
+ ErrorResult& aRv)
+{
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aPrincipal);
+ if (IsClosed()) {
+ return;
+ }
+ mPrincipal = aPrincipal;
+ aRv = ParseURL(aURL);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ // The conditional here is historical and not necessarily sane.
+ if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
+ nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
+ &mScriptColumn);
+ mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
+ }
- mOriginalURL = NS_ConvertUTF8toUTF16(spec);
- mSrc = srcURI;
- mOrigin = origin;
+ if (mIsMainThread) {
+ // we observe when the window freezes and thaws
+ aRv = AddWindowObservers();
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
mReconnectionTime =
Preferences::GetInt("dom.server-events.default-reconnection-time",
@@ -260,61 +582,34 @@ EventSource::Init(nsISupports* aOwner,
// url parameter, so we don't care about the InitChannelAndRequestEventSource
// result.
InitChannelAndRequestEventSource();
-
- return NS_OK;
-}
-
-/* virtual */ JSObject*
-EventSource::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
- return EventSourceBinding::Wrap(aCx, this, aGivenProto);
-}
-
-/* static */ already_AddRefed<EventSource>
-EventSource::Constructor(const GlobalObject& aGlobal,
- const nsAString& aURL,
- const EventSourceInit& aEventSourceInitDict,
- ErrorResult& aRv)
-{
- nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
- do_QueryInterface(aGlobal.GetAsSupports());
- if (!ownerWindow) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- MOZ_ASSERT(ownerWindow->IsInnerWindow());
-
- RefPtr<EventSource> eventSource = new EventSource(ownerWindow);
- aRv = eventSource->Init(aGlobal.GetAsSupports(), aURL,
- aEventSourceInitDict.mWithCredentials);
- return eventSource.forget();
}
//-----------------------------------------------------------------------------
-// EventSource::nsIObserver
+// EventSourceImpl::nsIObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
-EventSource::Observe(nsISupports* aSubject,
- const char* aTopic,
- const char16_t* aData)
+EventSourceImpl::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
{
- if (mReadyState == CLOSED) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
return NS_OK;
}
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
- if (!GetOwner() || window != GetOwner()) {
+ if (!mEventSource->GetOwner() || window != mEventSource->GetOwner()) {
return NS_OK;
}
DebugOnly<nsresult> rv;
if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
rv = Freeze();
- NS_ASSERTION(NS_SUCCEEDED(rv), "Freeze() failed");
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
} else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
rv = Thaw();
- NS_ASSERTION(NS_SUCCEEDED(rv), "Thaw() failed");
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
} else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
Close();
}
@@ -323,13 +618,16 @@ EventSource::Observe(nsISupports* aSubject,
}
//-----------------------------------------------------------------------------
-// EventSource::nsIStreamListener
+// EventSourceImpl::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
-EventSource::OnStartRequest(nsIRequest *aRequest,
- nsISupports *ctxt)
+EventSourceImpl::OnStartRequest(nsIRequest* aRequest, nsISupports* aCtxt)
{
+ AssertIsOnMainThread();
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
@@ -364,88 +662,148 @@ EventSource::OnStartRequest(nsIRequest *aRequest,
return NS_ERROR_ABORT;
}
- rv = NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::AnnounceConnection));
+ if (!mIsMainThread) {
+ // Try to retarget to worker thread, otherwise fall back to main thread.
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
+ if (rr) {
+ rv = rr->RetargetDeliveryTo(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ rv = Dispatch(NewRunnableMethod(this, &EventSourceImpl::AnnounceConnection),
+ NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
-
mStatus = PARSE_STATE_BEGIN_OF_STREAM;
-
return NS_OK;
}
// this method parses the characters as they become available instead of
// buffering them.
nsresult
-EventSource::StreamReaderFunc(nsIInputStream *aInputStream,
- void *aClosure,
- const char *aFromRawSegment,
- uint32_t aToOffset,
- uint32_t aCount,
- uint32_t *aWriteCount)
+EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
{
- EventSource* thisObject = static_cast<EventSource*>(aClosure);
+ EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
+ thisObject->AssertIsOnTargetThread();
if (!thisObject || !aWriteCount) {
NS_WARNING("EventSource cannot read from stream: no aClosure or aWriteCount");
return NS_ERROR_FAILURE;
}
+ thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
+ *aWriteCount = aCount;
+ return NS_OK;
+}
- *aWriteCount = 0;
-
+void
+EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength)
+{
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
+ return;
+ }
int32_t srcCount, outCount;
char16_t out[2];
- nsresult rv;
-
- const char *p = aFromRawSegment,
- *end = aFromRawSegment + aCount;
+ const char* p = aBuffer;
+ const char* end = aBuffer + aLength;
do {
- srcCount = aCount - (p - aFromRawSegment);
+ srcCount = aLength - (p - aBuffer);
outCount = 2;
- thisObject->mLastConvertionResult =
- thisObject->mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
- MOZ_ASSERT(thisObject->mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
+ mLastConvertionResult =
+ mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
+ MOZ_ASSERT(mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
for (int32_t i = 0; i < outCount; ++i) {
- rv = thisObject->ParseCharacter(out[i]);
- NS_ENSURE_SUCCESS(rv, rv);
+ nsresult rv = ParseCharacter(out[i]);
+ NS_ENSURE_SUCCESS_VOID(rv);
}
p = p + srcCount;
} while (p < end &&
- thisObject->mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
- thisObject->mLastConvertionResult != NS_OK);
-
- *aWriteCount = aCount;
- return NS_OK;
+ mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
+ mLastConvertionResult != NS_OK);
}
+class DataAvailableRunnable final : public Runnable
+{
+ private:
+ RefPtr<EventSourceImpl> mEventSourceImpl;
+ UniquePtr<char[]> mData;
+ uint32_t mLength;
+ public:
+ DataAvailableRunnable(EventSourceImpl* aEventSourceImpl,
+ UniquePtr<char[]> aData,
+ uint32_t aLength)
+ : mEventSourceImpl(aEventSourceImpl)
+ , mData(Move(aData))
+ , mLength(aLength)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mEventSourceImpl->ParseSegment(mData.get(), mLength);
+ return NS_OK;
+ }
+};
+
NS_IMETHODIMP
-EventSource::OnDataAvailable(nsIRequest *aRequest,
- nsISupports *aContext,
- nsIInputStream *aInputStream,
- uint64_t aOffset,
- uint32_t aCount)
+EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
{
+ // Although we try to retarget OnDataAvailable to target thread, it may fail
+ // and fallback to main thread.
NS_ENSURE_ARG_POINTER(aInputStream);
+ if (IsClosed()) {
+ return NS_ERROR_ABORT;
+ }
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t totalRead;
- return aInputStream->ReadSegments(EventSource::StreamReaderFunc, this,
+ if (IsTargetThread()) {
+ rv = aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
aCount, &totalRead);
+ } else {
+ // This could be happened when fail to retarget to target thread and
+ // fallback to the main thread.
+ AssertIsOnMainThread();
+ auto data = MakeUniqueFallible<char[]>(aCount);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = aInputStream->Read(data.get(), aCount, &totalRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(totalRead <= aCount, "EventSource read more than available!!");
+
+ nsCOMPtr<nsIRunnable> dataAvailable =
+ new DataAvailableRunnable(this, Move(data), totalRead);
+
+ MOZ_ASSERT(mWorkerPrivate);
+ rv = Dispatch(dataAvailable.forget(), NS_DISPATCH_NORMAL);
+ }
+ return rv;
}
NS_IMETHODIMP
-EventSource::OnStopRequest(nsIRequest *aRequest,
- nsISupports *aContext,
- nsresult aStatusCode)
+EventSourceImpl::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
{
- mWaitingForOnStopRequest = false;
+ AssertIsOnMainThread();
- if (mReadyState == CLOSED) {
+ if (IsClosed()) {
return NS_ERROR_ABORT;
}
-
+ MOZ_ASSERT(mSrc);
// "Network errors that prevents the connection from being established in the
// first place (e.g. DNS errors), must cause the user agent to asynchronously
// reestablish the connection.
@@ -468,24 +826,25 @@ EventSource::OnStopRequest(nsIRequest *aRequest,
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
- ClearFields();
-
- rv = NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::ReestablishConnection));
+ rv = Dispatch(
+ NewRunnableMethod(this, &EventSourceImpl::ReestablishConnection),
+ NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
//-----------------------------------------------------------------------------
-// EventSource::nsIChannelEventSink
+// EventSourceImpl::nsIChannelEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
-EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
- nsIChannel *aNewChannel,
- uint32_t aFlags,
- nsIAsyncVerifyRedirectCallback *aCallback)
+EventSourceImpl::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback)
{
+ AssertIsOnMainThread();
nsCOMPtr<nsIRequest> aOldRequest = do_QueryInterface(aOldChannel);
NS_PRECONDITION(aOldRequest, "Redirect from a null request?");
@@ -502,7 +861,7 @@ EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
(NS_SUCCEEDED(newURI->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
(NS_SUCCEEDED(newURI->SchemeIs("https", &isValidScheme)) && isValidScheme);
- rv = CheckInnerWindowCorrectness();
+ rv = mEventSource->CheckInnerWindowCorrectness();
if (NS_FAILED(rv) || !isValidScheme) {
DispatchFailConnection();
return NS_ERROR_DOM_SECURITY_ERR;
@@ -528,13 +887,13 @@ EventSource::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
}
//-----------------------------------------------------------------------------
-// EventSource::nsIInterfaceRequestor
+// EventSourceImpl::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
-EventSource::GetInterface(const nsIID & aIID,
- void **aResult)
+EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult)
{
+ AssertIsOnMainThread();
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
*aResult = static_cast<nsIChannelEventSink*>(this);
NS_ADDREF_THIS();
@@ -543,7 +902,7 @@ EventSource::GetInterface(const nsIID & aIID,
if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
- nsresult rv = CheckInnerWindowCorrectness();
+ nsresult rv = mEventSource->CheckInnerWindowCorrectness();
NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIPromptFactory> wwatch =
@@ -554,8 +913,8 @@ EventSource::GetInterface(const nsIID & aIID,
// of the dialogs works as it should when using tabs.
nsCOMPtr<nsPIDOMWindowOuter> window;
- if (GetOwner()) {
- window = GetOwner()->GetOuterWindow();
+ if (mEventSource->GetOwner()) {
+ window = mEventSource->GetOwner()->GetOuterWindow();
}
return wwatch->GetPrompt(window, aIID, aResult);
@@ -564,9 +923,17 @@ EventSource::GetInterface(const nsIID & aIID,
return QueryInterface(aIID, aResult);
}
+NS_IMETHODIMP
+EventSourceImpl::IsOnCurrentThread(bool* aResult)
+{
+ *aResult = IsTargetThread();
+ return NS_OK;
+}
+
nsresult
-EventSource::GetBaseURI(nsIURI **aBaseURI)
+EventSourceImpl::GetBaseURI(nsIURI** aBaseURI)
{
+ AssertIsOnMainThread();
NS_ENSURE_ARG_POINTER(aBaseURI);
*aBaseURI = nullptr;
@@ -574,7 +941,7 @@ EventSource::GetBaseURI(nsIURI **aBaseURI)
nsCOMPtr<nsIURI> baseURI;
// first we try from document->GetBaseURI()
- nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ nsCOMPtr<nsIDocument> doc = mEventSource->GetDocumentIfCurrent();
if (doc) {
baseURI = doc->GetBaseURI();
}
@@ -592,8 +959,9 @@ EventSource::GetBaseURI(nsIURI **aBaseURI)
}
void
-EventSource::SetupHttpChannel()
+EventSourceImpl::SetupHttpChannel()
{
+ AssertIsOnMainThread();
mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
/* set the http request headers */
@@ -610,9 +978,10 @@ EventSource::SetupHttpChannel()
}
nsresult
-EventSource::SetupReferrerPolicy()
+EventSourceImpl::SetupReferrerPolicy()
{
- nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ AssertIsOnMainThread();
+ nsCOMPtr<nsIDocument> doc = mEventSource->GetDocumentIfCurrent();
if (doc) {
nsresult rv = mHttpChannel->SetReferrerWithPolicy(doc->GetDocumentURI(),
doc->GetReferrerPolicy());
@@ -623,9 +992,10 @@ EventSource::SetupReferrerPolicy()
}
nsresult
-EventSource::InitChannelAndRequestEventSource()
+EventSourceImpl::InitChannelAndRequestEventSource()
{
- if (mReadyState == CLOSED) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
return NS_ERROR_ABORT;
}
@@ -633,7 +1003,7 @@ EventSource::InitChannelAndRequestEventSource()
(NS_SUCCEEDED(mSrc->SchemeIs("http", &isValidScheme)) && isValidScheme) ||
(NS_SUCCEEDED(mSrc->SchemeIs("https", &isValidScheme)) && isValidScheme);
- nsresult rv = CheckInnerWindowCorrectness();
+ nsresult rv = mEventSource->CheckInnerWindowCorrectness();
if (NS_FAILED(rv) || !isValidScheme) {
DispatchFailConnection();
return NS_ERROR_DOM_SECURITY_ERR;
@@ -642,24 +1012,25 @@ EventSource::InitChannelAndRequestEventSource()
nsLoadFlags loadFlags;
loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE;
- nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+ nsCOMPtr<nsIDocument> doc = mEventSource->GetDocumentIfCurrent();
nsSecurityFlags securityFlags =
nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
- if (mWithCredentials) {
+ if (mEventSource->mWithCredentials) {
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
}
nsCOMPtr<nsIChannel> channel;
// If we have the document, use it
if (doc) {
+ nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
rv = NS_NewChannel(getter_AddRefs(channel),
mSrc,
doc,
securityFlags,
nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
- mLoadGroup, // loadGroup
+ loadGroup,
nullptr, // aCallbacks
loadFlags); // aLoadFlags
} else {
@@ -669,7 +1040,7 @@ EventSource::InitChannelAndRequestEventSource()
mPrincipal,
securityFlags,
nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
- mLoadGroup, // loadGroup
+ nullptr, // loadGroup
nullptr, // aCallbacks
loadFlags); // aLoadFlags
}
@@ -698,18 +1069,17 @@ EventSource::InitChannelAndRequestEventSource()
DispatchFailConnection();
return rv;
}
- mWaitingForOnStopRequest = true;
+ // Create the connection. Ask EventSource to hold reference until Close is
+ // called or network error is received.
+ mEventSource->UpdateMustKeepAlive();
return rv;
}
void
-EventSource::AnnounceConnection()
+EventSourceImpl::AnnounceConnection()
{
- if (mReadyState == CLOSED) {
- return;
- }
-
- if (mReadyState != CONNECTING) {
+ AssertIsOnTargetThread();
+ if (ReadyState() != CONNECTING) {
NS_WARNING("Unexpected mReadyState!!!");
return;
}
@@ -718,87 +1088,117 @@ EventSource::AnnounceConnection()
// the readyState attribute to OPEN and queue a task to fire a simple event
// named open at the EventSource object.
- mReadyState = OPEN;
+ SetReadyState(OPEN);
- nsresult rv = CheckInnerWindowCorrectness();
+ nsresult rv = mEventSource->CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
-
- RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
-
- // it doesn't bubble, and it isn't cancelable
- event->InitEvent(NS_LITERAL_STRING("open"), false, false);
- event->SetTrusted(true);
-
- rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ rv = mEventSource->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("open"));
if (NS_FAILED(rv)) {
- NS_WARNING("Failed to dispatch the open event!!!");
+ NS_WARNING("Failed to dispatch the error event!!!");
return;
}
}
nsresult
-EventSource::ResetConnection()
+EventSourceImpl::ResetConnection()
{
+ AssertIsOnMainThread();
if (mHttpChannel) {
mHttpChannel->Cancel(NS_ERROR_ABORT);
+ mHttpChannel = nullptr;
}
+ return NS_OK;
+}
+void
+EventSourceImpl::ResetDecoder()
+{
+ AssertIsOnTargetThread();
if (mUnicodeDecoder) {
mUnicodeDecoder->Reset();
}
+ mStatus = PARSE_STATE_OFF;
mLastConvertionResult = NS_OK;
+ ClearFields();
+}
- mHttpChannel = nullptr;
- mStatus = PARSE_STATE_OFF;
+class CallRestartConnection final : public WorkerMainThreadRunnable
+{
+public:
+ explicit CallRestartConnection(EventSourceImpl* aEventSourceImpl)
+ : WorkerMainThreadRunnable(
+ aEventSourceImpl->mWorkerPrivate,
+ NS_LITERAL_CSTRING("EventSource :: RestartConnection"))
+ , mImpl(aEventSourceImpl)
+ {
+ mImpl->mWorkerPrivate->AssertIsOnWorkerThread();
+ }
- mReadyState = CONNECTING;
+ bool MainThreadRun() override
+ {
+ mImpl->RestartConnection();
+ return true;
+ }
+
+protected:
+ // Raw pointer because this runnable is sync.
+ EventSourceImpl* mImpl;
+};
+nsresult
+EventSourceImpl::RestartConnection()
+{
+ AssertIsOnMainThread();
+ nsresult rv = ResetConnection();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetReconnectionTimeout();
+ NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
void
-EventSource::ReestablishConnection()
+EventSourceImpl::ReestablishConnection()
{
- if (mReadyState == CLOSED) {
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
return;
}
- nsresult rv = ResetConnection();
- if (NS_FAILED(rv)) {
- NS_WARNING("Failed to reset the connection!!!");
- return;
+ nsresult rv;
+ if (mIsMainThread) {
+ rv = RestartConnection();
+ } else {
+ RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
+ ErrorResult result;
+ runnable->Dispatch(Terminating, result);
+ MOZ_ASSERT(!result.Failed());
+ rv = result.StealNSResult();
}
-
- rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
- RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
-
- // it doesn't bubble, and it isn't cancelable
- event->InitEvent(NS_LITERAL_STRING("error"), false, false);
- event->SetTrusted(true);
-
- rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+ rv = mEventSource->CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
- NS_WARNING("Failed to dispatch the error event!!!");
return;
}
- rv = SetReconnectionTimeout();
+ SetReadyState(CONNECTING);
+ ResetDecoder();
+ rv = mEventSource->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
if (NS_FAILED(rv)) {
- NS_WARNING("Failed to set the timeout for reestablishing the connection!!!");
+ NS_WARNING("Failed to dispatch the error event!!!");
return;
}
}
nsresult
-EventSource::SetReconnectionTimeout()
+EventSourceImpl::SetReconnectionTimeout()
{
- if (mReadyState == CLOSED) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
return NS_ERROR_ABORT;
}
@@ -817,11 +1217,12 @@ EventSource::SetReconnectionTimeout()
}
nsresult
-EventSource::PrintErrorOnConsole(const char *aBundleURI,
- const char16_t *aError,
- const char16_t **aFormatStrings,
- uint32_t aFormatStringsLen)
+EventSourceImpl::PrintErrorOnConsole(const char* aBundleURI,
+ const char16_t* aError,
+ const char16_t** aFormatStrings,
+ uint32_t aFormatStringsLen)
{
+ AssertIsOnMainThread();
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::services::GetStringBundleService();
NS_ENSURE_STATE(bundleService);
@@ -866,16 +1267,17 @@ EventSource::PrintErrorOnConsole(const char *aBundleURI,
}
nsresult
-EventSource::ConsoleError()
+EventSourceImpl::ConsoleError()
{
+ AssertIsOnMainThread();
nsAutoCString targetSpec;
nsresult rv = mSrc->GetSpec(targetSpec);
NS_ENSURE_SUCCESS(rv, rv);
NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
- const char16_t *formatStrings[] = { specUTF16.get() };
+ const char16_t* formatStrings[] = { specUTF16.get() };
- if (mReadyState == CONNECTING) {
+ if (ReadyState() == CONNECTING) {
rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
u"connectionFailure",
formatStrings, ArrayLength(formatStrings));
@@ -889,63 +1291,61 @@ EventSource::ConsoleError()
return NS_OK;
}
-nsresult
-EventSource::DispatchFailConnection()
-{
-
- return NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::FailConnection));
-}
-
void
-EventSource::FailConnection()
+EventSourceImpl::DispatchFailConnection()
{
- if (mReadyState == CLOSED) {
+ AssertIsOnMainThread();
+ if (IsClosed()) {
return;
}
-
nsresult rv = ConsoleError();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to print to the console error");
}
+ rv = Dispatch(NewRunnableMethod(this, &EventSourceImpl::FailConnection),
+ NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
- // When a user agent is to fail the connection, the user agent must set the
- // readyState attribute to CLOSED and queue a task to fire a simple event
- // named error at the EventSource object.
-
- Close(); // it sets mReadyState to CLOSED
-
- rv = CheckInnerWindowCorrectness();
- if (NS_FAILED(rv)) {
+void
+EventSourceImpl::FailConnection()
+{
+ AssertIsOnTargetThread();
+ if (IsClosed()) {
return;
}
-
- RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
-
- // it doesn't bubble, and it isn't cancelable
- event->InitEvent(NS_LITERAL_STRING("error"), false, false);
- event->SetTrusted(true);
-
- rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
- if (NS_FAILED(rv)) {
- NS_WARNING("Failed to dispatch the error event!!!");
- return;
+ // Must change state to closed before firing event to content.
+ SetReadyState(CLOSED);
+ // When a user agent is to fail the connection, the user agent must set the
+ // readyState attribute to CLOSED and queue a task to fire a simple event
+ // named error at the EventSource object.
+ nsresult rv = mEventSource->CheckInnerWindowCorrectness();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mEventSource->CreateAndDispatchSimpleEvent(NS_LITERAL_STRING("error"));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the error event!!!");
+ }
}
+ // Call CloseInternal in the end of function because it may release
+ // EventSourceImpl.
+ CloseInternal();
}
// static
void
-EventSource::TimerCallback(nsITimer* aTimer, void* aClosure)
+EventSourceImpl::TimerCallback(nsITimer* aTimer, void* aClosure)
{
- RefPtr<EventSource> thisObject = static_cast<EventSource*>(aClosure);
+ AssertIsOnMainThread();
+ RefPtr<EventSourceImpl> thisObject = static_cast<EventSourceImpl*>(aClosure);
- if (thisObject->mReadyState == CLOSED) {
+ if (thisObject->IsClosed()) {
return;
}
NS_PRECONDITION(!thisObject->mHttpChannel,
"the channel hasn't been cancelled!!");
- if (!thisObject->mFrozen) {
+ if (!thisObject->IsFrozen()) {
nsresult rv = thisObject->InitChannelAndRequestEventSource();
if (NS_FAILED(rv)) {
NS_WARNING("thisObject->InitChannelAndRequestEventSource() failed");
@@ -955,24 +1355,25 @@ EventSource::TimerCallback(nsITimer* aTimer, void* aClosure)
}
nsresult
-EventSource::Thaw()
+EventSourceImpl::Thaw()
{
- if (mReadyState == CLOSED || !mFrozen) {
+ AssertIsOnMainThread();
+ if (IsClosed() || !IsFrozen()) {
return NS_OK;
}
- NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
+ MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
- mFrozen = false;
+ SetFrozen(false);
nsresult rv;
if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
nsCOMPtr<nsIRunnable> event =
- NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
+ NewRunnableMethod(this, &EventSourceImpl::DispatchAllMessageEvents);
NS_ENSURE_STATE(event);
mGoingToDispatchAllMessages = true;
- rv = NS_DispatchToMainThread(event);
+ rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
}
@@ -983,20 +1384,22 @@ EventSource::Thaw()
}
nsresult
-EventSource::Freeze()
+EventSourceImpl::Freeze()
{
- if (mReadyState == CLOSED || mFrozen) {
+ AssertIsOnMainThread();
+ if (IsClosed() || IsFrozen()) {
return NS_OK;
}
- NS_ASSERTION(!mHttpChannel, "the connection hasn't been closed!!!");
- mFrozen = true;
+ MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
+ SetFrozen(true);
return NS_OK;
}
nsresult
-EventSource::DispatchCurrentMessageEvent()
+EventSourceImpl::DispatchCurrentMessageEvent()
{
+ AssertIsOnTargetThread();
nsAutoPtr<Message> message(new Message());
*message = mCurrentMessage;
@@ -1007,8 +1410,8 @@ EventSource::DispatchCurrentMessageEvent()
}
// removes the trailing LF from mData
- NS_ASSERTION(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
- "Invalid trailing character! LF was expected instead.");
+ MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
+ "Invalid trailing character! LF was expected instead.");
message->mData.SetLength(message->mData.Length() - 1);
if (message->mEventName.IsEmpty()) {
@@ -1027,34 +1430,42 @@ EventSource::DispatchCurrentMessageEvent()
if (!mGoingToDispatchAllMessages) {
nsCOMPtr<nsIRunnable> event =
- NewRunnableMethod(this, &EventSource::DispatchAllMessageEvents);
+ NewRunnableMethod(this, &EventSourceImpl::DispatchAllMessageEvents);
NS_ENSURE_STATE(event);
mGoingToDispatchAllMessages = true;
- return NS_DispatchToMainThread(event);
+ return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
}
return NS_OK;
}
void
-EventSource::DispatchAllMessageEvents()
+EventSourceImpl::DispatchAllMessageEvents()
{
- if (mReadyState == CLOSED || mFrozen) {
+ AssertIsOnTargetThread();
+ if (IsClosed() || IsFrozen()) {
return;
}
mGoingToDispatchAllMessages = false;
- nsresult rv = CheckInnerWindowCorrectness();
+ nsresult rv = mEventSource->CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
AutoJSAPI jsapi;
- if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
- return;
+ if (mIsMainThread) {
+ if (NS_WARN_IF(!jsapi.Init(mEventSource->GetOwner()))) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT(mWorkerPrivate);
+ if (NS_WARN_IF(!jsapi.Init(mWorkerPrivate->GlobalScope()))) {
+ return;
+ }
}
JSContext* cx = jsapi.cx();
@@ -1077,29 +1488,33 @@ EventSource::DispatchAllMessageEvents()
// create an event that uses the MessageEvent interface,
// which does not bubble, is not cancelable, and has no default action
- RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr);
+ RefPtr<MessageEvent> event = new MessageEvent(mEventSource, nullptr,
+ nullptr);
event->InitMessageEvent(nullptr, message->mEventName, false, false, jsData,
mOrigin, message->mLastEventID, nullptr,
Sequence<OwningNonNull<MessagePort>>());
event->SetTrusted(true);
- rv = DispatchDOMEvent(nullptr, static_cast<Event*>(event), nullptr,
- nullptr);
+ rv = mEventSource->DispatchDOMEvent(nullptr, static_cast<Event*>(event),
+ nullptr, nullptr);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the message event!!!");
return;
}
mLastEventID.Assign(message->mLastEventID);
+ if (IsClosed() || IsFrozen()) {
+ return;
+ }
}
}
nsresult
-EventSource::ClearFields()
+EventSourceImpl::ClearFields()
{
+ AssertIsOnTargetThread();
// mLastEventID and mReconnectionTime must be cached
-
mCurrentMessage.mEventName.Truncate();
mCurrentMessage.mLastEventID.Truncate();
mCurrentMessage.mData.Truncate();
@@ -1111,8 +1526,9 @@ EventSource::ClearFields()
}
nsresult
-EventSource::SetFieldAndClear()
+EventSourceImpl::SetFieldAndClear()
{
+ AssertIsOnTargetThread();
if (mLastFieldName.IsEmpty()) {
mLastFieldValue.Truncate();
return NS_OK;
@@ -1121,8 +1537,8 @@ EventSource::SetFieldAndClear()
char16_t first_char;
first_char = mLastFieldName.CharAt(0);
- switch (first_char) // with no case folding performed
- {
+ // with no case folding performed
+ switch (first_char) {
case char16_t('d'):
if (mLastFieldName.EqualsLiteral("data")) {
// If the field name is "data" append the field value to the data
@@ -1147,7 +1563,7 @@ EventSource::SetFieldAndClear()
case char16_t('r'):
if (mLastFieldName.EqualsLiteral("retry")) {
- uint32_t newValue=0;
+ uint32_t newValue = 0;
uint32_t i = 0; // we must ensure that there are only digits
bool assign = true;
for (i = 0; i < mLastFieldValue.Length(); ++i) {
@@ -1182,12 +1598,14 @@ EventSource::SetFieldAndClear()
}
nsresult
-EventSource::CheckHealthOfRequestCallback(nsIRequest *aRequestCallback)
+EventSourceImpl::CheckHealthOfRequestCallback(nsIRequest* aRequestCallback)
{
+ // This function could be run on target thread if http channel support
+ // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
+
// check if we have been closed or if the request has been canceled
// or if we have been frozen
- if (mReadyState == CLOSED || !mHttpChannel ||
- mFrozen || mErrorLoadOnRedirect) {
+ if (IsClosed() || IsFrozen() || !mHttpChannel) {
return NS_ERROR_ABORT;
}
@@ -1203,16 +1621,16 @@ EventSource::CheckHealthOfRequestCallback(nsIRequest *aRequestCallback)
}
nsresult
-EventSource::ParseCharacter(char16_t aChr)
+EventSourceImpl::ParseCharacter(char16_t aChr)
{
+ AssertIsOnTargetThread();
nsresult rv;
- if (mReadyState == CLOSED) {
+ if (IsClosed()) {
return NS_ERROR_ABORT;
}
- switch (mStatus)
- {
+ switch (mStatus) {
case PARSE_STATE_OFF:
NS_ERROR("Invalid state");
return NS_ERROR_FAILURE;
@@ -1351,5 +1769,330 @@ EventSource::ParseCharacter(char16_t aChr)
return NS_OK;
}
+void
+EventSourceImpl::AddRefObject()
+{
+ AddRef();
+}
+
+void
+EventSourceImpl::ReleaseObject()
+{
+ Release();
+}
+
+namespace {
+class EventSourceWorkerHolder final : public WorkerHolder
+{
+public:
+ explicit EventSourceWorkerHolder(EventSourceImpl* aEventSourceImpl)
+ : mEventSourceImpl(aEventSourceImpl)
+ {
+ }
+
+ bool Notify(Status aStatus) override
+ {
+ MOZ_ASSERT(aStatus > workers::Running);
+ if (aStatus >= Canceling) {
+ mEventSourceImpl->Close();
+ }
+ return true;
+ }
+
+private:
+ // Raw pointer because the EventSourceImpl object keeps alive the holder.
+ EventSourceImpl* mEventSourceImpl;
+};
+
+class WorkerRunnableDispatcher final : public WorkerRunnable
+{
+ RefPtr<EventSourceImpl> mEventSourceImpl;
+
+public:
+ WorkerRunnableDispatcher(EventSourceImpl* aImpl,
+ WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<nsIRunnable> aEvent)
+ : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ , mEventSourceImpl(aImpl)
+ , mEvent(Move(aEvent))
+ {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return !NS_FAILED(mEvent->Run());
+ }
+
+ void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override
+ {
+ }
+
+ bool PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ // We don't call WorkerRunnable::PreDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or
+ // transport thread).
+ return true;
+ }
+
+ void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override
+ {
+ // We don't call WorkerRunnable::PostDispatch because it would assert the
+ // wrong thing about which thread we're on. We're on whichever thread the
+ // channel implementation is running on (probably the main thread or
+ // transport thread).
+ }
+
+private:
+ nsCOMPtr<nsIRunnable> mEvent;
+};
+
+} // namespace
+
+bool EventSourceImpl::RegisterWorkerHolder()
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ MOZ_ASSERT(!mWorkerHolder);
+ mWorkerHolder = new EventSourceWorkerHolder(this);
+ if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
+ mWorkerHolder = nullptr;
+ return false;
+ }
+ return true;
+}
+
+void EventSourceImpl::UnregisterWorkerHolder()
+{
+ // RegisterWorkerHolder fail will destroy EventSourceImpl and invoke
+ // UnregisterWorkerHolder.
+ MOZ_ASSERT(IsClosed());
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ // The DTOR of this WorkerHolder will release the worker for us.
+ mWorkerHolder = nullptr;
+ mWorkerPrivate = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIEventTarget
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+ return Dispatch(event.forget(), aFlags);
+}
+
+NS_IMETHODIMP
+EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags)
+{
+ nsCOMPtr<nsIRunnable> event_ref(aEvent);
+ if (mIsMainThread) {
+ return NS_DispatchToMainThread(event_ref.forget());
+ }
+ MOZ_ASSERT(mWorkerPrivate);
+ // 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()) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// EventSourceImpl::nsIThreadRetargetableStreamListener
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+EventSourceImpl::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
+ return NS_OK;
+}
+////////////////////////////////////////////////////////////////////////////////
+// EventSource
+////////////////////////////////////////////////////////////////////////////////
+
+EventSource::EventSource(nsPIDOMWindowInner* aOwnerWindow,
+ bool aWithCredentials)
+ : DOMEventTargetHelper(aOwnerWindow)
+ , mWithCredentials(aWithCredentials)
+ , mIsMainThread(true)
+ , mKeepingAlive(false)
+{
+ mImpl = new EventSourceImpl(this);
+}
+
+EventSource::~EventSource()
+{
+}
+
+nsresult
+EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName)
+{
+ 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);
+}
+
+/* static */ already_AddRefed<EventSource>
+EventSource::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const EventSourceInit& aEventSourceInitDict,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+
+ MOZ_ASSERT(!NS_IsMainThread() ||
+ (ownerWindow && ownerWindow->IsInnerWindow()));
+
+ RefPtr<EventSource> eventSource =
+ new EventSource(ownerWindow, aEventSourceInitDict.mWithCredentials);
+ RefPtr<EventSourceImpl> eventSourceImp = eventSource->mImpl;
+
+ if (NS_IsMainThread()) {
+ // Get principal from document and init EventSourceImpl
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!scriptPrincipal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
+ if (!principal) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ eventSourceImp->Init(principal, aURL, aRv);
+ } else {
+ // In workers we have to keep the worker alive using a WorkerHolder in order
+ // to dispatch messages correctly.
+ if (!eventSourceImp->RegisterWorkerHolder()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ RefPtr<InitRunnable> runnable = new InitRunnable(eventSourceImp, aURL);
+ runnable->Dispatch(Terminating, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ aRv = runnable->ErrorCode();
+ }
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ return eventSource.forget();
+}
+
+// nsWrapperCache
+JSObject*
+EventSource::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return EventSourceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+EventSource::Close()
+{
+ AssertIsOnTargetThread();
+ if (mImpl) {
+ mImpl->Close();
+ }
+}
+
+void
+EventSource::UpdateMustKeepAlive()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mImpl);
+ if (mKeepingAlive) {
+ return;
+ }
+ mKeepingAlive = true;
+ mImpl->AddRefObject();
+}
+
+void
+EventSource::UpdateDontKeepAlive()
+{
+ // Here we could not have mImpl.
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+ if (mKeepingAlive) {
+ MOZ_ASSERT(mImpl);
+ mKeepingAlive = false;
+ mImpl->mEventSource = nullptr;
+ mImpl->ReleaseObject();
+ }
+ mImpl = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// EventSource::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(EventSource)
+ 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(EventSource)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(EventSource)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
+ DOMEventTargetHelper)
+ if (tmp->mImpl) {
+ tmp->mImpl->Close();
+ MOZ_ASSERT(!tmp->mImpl);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(EventSource)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/base/EventSource.h b/dom/base/EventSource.h
index b22cebd47a..cd43481908 100644
--- a/dom/base/EventSource.h
+++ b/dom/base/EventSource.h
@@ -35,221 +35,81 @@ namespace dom {
struct EventSourceInit;
+class EventSourceImpl;
+
class EventSource final : public DOMEventTargetHelper
- , public nsIObserver
- , public nsIStreamListener
- , public nsIChannelEventSink
- , public nsIInterfaceRequestor
- , public nsSupportsWeakReference
{
+ friend class EventSourceImpl;
public:
- explicit EventSource(nsPIDOMWindowInner* aOwnerWindow);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
EventSource, DOMEventTargetHelper)
- NS_DECL_NSIOBSERVER
- NS_DECL_NSISTREAMLISTENER
- NS_DECL_NSIREQUESTOBSERVER
- NS_DECL_NSICHANNELEVENTSINK
- NS_DECL_NSIINTERFACEREQUESTOR
+ // EventTarget
+ void DisconnectFromOwner() override
+ {
+ DOMEventTargetHelper::DisconnectFromOwner();
+ Close();
+ }
- // nsWrapperCache
- virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
// WebIDL
- nsPIDOMWindowInner*
- GetParentObject() const
- {
- return GetOwner();
- }
static already_AddRefed<EventSource>
Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
- const EventSourceInit& aEventSourceInitDict,
- ErrorResult& aRv);
+ const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv);
void GetUrl(nsAString& aURL) const
{
+ AssertIsOnTargetThread();
aURL = mOriginalURL;
}
+
bool WithCredentials() const
{
+ AssertIsOnTargetThread();
return mWithCredentials;
}
- enum {
- CONNECTING = 0U,
- OPEN = 1U,
- CLOSED = 2U
- };
uint16_t ReadyState() const
{
+ AssertIsOnTargetThread();
return mReadyState;
}
IMPL_EVENT_HANDLER(open)
IMPL_EVENT_HANDLER(message)
IMPL_EVENT_HANDLER(error)
- void Close();
- virtual void DisconnectFromOwner() override;
+ void Close();
-protected:
+private:
+ EventSource(nsPIDOMWindowInner* aOwnerWindow, bool aWithCredentials);
virtual ~EventSource();
+ // prevent bad usage
+ EventSource(const EventSource& x) = delete;
+ EventSource& operator=(const EventSource& x) = delete;
- MOZ_IS_CLASS_INIT
- nsresult Init(nsISupports* aOwner,
- const nsAString& aURL,
- bool aWithCredentials);
-
- nsresult GetBaseURI(nsIURI **aBaseURI);
-
- void SetupHttpChannel();
- nsresult SetupReferrerPolicy();
- nsresult InitChannelAndRequestEventSource();
- nsresult ResetConnection();
- nsresult DispatchFailConnection();
- nsresult SetReconnectionTimeout();
-
- void AnnounceConnection();
- void DispatchAllMessageEvents();
- void ReestablishConnection();
- void FailConnection();
-
- nsresult Thaw();
- nsresult Freeze();
-
- static void TimerCallback(nsITimer *aTimer, void *aClosure);
-
- nsresult PrintErrorOnConsole(const char *aBundleURI,
- const char16_t *aError,
- const char16_t **aFormatStrings,
- uint32_t aFormatStringsLen);
- nsresult ConsoleError();
-
- static nsresult StreamReaderFunc(nsIInputStream *aInputStream,
- void *aClosure,
- const char *aFromRawSegment,
- uint32_t aToOffset,
- uint32_t aCount,
- uint32_t *aWriteCount);
- nsresult SetFieldAndClear();
- nsresult ClearFields();
- nsresult ResetEvent();
- nsresult DispatchCurrentMessageEvent();
- nsresult ParseCharacter(char16_t aChr);
- nsresult CheckHealthOfRequestCallback(nsIRequest *aRequestCallback);
- nsresult OnRedirectVerifyCallback(nsresult result);
-
- nsCOMPtr<nsIURI> mSrc;
-
- nsString mLastEventID;
- uint32_t mReconnectionTime; // in ms
-
- struct Message {
- nsString mEventName;
- nsString mLastEventID;
- nsString mData;
- };
- nsDeque mMessagesToDispatch;
- Message mCurrentMessage;
-
- /**
- * A simple state machine used to manage the event-source's line buffer
- *
- * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
- *
- * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_BOM_WAS_READ |
- * PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE |
- * PARSE_STATE_COMMENT |
- * PARSE_STATE_FIELD_NAME
- *
- * PARSE_STATE_BOM_WAS_READ -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE |
- * PARSE_STATE_COMMENT |
- * PARSE_STATE_FIELD_NAME
- *
- * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_COMMENT |
- * PARSE_STATE_FIELD_NAME |
- * PARSE_STATE_BEGIN_OF_LINE
- *
- * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE
- *
- * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE |
- * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
- *
- * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
- * PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE
- *
- * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_BEGIN_OF_LINE
- *
- * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
- * PARSE_STATE_COMMENT |
- * PARSE_STATE_FIELD_NAME |
- * PARSE_STATE_BEGIN_OF_LINE
- *
- * Whenever the parser find an empty line or the end-of-file
- * it dispatches the stacked event.
- *
- */
- enum ParserStatus {
- PARSE_STATE_OFF,
- PARSE_STATE_BEGIN_OF_STREAM,
- PARSE_STATE_BOM_WAS_READ,
- PARSE_STATE_CR_CHAR,
- PARSE_STATE_COMMENT,
- PARSE_STATE_FIELD_NAME,
- PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
- PARSE_STATE_FIELD_VALUE,
- PARSE_STATE_BEGIN_OF_LINE
- };
- ParserStatus mStatus;
-
- bool mFrozen;
- bool mErrorLoadOnRedirect;
- bool mGoingToDispatchAllMessages;
- bool mWithCredentials;
- bool mWaitingForOnStopRequest;
-
- // used while reading the input streams
- nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
- nsresult mLastConvertionResult;
- nsString mLastFieldName;
- nsString mLastFieldValue;
-
- nsCOMPtr<nsILoadGroup> mLoadGroup;
-
- nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ void AssertIsOnTargetThread() const
+ {
+ MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+ }
- nsCOMPtr<nsITimer> mTimer;
+ nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
- uint16_t mReadyState;
+ // Raw pointer because this EventSourceImpl is created, managed and destroyed
+ // by EventSource.
+ EventSourceImpl* mImpl;
nsString mOriginalURL;
+ uint16_t mReadyState;
+ bool mWithCredentials;
+ bool mIsMainThread;
+ // This is used to keep EventSourceImpl instance when there is a connection.
+ bool mKeepingAlive;
- nsCOMPtr<nsIPrincipal> mPrincipal;
- nsString mOrigin;
-
- // Event Source owner information:
- // - the script file name
- // - source code line number and column number where the Event Source object
- // was constructed.
- // - the ID of the inner window where the script lives. Note that this may not
- // be the same as the Event Source owner window.
- // These attributes are used for error reporting.
- nsString mScriptFile;
- uint32_t mScriptLine;
- uint32_t mScriptColumn;
- uint64_t mInnerWindowID;
-
-private:
- EventSource(const EventSource& x); // prevent bad usage
- EventSource& operator=(const EventSource& x);
+ void UpdateMustKeepAlive();
+ void UpdateDontKeepAlive();
};
} // namespace dom
diff --git a/dom/webidl/EventSource.webidl b/dom/webidl/EventSource.webidl
index 1e8e9978e5..f6d54c3bd9 100644
--- a/dom/webidl/EventSource.webidl
+++ b/dom/webidl/EventSource.webidl
@@ -11,7 +11,8 @@
* and create derivative works of this document.
*/
-[Constructor(USVString url, optional EventSourceInit eventSourceInitDict)]
+[Exposed=(Window,DedicatedWorker,SharedWorker),
+ Constructor(USVString url, optional EventSourceInit eventSourceInitDict)]
interface EventSource : EventTarget {
[Constant]
readonly attribute DOMString url;