Bug 1267903 - Part 1: Implement EventSource for Worker. r=baku
authorStone Shih <sshih@mozilla.com>
Mon, 24 Oct 2016 10:11:04 +0800
changeset 374617 eae4cf8e37a81865865b7a6baa73e041e540f071
parent 374616 9d951b0e3ce23eb97920a6caf6a14dd68dbe9db7
child 374618 e8c7c25bb30c25484d8f5a733d2858a168a50fb7
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1267903
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;