Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / base / EventSource.cpp
blobafc08577114dca16e4cc44892513be7ba7da624e
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/GlobalFreezeObserver.h"
14 #include "mozilla/dom/EventSource.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/Try.h"
26 #include "mozilla/UniquePtrExtensions.h"
27 #include "nsComponentManagerUtils.h"
28 #include "nsIThreadRetargetableStreamListener.h"
29 #include "nsNetUtil.h"
30 #include "nsIAuthPrompt.h"
31 #include "nsIAuthPrompt2.h"
32 #include "nsIHttpChannel.h"
33 #include "nsIInputStream.h"
34 #include "nsIInterfaceRequestorUtils.h"
35 #include "nsMimeTypes.h"
36 #include "nsIPromptFactory.h"
37 #include "nsIWindowWatcher.h"
38 #include "nsPresContext.h"
39 #include "nsProxyRelease.h"
40 #include "nsContentPolicyUtils.h"
41 #include "nsIStringBundle.h"
42 #include "nsIConsoleService.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 "xpcpublic.h"
50 #include "nsWrapperCacheInlines.h"
51 #include "mozilla/Attributes.h"
52 #include "mozilla/StaticPrefs_dom.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.serverEvents.defaultReconnectionTime
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 nsIChannelEventSink,
76 public nsIInterfaceRequestor,
77 public nsISerialEventTarget,
78 public nsITimerCallback,
79 public nsINamed,
80 public nsIThreadRetargetableStreamListener,
81 public GlobalTeardownObserver,
82 public GlobalFreezeObserver {
83 public:
84 NS_DECL_THREADSAFE_ISUPPORTS
85 NS_DECL_NSIREQUESTOBSERVER
86 NS_DECL_NSISTREAMLISTENER
87 NS_DECL_NSICHANNELEVENTSINK
88 NS_DECL_NSIINTERFACEREQUESTOR
89 NS_DECL_NSIEVENTTARGET_FULL
90 NS_DECL_NSITIMERCALLBACK
91 NS_DECL_NSINAMED
92 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
94 EventSourceImpl(EventSource* aEventSource,
95 nsICookieJarSettings* aCookieJarSettings);
97 enum { CONNECTING = 0U, OPEN = 1U, CLOSED = 2U };
99 void Close();
101 void Init(nsIGlobalObject* aWindowGlobal, nsIPrincipal* aPrincipal,
102 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 void DisconnectFromOwner() override {
121 Close();
122 GlobalTeardownObserver::DisconnectFromOwner();
124 void FrozenCallback(nsIGlobalObject* aOwner) override {
125 DebugOnly<nsresult> rv = Freeze();
126 MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
128 void ThawedCallback(nsIGlobalObject* aOwner) override {
129 DebugOnly<nsresult> rv = Thaw();
130 MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
133 nsresult Thaw();
134 nsresult Freeze();
136 nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
137 const nsTArray<nsString>& aFormatStrings);
138 nsresult ConsoleError();
140 static nsresult StreamReaderFunc(nsIInputStream* aInputStream, void* aClosure,
141 const char* aFromRawSegment,
142 uint32_t aToOffset, uint32_t aCount,
143 uint32_t* aWriteCount);
144 void ParseSegment(const char* aBuffer, uint32_t aLength);
145 nsresult SetFieldAndClear();
146 void ClearFields();
147 nsresult ResetEvent();
148 nsresult DispatchCurrentMessageEvent();
149 nsresult ParseCharacter(char16_t aChr);
150 nsresult CheckHealthOfRequestCallback(nsIRequest* aRequestCallback);
151 nsresult OnRedirectVerifyCallback(nsresult result);
152 nsresult ParseURL(const nsAString& aURL);
153 nsresult AddGlobalObservers(nsIGlobalObject* aGlobal);
154 void RemoveWindowObservers();
156 void CloseInternal();
157 void CleanupOnMainThread();
159 bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
160 void ReleaseWorkerRef();
162 void AssertIsOnTargetThread() const {
163 MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
166 bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
168 uint16_t ReadyState() {
169 auto lock = mSharedData.Lock();
170 if (lock->mEventSource) {
171 return lock->mEventSource->mReadyState;
173 // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
174 // means that the EventSource has been closed.
175 return CLOSED;
178 void SetReadyState(uint16_t aReadyState) {
179 auto lock = mSharedData.Lock();
180 MOZ_ASSERT(lock->mEventSource);
181 MOZ_ASSERT(!mIsShutDown);
182 lock->mEventSource->mReadyState = aReadyState;
185 bool IsClosed() { return ReadyState() == CLOSED; }
187 RefPtr<EventSource> GetEventSource() {
188 AssertIsOnTargetThread();
189 auto lock = mSharedData.Lock();
190 return lock->mEventSource;
194 * A simple state machine used to manage the event-source's line buffer
196 * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
198 * PARSE_STATE_BEGIN_OF_STREAM -> PARSE_STATE_CR_CHAR |
199 * PARSE_STATE_BEGIN_OF_LINE |
200 * PARSE_STATE_COMMENT |
201 * PARSE_STATE_FIELD_NAME
203 * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
204 * PARSE_STATE_COMMENT |
205 * PARSE_STATE_FIELD_NAME |
206 * PARSE_STATE_BEGIN_OF_LINE
208 * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
209 * PARSE_STATE_BEGIN_OF_LINE
211 * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
212 * PARSE_STATE_BEGIN_OF_LINE |
213 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
215 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
216 * PARSE_STATE_CR_CHAR |
217 * PARSE_STATE_BEGIN_OF_LINE
219 * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
220 * PARSE_STATE_BEGIN_OF_LINE
222 * PARSE_STATE_BEGIN_OF_LINE -> PARSE_STATE_CR_CHAR |
223 * PARSE_STATE_COMMENT |
224 * PARSE_STATE_FIELD_NAME |
225 * PARSE_STATE_BEGIN_OF_LINE
227 * Whenever the parser find an empty line or the end-of-file
228 * it dispatches the stacked event.
231 enum ParserStatus {
232 PARSE_STATE_OFF = 0,
233 PARSE_STATE_BEGIN_OF_STREAM,
234 PARSE_STATE_CR_CHAR,
235 PARSE_STATE_COMMENT,
236 PARSE_STATE_FIELD_NAME,
237 PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE,
238 PARSE_STATE_FIELD_VALUE,
239 PARSE_STATE_IGNORE_FIELD_VALUE,
240 PARSE_STATE_BEGIN_OF_LINE
243 // Connection related data members. Should only be accessed on main thread.
244 nsCOMPtr<nsIURI> mSrc;
245 uint32_t mReconnectionTime; // in ms
246 nsCOMPtr<nsIPrincipal> mPrincipal;
247 nsString mOrigin;
248 nsCOMPtr<nsITimer> mTimer;
249 nsCOMPtr<nsIHttpChannel> mHttpChannel;
251 struct Message {
252 nsString mEventName;
253 // We need to be able to distinguish between different states of id field:
254 // 1) is not given at all
255 // 2) is given but is empty
256 // 3) is given and has a value
257 // We can't check for the 1st state with a simple nsString.
258 Maybe<nsString> mLastEventID;
259 nsString mData;
262 // Message related data members. May be set / initialized when initializing
263 // EventSourceImpl on target thread but should only be used on target thread.
264 nsString mLastEventID;
265 UniquePtr<Message> mCurrentMessage;
266 nsDeque<Message> mMessagesToDispatch;
267 ParserStatus mStatus;
268 mozilla::UniquePtr<mozilla::Decoder> mUnicodeDecoder;
269 nsString mLastFieldName;
270 nsString mLastFieldValue;
272 // EventSourceImpl internal states.
273 // WorkerRef to keep the worker alive. (accessed on worker thread only)
274 RefPtr<ThreadSafeWorkerRef> mWorkerRef;
275 // Whether the window is frozen. May be set on main thread and read on target
276 // thread.
277 Atomic<bool> mFrozen;
278 // There are some messages are going to be dispatched when thaw.
279 bool mGoingToDispatchAllMessages;
280 // Whether the EventSource is run on main thread.
281 const bool mIsMainThread;
282 // Whether the EventSourceImpl is going to be destroyed.
283 Atomic<bool> mIsShutDown;
285 class EventSourceServiceNotifier final {
286 public:
287 EventSourceServiceNotifier(RefPtr<EventSourceImpl>&& aEventSourceImpl,
288 uint64_t aHttpChannelId, uint64_t aInnerWindowID)
289 : mEventSourceImpl(std::move(aEventSourceImpl)),
290 mHttpChannelId(aHttpChannelId),
291 mInnerWindowID(aInnerWindowID),
292 mConnectionOpened(false) {
293 AssertIsOnMainThread();
294 mService = EventSourceEventService::GetOrCreate();
297 void ConnectionOpened() {
298 mEventSourceImpl->AssertIsOnTargetThread();
299 mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
300 mConnectionOpened = true;
303 void EventReceived(const nsAString& aEventName,
304 const nsAString& aLastEventID, const nsAString& aData,
305 uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
306 mEventSourceImpl->AssertIsOnTargetThread();
307 mService->EventReceived(mHttpChannelId, mInnerWindowID, aEventName,
308 aLastEventID, aData, aRetry, aTimeStamp);
311 ~EventSourceServiceNotifier() {
312 // It is safe to call this on any thread because
313 // EventSourceConnectionClosed method is thread safe and
314 // NS_ReleaseOnMainThread explicitly releases the service on the main
315 // thread.
316 if (mConnectionOpened) {
317 // We want to notify about connection being closed only if we told
318 // it was ever opened. The check is needed if OnStartRequest is called
319 // on the main thread while close() is called on a worker thread.
320 mService->EventSourceConnectionClosed(mHttpChannelId, mInnerWindowID);
322 NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
323 mService.forget());
326 private:
327 RefPtr<EventSourceEventService> mService;
328 RefPtr<EventSourceImpl> mEventSourceImpl;
329 uint64_t mHttpChannelId;
330 uint64_t mInnerWindowID;
331 bool mConnectionOpened;
334 struct SharedData {
335 RefPtr<EventSource> mEventSource;
336 UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
339 DataMutex<SharedData> mSharedData;
341 // Event Source owner information:
342 // - the script file name
343 // - source code line number and column number where the Event Source object
344 // was constructed.
345 // - the ID of the inner window where the script lives. Note that this may not
346 // be the same as the Event Source owner window.
347 // These attributes are used for error reporting. Should only be accessed on
348 // target thread
349 JSCallingLocation mCallingLocation;
350 uint64_t mInnerWindowID;
352 private:
353 nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
355 // Pointer to the target thread for checking whether we are
356 // on the target thread. This is intentionally a non-owning
357 // pointer in order not to affect the thread destruction
358 // sequence. This pointer must only be compared for equality
359 // and must not be dereferenced.
360 nsIThread* mTargetThread;
362 // prevent bad usage
363 EventSourceImpl(const EventSourceImpl& x) = delete;
364 EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
365 ~EventSourceImpl() {
366 if (IsClosed()) {
367 return;
369 // If we threw during Init we never called Close
370 SetReadyState(CLOSED);
371 CloseInternal();
375 NS_IMPL_ISUPPORTS(EventSourceImpl, nsIStreamListener, nsIRequestObserver,
376 nsIChannelEventSink, nsIInterfaceRequestor,
377 nsISerialEventTarget, nsIEventTarget,
378 nsIThreadRetargetableStreamListener, nsITimerCallback,
379 nsINamed)
381 EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
382 nsICookieJarSettings* aCookieJarSettings)
383 : mReconnectionTime(0),
384 mStatus(PARSE_STATE_OFF),
385 mFrozen(false),
386 mGoingToDispatchAllMessages(false),
387 mIsMainThread(NS_IsMainThread()),
388 mIsShutDown(false),
389 mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
390 mInnerWindowID(0),
391 mCookieJarSettings(aCookieJarSettings),
392 mTargetThread(NS_GetCurrentThread()) {
393 MOZ_ASSERT(aEventSource);
394 SetReadyState(CONNECTING);
397 class CleanupRunnable final : public WorkerMainThreadRunnable {
398 public:
399 explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
400 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
401 "EventSource :: Cleanup"_ns),
402 mESImpl(std::move(aEventSourceImpl)) {
403 MOZ_ASSERT(mESImpl);
406 bool MainThreadRun() override {
407 MOZ_ASSERT(mESImpl);
408 mESImpl->CleanupOnMainThread();
409 // We want to ensure the shortest possible remaining lifetime
410 // and not depend on the Runnable's destruction.
411 mESImpl = nullptr;
412 return true;
415 protected:
416 RefPtr<EventSourceImpl> mESImpl;
419 void EventSourceImpl::Close() {
420 if (IsClosed()) {
421 return;
424 SetReadyState(CLOSED);
425 // CloseInternal potentially kills ourself, ensure
426 // to not access any members afterwards.
427 CloseInternal();
430 void EventSourceImpl::CloseInternal() {
431 AssertIsOnTargetThread();
432 MOZ_ASSERT(IsClosed());
434 RefPtr<EventSource> myES;
436 auto lock = mSharedData.Lock();
437 // We want to ensure to release ourself even if we have
438 // the shutdown case, thus we put aside a pointer
439 // to the EventSource and null it out right now.
440 myES = std::move(lock->mEventSource);
441 lock->mEventSource = nullptr;
442 lock->mServiceNotifier = nullptr;
445 MOZ_ASSERT(!mIsShutDown);
446 if (mIsShutDown) {
447 return;
450 // Invoke CleanupOnMainThread before cleaning any members. It will call
451 // ShutDown, which is supposed to be called before cleaning any members.
452 if (NS_IsMainThread()) {
453 CleanupOnMainThread();
454 } else {
455 ErrorResult rv;
456 // run CleanupOnMainThread synchronously on main thread since it touches
457 // observers and members only can be accessed on main thread.
458 RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
459 runnable->Dispatch(GetCurrentThreadWorkerPrivate(), Killing, rv);
460 MOZ_ASSERT(!rv.Failed());
461 ReleaseWorkerRef();
464 while (mMessagesToDispatch.GetSize() != 0) {
465 delete mMessagesToDispatch.PopFront();
467 mFrozen = false;
468 ResetDecoder();
469 mUnicodeDecoder = nullptr;
470 // Release the object on its owner. Don't access to any members
471 // after it.
472 myES->mESImpl = nullptr;
475 void EventSourceImpl::CleanupOnMainThread() {
476 AssertIsOnMainThread();
477 MOZ_ASSERT(IsClosed());
479 // Call ShutDown before cleaning any members.
480 MOZ_ASSERT(!mIsShutDown);
481 mIsShutDown = true;
483 if (mIsMainThread) {
484 RemoveWindowObservers();
487 if (mTimer) {
488 mTimer->Cancel();
489 mTimer = nullptr;
492 ResetConnection();
493 mPrincipal = nullptr;
494 mSrc = nullptr;
497 class ConnectRunnable final : public WorkerMainThreadRunnable {
498 public:
499 explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
500 RefPtr<EventSourceImpl> aEventSourceImpl)
501 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
502 mESImpl(std::move(aEventSourceImpl)) {
503 MOZ_ASSERT(aWorkerPrivate);
504 aWorkerPrivate->AssertIsOnWorkerThread();
505 MOZ_ASSERT(mESImpl);
508 bool MainThreadRun() override {
509 MOZ_ASSERT(mESImpl);
510 // We are allowed to access the event target since this runnable is
511 // synchronized with the thread the event target lives on.
512 mESImpl->InitChannelAndRequestEventSource(true);
513 // We want to ensure the shortest possible remaining lifetime
514 // and not depend on the Runnable's destruction.
515 mESImpl = nullptr;
516 return true;
519 private:
520 RefPtr<EventSourceImpl> mESImpl;
523 nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
524 MOZ_ASSERT(!mIsShutDown);
525 // get the src
526 nsCOMPtr<nsIURI> baseURI;
527 nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
528 NS_ENSURE_SUCCESS(rv, rv);
530 nsCOMPtr<nsIURI> srcURI;
531 nsCOMPtr<Document> doc =
532 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
533 if (doc) {
534 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, doc->GetDocumentCharacterSet(),
535 baseURI);
536 } else {
537 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
540 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
542 nsAutoString origin;
543 rv = nsContentUtils::GetWebExposedOriginSerialization(srcURI, origin);
544 NS_ENSURE_SUCCESS(rv, rv);
546 nsAutoCString spec;
547 rv = srcURI->GetSpec(spec);
548 NS_ENSURE_SUCCESS(rv, rv);
550 // This assignment doesn't require extra synchronization because this function
551 // is only ever called from EventSourceImpl::Init(), which is either called
552 // directly if mEventSource was created on the main thread, or via a
553 // synchronous runnable if it was created on a worker thread.
555 // We can't use GetEventSource() here because it would modify the refcount,
556 // and that's not allowed off the owning thread.
557 auto lock = mSharedData.Lock();
558 lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
560 mSrc = srcURI;
561 mOrigin = origin;
562 return NS_OK;
565 nsresult EventSourceImpl::AddGlobalObservers(nsIGlobalObject* aGlobal) {
566 AssertIsOnMainThread();
567 MOZ_ASSERT(mIsMainThread);
568 MOZ_ASSERT(!mIsShutDown);
570 GlobalTeardownObserver::BindToOwner(aGlobal);
571 GlobalFreezeObserver::BindToOwner(aGlobal);
573 return NS_OK;
576 void EventSourceImpl::RemoveWindowObservers() {
577 AssertIsOnMainThread();
578 MOZ_ASSERT(mIsMainThread);
579 MOZ_ASSERT(IsClosed());
580 GlobalTeardownObserver::DisconnectFromOwner();
581 DisconnectFreezeObserver();
584 void EventSourceImpl::Init(nsIGlobalObject* aWindowGlobal,
585 nsIPrincipal* aPrincipal, const nsAString& aURL,
586 ErrorResult& aRv) {
587 // aWindowGlobal should only exist for main-thread EventSource
588 MOZ_ASSERT_IF(aWindowGlobal, mIsMainThread);
589 MOZ_ASSERT(aPrincipal);
590 MOZ_ASSERT(ReadyState() == CONNECTING);
591 mPrincipal = aPrincipal;
592 aRv = ParseURL(aURL);
593 if (NS_WARN_IF(aRv.Failed())) {
594 return;
596 // The conditional here is historical and not necessarily sane.
597 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
598 mCallingLocation = JSCallingLocation::Get();
599 if (mIsMainThread) {
600 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
604 if (mIsMainThread) {
605 // We observe when the window freezes and thaws.
607 // This will store raw pointer of us in GTO, which sounds scary as the
608 // object can be accessed cross thread. But it should be safe as GTO and
609 // EventSourceImpl lifetime should be managed in the same main thread.
611 // XXX(krosylight): But what about workers? See bug 1910585.
612 aRv = AddGlobalObservers(aWindowGlobal);
613 if (NS_WARN_IF(aRv.Failed())) {
614 return;
618 mReconnectionTime = StaticPrefs::dom_serverEvents_defaultReconnectionTime();
619 if (!mReconnectionTime) {
620 mReconnectionTime = DEFAULT_RECONNECTION_TIME_VALUE;
623 mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
626 //-----------------------------------------------------------------------------
627 // EventSourceImpl::nsIStreamListener
628 //-----------------------------------------------------------------------------
630 NS_IMETHODIMP
631 EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
632 AssertIsOnMainThread();
633 if (IsClosed()) {
634 return NS_ERROR_ABORT;
636 nsresult rv = CheckHealthOfRequestCallback(aRequest);
637 NS_ENSURE_SUCCESS(rv, rv);
639 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
640 NS_ENSURE_SUCCESS(rv, rv);
642 nsresult status;
643 rv = aRequest->GetStatus(&status);
644 NS_ENSURE_SUCCESS(rv, rv);
646 if (NS_FAILED(status)) {
647 // EventSource::OnStopRequest will evaluate if it shall either reestablish
648 // or fail the connection, based on the status.
649 return status;
652 uint32_t httpStatus;
653 rv = httpChannel->GetResponseStatus(&httpStatus);
654 NS_ENSURE_SUCCESS(rv, rv);
656 if (httpStatus != 200) {
657 DispatchFailConnection();
658 return NS_ERROR_ABORT;
661 nsAutoCString contentType;
662 rv = httpChannel->GetContentType(contentType);
663 NS_ENSURE_SUCCESS(rv, rv);
665 if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
666 DispatchFailConnection();
667 return NS_ERROR_ABORT;
670 if (!mIsMainThread) {
671 // Try to retarget to worker thread, otherwise fall back to main thread.
672 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
673 if (rr) {
674 rv = rr->RetargetDeliveryTo(this);
675 if (NS_WARN_IF(NS_FAILED(rv))) {
676 NS_WARNING("Retargeting failed");
682 auto lock = mSharedData.Lock();
683 lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
684 this, mHttpChannel->ChannelId(), mInnerWindowID);
686 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
687 this, &EventSourceImpl::AnnounceConnection),
688 NS_DISPATCH_NORMAL);
689 NS_ENSURE_SUCCESS(rv, rv);
690 mStatus = PARSE_STATE_BEGIN_OF_STREAM;
691 return NS_OK;
694 // this method parses the characters as they become available instead of
695 // buffering them.
696 nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
697 void* aClosure,
698 const char* aFromRawSegment,
699 uint32_t aToOffset, uint32_t aCount,
700 uint32_t* aWriteCount) {
701 // The EventSourceImpl instance is hold alive on the
702 // synchronously calling stack, so raw pointer is fine here.
703 EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
704 if (!thisObject || !aWriteCount) {
705 NS_WARNING(
706 "EventSource cannot read from stream: no aClosure or aWriteCount");
707 return NS_ERROR_FAILURE;
709 thisObject->AssertIsOnTargetThread();
710 MOZ_ASSERT(!thisObject->mIsShutDown);
711 thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
712 *aWriteCount = aCount;
713 return NS_OK;
716 void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
717 AssertIsOnTargetThread();
718 if (IsClosed()) {
719 return;
721 char16_t buffer[1024];
722 auto dst = Span(buffer);
723 auto src = AsBytes(Span(aBuffer, aLength));
724 // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
725 for (;;) {
726 uint32_t result;
727 size_t read;
728 size_t written;
729 std::tie(result, read, written, std::ignore) =
730 mUnicodeDecoder->DecodeToUTF16(src, dst, false);
731 for (auto c : dst.To(written)) {
732 nsresult rv = ParseCharacter(c);
733 NS_ENSURE_SUCCESS_VOID(rv);
735 if (result == kInputEmpty) {
736 return;
738 src = src.From(read);
742 NS_IMETHODIMP
743 EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
744 nsIInputStream* aInputStream, uint64_t aOffset,
745 uint32_t aCount) {
746 AssertIsOnTargetThread();
747 NS_ENSURE_ARG_POINTER(aInputStream);
748 if (IsClosed()) {
749 return NS_ERROR_ABORT;
752 nsresult rv = CheckHealthOfRequestCallback(aRequest);
753 NS_ENSURE_SUCCESS(rv, rv);
755 uint32_t totalRead;
756 return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
757 aCount, &totalRead);
760 NS_IMETHODIMP
761 EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
762 AssertIsOnMainThread();
764 if (IsClosed()) {
765 return NS_ERROR_ABORT;
767 MOZ_ASSERT(mSrc);
768 // "Network errors that prevents the connection from being established in the
769 // first place (e.g. DNS errors), must cause the user agent to asynchronously
770 // reestablish the connection.
772 // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
773 // response to window.stop() or the user canceling the network connection
774 // manually) must cause the user agent to fail the connection.
775 // There could be additional network errors that are not covered in the above
776 // checks
777 // See Bug 1808511
778 if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
779 aStatusCode != NS_ERROR_NET_TIMEOUT &&
780 aStatusCode != NS_ERROR_NET_RESET &&
781 aStatusCode != NS_ERROR_NET_INTERRUPT &&
782 aStatusCode != NS_ERROR_NET_PARTIAL_TRANSFER &&
783 aStatusCode != NS_ERROR_NET_TIMEOUT_EXTERNAL &&
784 aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
785 aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL &&
786 aStatusCode != NS_ERROR_INVALID_CONTENT_ENCODING) {
787 DispatchFailConnection();
788 return NS_ERROR_ABORT;
791 nsresult rv = CheckHealthOfRequestCallback(aRequest);
792 NS_ENSURE_SUCCESS(rv, rv);
794 rv =
795 Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
796 this, &EventSourceImpl::ReestablishConnection),
797 NS_DISPATCH_NORMAL);
798 NS_ENSURE_SUCCESS(rv, rv);
800 return NS_OK;
803 //-----------------------------------------------------------------------------
804 // EventSourceImpl::nsIChannelEventSink
805 //-----------------------------------------------------------------------------
807 NS_IMETHODIMP
808 EventSourceImpl::AsyncOnChannelRedirect(
809 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
810 nsIAsyncVerifyRedirectCallback* aCallback) {
811 AssertIsOnMainThread();
812 if (IsClosed()) {
813 return NS_ERROR_ABORT;
815 nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
816 MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
818 nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
819 NS_ENSURE_SUCCESS(rv, rv);
821 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
823 nsCOMPtr<nsIURI> newURI;
824 rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
825 NS_ENSURE_SUCCESS(rv, rv);
827 bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
829 rv =
830 mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
831 if (NS_FAILED(rv) || !isValidScheme) {
832 DispatchFailConnection();
833 return NS_ERROR_DOM_SECURITY_ERR;
836 // update our channel
838 mHttpChannel = do_QueryInterface(aNewChannel);
839 NS_ENSURE_STATE(mHttpChannel);
841 SetupHttpChannel();
842 // The HTTP impl already copies over the referrer info on
843 // redirects, so we don't need to SetupReferrerInfo().
845 if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
846 rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
847 NS_ENSURE_SUCCESS(rv, rv);
850 aCallback->OnRedirectVerifyCallback(NS_OK);
852 return NS_OK;
855 //-----------------------------------------------------------------------------
856 // EventSourceImpl::nsIInterfaceRequestor
857 //-----------------------------------------------------------------------------
859 NS_IMETHODIMP
860 EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
861 AssertIsOnMainThread();
863 if (IsClosed()) {
864 return NS_ERROR_FAILURE;
867 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
868 *aResult = static_cast<nsIChannelEventSink*>(this);
869 NS_ADDREF_THIS();
870 return NS_OK;
873 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
874 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
875 nsresult rv;
876 nsCOMPtr<nsIPromptFactory> wwatch =
877 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
878 NS_ENSURE_SUCCESS(rv, rv);
880 nsCOMPtr<nsPIDOMWindowOuter> window;
882 // To avoid a data race we may only access the event target if it lives on
883 // the main thread.
884 if (mIsMainThread) {
885 auto lock = mSharedData.Lock();
886 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
887 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
889 if (nsGlobalWindowInner* win = lock->mEventSource->GetOwnerWindow()) {
890 window = win->GetOuterWindow();
894 // Get the an auth prompter for our window so that the parenting
895 // of the dialogs works as it should when using tabs.
897 return wwatch->GetPrompt(window, aIID, aResult);
900 return QueryInterface(aIID, aResult);
903 NS_IMETHODIMP
904 EventSourceImpl::IsOnCurrentThread(bool* aResult) {
905 *aResult = IsTargetThread();
906 return NS_OK;
909 NS_IMETHODIMP_(bool)
910 EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
912 nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
913 MOZ_ASSERT(!mIsShutDown);
914 NS_ENSURE_ARG_POINTER(aBaseURI);
916 *aBaseURI = nullptr;
918 nsCOMPtr<nsIURI> baseURI;
920 // first we try from document->GetBaseURI()
921 nsCOMPtr<Document> doc =
922 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
923 if (doc) {
924 baseURI = doc->GetBaseURI();
927 // otherwise we get from the doc's principal
928 if (!baseURI) {
929 auto* basePrin = BasePrincipal::Cast(mPrincipal);
930 nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
931 NS_ENSURE_SUCCESS(rv, rv);
934 NS_ENSURE_STATE(baseURI);
936 baseURI.forget(aBaseURI);
937 return NS_OK;
940 void EventSourceImpl::SetupHttpChannel() {
941 AssertIsOnMainThread();
942 MOZ_ASSERT(!mIsShutDown);
943 nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
944 MOZ_ASSERT(NS_SUCCEEDED(rv));
946 /* set the http request headers */
948 rv = mHttpChannel->SetRequestHeader(
949 "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
950 MOZ_ASSERT(NS_SUCCEEDED(rv));
952 // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
954 if (mLastEventID.IsEmpty()) {
955 return;
957 NS_ConvertUTF16toUTF8 eventId(mLastEventID);
958 rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
959 #ifdef DEBUG
960 if (NS_FAILED(rv)) {
961 MOZ_LOG(gEventSourceLog, LogLevel::Warning,
962 ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
964 #endif
965 Unused << rv;
968 nsresult EventSourceImpl::SetupReferrerInfo(
969 const nsCOMPtr<Document>& aDocument) {
970 AssertIsOnMainThread();
971 MOZ_ASSERT(!mIsShutDown);
973 if (aDocument) {
974 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
975 nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
976 NS_ENSURE_SUCCESS(rv, rv);
979 return NS_OK;
982 nsresult EventSourceImpl::InitChannelAndRequestEventSource(
983 const bool aEventTargetAccessAllowed) {
984 AssertIsOnMainThread();
985 if (IsClosed()) {
986 return NS_ERROR_ABORT;
989 bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
991 MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
993 nsresult rv = aEventTargetAccessAllowed ? [this]() {
994 // We can't call GetEventSource() because we're not
995 // allowed to touch the refcount off the worker thread
996 // due to an assertion, event if it would have otherwise
997 // been safe.
998 auto lock = mSharedData.Lock();
999 return lock->mEventSource->CheckCurrentGlobalCorrectness();
1001 : NS_OK;
1002 if (NS_FAILED(rv) || !isValidScheme) {
1003 DispatchFailConnection();
1004 return NS_ERROR_DOM_SECURITY_ERR;
1007 nsCOMPtr<Document> doc;
1008 nsSecurityFlags securityFlags =
1009 nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1011 auto lock = mSharedData.Lock();
1012 doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1013 : nullptr;
1015 if (lock->mEventSource->mWithCredentials) {
1016 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1020 // The html spec requires we use fetch cache mode of "no-store". This
1021 // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
1022 nsLoadFlags loadFlags;
1023 loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
1024 nsIRequest::INHIBIT_CACHING;
1026 nsCOMPtr<nsIChannel> channel;
1027 // If we have the document, use it
1028 if (doc) {
1029 MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
1031 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
1032 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
1033 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1034 nullptr, // aPerformanceStorage
1035 loadGroup,
1036 nullptr, // aCallbacks
1037 loadFlags); // aLoadFlags
1038 } else {
1039 // otherwise use the principal
1040 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
1041 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1042 mCookieJarSettings,
1043 nullptr, // aPerformanceStorage
1044 nullptr, // loadGroup
1045 nullptr, // aCallbacks
1046 loadFlags); // aLoadFlags
1049 NS_ENSURE_SUCCESS(rv, rv);
1051 mHttpChannel = do_QueryInterface(channel);
1052 NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
1054 SetupHttpChannel();
1055 rv = SetupReferrerInfo(doc);
1056 NS_ENSURE_SUCCESS(rv, rv);
1058 #ifdef DEBUG
1060 nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
1061 mHttpChannel->GetNotificationCallbacks(
1062 getter_AddRefs(notificationCallbacks));
1063 MOZ_ASSERT(!notificationCallbacks);
1065 #endif
1067 mHttpChannel->SetNotificationCallbacks(this);
1069 // Start reading from the channel
1070 rv = mHttpChannel->AsyncOpen(this);
1071 if (NS_FAILED(rv)) {
1072 DispatchFailConnection();
1073 return rv;
1076 return rv;
1079 void EventSourceImpl::AnnounceConnection() {
1080 AssertIsOnTargetThread();
1081 if (ReadyState() != CONNECTING) {
1082 NS_WARNING("Unexpected mReadyState!!!");
1083 return;
1087 auto lock = mSharedData.Lock();
1088 if (lock->mServiceNotifier) {
1089 lock->mServiceNotifier->ConnectionOpened();
1093 // When a user agent is to announce the connection, the user agent must set
1094 // the readyState attribute to OPEN and queue a task to fire a simple event
1095 // named open at the EventSource object.
1097 SetReadyState(OPEN);
1099 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1100 if (NS_FAILED(rv)) {
1101 return;
1103 // We can't hold the mutex while dispatching the event because the mutex is
1104 // not reentrant, and content might call back into our code.
1105 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
1106 if (NS_FAILED(rv)) {
1107 NS_WARNING("Failed to dispatch the error event!!!");
1108 return;
1112 nsresult EventSourceImpl::ResetConnection() {
1113 AssertIsOnMainThread();
1114 if (mHttpChannel) {
1115 mHttpChannel->Cancel(NS_ERROR_ABORT);
1116 mHttpChannel = nullptr;
1118 return NS_OK;
1121 void EventSourceImpl::ResetDecoder() {
1122 AssertIsOnTargetThread();
1123 if (mUnicodeDecoder) {
1124 UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
1126 mStatus = PARSE_STATE_OFF;
1127 ClearFields();
1130 class CallRestartConnection final : public WorkerMainThreadRunnable {
1131 public:
1132 explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
1133 : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
1134 "EventSource :: RestartConnection"_ns),
1135 mESImpl(std::move(aEventSourceImpl)) {
1136 MOZ_ASSERT(mESImpl);
1139 bool MainThreadRun() override {
1140 MOZ_ASSERT(mESImpl);
1141 mESImpl->RestartConnection();
1142 // We want to ensure the shortest possible remaining lifetime
1143 // and not depend on the Runnable's destruction.
1144 mESImpl = nullptr;
1145 return true;
1148 protected:
1149 RefPtr<EventSourceImpl> mESImpl;
1152 nsresult EventSourceImpl::RestartConnection() {
1153 AssertIsOnMainThread();
1154 if (IsClosed()) {
1155 return NS_ERROR_ABORT;
1158 nsresult rv = ResetConnection();
1159 NS_ENSURE_SUCCESS(rv, rv);
1160 rv = SetReconnectionTimeout();
1161 NS_ENSURE_SUCCESS(rv, rv);
1162 return NS_OK;
1165 void EventSourceImpl::ReestablishConnection() {
1166 AssertIsOnTargetThread();
1167 if (IsClosed()) {
1168 return;
1171 nsresult rv;
1172 if (mIsMainThread) {
1173 rv = RestartConnection();
1174 } else {
1175 RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
1176 ErrorResult result;
1177 runnable->Dispatch(GetCurrentThreadWorkerPrivate(), Canceling, result);
1178 MOZ_ASSERT(!result.Failed());
1179 rv = result.StealNSResult();
1181 if (NS_FAILED(rv)) {
1182 return;
1185 rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1186 if (NS_FAILED(rv)) {
1187 return;
1190 SetReadyState(CONNECTING);
1191 ResetDecoder();
1192 // We can't hold the mutex while dispatching the event because the mutex is
1193 // not reentrant, and content might call back into our code.
1194 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1195 if (NS_FAILED(rv)) {
1196 NS_WARNING("Failed to dispatch the error event!!!");
1197 return;
1201 nsresult EventSourceImpl::SetReconnectionTimeout() {
1202 AssertIsOnMainThread();
1203 if (IsClosed()) {
1204 return NS_ERROR_ABORT;
1207 // the timer will be used whenever the requests are going finished.
1208 if (!mTimer) {
1209 mTimer = NS_NewTimer();
1210 NS_ENSURE_STATE(mTimer);
1213 MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
1214 nsITimer::TYPE_ONE_SHOT));
1216 return NS_OK;
1219 nsresult EventSourceImpl::PrintErrorOnConsole(
1220 const char* aBundleURI, const char* aError,
1221 const nsTArray<nsString>& aFormatStrings) {
1222 AssertIsOnMainThread();
1223 MOZ_ASSERT(!mIsShutDown);
1224 nsCOMPtr<nsIStringBundleService> bundleService =
1225 mozilla::components::StringBundle::Service();
1226 NS_ENSURE_STATE(bundleService);
1228 nsCOMPtr<nsIStringBundle> strBundle;
1229 nsresult rv =
1230 bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
1231 NS_ENSURE_SUCCESS(rv, rv);
1233 nsCOMPtr<nsIConsoleService> console(
1234 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1235 NS_ENSURE_SUCCESS(rv, rv);
1237 nsCOMPtr<nsIScriptError> errObj(
1238 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
1239 NS_ENSURE_SUCCESS(rv, rv);
1241 // Localize the error message
1242 nsAutoString message;
1243 if (!aFormatStrings.IsEmpty()) {
1244 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
1245 } else {
1246 rv = strBundle->GetStringFromName(aError, message);
1248 NS_ENSURE_SUCCESS(rv, rv);
1250 rv = errObj->InitWithWindowID(
1251 message, mCallingLocation.FileName(), mCallingLocation.mLine,
1252 mCallingLocation.mColumn, nsIScriptError::errorFlag, "Event Source",
1253 mInnerWindowID);
1254 NS_ENSURE_SUCCESS(rv, rv);
1256 // print the error message directly to the JS console
1257 rv = console->LogMessage(errObj);
1258 NS_ENSURE_SUCCESS(rv, rv);
1260 return NS_OK;
1263 nsresult EventSourceImpl::ConsoleError() {
1264 AssertIsOnMainThread();
1265 MOZ_ASSERT(!mIsShutDown);
1266 nsAutoCString targetSpec;
1267 nsresult rv = mSrc->GetSpec(targetSpec);
1268 NS_ENSURE_SUCCESS(rv, rv);
1270 AutoTArray<nsString, 1> formatStrings;
1271 CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
1273 if (ReadyState() == CONNECTING) {
1274 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1275 "connectionFailure", formatStrings);
1276 } else {
1277 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1278 "netInterrupt", formatStrings);
1280 NS_ENSURE_SUCCESS(rv, rv);
1282 return NS_OK;
1285 void EventSourceImpl::DispatchFailConnection() {
1286 AssertIsOnMainThread();
1287 if (IsClosed()) {
1288 return;
1290 nsresult rv = ConsoleError();
1291 if (NS_FAILED(rv)) {
1292 NS_WARNING("Failed to print to the console error");
1294 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
1295 &EventSourceImpl::FailConnection),
1296 NS_DISPATCH_NORMAL);
1297 if (NS_WARN_IF(NS_FAILED(rv))) {
1298 // if the worker is shutting down, the dispatching of normal WorkerRunnables
1299 // fails.
1300 return;
1304 void EventSourceImpl::FailConnection() {
1305 AssertIsOnTargetThread();
1306 if (IsClosed()) {
1307 return;
1309 // Must change state to closed before firing event to content.
1310 SetReadyState(CLOSED);
1311 // When a user agent is to fail the connection, the user agent must set the
1312 // readyState attribute to CLOSED and queue a task to fire a simple event
1313 // named error at the EventSource object.
1314 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1315 if (NS_SUCCEEDED(rv)) {
1316 // We can't hold the mutex while dispatching the event because the mutex
1317 // is not reentrant, and content might call back into our code.
1318 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1319 if (NS_FAILED(rv)) {
1320 NS_WARNING("Failed to dispatch the error event!!!");
1323 // Call CloseInternal in the end of function because it may release
1324 // EventSourceImpl.
1325 CloseInternal();
1328 NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
1329 AssertIsOnMainThread();
1330 if (IsClosed()) {
1331 return NS_OK;
1334 MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
1336 if (!mFrozen) {
1337 nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
1338 if (NS_FAILED(rv)) {
1339 NS_WARNING("InitChannelAndRequestEventSource() failed");
1342 return NS_OK;
1345 NS_IMETHODIMP EventSourceImpl::GetName(nsACString& aName) {
1346 aName.AssignLiteral("EventSourceImpl");
1347 return NS_OK;
1350 nsresult EventSourceImpl::Thaw() {
1351 AssertIsOnMainThread();
1352 if (IsClosed() || !mFrozen) {
1353 return NS_OK;
1356 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1358 mFrozen = false;
1359 nsresult rv;
1360 if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
1361 nsCOMPtr<nsIRunnable> event =
1362 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1363 this, &EventSourceImpl::DispatchAllMessageEvents);
1364 NS_ENSURE_STATE(event);
1366 mGoingToDispatchAllMessages = true;
1368 rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1369 NS_ENSURE_SUCCESS(rv, rv);
1372 rv = InitChannelAndRequestEventSource(mIsMainThread);
1373 NS_ENSURE_SUCCESS(rv, rv);
1375 return NS_OK;
1378 nsresult EventSourceImpl::Freeze() {
1379 AssertIsOnMainThread();
1380 if (IsClosed() || mFrozen) {
1381 return NS_OK;
1384 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1385 mFrozen = true;
1386 return NS_OK;
1389 nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
1390 AssertIsOnTargetThread();
1391 MOZ_ASSERT(!mIsShutDown);
1392 UniquePtr<Message> message(std::move(mCurrentMessage));
1393 ClearFields();
1395 if (!message || message->mData.IsEmpty()) {
1396 return NS_OK;
1399 // removes the trailing LF from mData
1400 MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
1401 "Invalid trailing character! LF was expected instead.");
1402 message->mData.SetLength(message->mData.Length() - 1);
1404 if (message->mEventName.IsEmpty()) {
1405 message->mEventName.AssignLiteral("message");
1408 mMessagesToDispatch.Push(message.release());
1410 if (!mGoingToDispatchAllMessages) {
1411 nsCOMPtr<nsIRunnable> event =
1412 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1413 this, &EventSourceImpl::DispatchAllMessageEvents);
1414 NS_ENSURE_STATE(event);
1416 mGoingToDispatchAllMessages = true;
1418 return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1421 return NS_OK;
1424 void EventSourceImpl::DispatchAllMessageEvents() {
1425 AssertIsOnTargetThread();
1426 mGoingToDispatchAllMessages = false;
1428 if (IsClosed() || mFrozen) {
1429 return;
1432 nsresult rv;
1433 AutoJSAPI jsapi;
1435 auto lock = mSharedData.Lock();
1436 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1437 if (NS_FAILED(rv)) {
1438 return;
1441 if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1442 return;
1446 JSContext* cx = jsapi.cx();
1448 while (mMessagesToDispatch.GetSize() > 0) {
1449 UniquePtr<Message> message(mMessagesToDispatch.PopFront());
1451 if (message->mLastEventID.isSome()) {
1452 mLastEventID.Assign(message->mLastEventID.value());
1455 if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
1456 message->mLastEventID = Some(mLastEventID);
1460 auto lock = mSharedData.Lock();
1461 if (lock->mServiceNotifier) {
1462 lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1463 message->mData, mReconnectionTime,
1464 PR_Now());
1468 // Now we can turn our string into a jsval
1469 JS::Rooted<JS::Value> jsData(cx);
1471 JSString* jsString;
1472 jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
1473 message->mData.Length());
1474 NS_ENSURE_TRUE_VOID(jsString);
1476 jsData.setString(jsString);
1479 // create an event that uses the MessageEvent interface,
1480 // which does not bubble, is not cancelable, and has no default action
1482 RefPtr<EventSource> eventSource = GetEventSource();
1483 RefPtr<MessageEvent> event =
1484 new MessageEvent(eventSource, nullptr, nullptr);
1486 event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
1487 Cancelable::eNo, jsData, mOrigin, mLastEventID,
1488 nullptr, Sequence<OwningNonNull<MessagePort>>());
1489 event->SetTrusted(true);
1491 // We can't hold the mutex while dispatching the event because the mutex is
1492 // not reentrant, and content might call back into our code.
1493 IgnoredErrorResult err;
1494 eventSource->DispatchEvent(*event, err);
1495 if (err.Failed()) {
1496 NS_WARNING("Failed to dispatch the message event!!!");
1497 return;
1500 if (IsClosed() || mFrozen) {
1501 return;
1506 void EventSourceImpl::ClearFields() {
1507 AssertIsOnTargetThread();
1508 mCurrentMessage = nullptr;
1509 mLastFieldName.Truncate();
1510 mLastFieldValue.Truncate();
1513 nsresult EventSourceImpl::SetFieldAndClear() {
1514 MOZ_ASSERT(!mIsShutDown);
1515 AssertIsOnTargetThread();
1516 if (mLastFieldName.IsEmpty()) {
1517 mLastFieldValue.Truncate();
1518 return NS_OK;
1520 if (!mCurrentMessage) {
1521 mCurrentMessage = MakeUnique<Message>();
1523 char16_t first_char;
1524 first_char = mLastFieldName.CharAt(0);
1526 // with no case folding performed
1527 switch (first_char) {
1528 case char16_t('d'):
1529 if (mLastFieldName.EqualsLiteral("data")) {
1530 // If the field name is "data" append the field value to the data
1531 // buffer, then append a single U+000A LINE FEED (LF) character
1532 // to the data buffer.
1533 mCurrentMessage->mData.Append(mLastFieldValue);
1534 mCurrentMessage->mData.Append(LF_CHAR);
1536 break;
1538 case char16_t('e'):
1539 if (mLastFieldName.EqualsLiteral("event")) {
1540 mCurrentMessage->mEventName.Assign(mLastFieldValue);
1542 break;
1544 case char16_t('i'):
1545 if (mLastFieldName.EqualsLiteral("id")) {
1546 mCurrentMessage->mLastEventID = Some(mLastFieldValue);
1548 break;
1550 case char16_t('r'):
1551 if (mLastFieldName.EqualsLiteral("retry")) {
1552 uint32_t newValue = 0;
1553 uint32_t i = 0; // we must ensure that there are only digits
1554 bool assign = true;
1555 for (i = 0; i < mLastFieldValue.Length(); ++i) {
1556 if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
1557 mLastFieldValue.CharAt(i) > (char16_t)'9') {
1558 assign = false;
1559 break;
1561 newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
1562 ((uint32_t)((char16_t)'0')));
1565 if (assign) {
1566 if (newValue < MIN_RECONNECTION_TIME_VALUE) {
1567 mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
1568 } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
1569 mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
1570 } else {
1571 mReconnectionTime = newValue;
1574 break;
1576 break;
1579 mLastFieldName.Truncate();
1580 mLastFieldValue.Truncate();
1582 return NS_OK;
1585 nsresult EventSourceImpl::CheckHealthOfRequestCallback(
1586 nsIRequest* aRequestCallback) {
1587 // This function could be run on target thread if http channel support
1588 // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
1590 // check if we have been closed or if the request has been canceled
1591 // or if we have been frozen
1592 if (IsClosed() || mFrozen || !mHttpChannel) {
1593 return NS_ERROR_ABORT;
1596 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
1597 NS_ENSURE_STATE(httpChannel);
1599 if (httpChannel != mHttpChannel) {
1600 NS_WARNING("wrong channel from request callback");
1601 return NS_ERROR_ABORT;
1604 return NS_OK;
1607 nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
1608 AssertIsOnTargetThread();
1609 nsresult rv;
1611 if (IsClosed()) {
1612 return NS_ERROR_ABORT;
1615 switch (mStatus) {
1616 case PARSE_STATE_OFF:
1617 NS_ERROR("Invalid state");
1618 return NS_ERROR_FAILURE;
1619 break;
1621 case PARSE_STATE_BEGIN_OF_STREAM:
1622 if (aChr == CR_CHAR) {
1623 mStatus = PARSE_STATE_CR_CHAR;
1624 } else if (aChr == LF_CHAR) {
1625 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1626 } else if (aChr == COLON_CHAR) {
1627 mStatus = PARSE_STATE_COMMENT;
1628 } else {
1629 mLastFieldName += aChr;
1630 mStatus = PARSE_STATE_FIELD_NAME;
1632 break;
1634 case PARSE_STATE_CR_CHAR:
1635 if (aChr == CR_CHAR) {
1636 rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
1637 NS_ENSURE_SUCCESS(rv, rv);
1638 } else if (aChr == LF_CHAR) {
1639 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1640 } else if (aChr == COLON_CHAR) {
1641 mStatus = PARSE_STATE_COMMENT;
1642 } else {
1643 mLastFieldName += aChr;
1644 mStatus = PARSE_STATE_FIELD_NAME;
1647 break;
1649 case PARSE_STATE_COMMENT:
1650 if (aChr == CR_CHAR) {
1651 mStatus = PARSE_STATE_CR_CHAR;
1652 } else if (aChr == LF_CHAR) {
1653 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1656 break;
1658 case PARSE_STATE_FIELD_NAME:
1659 if (aChr == CR_CHAR) {
1660 rv = SetFieldAndClear();
1661 NS_ENSURE_SUCCESS(rv, rv);
1663 mStatus = PARSE_STATE_CR_CHAR;
1664 } else if (aChr == LF_CHAR) {
1665 rv = SetFieldAndClear();
1666 NS_ENSURE_SUCCESS(rv, rv);
1668 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1669 } else if (aChr == COLON_CHAR) {
1670 mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
1671 } else {
1672 mLastFieldName += aChr;
1675 break;
1677 case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
1678 if (aChr == CR_CHAR) {
1679 rv = SetFieldAndClear();
1680 NS_ENSURE_SUCCESS(rv, rv);
1682 mStatus = PARSE_STATE_CR_CHAR;
1683 } else if (aChr == LF_CHAR) {
1684 rv = SetFieldAndClear();
1685 NS_ENSURE_SUCCESS(rv, rv);
1687 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1688 } else if (aChr == SPACE_CHAR) {
1689 mStatus = PARSE_STATE_FIELD_VALUE;
1690 } else {
1691 mLastFieldValue += aChr;
1692 mStatus = PARSE_STATE_FIELD_VALUE;
1695 break;
1697 case PARSE_STATE_FIELD_VALUE:
1698 if (aChr == CR_CHAR) {
1699 rv = SetFieldAndClear();
1700 NS_ENSURE_SUCCESS(rv, rv);
1702 mStatus = PARSE_STATE_CR_CHAR;
1703 } else if (aChr == LF_CHAR) {
1704 rv = SetFieldAndClear();
1705 NS_ENSURE_SUCCESS(rv, rv);
1707 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1708 } else if (aChr != 0) {
1709 // Avoid appending the null char to the field value.
1710 mLastFieldValue += aChr;
1711 } else if (mLastFieldName.EqualsLiteral("id")) {
1712 // Ignore the whole id field if aChr is null
1713 mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
1714 mLastFieldValue.Truncate();
1717 break;
1719 case PARSE_STATE_IGNORE_FIELD_VALUE:
1720 if (aChr == CR_CHAR) {
1721 mStatus = PARSE_STATE_CR_CHAR;
1722 } else if (aChr == LF_CHAR) {
1723 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1725 break;
1727 case PARSE_STATE_BEGIN_OF_LINE:
1728 if (aChr == CR_CHAR) {
1729 rv = DispatchCurrentMessageEvent(); // there is an empty line
1730 NS_ENSURE_SUCCESS(rv, rv);
1732 mStatus = PARSE_STATE_CR_CHAR;
1733 } else if (aChr == LF_CHAR) {
1734 rv = DispatchCurrentMessageEvent(); // there is an empty line
1735 NS_ENSURE_SUCCESS(rv, rv);
1737 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1738 } else if (aChr == COLON_CHAR) {
1739 mStatus = PARSE_STATE_COMMENT;
1740 } else if (aChr != 0) {
1741 // Avoid appending the null char to the field name.
1742 mLastFieldName += aChr;
1743 mStatus = PARSE_STATE_FIELD_NAME;
1746 break;
1749 return NS_OK;
1752 namespace {
1754 class WorkerRunnableDispatcher final : public WorkerThreadRunnable {
1755 RefPtr<EventSourceImpl> mEventSourceImpl;
1757 public:
1758 WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
1759 WorkerPrivate* aWorkerPrivate,
1760 already_AddRefed<nsIRunnable> aEvent)
1761 : WorkerThreadRunnable("WorkerRunnableDispatcher"),
1762 mEventSourceImpl(std::move(aImpl)),
1763 mEvent(std::move(aEvent)) {}
1765 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1766 aWorkerPrivate->AssertIsOnWorkerThread();
1767 return !NS_FAILED(mEvent->Run());
1770 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
1771 bool aRunResult) override {
1772 // Ensure we drop the RefPtr on the worker thread
1773 // and to not keep us alive longer than needed.
1774 mEventSourceImpl = nullptr;
1777 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
1778 // We don't call WorkerRunnable::PreDispatch because it would assert the
1779 // wrong thing about which thread we're on. We're on whichever thread the
1780 // channel implementation is running on (probably the main thread or
1781 // transport thread).
1782 return true;
1785 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1786 bool aDispatchResult) override {
1787 // We don't call WorkerRunnable::PostDispatch because it would assert the
1788 // wrong thing about which thread we're on. We're on whichever thread the
1789 // channel implementation is running on (probably the main thread or
1790 // transport thread).
1793 private:
1794 nsCOMPtr<nsIRunnable> mEvent;
1797 } // namespace
1799 bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
1800 MOZ_ASSERT(!mWorkerRef);
1801 MOZ_ASSERT(aWorkerPrivate);
1802 aWorkerPrivate->AssertIsOnWorkerThread();
1804 if (mIsShutDown) {
1805 return false;
1808 RefPtr<EventSourceImpl> self = this;
1809 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
1810 aWorkerPrivate, "EventSource", [self]() { self->Close(); });
1812 if (NS_WARN_IF(!workerRef)) {
1813 return false;
1816 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
1817 return true;
1820 void EventSourceImpl::ReleaseWorkerRef() {
1821 MOZ_ASSERT(IsClosed());
1822 MOZ_ASSERT(IsCurrentThreadRunningWorker());
1823 mWorkerRef = nullptr;
1826 //-----------------------------------------------------------------------------
1827 // EventSourceImpl::nsIEventTarget
1828 //-----------------------------------------------------------------------------
1829 NS_IMETHODIMP
1830 EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
1831 nsCOMPtr<nsIRunnable> event(aEvent);
1832 return Dispatch(event.forget(), aFlags);
1835 NS_IMETHODIMP
1836 EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
1837 uint32_t aFlags) {
1838 nsCOMPtr<nsIRunnable> event_ref(aEvent);
1839 if (mIsMainThread) {
1840 return NS_DispatchToMainThread(event_ref.forget());
1843 if (mIsShutDown) {
1844 // We want to avoid clutter about errors in our shutdown logs,
1845 // so just report NS_OK (we have no explicit return value
1846 // for shutdown).
1847 return NS_OK;
1850 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
1851 // runnable.
1852 RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
1853 this, mWorkerRef->Private(), event_ref.forget());
1855 if (!event->Dispatch(mWorkerRef->Private())) {
1856 return NS_ERROR_FAILURE;
1858 return NS_OK;
1861 NS_IMETHODIMP
1862 EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
1863 uint32_t aDelayMs) {
1864 return NS_ERROR_NOT_IMPLEMENTED;
1867 NS_IMETHODIMP
1868 EventSourceImpl::RegisterShutdownTask(nsITargetShutdownTask*) {
1869 return NS_ERROR_NOT_IMPLEMENTED;
1872 NS_IMETHODIMP
1873 EventSourceImpl::UnregisterShutdownTask(nsITargetShutdownTask*) {
1874 return NS_ERROR_NOT_IMPLEMENTED;
1877 //-----------------------------------------------------------------------------
1878 // EventSourceImpl::nsIThreadRetargetableStreamListener
1879 //-----------------------------------------------------------------------------
1880 NS_IMETHODIMP
1881 EventSourceImpl::CheckListenerChain() {
1882 MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
1883 return NS_OK;
1886 NS_IMETHODIMP
1887 EventSourceImpl::OnDataFinished(nsresult) { return NS_OK; }
1889 ////////////////////////////////////////////////////////////////////////////////
1890 // EventSource
1891 ////////////////////////////////////////////////////////////////////////////////
1893 EventSource::EventSource(nsIGlobalObject* aGlobal,
1894 nsICookieJarSettings* aCookieJarSettings,
1895 bool aWithCredentials)
1896 : DOMEventTargetHelper(aGlobal),
1897 mWithCredentials(aWithCredentials),
1898 mIsMainThread(NS_IsMainThread()) {
1899 MOZ_ASSERT(aGlobal);
1900 MOZ_ASSERT(aCookieJarSettings);
1901 mESImpl = new EventSourceImpl(this, aCookieJarSettings);
1904 EventSource::~EventSource() = default;
1906 nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1907 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1908 // it doesn't bubble, and it isn't cancelable
1909 event->InitEvent(aName, false, false);
1910 event->SetTrusted(true);
1911 ErrorResult rv;
1912 DispatchEvent(*event, rv);
1913 return rv.StealNSResult();
1916 /* static */
1917 already_AddRefed<EventSource> EventSource::Constructor(
1918 const GlobalObject& aGlobal, const nsAString& aURL,
1919 const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
1920 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1921 if (NS_WARN_IF(!global)) {
1922 aRv.Throw(NS_ERROR_FAILURE);
1923 return nullptr;
1926 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1927 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
1928 if (ownerWindow) {
1929 Document* doc = ownerWindow->GetExtantDoc();
1930 if (NS_WARN_IF(!doc)) {
1931 aRv.Throw(NS_ERROR_FAILURE);
1932 return nullptr;
1935 cookieJarSettings = doc->CookieJarSettings();
1936 } else {
1937 // Worker side.
1938 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1939 if (!workerPrivate) {
1940 aRv.Throw(NS_ERROR_FAILURE);
1941 return nullptr;
1944 cookieJarSettings = workerPrivate->CookieJarSettings();
1947 RefPtr<EventSource> eventSource = new EventSource(
1948 global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
1950 if (NS_IsMainThread()) {
1951 // Get principal from document and init EventSourceImpl
1952 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1953 do_QueryInterface(aGlobal.GetAsSupports());
1954 if (!scriptPrincipal) {
1955 aRv.Throw(NS_ERROR_FAILURE);
1956 return nullptr;
1958 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
1959 if (!principal) {
1960 aRv.Throw(NS_ERROR_FAILURE);
1961 return nullptr;
1963 eventSource->mESImpl->Init(global, principal, aURL, aRv);
1964 if (NS_WARN_IF(aRv.Failed())) {
1965 return nullptr;
1968 eventSource->mESImpl->InitChannelAndRequestEventSource(true);
1969 return eventSource.forget();
1972 // Worker side.
1974 // Scope for possible failures that need cleanup
1975 auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
1977 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1978 MOZ_ASSERT(workerPrivate);
1980 eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
1982 eventSource->mESImpl->Init(nullptr, workerPrivate->GetPrincipal(), aURL,
1983 aRv);
1984 if (NS_WARN_IF(aRv.Failed())) {
1985 return nullptr;
1988 // In workers we have to keep the worker alive using a WorkerRef in order
1989 // to dispatch messages correctly.
1990 if (!eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
1991 // The worker is already shutting down. Let's return an already closed
1992 // object, but marked as Connecting.
1993 if (eventSource->mESImpl) {
1994 // mESImpl is nulled by this call such that EventSourceImpl is
1995 // released before returning the object, otherwise
1996 // it will set EventSource to a CLOSED state in its DTOR..
1997 eventSource->mESImpl->Close();
1999 eventSource->mReadyState = EventSourceImpl::CONNECTING;
2001 guardESImpl.release();
2002 return eventSource.forget();
2005 // Let's connect to the server.
2006 RefPtr<ConnectRunnable> connectRunnable =
2007 new ConnectRunnable(workerPrivate, eventSource->mESImpl);
2008 connectRunnable->Dispatch(workerPrivate, Canceling, aRv);
2009 if (NS_WARN_IF(aRv.Failed())) {
2010 return nullptr;
2013 // End of scope for possible failures
2014 guardESImpl.release();
2017 return eventSource.forget();
2020 // nsWrapperCache
2021 JSObject* EventSource::WrapObject(JSContext* aCx,
2022 JS::Handle<JSObject*> aGivenProto) {
2023 return EventSource_Binding::Wrap(aCx, this, aGivenProto);
2026 void EventSource::Close() {
2027 AssertIsOnTargetThread();
2028 if (mESImpl) {
2029 // Close potentially kills ourself, ensure
2030 // to not access any members afterwards.
2031 mESImpl->Close();
2035 //-----------------------------------------------------------------------------
2036 // EventSource::nsISupports
2037 //-----------------------------------------------------------------------------
2039 NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
2041 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
2042 DOMEventTargetHelper)
2043 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2045 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
2046 DOMEventTargetHelper)
2047 if (tmp->mESImpl) {
2048 // IsCertainlyaliveForCC will return true and cause the cycle
2049 // collector to skip this instance when mESImpl is non-null and
2050 // points back to ourself.
2051 // mESImpl is initialized to be non-null in the constructor
2052 // and should have been wiped out in our close function.
2053 MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
2054 tmp->Close();
2056 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2058 bool EventSource::IsCertainlyAliveForCC() const {
2059 // Until we are double linked forth and back, we want to stay alive.
2060 if (!mESImpl) {
2061 return false;
2063 auto lock = mESImpl->mSharedData.Lock();
2064 return lock->mEventSource == this;
2067 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
2068 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
2070 NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
2071 NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
2073 } // namespace mozilla::dom