Bug 1700051: part 35) Reduce accessibility of `mSoftText.mDOMMapping` to `private...
[gecko.git] / dom / base / EventSource.cpp
blob3fbe12b40a67211070eaf3664442b1c38740db54
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/dom/EventSource.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Components.h"
11 #include "mozilla/DataMutex.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/LoadInfo.h"
14 #include "mozilla/DOMEventTargetHelper.h"
15 #include "mozilla/dom/EventSourceBinding.h"
16 #include "mozilla/dom/MessageEvent.h"
17 #include "mozilla/dom/MessageEventBinding.h"
18 #include "mozilla/dom/ScriptSettings.h"
19 #include "mozilla/dom/WorkerPrivate.h"
20 #include "mozilla/dom/WorkerRef.h"
21 #include "mozilla/dom/WorkerRunnable.h"
22 #include "mozilla/dom/WorkerScope.h"
23 #include "mozilla/dom/EventSourceEventService.h"
24 #include "mozilla/ScopeExit.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 static LazyLogModule gEventSourceLog("EventSource");
61 #define SPACE_CHAR (char16_t)0x0020
62 #define CR_CHAR (char16_t)0x000D
63 #define LF_CHAR (char16_t)0x000A
64 #define COLON_CHAR (char16_t)0x003A
66 // Reconnection time related values in milliseconds. The default one is equal
67 // to the default value of the pref dom.server-events.default-reconnection-time
68 #define MIN_RECONNECTION_TIME_VALUE 500
69 #define DEFAULT_RECONNECTION_TIME_VALUE 5000
70 #define MAX_RECONNECTION_TIME_VALUE \
71 PR_IntervalToMilliseconds(DELAY_INTERVAL_LIMIT)
73 class EventSourceImpl final : public nsIObserver,
74 public nsIStreamListener,
75 public nsIChannelEventSink,
76 public nsIInterfaceRequestor,
77 public nsSupportsWeakReference,
78 public nsIEventTarget,
79 public nsITimerCallback,
80 public nsIThreadRetargetableStreamListener {
81 public:
82 NS_DECL_THREADSAFE_ISUPPORTS
83 NS_DECL_NSIOBSERVER
84 NS_DECL_NSIREQUESTOBSERVER
85 NS_DECL_NSISTREAMLISTENER
86 NS_DECL_NSICHANNELEVENTSINK
87 NS_DECL_NSIINTERFACEREQUESTOR
88 NS_DECL_NSIEVENTTARGET_FULL
89 NS_DECL_NSITIMERCALLBACK
90 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
92 EventSourceImpl(EventSource* aEventSource,
93 nsICookieJarSettings* aCookieJarSettings);
95 enum { CONNECTING = 0U, OPEN = 1U, CLOSED = 2U };
97 void Close();
99 void Init(nsIPrincipal* aPrincipal, const nsAString& aURL, ErrorResult& aRv);
101 nsresult GetBaseURI(nsIURI** aBaseURI);
103 void SetupHttpChannel();
104 nsresult SetupReferrerInfo(const nsCOMPtr<Document>& aDocument);
105 nsresult InitChannelAndRequestEventSource(bool aEventTargetAccessAllowed);
106 nsresult ResetConnection();
107 void ResetDecoder();
108 nsresult SetReconnectionTimeout();
110 void AnnounceConnection();
111 void DispatchAllMessageEvents();
112 nsresult RestartConnection();
113 void ReestablishConnection();
114 void DispatchFailConnection();
115 void FailConnection();
117 nsresult Thaw();
118 nsresult Freeze();
120 nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
121 const nsTArray<nsString>& aFormatStrings);
122 nsresult ConsoleError();
124 static nsresult StreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
125 const char* aFromRawSegment,
126 uint32_t aToOffset, uint32_t aCount,
127 uint32_t* aWriteCount);
128 void ParseSegment(const char* aBuffer, uint32_t aLength);
129 nsresult SetFieldAndClear();
130 void ClearFields();
131 nsresult ResetEvent();
132 nsresult DispatchCurrentMessageEvent();
133 nsresult ParseCharacter(char16_t aChr);
134 nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
135 nsresult OnRedirectVerifyCallback(nsresult result);
136 nsresult ParseURL(const nsAString& aURL);
137 nsresult AddWindowObservers();
138 void RemoveWindowObservers();
140 void CloseInternal();
141 void CleanupOnMainThread();
143 bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
144 void ReleaseWorkerRef();
146 void AssertIsOnTargetThread() const {
147 MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
150 bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
152 uint16_t ReadyState() {
153 auto lock = mSharedData.Lock();
154 if (lock->mEventSource) {
155 return lock->mEventSource->mReadyState;
157 // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
158 // means that the EventSource has been closed.
159 return CLOSED;
162 void SetReadyState(uint16_t aReadyState) {
163 auto lock = mSharedData.Lock();
164 MOZ_ASSERT(lock->mEventSource);
165 MOZ_ASSERT(!mIsShutDown);
166 lock->mEventSource->mReadyState = aReadyState;
169 bool IsClosed() { return ReadyState() == CLOSED; }
171 RefPtr<EventSource> GetEventSource() {
172 AssertIsOnTargetThread();
173 auto lock = mSharedData.Lock();
174 return lock->mEventSource;
178 * A simple state machine used to manage the event-source's line buffer
180 * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
182 * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_CR_CHAR |
183 * PARSE_STATE_BEGIN_OF_LINE |
184 * PARSE_STATE_COMMENT |
185 * PARSE_STATE_FIELD_NAME
187 * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
188 * PARSE_STATE_COMMENT |
189 * PARSE_STATE_FIELD_NAME |
190 * PARSE_STATE_BEGIN_OF_LINE
192 * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
193 * PARSE_STATE_BEGIN_OF_LINE
195 * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
196 * PARSE_STATE_BEGIN_OF_LINE |
197 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
199 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
200 * PARSE_STATE_CR_CHAR |
201 * PARSE_STATE_BEGIN_OF_LINE
203 * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
204 * PARSE_STATE_BEGIN_OF_LINE
206 * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
207 * PARSE_STATE_COMMENT |
208 * PARSE_STATE_FIELD_NAME |
209 * PARSE_STATE_BEGIN_OF_LINE
211 * Whenever the parser find an empty line or the end-of-file
212 * it dispatches the stacked event.
215 enum ParserStatus {
216 PARSE_STATE_OFF = 0,
217 PARSE_STATE_BEGIN_OF_STREAM,
218 PARSE_STATE_CR_CHAR,
219 PARSE_STATE_COMMENT,
220 PARSE_STATE_FIELD_NAME,
221 PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
222 PARSE_STATE_FIELD_VALUE,
223 PARSE_STATE_IGNORE_FIELD_VALUE,
224 PARSE_STATE_BEGIN_OF_LINE
227 // Connection related data members. Should only be accessed on main thread.
228 nsCOMPtr<nsIURI> mSrc;
229 uint32_t mReconnectionTime; // in ms
230 nsCOMPtr<nsIPrincipal> mPrincipal;
231 nsString mOrigin;
232 nsCOMPtr<nsITimer> mTimer;
233 nsCOMPtr<nsIHttpChannel> mHttpChannel;
235 struct Message {
236 nsString mEventName;
237 // We need to be able to distinguish between different states of id field:
238 // 1) is not given at all
239 // 2) is given but is empty
240 // 3) is given and has a value
241 // We can't check for the 1st state with a simple nsString.
242 Maybe<nsString> mLastEventID;
243 nsString mData;
246 // Message related data members. May be set / initialized when initializing
247 // EventSourceImpl on target thread but should only be used on target thread.
248 nsString mLastEventID;
249 UniquePtr<Message> mCurrentMessage;
250 nsDeque<Message> mMessagesToDispatch;
251 ParserStatus mStatus;
252 mozilla::UniquePtr<mozilla::Decoder> mUnicodeDecoder;
253 nsString mLastFieldName;
254 nsString mLastFieldValue;
256 // EventSourceImpl internal states.
257 // WorkerRef to keep the worker alive. (accessed on worker thread only)
258 RefPtr<ThreadSafeWorkerRef> mWorkerRef;
259 // Whether the window is frozen. May be set on main thread and read on target
260 // thread.
261 Atomic<bool> mFrozen;
262 // There are some messages are going to be dispatched when thaw.
263 bool mGoingToDispatchAllMessages;
264 // Whether the EventSource is run on main thread.
265 const bool mIsMainThread;
266 // Whether the EventSourceImpl is going to be destroyed.
267 Atomic<bool> mIsShutDown;
269 class EventSourceServiceNotifier final {
270 public:
271 EventSourceServiceNotifier(RefPtr<EventSourceImpl>&& aEventSourceImpl,
272 uint64_t aHttpChannelId, uint64_t aInnerWindowID)
273 : mEventSourceImpl(std::move(aEventSourceImpl)),
274 mHttpChannelId(aHttpChannelId),
275 mInnerWindowID(aInnerWindowID),
276 mConnectionOpened(false) {
277 AssertIsOnMainThread();
278 mService = EventSourceEventService::GetOrCreate();
281 void ConnectionOpened() {
282 mEventSourceImpl->AssertIsOnTargetThread();
283 mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
284 mConnectionOpened = true;
287 void EventReceived(const nsAString& aEventName,
288 const nsAString& aLastEventID, const nsAString& aData,
289 uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
290 mEventSourceImpl->AssertIsOnTargetThread();
291 mService->EventReceived(mHttpChannelId, mInnerWindowID, aEventName,
292 aLastEventID, aData, aRetry, aTimeStamp);
295 ~EventSourceServiceNotifier() {
296 // It is safe to call this on any thread because
297 // EventSourceConnectionClosed method is thread safe and
298 // NS_ReleaseOnMainThread explicitly releases the service on the main
299 // thread.
300 if (mConnectionOpened) {
301 // We want to notify about connection being closed only if we told
302 // it was ever opened. The check is needed if OnStartRequest is called
303 // on the main thread while close() is called on a worker thread.
304 mService->EventSourceConnectionClosed(mHttpChannelId, mInnerWindowID);
306 NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
307 mService.forget());
310 private:
311 RefPtr<EventSourceEventService> mService;
312 RefPtr<EventSourceImpl> mEventSourceImpl;
313 uint64_t mHttpChannelId;
314 uint64_t mInnerWindowID;
315 bool mConnectionOpened;
318 struct SharedData {
319 RefPtr<EventSource> mEventSource;
320 UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
323 DataMutex<SharedData> mSharedData;
325 // Event Source owner information:
326 // - the script file name
327 // - source code line number and column number where the Event Source object
328 // was constructed.
329 // - the ID of the inner window where the script lives. Note that this may not
330 // be the same as the Event Source owner window.
331 // These attributes are used for error reporting. Should only be accessed on
332 // target thread
333 nsString mScriptFile;
334 uint32_t mScriptLine;
335 uint32_t mScriptColumn;
336 uint64_t mInnerWindowID;
338 private:
339 nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
341 // Pointer to the target thread for checking whether we are
342 // on the target thread. This is intentionally a non-owning
343 // pointer in order not to affect the thread destruction
344 // sequence. This pointer must only be compared for equality
345 // and must not be dereferenced.
346 nsIThread* mTargetThread;
348 // prevent bad usage
349 EventSourceImpl(const EventSourceImpl& x) = delete;
350 EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
351 ~EventSourceImpl() {
352 if (IsClosed()) {
353 return;
355 // If we threw during Init we never called Close
356 SetReadyState(CLOSED);
357 CloseInternal();
361 NS_IMPL_ISUPPORTS(EventSourceImpl, nsIObserver, nsIStreamListener,
362 nsIRequestObserver, nsIChannelEventSink,
363 nsIInterfaceRequestor, nsISupportsWeakReference,
364 nsIEventTarget, nsIThreadRetargetableStreamListener)
366 EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
367 nsICookieJarSettings* aCookieJarSettings)
368 : mReconnectionTime(0),
369 mStatus(PARSE_STATE_OFF),
370 mFrozen(false),
371 mGoingToDispatchAllMessages(false),
372 mIsMainThread(NS_IsMainThread()),
373 mIsShutDown(false),
374 mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
375 mScriptLine(0),
376 mScriptColumn(0),
377 mInnerWindowID(0),
378 mCookieJarSettings(aCookieJarSettings),
379 mTargetThread(NS_GetCurrentThread()) {
380 MOZ_ASSERT(aEventSource);
381 SetReadyState(CONNECTING);
384 class CleanupRunnable final : public WorkerMainThreadRunnable {
385 public:
386 explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
387 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
388 "EventSource :: Cleanup"_ns),
389 mESImpl(std::move(aEventSourceImpl)) {
390 MOZ_ASSERT(mESImpl);
391 mWorkerPrivate->AssertIsOnWorkerThread();
394 bool MainThreadRun() override {
395 MOZ_ASSERT(mESImpl);
396 mESImpl->CleanupOnMainThread();
397 // We want to ensure the shortest possible remaining lifetime
398 // and not depend on the Runnable's destruction.
399 mESImpl = nullptr;
400 return true;
403 protected:
404 RefPtr<EventSourceImpl> mESImpl;
407 void EventSourceImpl::Close() {
408 if (IsClosed()) {
409 return;
412 SetReadyState(CLOSED);
413 // CloseInternal potentially kills ourself, ensure
414 // to not access any members afterwards.
415 CloseInternal();
418 void EventSourceImpl::CloseInternal() {
419 AssertIsOnTargetThread();
420 MOZ_ASSERT(IsClosed());
422 RefPtr<EventSource> myES;
424 auto lock = mSharedData.Lock();
425 // We want to ensure to release ourself even if we have
426 // the shutdown case, thus we put aside a pointer
427 // to the EventSource and null it out right now.
428 myES = std::move(lock->mEventSource);
429 lock->mEventSource = nullptr;
430 lock->mServiceNotifier = nullptr;
433 MOZ_ASSERT(!mIsShutDown);
434 if (mIsShutDown) {
435 return;
438 // Invoke CleanupOnMainThread before cleaning any members. It will call
439 // ShutDown, which is supposed to be called before cleaning any members.
440 if (NS_IsMainThread()) {
441 CleanupOnMainThread();
442 } else {
443 ErrorResult rv;
444 // run CleanupOnMainThread synchronously on main thread since it touches
445 // observers and members only can be accessed on main thread.
446 RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
447 runnable->Dispatch(Killing, rv);
448 MOZ_ASSERT(!rv.Failed());
449 ReleaseWorkerRef();
452 while (mMessagesToDispatch.GetSize() != 0) {
453 delete mMessagesToDispatch.PopFront();
455 mFrozen = false;
456 ResetDecoder();
457 mUnicodeDecoder = nullptr;
458 // Release the object on its owner. Don't access to any members
459 // after it.
460 myES->mESImpl = nullptr;
463 void EventSourceImpl::CleanupOnMainThread() {
464 AssertIsOnMainThread();
465 MOZ_ASSERT(IsClosed());
467 // Call ShutDown before cleaning any members.
468 MOZ_ASSERT(!mIsShutDown);
469 mIsShutDown = true;
471 if (mIsMainThread) {
472 RemoveWindowObservers();
475 if (mTimer) {
476 mTimer->Cancel();
477 mTimer = nullptr;
480 ResetConnection();
481 mPrincipal = nullptr;
482 mSrc = nullptr;
485 class InitRunnable final : public WorkerMainThreadRunnable {
486 public:
487 InitRunnable(WorkerPrivate* aWorkerPrivate,
488 RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
489 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
490 mESImpl(std::move(aEventSourceImpl)),
491 mURL(aURL),
492 mRv(NS_ERROR_NOT_INITIALIZED) {
493 MOZ_ASSERT(aWorkerPrivate);
494 aWorkerPrivate->AssertIsOnWorkerThread();
495 MOZ_ASSERT(mESImpl);
498 bool MainThreadRun() override {
499 // Get principal from worker's owner document or from worker.
500 WorkerPrivate* wp = mWorkerPrivate;
501 while (wp->GetParent()) {
502 wp = wp->GetParent();
504 nsPIDOMWindowInner* window = wp->GetWindow();
505 Document* doc = window ? window->GetExtantDoc() : nullptr;
506 nsCOMPtr<nsIPrincipal> principal =
507 doc ? doc->NodePrincipal() : wp->GetPrincipal();
508 if (!principal) {
509 mRv = NS_ERROR_FAILURE;
510 return true;
512 ErrorResult rv;
513 mESImpl->Init(principal, mURL, rv);
514 mRv = rv.StealNSResult();
516 // We want to ensure that EventSourceImpl's lifecycle
517 // does not depend on this Runnable's one.
518 mESImpl = nullptr;
520 return true;
523 nsresult ErrorCode() const { return mRv; }
525 private:
526 RefPtr<EventSourceImpl> mESImpl;
527 const nsAString& mURL;
528 nsresult mRv;
531 class ConnectRunnable final : public WorkerMainThreadRunnable {
532 public:
533 explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
534 RefPtr<EventSourceImpl> aEventSourceImpl)
535 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
536 mESImpl(std::move(aEventSourceImpl)) {
537 MOZ_ASSERT(aWorkerPrivate);
538 aWorkerPrivate->AssertIsOnWorkerThread();
539 MOZ_ASSERT(mESImpl);
542 bool MainThreadRun() override {
543 MOZ_ASSERT(mESImpl);
544 // We are allowed to access the event target since this runnable is
545 // synchronized with the thread the event target lives on.
546 mESImpl->InitChannelAndRequestEventSource(true);
547 // We want to ensure the shortest possible remaining lifetime
548 // and not depend on the Runnable's destruction.
549 mESImpl = nullptr;
550 return true;
553 private:
554 RefPtr<EventSourceImpl> mESImpl;
557 nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
558 AssertIsOnMainThread();
559 MOZ_ASSERT(!mIsShutDown);
560 // get the src
561 nsCOMPtr<nsIURI> baseURI;
562 nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
563 NS_ENSURE_SUCCESS(rv, rv);
565 nsCOMPtr<nsIURI> srcURI;
566 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
567 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
569 nsAutoString origin;
570 rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
571 NS_ENSURE_SUCCESS(rv, rv);
573 nsAutoCString spec;
574 rv = srcURI->GetSpec(spec);
575 NS_ENSURE_SUCCESS(rv, rv);
577 // This assignment doesn't require extra synchronization because this function
578 // is only ever called from EventSourceImpl::Init(), which is either called
579 // directly if mEventSource was created on the main thread, or via a
580 // synchronous runnable if it was created on a worker thread.
582 // We can't use GetEventSource() here because it would modify the refcount,
583 // and that's not allowed off the owning thread.
584 auto lock = mSharedData.Lock();
585 lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
587 mSrc = srcURI;
588 mOrigin = origin;
589 return NS_OK;
592 nsresult EventSourceImpl::AddWindowObservers() {
593 AssertIsOnMainThread();
594 MOZ_ASSERT(mIsMainThread);
595 MOZ_ASSERT(!mIsShutDown);
596 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
597 NS_ENSURE_STATE(os);
599 nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
600 NS_ENSURE_SUCCESS(rv, rv);
601 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
602 NS_ENSURE_SUCCESS(rv, rv);
603 rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
604 NS_ENSURE_SUCCESS(rv, rv);
605 return NS_OK;
608 void EventSourceImpl::RemoveWindowObservers() {
609 AssertIsOnMainThread();
610 MOZ_ASSERT(mIsMainThread);
611 MOZ_ASSERT(IsClosed());
612 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
613 if (os) {
614 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
615 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
616 os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
620 void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
621 ErrorResult& aRv) {
622 AssertIsOnMainThread();
623 MOZ_ASSERT(aPrincipal);
624 MOZ_ASSERT(ReadyState() == CONNECTING);
625 mPrincipal = aPrincipal;
626 aRv = ParseURL(aURL);
627 if (NS_WARN_IF(aRv.Failed())) {
628 return;
630 // The conditional here is historical and not necessarily sane.
631 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
632 nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
633 &mScriptColumn);
634 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
637 if (mIsMainThread) {
638 // we observe when the window freezes and thaws
639 aRv = AddWindowObservers();
640 if (NS_WARN_IF(aRv.Failed())) {
641 return;
645 mReconnectionTime =
646 Preferences::GetInt("dom.server-events.default-reconnection-time",
647 DEFAULT_RECONNECTION_TIME_VALUE);
649 mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
652 //-----------------------------------------------------------------------------
653 // EventSourceImpl::nsIObserver
654 //-----------------------------------------------------------------------------
656 NS_IMETHODIMP
657 EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
658 const char16_t* aData) {
659 AssertIsOnMainThread();
660 if (IsClosed()) {
661 return NS_OK;
664 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
665 MOZ_ASSERT(mIsMainThread);
667 auto lock = mSharedData.Lock();
668 if (!lock->mEventSource->GetOwner() ||
669 window != lock->mEventSource->GetOwner()) {
670 return NS_OK;
674 DebugOnly<nsresult> rv;
675 if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
676 rv = Freeze();
677 MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
678 } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
679 rv = Thaw();
680 MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
681 } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
682 Close();
685 return NS_OK;
688 //-----------------------------------------------------------------------------
689 // EventSourceImpl::nsIStreamListener
690 //-----------------------------------------------------------------------------
692 NS_IMETHODIMP
693 EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
694 AssertIsOnMainThread();
695 if (IsClosed()) {
696 return NS_ERROR_ABORT;
698 nsresult rv = CheckHealthOfRequestCallback(aRequest);
699 NS_ENSURE_SUCCESS(rv, rv);
701 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
702 NS_ENSURE_SUCCESS(rv, rv);
704 nsresult status;
705 rv = aRequest->GetStatus(&status);
706 NS_ENSURE_SUCCESS(rv, rv);
708 if (NS_FAILED(status)) {
709 // EventSource::OnStopRequest will evaluate if it shall either reestablish
710 // or fail the connection
711 return NS_ERROR_ABORT;
714 uint32_t httpStatus;
715 rv = httpChannel->GetResponseStatus(&httpStatus);
716 NS_ENSURE_SUCCESS(rv, rv);
718 if (httpStatus != 200) {
719 DispatchFailConnection();
720 return NS_ERROR_ABORT;
723 nsAutoCString contentType;
724 rv = httpChannel->GetContentType(contentType);
725 NS_ENSURE_SUCCESS(rv, rv);
727 if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
728 DispatchFailConnection();
729 return NS_ERROR_ABORT;
732 if (!mIsMainThread) {
733 // Try to retarget to worker thread, otherwise fall back to main thread.
734 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
735 if (rr) {
736 rv = rr->RetargetDeliveryTo(this);
737 if (NS_WARN_IF(NS_FAILED(rv))) {
738 NS_WARNING("Retargeting failed");
744 auto lock = mSharedData.Lock();
745 lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
746 this, mHttpChannel->ChannelId(), mInnerWindowID);
748 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
749 this, &EventSourceImpl::AnnounceConnection),
750 NS_DISPATCH_NORMAL);
751 NS_ENSURE_SUCCESS(rv, rv);
752 mStatus = PARSE_STATE_BEGIN_OF_STREAM;
753 return NS_OK;
756 // this method parses the characters as they become available instead of
757 // buffering them.
758 nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
759 void* aClosure,
760 const char* aFromRawSegment,
761 uint32_t aToOffset, uint32_t aCount,
762 uint32_t* aWriteCount) {
763 // The EventSourceImpl instance is hold alive on the
764 // synchronously calling stack, so raw pointer is fine here.
765 EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
766 if (!thisObject || !aWriteCount) {
767 NS_WARNING(
768 "EventSource cannot read from stream: no aClosure or aWriteCount");
769 return NS_ERROR_FAILURE;
771 thisObject->AssertIsOnTargetThread();
772 MOZ_ASSERT(!thisObject->mIsShutDown);
773 thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
774 *aWriteCount = aCount;
775 return NS_OK;
778 void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
779 AssertIsOnTargetThread();
780 if (IsClosed()) {
781 return;
783 char16_t buffer[1024];
784 auto dst = Span(buffer);
785 auto src = AsBytes(Span(aBuffer, aLength));
786 // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
787 for (;;) {
788 uint32_t result;
789 size_t read;
790 size_t written;
791 bool hadErrors;
792 Tie(result, read, written, hadErrors) =
793 mUnicodeDecoder->DecodeToUTF16(src, dst, false);
794 Unused << hadErrors;
795 for (auto c : dst.To(written)) {
796 nsresult rv = ParseCharacter(c);
797 NS_ENSURE_SUCCESS_VOID(rv);
799 if (result == kInputEmpty) {
800 return;
802 src = src.From(read);
806 NS_IMETHODIMP
807 EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
808 nsIInputStream* aInputStream, uint64_t aOffset,
809 uint32_t aCount) {
810 AssertIsOnTargetThread();
811 NS_ENSURE_ARG_POINTER(aInputStream);
812 if (IsClosed()) {
813 return NS_ERROR_ABORT;
816 nsresult rv = CheckHealthOfRequestCallback(aRequest);
817 NS_ENSURE_SUCCESS(rv, rv);
819 uint32_t totalRead;
820 return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
821 aCount, &totalRead);
824 NS_IMETHODIMP
825 EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
826 AssertIsOnMainThread();
828 if (IsClosed()) {
829 return NS_ERROR_ABORT;
831 MOZ_ASSERT(mSrc);
832 // "Network errors that prevents the connection from being established in the
833 // first place (e.g. DNS errors), must cause the user agent to asynchronously
834 // reestablish the connection.
836 // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
837 // response to window.stop() or the user canceling the network connection
838 // manually) must cause the user agent to fail the connection.
840 if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
841 aStatusCode != NS_ERROR_NET_TIMEOUT &&
842 aStatusCode != NS_ERROR_NET_RESET &&
843 aStatusCode != NS_ERROR_NET_INTERRUPT &&
844 aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
845 aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
846 DispatchFailConnection();
847 return NS_ERROR_ABORT;
850 nsresult rv = CheckHealthOfRequestCallback(aRequest);
851 NS_ENSURE_SUCCESS(rv, rv);
853 rv =
854 Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
855 this, &EventSourceImpl::ReestablishConnection),
856 NS_DISPATCH_NORMAL);
857 NS_ENSURE_SUCCESS(rv, rv);
859 return NS_OK;
862 //-----------------------------------------------------------------------------
863 // EventSourceImpl::nsIChannelEventSink
864 //-----------------------------------------------------------------------------
866 NS_IMETHODIMP
867 EventSourceImpl::AsyncOnChannelRedirect(
868 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
869 nsIAsyncVerifyRedirectCallback* aCallback) {
870 AssertIsOnMainThread();
871 if (IsClosed()) {
872 return NS_ERROR_ABORT;
874 nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
875 MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
877 nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
878 NS_ENSURE_SUCCESS(rv, rv);
880 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
882 nsCOMPtr<nsIURI> newURI;
883 rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
884 NS_ENSURE_SUCCESS(rv, rv);
886 bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
888 rv =
889 mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
890 if (NS_FAILED(rv) || !isValidScheme) {
891 DispatchFailConnection();
892 return NS_ERROR_DOM_SECURITY_ERR;
895 // update our channel
897 mHttpChannel = do_QueryInterface(aNewChannel);
898 NS_ENSURE_STATE(mHttpChannel);
900 SetupHttpChannel();
901 // The HTTP impl already copies over the referrer info on
902 // redirects, so we don't need to SetupReferrerInfo().
904 if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
905 rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
906 NS_ENSURE_SUCCESS(rv, rv);
909 aCallback->OnRedirectVerifyCallback(NS_OK);
911 return NS_OK;
914 //-----------------------------------------------------------------------------
915 // EventSourceImpl::nsIInterfaceRequestor
916 //-----------------------------------------------------------------------------
918 NS_IMETHODIMP
919 EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
920 AssertIsOnMainThread();
922 if (IsClosed()) {
923 return NS_ERROR_FAILURE;
926 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
927 *aResult = static_cast<nsIChannelEventSink*>(this);
928 NS_ADDREF_THIS();
929 return NS_OK;
932 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
933 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
934 nsresult rv;
935 nsCOMPtr<nsIPromptFactory> wwatch =
936 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
937 NS_ENSURE_SUCCESS(rv, rv);
939 nsCOMPtr<nsPIDOMWindowOuter> window;
941 // To avoid a data race we may only access the event target if it lives on
942 // the main thread.
943 if (mIsMainThread) {
944 auto lock = mSharedData.Lock();
945 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
946 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
948 if (lock->mEventSource->GetOwner()) {
949 window = lock->mEventSource->GetOwner()->GetOuterWindow();
953 // Get the an auth prompter for our window so that the parenting
954 // of the dialogs works as it should when using tabs.
956 return wwatch->GetPrompt(window, aIID, aResult);
959 return QueryInterface(aIID, aResult);
962 NS_IMETHODIMP
963 EventSourceImpl::IsOnCurrentThread(bool* aResult) {
964 *aResult = IsTargetThread();
965 return NS_OK;
968 NS_IMETHODIMP_(bool)
969 EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
971 nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
972 AssertIsOnMainThread();
973 MOZ_ASSERT(!mIsShutDown);
974 NS_ENSURE_ARG_POINTER(aBaseURI);
976 *aBaseURI = nullptr;
978 nsCOMPtr<nsIURI> baseURI;
980 // first we try from document->GetBaseURI()
981 nsCOMPtr<Document> doc =
982 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
983 if (doc) {
984 baseURI = doc->GetBaseURI();
987 // otherwise we get from the doc's principal
988 if (!baseURI) {
989 auto* basePrin = BasePrincipal::Cast(mPrincipal);
990 nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
991 NS_ENSURE_SUCCESS(rv, rv);
994 NS_ENSURE_STATE(baseURI);
996 baseURI.forget(aBaseURI);
997 return NS_OK;
1000 void EventSourceImpl::SetupHttpChannel() {
1001 AssertIsOnMainThread();
1002 MOZ_ASSERT(!mIsShutDown);
1003 nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
1004 MOZ_ASSERT(NS_SUCCEEDED(rv));
1006 /* set the http request headers */
1008 rv = mHttpChannel->SetRequestHeader(
1009 "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
1010 MOZ_ASSERT(NS_SUCCEEDED(rv));
1012 // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
1014 if (mLastEventID.IsEmpty()) {
1015 return;
1017 NS_ConvertUTF16toUTF8 eventId(mLastEventID);
1018 rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
1019 #ifdef DEBUG
1020 if (NS_FAILED(rv)) {
1021 MOZ_LOG(gEventSourceLog, LogLevel::Warning,
1022 ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
1024 #endif
1025 Unused << rv;
1028 nsresult EventSourceImpl::SetupReferrerInfo(
1029 const nsCOMPtr<Document>& aDocument) {
1030 AssertIsOnMainThread();
1031 MOZ_ASSERT(!mIsShutDown);
1033 if (aDocument) {
1034 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
1035 nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1036 NS_ENSURE_SUCCESS(rv, rv);
1039 return NS_OK;
1042 nsresult EventSourceImpl::InitChannelAndRequestEventSource(
1043 const bool aEventTargetAccessAllowed) {
1044 AssertIsOnMainThread();
1045 if (IsClosed()) {
1046 return NS_ERROR_ABORT;
1049 bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
1051 MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
1053 nsresult rv = aEventTargetAccessAllowed
1054 ? [this]() {
1055 // We can't call GetEventSource() because we're not
1056 // allowed to touch the refcount off the worker thread
1057 // due to an assertion, event if it would have otherwise
1058 // been safe.
1059 auto lock = mSharedData.Lock();
1060 return lock->mEventSource->CheckCurrentGlobalCorrectness();
1062 : NS_OK;
1063 if (NS_FAILED(rv) || !isValidScheme) {
1064 DispatchFailConnection();
1065 return NS_ERROR_DOM_SECURITY_ERR;
1068 nsCOMPtr<Document> doc;
1069 nsSecurityFlags securityFlags =
1070 nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1072 auto lock = mSharedData.Lock();
1073 doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1074 : nullptr;
1076 if (lock->mEventSource->mWithCredentials) {
1077 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1081 // The html spec requires we use fetch cache mode of "no-store". This
1082 // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
1083 nsLoadFlags loadFlags;
1084 loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
1085 nsIRequest::INHIBIT_CACHING;
1087 nsCOMPtr<nsIChannel> channel;
1088 // If we have the document, use it
1089 if (doc) {
1090 MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
1092 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
1093 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
1094 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1095 nullptr, // aPerformanceStorage
1096 loadGroup,
1097 nullptr, // aCallbacks
1098 loadFlags); // aLoadFlags
1099 } else {
1100 // otherwise use the principal
1101 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
1102 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1103 mCookieJarSettings,
1104 nullptr, // aPerformanceStorage
1105 nullptr, // loadGroup
1106 nullptr, // aCallbacks
1107 loadFlags); // aLoadFlags
1110 NS_ENSURE_SUCCESS(rv, rv);
1112 mHttpChannel = do_QueryInterface(channel);
1113 NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
1115 SetupHttpChannel();
1116 rv = SetupReferrerInfo(doc);
1117 NS_ENSURE_SUCCESS(rv, rv);
1119 #ifdef DEBUG
1121 nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
1122 mHttpChannel->GetNotificationCallbacks(
1123 getter_AddRefs(notificationCallbacks));
1124 MOZ_ASSERT(!notificationCallbacks);
1126 #endif
1128 mHttpChannel->SetNotificationCallbacks(this);
1130 // Start reading from the channel
1131 rv = mHttpChannel->AsyncOpen(this);
1132 if (NS_FAILED(rv)) {
1133 DispatchFailConnection();
1134 return rv;
1137 return rv;
1140 void EventSourceImpl::AnnounceConnection() {
1141 AssertIsOnTargetThread();
1142 if (ReadyState() != CONNECTING) {
1143 NS_WARNING("Unexpected mReadyState!!!");
1144 return;
1148 auto lock = mSharedData.Lock();
1149 if (lock->mServiceNotifier) {
1150 lock->mServiceNotifier->ConnectionOpened();
1154 // When a user agent is to announce the connection, the user agent must set
1155 // the readyState attribute to OPEN and queue a task to fire a simple event
1156 // named open at the EventSource object.
1158 SetReadyState(OPEN);
1160 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1161 if (NS_FAILED(rv)) {
1162 return;
1164 // We can't hold the mutex while dispatching the event because the mutex is
1165 // not reentrant, and content might call back into our code.
1166 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
1167 if (NS_FAILED(rv)) {
1168 NS_WARNING("Failed to dispatch the error event!!!");
1169 return;
1173 nsresult EventSourceImpl::ResetConnection() {
1174 AssertIsOnMainThread();
1175 if (mHttpChannel) {
1176 mHttpChannel->Cancel(NS_ERROR_ABORT);
1177 mHttpChannel = nullptr;
1179 return NS_OK;
1182 void EventSourceImpl::ResetDecoder() {
1183 AssertIsOnTargetThread();
1184 if (mUnicodeDecoder) {
1185 UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
1187 mStatus = PARSE_STATE_OFF;
1188 ClearFields();
1191 class CallRestartConnection final : public WorkerMainThreadRunnable {
1192 public:
1193 explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
1194 : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
1195 "EventSource :: RestartConnection"_ns),
1196 mESImpl(std::move(aEventSourceImpl)) {
1197 mWorkerPrivate->AssertIsOnWorkerThread();
1198 MOZ_ASSERT(mESImpl);
1201 bool MainThreadRun() override {
1202 MOZ_ASSERT(mESImpl);
1203 mESImpl->RestartConnection();
1204 // We want to ensure the shortest possible remaining lifetime
1205 // and not depend on the Runnable's destruction.
1206 mESImpl = nullptr;
1207 return true;
1210 protected:
1211 RefPtr<EventSourceImpl> mESImpl;
1214 nsresult EventSourceImpl::RestartConnection() {
1215 AssertIsOnMainThread();
1216 if (IsClosed()) {
1217 return NS_ERROR_ABORT;
1220 nsresult rv = ResetConnection();
1221 NS_ENSURE_SUCCESS(rv, rv);
1222 rv = SetReconnectionTimeout();
1223 NS_ENSURE_SUCCESS(rv, rv);
1224 return NS_OK;
1227 void EventSourceImpl::ReestablishConnection() {
1228 AssertIsOnTargetThread();
1229 if (IsClosed()) {
1230 return;
1233 nsresult rv;
1234 if (mIsMainThread) {
1235 rv = RestartConnection();
1236 } else {
1237 RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
1238 ErrorResult result;
1239 runnable->Dispatch(Canceling, result);
1240 MOZ_ASSERT(!result.Failed());
1241 rv = result.StealNSResult();
1243 if (NS_FAILED(rv)) {
1244 return;
1247 rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1248 if (NS_FAILED(rv)) {
1249 return;
1252 SetReadyState(CONNECTING);
1253 ResetDecoder();
1254 // We can't hold the mutex while dispatching the event because the mutex is
1255 // not reentrant, and content might call back into our code.
1256 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1257 if (NS_FAILED(rv)) {
1258 NS_WARNING("Failed to dispatch the error event!!!");
1259 return;
1263 nsresult EventSourceImpl::SetReconnectionTimeout() {
1264 AssertIsOnMainThread();
1265 if (IsClosed()) {
1266 return NS_ERROR_ABORT;
1269 // the timer will be used whenever the requests are going finished.
1270 if (!mTimer) {
1271 mTimer = NS_NewTimer();
1272 NS_ENSURE_STATE(mTimer);
1275 MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
1276 nsITimer::TYPE_ONE_SHOT));
1278 return NS_OK;
1281 nsresult EventSourceImpl::PrintErrorOnConsole(
1282 const char* aBundleURI, const char* aError,
1283 const nsTArray<nsString>& aFormatStrings) {
1284 AssertIsOnMainThread();
1285 MOZ_ASSERT(!mIsShutDown);
1286 nsCOMPtr<nsIStringBundleService> bundleService =
1287 mozilla::components::StringBundle::Service();
1288 NS_ENSURE_STATE(bundleService);
1290 nsCOMPtr<nsIStringBundle> strBundle;
1291 nsresult rv =
1292 bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
1293 NS_ENSURE_SUCCESS(rv, rv);
1295 nsCOMPtr<nsIConsoleService> console(
1296 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1297 NS_ENSURE_SUCCESS(rv, rv);
1299 nsCOMPtr<nsIScriptError> errObj(
1300 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
1301 NS_ENSURE_SUCCESS(rv, rv);
1303 // Localize the error message
1304 nsAutoString message;
1305 if (!aFormatStrings.IsEmpty()) {
1306 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
1307 } else {
1308 rv = strBundle->GetStringFromName(aError, message);
1310 NS_ENSURE_SUCCESS(rv, rv);
1312 rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
1313 mScriptColumn, nsIScriptError::errorFlag,
1314 "Event Source", mInnerWindowID);
1315 NS_ENSURE_SUCCESS(rv, rv);
1317 // print the error message directly to the JS console
1318 rv = console->LogMessage(errObj);
1319 NS_ENSURE_SUCCESS(rv, rv);
1321 return NS_OK;
1324 nsresult EventSourceImpl::ConsoleError() {
1325 AssertIsOnMainThread();
1326 MOZ_ASSERT(!mIsShutDown);
1327 nsAutoCString targetSpec;
1328 nsresult rv = mSrc->GetSpec(targetSpec);
1329 NS_ENSURE_SUCCESS(rv, rv);
1331 AutoTArray<nsString, 1> formatStrings;
1332 CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
1334 if (ReadyState() == CONNECTING) {
1335 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1336 "connectionFailure", formatStrings);
1337 } else {
1338 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1339 "netInterrupt", formatStrings);
1341 NS_ENSURE_SUCCESS(rv, rv);
1343 return NS_OK;
1346 void EventSourceImpl::DispatchFailConnection() {
1347 AssertIsOnMainThread();
1348 if (IsClosed()) {
1349 return;
1351 nsresult rv = ConsoleError();
1352 if (NS_FAILED(rv)) {
1353 NS_WARNING("Failed to print to the console error");
1355 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
1356 &EventSourceImpl::FailConnection),
1357 NS_DISPATCH_NORMAL);
1358 if (NS_WARN_IF(NS_FAILED(rv))) {
1359 // if the worker is shutting down, the dispatching of normal WorkerRunnables
1360 // fails.
1361 return;
1365 void EventSourceImpl::FailConnection() {
1366 AssertIsOnTargetThread();
1367 if (IsClosed()) {
1368 return;
1370 // Must change state to closed before firing event to content.
1371 SetReadyState(CLOSED);
1372 // When a user agent is to fail the connection, the user agent must set the
1373 // readyState attribute to CLOSED and queue a task to fire a simple event
1374 // named error at the EventSource object.
1375 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1376 if (NS_SUCCEEDED(rv)) {
1377 // We can't hold the mutex while dispatching the event because the mutex
1378 // is not reentrant, and content might call back into our code.
1379 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1380 if (NS_FAILED(rv)) {
1381 NS_WARNING("Failed to dispatch the error event!!!");
1384 // Call CloseInternal in the end of function because it may release
1385 // EventSourceImpl.
1386 CloseInternal();
1389 NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
1390 AssertIsOnMainThread();
1391 if (IsClosed()) {
1392 return NS_OK;
1395 MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
1397 if (!mFrozen) {
1398 nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
1399 if (NS_FAILED(rv)) {
1400 NS_WARNING("InitChannelAndRequestEventSource() failed");
1403 return NS_OK;
1406 nsresult EventSourceImpl::Thaw() {
1407 AssertIsOnMainThread();
1408 if (IsClosed() || !mFrozen) {
1409 return NS_OK;
1412 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1414 mFrozen = false;
1415 nsresult rv;
1416 if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
1417 nsCOMPtr<nsIRunnable> event =
1418 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1419 this, &EventSourceImpl::DispatchAllMessageEvents);
1420 NS_ENSURE_STATE(event);
1422 mGoingToDispatchAllMessages = true;
1424 rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1425 NS_ENSURE_SUCCESS(rv, rv);
1428 rv = InitChannelAndRequestEventSource(mIsMainThread);
1429 NS_ENSURE_SUCCESS(rv, rv);
1431 return NS_OK;
1434 nsresult EventSourceImpl::Freeze() {
1435 AssertIsOnMainThread();
1436 if (IsClosed() || mFrozen) {
1437 return NS_OK;
1440 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1441 mFrozen = true;
1442 return NS_OK;
1445 nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
1446 AssertIsOnTargetThread();
1447 MOZ_ASSERT(!mIsShutDown);
1448 UniquePtr<Message> message(std::move(mCurrentMessage));
1449 ClearFields();
1451 if (!message || message->mData.IsEmpty()) {
1452 return NS_OK;
1455 // removes the trailing LF from mData
1456 MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
1457 "Invalid trailing character! LF was expected instead.");
1458 message->mData.SetLength(message->mData.Length() - 1);
1460 if (message->mEventName.IsEmpty()) {
1461 message->mEventName.AssignLiteral("message");
1464 mMessagesToDispatch.Push(message.release());
1466 if (!mGoingToDispatchAllMessages) {
1467 nsCOMPtr<nsIRunnable> event =
1468 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1469 this, &EventSourceImpl::DispatchAllMessageEvents);
1470 NS_ENSURE_STATE(event);
1472 mGoingToDispatchAllMessages = true;
1474 return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1477 return NS_OK;
1480 void EventSourceImpl::DispatchAllMessageEvents() {
1481 AssertIsOnTargetThread();
1482 mGoingToDispatchAllMessages = false;
1484 if (IsClosed() || mFrozen) {
1485 return;
1488 nsresult rv;
1489 AutoJSAPI jsapi;
1491 auto lock = mSharedData.Lock();
1492 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1493 if (NS_FAILED(rv)) {
1494 return;
1497 if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1498 return;
1502 JSContext* cx = jsapi.cx();
1504 while (mMessagesToDispatch.GetSize() > 0) {
1505 UniquePtr<Message> message(mMessagesToDispatch.PopFront());
1507 if (message->mLastEventID.isSome()) {
1508 mLastEventID.Assign(message->mLastEventID.value());
1511 if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
1512 message->mLastEventID = Some(mLastEventID);
1516 auto lock = mSharedData.Lock();
1517 if (lock->mServiceNotifier) {
1518 lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1519 message->mData, mReconnectionTime,
1520 PR_Now());
1524 // Now we can turn our string into a jsval
1525 JS::Rooted<JS::Value> jsData(cx);
1527 JSString* jsString;
1528 jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
1529 message->mData.Length());
1530 NS_ENSURE_TRUE_VOID(jsString);
1532 jsData.setString(jsString);
1535 // create an event that uses the MessageEvent interface,
1536 // which does not bubble, is not cancelable, and has no default action
1538 RefPtr<EventSource> eventSource = GetEventSource();
1539 RefPtr<MessageEvent> event =
1540 new MessageEvent(eventSource, nullptr, nullptr);
1542 event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
1543 Cancelable::eNo, jsData, mOrigin, mLastEventID,
1544 nullptr, Sequence<OwningNonNull<MessagePort>>());
1545 event->SetTrusted(true);
1547 // We can't hold the mutex while dispatching the event because the mutex is
1548 // not reentrant, and content might call back into our code.
1549 IgnoredErrorResult err;
1550 eventSource->DispatchEvent(*event, err);
1551 if (err.Failed()) {
1552 NS_WARNING("Failed to dispatch the message event!!!");
1553 return;
1556 if (IsClosed() || mFrozen) {
1557 return;
1562 void EventSourceImpl::ClearFields() {
1563 AssertIsOnTargetThread();
1564 mCurrentMessage = nullptr;
1565 mLastFieldName.Truncate();
1566 mLastFieldValue.Truncate();
1569 nsresult EventSourceImpl::SetFieldAndClear() {
1570 MOZ_ASSERT(!mIsShutDown);
1571 AssertIsOnTargetThread();
1572 if (mLastFieldName.IsEmpty()) {
1573 mLastFieldValue.Truncate();
1574 return NS_OK;
1576 if (!mCurrentMessage) {
1577 mCurrentMessage = MakeUnique<Message>();
1579 char16_t first_char;
1580 first_char = mLastFieldName.CharAt(0);
1582 // with no case folding performed
1583 switch (first_char) {
1584 case char16_t('d'):
1585 if (mLastFieldName.EqualsLiteral("data")) {
1586 // If the field name is "data" append the field value to the data
1587 // buffer, then append a single U+000A LINE FEED (LF) character
1588 // to the data buffer.
1589 mCurrentMessage->mData.Append(mLastFieldValue);
1590 mCurrentMessage->mData.Append(LF_CHAR);
1592 break;
1594 case char16_t('e'):
1595 if (mLastFieldName.EqualsLiteral("event")) {
1596 mCurrentMessage->mEventName.Assign(mLastFieldValue);
1598 break;
1600 case char16_t('i'):
1601 if (mLastFieldName.EqualsLiteral("id")) {
1602 mCurrentMessage->mLastEventID = Some(mLastFieldValue);
1604 break;
1606 case char16_t('r'):
1607 if (mLastFieldName.EqualsLiteral("retry")) {
1608 uint32_t newValue = 0;
1609 uint32_t i = 0; // we must ensure that there are only digits
1610 bool assign = true;
1611 for (i = 0; i < mLastFieldValue.Length(); ++i) {
1612 if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
1613 mLastFieldValue.CharAt(i) > (char16_t)'9') {
1614 assign = false;
1615 break;
1617 newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
1618 ((uint32_t)((char16_t)'0')));
1621 if (assign) {
1622 if (newValue < MIN_RECONNECTION_TIME_VALUE) {
1623 mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
1624 } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
1625 mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
1626 } else {
1627 mReconnectionTime = newValue;
1630 break;
1632 break;
1635 mLastFieldName.Truncate();
1636 mLastFieldValue.Truncate();
1638 return NS_OK;
1641 nsresult EventSourceImpl::CheckHealthOfRequestCallback(
1642 nsIRequest* aRequestCallback) {
1643 // This function could be run on target thread if http channel support
1644 // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
1646 // check if we have been closed or if the request has been canceled
1647 // or if we have been frozen
1648 if (IsClosed() || mFrozen || !mHttpChannel) {
1649 return NS_ERROR_ABORT;
1652 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
1653 NS_ENSURE_STATE(httpChannel);
1655 if (httpChannel != mHttpChannel) {
1656 NS_WARNING("wrong channel from request callback");
1657 return NS_ERROR_ABORT;
1660 return NS_OK;
1663 nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
1664 AssertIsOnTargetThread();
1665 nsresult rv;
1667 if (IsClosed()) {
1668 return NS_ERROR_ABORT;
1671 switch (mStatus) {
1672 case PARSE_STATE_OFF:
1673 NS_ERROR("Invalid state");
1674 return NS_ERROR_FAILURE;
1675 break;
1677 case PARSE_STATE_BEGIN_OF_STREAM:
1678 if (aChr == CR_CHAR) {
1679 mStatus = PARSE_STATE_CR_CHAR;
1680 } else if (aChr == LF_CHAR) {
1681 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1682 } else if (aChr == COLON_CHAR) {
1683 mStatus = PARSE_STATE_COMMENT;
1684 } else {
1685 mLastFieldName += aChr;
1686 mStatus = PARSE_STATE_FIELD_NAME;
1688 break;
1690 case PARSE_STATE_CR_CHAR:
1691 if (aChr == CR_CHAR) {
1692 rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
1693 NS_ENSURE_SUCCESS(rv, rv);
1694 } else if (aChr == LF_CHAR) {
1695 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1696 } else if (aChr == COLON_CHAR) {
1697 mStatus = PARSE_STATE_COMMENT;
1698 } else {
1699 mLastFieldName += aChr;
1700 mStatus = PARSE_STATE_FIELD_NAME;
1703 break;
1705 case PARSE_STATE_COMMENT:
1706 if (aChr == CR_CHAR) {
1707 mStatus = PARSE_STATE_CR_CHAR;
1708 } else if (aChr == LF_CHAR) {
1709 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1712 break;
1714 case PARSE_STATE_FIELD_NAME:
1715 if (aChr == CR_CHAR) {
1716 rv = SetFieldAndClear();
1717 NS_ENSURE_SUCCESS(rv, rv);
1719 mStatus = PARSE_STATE_CR_CHAR;
1720 } else if (aChr == LF_CHAR) {
1721 rv = SetFieldAndClear();
1722 NS_ENSURE_SUCCESS(rv, rv);
1724 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1725 } else if (aChr == COLON_CHAR) {
1726 mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
1727 } else {
1728 mLastFieldName += aChr;
1731 break;
1733 case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
1734 if (aChr == CR_CHAR) {
1735 rv = SetFieldAndClear();
1736 NS_ENSURE_SUCCESS(rv, rv);
1738 mStatus = PARSE_STATE_CR_CHAR;
1739 } else if (aChr == LF_CHAR) {
1740 rv = SetFieldAndClear();
1741 NS_ENSURE_SUCCESS(rv, rv);
1743 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1744 } else if (aChr == SPACE_CHAR) {
1745 mStatus = PARSE_STATE_FIELD_VALUE;
1746 } else {
1747 mLastFieldValue += aChr;
1748 mStatus = PARSE_STATE_FIELD_VALUE;
1751 break;
1753 case PARSE_STATE_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 != 0) {
1765 // Avoid appending the null char to the field value.
1766 mLastFieldValue += aChr;
1767 } else if (mLastFieldName.EqualsLiteral("id")) {
1768 // Ignore the whole id field if aChr is null
1769 mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
1770 mLastFieldValue.Truncate();
1773 break;
1775 case PARSE_STATE_IGNORE_FIELD_VALUE:
1776 if (aChr == CR_CHAR) {
1777 mStatus = PARSE_STATE_CR_CHAR;
1778 } else if (aChr == LF_CHAR) {
1779 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1781 break;
1783 case PARSE_STATE_BEGIN_OF_LINE:
1784 if (aChr == CR_CHAR) {
1785 rv = DispatchCurrentMessageEvent(); // there is an empty line
1786 NS_ENSURE_SUCCESS(rv, rv);
1788 mStatus = PARSE_STATE_CR_CHAR;
1789 } else if (aChr == LF_CHAR) {
1790 rv = DispatchCurrentMessageEvent(); // there is an empty line
1791 NS_ENSURE_SUCCESS(rv, rv);
1793 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1794 } else if (aChr == COLON_CHAR) {
1795 mStatus = PARSE_STATE_COMMENT;
1796 } else if (aChr != 0) {
1797 // Avoid appending the null char to the field name.
1798 mLastFieldName += aChr;
1799 mStatus = PARSE_STATE_FIELD_NAME;
1802 break;
1805 return NS_OK;
1808 namespace {
1810 class WorkerRunnableDispatcher final : public WorkerRunnable {
1811 RefPtr<EventSourceImpl> mEventSourceImpl;
1813 public:
1814 WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
1815 WorkerPrivate* aWorkerPrivate,
1816 already_AddRefed<nsIRunnable> aEvent)
1817 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
1818 mEventSourceImpl(std::move(aImpl)),
1819 mEvent(std::move(aEvent)) {}
1821 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1822 aWorkerPrivate->AssertIsOnWorkerThread();
1823 return !NS_FAILED(mEvent->Run());
1826 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
1827 bool aRunResult) override {
1828 // Ensure we drop the RefPtr on the worker thread
1829 // and to not keep us alive longer than needed.
1830 mEventSourceImpl = nullptr;
1833 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
1834 // We don't call WorkerRunnable::PreDispatch because it would assert the
1835 // wrong thing about which thread we're on. We're on whichever thread the
1836 // channel implementation is running on (probably the main thread or
1837 // transport thread).
1838 return true;
1841 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1842 bool aDispatchResult) override {
1843 // We don't call WorkerRunnable::PostDispatch because it would assert the
1844 // wrong thing about which thread we're on. We're on whichever thread the
1845 // channel implementation is running on (probably the main thread or
1846 // transport thread).
1849 private:
1850 nsCOMPtr<nsIRunnable> mEvent;
1853 } // namespace
1855 bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
1856 MOZ_ASSERT(!mWorkerRef);
1857 MOZ_ASSERT(aWorkerPrivate);
1858 aWorkerPrivate->AssertIsOnWorkerThread();
1860 if (mIsShutDown) {
1861 return false;
1864 RefPtr<EventSourceImpl> self = this;
1865 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
1866 aWorkerPrivate, "EventSource", [self]() { self->Close(); });
1868 if (NS_WARN_IF(!workerRef)) {
1869 return false;
1872 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
1873 return true;
1876 void EventSourceImpl::ReleaseWorkerRef() {
1877 MOZ_ASSERT(IsClosed());
1878 MOZ_ASSERT(IsCurrentThreadRunningWorker());
1879 mWorkerRef = nullptr;
1882 //-----------------------------------------------------------------------------
1883 // EventSourceImpl::nsIEventTarget
1884 //-----------------------------------------------------------------------------
1885 NS_IMETHODIMP
1886 EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
1887 nsCOMPtr<nsIRunnable> event(aEvent);
1888 return Dispatch(event.forget(), aFlags);
1891 NS_IMETHODIMP
1892 EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
1893 uint32_t aFlags) {
1894 nsCOMPtr<nsIRunnable> event_ref(aEvent);
1895 if (mIsMainThread) {
1896 return NS_DispatchToMainThread(event_ref.forget());
1899 if (mIsShutDown) {
1900 // We want to avoid clutter about errors in our shutdown logs,
1901 // so just report NS_OK (we have no explicit return value
1902 // for shutdown).
1903 return NS_OK;
1906 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
1907 // runnable.
1908 RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
1909 this, mWorkerRef->Private(), event_ref.forget());
1911 if (!event->Dispatch()) {
1912 return NS_ERROR_FAILURE;
1914 return NS_OK;
1917 NS_IMETHODIMP
1918 EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
1919 uint32_t aDelayMs) {
1920 return NS_ERROR_NOT_IMPLEMENTED;
1923 //-----------------------------------------------------------------------------
1924 // EventSourceImpl::nsIThreadRetargetableStreamListener
1925 //-----------------------------------------------------------------------------
1926 NS_IMETHODIMP
1927 EventSourceImpl::CheckListenerChain() {
1928 MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
1929 return NS_OK;
1931 ////////////////////////////////////////////////////////////////////////////////
1932 // EventSource
1933 ////////////////////////////////////////////////////////////////////////////////
1935 EventSource::EventSource(nsIGlobalObject* aGlobal,
1936 nsICookieJarSettings* aCookieJarSettings,
1937 bool aWithCredentials)
1938 : DOMEventTargetHelper(aGlobal),
1939 mWithCredentials(aWithCredentials),
1940 mIsMainThread(NS_IsMainThread()) {
1941 MOZ_ASSERT(aGlobal);
1942 MOZ_ASSERT(aCookieJarSettings);
1943 mESImpl = new EventSourceImpl(this, aCookieJarSettings);
1946 EventSource::~EventSource() = default;
1948 nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1949 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1950 // it doesn't bubble, and it isn't cancelable
1951 event->InitEvent(aName, false, false);
1952 event->SetTrusted(true);
1953 ErrorResult rv;
1954 DispatchEvent(*event, rv);
1955 return rv.StealNSResult();
1958 /* static */
1959 already_AddRefed<EventSource> EventSource::Constructor(
1960 const GlobalObject& aGlobal, const nsAString& aURL,
1961 const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
1962 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1963 if (NS_WARN_IF(!global)) {
1964 aRv.Throw(NS_ERROR_FAILURE);
1965 return nullptr;
1968 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1969 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
1970 if (ownerWindow) {
1971 Document* doc = ownerWindow->GetExtantDoc();
1972 if (NS_WARN_IF(!doc)) {
1973 aRv.Throw(NS_ERROR_FAILURE);
1974 return nullptr;
1977 cookieJarSettings = doc->CookieJarSettings();
1978 } else {
1979 // Worker side.
1980 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1981 MOZ_ASSERT(workerPrivate);
1982 cookieJarSettings = workerPrivate->CookieJarSettings();
1985 RefPtr<EventSource> eventSource = new EventSource(
1986 global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
1988 if (NS_IsMainThread()) {
1989 // Get principal from document and init EventSourceImpl
1990 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1991 do_QueryInterface(aGlobal.GetAsSupports());
1992 if (!scriptPrincipal) {
1993 aRv.Throw(NS_ERROR_FAILURE);
1994 return nullptr;
1996 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
1997 if (!principal) {
1998 aRv.Throw(NS_ERROR_FAILURE);
1999 return nullptr;
2001 eventSource->mESImpl->Init(principal, aURL, aRv);
2002 if (NS_WARN_IF(aRv.Failed())) {
2003 return nullptr;
2006 eventSource->mESImpl->InitChannelAndRequestEventSource(true);
2007 return eventSource.forget();
2010 // Worker side.
2012 // Scope for possible failures that need cleanup
2013 auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
2015 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2016 MOZ_ASSERT(workerPrivate);
2018 eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
2020 RefPtr<InitRunnable> initRunnable =
2021 new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
2022 initRunnable->Dispatch(Canceling, aRv);
2023 if (NS_WARN_IF(aRv.Failed())) {
2024 return nullptr;
2027 aRv = initRunnable->ErrorCode();
2028 if (NS_WARN_IF(aRv.Failed())) {
2029 return nullptr;
2032 // In workers we have to keep the worker alive using a WorkerRef in order
2033 // to dispatch messages correctly.
2034 if (!eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
2035 // The worker is already shutting down. Let's return an already closed
2036 // object, but marked as Connecting.
2037 // mESImpl is nulled by this call such that EventSourceImpl is
2038 // released before returning the object, otherwise
2039 // it will set EventSource to a CLOSED state in its DTOR..
2040 eventSource->mESImpl->Close();
2041 eventSource->mReadyState = EventSourceImpl::CONNECTING;
2043 return eventSource.forget();
2046 // Let's connect to the server.
2047 RefPtr<ConnectRunnable> connectRunnable =
2048 new ConnectRunnable(workerPrivate, eventSource->mESImpl);
2049 connectRunnable->Dispatch(Canceling, aRv);
2050 if (NS_WARN_IF(aRv.Failed())) {
2051 return nullptr;
2054 // End of scope for possible failures
2055 guardESImpl.release();
2058 return eventSource.forget();
2061 // nsWrapperCache
2062 JSObject* EventSource::WrapObject(JSContext* aCx,
2063 JS::Handle<JSObject*> aGivenProto) {
2064 return EventSource_Binding::Wrap(aCx, this, aGivenProto);
2067 void EventSource::Close() {
2068 AssertIsOnTargetThread();
2069 if (mESImpl) {
2070 // Close potentially kills ourself, ensure
2071 // to not access any members afterwards.
2072 mESImpl->Close();
2076 //-----------------------------------------------------------------------------
2077 // EventSource::nsISupports
2078 //-----------------------------------------------------------------------------
2080 NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
2082 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
2083 DOMEventTargetHelper)
2084 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2086 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
2087 DOMEventTargetHelper)
2088 if (tmp->mESImpl) {
2089 // IsCertainlyaliveForCC will return true and cause the cycle
2090 // collector to skip this instance when mESImpl is non-null and
2091 // points back to ourself.
2092 // mESImpl is initialized to be non-null in the constructor
2093 // and should have been wiped out in our close function.
2094 MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
2095 tmp->Close();
2097 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2099 bool EventSource::IsCertainlyAliveForCC() const {
2100 // Until we are double linked forth and back, we want to stay alive.
2101 if (!mESImpl) {
2102 return false;
2104 auto lock = mESImpl->mSharedData.Lock();
2105 return lock->mEventSource == this;
2108 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
2109 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
2111 NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
2112 NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
2114 } // namespace mozilla::dom