forked from mirrors/gecko-dev
		
	 5b2ada26c5
			
		
	
	
		5b2ada26c5
		
	
	
	
	
		
			
			Tasks registered with shutdown tasks are called when the target nsISerialEventTarget is about to be destroyed. Differential Revision: https://phabricator.services.mozilla.com/D129840
		
			
				
	
	
		
			2140 lines
		
	
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2140 lines
		
	
	
	
		
			65 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/Components.h"
 | |
| #include "mozilla/DataMutex.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/LoadInfo.h"
 | |
| #include "mozilla/DOMEventTargetHelper.h"
 | |
| #include "mozilla/dom/EventSource.h"
 | |
| #include "mozilla/dom/EventSourceBinding.h"
 | |
| #include "mozilla/dom/MessageEvent.h"
 | |
| #include "mozilla/dom/MessageEventBinding.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "mozilla/dom/WorkerPrivate.h"
 | |
| #include "mozilla/dom/WorkerRef.h"
 | |
| #include "mozilla/dom/WorkerRunnable.h"
 | |
| #include "mozilla/dom/WorkerScope.h"
 | |
| #include "mozilla/dom/EventSourceEventService.h"
 | |
| #include "mozilla/ScopeExit.h"
 | |
| #include "mozilla/UniquePtrExtensions.h"
 | |
| #include "nsComponentManagerUtils.h"
 | |
| #include "nsIThreadRetargetableStreamListener.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsIAuthPrompt.h"
 | |
| #include "nsIAuthPrompt2.h"
 | |
| #include "nsIHttpChannel.h"
 | |
| #include "nsIInputStream.h"
 | |
| #include "nsIInterfaceRequestorUtils.h"
 | |
| #include "nsMimeTypes.h"
 | |
| #include "nsIPromptFactory.h"
 | |
| #include "nsIWindowWatcher.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsProxyRelease.h"
 | |
| #include "nsContentPolicyUtils.h"
 | |
| #include "nsIStringBundle.h"
 | |
| #include "nsIConsoleService.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsIScriptObjectPrincipal.h"
 | |
| #include "nsJSUtils.h"
 | |
| #include "nsIThreadRetargetableRequest.h"
 | |
| #include "nsIAsyncVerifyRedirectCallback.h"
 | |
| #include "nsIScriptError.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "xpcpublic.h"
 | |
| #include "nsWrapperCacheInlines.h"
 | |
| #include "mozilla/Attributes.h"
 | |
| #include "nsError.h"
 | |
| #include "mozilla/Encoding.h"
 | |
| #include "ReferrerInfo.h"
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| #ifdef DEBUG
 | |
| static LazyLogModule gEventSourceLog("EventSource");
 | |
| #endif
 | |
| 
 | |
| #define SPACE_CHAR (char16_t)0x0020
 | |
| #define CR_CHAR (char16_t)0x000D
 | |
| #define LF_CHAR (char16_t)0x000A
 | |
| #define COLON_CHAR (char16_t)0x003A
 | |
| 
 | |
| // 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)
 | |
| 
 | |
| class EventSourceImpl final : public nsIObserver,
 | |
|                               public nsIStreamListener,
 | |
|                               public nsIChannelEventSink,
 | |
|                               public nsIInterfaceRequestor,
 | |
|                               public nsSupportsWeakReference,
 | |
|                               public nsIEventTarget,
 | |
|                               public nsITimerCallback,
 | |
|                               public nsINamed,
 | |
|                               public nsIThreadRetargetableStreamListener {
 | |
|  public:
 | |
|   NS_DECL_THREADSAFE_ISUPPORTS
 | |
|   NS_DECL_NSIOBSERVER
 | |
|   NS_DECL_NSIREQUESTOBSERVER
 | |
|   NS_DECL_NSISTREAMLISTENER
 | |
|   NS_DECL_NSICHANNELEVENTSINK
 | |
|   NS_DECL_NSIINTERFACEREQUESTOR
 | |
|   NS_DECL_NSIEVENTTARGET_FULL
 | |
|   NS_DECL_NSITIMERCALLBACK
 | |
|   NS_DECL_NSINAMED
 | |
|   NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
 | |
| 
 | |
|   EventSourceImpl(EventSource* aEventSource,
 | |
|                   nsICookieJarSettings* aCookieJarSettings);
 | |
| 
 | |
|   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 SetupReferrerInfo(const nsCOMPtr<Document>& aDocument);
 | |
|   nsresult InitChannelAndRequestEventSource(bool aEventTargetAccessAllowed);
 | |
|   nsresult ResetConnection();
 | |
|   void ResetDecoder();
 | |
|   nsresult SetReconnectionTimeout();
 | |
| 
 | |
|   void AnnounceConnection();
 | |
|   void DispatchAllMessageEvents();
 | |
|   nsresult RestartConnection();
 | |
|   void ReestablishConnection();
 | |
|   void DispatchFailConnection();
 | |
|   void FailConnection();
 | |
| 
 | |
|   nsresult Thaw();
 | |
|   nsresult Freeze();
 | |
| 
 | |
|   nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
 | |
|                                const nsTArray<nsString>& aFormatStrings);
 | |
|   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();
 | |
|   void 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();
 | |
| 
 | |
|   bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
 | |
|   void ReleaseWorkerRef();
 | |
| 
 | |
|   void AssertIsOnTargetThread() const {
 | |
|     MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
 | |
|   }
 | |
| 
 | |
|   bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
 | |
| 
 | |
|   uint16_t ReadyState() {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     if (lock->mEventSource) {
 | |
|       return lock->mEventSource->mReadyState;
 | |
|     }
 | |
|     // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
 | |
|     // means that the EventSource has been closed.
 | |
|     return CLOSED;
 | |
|   }
 | |
| 
 | |
|   void SetReadyState(uint16_t aReadyState) {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     MOZ_ASSERT(lock->mEventSource);
 | |
|     MOZ_ASSERT(!mIsShutDown);
 | |
|     lock->mEventSource->mReadyState = aReadyState;
 | |
|   }
 | |
| 
 | |
|   bool IsClosed() { return ReadyState() == CLOSED; }
 | |
| 
 | |
