Bug 1267903 - Part 1: Implement EventSource for Worker. r=baku
authorStone Shih <sshih@mozilla.com>
Mon, 24 Oct 2016 10:11:04 +0800
changeset 462257 eae4cf8e37a81865865b7a6baa73e041e540f071
parent 462256 9d951b0e3ce23eb97920a6caf6a14dd68dbe9db7
child 462258 e8c7c25bb30c25484d8f5a733d2858a168a50fb7
push id41683
push userbmo:steffen.wilberg@web.de
push dateMon, 16 Jan 2017 21:50:32 +0000
reviewersbaku
bugs1267903
milestone53.0a1
Bug 1267903 - Part 1: Implement EventSource for Worker. r=baku
dom/base/EventSource.cpp
dom/base/EventSource.h
dom/base/WebSocket.cpp
dom/webidl/EventSource.webidl
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -9,333 +9,631 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/DOMEventTargetHelper.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/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/UniquePtrExtensions.h"
 #include "nsAutoPtr.h"
 #include "nsNetUtil.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIInputStream.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsMimeTypes.h"
 #include "nsIPromptFactory.h"
 #include "nsIWindowWatcher.h"
 #include "nsPresContext.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 "mozilla/dom/EncodingUtils.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
 #include "xpcpublic.h"
 #include "nsWrapperCacheInlines.h"
 #include "mozilla/Attributes.h"
 #include "nsError.h"
 
 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
 #define CR_CHAR              (char16_t)0x000D
 #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)
+class EventSourceImpl final : public nsIObserver
+                            , public nsIStreamListener
+                            , public nsIChannelEventSink
+                            , public nsIInterfaceRequestor
+                            , public nsSupportsWeakReference
+                            , public nsIEventTarget
+                            , 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
+  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());
+  }
+
+  bool IsTargetThread() const
+  {
+    return NS_IsMainThread() == mIsMainThread;
+  }
+
+  uint16_t ReadyState()
+  {
+    if (mEventSource) {
+      MutexAutoLock lock(mMutex);
+      return 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)
+  {
+    MOZ_ASSERT(mEventSource);
+    MutexAutoLock lock(mMutex);
+    mEventSource->mReadyState = aReadyState;
+  }
+
+  bool IsFrozen()
+  {
+    MutexAutoLock lock(mMutex);
+    return mFrozen;
+  }
+
+  void SetFrozen(bool aFrozen)
+  {
+    MutexAutoLock lock(mMutex);
+    mFrozen = aFrozen;
+  }
+
+  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);
 }
 
-EventSource::~EventSource()
+class CleanupRunnable final : public WorkerMainThreadRunnable
 {
-  Close();
-}
-
-//-----------------------------------------------------------------------------
-// EventSource::nsISupports
-//-----------------------------------------------------------------------------
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
+public:
+  explicit CleanupRunnable(EventSourceImpl* aEventSourceImpl)
+    : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerPrivate,
+                               NS_LITERAL_CSTRING("EventSource :: Cleanup"))
+    , mImpl(aEventSourceImpl)
+  {
+    mImpl->mWorkerPrivate->AssertIsOnWorkerThread();
+  }
 
-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();
-    }
+  bool MainThreadRun() override
+  {
+    mImpl->CleanupOnMainThread();
     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(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
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
-                                                DOMEventTargetHelper)
-  tmp->Close();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-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)
-
-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) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
 
   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);
-
-  mPrincipal = principal;
-  mWithCredentials = aWithCredentials;
-
-  // The conditional here is historical and not necessarily sane.
-  if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
-    nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
-                                  &mScriptColumn);
-    mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
+  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;
   }
 
-  // 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 ErrorCode() const { return mRv; }
+
+protected:
+  // Raw pointer because this runnable is sync.
+  EventSourceImpl* mImpl;
+  const nsAString& mURL;
+  nsresult mRv;
+};
+
+nsresult
+EventSourceImpl::ParseURL(const nsAString& aURL)
+{
+  AssertIsOnMainThread();
   // 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);
 
-  // we observe when the window freezes and thaws
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  NS_ENSURE_STATE(os);
-
-  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);
-
   nsAutoString origin;
   rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString spec;
   rv = srcURI->GetSpec(spec);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mOriginalURL = NS_ConvertUTF8toUTF16(spec);
