Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / base / EventSource.cpp
blobdef3c90ec0a92141ccb8e81e4595e58e5ce2d637
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/Try.h"
25 #include "mozilla/UniquePtrExtensions.h"
26 #include "nsComponentManagerUtils.h"
27 #include "nsIThreadRetargetableStreamListener.h"
28 #include "nsNetUtil.h"
29 #include "nsIAuthPrompt.h"
30 #include "nsIAuthPrompt2.h"
31 #include "nsIHttpChannel.h"
32 #include "nsIInputStream.h"
33 #include "nsIInterfaceRequestorUtils.h"
34 #include "nsMimeTypes.h"
35 #include "nsIPromptFactory.h"
36 #include "nsIWindowWatcher.h"
37 #include "nsPresContext.h"
38 #include "nsProxyRelease.h"
39 #include "nsContentPolicyUtils.h"
40 #include "nsIStringBundle.h"
41 #include "nsIConsoleService.h"
42 #include "nsIObserverService.h"
43 #include "nsIScriptObjectPrincipal.h"
44 #include "nsJSUtils.h"
45 #include "nsIThreadRetargetableRequest.h"
46 #include "nsIAsyncVerifyRedirectCallback.h"
47 #include "nsIScriptError.h"
48 #include "nsContentUtils.h"
49 #include "mozilla/Preferences.h"
50 #include "xpcpublic.h"
51 #include "nsWrapperCacheInlines.h"
52 #include "mozilla/Attributes.h"
53 #include "nsError.h"
54 #include "mozilla/Encoding.h"
55 #include "ReferrerInfo.h"
57 namespace mozilla::dom {
59 #ifdef DEBUG
60 static LazyLogModule gEventSourceLog("EventSource");
61 #endif
63 #define SPACE_CHAR (char16_t)0x0020
64 #define CR_CHAR (char16_t)0x000D
65 #define LF_CHAR (char16_t)0x000A
66 #define COLON_CHAR (char16_t)0x003A
68 // Reconnection time related values in milliseconds. The default one is equal
69 // to the default value of the pref dom.server-events.default-reconnection-time
70 #define MIN_RECONNECTION_TIME_VALUE 500
71 #define DEFAULT_RECONNECTION_TIME_VALUE 5000
72 #define MAX_RECONNECTION_TIME_VALUE \
73 PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
75 class EventSourceImpl final : public nsIObserver,
76 public nsIChannelEventSink,
77 public nsIInterfaceRequestor,
78 public nsSupportsWeakReference,
79 public nsISerialEventTarget,
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 nsISerialEventTarget, nsIEventTarget,
368 nsIThreadRetargetableStreamListener, nsITimerCallback,
369 nsINamed)
371 EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
372 nsICookieJarSettings* aCookieJarSettings)
373 : mReconnectionTime(0),
374 mStatus(PARSE_STATE_OFF),
375 mFrozen(false),
376 mGoingToDispatchAllMessages(false),
377 mIsMainThread(NS_IsMainThread()),
378 mIsShutDown(false),
379 mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
380 mScriptLine(0),
381 mScriptColumn(1),
382 mInnerWindowID(0),
383 mCookieJarSettings(aCookieJarSettings),
384 mTargetThread(NS_GetCurrentThread()) {
385 MOZ_ASSERT(aEventSource);
386 SetReadyState(CONNECTING);
389 class CleanupRunnable final : public WorkerMainThreadRunnable {
390 public:
391 explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
392 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
393 "EventSource :: Cleanup"_ns),
394 mESImpl(std::move(aEventSourceImpl)) {
395 MOZ_ASSERT(mESImpl);
396 mWorkerPrivate->AssertIsOnWorkerThread();
399 bool MainThreadRun() override {
400 MOZ_ASSERT(mESImpl);
401 mESImpl->CleanupOnMainThread();
402 // We want to ensure the shortest possible remaining lifetime
403 // and not depend on the Runnable's destruction.
404 mESImpl = nullptr;
405 return true;
408 protected:
409 RefPtr<EventSourceImpl> mESImpl;
412 void EventSourceImpl::Close() {
413 if (IsClosed()) {
414 return;
417 SetReadyState(CLOSED);
418 // CloseInternal potentially kills ourself, ensure
419 // to not access any members afterwards.
420 CloseInternal();
423 void EventSourceImpl::CloseInternal() {
424 AssertIsOnTargetThread();
425 MOZ_ASSERT(IsClosed());
427 RefPtr<EventSource> myES;
429 auto lock = mSharedData.Lock();
430 // We want to ensure to release ourself even if we have
431 // the shutdown case, thus we put aside a pointer
432 // to the EventSource and null it out right now.
433 myES = std::move(lock->mEventSource);
434 lock->mEventSource = nullptr;
435 lock->mServiceNotifier = nullptr;
438 MOZ_ASSERT(!mIsShutDown);
439 if (mIsShutDown) {
440 return;
443 // Invoke CleanupOnMainThread before cleaning any members. It will call
444 // ShutDown, which is supposed to be called before cleaning any members.
445 if (NS_IsMainThread()) {
446 CleanupOnMainThread();
447 } else {
448 ErrorResult rv;
449 // run CleanupOnMainThread synchronously on main thread since it touches
450 // observers and members only can be accessed on main thread.
451 RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
452 runnable->Dispatch(Killing, rv);
453 MOZ_ASSERT(!rv.Failed());
454 ReleaseWorkerRef();
457 while (mMessagesToDispatch.GetSize() != 0) {
458 delete mMessagesToDispatch.PopFront();
460 mFrozen = false;
461 ResetDecoder();
462 mUnicodeDecoder = nullptr;
463 // Release the object on its owner. Don't access to any members
464 // after it.
465 myES->mESImpl = nullptr;
468 void EventSourceImpl::CleanupOnMainThread() {
469 AssertIsOnMainThread();
470 MOZ_ASSERT(IsClosed());
472 // Call ShutDown before cleaning any members.
473 MOZ_ASSERT(!mIsShutDown);
474 mIsShutDown = true;
476 if (mIsMainThread) {
477 RemoveWindowObservers();
480 if (mTimer) {
481 mTimer->Cancel();
482 mTimer = nullptr;
485 ResetConnection();
486 mPrincipal = nullptr;
487 mSrc = nullptr;
490 class InitRunnable final : public WorkerMainThreadRunnable {
491 public:
492 InitRunnable(WorkerPrivate* aWorkerPrivate,
493 RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
494 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
495 mESImpl(std::move(aEventSourceImpl)),
496 mURL(aURL),
497 mRv(NS_ERROR_NOT_INITIALIZED) {
498 MOZ_ASSERT(aWorkerPrivate);
499 aWorkerPrivate->AssertIsOnWorkerThread();
500 MOZ_ASSERT(mESImpl);
503 bool MainThreadRun() override {
504 // Get principal from worker's owner document or from worker.
505 WorkerPrivate* wp = mWorkerPrivate;
506 while (wp->GetParent()) {
507 wp = wp->GetParent();
509 nsPIDOMWindowInner* window = wp->GetWindow();
510 Document* doc = window ? window->GetExtantDoc() : nullptr;
511 nsCOMPtr<nsIPrincipal> principal =
512 doc ? doc->NodePrincipal() : wp->GetPrincipal();
513 if (!principal) {
514 mRv = NS_ERROR_FAILURE;
515 return true;
517 ErrorResult rv;
518 mESImpl->Init(principal, mURL, rv);
519 mRv = rv.StealNSResult();
521 // We want to ensure that EventSourceImpl's lifecycle
522 // does not depend on this Runnable's one.
523 mESImpl = nullptr;
525 return true;
528 nsresult ErrorCode() const { return mRv; }
530 private:
531 RefPtr<EventSourceImpl> mESImpl;
532 const nsAString& mURL;
533 nsresult mRv;
536 class ConnectRunnable final : public WorkerMainThreadRunnable {
537 public:
538 explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
539 RefPtr<EventSourceImpl> aEventSourceImpl)
540 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
541 mESImpl(std::move(aEventSourceImpl)) {
542 MOZ_ASSERT(aWorkerPrivate);
543 aWorkerPrivate->AssertIsOnWorkerThread();
544 MOZ_ASSERT(mESImpl);
547 bool MainThreadRun() override {
548 MOZ_ASSERT(mESImpl);
549 // We are allowed to access the event target since this runnable is
550 // synchronized with the thread the event target lives on.
551 mESImpl->InitChannelAndRequestEventSource(true);
552 // We want to ensure the shortest possible remaining lifetime
553 // and not depend on the Runnable's destruction.
554 mESImpl = nullptr;
555 return true;
558 private:
559 RefPtr<EventSourceImpl> mESImpl;
562 nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
563 AssertIsOnMainThread();
564 MOZ_ASSERT(!mIsShutDown);
565 // get the src
566 nsCOMPtr<nsIURI> baseURI;
567 nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
568 NS_ENSURE_SUCCESS(rv, rv);
570 nsCOMPtr<nsIURI> srcURI;
571 nsCOMPtr<Document> doc =
572 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
573 if (doc) {
574 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, doc->GetDocumentCharacterSet(),
575 baseURI);
576 } else {
577 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
580 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
582 nsAutoString origin;
583 rv = nsContentUtils::GetWebExposedOriginSerialization(srcURI, origin);
584 NS_ENSURE_SUCCESS(rv, rv);
586 nsAutoCString spec;
587 rv = srcURI->GetSpec(spec);
588 NS_ENSURE_SUCCESS(rv, rv);
590 // This assignment doesn't require extra synchronization because this function
591 // is only ever called from EventSourceImpl::Init(), which is either called
592 // directly if mEventSource was created on the main thread, or via a
593 // synchronous runnable if it was created on a worker thread.
595 // We can't use GetEventSource() here because it would modify the refcount,
596 // and that's not allowed off the owning thread.
597 auto lock = mSharedData.Lock();
598 lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
600 mSrc = srcURI;
601 mOrigin = origin;
602 return NS_OK;
605 nsresult EventSourceImpl::AddWindowObservers() {
606 AssertIsOnMainThread();
607 MOZ_ASSERT(mIsMainThread);
608 MOZ_ASSERT(!mIsShutDown);
609 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
610 NS_ENSURE_STATE(os);
612 nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
613 NS_ENSURE_SUCCESS(rv, rv);
614 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
615 NS_ENSURE_SUCCESS(rv, rv);
616 rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
617 NS_ENSURE_SUCCESS(rv, rv);
618 return NS_OK;
621 void EventSourceImpl::RemoveWindowObservers() {
622 AssertIsOnMainThread();
623 MOZ_ASSERT(mIsMainThread);
624 MOZ_ASSERT(IsClosed());
625 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
626 if (os) {
627 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
628 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
629 os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
633 void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
634 ErrorResult& aRv) {
635 AssertIsOnMainThread();
636 MOZ_ASSERT(aPrincipal);
637 MOZ_ASSERT(ReadyState() == CONNECTING);
638 mPrincipal = aPrincipal;
639 aRv = ParseURL(aURL);
640 if (NS_WARN_IF(aRv.Failed())) {
641 return;
643 // The conditional here is historical and not necessarily sane.
644 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
645 nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
646 &mScriptColumn);
647 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
650 if (mIsMainThread) {
651 // we observe when the window freezes and thaws
652 aRv = AddWindowObservers();
653 if (NS_WARN_IF(aRv.Failed())) {
654 return;
658 mReconnectionTime =
659 Preferences::GetInt("dom.server-events.default-reconnection-time",
660 DEFAULT_RECONNECTION_TIME_VALUE);
662 mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
665 //-----------------------------------------------------------------------------
666 // EventSourceImpl::nsIObserver
667 //-----------------------------------------------------------------------------
669 NS_IMETHODIMP
670 EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
671 const char16_t* aData) {
672 AssertIsOnMainThread();
673 if (IsClosed()) {
674 return NS_OK;
677 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
678 MOZ_ASSERT(mIsMainThread);
680 auto lock = mSharedData.Lock();
681 if (!lock->mEventSource->GetOwner() ||
682 window != lock->mEventSource->GetOwner()) {
683 return NS_OK;
687 DebugOnly<nsresult> rv;
688 if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
689 rv = Freeze();
690 MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
691 } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
692 rv = Thaw();
693 MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
694 } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
695 Close();
698 return NS_OK;
701 //-----------------------------------------------------------------------------
702 // EventSourceImpl::nsIStreamListener
703 //-----------------------------------------------------------------------------
705 NS_IMETHODIMP
706 EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
707 AssertIsOnMainThread();
708 if (IsClosed()) {
709 return NS_ERROR_ABORT;
711 nsresult rv = CheckHealthOfRequestCallback(aRequest);
712 NS_ENSURE_SUCCESS(rv, rv);
714 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
715 NS_ENSURE_SUCCESS(rv, rv);
717 nsresult status;
718 rv = aRequest->GetStatus(&status);
719 NS_ENSURE_SUCCESS(rv, rv);
721 if (NS_FAILED(status)) {
722 // EventSource::OnStopRequest will evaluate if it shall either reestablish
723 // or fail the connection, based on the status.
724 return status;
727 uint32_t httpStatus;
728 rv = httpChannel->GetResponseStatus(&httpStatus);
729 NS_ENSURE_SUCCESS(rv, rv);
731 if (httpStatus != 200) {
732 DispatchFailConnection();
733 return NS_ERROR_ABORT;
736 nsAutoCString contentType;
737 rv = httpChannel->GetContentType(contentType);
738 NS_ENSURE_SUCCESS(rv, rv);
740 if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
741 DispatchFailConnection();
742 return NS_ERROR_ABORT;
745 if (!mIsMainThread) {
746 // Try to retarget to worker thread, otherwise fall back to main thread.
747 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
748 if (rr) {
749 rv = rr->RetargetDeliveryTo(this);
750 if (NS_WARN_IF(NS_FAILED(rv))) {
751 NS_WARNING("Retargeting failed");
757 auto lock = mSharedData.Lock();
758 lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
759 this, mHttpChannel->ChannelId(), mInnerWindowID);
761 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
762 this, &EventSourceImpl::AnnounceConnection),
763 NS_DISPATCH_NORMAL);
764 NS_ENSURE_SUCCESS(rv, rv);
765 mStatus = PARSE_STATE_BEGIN_OF_STREAM;
766 return NS_OK;
769 // this method parses the characters as they become available instead of
770 // buffering them.
771 nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
772 void* aClosure,
773 const char* aFromRawSegment,
774 uint32_t aToOffset, uint32_t aCount,
775 uint32_t* aWriteCount) {
776 // The EventSourceImpl instance is hold alive on the
777 // synchronously calling stack, so raw pointer is fine here.
778 EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
779 if (!thisObject || !aWriteCount) {
780 NS_WARNING(
781 "EventSource cannot read from stream: no aClosure or aWriteCount");
782 return NS_ERROR_FAILURE;
784 thisObject->AssertIsOnTargetThread();
785 MOZ_ASSERT(!thisObject->mIsShutDown);
786 thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
787 *aWriteCount = aCount;
788 return NS_OK;
791 void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
792 AssertIsOnTargetThread();
793 if (IsClosed()) {
794 return;
796 char16_t buffer[1024];
797 auto dst = Span(buffer);
798 auto src = AsBytes(Span(aBuffer, aLength));
799 // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
800 for (;;) {
801 uint32_t result;
802 size_t read;
803 size_t written;
804 std::tie(result, read, written, std::ignore) =
805 mUnicodeDecoder->DecodeToUTF16(src, dst, false);
806 for (auto c : dst.To(written)) {
807 nsresult rv = ParseCharacter(c);
808 NS_ENSURE_SUCCESS_VOID(rv);
810 if (result == kInputEmpty) {
811 return;
813 src = src.From(read);
817 NS_IMETHODIMP
818 EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
819 nsIInputStream* aInputStream, uint64_t aOffset,
820 uint32_t aCount) {
821 AssertIsOnTargetThread();
822 NS_ENSURE_ARG_POINTER(aInputStream);
823 if (IsClosed()) {
824 return NS_ERROR_ABORT;
827 nsresult rv = CheckHealthOfRequestCallback(aRequest);
828 NS_ENSURE_SUCCESS(rv, rv);
830 uint32_t totalRead;
831 return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
832 aCount, &totalRead);
835 NS_IMETHODIMP
836 EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
837 AssertIsOnMainThread();
839 if (IsClosed()) {
840 return NS_ERROR_ABORT;
842 MOZ_ASSERT(mSrc);
843 // "Network errors that prevents the connection from being established in the
844 // first place (e.g. DNS errors), must cause the user agent to asynchronously
845 // reestablish the connection.
847 // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
848 // response to window.stop() or the user canceling the network connection
849 // manually) must cause the user agent to fail the connection.
850 // There could be additional network errors that are not covered in the above
851 // checks
852 // See Bug 1808511
853 if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
854 aStatusCode != NS_ERROR_NET_TIMEOUT &&
855 aStatusCode != NS_ERROR_NET_RESET &&
856 aStatusCode != NS_ERROR_NET_INTERRUPT &&
857 aStatusCode != NS_ERROR_NET_PARTIAL_TRANSFER &&
858 aStatusCode != NS_ERROR_NET_TIMEOUT_EXTERNAL &&
859 aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
860 aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL &&
861 aStatusCode != NS_ERROR_INVALID_CONTENT_ENCODING) {
862 DispatchFailConnection();
863 return NS_ERROR_ABORT;
866 nsresult rv = CheckHealthOfRequestCallback(aRequest);
867 NS_ENSURE_SUCCESS(rv, rv);
869 rv =
870 Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
871 this, &EventSourceImpl::ReestablishConnection),
872 NS_DISPATCH_NORMAL);
873 NS_ENSURE_SUCCESS(rv, rv);
875 return NS_OK;
878 //-----------------------------------------------------------------------------
879 // EventSourceImpl::nsIChannelEventSink
880 //-----------------------------------------------------------------------------
882 NS_IMETHODIMP
883 EventSourceImpl::AsyncOnChannelRedirect(
884 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
885 nsIAsyncVerifyRedirectCallback* aCallback) {
886 AssertIsOnMainThread();
887 if (IsClosed()) {
888 return NS_ERROR_ABORT;
890 nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
891 MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
893 nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
894 NS_ENSURE_SUCCESS(rv, rv);
896 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
898 nsCOMPtr<nsIURI> newURI;
899 rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
900 NS_ENSURE_SUCCESS(rv, rv);
902 bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
904 rv =
905 mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
906 if (NS_FAILED(rv) || !isValidScheme) {
907 DispatchFailConnection();
908 return NS_ERROR_DOM_SECURITY_ERR;
911 // update our channel
913 mHttpChannel = do_QueryInterface(aNewChannel);
914 NS_ENSURE_STATE(mHttpChannel);
916 SetupHttpChannel();
917 // The HTTP impl already copies over the referrer info on
918 // redirects, so we don't need to SetupReferrerInfo().
920 if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
921 rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
922 NS_ENSURE_SUCCESS(rv, rv);
925 aCallback->OnRedirectVerifyCallback(NS_OK);
927 return NS_OK;
930 //-----------------------------------------------------------------------------
931 // EventSourceImpl::nsIInterfaceRequestor
932 //-----------------------------------------------------------------------------
934 NS_IMETHODIMP
935 EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
936 AssertIsOnMainThread();
938 if (IsClosed()) {
939 return NS_ERROR_FAILURE;
942 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
943 *aResult = static_cast<nsIChannelEventSink*>(this);
944 NS_ADDREF_THIS();
945 return NS_OK;
948 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
949 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
950 nsresult rv;
951 nsCOMPtr<nsIPromptFactory> wwatch =
952 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
953 NS_ENSURE_SUCCESS(rv, rv);
955 nsCOMPtr<nsPIDOMWindowOuter> window;
957 // To avoid a data race we may only access the event target if it lives on
958 // the main thread.
959 if (mIsMainThread) {
960 auto lock = mSharedData.Lock();
961 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
962 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
964 if (lock->mEventSource->GetOwner()) {
965 window = lock->mEventSource->GetOwner()->GetOuterWindow();
969 // Get the an auth prompter for our window so that the parenting
970 // of the dialogs works as it should when using tabs.
972 return wwatch->GetPrompt(window, aIID, aResult);
975 return QueryInterface(aIID, aResult);
978 NS_IMETHODIMP
979 EventSourceImpl::IsOnCurrentThread(bool* aResult) {
980 *aResult = IsTargetThread();
981 return NS_OK;
984 NS_IMETHODIMP_(bool)
985 EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
987 nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
988 AssertIsOnMainThread();
989 MOZ_ASSERT(!mIsShutDown);
990 NS_ENSURE_ARG_POINTER(aBaseURI);
992 *aBaseURI = nullptr;
994 nsCOMPtr<nsIURI> baseURI;
996 // first we try from document->GetBaseURI()
997 nsCOMPtr<Document> doc =
998 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
999 if (doc) {
1000 baseURI = doc->GetBaseURI();
1003 // otherwise we get from the doc's principal
1004 if (!baseURI) {
1005 auto* basePrin = BasePrincipal::Cast(mPrincipal);
1006 nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
1007 NS_ENSURE_SUCCESS(rv, rv);
1010 NS_ENSURE_STATE(baseURI);
1012 baseURI.forget(aBaseURI);
1013 return NS_OK;
1016 void EventSourceImpl::SetupHttpChannel() {
1017 AssertIsOnMainThread();
1018 MOZ_ASSERT(!mIsShutDown);
1019 nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
1020 MOZ_ASSERT(NS_SUCCEEDED(rv));
1022 /* set the http request headers */
1024 rv = mHttpChannel->SetRequestHeader(
1025 "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
1026 MOZ_ASSERT(NS_SUCCEEDED(rv));
1028 // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
1030 if (mLastEventID.IsEmpty()) {
1031 return;
1033 NS_ConvertUTF16toUTF8 eventId(mLastEventID);
1034 rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
1035 #ifdef DEBUG
1036 if (NS_FAILED(rv)) {
1037 MOZ_LOG(gEventSourceLog, LogLevel::Warning,
1038 ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
1040 #endif
1041 Unused << rv;
1044 nsresult EventSourceImpl::SetupReferrerInfo(
1045 const nsCOMPtr<Document>& aDocument) {
1046 AssertIsOnMainThread();
1047 MOZ_ASSERT(!mIsShutDown);
1049 if (aDocument) {
1050 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
1051 nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1052 NS_ENSURE_SUCCESS(rv, rv);
1055 return NS_OK;
1058 nsresult EventSourceImpl::InitChannelAndRequestEventSource(
1059 const bool aEventTargetAccessAllowed) {
1060 AssertIsOnMainThread();
1061 if (IsClosed()) {
1062 return NS_ERROR_ABORT;
1065 bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
1067 MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
1069 nsresult rv = aEventTargetAccessAllowed ? [this]() {
1070 // We can't call GetEventSource() because we're not
1071 // allowed to touch the refcount off the worker thread
1072 // due to an assertion, event if it would have otherwise
1073 // been safe.
1074 auto lock = mSharedData.Lock();
1075 return lock->mEventSource->CheckCurrentGlobalCorrectness();
1077 : NS_OK;
1078 if (NS_FAILED(rv) || !isValidScheme) {
1079 DispatchFailConnection();
1080 return NS_ERROR_DOM_SECURITY_ERR;
1083 nsCOMPtr<Document> doc;
1084 nsSecurityFlags securityFlags =
1085 nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1087 auto lock = mSharedData.Lock();
1088 doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1089 : nullptr;
1091 if (lock->mEventSource->mWithCredentials) {
1092 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1096 // The html spec requires we use fetch cache mode of "no-store". This
1097 // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
1098 nsLoadFlags loadFlags;
1099 loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
1100 nsIRequest::INHIBIT_CACHING;
1102 nsCOMPtr<nsIChannel> channel;
1103 // If we have the document, use it
1104 if (doc) {
1105 MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
1107 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
1108 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
1109 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1110 nullptr, // aPerformanceStorage
1111 loadGroup,
1112 nullptr, // aCallbacks
1113 loadFlags); // aLoadFlags
1114 } else {
1115 // otherwise use the principal
1116 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
1117 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1118 mCookieJarSettings,
1119 nullptr, // aPerformanceStorage
1120 nullptr, // loadGroup
1121 nullptr, // aCallbacks
1122 loadFlags); // aLoadFlags
1125 NS_ENSURE_SUCCESS(rv, rv);
1127 mHttpChannel = do_QueryInterface(channel);
1128 NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
1130 SetupHttpChannel();
1131 rv = SetupReferrerInfo(doc);
1132 NS_ENSURE_SUCCESS(rv, rv);
1134 #ifdef DEBUG
1136 nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
1137 mHttpChannel->GetNotificationCallbacks(
1138 getter_AddRefs(notificationCallbacks));
1139 MOZ_ASSERT(!notificationCallbacks);
1141 #endif
1143 mHttpChannel->SetNotificationCallbacks(this);
1145 // Start reading from the channel
1146 rv = mHttpChannel->AsyncOpen(this);
1147 if (NS_FAILED(rv)) {
1148 DispatchFailConnection();
1149 return rv;
1152 return rv;
1155 void EventSourceImpl::AnnounceConnection() {
1156 AssertIsOnTargetThread();
1157 if (ReadyState() != CONNECTING) {
1158 NS_WARNING("Unexpected mReadyState!!!");
1159 return;
1163 auto lock = mSharedData.Lock();
1164 if (lock->mServiceNotifier) {
1165 lock->mServiceNotifier->ConnectionOpened();
1169 // When a user agent is to announce the connection, the user agent must set
1170 // the readyState attribute to OPEN and queue a task to fire a simple event
1171 // named open at the EventSource object.
1173 SetReadyState(OPEN);
1175 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1176 if (NS_FAILED(rv)) {
1177 return;
1179 // We can't hold the mutex while dispatching the event because the mutex is
1180 // not reentrant, and content might call back into our code.
1181 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
1182 if (NS_FAILED(rv)) {
1183 NS_WARNING("Failed to dispatch the error event!!!");
1184 return;
1188 nsresult EventSourceImpl::ResetConnection() {
1189 AssertIsOnMainThread();
1190 if (mHttpChannel) {
1191 mHttpChannel->Cancel(NS_ERROR_ABORT);
1192 mHttpChannel = nullptr;
1194 return NS_OK;
1197 void EventSourceImpl::ResetDecoder() {
1198 AssertIsOnTargetThread();
1199 if (mUnicodeDecoder) {
1200 UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
1202 mStatus = PARSE_STATE_OFF;
1203 ClearFields();
1206 class CallRestartConnection final : public WorkerMainThreadRunnable {
1207 public:
1208 explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
1209 : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
1210 "EventSource :: RestartConnection"_ns),
1211 mESImpl(std::move(aEventSourceImpl)) {
1212 mWorkerPrivate->AssertIsOnWorkerThread();
1213 MOZ_ASSERT(mESImpl);
1216 bool MainThreadRun() override {
1217 MOZ_ASSERT(mESImpl);
1218 mESImpl->RestartConnection();
1219 // We want to ensure the shortest possible remaining lifetime
1220 // and not depend on the Runnable's destruction.
1221 mESImpl = nullptr;
1222 return true;
1225 protected:
1226 RefPtr<EventSourceImpl> mESImpl;
1229 nsresult EventSourceImpl::RestartConnection() {
1230 AssertIsOnMainThread();
1231 if (IsClosed()) {
1232 return NS_ERROR_ABORT;
1235 nsresult rv = ResetConnection();
1236 NS_ENSURE_SUCCESS(rv, rv);
1237 rv = SetReconnectionTimeout();
1238 NS_ENSURE_SUCCESS(rv, rv);
1239 return NS_OK;
1242 void EventSourceImpl::ReestablishConnection() {
1243 AssertIsOnTargetThread();
1244 if (IsClosed()) {
1245 return;
1248 nsresult rv;
1249 if (mIsMainThread) {
1250 rv = RestartConnection();
1251 } else {
1252 RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
1253 ErrorResult result;
1254 runnable->Dispatch(Canceling, result);
1255 MOZ_ASSERT(!result.Failed());
1256 rv = result.StealNSResult();
1258 if (NS_FAILED(rv)) {
1259 return;
1262 rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1263 if (NS_FAILED(rv)) {
1264 return;
1267 SetReadyState(CONNECTING);
1268 ResetDecoder();
1269 // We can't hold the mutex while dispatching the event because the mutex is
1270 // not reentrant, and content might call back into our code.
1271 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1272 if (NS_FAILED(rv)) {
1273 NS_WARNING("Failed to dispatch the error event!!!");
1274 return;
1278 nsresult EventSourceImpl::SetReconnectionTimeout() {
1279 AssertIsOnMainThread();
1280 if (IsClosed()) {
1281 return NS_ERROR_ABORT;
1284 // the timer will be used whenever the requests are going finished.
1285 if (!mTimer) {
1286 mTimer = NS_NewTimer();
1287 NS_ENSURE_STATE(mTimer);
1290 MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
1291 nsITimer::TYPE_ONE_SHOT));
1293 return NS_OK;
1296 nsresult EventSourceImpl::PrintErrorOnConsole(
1297 const char* aBundleURI, const char* aError,
1298 const nsTArray<nsString>& aFormatStrings) {
1299 AssertIsOnMainThread();
1300 MOZ_ASSERT(!mIsShutDown);
1301 nsCOMPtr<nsIStringBundleService> bundleService =
1302 mozilla::components::StringBundle::Service();
1303 NS_ENSURE_STATE(bundleService);
1305 nsCOMPtr<nsIStringBundle> strBundle;
1306 nsresult rv =
1307 bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
1308 NS_ENSURE_SUCCESS(rv, rv);
1310 nsCOMPtr<nsIConsoleService> console(
1311 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1312 NS_ENSURE_SUCCESS(rv, rv);
1314 nsCOMPtr<nsIScriptError> errObj(
1315 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
1316 NS_ENSURE_SUCCESS(rv, rv);
1318 // Localize the error message
1319 nsAutoString message;
1320 if (!aFormatStrings.IsEmpty()) {
1321 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
1322 } else {
1323 rv = strBundle->GetStringFromName(aError, message);
1325 NS_ENSURE_SUCCESS(rv, rv);
1327 rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
1328 mScriptColumn, nsIScriptError::errorFlag,
1329 "Event Source", mInnerWindowID);
1330 NS_ENSURE_SUCCESS(rv, rv);
1332 // print the error message directly to the JS console
1333 rv = console->LogMessage(errObj);
1334 NS_ENSURE_SUCCESS(rv, rv);
1336 return NS_OK;
1339 nsresult EventSourceImpl::ConsoleError() {
1340 AssertIsOnMainThread();
1341 MOZ_ASSERT(!mIsShutDown);
1342 nsAutoCString targetSpec;
1343 nsresult rv = mSrc->GetSpec(targetSpec);
1344 NS_ENSURE_SUCCESS(rv, rv);
1346 AutoTArray<nsString, 1> formatStrings;
1347 CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
1349 if (ReadyState() == CONNECTING) {
1350 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1351 "connectionFailure", formatStrings);
1352 } else {
1353 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1354 "netInterrupt", formatStrings);
1356 NS_ENSURE_SUCCESS(rv, rv);
1358 return NS_OK;
1361 void EventSourceImpl::DispatchFailConnection() {
1362 AssertIsOnMainThread();
1363 if (IsClosed()) {
1364 return;
1366 nsresult rv = ConsoleError();
1367 if (NS_FAILED(rv)) {
1368 NS_WARNING("Failed to print to the console error");
1370 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
1371 &EventSourceImpl::FailConnection),
1372 NS_DISPATCH_NORMAL);
1373 if (NS_WARN_IF(NS_FAILED(rv))) {
1374 // if the worker is shutting down, the dispatching of normal WorkerRunnables
1375 // fails.
1376 return;
1380 void EventSourceImpl::FailConnection() {
1381 AssertIsOnTargetThread();
1382 if (IsClosed()) {
1383 return;
1385 // Must change state to closed before firing event to content.
1386 SetReadyState(CLOSED);
1387 // When a user agent is to fail the connection, the user agent must set the
1388 // readyState attribute to CLOSED and queue a task to fire a simple event
1389 // named error at the EventSource object.
1390 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1391 if (NS_SUCCEEDED(rv)) {
1392 // We can't hold the mutex while dispatching the event because the mutex
1393 // is not reentrant, and content might call back into our code.
1394 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1395 if (NS_FAILED(rv)) {
1396 NS_WARNING("Failed to dispatch the error event!!!");
1399 // Call CloseInternal in the end of function because it may release
1400 // EventSourceImpl.
1401 CloseInternal();
1404 NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
1405 AssertIsOnMainThread();
1406 if (IsClosed()) {
1407 return NS_OK;
1410 MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
1412 if (!mFrozen) {
1413 nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
1414 if (NS_FAILED(rv)) {
1415 NS_WARNING("InitChannelAndRequestEventSource() failed");
1418 return NS_OK;
1421 NS_IMETHODIMP EventSourceImpl::GetName(nsACString& aName) {
1422 aName.AssignLiteral("EventSourceImpl");
1423 return NS_OK;
1426 nsresult EventSourceImpl::Thaw() {
1427 AssertIsOnMainThread();
1428 if (IsClosed() || !mFrozen) {
1429 return NS_OK;
1432 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1434 mFrozen = false;
1435 nsresult rv;
1436 if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
1437 nsCOMPtr<nsIRunnable> event =
1438 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1439 this, &EventSourceImpl::DispatchAllMessageEvents);
1440 NS_ENSURE_STATE(event);
1442 mGoingToDispatchAllMessages = true;
1444 rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1445 NS_ENSURE_SUCCESS(rv, rv);
1448 rv = InitChannelAndRequestEventSource(mIsMainThread);
1449 NS_ENSURE_SUCCESS(rv, rv);
1451 return NS_OK;
1454 nsresult EventSourceImpl::Freeze() {
1455 AssertIsOnMainThread();
1456 if (IsClosed() || mFrozen) {
1457 return NS_OK;
1460 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1461 mFrozen = true;
1462 return NS_OK;
1465 nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
1466 AssertIsOnTargetThread();
1467 MOZ_ASSERT(!mIsShutDown);
1468 UniquePtr<Message> message(std::move(mCurrentMessage));
1469 ClearFields();
1471 if (!message || message->mData.IsEmpty()) {
1472 return NS_OK;
1475 // removes the trailing LF from mData
1476 MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
1477 "Invalid trailing character! LF was expected instead.");
1478 message->mData.SetLength(message->mData.Length() - 1);
1480 if (message->mEventName.IsEmpty()) {
1481 message->mEventName.AssignLiteral("message");
1484 mMessagesToDispatch.Push(message.release());
1486 if (!mGoingToDispatchAllMessages) {
1487 nsCOMPtr<nsIRunnable> event =
1488 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1489 this, &EventSourceImpl::DispatchAllMessageEvents);
1490 NS_ENSURE_STATE(event);
1492 mGoingToDispatchAllMessages = true;
1494 return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1497 return NS_OK;
1500 void EventSourceImpl::DispatchAllMessageEvents() {
1501 AssertIsOnTargetThread();
1502 mGoingToDispatchAllMessages = false;
1504 if (IsClosed() || mFrozen) {
1505 return;
1508 nsresult rv;
1509 AutoJSAPI jsapi;
1511 auto lock = mSharedData.Lock();
1512 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1513 if (NS_FAILED(rv)) {
1514 return;
1517 if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1518 return;
1522 JSContext* cx = jsapi.cx();
1524 while (mMessagesToDispatch.GetSize() > 0) {
1525 UniquePtr<Message> message(mMessagesToDispatch.PopFront());
1527 if (message->mLastEventID.isSome()) {
1528 mLastEventID.Assign(message->mLastEventID.value());
1531 if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
1532 message->mLastEventID = Some(mLastEventID);
1536 auto lock = mSharedData.Lock();
1537 if (lock->mServiceNotifier) {
1538 lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1539 message->mData, mReconnectionTime,
1540 PR_Now());
1544 // Now we can turn our string into a jsval
1545 JS::Rooted<JS::Value> jsData(cx);
1547 JSString* jsString;
1548 jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
1549 message->mData.Length());
1550 NS_ENSURE_TRUE_VOID(jsString);
1552 jsData.setString(jsString);
1555 // create an event that uses the MessageEvent interface,
1556 // which does not bubble, is not cancelable, and has no default action
1558 RefPtr<EventSource> eventSource = GetEventSource();
1559 RefPtr<MessageEvent> event =
1560 new MessageEvent(eventSource, nullptr, nullptr);
1562 event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
1563 Cancelable::eNo, jsData, mOrigin, mLastEventID,
1564 nullptr, Sequence<OwningNonNull<MessagePort>>());
1565 event->SetTrusted(true);
1567 // We can't hold the mutex while dispatching the event because the mutex is
1568 // not reentrant, and content might call back into our code.
1569 IgnoredErrorResult err;
1570 eventSource->DispatchEvent(*event, err);
1571 if (err.Failed()) {
1572 NS_WARNING("Failed to dispatch the message event!!!");
1573 return;
1576 if (IsClosed() || mFrozen) {
1577 return;
1582 void EventSourceImpl::ClearFields() {
1583 AssertIsOnTargetThread();
1584 mCurrentMessage = nullptr;
1585 mLastFieldName.Truncate();
1586 mLastFieldValue.Truncate();
1589 nsresult EventSourceImpl::SetFieldAndClear() {
1590 MOZ_ASSERT(!mIsShutDown);
1591 AssertIsOnTargetThread();
1592 if (mLastFieldName.IsEmpty()) {
1593 mLastFieldValue.Truncate();
1594 return NS_OK;
1596 if (!mCurrentMessage) {
1597 mCurrentMessage = MakeUnique<Message>();
1599 char16_t first_char;
1600 first_char = mLastFieldName.CharAt(0);
1602 // with no case folding performed
1603 switch (first_char) {
1604 case char16_t('d'):
1605 if (mLastFieldName.EqualsLiteral("data")) {
1606 // If the field name is "data" append the field value to the data
1607 // buffer, then append a single U+000A LINE FEED (LF) character
1608 // to the data buffer.
1609 mCurrentMessage->mData.Append(mLastFieldValue);
1610 mCurrentMessage->mData.Append(LF_CHAR);
1612 break;
1614 case char16_t('e'):
1615 if (mLastFieldName.EqualsLiteral("event")) {
1616 mCurrentMessage->mEventName.Assign(mLastFieldValue);
1618 break;
1620 case char16_t('i'):
1621 if (mLastFieldName.EqualsLiteral("id")) {
1622 mCurrentMessage->mLastEventID = Some(mLastFieldValue);
1624 break;
1626 case char16_t('r'):
1627 if (mLastFieldName.EqualsLiteral("retry")) {
1628 uint32_t newValue = 0;
1629 uint32_t i = 0; // we must ensure that there are only digits
1630 bool assign = true;
1631 for (i = 0; i < mLastFieldValue.Length(); ++i) {
1632 if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
1633 mLastFieldValue.CharAt(i) > (char16_t)'9') {
1634 assign = false;
1635 break;
1637 newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
1638 ((uint32_t)((char16_t)'0')));
1641 if (assign) {
1642 if (newValue < MIN_RECONNECTION_TIME_VALUE) {
1643 mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
1644 } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
1645 mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
1646 } else {
1647 mReconnectionTime = newValue;
1650 break;
1652 break;
1655 mLastFieldName.Truncate();
1656 mLastFieldValue.Truncate();
1658 return NS_OK;
1661 nsresult EventSourceImpl::CheckHealthOfRequestCallback(
1662 nsIRequest* aRequestCallback) {
1663 // This function could be run on target thread if http channel support
1664 // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
1666 // check if we have been closed or if the request has been canceled
1667 // or if we have been frozen
1668 if (IsClosed() || mFrozen || !mHttpChannel) {
1669 return NS_ERROR_ABORT;
1672 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
1673 NS_ENSURE_STATE(httpChannel);
1675 if (httpChannel != mHttpChannel) {
1676 NS_WARNING("wrong channel from request callback");
1677 return NS_ERROR_ABORT;
1680 return NS_OK;
1683 nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
1684 AssertIsOnTargetThread();
1685 nsresult rv;
1687 if (IsClosed()) {
1688 return NS_ERROR_ABORT;
1691 switch (mStatus) {
1692 case PARSE_STATE_OFF:
1693 NS_ERROR("Invalid state");
1694 return NS_ERROR_FAILURE;
1695 break;
1697 case PARSE_STATE_BEGIN_OF_STREAM:
1698 if (aChr == CR_CHAR) {
1699 mStatus = PARSE_STATE_CR_CHAR;
1700 } else if (aChr == LF_CHAR) {
1701 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1702 } else if (aChr == COLON_CHAR) {
1703 mStatus = PARSE_STATE_COMMENT;
1704 } else {
1705 mLastFieldName += aChr;
1706 mStatus = PARSE_STATE_FIELD_NAME;
1708 break;
1710 case PARSE_STATE_CR_CHAR:
1711 if (aChr == CR_CHAR) {
1712 rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
1713 NS_ENSURE_SUCCESS(rv, rv);
1714 } else if (aChr == LF_CHAR) {
1715 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1716 } else if (aChr == COLON_CHAR) {
1717 mStatus = PARSE_STATE_COMMENT;
1718 } else {
1719 mLastFieldName += aChr;
1720 mStatus = PARSE_STATE_FIELD_NAME;
1723 break;
1725 case PARSE_STATE_COMMENT:
1726 if (aChr == CR_CHAR) {
1727 mStatus = PARSE_STATE_CR_CHAR;
1728 } else if (aChr == LF_CHAR) {
1729 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1732 break;
1734 case PARSE_STATE_FIELD_NAME:
1735 if (aChr == CR_CHAR) {
1736 rv = SetFieldAndClear();
1737 NS_ENSURE_SUCCESS(rv, rv);
1739 mStatus = PARSE_STATE_CR_CHAR;
1740 } else if (aChr == LF_CHAR) {
1741 rv = SetFieldAndClear();
1742 NS_ENSURE_SUCCESS(rv, rv);
1744 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1745 } else if (aChr == COLON_CHAR) {
1746 mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
1747 } else {
1748 mLastFieldName += aChr;
1751 break;
1753 case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
1754 if (aChr == CR_CHAR) {
1755 rv = SetFieldAndClear();
1756 NS_ENSURE_SUCCESS(rv, rv);
1758 mStatus = PARSE_STATE_CR_CHAR;
1759 } else if (aChr == LF_CHAR) {
1760 rv = SetFieldAndClear();
1761 NS_ENSURE_SUCCESS(rv, rv);
1763 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1764 } else if (aChr == SPACE_CHAR) {
1765 mStatus = PARSE_STATE_FIELD_VALUE;
1766 } else {
1767 mLastFieldValue += aChr;
1768 mStatus = PARSE_STATE_FIELD_VALUE;
1771 break;
1773 case PARSE_STATE_FIELD_VALUE:
1774 if (aChr == CR_CHAR) {
1775 rv = SetFieldAndClear();
1776 NS_ENSURE_SUCCESS(rv, rv);
1778 mStatus = PARSE_STATE_CR_CHAR;
1779 } else if (aChr == LF_CHAR) {
1780 rv = SetFieldAndClear();
1781 NS_ENSURE_SUCCESS(rv, rv);
1783 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1784 } else if (aChr != 0) {
1785 // Avoid appending the null char to the field value.
1786 mLastFieldValue += aChr;
1787 } else if (mLastFieldName.EqualsLiteral("id")) {
1788 // Ignore the whole id field if aChr is null
1789 mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
1790 mLastFieldValue.Truncate();
1793 break;
1795 case PARSE_STATE_IGNORE_FIELD_VALUE:
1796 if (aChr == CR_CHAR) {
1797 mStatus = PARSE_STATE_CR_CHAR;
1798 } else if (aChr == LF_CHAR) {
1799 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1801 break;
1803 case PARSE_STATE_BEGIN_OF_LINE:
1804 if (aChr == CR_CHAR) {
1805 rv = DispatchCurrentMessageEvent(); // there is an empty line
1806 NS_ENSURE_SUCCESS(rv, rv);
1808 mStatus = PARSE_STATE_CR_CHAR;
1809 } else if (aChr == LF_CHAR) {
1810 rv = DispatchCurrentMessageEvent(); // there is an empty line
1811 NS_ENSURE_SUCCESS(rv, rv);
1813 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1814 } else if (aChr == COLON_CHAR) {
1815 mStatus = PARSE_STATE_COMMENT;
1816 } else if (aChr != 0) {
1817 // Avoid appending the null char to the field name.
1818 mLastFieldName += aChr;
1819 mStatus = PARSE_STATE_FIELD_NAME;
1822 break;
1825 return NS_OK;
1828 namespace {
1830 class WorkerRunnableDispatcher final : public WorkerRunnable {
1831 RefPtr<EventSourceImpl> mEventSourceImpl;
1833 public:
1834 WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
1835 WorkerPrivate* aWorkerPrivate,
1836 already_AddRefed<nsIRunnable> aEvent)
1837 : WorkerRunnable(aWorkerPrivate, "WorkerRunnableDispatcher"),
1838 mEventSourceImpl(std::move(aImpl)),
1839 mEvent(std::move(aEvent)) {}
1841 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1842 aWorkerPrivate->AssertIsOnWorkerThread();
1843 return !NS_FAILED(mEvent->Run());
1846 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
1847 bool aRunResult) override {
1848 // Ensure we drop the RefPtr on the worker thread
1849 // and to not keep us alive longer than needed.
1850 mEventSourceImpl = nullptr;
1853 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
1854 // We don't call WorkerRunnable::PreDispatch 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).
1858 return true;
1861 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1862 bool aDispatchResult) override {
1863 // We don't call WorkerRunnable::PostDispatch because it would assert the
1864 // wrong thing about which thread we're on. We're on whichever thread the
1865 // channel implementation is running on (probably the main thread or
1866 // transport thread).
1869 private:
1870 nsCOMPtr<nsIRunnable> mEvent;
1873 } // namespace
1875 bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
1876 MOZ_ASSERT(!mWorkerRef);
1877 MOZ_ASSERT(aWorkerPrivate);
1878 aWorkerPrivate->AssertIsOnWorkerThread();
1880 if (mIsShutDown) {
1881 return false;
1884 RefPtr<EventSourceImpl> self = this;
1885 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
1886 aWorkerPrivate, "EventSource", [self]() { self->Close(); });
1888 if (NS_WARN_IF(!workerRef)) {
1889 return false;
1892 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
1893 return true;
1896 void EventSourceImpl::ReleaseWorkerRef() {
1897 MOZ_ASSERT(IsClosed());
1898 MOZ_ASSERT(IsCurrentThreadRunningWorker());
1899 mWorkerRef = nullptr;
1902 //-----------------------------------------------------------------------------
1903 // EventSourceImpl::nsIEventTarget
1904 //-----------------------------------------------------------------------------
1905 NS_IMETHODIMP
1906 EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
1907 nsCOMPtr<nsIRunnable> event(aEvent);
1908 return Dispatch(event.forget(), aFlags);
1911 NS_IMETHODIMP
1912 EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
1913 uint32_t aFlags) {
1914 nsCOMPtr<nsIRunnable> event_ref(aEvent);
1915 if (mIsMainThread) {
1916 return NS_DispatchToMainThread(event_ref.forget());
1919 if (mIsShutDown) {
1920 // We want to avoid clutter about errors in our shutdown logs,
1921 // so just report NS_OK (we have no explicit return value
1922 // for shutdown).
1923 return NS_OK;
1926 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
1927 // runnable.
1928 RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
1929 this, mWorkerRef->Private(), event_ref.forget());
1931 if (!event->Dispatch()) {
1932 return NS_ERROR_FAILURE;
1934 return NS_OK;
1937 NS_IMETHODIMP
1938 EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
1939 uint32_t aDelayMs) {
1940 return NS_ERROR_NOT_IMPLEMENTED;
1943 NS_IMETHODIMP
1944 EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
1945 return NS_ERROR_NOT_IMPLEMENTED;
1948 NS_IMETHODIMP
1949 EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
1950 return NS_ERROR_NOT_IMPLEMENTED;
1953 //-----------------------------------------------------------------------------
1954 // EventSourceImpl::nsIThreadRetargetableStreamListener
1955 //-----------------------------------------------------------------------------
1956 NS_IMETHODIMP
1957 EventSourceImpl::CheckListenerChain() {
1958 MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
1959 return NS_OK;
1962 NS_IMETHODIMP
1963 EventSourceImpl::OnDataFinished(nsresult) { return NS_OK; }
1965 ////////////////////////////////////////////////////////////////////////////////
1966 // EventSource
1967 ////////////////////////////////////////////////////////////////////////////////
1969 EventSource::EventSource(nsIGlobalObject* aGlobal,
1970 nsICookieJarSettings* aCookieJarSettings,
1971 bool aWithCredentials)
1972 : DOMEventTargetHelper(aGlobal),
1973 mWithCredentials(aWithCredentials),
1974 mIsMainThread(NS_IsMainThread()) {
1975 MOZ_ASSERT(aGlobal);
1976 MOZ_ASSERT(aCookieJarSettings);
1977 mESImpl = new EventSourceImpl(this, aCookieJarSettings);
1980 EventSource::~EventSource() = default;
1982 nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1983 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1984 // it doesn't bubble, and it isn't cancelable
1985 event->InitEvent(aName, false, false);
1986 event->SetTrusted(true);
1987 ErrorResult rv;
1988 DispatchEvent(*event, rv);
1989 return rv.StealNSResult();
1992 /* static */
1993 already_AddRefed<EventSource> EventSource::Constructor(
1994 const GlobalObject& aGlobal, const nsAString& aURL,
1995 const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
1996 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1997 if (NS_WARN_IF(!global)) {
1998 aRv.Throw(NS_ERROR_FAILURE);
1999 return nullptr;
2002 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
2003 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
2004 if (ownerWindow) {
2005 Document* doc = ownerWindow->GetExtantDoc();
2006 if (NS_WARN_IF(!doc)) {
2007 aRv.Throw(NS_ERROR_FAILURE);
2008 return nullptr;
2011 cookieJarSettings = doc->CookieJarSettings();
2012 } else {
2013 // Worker side.
2014 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2015 if (!workerPrivate) {
2016 aRv.Throw(NS_ERROR_FAILURE);
2017 return nullptr;
2020 cookieJarSettings = workerPrivate->CookieJarSettings();
2023 RefPtr<EventSource> eventSource = new EventSource(
2024 global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
2026 if (NS_IsMainThread()) {
2027 // Get principal from document and init EventSourceImpl
2028 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
2029 do_QueryInterface(aGlobal.GetAsSupports());
2030 if (!scriptPrincipal) {
2031 aRv.Throw(NS_ERROR_FAILURE);
2032 return nullptr;
2034 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
2035 if (!principal) {
2036 aRv.Throw(NS_ERROR_FAILURE);
2037 return nullptr;
2039 eventSource->mESImpl->Init(principal, aURL, aRv);
2040 if (NS_WARN_IF(aRv.Failed())) {
2041 return nullptr;
2044 eventSource->mESImpl->InitChannelAndRequestEventSource(true);
2045 return eventSource.forget();
2048 // Worker side.
2050 // Scope for possible failures that need cleanup
2051 auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
2053 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2054 MOZ_ASSERT(workerPrivate);
2056 eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
2058 RefPtr<InitRunnable> initRunnable =
2059 new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
2060 initRunnable->Dispatch(Canceling, aRv);
2061 if (NS_WARN_IF(aRv.Failed())) {
2062 return nullptr;
2065 aRv = initRunnable->ErrorCode();
2066 if (NS_WARN_IF(aRv.Failed())) {
2067 return nullptr;
2070 // In workers we have to keep the worker alive using a WorkerRef in order
2071 // to dispatch messages correctly.
2072 // Note, initRunnable->Dispatch may have cleared mESImpl.
2073 if (!eventSource->mESImpl ||
2074 !eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
2075 // The worker is already shutting down. Let's return an already closed
2076 // object, but marked as Connecting.
2077 if (eventSource->mESImpl) {
2078 // mESImpl is nulled by this call such that EventSourceImpl is
2079 // released before returning the object, otherwise
2080 // it will set EventSource to a CLOSED state in its DTOR..
2081 eventSource->mESImpl->Close();
2083 eventSource->mReadyState = EventSourceImpl::CONNECTING;
2085 guardESImpl.release();
2086 return eventSource.forget();
2089 // Let's connect to the server.
2090 RefPtr<ConnectRunnable> connectRunnable =
2091 new ConnectRunnable(workerPrivate, eventSource->mESImpl);
2092 connectRunnable->Dispatch(Canceling, aRv);
2093 if (NS_WARN_IF(aRv.Failed())) {
2094 return nullptr;
2097 // End of scope for possible failures
2098 guardESImpl.release();
2101 return eventSource.forget();
2104 // nsWrapperCache
2105 JSObject* EventSource::WrapObject(JSContext* aCx,
2106 JS::Handle<JSObject*> aGivenProto) {
2107 return EventSource_Binding::Wrap(aCx, this, aGivenProto);
2110 void EventSource::Close() {
2111 AssertIsOnTargetThread();
2112 if (mESImpl) {
2113 // Close potentially kills ourself, ensure
2114 // to not access any members afterwards.
2115 mESImpl->Close();
2119 //-----------------------------------------------------------------------------
2120 // EventSource::nsISupports
2121 //-----------------------------------------------------------------------------
2123 NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
2125 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
2126 DOMEventTargetHelper)
2127 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2129 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
2130 DOMEventTargetHelper)
2131 if (tmp->mESImpl) {
2132 // IsCertainlyaliveForCC will return true and cause the cycle
2133 // collector to skip this instance when mESImpl is non-null and
2134 // points back to ourself.
2135 // mESImpl is initialized to be non-null in the constructor
2136 // and should have been wiped out in our close function.
2137 MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
2138 tmp->Close();
2140 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2142 bool EventSource::IsCertainlyAliveForCC() const {
2143 // Until we are double linked forth and back, we want to stay alive.
2144 if (!mESImpl) {
2145 return false;
2147 auto lock = mESImpl->mSharedData.Lock();
2148 return lock->mEventSource == this;
2151 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
2152 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
2154 NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
2155 NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
2157 } // namespace mozilla::dom