|   RefPtr<EventSource> GetEventSource() {
 | |
|     AssertIsOnTargetThread();
 | |
|     auto lock = mSharedData.Lock();
 | |
|     return lock->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_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_CR_CHAR,
 | |
|     PARSE_STATE_COMMENT,
 | |
|     PARSE_STATE_FIELD_NAME,
 | |
|     PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
 | |
|     PARSE_STATE_FIELD_VALUE,
 | |
|     PARSE_STATE_IGNORE_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;
 | |
|     // We need to be able to distinguish between different states of id field:
 | |
|     // 1) is not given at all
 | |
|     // 2) is given but is empty
 | |
|     // 3) is given and has a value
 | |
|     // We can't check for the 1st state with a simple nsString.
 | |
|     Maybe<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;
 | |
|   UniquePtr<Message> mCurrentMessage;
 | |
|   nsDeque<Message> mMessagesToDispatch;
 | |
|   ParserStatus mStatus;
 | |
|   mozilla::UniquePtr<mozilla::Decoder> mUnicodeDecoder;
 | |
|   nsString mLastFieldName;
 | |
|   nsString mLastFieldValue;
 | |
| 
 | |
|   // EventSourceImpl internal states.
 | |
|   // WorkerRef to keep the worker alive. (accessed on worker thread only)
 | |
|   RefPtr<ThreadSafeWorkerRef> mWorkerRef;
 | |
|   // Whether the window is frozen. May be set on main thread and read on target
 | |
|   // thread.
 | |
|   Atomic<bool> mFrozen;
 | |
|   // There are some messages are going to be dispatched when thaw.
 | |
|   bool mGoingToDispatchAllMessages;
 | |
|   // Whether the EventSource is run on main thread.
 | |
|   const bool mIsMainThread;
 | |
|   // Whether the EventSourceImpl is going to be destroyed.
 | |
|   Atomic<bool> mIsShutDown;
 | |
| 
 | |
|   class EventSourceServiceNotifier final {
 | |
|    public:
 | |
|     EventSourceServiceNotifier(RefPtr<EventSourceImpl>&& aEventSourceImpl,
 | |
|                                uint64_t aHttpChannelId, uint64_t aInnerWindowID)
 | |
|         : mEventSourceImpl(std::move(aEventSourceImpl)),
 | |
|           mHttpChannelId(aHttpChannelId),
 | |
|           mInnerWindowID(aInnerWindowID),
 | |
|           mConnectionOpened(false) {
 | |
|       AssertIsOnMainThread();
 | |
|       mService = EventSourceEventService::GetOrCreate();
 | |
|     }
 | |
| 
 | |
|     void ConnectionOpened() {
 | |
|       mEventSourceImpl->AssertIsOnTargetThread();
 | |
|       mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
 | |
|       mConnectionOpened = true;
 | |
|     }
 | |
| 
 | |
|     void EventReceived(const nsAString& aEventName,
 | |
|                        const nsAString& aLastEventID, const nsAString& aData,
 | |
|                        uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
 | |
|       mEventSourceImpl->AssertIsOnTargetThread();
 | |
|       mService->EventReceived(mHttpChannelId, mInnerWindowID, aEventName,
 | |
|                               aLastEventID, aData, aRetry, aTimeStamp);
 | |
|     }
 | |
| 
 | |
|     ~EventSourceServiceNotifier() {
 | |
|       // It is safe to call this on any thread because
 | |
|       // EventSourceConnectionClosed method is thread safe and
 | |
|       // NS_ReleaseOnMainThread explicitly releases the service on the main
 | |
|       // thread.
 | |
|       if (mConnectionOpened) {
 | |
|         // We want to notify about connection being closed only if we told
 | |
|         // it was ever opened. The check is needed if OnStartRequest is called
 | |
|         // on the main thread while close() is called on a worker thread.
 | |
|         mService->EventSourceConnectionClosed(mHttpChannelId, mInnerWindowID);
 | |
|       }
 | |
|       NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
 | |
|                              mService.forget());
 | |
|     }
 | |
| 
 | |
|    private:
 | |
|     RefPtr<EventSourceEventService> mService;
 | |
|     RefPtr<EventSourceImpl> mEventSourceImpl;
 | |
|     uint64_t mHttpChannelId;
 | |
|     uint64_t mInnerWindowID;
 | |
|     bool mConnectionOpened;
 | |
|   };
 | |
| 
 | |
|   struct SharedData {
 | |
|     RefPtr<EventSource> mEventSource;
 | |
|     UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
 | |
|   };
 | |
| 
 | |
|   DataMutex<SharedData> mSharedData;
 | |
| 
 | |
|   // 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:
 | |
|   nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
 | |
| 
 | |
|   // Pointer to the target thread for checking whether we are
 | |
|   // on the target thread. This is intentionally a non-owning
 | |
|   // pointer in order not to affect the thread destruction
 | |
|   // sequence. This pointer must only be compared for equality
 | |
|   // and must not be dereferenced.
 | |
|   nsIThread* mTargetThread;
 | |
| 
 | |
|   // 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,
 | |
|                   nsITimerCallback, nsINamed)
 | |
| 
 | |
| EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
 | |
|                                  nsICookieJarSettings* aCookieJarSettings)
 | |
|     : mReconnectionTime(0),
 | |
|       mStatus(PARSE_STATE_OFF),
 | |
|       mFrozen(false),
 | |
|       mGoingToDispatchAllMessages(false),
 | |
|       mIsMainThread(NS_IsMainThread()),
 | |
|       mIsShutDown(false),
 | |
|       mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
 | |
|       mScriptLine(0),
 | |
|       mScriptColumn(0),
 | |
|       mInnerWindowID(0),
 | |
|       mCookieJarSettings(aCookieJarSettings),
 | |
|       mTargetThread(NS_GetCurrentThread()) {
 | |
|   MOZ_ASSERT(aEventSource);
 | |
|   SetReadyState(CONNECTING);
 | |
| }
 | |
| 
 | |
| class CleanupRunnable final : public WorkerMainThreadRunnable {
 | |
|  public:
 | |
|   explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
 | |
|       : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
 | |
|                                  "EventSource :: Cleanup"_ns),
 | |
|         mESImpl(std::move(aEventSourceImpl)) {
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|     mWorkerPrivate->AssertIsOnWorkerThread();
 | |
|   }
 | |
| 
 | |
|   bool MainThreadRun() override {
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|     mESImpl->CleanupOnMainThread();
 | |
|     // We want to ensure the shortest possible remaining lifetime
 | |
|     // and not depend on the Runnable's destruction.
 | |
|     mESImpl = nullptr;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   RefPtr<EventSourceImpl> mESImpl;
 | |
| };
 | |
| 
 | |