+  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);
+
+  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);
+  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);
+  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);
+  }
+
+  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 = EncodingUtils::DecoderForEncoding("UTF-8");
 
   // the constructor should throw a SYNTAX_ERROR only if it fails resolving the
   // 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();
   }
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// 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);
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsresult status;
   rv = aRequest->GetStatus(&status);
@@ -360,98 +658,158 @@ EventSource::OnStartRequest(nsIRequest *
   rv = httpChannel->GetContentType(contentType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
     DispatchFailConnection();
     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;
   }
-
-  *aWriteCount = 0;
-
-  int32_t srcCount, outCount;
-  char16_t out[2];
-  nsresult rv;
-
-  const char *p = aFromRawSegment,
-             *end = aFromRawSegment + aCount;
-
-  do {
-    srcCount = aCount - (p - aFromRawSegment);
-    outCount = 2;
-
-    thisObject->mLastConvertionResult =
-      thisObject->mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
-    MOZ_ASSERT(thisObject->mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
-
-    for (int32_t i = 0; i < outCount; ++i) {
-      rv = thisObject->ParseCharacter(out[i]);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-    p = p + srcCount;
-  } while (p < end &&
-           thisObject->mLastConvertionResult != NS_PARTIAL_MORE_INPUT &&
-           thisObject->mLastConvertionResult != NS_OK);
-
+  thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
   *aWriteCount = aCount;
   return NS_OK;
 }
 
+void
+EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength)
+{
+  AssertIsOnTargetThread();
+  if (IsClosed()) {
+    return;
+  }
+  int32_t srcCount, outCount;
+  char16_t out[2];
+  const char* p = aBuffer;
+  const char* end = aBuffer + aLength;
+
+  do {
+    srcCount = aLength - (p - aBuffer);
+    outCount = 2;
+
+    mLastConvertionResult =
+      mUnicodeDecoder->Convert(p, &srcCount, out, &outCount);
+    MOZ_ASSERT(mLastConvertionResult != NS_ERROR_ILLEGAL_INPUT);
+
+    for (int32_t i = 0; i < outCount; ++i) {
+      nsresult rv = ParseCharacter(out[i]);
+      NS_ENSURE_SUCCESS_VOID(rv);
+    }
+    p = p + srcCount;
+  } while (p < end &&
+           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.
   //
   //  (...) 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.
 
@@ -464,51 +822,52 @@ EventSource::OnStopRequest(nsIRequest *a
       aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
     DispatchFailConnection();
     return NS_ERROR_ABORT;
   }
 
   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?");
 
   nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_PRECONDITION(aNewChannel, "Redirect without a channel?");
 
   nsCOMPtr<nsIURI> newURI;
   rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool isValidScheme =
     (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;
   }
 
   // update our channel
 
   mHttpChannel = do_QueryInterface(aNewChannel);
@@ -524,63 +883,71 @@ EventSource::AsyncOnChannelRedirect(nsIC
   }
 
   aCallback->OnRedirectVerifyCallback(NS_OK);
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// 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();
     return NS_OK;
   }
 
   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 =
       do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Get the an auth prompter for our window so that the parenting
     // 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);
   }
 
   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;
 
   nsCOMPtr<nsIURI> baseURI;
 
   // first we try from document->GetBaseURI()
-  nsCOMPtr<nsIDocument> doc = GetDocumentIfCurrent();
+  nsCOMPtr<nsIDocument> doc = mEventSource->GetDocumentIfCurrent();
   if (doc) {
     baseURI = doc->GetBaseURI();
   }
 
   // otherwise we get from the doc's principal
   if (!baseURI) {
     nsresult rv = mPrincipal->GetURI(getter_AddRefs(baseURI));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -588,94 +955,98 @@ EventSource::GetBaseURI(nsIURI **aBaseUR
 
   NS_ENSURE_STATE(baseURI);
 
   baseURI.forget(aBaseURI);
   return NS_OK;
 }
 
 void
-EventSource::SetupHttpChannel()
+EventSourceImpl::SetupHttpChannel()
 {
+  AssertIsOnMainThread();
   mHttpChannel->SetRequestMethod(NS_LITERAL_CSTRING("GET"));
 
   /* set the http request headers */
 
   mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
     NS_LITERAL_CSTRING(TEXT_EVENT_STREAM), false);
 
   // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
 
   if (!mLastEventID.IsEmpty()) {
     mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Last-Event-ID"),
       NS_ConvertUTF16toUTF8(mLastEventID), false);
   }
 }
 
 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());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 nsresult
