Bug 1835710 - Cancel off-thread JIT compilation before changing nursery allocation...
[gecko.git] / dom / base / EventSource.cpp
blob2e5c7c05cd6f60264119512824ceb993952e7e18
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/Components.h"
9 #include "mozilla/DataMutex.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/LoadInfo.h"
12 #include "mozilla/DOMEventTargetHelper.h"
13 #include "mozilla/dom/EventSource.h"
14 #include "mozilla/dom/EventSourceBinding.h"
15 #include "mozilla/dom/MessageEvent.h"
16 #include "mozilla/dom/MessageEventBinding.h"
17 #include "mozilla/dom/ScriptSettings.h"
18 #include "mozilla/dom/WorkerPrivate.h"
19 #include "mozilla/dom/WorkerRef.h"
20 #include "mozilla/dom/WorkerRunnable.h"
21 #include "mozilla/dom/WorkerScope.h"
22 #include "mozilla/dom/EventSourceEventService.h"
23 #include "mozilla/ScopeExit.h"
24 #include "mozilla/UniquePtrExtensions.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsIThreadRetargetableStreamListener.h"
27 #include "nsNetUtil.h"
28 #include "nsIAuthPrompt.h"
29 #include "nsIAuthPrompt2.h"
30 #include "nsIHttpChannel.h"
31 #include "nsIInputStream.h"
32 #include "nsIInterfaceRequestorUtils.h"
33 #include "nsMimeTypes.h"
34 #include "nsIPromptFactory.h"
35 #include "nsIWindowWatcher.h"
36 #include "nsPresContext.h"
37 #include "nsProxyRelease.h"
38 #include "nsContentPolicyUtils.h"
39 #include "nsIStringBundle.h"
40 #include "nsIConsoleService.h"
41 #include "nsIObserverService.h"
42 #include "nsIScriptObjectPrincipal.h"
43 #include "nsJSUtils.h"
44 #include "nsIThreadRetargetableRequest.h"
45 #include "nsIAsyncVerifyRedirectCallback.h"
46 #include "nsIScriptError.h"
47 #include "nsContentUtils.h"
48 #include "mozilla/Preferences.h"
49 #include "xpcpublic.h"
50 #include "nsWrapperCacheInlines.h"
51 #include "mozilla/Attributes.h"
52 #include "nsError.h"
53 #include "mozilla/Encoding.h"
54 #include "ReferrerInfo.h"
56 namespace mozilla::dom {
58 #ifdef DEBUG
59 static LazyLogModule gEventSourceLog("EventSource");
60 #endif
62 #define SPACE_CHAR (char16_t)0x0020
63 #define CR_CHAR (char16_t)0x000D
64 #define LF_CHAR (char16_t)0x000A
65 #define COLON_CHAR (char16_t)0x003A
67 // Reconnection time related values in milliseconds. The default one is equal
68 // to the default value of the pref dom.server-events.default-reconnection-time
69 #define MIN_RECONNECTION_TIME_VALUE 500
70 #define DEFAULT_RECONNECTION_TIME_VALUE 5000
71 #define MAX_RECONNECTION_TIME_VALUE \
72 PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
74 class EventSourceImpl final : public nsIObserver,
75 public nsIStreamListener,
76 public nsIChannelEventSink,
77 public nsIInterfaceRequestor,
78 public nsSupportsWeakReference,
79 public nsIEventTarget,
80 public nsITimerCallback,
81 public nsINamed,
82 public nsIThreadRetargetableStreamListener {
83 public:
84 NS_DECL_THREADSAFE_ISUPPORTS
85 NS_DECL_NSIOBSERVER
86 NS_DECL_NSIREQUESTOBSERVER
87 NS_DECL_NSISTREAMLISTENER
88 NS_DECL_NSICHANNELEVENTSINK
89 NS_DECL_NSIINTERFACEREQUESTOR
90 NS_DECL_NSIEVENTTARGET_FULL
91 NS_DECL_NSITIMERCALLBACK
92 NS_DECL_NSINAMED
93 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
95 EventSourceImpl(EventSource* aEventSource,
96 nsICookieJarSettings* aCookieJarSettings);
98 enum { CONNECTING = 0U, OPEN = 1U, CLOSED = 2U };
100 void Close();
102 void Init(nsIPrincipal* aPrincipal, const nsAString& aURL, ErrorResult& aRv);
104 nsresult GetBaseURI(nsIURI** aBaseURI);
106 void SetupHttpChannel();
107 nsresult SetupReferrerInfo(const nsCOMPtr<Document>& aDocument);
108 nsresult InitChannelAndRequestEventSource(bool aEventTargetAccessAllowed);
109 nsresult ResetConnection();
110 void ResetDecoder();
111 nsresult SetReconnectionTimeout();
113 void AnnounceConnection();
114 void DispatchAllMessageEvents();
115 nsresult RestartConnection();
116 void ReestablishConnection();
117 void DispatchFailConnection();
118 void FailConnection();
120 nsresult Thaw();
121 nsresult Freeze();
123 nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
124 const nsTArray<nsString>& aFormatStrings);
125 nsresult ConsoleError();
127 static nsresult StreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
128 const char* aFromRawSegment,
129 uint32_t aToOffset, uint32_t aCount,
130 uint32_t* aWriteCount);
131 void ParseSegment(const char* aBuffer, uint32_t aLength);
132 nsresult SetFieldAndClear();
133 void ClearFields();
134 nsresult ResetEvent();
135 nsresult DispatchCurrentMessageEvent();
136 nsresult ParseCharacter(char16_t aChr);
137 nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
138 nsresult OnRedirectVerifyCallback(nsresult result);
139 nsresult ParseURL(const nsAString& aURL);
140 nsresult AddWindowObservers();
141 void RemoveWindowObservers();
143 void CloseInternal();
144 void CleanupOnMainThread();
146 bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
147 void ReleaseWorkerRef();
149 void AssertIsOnTargetThread() const {
150 MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
153 bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
155 uint16_t ReadyState() {
156 auto lock = mSharedData.Lock();
157 if (lock->mEventSource) {
158 return lock->mEventSource->mReadyState;
160 // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
161 // means that the EventSource has been closed.
162 return CLOSED;
165 void SetReadyState(uint16_t aReadyState) {
166 auto lock = mSharedData.Lock();
167 MOZ_ASSERT(lock->mEventSource);
168 MOZ_ASSERT(!mIsShutDown);
169 lock->mEventSource->mReadyState = aReadyState;
172 bool IsClosed() { return ReadyState() == CLOSED; }
174 RefPtr<EventSource> GetEventSource() {
175 AssertIsOnTargetThread();
176 auto lock = mSharedData.Lock();
177 return lock->mEventSource;
181 * A simple state machine used to manage the event-source's line buffer
183 * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
185 * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_CR_CHAR |
186 * PARSE_STATE_BEGIN_OF_LINE |
187 * PARSE_STATE_COMMENT |
188 * PARSE_STATE_FIELD_NAME
190 * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
191 * PARSE_STATE_COMMENT |
192 * PARSE_STATE_FIELD_NAME |
193 * PARSE_STATE_BEGIN_OF_LINE
195 * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
196 * PARSE_STATE_BEGIN_OF_LINE
198 * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
199 * PARSE_STATE_BEGIN_OF_LINE |
200 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
202 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
203 * PARSE_STATE_CR_CHAR |
204 * PARSE_STATE_BEGIN_OF_LINE
206 * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
207 * PARSE_STATE_BEGIN_OF_LINE
209 * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
210 * PARSE_STATE_COMMENT |
211 * PARSE_STATE_FIELD_NAME |
212 * PARSE_STATE_BEGIN_OF_LINE
214 * Whenever the parser find an empty line or the end-of-file
215 * it dispatches the stacked event.
218 enum ParserStatus {
219 PARSE_STATE_OFF = 0,
220 PARSE_STATE_BEGIN_OF_STREAM,
221 PARSE_STATE_CR_CHAR,
222 PARSE_STATE_COMMENT,
223 PARSE_STATE_FIELD_NAME,
224 PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
225 PARSE_STATE_FIELD_VALUE,
226 PARSE_STATE_IGNORE_FIELD_VALUE,
227 PARSE_STATE_BEGIN_OF_LINE
230 // Connection related data members. Should only be accessed on main thread.
231 nsCOMPtr<nsIURI> mSrc;
232 uint32_t mReconnectionTime; // in ms
233 nsCOMPtr<nsIPrincipal> mPrincipal;
234 nsString mOrigin;
235 nsCOMPtr<nsITimer> mTimer;
236 nsCOMPtr<nsIHttpChannel> mHttpChannel;
238 struct Message {
239 nsString mEventName;
240 // We need to be able to distinguish between different states of id field:
241 // 1) is not given at all
242 // 2) is given but is empty
243 // 3) is given and has a value
244 // We can't check for the 1st state with a simple nsString.
245 Maybe<nsString> mLastEventID;
246 nsString mData;
249 // Message related data members. May be set / initialized when initializing
250 // EventSourceImpl on target thread but should only be used on target thread.
251 nsString mLastEventID;
252 UniquePtr<Message> mCurrentMessage;
253 nsDeque<Message> mMessagesToDispatch;
254 ParserStatus mStatus;
255 mozilla::UniquePtr<mozilla::Decoder> mUnicodeDecoder;
256 nsString mLastFieldName;
257 nsString mLastFieldValue;
259 // EventSourceImpl internal states.
260 // WorkerRef to keep the worker alive. (accessed on worker thread only)
261 RefPtr<ThreadSafeWorkerRef> mWorkerRef;
262 // Whether the window is frozen. May be set on main thread and read on target
263 // thread.
264 Atomic<bool> mFrozen;
265 // There are some messages are going to be dispatched when thaw.
266 bool mGoingToDispatchAllMessages;
267 // Whether the EventSource is run on main thread.
268 const bool mIsMainThread;
269 // Whether the EventSourceImpl is going to be destroyed.
270 Atomic<bool> mIsShutDown;
272 class EventSourceServiceNotifier final {
273 public:
274 EventSourceServiceNotifier(RefPtr<EventSourceImpl>&& aEventSourceImpl,
275 uint64_t aHttpChannelId, uint64_t aInnerWindowID)
276 : mEventSourceImpl(std::move(aEventSourceImpl)),
277 mHttpChannelId(aHttpChannelId),
278 mInnerWindowID(aInnerWindowID),
279 mConnectionOpened(false) {
280 AssertIsOnMainThread();
281 mService = EventSourceEventService::GetOrCreate();
284 void ConnectionOpened() {
285 mEventSourceImpl->AssertIsOnTargetThread();
286 mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
287 mConnectionOpened = true;
290 void EventReceived(const nsAString& aEventName,
291 const nsAString& aLastEventID, const nsAString& aData,
292 uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
293 mEventSourceImpl->AssertIsOnTargetThread();
294 mService->EventReceived(mHttpChannelId, mInnerWindowID, aEventName,
295 aLastEventID, aData, aRetry, aTimeStamp);
298 ~EventSourceServiceNotifier() {
299 // It is safe to call this on any thread because
300 // EventSourceConnectionClosed method is thread safe and
301 // NS_ReleaseOnMainThread explicitly releases the service on the main
302 // thread.
303 if (mConnectionOpened) {
304 // We want to notify about connection being closed only if we told
305 // it was ever opened. The check is needed if OnStartRequest is called
306 // on the main thread while close() is called on a worker thread.
307 mService->EventSourceConnectionClosed(mHttpChannelId, mInnerWindowID);
309 NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
310 mService.forget());
313 private:
314 RefPtr<EventSourceEventService> mService;
315 RefPtr<EventSourceImpl> mEventSourceImpl;
316 uint64_t mHttpChannelId;
317 uint64_t mInnerWindowID;
318 bool mConnectionOpened;
321 struct SharedData {
322 RefPtr<EventSource> mEventSource;
323 UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
326 DataMutex<SharedData> mSharedData;
328 // Event Source owner information:
329 // - the script file name
330 // - source code line number and column number where the Event Source object
331 // was constructed.
332 // - the ID of the inner window where the script lives. Note that this may not
333 // be the same as the Event Source owner window.
334 // These attributes are used for error reporting. Should only be accessed on
335 // target thread
336 nsString mScriptFile;
337 uint32_t mScriptLine;
338 uint32_t mScriptColumn;
339 uint64_t mInnerWindowID;
341 private:
342 nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
344 // Pointer to the target thread for checking whether we are
345 // on the target thread. This is intentionally a non-owning
346 // pointer in order not to affect the thread destruction
347 // sequence. This pointer must only be compared for equality
348 // and must not be dereferenced.
349 nsIThread* mTargetThread;
351 // prevent bad usage
352 EventSourceImpl(const EventSourceImpl& x) = delete;
353 EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
354 ~EventSourceImpl() {
355 if (IsClosed()) {
356 return;
358 // If we threw during Init we never called Close
359 SetReadyState(CLOSED);
360 CloseInternal();
364 NS_IMPL_ISUPPORTS(EventSourceImpl, nsIObserver, nsIStreamListener,
365 nsIRequestObserver, nsIChannelEventSink,
366 nsIInterfaceRequestor, nsISupportsWeakReference,
367 nsIEventTarget, nsIThreadRetargetableStreamListener,
368 nsITimerCallback, nsINamed)
370 EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
371 nsICookieJarSettings* aCookieJarSettings)
372 : mReconnectionTime(0),
373 mStatus(PARSE_STATE_OFF),
374 mFrozen(false),
375 mGoingToDispatchAllMessages(false),
376 mIsMainThread(NS_IsMainThread()),
377 mIsShutDown(false),
378 mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
379 mScriptLine(0),
380 mScriptColumn(0),
381 mInnerWindowID(0),
382 mCookieJarSettings(aCookieJarSettings),
383 mTargetThread(NS_GetCurrentThread()) {
384 MOZ_ASSERT(aEventSource);
385 SetReadyState(CONNECTING);
388 class CleanupRunnable final : public WorkerMainThreadRunnable {
389 public:
390 explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
391 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
392 "EventSource :: Cleanup"_ns),
393 mESImpl(std::move(aEventSourceImpl)) {
394 MOZ_ASSERT(mESImpl);
395 mWorkerPrivate->AssertIsOnWorkerThread();
398 bool MainThreadRun() override {
399 MOZ_ASSERT(mESImpl);
400 mESImpl->CleanupOnMainThread();
401 // We want to ensure the shortest possible remaining lifetime
402 // and not depend on the Runnable's destruction.
403 mESImpl = nullptr;
404 return true;
407 protected:
408 RefPtr<EventSourceImpl> mESImpl;
411 void EventSourceImpl::Close() {
412 if (IsClosed()) {
413 return;
416 SetReadyState(CLOSED);
417 // CloseInternal potentially kills ourself, ensure
418 // to not access any members afterwards.
419 CloseInternal();
422 void EventSourceImpl::CloseInternal() {
423 AssertIsOnTargetThread();
424 MOZ_ASSERT(IsClosed());
426 RefPtr<EventSource> myES;
428 auto lock = mSharedData.Lock();
429 // We want to ensure to release ourself even if we have
430 // the shutdown case, thus we put aside a pointer
431 // to the EventSource and null it out right now.
432 myES = std::move(lock->mEventSource);
433 lock->mEventSource = nullptr;
434 lock->mServiceNotifier = nullptr;
437 MOZ_ASSERT(!mIsShutDown);
438 if (mIsShutDown) {
439 return;
442 // Invoke CleanupOnMainThread before cleaning any members. It will call
443 // ShutDown, which is supposed to be called before cleaning any members.
444 if (NS_IsMainThread()) {
445 CleanupOnMainThread();
446 } else {
447 ErrorResult rv;
448 // run CleanupOnMainThread synchronously on main thread since it touches
449 // observers and members only can be accessed on main thread.
450 RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
451 runnable->Dispatch(Killing, rv);
452 MOZ_ASSERT(!rv.Failed());
453 ReleaseWorkerRef();
456 while (mMessagesToDispatch.GetSize() != 0) {
457 delete mMessagesToDispatch.PopFront();
459 mFrozen = false;
460 ResetDecoder();
461 mUnicodeDecoder = nullptr;
462 // Release the object on its owner. Don't access to any members
463 // after it.
464 myES->mESImpl = nullptr;
467 void EventSourceImpl::CleanupOnMainThread() {
468 AssertIsOnMainThread();
469 MOZ_ASSERT(IsClosed());
471 // Call ShutDown before cleaning any members.
472 MOZ_ASSERT(!mIsShutDown);
473 mIsShutDown = true;
475 if (mIsMainThread) {
476 RemoveWindowObservers();
479 if (mTimer) {
480 mTimer->Cancel();
481 mTimer = nullptr;
484 ResetConnection();
485 mPrincipal = nullptr;
486 mSrc = nullptr;
489 class InitRunnable final : public WorkerMainThreadRunnable {
490 public:
491 InitRunnable(WorkerPrivate* aWorkerPrivate,
492 RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
493 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
494 mESImpl(std::move(aEventSourceImpl)),
495 mURL(aURL),
496 mRv(NS_ERROR_NOT_INITIALIZED) {
497 MOZ_ASSERT(aWorkerPrivate);
498 aWorkerPrivate->AssertIsOnWorkerThread();
499 MOZ_ASSERT(mESImpl);
502 bool MainThreadRun() override {
503 // Get principal from worker's owner document or from worker.
504 WorkerPrivate* wp = mWorkerPrivate;
505 while (wp->GetParent()) {
506 wp = wp->GetParent();
508 nsPIDOMWindowInner* window = wp->GetWindow();
509 Document* doc = window ? window->GetExtantDoc() : nullptr;
510 nsCOMPtr<nsIPrincipal> principal =
511 doc ? doc->NodePrincipal() : wp->GetPrincipal();
512 if (!principal) {
513 mRv = NS_ERROR_FAILURE;
514 return true;
516 ErrorResult rv;
517 mESImpl->Init(principal, mURL, rv);
518 mRv = rv.StealNSResult();
520 // We want to ensure that EventSourceImpl's lifecycle
521 // does not depend on this Runnable's one.
522 mESImpl = nullptr;
524 return true;
527 nsresult ErrorCode() const { return mRv; }
529 private:
530 RefPtr<EventSourceImpl> mESImpl;
531 const nsAString& mURL;
532 nsresult mRv;
535 class ConnectRunnable final : public WorkerMainThreadRunnable {
536 public:
537 explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
538 RefPtr<EventSourceImpl> aEventSourceImpl)
539 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
540 mESImpl(std::move(aEventSourceImpl)) {
541 MOZ_ASSERT(aWorkerPrivate);
542 aWorkerPrivate->AssertIsOnWorkerThread();
543 MOZ_ASSERT(mESImpl);
546 bool MainThreadRun() override {
547 MOZ_ASSERT(mESImpl);
548 // We are allowed to access the event target since this runnable is
549 // synchronized with the thread the event target lives on.
550 mESImpl->InitChannelAndRequestEventSource(true);
551 // We want to ensure the shortest possible remaining lifetime
552 // and not depend on the Runnable's destruction.
553 mESImpl = nullptr;
554 return true;
557 private:
558 RefPtr<EventSourceImpl> mESImpl;
561 nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
562 AssertIsOnMainThread();
563 MOZ_ASSERT(!mIsShutDown);
564 // get the src
565 nsCOMPtr<nsIURI> baseURI;
566 nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
567 NS_ENSURE_SUCCESS(rv, rv);
569 nsCOMPtr<nsIURI> srcURI;
570 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
571 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
573 nsAutoString origin;
574 rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
575 NS_ENSURE_SUCCESS(rv, rv);
577 nsAutoCString spec;
578 rv = srcURI->GetSpec(spec);
579 NS_ENSURE_SUCCESS(rv, rv);
581 // This assignment doesn't require extra synchronization because this function
582 // is only ever called from EventSourceImpl::Init(), which is either called
583 // directly if mEventSource was created on the main thread, or via a
584 // synchronous runnable if it was created on a worker thread.
586 // We can't use GetEventSource() here because it would modify the refcount,
587 // and that's not allowed off the owning thread.
588 auto lock = mSharedData.Lock();
589 lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
591 mSrc = srcURI;
592 mOrigin = origin;
593 return NS_OK;
596 nsresult EventSourceImpl::AddWindowObservers() {
597 AssertIsOnMainThread();
598 MOZ_ASSERT(mIsMainThread);
599 MOZ_ASSERT(!mIsShutDown);
600 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
601 NS_ENSURE_STATE(os);
603 nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
604 NS_ENSURE_SUCCESS(rv, rv);
605 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
606 NS_ENSURE_SUCCESS(rv, rv);
607 rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
608 NS_ENSURE_SUCCESS(rv, rv);
609 return NS_OK;
612 void EventSourceImpl::RemoveWindowObservers() {
613 AssertIsOnMainThread();
614 MOZ_ASSERT(mIsMainThread);
615 MOZ_ASSERT(IsClosed());
616 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
617 if (os) {
618 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
619 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
620 os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
624 void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
625 ErrorResult& aRv) {
626 AssertIsOnMainThread();
627 MOZ_ASSERT(aPrincipal);
628 MOZ_ASSERT(ReadyState() == CONNECTING);
629 mPrincipal = aPrincipal;
630 aRv = ParseURL(aURL);
631 if (NS_WARN_IF(aRv.Failed())) {
632 return;
634 // The conditional here is historical and not necessarily sane.
635 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
636 nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
637 &mScriptColumn);
638 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
641 if (mIsMainThread) {
642 // we observe when the window freezes and thaws
643 aRv = AddWindowObservers();
644 if (NS_WARN_IF(aRv.Failed())) {
645 return;
649 mReconnectionTime =
650 Preferences::GetInt("dom.server-events.default-reconnection-time",
651 DEFAULT_RECONNECTION_TIME_VALUE);
653 mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
656 //-----------------------------------------------------------------------------
657 // EventSourceImpl::nsIObserver
658 //-----------------------------------------------------------------------------
660 NS_IMETHODIMP
661 EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
662 const char16_t* aData) {
663 AssertIsOnMainThread();
664 if (IsClosed()) {
665 return NS_OK;
668 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
669 MOZ_ASSERT(mIsMainThread);
671 auto lock = mSharedData.Lock();
672 if (!lock->mEventSource->GetOwner() ||
673 window != lock->mEventSource->GetOwner()) {
674 return NS_OK;
678 DebugOnly<nsresult> rv;
679 if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
680 rv = Freeze();
681 MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
682 } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
683 rv = Thaw();
684 MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
685 } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
686 Close();
689 return NS_OK;
692 //-----------------------------------------------------------------------------
693 // EventSourceImpl::nsIStreamListener
694 //-----------------------------------------------------------------------------
696 NS_IMETHODIMP
697 EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
698 AssertIsOnMainThread();
699 if (IsClosed()) {
700 return NS_ERROR_ABORT;
702 nsresult rv = CheckHealthOfRequestCallback(aRequest);
703 NS_ENSURE_SUCCESS(rv, rv);
705 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
706 NS_ENSURE_SUCCESS(rv, rv);
708 nsresult status;
709 rv = aRequest->GetStatus(&status);
710 NS_ENSURE_SUCCESS(rv, rv);
712 if (NS_FAILED(status)) {
713 // EventSource::OnStopRequest will evaluate if it shall either reestablish
714 // or fail the connection
715 return NS_ERROR_ABORT;
718 uint32_t httpStatus;
719 rv = httpChannel->GetResponseStatus(&httpStatus);
720 NS_ENSURE_SUCCESS(rv, rv);
722 if (httpStatus != 200) {
723 DispatchFailConnection();
724 return NS_ERROR_ABORT;
727 nsAutoCString contentType;
728 rv = httpChannel->GetContentType(contentType);
729 NS_ENSURE_SUCCESS(rv, rv);
731 if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
732 DispatchFailConnection();
733 return NS_ERROR_ABORT;
736 if (!mIsMainThread) {
737 // Try to retarget to worker thread, otherwise fall back to main thread.
738 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
739 if (rr) {
740 rv = rr->RetargetDeliveryTo(this);
741 if (NS_WARN_IF(NS_FAILED(rv))) {
742 NS_WARNING("Retargeting failed");
748 auto lock = mSharedData.Lock();
749 lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
750 this, mHttpChannel->ChannelId(), mInnerWindowID);
752 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
753 this, &EventSourceImpl::AnnounceConnection),
754 NS_DISPATCH_NORMAL);
755 NS_ENSURE_SUCCESS(rv, rv);
756 mStatus = PARSE_STATE_BEGIN_OF_STREAM;
757 return NS_OK;
760 // this method parses the characters as they become available instead of
761 // buffering them.
762 nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
763 void* aClosure,
764 const char* aFromRawSegment,
765 uint32_t aToOffset, uint32_t aCount,
766 uint32_t* aWriteCount) {
767 // The EventSourceImpl instance is hold alive on the
768 // synchronously calling stack, so raw pointer is fine here.
769 EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
770 if (!thisObject || !aWriteCount) {
771 NS_WARNING(
772 "EventSource cannot read from stream: no aClosure or aWriteCount");
773 return NS_ERROR_FAILURE;
775 thisObject->AssertIsOnTargetThread();
776 MOZ_ASSERT(!thisObject->mIsShutDown);
777 thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
778 *aWriteCount = aCount;
779 return NS_OK;
782 void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
783 AssertIsOnTargetThread();
784 if (IsClosed()) {
785 return;
787 char16_t buffer[1024];
788 auto dst = Span(buffer);
789 auto src = AsBytes(Span(aBuffer, aLength));
790 // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
791 for (;;) {
792 uint32_t result;
793 size_t read;
794 size_t written;
795 std::tie(result, read, written, std::ignore) =
796 mUnicodeDecoder->DecodeToUTF16(src, dst, false);
797 for (auto c : dst.To(written)) {
798 nsresult rv = ParseCharacter(c);
799 NS_ENSURE_SUCCESS_VOID(rv);
801 if (result == kInputEmpty) {
802 return;
804 src = src.From(read);
808 NS_IMETHODIMP
809 EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
810 nsIInputStream* aInputStream, uint64_t aOffset,
811 uint32_t aCount) {
812 AssertIsOnTargetThread();
813 NS_ENSURE_ARG_POINTER(aInputStream);
814 if (IsClosed()) {
815 return NS_ERROR_ABORT;
818 nsresult rv = CheckHealthOfRequestCallback(aRequest);
819 NS_ENSURE_SUCCESS(rv, rv);
821 uint32_t totalRead;
822 return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
823 aCount, &totalRead);
826 NS_IMETHODIMP
827 EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
828 AssertIsOnMainThread();
830 if (IsClosed()) {
831 return NS_ERROR_ABORT;
833 MOZ_ASSERT(mSrc);
834 // "Network errors that prevents the connection from being established in the
835 // first place (e.g. DNS errors), must cause the user agent to asynchronously
836 // reestablish the connection.
838 // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
839 // response to window.stop() or the user canceling the network connection
840 // manually) must cause the user agent to fail the connection.
841 // There could be additional network errors that are not covered in the above
842 // checks
843 // See Bug 1808511
844 if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
845 aStatusCode != NS_ERROR_NET_TIMEOUT &&
846 aStatusCode != NS_ERROR_NET_RESET &&
847 aStatusCode != NS_ERROR_NET_INTERRUPT &&
848 aStatusCode != NS_ERROR_NET_PARTIAL_TRANSFER &&
849 aStatusCode != NS_ERROR_NET_TIMEOUT_EXTERNAL &&
850 aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
851 aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
852 DispatchFailConnection();
853 return NS_ERROR_ABORT;
856 nsresult rv = CheckHealthOfRequestCallback(aRequest);
857 NS_ENSURE_SUCCESS(rv, rv);
859 rv =
860 Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
861 this, &EventSourceImpl::ReestablishConnection),
862 NS_DISPATCH_NORMAL);
863 NS_ENSURE_SUCCESS(rv, rv);
865 return NS_OK;
868 //-----------------------------------------------------------------------------
869 // EventSourceImpl::nsIChannelEventSink
870 //-----------------------------------------------------------------------------
872 NS_IMETHODIMP
873 EventSourceImpl::AsyncOnChannelRedirect(
874 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
875 nsIAsyncVerifyRedirectCallback* aCallback) {
876 AssertIsOnMainThread();
877 if (IsClosed()) {
878 return NS_ERROR_ABORT;
880 nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
881 MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
883 nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
884 NS_ENSURE_SUCCESS(rv, rv);
886 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
888 nsCOMPtr<nsIURI> newURI;
889 rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
890 NS_ENSURE_SUCCESS(rv, rv);
892 bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
894 rv =
895 mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
896 if (NS_FAILED(rv) || !isValidScheme) {
897 DispatchFailConnection();
898 return NS_ERROR_DOM_SECURITY_ERR;
901 // update our channel
903 mHttpChannel = do_QueryInterface(aNewChannel);
904 NS_ENSURE_STATE(mHttpChannel);
906 SetupHttpChannel();
907 // The HTTP impl already copies over the referrer info on
908 // redirects, so we don't need to SetupReferrerInfo().
910 if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
911 rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
912 NS_ENSURE_SUCCESS(rv, rv);
915 aCallback->OnRedirectVerifyCallback(NS_OK);
917 return NS_OK;
920 //-----------------------------------------------------------------------------
921 // EventSourceImpl::nsIInterfaceRequestor
922 //-----------------------------------------------------------------------------
924 NS_IMETHODIMP
925 EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
926 AssertIsOnMainThread();
928 if (IsClosed()) {
929 return NS_ERROR_FAILURE;
932 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
933 *aResult = static_cast<nsIChannelEventSink*>(this);
934 NS_ADDREF_THIS();
935 return NS_OK;
938 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
939 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
940 nsresult rv;
941 nsCOMPtr<nsIPromptFactory> wwatch =
942 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
943 NS_ENSURE_SUCCESS(rv, rv);
945 nsCOMPtr<nsPIDOMWindowOuter> window;
947 // To avoid a data race we may only access the event target if it lives on
948 // the main thread.
949 if (mIsMainThread) {
950 auto lock = mSharedData.Lock();
951 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
952 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
954 if (lock->mEventSource->GetOwner()) {
955 window = lock->mEventSource->GetOwner()->GetOuterWindow();
959 // Get the an auth prompter for our window so that the parenting
960 // of the dialogs works as it should when using tabs.
962 return wwatch->GetPrompt(window, aIID, aResult);
965 return QueryInterface(aIID, aResult);
968 NS_IMETHODIMP
969 EventSourceImpl::IsOnCurrentThread(bool* aResult) {
970 *aResult = IsTargetThread();
971 return NS_OK;
974 NS_IMETHODIMP_(bool)
975 EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
977 nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
978 AssertIsOnMainThread();
979 MOZ_ASSERT(!mIsShutDown);
980 NS_ENSURE_ARG_POINTER(aBaseURI);
982 *aBaseURI = nullptr;
984 nsCOMPtr<nsIURI> baseURI;
986 // first we try from document->GetBaseURI()
987 nsCOMPtr<Document> doc =
988 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
989 if (doc) {
990 baseURI = doc->GetBaseURI();
993 // otherwise we get from the doc's principal
994 if (!baseURI) {
995 auto* basePrin = BasePrincipal::Cast(mPrincipal);
996 nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
997 NS_ENSURE_SUCCESS(rv, rv);
1000 NS_ENSURE_STATE(baseURI);
1002 baseURI.forget(aBaseURI);
1003 return NS_OK;
1006 void EventSourceImpl::SetupHttpChannel() {
1007 AssertIsOnMainThread();
1008 MOZ_ASSERT(!mIsShutDown);
1009 nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
1010 MOZ_ASSERT(NS_SUCCEEDED(rv));
1012 /* set the http request headers */
1014 rv = mHttpChannel->SetRequestHeader(
1015 "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
1016 MOZ_ASSERT(NS_SUCCEEDED(rv));
1018 // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
1020 if (mLastEventID.IsEmpty()) {
1021 return;
1023 NS_ConvertUTF16toUTF8 eventId(mLastEventID);
1024 rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
1025 #ifdef DEBUG
1026 if (NS_FAILED(rv)) {
1027 MOZ_LOG(gEventSourceLog, LogLevel::Warning,
1028 ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
1030 #endif
1031 Unused << rv;
1034 nsresult EventSourceImpl::SetupReferrerInfo(
1035 const nsCOMPtr<Document>& aDocument) {
1036 AssertIsOnMainThread();
1037 MOZ_ASSERT(!mIsShutDown);
1039 if (aDocument) {
1040 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
1041 nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1042 NS_ENSURE_SUCCESS(rv, rv);
1045 return NS_OK;
1048 nsresult EventSourceImpl::InitChannelAndRequestEventSource(
1049 const bool aEventTargetAccessAllowed) {
1050 AssertIsOnMainThread();
1051 if (IsClosed()) {
1052 return NS_ERROR_ABORT;
1055 bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
1057 MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
1059 nsresult rv = aEventTargetAccessAllowed
1060 ? [this]() {
1061 // We can't call GetEventSource() because we're not
1062 // allowed to touch the refcount off the worker thread
1063 // due to an assertion, event if it would have otherwise
1064 // been safe.
1065 auto lock = mSharedData.Lock();
1066 return lock->mEventSource->CheckCurrentGlobalCorrectness();
1068 : NS_OK;
1069 if (NS_FAILED(rv) || !isValidScheme) {
1070 DispatchFailConnection();
1071 return NS_ERROR_DOM_SECURITY_ERR;
1074 nsCOMPtr<Document> doc;
1075 nsSecurityFlags securityFlags =
1076 nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1078 auto lock = mSharedData.Lock();
1079 doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1080 : nullptr;
1082 if (lock->mEventSource->mWithCredentials) {
1083 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1087 // The html spec requires we use fetch cache mode of "no-store". This
1088 // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
1089 nsLoadFlags loadFlags;
1090 loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
1091 nsIRequest::INHIBIT_CACHING;
1093 nsCOMPtr<nsIChannel> channel;
1094 // If we have the document, use it
1095 if (doc) {
1096 MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
1098 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
1099 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
1100 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1101 nullptr, // aPerformanceStorage
1102 loadGroup,
1103 nullptr, // aCallbacks
1104 loadFlags); // aLoadFlags
1105 } else {
1106 // otherwise use the principal
1107 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
1108 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1109 mCookieJarSettings,
1110 nullptr, // aPerformanceStorage
1111 nullptr, // loadGroup
1112 nullptr, // aCallbacks
1113 loadFlags); // aLoadFlags
1116 NS_ENSURE_SUCCESS(rv, rv);
1118 mHttpChannel = do_QueryInterface(channel);
1119 NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
1121 SetupHttpChannel();
1122 rv = SetupReferrerInfo(doc);
1123 NS_ENSURE_SUCCESS(rv, rv);
1125 #ifdef DEBUG
1127 nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
1128 mHttpChannel->GetNotificationCallbacks(
1129 getter_AddRefs(notificationCallbacks));
1130 MOZ_ASSERT(!notificationCallbacks);
1132 #endif
1134 mHttpChannel->SetNotificationCallbacks(this);
1136 // Start reading from the channel
1137 rv = mHttpChannel->AsyncOpen(this);
1138 if (NS_FAILED(rv)) {
1139 DispatchFailConnection();
1140 return rv;
1143 return rv;
1146 void EventSourceImpl::AnnounceConnection() {
1147 AssertIsOnTargetThread();
1148 if (ReadyState() != CONNECTING) {
1149 NS_WARNING("Unexpected mReadyState!!!");
1150 return;
1154 auto lock = mSharedData.Lock();
1155 if (lock->mServiceNotifier) {
1156 lock->mServiceNotifier->ConnectionOpened();
1160 // When a user agent is to announce the connection, the user agent must set
1161 // the readyState attribute to OPEN and queue a task to fire a simple event
1162 // named open at the EventSource object.
1164 SetReadyState(OPEN);
1166 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1167 if (NS_FAILED(rv)) {
1168 return;
1170 // We can't hold the mutex while dispatching the event because the mutex is
1171 // not reentrant, and content might call back into our code.
1172 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
1173 if (NS_FAILED(rv)) {
1174 NS_WARNING("Failed to dispatch the error event!!!");
1175 return;
1179 nsresult EventSourceImpl::ResetConnection() {
1180 AssertIsOnMainThread();
1181 if (mHttpChannel) {
1182 mHttpChannel->Cancel(NS_ERROR_ABORT);
1183 mHttpChannel = nullptr;
1185 return NS_OK;
1188 void EventSourceImpl::ResetDecoder() {
1189 AssertIsOnTargetThread();
1190 if (mUnicodeDecoder) {
1191 UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
1193 mStatus = PARSE_STATE_OFF;
1194 ClearFields();
1197 class CallRestartConnection final : public WorkerMainThreadRunnable {
1198 public:
1199 explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
1200 : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
1201 "EventSource :: RestartConnection"_ns),
1202 mESImpl(std::move(aEventSourceImpl)) {
1203 mWorkerPrivate->AssertIsOnWorkerThread();
1204 MOZ_ASSERT(mESImpl);
1207 bool MainThreadRun() override {
1208 MOZ_ASSERT(mESImpl);
1209 mESImpl->RestartConnection();
1210 // We want to ensure the shortest possible remaining lifetime
1211 // and not depend on the Runnable's destruction.
1212 mESImpl = nullptr;
1213 return true;
1216 protected:
1217 RefPtr<EventSourceImpl> mESImpl;
1220 nsresult EventSourceImpl::RestartConnection() {
1221 AssertIsOnMainThread();
1222 if (IsClosed()) {
1223 return NS_ERROR_ABORT;
1226 nsresult rv = ResetConnection();
1227 NS_ENSURE_SUCCESS(rv, rv);
1228 rv = SetReconnectionTimeout();
1229 NS_ENSURE_SUCCESS(rv, rv);
1230 return NS_OK;
1233 void EventSourceImpl::ReestablishConnection() {
1234 AssertIsOnTargetThread();
1235 if (IsClosed()) {
1236 return;
1239 nsresult rv;
1240 if (mIsMainThread) {
1241 rv = RestartConnection();
1242 } else {
1243 RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
1244 ErrorResult result;
1245 runnable->Dispatch(Canceling, result);
1246 MOZ_ASSERT(!result.Failed());
1247 rv = result.StealNSResult();
1249 if (NS_FAILED(rv)) {
1250 return;
1253 rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1254 if (NS_FAILED(rv)) {
1255 return;
1258 SetReadyState(CONNECTING);
1259 ResetDecoder();
1260 // We can't hold the mutex while dispatching the event because the mutex is
1261 // not reentrant, and content might call back into our code.
1262 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1263 if (NS_FAILED(rv)) {
1264 NS_WARNING("Failed to dispatch the error event!!!");
1265 return;
1269 nsresult EventSourceImpl::SetReconnectionTimeout() {
1270 AssertIsOnMainThread();
1271 if (IsClosed()) {
1272 return NS_ERROR_ABORT;
1275 // the timer will be used whenever the requests are going finished.
1276 if (!mTimer) {
1277 mTimer = NS_NewTimer();
1278 NS_ENSURE_STATE(mTimer);
1281 MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
1282 nsITimer::TYPE_ONE_SHOT));
1284 return NS_OK;
1287 nsresult EventSourceImpl::PrintErrorOnConsole(
1288 const char* aBundleURI, const char* aError,
1289 const nsTArray<nsString>& aFormatStrings) {
1290 AssertIsOnMainThread();
1291 MOZ_ASSERT(!mIsShutDown);
1292 nsCOMPtr<nsIStringBundleService> bundleService =
1293 mozilla::components::StringBundle::Service();
1294 NS_ENSURE_STATE(bundleService);
1296 nsCOMPtr<nsIStringBundle> strBundle;
1297 nsresult rv =
1298 bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
1299 NS_ENSURE_SUCCESS(rv, rv);
1301 nsCOMPtr<nsIConsoleService> console(
1302 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1303 NS_ENSURE_SUCCESS(rv, rv);
1305 nsCOMPtr<nsIScriptError> errObj(
1306 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
1307 NS_ENSURE_SUCCESS(rv, rv);
1309 // Localize the error message
1310 nsAutoString message;
1311 if (!aFormatStrings.IsEmpty()) {
1312 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
1313 } else {
1314 rv = strBundle->GetStringFromName(aError, message);
1316 NS_ENSURE_SUCCESS(rv, rv);
1318 rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
1319 mScriptColumn, nsIScriptError::errorFlag,
1320 "Event Source", mInnerWindowID);
1321 NS_ENSURE_SUCCESS(rv, rv);
1323 // print the error message directly to the JS console
1324 rv = console->LogMessage(errObj);
1325 NS_ENSURE_SUCCESS(rv, rv);
1327 return NS_OK;
1330 nsresult EventSourceImpl::ConsoleError() {
1331 AssertIsOnMainThread();
1332 MOZ_ASSERT(!mIsShutDown);
1333 nsAutoCString targetSpec;
1334 nsresult rv = mSrc->GetSpec(targetSpec);
1335 NS_ENSURE_SUCCESS(rv, rv);
1337 AutoTArray<nsString, 1> formatStrings;
1338 CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
1340 if (ReadyState() == CONNECTING) {
1341 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1342 "connectionFailure", formatStrings);
1343 } else {
1344 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1345 "netInterrupt", formatStrings);
1347 NS_ENSURE_SUCCESS(rv, rv);
1349 return NS_OK;
1352 void EventSourceImpl::DispatchFailConnection() {
1353 AssertIsOnMainThread();
1354 if (IsClosed()) {
1355 return;
1357 nsresult rv = ConsoleError();
1358 if (NS_FAILED(rv)) {
1359 NS_WARNING("Failed to print to the console error");
1361 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
1362 &EventSourceImpl::FailConnection),
1363 NS_DISPATCH_NORMAL);
1364 if (NS_WARN_IF(NS_FAILED(rv))) {
1365 // if the worker is shutting down, the dispatching of normal WorkerRunnables
1366 // fails.
1367 return;
1371 void EventSourceImpl::FailConnection() {
1372 AssertIsOnTargetThread();
1373 if (IsClosed()) {
1374 return;
1376 // Must change state to closed before firing event to content.
1377 SetReadyState(CLOSED);
1378 // When a user agent is to fail the connection, the user agent must set the
1379 // readyState attribute to CLOSED and queue a task to fire a simple event
1380 // named error at the EventSource object.
1381 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1382 if (NS_SUCCEEDED(rv)) {
1383 // We can't hold the mutex while dispatching the event because the mutex
1384 // is not reentrant, and content might call back into our code.
1385 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1386 if (NS_FAILED(rv)) {
1387 NS_WARNING("Failed to dispatch the error event!!!");
1390 // Call CloseInternal in the end of function because it may release
1391 // EventSourceImpl.
1392 CloseInternal();
1395 NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
1396 AssertIsOnMainThread();
1397 if (IsClosed()) {
1398 return NS_OK;
1401 MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
1403 if (!mFrozen) {
1404 nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
1405 if (NS_FAILED(rv)) {
1406 NS_WARNING("InitChannelAndRequestEventSource() failed");
1409 return NS_OK;
1412 NS_IMETHODIMP EventSourceImpl::GetName(nsACString& aName) {
1413 aName.AssignLiteral("EventSourceImpl");
1414 return NS_OK;
1417 nsresult EventSourceImpl::Thaw() {
1418 AssertIsOnMainThread();
1419 if (IsClosed() || !mFrozen) {
1420 return NS_OK;
1423 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1425 mFrozen = false;
1426 nsresult rv;
1427 if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
1428 nsCOMPtr<nsIRunnable> event =
1429 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1430 this, &EventSourceImpl::DispatchAllMessageEvents);
1431 NS_ENSURE_STATE(event);
1433 mGoingToDispatchAllMessages = true;
1435 rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1436 NS_ENSURE_SUCCESS(rv, rv);
1439 rv = InitChannelAndRequestEventSource(mIsMainThread);
1440 NS_ENSURE_SUCCESS(rv, rv);
1442 return NS_OK;
1445 nsresult EventSourceImpl::Freeze() {
1446 AssertIsOnMainThread();
1447 if (IsClosed() || mFrozen) {
1448 return NS_OK;
1451 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1452 mFrozen = true;
1453 return NS_OK;
1456 nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
1457 AssertIsOnTargetThread();
1458 MOZ_ASSERT(!mIsShutDown);
1459 UniquePtr<Message> message(std::move(mCurrentMessage));
1460 ClearFields();
1462 if (!message || message->mData.IsEmpty()) {
1463 return NS_OK;
1466 // removes the trailing LF from mData
1467 MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
1468 "Invalid trailing character! LF was expected instead.");
1469 message->mData.SetLength(message->mData.Length() - 1);
1471 if (message->mEventName.IsEmpty()) {
1472 message->mEventName.AssignLiteral("message");
1475 mMessagesToDispatch.Push(message.release());
1477 if (!mGoingToDispatchAllMessages) {
1478 nsCOMPtr<nsIRunnable> event =
1479 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1480 this, &EventSourceImpl::DispatchAllMessageEvents);
1481 NS_ENSURE_STATE(event);
1483 mGoingToDispatchAllMessages = true;
1485 return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1488 return NS_OK;
1491 void EventSourceImpl::DispatchAllMessageEvents() {
1492 AssertIsOnTargetThread();
1493 mGoingToDispatchAllMessages = false;
1495 if (IsClosed() || mFrozen) {
1496 return;
1499 nsresult rv;
1500 AutoJSAPI jsapi;
1502 auto lock = mSharedData.Lock();
1503 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1504 if (NS_FAILED(rv)) {
1505 return;
1508 if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1509 return;
1513 JSContext* cx = jsapi.cx();
1515 while (mMessagesToDispatch.GetSize() > 0) {
1516 UniquePtr<Message> message(mMessagesToDispatch.PopFront());
1518 if (message->mLastEventID.isSome()) {
1519 mLastEventID.Assign(message->mLastEventID.value());
1522 if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
1523 message->mLastEventID = Some(mLastEventID);
1527 auto lock = mSharedData.Lock();
1528 if (lock->mServiceNotifier) {
1529 lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1530 message->mData, mReconnectionTime,
1531 PR_Now());
1535 // Now we can turn our string into a jsval
1536 JS::Rooted<JS::Value> jsData(cx);
1538 JSString* jsString;
1539 jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
1540 message->mData.Length());
1541 NS_ENSURE_TRUE_VOID(jsString);
1543 jsData.setString(jsString);
1546 // create an event that uses the MessageEvent interface,
1547 // which does not bubble, is not cancelable, and has no default action
1549 RefPtr<EventSource> eventSource = GetEventSource();
1550 RefPtr<MessageEvent> event =
1551 new MessageEvent(eventSource, nullptr, nullptr);
1553 event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
1554 Cancelable::eNo, jsData, mOrigin, mLastEventID,
1555 nullptr, Sequence<OwningNonNull<MessagePort>>());
1556 event->SetTrusted(true);
1558 // We can't hold the mutex while dispatching the event because the mutex is
1559 // not reentrant, and content might call back into our code.
1560 IgnoredErrorResult err;
1561 eventSource->DispatchEvent(*event, err);
1562 if (err.Failed()) {
1563 NS_WARNING("Failed to dispatch the message event!!!");
1564 return;
1567 if (IsClosed() || mFrozen) {
1568 return;
1573 void EventSourceImpl::ClearFields() {
1574 AssertIsOnTargetThread();
1575 mCurrentMessage = nullptr;
1576 mLastFieldName.Truncate();
1577 mLastFieldValue.Truncate();
1580 nsresult EventSourceImpl::SetFieldAndClear() {
1581 MOZ_ASSERT(!mIsShutDown);
1582 AssertIsOnTargetThread();
1583 if (mLastFieldName.IsEmpty()) {
1584 mLastFieldValue.Truncate();
1585 return NS_OK;
1587 if (!mCurrentMessage) {
1588 mCurrentMessage = MakeUnique<Message>();
1590 char16_t first_char;
1591 first_char = mLastFieldName.CharAt(0);
1593 // with no case folding performed
1594 switch (first_char) {
1595 case char16_t('d'):
1596 if (mLastFieldName.EqualsLiteral("data")) {
1597 // If the field name is "data" append the field value to the data
1598 // buffer, then append a single U+000A LINE FEED (LF) character
1599 // to the data buffer.
1600 mCurrentMessage->mData.Append(mLastFieldValue);
1601 mCurrentMessage->mData.Append(LF_CHAR);
1603 break;
1605 case char16_t('e'):
1606 if (mLastFieldName.EqualsLiteral("event")) {
1607 mCurrentMessage->mEventName.Assign(mLastFieldValue);
1609 break;
1611 case char16_t('i'):
1612 if (mLastFieldName.EqualsLiteral("id")) {
1613 mCurrentMessage->mLastEventID = Some(mLastFieldValue);
1615 break;
1617 case char16_t('r'):
1618 if (mLastFieldName.EqualsLiteral("retry")) {
1619 uint32_t newValue = 0;
1620 uint32_t i = 0; // we must ensure that there are only digits
1621 bool assign = true;
1622 for (i = 0; i < mLastFieldValue.Length(); ++i) {
1623 if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
1624 mLastFieldValue.CharAt(i) > (char16_t)'9') {
1625 assign = false;
1626 break;
1628 newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
1629 ((uint32_t)((char16_t)'0')));
1632 if (assign) {
1633 if (newValue < MIN_RECONNECTION_TIME_VALUE) {
1634 mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
1635 } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
1636 mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
1637 } else {
1638 mReconnectionTime = newValue;
1641 break;
1643 break;
1646 mLastFieldName.Truncate();
1647 mLastFieldValue.Truncate();
1649 return NS_OK;
1652 nsresult EventSourceImpl::CheckHealthOfRequestCallback(
1653 nsIRequest* aRequestCallback) {
1654 // This function could be run on target thread if http channel support
1655 // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
1657 // check if we have been closed or if the request has been canceled
1658 // or if we have been frozen
1659 if (IsClosed() || mFrozen || !mHttpChannel) {
1660 return NS_ERROR_ABORT;
1663 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
1664 NS_ENSURE_STATE(httpChannel);
1666 if (httpChannel != mHttpChannel) {
1667 NS_WARNING("wrong channel from request callback");
1668 return NS_ERROR_ABORT;
1671 return NS_OK;
1674 nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
1675 AssertIsOnTargetThread();
1676 nsresult rv;
1678 if (IsClosed()) {
1679 return NS_ERROR_ABORT;
1682 switch (mStatus) {
1683 case PARSE_STATE_OFF:
1684 NS_ERROR("Invalid state");
1685 return NS_ERROR_FAILURE;
1686 break;
1688 case PARSE_STATE_BEGIN_OF_STREAM:
1689 if (aChr == CR_CHAR) {
1690 mStatus = PARSE_STATE_CR_CHAR;
1691 } else if (aChr == LF_CHAR) {
1692 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1693 } else if (aChr == COLON_CHAR) {
1694 mStatus = PARSE_STATE_COMMENT;
1695 } else {
1696 mLastFieldName += aChr;
1697 mStatus = PARSE_STATE_FIELD_NAME;
1699 break;
1701 case PARSE_STATE_CR_CHAR:
1702 if (aChr == CR_CHAR) {
1703 rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
1704 NS_ENSURE_SUCCESS(rv, rv);
1705 } else if (aChr == LF_CHAR) {
1706 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1707 } else if (aChr == COLON_CHAR) {
1708 mStatus = PARSE_STATE_COMMENT;
1709 } else {
1710 mLastFieldName += aChr;
1711 mStatus = PARSE_STATE_FIELD_NAME;
1714 break;
1716 case PARSE_STATE_COMMENT:
1717 if (aChr == CR_CHAR) {
1718 mStatus = PARSE_STATE_CR_CHAR;
1719 } else if (aChr == LF_CHAR) {
1720 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1723 break;
1725 case PARSE_STATE_FIELD_NAME:
1726 if (aChr == CR_CHAR) {
1727 rv = SetFieldAndClear();
1728 NS_ENSURE_SUCCESS(rv, rv);
1730 mStatus = PARSE_STATE_CR_CHAR;
1731 } else if (aChr == LF_CHAR) {
1732 rv = SetFieldAndClear();
1733 NS_ENSURE_SUCCESS(rv, rv);
1735 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1736 } else if (aChr == COLON_CHAR) {
1737 mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
1738 } else {
1739 mLastFieldName += aChr;
1742 break;
1744 case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
1745 if (aChr == CR_CHAR) {
1746 rv = SetFieldAndClear();
1747 NS_ENSURE_SUCCESS(rv, rv);
1749 mStatus = PARSE_STATE_CR_CHAR;
1750 } else if (aChr == LF_CHAR) {
1751 rv = SetFieldAndClear();
1752 NS_ENSURE_SUCCESS(rv, rv);
1754 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1755 } else if (aChr == SPACE_CHAR) {
1756 mStatus = PARSE_STATE_FIELD_VALUE;
1757 } else {
1758 mLastFieldValue += aChr;
1759 mStatus = PARSE_STATE_FIELD_VALUE;
1762 break;
1764 case PARSE_STATE_FIELD_VALUE:
1765 if (aChr == CR_CHAR) {
1766 rv = SetFieldAndClear();
1767 NS_ENSURE_SUCCESS(rv, rv);
1769 mStatus = PARSE_STATE_CR_CHAR;
1770 } else if (aChr == LF_CHAR) {
1771 rv = SetFieldAndClear();
1772 NS_ENSURE_SUCCESS(rv, rv);
1774 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1775 } else if (aChr != 0) {
1776 // Avoid appending the null char to the field value.
1777 mLastFieldValue += aChr;
1778 } else if (mLastFieldName.EqualsLiteral("id")) {
1779 // Ignore the whole id field if aChr is null
1780 mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
1781 mLastFieldValue.Truncate();
1784 break;
1786 case PARSE_STATE_IGNORE_FIELD_VALUE:
1787 if (aChr == CR_CHAR) {
1788 mStatus = PARSE_STATE_CR_CHAR;
1789 } else if (aChr == LF_CHAR) {
1790 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1792 break;
1794 case PARSE_STATE_BEGIN_OF_LINE:
1795 if (aChr == CR_CHAR) {
1796 rv = DispatchCurrentMessageEvent(); // there is an empty line
1797 NS_ENSURE_SUCCESS(rv, rv);
1799 mStatus = PARSE_STATE_CR_CHAR;
1800 } else if (aChr == LF_CHAR) {
1801 rv = DispatchCurrentMessageEvent(); // there is an empty line
1802 NS_ENSURE_SUCCESS(rv, rv);
1804 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1805 } else if (aChr == COLON_CHAR) {
1806 mStatus = PARSE_STATE_COMMENT;
1807 } else if (aChr != 0) {
1808 // Avoid appending the null char to the field name.
1809 mLastFieldName += aChr;
1810 mStatus = PARSE_STATE_FIELD_NAME;
1813 break;
1816 return NS_OK;
1819 namespace {
1821 class WorkerRunnableDispatcher final : public WorkerRunnable {
1822 RefPtr<EventSourceImpl> mEventSourceImpl;
1824 public:
1825 WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
1826 WorkerPrivate* aWorkerPrivate,
1827 already_AddRefed<nsIRunnable> aEvent)
1828 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
1829 mEventSourceImpl(std::move(aImpl)),
1830 mEvent(std::move(aEvent)) {}
1832 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1833 aWorkerPrivate->AssertIsOnWorkerThread();
1834 return !NS_FAILED(mEvent->Run());
1837 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
1838 bool aRunResult) override {
1839 // Ensure we drop the RefPtr on the worker thread
1840 // and to not keep us alive longer than needed.
1841 mEventSourceImpl = nullptr;
1844 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
1845 // We don't call WorkerRunnable::PreDispatch because it would assert the
1846 // wrong thing about which thread we're on. We're on whichever thread the
1847 // channel implementation is running on (probably the main thread or
1848 // transport thread).
1849 return true;
1852 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1853 bool aDispatchResult) override {
1854 // We don't call WorkerRunnable::PostDispatch because it would assert the
1855 // wrong thing about which thread we're on. We're on whichever thread the
1856 // channel implementation is running on (probably the main thread or
1857 // transport thread).
1860 private:
1861 nsCOMPtr<nsIRunnable> mEvent;
1864 } // namespace
1866 bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
1867 MOZ_ASSERT(!mWorkerRef);
1868 MOZ_ASSERT(aWorkerPrivate);
1869 aWorkerPrivate->AssertIsOnWorkerThread();
1871 if (mIsShutDown) {
1872 return false;
1875 RefPtr<EventSourceImpl> self = this;
1876 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
1877 aWorkerPrivate, "EventSource", [self]() { self->Close(); });
1879 if (NS_WARN_IF(!workerRef)) {
1880 return false;
1883 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
1884 return true;
1887 void EventSourceImpl::ReleaseWorkerRef() {
1888 MOZ_ASSERT(IsClosed());
1889 MOZ_ASSERT(IsCurrentThreadRunningWorker());
1890 mWorkerRef = nullptr;
1893 //-----------------------------------------------------------------------------
1894 // EventSourceImpl::nsIEventTarget
1895 //-----------------------------------------------------------------------------
1896 NS_IMETHODIMP
1897 EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
1898 nsCOMPtr<nsIRunnable> event(aEvent);
1899 return Dispatch(event.forget(), aFlags);
1902 NS_IMETHODIMP
1903 EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
1904 uint32_t aFlags) {
1905 nsCOMPtr<nsIRunnable> event_ref(aEvent);
1906 if (mIsMainThread) {
1907 return NS_DispatchToMainThread(event_ref.forget());
1910 if (mIsShutDown) {
1911 // We want to avoid clutter about errors in our shutdown logs,
1912 // so just report NS_OK (we have no explicit return value
1913 // for shutdown).
1914 return NS_OK;
1917 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
1918 // runnable.
1919 RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
1920 this, mWorkerRef->Private(), event_ref.forget());
1922 if (!event->Dispatch()) {
1923 return NS_ERROR_FAILURE;
1925 return NS_OK;
1928 NS_IMETHODIMP
1929 EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
1930 uint32_t aDelayMs) {
1931 return NS_ERROR_NOT_IMPLEMENTED;
1934 NS_IMETHODIMP
1935 EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
1936 return NS_ERROR_NOT_IMPLEMENTED;
1939 NS_IMETHODIMP
1940 EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
1941 return NS_ERROR_NOT_IMPLEMENTED;
1944 //-----------------------------------------------------------------------------
1945 // EventSourceImpl::nsIThreadRetargetableStreamListener
1946 //-----------------------------------------------------------------------------
1947 NS_IMETHODIMP
1948 EventSourceImpl::CheckListenerChain() {
1949 MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
1950 return NS_OK;
1952 ////////////////////////////////////////////////////////////////////////////////
1953 // EventSource
1954 ////////////////////////////////////////////////////////////////////////////////
1956 EventSource::EventSource(nsIGlobalObject* aGlobal,
1957 nsICookieJarSettings* aCookieJarSettings,
1958 bool aWithCredentials)
1959 : DOMEventTargetHelper(aGlobal),
1960 mWithCredentials(aWithCredentials),
1961 mIsMainThread(NS_IsMainThread()) {
1962 MOZ_ASSERT(aGlobal);
1963 MOZ_ASSERT(aCookieJarSettings);
1964 mESImpl = new EventSourceImpl(this, aCookieJarSettings);
1967 EventSource::~EventSource() = default;
1969 nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1970 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1971 // it doesn't bubble, and it isn't cancelable
1972 event->InitEvent(aName, false, false);
1973 event->SetTrusted(true);
1974 ErrorResult rv;
1975 DispatchEvent(*event, rv);
1976 return rv.StealNSResult();
1979 /* static */
1980 already_AddRefed<EventSource> EventSource::Constructor(
1981 const GlobalObject& aGlobal, const nsAString& aURL,
1982 const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
1983 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1984 if (NS_WARN_IF(!global)) {
1985 aRv.Throw(NS_ERROR_FAILURE);
1986 return nullptr;
1989 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1990 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
1991 if (ownerWindow) {
1992 Document* doc = ownerWindow->GetExtantDoc();
1993 if (NS_WARN_IF(!doc)) {
1994 aRv.Throw(NS_ERROR_FAILURE);
1995 return nullptr;
1998 cookieJarSettings = doc->CookieJarSettings();
1999 } else {
2000 // Worker side.
2001 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2002 if (!workerPrivate) {
2003 aRv.Throw(NS_ERROR_FAILURE);
2004 return nullptr;
2007 cookieJarSettings = workerPrivate->CookieJarSettings();
2010 RefPtr<EventSource> eventSource = new EventSource(
2011 global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
2013 if (NS_IsMainThread()) {
2014 // Get principal from document and init EventSourceImpl
2015 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
2016 do_QueryInterface(aGlobal.GetAsSupports());
2017 if (!scriptPrincipal) {
2018 aRv.Throw(NS_ERROR_FAILURE);
2019 return nullptr;
2021 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
2022 if (!principal) {
2023 aRv.Throw(NS_ERROR_FAILURE);
2024 return nullptr;
2026 eventSource->mESImpl->Init(principal, aURL, aRv);
2027 if (NS_WARN_IF(aRv.Failed())) {
2028 return nullptr;
2031 eventSource->mESImpl->InitChannelAndRequestEventSource(true);
2032 return eventSource.forget();
2035 // Worker side.
2037 // Scope for possible failures that need cleanup
2038 auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
2040 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2041 MOZ_ASSERT(workerPrivate);
2043 eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
2045 RefPtr<InitRunnable> initRunnable =
2046 new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
2047 initRunnable->Dispatch(Canceling, aRv);
2048 if (NS_WARN_IF(aRv.Failed())) {
2049 return nullptr;
2052 aRv = initRunnable->ErrorCode();
2053 if (NS_WARN_IF(aRv.Failed())) {
2054 return nullptr;
2057 // In workers we have to keep the worker alive using a WorkerRef in order
2058 // to dispatch messages correctly.
2059 // Note, initRunnable->Dispatch may have cleared mESImpl.
2060 if (!eventSource->mESImpl ||
2061 !eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
2062 // The worker is already shutting down. Let's return an already closed
2063 // object, but marked as Connecting.
2064 if (eventSource->mESImpl) {
2065 // mESImpl is nulled by this call such that EventSourceImpl is
2066 // released before returning the object, otherwise
2067 // it will set EventSource to a CLOSED state in its DTOR..
2068 eventSource->mESImpl->Close();
2070 eventSource->mReadyState = EventSourceImpl::CONNECTING;
2072 guardESImpl.release();
2073 return eventSource.forget();
2076 // Let's connect to the server.
2077 RefPtr<ConnectRunnable> connectRunnable =
2078 new ConnectRunnable(workerPrivate, eventSource->mESImpl);
2079 connectRunnable->Dispatch(Canceling, aRv);
2080 if (NS_WARN_IF(aRv.Failed())) {
2081 return nullptr;
2084 // End of scope for possible failures
2085 guardESImpl.release();
2088 return eventSource.forget();
2091 // nsWrapperCache
2092 JSObject* EventSource::WrapObject(JSContext* aCx,
2093 JS::Handle<JSObject*> aGivenProto) {
2094 return EventSource_Binding::Wrap(aCx, this, aGivenProto);
2097 void EventSource::Close() {
2098 AssertIsOnTargetThread();
2099 if (mESImpl) {
2100 // Close potentially kills ourself, ensure
2101 // to not access any members afterwards.
2102 mESImpl->Close();
2106 //-----------------------------------------------------------------------------
2107 // EventSource::nsISupports
2108 //-----------------------------------------------------------------------------
2110 NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
2112 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
2113 DOMEventTargetHelper)
2114 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2116 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
2117 DOMEventTargetHelper)
2118 if (tmp->mESImpl) {
2119 // IsCertainlyaliveForCC will return true and cause the cycle
2120 // collector to skip this instance when mESImpl is non-null and
2121 // points back to ourself.
2122 // mESImpl is initialized to be non-null in the constructor
2123 // and should have been wiped out in our close function.
2124 MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
2125 tmp->Close();
2127 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2129 bool EventSource::IsCertainlyAliveForCC() const {
2130 // Until we are double linked forth and back, we want to stay alive.
2131 if (!mESImpl) {
2132 return false;
2134 auto lock = mESImpl->mSharedData.Lock();
2135 return lock->mEventSource == this;
2138 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
2139 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
2141 NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
2142 NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
2144 } // namespace mozilla::dom