| void EventSourceImpl::Close() {
 | |
|   if (IsClosed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SetReadyState(CLOSED);
 | |
|   // CloseInternal potentially kills ourself, ensure
 | |
|   // to not access any members afterwards.
 | |
|   CloseInternal();
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::CloseInternal() {
 | |
|   AssertIsOnTargetThread();
 | |
|   MOZ_ASSERT(IsClosed());
 | |
| 
 | |
|   RefPtr<EventSource> myES;
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     // We want to ensure to release ourself even if we have
 | |
|     // the shutdown case, thus we put aside a pointer
 | |
|     // to the EventSource and null it out right now.
 | |
|     myES = std::move(lock->mEventSource);
 | |
|     lock->mEventSource = nullptr;
 | |
|     lock->mServiceNotifier = nullptr;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   if (mIsShutDown) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Invoke CleanupOnMainThread before cleaning any members. It will call
 | |
|   // ShutDown, which is supposed to be called before cleaning any members.
 | |
|   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());
 | |
|     ReleaseWorkerRef();
 | |
|   }
 | |
| 
 | |
|   while (mMessagesToDispatch.GetSize() != 0) {
 | |
|     delete mMessagesToDispatch.PopFront();
 | |
|   }
 | |
|   mFrozen = false;
 | |
|   ResetDecoder();
 | |
|   mUnicodeDecoder = nullptr;
 | |
|   // Release the object on its owner. Don't access to any members
 | |
|   // after it.
 | |
|   myES->mESImpl = nullptr;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::CleanupOnMainThread() {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(IsClosed());
 | |
| 
 | |
|   // Call ShutDown before cleaning any members.
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   mIsShutDown = true;
 | |
| 
 | |
|   if (mIsMainThread) {
 | |
|     RemoveWindowObservers();
 | |
|   }
 | |
| 
 | |
|   if (mTimer) {
 | |
|     mTimer->Cancel();
 | |
|     mTimer = nullptr;
 | |
|   }
 | |
| 
 | |
|   ResetConnection();
 | |
|   mPrincipal = nullptr;
 | |
|   mSrc = nullptr;
 | |
| }
 | |
| 
 | |
| class InitRunnable final : public WorkerMainThreadRunnable {
 | |
|  public:
 | |
|   InitRunnable(WorkerPrivate* aWorkerPrivate,
 | |
|                RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
 | |
|       : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
 | |
|         mESImpl(std::move(aEventSourceImpl)),
 | |
|         mURL(aURL),
 | |
|         mRv(NS_ERROR_NOT_INITIALIZED) {
 | |
|     MOZ_ASSERT(aWorkerPrivate);
 | |
|     aWorkerPrivate->AssertIsOnWorkerThread();
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|   }
 | |
| 
 | |
|   bool MainThreadRun() override {
 | |
|     // Get principal from worker's owner document or from worker.
 | |
|     WorkerPrivate* wp = mWorkerPrivate;
 | |
|     while (wp->GetParent()) {
 | |
|       wp = wp->GetParent();
 | |
|     }
 | |
|     nsPIDOMWindowInner* window = wp->GetWindow();
 | |
|     Document* doc = window ? window->GetExtantDoc() : nullptr;
 | |
|     nsCOMPtr<nsIPrincipal> principal =
 | |
|         doc ? doc->NodePrincipal() : wp->GetPrincipal();
 | |
|     if (!principal) {
 | |
|       mRv = NS_ERROR_FAILURE;
 | |
|       return true;
 | |
|     }
 | |
|     ErrorResult rv;
 | |
|     mESImpl->Init(principal, mURL, rv);
 | |
|     mRv = rv.StealNSResult();
 | |
| 
 | |
|     // We want to ensure that EventSourceImpl's lifecycle
 | |
|     // does not depend on this Runnable's one.
 | |
|     mESImpl = nullptr;
 | |
| 
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   nsresult ErrorCode() const { return mRv; }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<EventSourceImpl> mESImpl;
 | |
|   const nsAString& mURL;
 | |
|   nsresult mRv;
 | |
| };
 | |
| 
 | |
| class ConnectRunnable final : public WorkerMainThreadRunnable {
 | |
|  public:
 | |
|   explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
 | |
|                            RefPtr<EventSourceImpl> aEventSourceImpl)
 | |
|       : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
 | |
|         mESImpl(std::move(aEventSourceImpl)) {
 | |
|     MOZ_ASSERT(aWorkerPrivate);
 | |
|     aWorkerPrivate->AssertIsOnWorkerThread();
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|   }
 | |
| 
 | |
|   bool MainThreadRun() override {
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|     // We are allowed to access the event target since this runnable is
 | |
|     // synchronized with the thread the event target lives on.
 | |
|     mESImpl->InitChannelAndRequestEventSource(true);
 | |
|     // We want to ensure the shortest possible remaining lifetime
 | |
|     // and not depend on the Runnable's destruction.
 | |
|     mESImpl = nullptr;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<EventSourceImpl> mESImpl;
 | |
| };
 | |
| 
 | |
| nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   // get the src
 | |
|   nsCOMPtr<nsIURI> baseURI;
 | |
|   nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIURI> srcURI;
 | |
|   rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
 | |
|   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
 | |
| 
 | |
|   nsAutoString origin;
 | |
|   rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsAutoCString spec;
 | |
|   rv = srcURI->GetSpec(spec);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // This assignment doesn't require extra synchronization because this function
 | |
|   // is only ever called from EventSourceImpl::Init(), which is either called
 | |
|   // directly if mEventSource was created on the main thread, or via a
 | |
|   // synchronous runnable if it was created on a worker thread.
 | |
|   {
 | |
|     // We can't use GetEventSource() here because it would modify the refcount,
 | |
|     // and that's not allowed off the owning thread.
 | |
|     auto lock = mSharedData.Lock();
 | |
|     lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
 | |
|   }
 | |
|   mSrc = srcURI;
 | |
|   mOrigin = origin;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::AddWindowObservers() {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(mIsMainThread);
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
 | |
|   NS_ENSURE_STATE(os);
 | |
| 
 | |
|   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;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::RemoveWindowObservers() {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(mIsMainThread);
 | |
|   MOZ_ASSERT(IsClosed());
 | |
|   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);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
 | |
|                            ErrorResult& aRv) {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(aPrincipal);
 | |
|   MOZ_ASSERT(ReadyState() == CONNECTING);
 | |
|   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);
 | |
|   }
 | |
| 
 | |
|   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",
 | |
|                           DEFAULT_RECONNECTION_TIME_VALUE);
 | |
| 
 | |
|   mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // EventSourceImpl::nsIObserver
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                          const char16_t* aData) {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
 | |
|   MOZ_ASSERT(mIsMainThread);
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     if (!lock->mEventSource->GetOwner() ||
 | |
|         window != lock->mEventSource->GetOwner()) {
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DebugOnly<nsresult> rv;
 | |
|   if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
 | |
|     rv = Freeze();
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
 | |
|   } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
 | |
|     rv = Thaw();
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
 | |
|   } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
 | |
|     Close();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // EventSourceImpl::nsIStreamListener
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
|   nsresult rv = CheckHealthOfRequestCallback(aRequest);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsresult status;
 | |