-EventSource::InitChannelAndRequestEventSource()
+EventSourceImpl::InitChannelAndRequestEventSource()
 {
-  if (mReadyState == CLOSED) {
+  AssertIsOnMainThread();
+  if (IsClosed()) {
     return NS_ERROR_ABORT;
   }
 
   bool isValidScheme =
     (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;
   }
 
   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 {
     // otherwise use the principal
     rv = NS_NewChannel(getter_AddRefs(channel),
                        mSrc,
                        mPrincipal,
                        securityFlags,
                        nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
-                       mLoadGroup,       // loadGroup
+                       nullptr,          // loadGroup
                        nullptr,          // aCallbacks
                        loadFlags);       // aLoadFlags
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   mHttpChannel = do_QueryInterface(channel);
   NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
@@ -694,117 +1065,146 @@ EventSource::InitChannelAndRequestEventS
   mHttpChannel->SetNotificationCallbacks(this);
 
   // Start reading from the channel
   rv = mHttpChannel->AsyncOpen2(this);
   if (NS_FAILED(rv)) {
     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;
   }
 
   // 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.
 
-  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);
-  }
-
-  if (mUnicodeDecoder) {
-    mUnicodeDecoder->Reset();
+    mHttpChannel = nullptr;
   }
-  mLastConvertionResult = NS_OK;
-
-  mHttpChannel = nullptr;
-  mStatus = PARSE_STATE_OFF;
-
-  mReadyState = CONNECTING;
-
   return NS_OK;
 }
 
 void
-EventSource::ReestablishConnection()
+EventSourceImpl::ResetDecoder()
+{
+  AssertIsOnTargetThread();
+  if (mUnicodeDecoder) {
+    mUnicodeDecoder->Reset();
+  }
+  mStatus = PARSE_STATE_OFF;
+  mLastConvertionResult = NS_OK;
+  ClearFields();
+}
+
+class CallRestartConnection final : public WorkerMainThreadRunnable
 {
-  if (mReadyState == CLOSED) {
+public:
+  explicit CallRestartConnection(EventSourceImpl* aEventSourceImpl)
+    : WorkerMainThreadRunnable(
+        aEventSourceImpl->mWorkerPrivate,
+        NS_LITERAL_CSTRING("EventSource :: RestartConnection"))
+    , mImpl(aEventSourceImpl)
+  {
+    mImpl->mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  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
+EventSourceImpl::ReestablishConnection()
+{
+  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;
   }
 
   // the timer will be used whenever the requests are going finished.
   if (!mTimer) {
     mTimer = do_CreateInstance("@mozilla.org/timer;1");
     NS_ENSURE_STATE(mTimer);
   }
@@ -813,21 +1213,22 @@ EventSource::SetReconnectionTimeout()
                                              mReconnectionTime,
                                              nsITimer::TYPE_ONE_SHOT);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 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);
 
   nsCOMPtr<nsIStringBundle> strBundle;
   nsresult rv =
     bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -862,159 +1263,161 @@ EventSource::PrintErrorOnConsole(const c
   // print the error message directly to the JS console
   rv = console->LogMessage(errObj);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 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));
   } else {
     rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
                              u"netInterrupt",
                              formatStrings, ArrayLength(formatStrings));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-nsresult
