diff options
author | Brian Smith <brian@dbsoft.org> | 2022-08-18 16:30:38 -0500 |
---|---|---|
committer | Brian Smith <brian@dbsoft.org> | 2022-08-18 16:30:38 -0500 |
commit | b52731f9e00920ad9e750069bf5e2f4547cb0893 (patch) | |
tree | 65a756bf799aa70f09286b0a93fd08a8cd0edc8d /dom/base/EventSource.cpp | |
parent | 544366e3010ea16601ff363a2f41df5f84f77d47 (diff) | |
download | uxp-b52731f9e00920ad9e750069bf5e2f4547cb0893.tar.gz |
Issue #1990 - Part 1 - EventSource for workers - Mozilla Bug 1267903
Diffstat (limited to 'dom/base/EventSource.cpp')
-rw-r--r-- | dom/base/EventSource.cpp | 1457 |
1 files changed, 1100 insertions, 357 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 |