|   rv = aRequest->GetStatus(&status);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (NS_FAILED(status)) {
 | |
|     // EventSource::OnStopRequest will evaluate if it shall either reestablish
 | |
|     // or fail the connection
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   uint32_t httpStatus;
 | |
|   rv = httpChannel->GetResponseStatus(&httpStatus);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (httpStatus != 200) {
 | |
|     DispatchFailConnection();
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString contentType;
 | |
|   rv = httpChannel->GetContentType(contentType);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
 | |
|     DispatchFailConnection();
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   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);
 | |
|       if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|         NS_WARNING("Retargeting failed");
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
 | |
|         this, mHttpChannel->ChannelId(), mInnerWindowID);
 | |
|   }
 | |
|   rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
 | |
|                                   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 EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
 | |
|                                            void* aClosure,
 | |
|                                            const char* aFromRawSegment,
 | |
|                                            uint32_t aToOffset, uint32_t aCount,
 | |
|                                            uint32_t* aWriteCount) {
 | |
|   // The EventSourceImpl instance is hold alive on the
 | |
|   // synchronously calling stack, so raw pointer is fine here.
 | |
|   EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
 | |
|   if (!thisObject || !aWriteCount) {
 | |
|     NS_WARNING(
 | |
|         "EventSource cannot read from stream: no aClosure or aWriteCount");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
|   thisObject->AssertIsOnTargetThread();
 | |
|   MOZ_ASSERT(!thisObject->mIsShutDown);
 | |
|   thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
 | |
|   *aWriteCount = aCount;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (IsClosed()) {
 | |
|     return;
 | |
|   }
 | |
|   char16_t buffer[1024];
 | |
|   auto dst = Span(buffer);
 | |
|   auto src = AsBytes(Span(aBuffer, aLength));
 | |
|   // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
 | |
|   for (;;) {
 | |
|     uint32_t result;
 | |
|     size_t read;
 | |
|     size_t written;
 | |
|     std::tie(result, read, written, std::ignore) =
 | |
|         mUnicodeDecoder->DecodeToUTF16(src, dst, false);
 | |
|     for (auto c : dst.To(written)) {
 | |
|       nsresult rv = ParseCharacter(c);
 | |
|       NS_ENSURE_SUCCESS_VOID(rv);
 | |
|     }
 | |
|     if (result == kInputEmpty) {
 | |
|       return;
 | |
|     }
 | |
|     src = src.From(read);
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
 | |
|                                  nsIInputStream* aInputStream, uint64_t aOffset,
 | |
|                                  uint32_t aCount) {
 | |
|   AssertIsOnTargetThread();
 | |
|   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(EventSourceImpl::StreamReaderFunc, this,
 | |
|                                     aCount, &totalRead);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
 | |
|   AssertIsOnMainThread();
 | |
| 
 | |
|   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.
 | |
|   //
 | |
|   //  (...) the cancelation of the fetch algorithm by the user agent (e.g. in
 | |
|   //  response to window.stop() or the user canceling the network connection
 | |
|   //  manually) must cause the user agent to fail the connection.
 | |
| 
 | |
|   if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
 | |
|       aStatusCode != NS_ERROR_NET_TIMEOUT &&
 | |
|       aStatusCode != NS_ERROR_NET_RESET &&
 | |
|       aStatusCode != NS_ERROR_NET_INTERRUPT &&
 | |
|       aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
 | |
|       aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
 | |
|     DispatchFailConnection();
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = CheckHealthOfRequestCallback(aRequest);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv =
 | |
|       Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
 | |
|                                  this, &EventSourceImpl::ReestablishConnection),
 | |
|                NS_DISPATCH_NORMAL);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // EventSourceImpl::nsIChannelEventSink
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::AsyncOnChannelRedirect(
 | |
|     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
 | |
|     nsIAsyncVerifyRedirectCallback* aCallback) {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
|   nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
 | |
|   MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
 | |
| 
 | |
|   nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
 | |
| 
 | |
|   nsCOMPtr<nsIURI> newURI;
 | |
|   rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
 | |
| 
 | |
|   rv =
 | |
|       mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
 | |
|   if (NS_FAILED(rv) || !isValidScheme) {
 | |
|     DispatchFailConnection();
 | |
|     return NS_ERROR_DOM_SECURITY_ERR;
 | |
|   }
 | |
| 
 | |
|   // update our channel
 | |
| 
 | |
|   mHttpChannel = do_QueryInterface(aNewChannel);
 | |
|   NS_ENSURE_STATE(mHttpChannel);
 | |
| 
 | |
|   SetupHttpChannel();
 | |
|   // The HTTP impl already copies over the referrer info on
 | |
|   // redirects, so we don't need to SetupReferrerInfo().
 | |
| 
 | |
|   if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
 | |
|     rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   aCallback->OnRedirectVerifyCallback(NS_OK);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // EventSourceImpl::nsIInterfaceRequestor
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
 | |
|   AssertIsOnMainThread();
 | |
| 
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
 | |
|     *aResult = static_cast<nsIChannelEventSink*>(this);
 | |
|     NS_ADDREF_THIS();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
 | |
|       aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
 | |
|     nsresult rv;
 | |
|     nsCOMPtr<nsIPromptFactory> wwatch =
 | |
|         do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|     nsCOMPtr<nsPIDOMWindowOuter> window;
 | |
| 
 | |
|     // To avoid a data race we may only access the event target if it lives on
 | |
|     // the main thread.
 | |
|     if (mIsMainThread) {
 | |
|       auto lock = mSharedData.Lock();
 | |
|       rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
 | |
|       NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
 | |
| 
 | |
|       if (lock->mEventSource->GetOwner()) {
 | |
|         window = lock->mEventSource->GetOwner()->GetOuterWindow();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Get the an auth prompter for our window so that the parenting
 | |
|     // of the dialogs works as it should when using tabs.
 | |
| 
 | |
|     return wwatch->GetPrompt(window, aIID, aResult);
 | |
|   }
 | |
| 
 | |
|   return QueryInterface(aIID, aResult);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::IsOnCurrentThread(bool* aResult) {
 | |
|   *aResult = IsTargetThread();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP_(bool)
 | |
| EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
 | |
| 
 | |
| nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   NS_ENSURE_ARG_POINTER(aBaseURI);
 | |
| 
 | |
|   *aBaseURI = nullptr;
 | |
| 
 | |
|   nsCOMPtr<nsIURI> baseURI;
 | |
| 
 | |
|   // first we try from document->GetBaseURI()
 | |
|   nsCOMPtr<Document> doc =
 | |
|       mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
 | |
|   if (doc) {
 | |
|     baseURI = doc->GetBaseURI();
 | |
|   }
 | |
| 
 | |
|   // otherwise we get from the doc's principal
 | |
|   if (!baseURI) {
 | |
|     auto* basePrin = BasePrincipal::Cast(mPrincipal);
 | |
|     nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   NS_ENSURE_STATE(baseURI);
 | |
| 
 | |
|   baseURI.forget(aBaseURI);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::SetupHttpChannel() {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
| 
 | |
|   /* set the http request headers */
 | |
| 
 | |
|   rv = mHttpChannel->SetRequestHeader(
 | |
|       "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
| 
 | |
|   // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
 | |
| 
 | |
|   if (mLastEventID.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
|   NS_ConvertUTF16toUTF8 eventId(mLastEventID);
 | |
|   rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
 | |
| #ifdef DEBUG
 | |
|   if (NS_FAILED(rv)) {
 | |
|     MOZ_LOG(gEventSourceLog, LogLevel::Warning,
 | |
|             ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
 | |
|   }
 | |
| #endif
 | |
|   Unused << rv;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::SetupReferrerInfo(
 | |
|     const nsCOMPtr<Document>& aDocument) {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
| 
 | |
|   if (aDocument) {
 | |
|     auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
 | |
|     nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::InitChannelAndRequestEventSource(
 | |
|     const bool aEventTargetAccessAllowed) {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
 | |
| 
 | |
|   MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
 | |
| 
 | |
|   nsresult rv = aEventTargetAccessAllowed
 | |
|                     ? [this]() {
 | |
|                         // We can't call GetEventSource() because we're not
 | |
|                         // allowed to touch the refcount off the worker thread
 | |
|                         // due to an assertion, event if it would have otherwise
 | |
|                         // been safe.
 | |
|                         auto lock = mSharedData.Lock();
 | |
|                         return lock->mEventSource->CheckCurrentGlobalCorrectness();
 | |
|                       }()
 | |
|                     : NS_OK;
 | |
|   if (NS_FAILED(rv) || !isValidScheme) {
 | |
|     DispatchFailConnection();
 | |
|     return NS_ERROR_DOM_SECURITY_ERR;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Document> doc;
 | |
|   nsSecurityFlags securityFlags =
 | |
|       nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
 | |
|                                     : nullptr;
 | |
| 
 | |
|     if (lock->mEventSource->mWithCredentials) {
 | |
|       securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // The html spec requires we use fetch cache mode of "no-store".  This
 | |
|   // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
 | |
|   nsLoadFlags loadFlags;
 | |
|   loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
 | |
|               nsIRequest::INHIBIT_CACHING;
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> channel;
 | |
|   // If we have the document, use it
 | |
|   if (doc) {
 | |
|     MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
 | |
| 
 | |
|     nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
 | |
|     rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
 | |
|                        nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
 | |
|                        nullptr,  // aPerformanceStorage
 | |
|                        loadGroup,
 | |
|                        nullptr,     // aCallbacks
 | |
|                        loadFlags);  // aLoadFlags
 | |
|   } else {
 | |
|     // otherwise use the principal
 | |
|     rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
 | |
|                        nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
 | |
|                        mCookieJarSettings,
 | |
|                        nullptr,     // aPerformanceStorage
 | |
|                        nullptr,     // loadGroup
 | |
|                        nullptr,     // aCallbacks
 | |
|                        loadFlags);  // aLoadFlags
 | |
|   }
 | |
| 
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   mHttpChannel = do_QueryInterface(channel);
 | |
|   NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
 | |
| 
 | |
|   SetupHttpChannel();
 | |
|   rv = SetupReferrerInfo(doc);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
| #ifdef DEBUG
 | |
|   {
 | |
|     nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
 | |
|     mHttpChannel->GetNotificationCallbacks(
 | |
|         getter_AddRefs(notificationCallbacks));
 | |
|     MOZ_ASSERT(!notificationCallbacks);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   mHttpChannel->SetNotificationCallbacks(this);
 | |
| 
 | |
|   // Start reading from the channel
 | |
|   rv = mHttpChannel->AsyncOpen(this);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     DispatchFailConnection();
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::AnnounceConnection() {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (ReadyState() != CONNECTING) {
 | |
|     NS_WARNING("Unexpected mReadyState!!!");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     if (lock->mServiceNotifier) {
 | |
|       lock->mServiceNotifier->ConnectionOpened();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // When a user agent is to announce the connection, the user agent must set
 | |
|   // the readyState attribute to OPEN and queue a task to fire a simple event
 | |
|   // named open at the EventSource object.
 | |
| 
 | |
|   SetReadyState(OPEN);
 | |
| 
 | |
|   nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
|   // We can't hold the mutex while dispatching the event because the mutex is
 | |
|   // not reentrant, and content might call back into our code.
 | |
|   rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     NS_WARNING("Failed to dispatch the error event!!!");
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::ResetConnection() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (mHttpChannel) {
 | |
|     mHttpChannel->Cancel(NS_ERROR_ABORT);
 | |
|     mHttpChannel = nullptr;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::ResetDecoder() {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (mUnicodeDecoder) {
 | |
|     UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
 | |
|   }
 | |
|   mStatus = PARSE_STATE_OFF;
 | |
|   ClearFields();
 | |
| }
 | |
| 
 | |
| class CallRestartConnection final : public WorkerMainThreadRunnable {
 | |
|  public:
 | |
|   explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
 | |
|       : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
 | |
|                                  "EventSource :: RestartConnection"_ns),
 | |
|         mESImpl(std::move(aEventSourceImpl)) {
 | |
|     mWorkerPrivate->AssertIsOnWorkerThread();
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|   }
 | |
| 
 | |
|   bool MainThreadRun() override {
 | |
|     MOZ_ASSERT(mESImpl);
 | |
|     mESImpl->RestartConnection();
 | |
|     // We want to ensure the shortest possible remaining lifetime
 | |
|     // and not depend on the Runnable's destruction.
 | |
|     mESImpl = nullptr;
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|  protected:
 | |
|   RefPtr<EventSourceImpl> mESImpl;
 | |
| };
 | |
| 
 | |
| nsresult EventSourceImpl::RestartConnection() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   nsresult rv = ResetConnection();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   rv = SetReconnectionTimeout();
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::ReestablishConnection() {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (IsClosed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
|   if (mIsMainThread) {
 | |
|     rv = RestartConnection();
 | |
|   } else {
 | |
|     RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
 | |
|     ErrorResult result;
 | |
|     runnable->Dispatch(Canceling, result);
 | |
|     MOZ_ASSERT(!result.Failed());
 | |
|     rv = result.StealNSResult();
 | |
|   }
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   rv = GetEventSource()->CheckCurrentGlobalCorrectness();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SetReadyState(CONNECTING);
 | |
|   ResetDecoder();
 | |
|   // We can't hold the mutex while dispatching the event because the mutex is
 | |
|   // not reentrant, and content might call back into our code.
 | |
|   rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     NS_WARNING("Failed to dispatch the error event!!!");
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::SetReconnectionTimeout() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   // the timer will be used whenever the requests are going finished.
 | |
|   if (!mTimer) {
 | |
|     mTimer = NS_NewTimer();
 | |
|     NS_ENSURE_STATE(mTimer);
 | |
|   }
 | |
| 
 | |
|   MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
 | |
|                                    nsITimer::TYPE_ONE_SHOT));
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::PrintErrorOnConsole(
 | |
|     const char* aBundleURI, const char* aError,
 | |
|     const nsTArray<nsString>& aFormatStrings) {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   nsCOMPtr<nsIStringBundleService> bundleService =
 | |
|       mozilla::components::StringBundle::Service();
 | |
|   NS_ENSURE_STATE(bundleService);
 | |
| 
 | |
|   nsCOMPtr<nsIStringBundle> strBundle;
 | |
|   nsresult rv =
 | |
|       bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIConsoleService> console(
 | |
|       do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIScriptError> errObj(
 | |
|       do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Localize the error message
 | |
|   nsAutoString message;
 | |
|   if (!aFormatStrings.IsEmpty()) {
 | |
|     rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
 | |
|   } else {
 | |
|     rv = strBundle->GetStringFromName(aError, message);
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
 | |
|                                 mScriptColumn, nsIScriptError::errorFlag,
 | |
|                                 "Event Source", mInnerWindowID);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // print the error message directly to the JS console
 | |
|   rv = console->LogMessage(errObj);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::ConsoleError() {
 | |
|   AssertIsOnMainThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   nsAutoCString targetSpec;
 | |
|   nsresult rv = mSrc->GetSpec(targetSpec);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   AutoTArray<nsString, 1> formatStrings;
 | |
|   CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
 | |
| 
 | |
|   if (ReadyState() == CONNECTING) {
 | |
|     rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
 | |
|                              "connectionFailure", formatStrings);
 | |
|   } else {
 | |
|     rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
 | |
|                              "netInterrupt", formatStrings);
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::DispatchFailConnection() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return;
 | |
|   }
 | |
|   nsresult rv = ConsoleError();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     NS_WARNING("Failed to print to the console error");
 | |
|   }
 | |
|   rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
 | |
|                                   &EventSourceImpl::FailConnection),
 | |
|                 NS_DISPATCH_NORMAL);
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // if the worker is shutting down, the dispatching of normal WorkerRunnables
 | |
|     // fails.
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::FailConnection() {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (IsClosed()) {
 | |
|     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 = GetEventSource()->CheckCurrentGlobalCorrectness();
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     // We can't hold the mutex while dispatching the event because the mutex
 | |
|     // is not reentrant, and content might call back into our code.
 | |
|     rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
 | |
|     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();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
 | |
| 
 | |
|   if (!mFrozen) {
 | |
|     nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       NS_WARNING("InitChannelAndRequestEventSource() failed");
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP EventSourceImpl::GetName(nsACString& aName) {
 | |
|   aName.AssignLiteral("EventSourceImpl");
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::Thaw() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed() || !mFrozen) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
 | |
| 
 | |
|   mFrozen = false;
 | |
|   nsresult rv;
 | |
|   if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
 | |
|     nsCOMPtr<nsIRunnable> event =
 | |
|         NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
 | |
|                           this, &EventSourceImpl::DispatchAllMessageEvents);
 | |
|     NS_ENSURE_STATE(event);
 | |
| 
 | |
|     mGoingToDispatchAllMessages = true;
 | |
| 
 | |
|     rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   rv = InitChannelAndRequestEventSource(mIsMainThread);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::Freeze() {
 | |
|   AssertIsOnMainThread();
 | |
|   if (IsClosed() || mFrozen) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
 | |
|   mFrozen = true;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
 | |
|   AssertIsOnTargetThread();
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   UniquePtr<Message> message(std::move(mCurrentMessage));
 | |
|   ClearFields();
 | |
| 
 | |
|   if (!message || message->mData.IsEmpty()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // removes the trailing LF from mData
 | |
|   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()) {
 | |
|     message->mEventName.AssignLiteral("message");
 | |
|   }
 | |
| 
 | |
|   mMessagesToDispatch.Push(message.release());
 | |
| 
 | |
|   if (!mGoingToDispatchAllMessages) {
 | |
|     nsCOMPtr<nsIRunnable> event =
 | |
|         NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
 | |
|                           this, &EventSourceImpl::DispatchAllMessageEvents);
 | |
|     NS_ENSURE_STATE(event);
 | |
| 
 | |
|     mGoingToDispatchAllMessages = true;
 | |
| 
 | |
|     return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::DispatchAllMessageEvents() {
 | |
|   AssertIsOnTargetThread();
 | |
|   mGoingToDispatchAllMessages = false;
 | |
| 
 | |
|   if (IsClosed() || mFrozen) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsresult rv;
 | |
|   AutoJSAPI jsapi;
 | |
|   {
 | |
|     auto lock = mSharedData.Lock();
 | |
|     rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   JSContext* cx = jsapi.cx();
 | |
| 
 | |
|   while (mMessagesToDispatch.GetSize() > 0) {
 | |
|     UniquePtr<Message> message(mMessagesToDispatch.PopFront());
 | |
| 
 | |
|     if (message->mLastEventID.isSome()) {
 | |
|       mLastEventID.Assign(message->mLastEventID.value());
 | |
|     }
 | |
| 
 | |
|     if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
 | |
|       message->mLastEventID = Some(mLastEventID);
 | |
|     }
 | |
| 
 | |
|     {
 | |
|       auto lock = mSharedData.Lock();
 | |
|       if (lock->mServiceNotifier) {
 | |
|         lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
 | |
|                                               message->mData, mReconnectionTime,
 | |
|                                               PR_Now());
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Now we can turn our string into a jsval
 | |
|     JS::Rooted<JS::Value> jsData(cx);
 | |
|     {
 | |
|       JSString* jsString;
 | |
|       jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
 | |
|                                      message->mData.Length());
 | |
|       NS_ENSURE_TRUE_VOID(jsString);
 | |
| 
 | |
|       jsData.setString(jsString);
 | |
|     }
 | |
| 
 | |
|     // create an event that uses the MessageEvent interface,
 | |
|     // which does not bubble, is not cancelable, and has no default action
 | |
| 
 | |
|     RefPtr<EventSource> eventSource = GetEventSource();
 | |
|     RefPtr<MessageEvent> event =
 | |
|         new MessageEvent(eventSource, nullptr, nullptr);
 | |
| 
 | |
|     event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
 | |
|                             Cancelable::eNo, jsData, mOrigin, mLastEventID,
 | |
|                             nullptr, Sequence<OwningNonNull<MessagePort>>());
 | |
|     event->SetTrusted(true);
 | |
| 
 | |
|     // We can't hold the mutex while dispatching the event because the mutex is
 | |
|     // not reentrant, and content might call back into our code.
 | |
|     IgnoredErrorResult err;
 | |
|     eventSource->DispatchEvent(*event, err);
 | |
|     if (err.Failed()) {
 | |
|       NS_WARNING("Failed to dispatch the message event!!!");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (IsClosed() || mFrozen) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::ClearFields() {
 | |
|   AssertIsOnTargetThread();
 | |
|   mCurrentMessage = nullptr;
 | |
|   mLastFieldName.Truncate();
 | |
|   mLastFieldValue.Truncate();
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::SetFieldAndClear() {
 | |
|   MOZ_ASSERT(!mIsShutDown);
 | |
|   AssertIsOnTargetThread();
 | |
|   if (mLastFieldName.IsEmpty()) {
 | |
|     mLastFieldValue.Truncate();
 | |
|     return NS_OK;
 | |
|   }
 | |
|   if (!mCurrentMessage) {
 | |
|     mCurrentMessage = MakeUnique<Message>();
 | |
|   }
 | |
|   char16_t first_char;
 | |
|   first_char = mLastFieldName.CharAt(0);
 | |
| 
 | |
|   // 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
 | |
|         // buffer, then append a single U+000A LINE FEED (LF) character
 | |
|         // to the data buffer.
 | |
|         mCurrentMessage->mData.Append(mLastFieldValue);
 | |
|         mCurrentMessage->mData.Append(LF_CHAR);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case char16_t('e'):
 | |
|       if (mLastFieldName.EqualsLiteral("event")) {
 | |
|         mCurrentMessage->mEventName.Assign(mLastFieldValue);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case char16_t('i'):
 | |
|       if (mLastFieldName.EqualsLiteral("id")) {
 | |
|         mCurrentMessage->mLastEventID = Some(mLastFieldValue);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case char16_t('r'):
 | |
|       if (mLastFieldName.EqualsLiteral("retry")) {
 | |
|         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) {
 | |
|           if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
 | |
|               mLastFieldValue.CharAt(i) > (char16_t)'9') {
 | |
|             assign = false;
 | |
|             break;
 | |
|           }
 | |
|           newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
 | |
|                                       ((uint32_t)((char16_t)'0')));
 | |
|         }
 | |
| 
 | |
|         if (assign) {
 | |
|           if (newValue < MIN_RECONNECTION_TIME_VALUE) {
 | |
|             mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
 | |
|           } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
 | |
|             mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
 | |
|           } else {
 | |
|             mReconnectionTime = newValue;
 | |
|           }
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   mLastFieldName.Truncate();
 | |
|   mLastFieldValue.Truncate();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult 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 (IsClosed() || mFrozen || !mHttpChannel) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
 | |
|   NS_ENSURE_STATE(httpChannel);
 | |
| 
 | |
|   if (httpChannel != mHttpChannel) {
 | |
|     NS_WARNING("wrong channel from request callback");
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
 | |
|   AssertIsOnTargetThread();
 | |
|   nsresult rv;
 | |
| 
 | |
|   if (IsClosed()) {
 | |
|     return NS_ERROR_ABORT;
 | |
|   }
 | |
| 
 | |
|   switch (mStatus) {
 | |
|     case PARSE_STATE_OFF:
 | |
|       NS_ERROR("Invalid state");
 | |
|       return NS_ERROR_FAILURE;
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_BEGIN_OF_STREAM:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr == COLON_CHAR) {
 | |
|         mStatus = PARSE_STATE_COMMENT;
 | |
|       } else {
 | |
|         mLastFieldName += aChr;
 | |
|         mStatus = PARSE_STATE_FIELD_NAME;
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_CR_CHAR:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         rv = DispatchCurrentMessageEvent();  // there is an empty line (CRCR)
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr == COLON_CHAR) {
 | |
|         mStatus = PARSE_STATE_COMMENT;
 | |
|       } else {
 | |
|         mLastFieldName += aChr;
 | |
|         mStatus = PARSE_STATE_FIELD_NAME;
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_COMMENT:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_FIELD_NAME:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr == COLON_CHAR) {
 | |
|         mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
 | |
|       } else {
 | |
|         mLastFieldName += aChr;
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr == SPACE_CHAR) {
 | |
|         mStatus = PARSE_STATE_FIELD_VALUE;
 | |
|       } else {
 | |
|         mLastFieldValue += aChr;
 | |
|         mStatus = PARSE_STATE_FIELD_VALUE;
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_FIELD_VALUE:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         rv = SetFieldAndClear();
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr != 0) {
 | |
|         // Avoid appending the null char to the field value.
 | |
|         mLastFieldValue += aChr;
 | |
|       } else if (mLastFieldName.EqualsLiteral("id")) {
 | |
|         // Ignore the whole id field if aChr is null
 | |
|         mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
 | |
|         mLastFieldValue.Truncate();
 | |
|       }
 | |
| 
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_IGNORE_FIELD_VALUE:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case PARSE_STATE_BEGIN_OF_LINE:
 | |
|       if (aChr == CR_CHAR) {
 | |
|         rv = DispatchCurrentMessageEvent();  // there is an empty line
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_CR_CHAR;
 | |
|       } else if (aChr == LF_CHAR) {
 | |
|         rv = DispatchCurrentMessageEvent();  // there is an empty line
 | |
|         NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|         mStatus = PARSE_STATE_BEGIN_OF_LINE;
 | |
|       } else if (aChr == COLON_CHAR) {
 | |
|         mStatus = PARSE_STATE_COMMENT;
 | |
|       } else if (aChr != 0) {
 | |
|         // Avoid appending the null char to the field name.
 | |
|         mLastFieldName += aChr;
 | |
|         mStatus = PARSE_STATE_FIELD_NAME;
 | |
|       }
 | |
| 
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| class WorkerRunnableDispatcher final : public WorkerRunnable {
 | |
|   RefPtr<EventSourceImpl> mEventSourceImpl;
 | |
| 
 | |
|  public:
 | |
|   WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
 | |
|                            WorkerPrivate* aWorkerPrivate,
 | |
|                            already_AddRefed<nsIRunnable> aEvent)
 | |
|       : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
 | |
|         mEventSourceImpl(std::move(aImpl)),
 | |
|         mEvent(std::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 {
 | |
|     // Ensure we drop the RefPtr on the worker thread
 | |
|     // and to not keep us alive longer than needed.
 | |
|     mEventSourceImpl = nullptr;
 | |
|   }
 | |
| 
 | |
|   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::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
 | |
|   MOZ_ASSERT(!mWorkerRef);
 | |
|   MOZ_ASSERT(aWorkerPrivate);
 | |
|   aWorkerPrivate->AssertIsOnWorkerThread();
 | |
| 
 | |
|   if (mIsShutDown) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   RefPtr<EventSourceImpl> self = this;
 | |
|   RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
 | |
|       aWorkerPrivate, "EventSource", [self]() { self->Close(); });
 | |
| 
 | |
|   if (NS_WARN_IF(!workerRef)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   mWorkerRef = new ThreadSafeWorkerRef(workerRef);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void EventSourceImpl::ReleaseWorkerRef() {
 | |
|   MOZ_ASSERT(IsClosed());
 | |
|   MOZ_ASSERT(IsCurrentThreadRunningWorker());
 | |
|   mWorkerRef = 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());
 | |
|   }
 | |
| 
 | |
|   if (mIsShutDown) {
 | |
|     // We want to avoid clutter about errors in our shutdown logs,
 | |
|     // so just report NS_OK (we have no explicit return value
 | |
|     // for shutdown).
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
 | |
|   // runnable.
 | |
|   RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
 | |
|       this, mWorkerRef->Private(), 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;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
 | |
|   return NS_ERROR_NOT_IMPLEMENTED;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
 | |
|   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(nsIGlobalObject* aGlobal,
 | |
|                          nsICookieJarSettings* aCookieJarSettings,
 | |
|                          bool aWithCredentials)
 | |
|     : DOMEventTargetHelper(aGlobal),
 | |
|       mWithCredentials(aWithCredentials),
 | |
|       mIsMainThread(NS_IsMainThread()) {
 | |
|   MOZ_ASSERT(aGlobal);
 | |
|   MOZ_ASSERT(aCookieJarSettings);
 | |
|   mESImpl = new EventSourceImpl(this, aCookieJarSettings);
 | |
| }
 | |
| 
 | |
| EventSource::~EventSource() = default;
 | |
| 
 | |
| 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);
 | |
|   ErrorResult rv;
 | |
|   DispatchEvent(*event, rv);
 | |
|   return rv.StealNSResult();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| already_AddRefed<EventSource> EventSource::Constructor(
 | |
|     const GlobalObject& aGlobal, const nsAString& aURL,
 | |
|     const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
 | |
|   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 | |
|   if (NS_WARN_IF(!global)) {
 | |
|     aRv.Throw(NS_ERROR_FAILURE);
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
 | |
|   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
 | |
|   if (ownerWindow) {
 | |
|     Document* doc = ownerWindow->GetExtantDoc();
 | |
|     if (NS_WARN_IF(!doc)) {
 | |
|       aRv.Throw(NS_ERROR_FAILURE);
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     cookieJarSettings = doc->CookieJarSettings();
 | |
|   } else {
 | |
|     // Worker side.
 | |
|     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
 | |
|     if (!workerPrivate) {
 | |
|       aRv.Throw(NS_ERROR_FAILURE);
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     cookieJarSettings = workerPrivate->CookieJarSettings();
 | |
|   }
 | |
| 
 | |
|   RefPtr<EventSource> eventSource = new EventSource(
 | |
|       global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
 | |
| 
 | |
|   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;
 | |
|     }
 | |
|     eventSource->mESImpl->Init(principal, aURL, aRv);
 | |
|     if (NS_WARN_IF(aRv.Failed())) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     eventSource->mESImpl->InitChannelAndRequestEventSource(true);
 | |
|     return eventSource.forget();
 | |
|   }
 | |
| 
 | |
|   // Worker side.
 | |
|   {
 | |
|     // Scope for possible failures that need cleanup
 | |
|     auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
 | |
| 
 | |
|     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
 | |
|     MOZ_ASSERT(workerPrivate);
 | |
| 
 | |
|     eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
 | |
| 
 | |
|     RefPtr<InitRunnable> initRunnable =
 | |
|         new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
 | |
|     initRunnable->Dispatch(Canceling, aRv);
 | |
|     if (NS_WARN_IF(aRv.Failed())) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     aRv = initRunnable->ErrorCode();
 | |
|     if (NS_WARN_IF(aRv.Failed())) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     // In workers we have to keep the worker alive using a WorkerRef in order
 | |
|     // to dispatch messages correctly.
 | |
|     // Note, initRunnable->Dispatch may have cleared mESImpl.
 | |
|     if (!eventSource->mESImpl ||
 | |
|         !eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
 | |
|       // The worker is already shutting down. Let's return an already closed
 | |
|       // object, but marked as Connecting.
 | |
|       if (eventSource->mESImpl) {
 | |
|         // mESImpl is nulled by this call such that EventSourceImpl is
 | |
|         // released before returning the object, otherwise
 | |
|         // it will set EventSource to a CLOSED state in its DTOR..
 | |
|         eventSource->mESImpl->Close();
 | |
|       }
 | |
|       eventSource->mReadyState = EventSourceImpl::CONNECTING;
 | |
| 
 | |
|       guardESImpl.release();
 | |
|       return eventSource.forget();
 | |
|     }
 | |
| 
 | |
|     // Let's connect to the server.
 | |
|     RefPtr<ConnectRunnable> connectRunnable =
 | |
|         new ConnectRunnable(workerPrivate, eventSource->mESImpl);
 | |
|     connectRunnable->Dispatch(Canceling, aRv);
 | |
|     if (NS_WARN_IF(aRv.Failed())) {
 | |
|       return nullptr;
 | |
|     }
 | |
| 
 | |
|     // End of scope for possible failures
 | |
|     guardESImpl.release();
 | |
|   }
 | |
| 
 | |
|   return eventSource.forget();
 | |
| }
 | |
| 
 | |
| // nsWrapperCache
 | |
| JSObject* EventSource::WrapObject(JSContext* aCx,
 | |
|                                   JS::Handle<JSObject*> aGivenProto) {
 | |
|   return EventSource_Binding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| void EventSource::Close() {
 | |
|   AssertIsOnTargetThread();
 | |
|   if (mESImpl) {
 | |
|     // Close potentially kills ourself, ensure
 | |
|     // to not access any members afterwards.
 | |
|     mESImpl->Close();
 | |
|   }
 | |
| }
 | |
| 
 | |
| //-----------------------------------------------------------------------------
 | |
| // EventSource::nsISupports
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
 | |
| 
 | |
| 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->mESImpl) {
 | |
|     // IsCertainlyaliveForCC will return true and cause the cycle
 | |
|     // collector to skip this instance when mESImpl is non-null and
 | |
|     // points back to ourself.
 | |
|     // mESImpl is initialized to be non-null in the constructor
 | |
|     // and should have been wiped out in our close function.
 | |
|     MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
 | |
|     tmp->Close();
 | |
|   }
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| bool EventSource::IsCertainlyAliveForCC() const {
 | |
|   // Until we are double linked forth and back, we want to stay alive.
 | |
|   if (!mESImpl) {
 | |
|     return false;
 | |
|   }
 | |
|   auto lock = mESImpl->mSharedData.Lock();
 | |
|   return lock->mEventSource == this;
 | |
| }
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
 | |
| NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 | |
| 
 | |
| NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
 | |
| NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
 | |
| 
 | |
| }  // namespace mozilla::dom
 |