-EventSource::DispatchFailConnection()
+void
+EventSourceImpl::DispatchFailConnection()
 {
-
-  return NS_DispatchToMainThread(NewRunnableMethod(this, &EventSource::FailConnection));
-}
-
-void
-EventSource::FailConnection()
-{
-  if (mReadyState == CLOSED) {
+  AssertIsOnMainThread();
+  if (IsClosed()) {
     return;
   }
-
   nsresult rv = ConsoleError();
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to print to the console error");
   }
-
-  // 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.
+  rv = Dispatch(NewRunnableMethod(this, &EventSourceImpl::FailConnection),
+                NS_DISPATCH_NORMAL);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
 
-  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");
       return;
     }
   }
 }
 
 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);
   }
 
   rv = InitChannelAndRequestEventSource();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 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;
 
   ClearFields();
 
   if (message->mData.IsEmpty()) {
     return NS_OK;
   }
 
   // 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()) {
     message->mEventName.AssignLiteral("message");
   }
 
   if (message->mLastEventID.IsEmpty() && !mLastEventID.IsEmpty()) {
     message->mLastEventID.Assign(mLastEventID);
@@ -1023,44 +1426,52 @@ EventSource::DispatchCurrentMessageEvent
   size_t sizeBefore = mMessagesToDispatch.GetSize();
   mMessagesToDispatch.Push(message.forget());
   NS_ENSURE_TRUE(mMessagesToDispatch.GetSize() == sizeBefore + 1,
                  NS_ERROR_OUT_OF_MEMORY);
 
 
   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();
 
   while (mMessagesToDispatch.GetSize() > 0) {
     nsAutoPtr<Message>
       message(static_cast<Message*>(mMessagesToDispatch.PopFront()));
 
     // Now we can turn our string into a jsval
@@ -1073,62 +1484,67 @@ EventSource::DispatchAllMessageEvents()
       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<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();
 
   mLastFieldName.Truncate();
   mLastFieldValue.Truncate();
 
   return NS_OK;
 }
 
 nsresult
-EventSource::SetFieldAndClear()
+EventSourceImpl::SetFieldAndClear()
 {
+  AssertIsOnTargetThread();
   if (mLastFieldName.IsEmpty()) {
     mLastFieldValue.Truncate();
     return NS_OK;
   }
 
   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
         // buffer, then append a single U+000A LINE FEED (LF) character
         // to the data buffer.
         mCurrentMessage.mData.Append(mLastFieldValue);
         mCurrentMessage.mData.Append(LF_CHAR);
       }
@@ -1143,17 +1559,17 @@ EventSource::SetFieldAndClear()
     case char16_t('i'):
       if (mLastFieldName.EqualsLiteral("id")) {
         mCurrentMessage.mLastEventID.Assign(mLastFieldValue);
       }
       break;
 
     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) {
           if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
               mLastFieldValue.CharAt(i) > (char16_t)'9') {
             assign = false;
             break;
           }
@@ -1178,47 +1594,49 @@ EventSource::SetFieldAndClear()
 
   mLastFieldName.Truncate();
   mLastFieldValue.Truncate();
 
   return NS_OK;
 }
 
 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;
   }
 
   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
-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;
       break;
 
     case PARSE_STATE_BEGIN_OF_STREAM:
       if (aChr == BOM_CHAR) {
         mStatus = PARSE_STATE_BOM_WAS_READ;  // ignore it
@@ -1347,10 +1765,335 @@ EventSource::ParseCharacter(char16_t aCh
       }
 
       break;
   }
 
   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
--- a/dom/base/EventSource.h
+++ b/dom/base/EventSource.h
@@ -31,229 +31,89 @@ class nsPIDOMWindowInner;
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 struct EventSourceInit;
 
+class EventSourceImpl;
+
 class EventSource final : public DOMEventTargetHelper
-                        , public nsIObserver
-                        , public nsIStreamListener
-                        , public nsIChannelEventSink
-                        , public nsIInterfaceRequestor
-                        , public nsSupportsWeakReference
 {
+  friend class EventSourceImpl;
 public:
-  explicit EventSource(nsPIDOMWindowInner* aOwnerWindow);
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(
     EventSource, DOMEventTargetHelper)
 
-  NS_DECL_NSIOBSERVER
-  NS_DECL_NSISTREAMLISTENER
-  NS_DECL_NSIREQUESTOBSERVER
-  NS_DECL_NSICHANNELEVENTSINK
-  NS_DECL_NSIINTERFACEREQUESTOR
+  // EventTarget
+  void DisconnectFromOwner() override
+  {
+    DOMEventTargetHelper::DisconnectFromOwner();
+    Close();
+  }
 
-  // nsWrapperCache
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL
-  nsPIDOMWindowInner*
-  GetParentObject() const
-  {
-    return GetOwner();
-  }
   static already_AddRefed<EventSource>
   Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
-              const EventSourceInit& aEventSourceInitDict,
-              ErrorResult& aRv);
+              const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv);
 
   void GetUrl(nsAString& aURL) const
   {
+    AssertIsOnTargetThread();
     aURL = mOriginalURL;
   }
+
   bool WithCredentials() const
   {
+    AssertIsOnTargetThread();
     return mWithCredentials;
   }
 
-  enum {
-    CONNECTING = 0U,
-    OPEN = 1U,
-    CLOSED = 2U
-  };
   uint16_t ReadyState() const
   {
+    AssertIsOnTargetThread();
     return mReadyState;
   }
 
   IMPL_EVENT_HANDLER(open)
   IMPL_EVENT_HANDLER(message)
   IMPL_EVENT_HANDLER(error)
+
   void Close();
 
-  virtual void DisconnectFromOwner() override;
-
-protected:
+private:
+  EventSource(nsPIDOMWindowInner* aOwnerWindow, bool aWithCredentials);
   virtual ~EventSource();
-
-  MOZ_IS_CLASS_INIT
-  nsresult Init(nsISupports* aOwner,
-                const nsAString& aURL,
-                bool aWithCredentials);
-
-  nsresult GetBaseURI(nsIURI **aBaseURI);
-
-  void SetupHttpChannel();
-  nsresult SetupReferrerPolicy();
-  nsresult InitChannelAndRequestEventSource();
-  nsresult ResetConnection();
-  nsresult DispatchFailConnection();
-  nsresult SetReconnectionTimeout();
-
-  void AnnounceConnection();
-  void DispatchAllMessageEvents();
-  void ReestablishConnection();
-  void FailConnection();
-
-  nsresult Thaw();
-  nsresult Freeze();
-
-  static void TimerCallback(nsITimer *aTimer, void *aClosure);
+  // prevent bad usage
+  EventSource(const EventSource& x) = delete;
+  EventSource& operator=(const EventSource& x) = delete;
 
-  nsresult PrintErrorOnConsole(const char       *aBundleURI,
-                               const char16_t  *aError,
-                               const char16_t **aFormatStrings,
-                               uint32_t          aFormatStringsLen);
-  nsresult ConsoleError();
-
-  static nsresult StreamReaderFunc(nsIInputStream *aInputStream,
-                                   void           *aClosure,
-                                   const char     *aFromRawSegment,
-                                   uint32_t        aToOffset,
-                                   uint32_t        aCount,
-                                   uint32_t       *aWriteCount);
-  nsresult SetFieldAndClear();
-  nsresult ClearFields();
-  nsresult ResetEvent();
-  nsresult DispatchCurrentMessageEvent();
-  nsresult ParseCharacter(char16_t aChr);
-  nsresult CheckHealthOfRequestCallback(nsIRequest *aRequestCallback);
-  nsresult OnRedirectVerifyCallback(nsresult result);
-
-  nsCOMPtr<nsIURI> mSrc;
-
-  nsString mLastEventID;
-  uint32_t mReconnectionTime;  // in ms
-
-  struct Message {
-    nsString mEventName;
-    nsString mLastEventID;
-    nsString mData;
-  };
-  nsDeque mMessagesToDispatch;
-  Message mCurrentMessage;
+  void AssertIsOnTargetThread() const
+  {
+    MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
+  }
 
-  /**
-   * A simple state machine used to manage the event-source's line buffer
-   *
-   * PARSE_STATE_OFF              -> PARSE_STATE_BEGIN_OF_STREAM
-   *
-   * PARSE_STATE_BEGIN_OF_STREAM  -> PARSE_STATE_BOM_WAS_READ |
-   *                                 PARSE_STATE_CR_CHAR |
-   *                                 PARSE_STATE_BEGIN_OF_LINE |
-   *                                 PARSE_STATE_COMMENT |
-   *                                 PARSE_STATE_FIELD_NAME
-   *
-   * PARSE_STATE_BOM_WAS_READ     -> PARSE_STATE_CR_CHAR |
-   *                                 PARSE_STATE_BEGIN_OF_LINE |
-   *                                 PARSE_STATE_COMMENT |
-   *                                 PARSE_STATE_FIELD_NAME
-   *
-   * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
-   *                        PARSE_STATE_COMMENT |
-   *                        PARSE_STATE_FIELD_NAME |
-   *                        PARSE_STATE_BEGIN_OF_LINE
-   *
-   * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
-   *                        PARSE_STATE_BEGIN_OF_LINE
-   *
-   * PARSE_STATE_FIELD_NAME   -> PARSE_STATE_CR_CHAR |
-   *                             PARSE_STATE_BEGIN_OF_LINE |
-   *                             PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
-   *
-   * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE  -> PARSE_STATE_FIELD_VALUE |
-   *                                           PARSE_STATE_CR_CHAR |
-   *                                           PARSE_STATE_BEGIN_OF_LINE
-   *
-   * PARSE_STATE_FIELD_VALUE      -> PARSE_STATE_CR_CHAR |
-   *                                 PARSE_STATE_BEGIN_OF_LINE
-   *
-   * PARSE_STATE_BEGIN_OF_LINE   -> PARSE_STATE_CR_CHAR |
-   *                                PARSE_STATE_COMMENT |
-   *                                PARSE_STATE_FIELD_NAME |
-   *                                PARSE_STATE_BEGIN_OF_LINE
-   *
-   * Whenever the parser find an empty line or the end-of-file
-   * it dispatches the stacked event.
-   *
-   */
-  enum ParserStatus {
-    PARSE_STATE_OFF,
-    PARSE_STATE_BEGIN_OF_STREAM,
-    PARSE_STATE_BOM_WAS_READ,
-    PARSE_STATE_CR_CHAR,
-    PARSE_STATE_COMMENT,
-    PARSE_STATE_FIELD_NAME,
-    PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
-    PARSE_STATE_FIELD_VALUE,
-    PARSE_STATE_BEGIN_OF_LINE
-  };
-  ParserStatus mStatus;
+  nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
 
-  bool mFrozen;
-  bool mErrorLoadOnRedirect;
-  bool mGoingToDispatchAllMessages;
-  bool mWithCredentials;
-  bool mWaitingForOnStopRequest;
-
-  // used while reading the input streams
-  nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
-  nsresult mLastConvertionResult;
-  nsString mLastFieldName;
-  nsString mLastFieldValue;
-
-  nsCOMPtr<nsILoadGroup> mLoadGroup;
-
-  nsCOMPtr<nsIHttpChannel> mHttpChannel;
-
-  nsCOMPtr<nsITimer> mTimer;
-
-  uint16_t mReadyState;
+  // Raw pointer because this EventSourceImpl is created, managed and destroyed
+  // by EventSource.
+  EventSourceImpl* mImpl;
   nsString mOriginalURL;
-
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsString mOrigin;
+  uint16_t mReadyState;
+  bool mWithCredentials;
+  bool mIsMainThread;
+  // This is used to keep EventSourceImpl instance when there is a connection.
+  bool mKeepingAlive;
 
-  // Event Source owner information:
-  // - the script file name
-  // - source code line number and column number where the Event Source object
-  //   was constructed.
-  // - the ID of the inner window where the script lives. Note that this may not
-  //   be the same as the Event Source owner window.
-  // These attributes are used for error reporting.
-  nsString mScriptFile;
-  uint32_t mScriptLine;
-  uint32_t mScriptColumn;
-  uint64_t mInnerWindowID;
-
-private:
-  EventSource(const EventSource& x);   // prevent bad usage
-  EventSource& operator=(const EventSource& x);
+  void UpdateMustKeepAlive();
+  void UpdateDontKeepAlive();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_EventSource_h
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -2241,16 +2241,17 @@ public:
       mWebSocketImpl->CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY,
                                       EmptyCString());
     }
 
     return true;
   }
 
 private:
+  // RawPointer because this proxy keeps alive the holder.
   WebSocketImpl* mWebSocketImpl;
 };
 
 } // namespace
 
 void
 WebSocketImpl::AddRefObject()
 {
--- a/dom/webidl/EventSource.webidl
+++ b/dom/webidl/EventSource.webidl
@@ -6,17 +6,18 @@
  * The origin of this IDL file is
  * https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface
  *
  * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
  * Opera Software ASA. You are granted a license to use, reproduce
  * and create derivative works of this document.
  */
 
-[Constructor(USVString url, optional EventSourceInit eventSourceInitDict)]
+[Exposed=(Window,DedicatedWorker,SharedWorker),
+ Constructor(USVString url, optional EventSourceInit eventSourceInitDict)]
 interface EventSource : EventTarget {
   [Constant]
   readonly attribute DOMString url;
   [Constant]
   readonly attribute boolean withCredentials;
 
   // ready state
   const unsigned short CONNECTING = 0;