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/. */
6
7 #include "mozilla/dom/EventSource.h"
8
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"
56
57 namespace mozilla::dom {
58
59 static LazyLogModule gEventSourceLog("EventSource");
60
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
65
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)
72
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
91
92 EventSourceImpl(EventSource* aEventSource,
93 nsICookieJarSettings* aCookieJarSettings);
94
95 enum { CONNECTING = 0U, OPEN = 1U, CLOSED = 2U };
96
97 void Close();
98
99 void Init(nsIPrincipal* aPrincipal, const nsAString& aURL, ErrorResult& aRv);
100
101 nsresult GetBaseURI(nsIURI** aBaseURI);
102
103 void SetupHttpChannel();
104 nsresult SetupReferrerInfo(const nsCOMPtr<Document>& aDocument);
105 nsresult InitChannelAndRequestEventSource(bool aEventTargetAccessAllowed);
106 nsresult ResetConnection();
107 void ResetDecoder();
108 nsresult SetReconnectionTimeout();
109
110 void AnnounceConnection();
111 void DispatchAllMessageEvents();
112 nsresult RestartConnection();
113 void ReestablishConnection();
114 void DispatchFailConnection();
115 void FailConnection();
116
117 nsresult Thaw();
118 nsresult Freeze();
119
120 nsresult PrintErrorOnConsole(const char* aBundleURI, const char* aError,
121 const nsTArray<nsString>& aFormatStrings);
122 nsresult ConsoleError();
123
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();
139
140 void CloseInternal();
141 void CleanupOnMainThread();
142
143 bool CreateWorkerRef(WorkerPrivate* aWorkerPrivate);
144 void ReleaseWorkerRef();
145
AssertIsOnTargetThread() const146 void AssertIsOnTargetThread() const {
147 MOZ_DIAGNOSTIC_ASSERT(IsTargetThread());
148 }
149
IsTargetThread() const150 bool IsTargetThread() const { return NS_GetCurrentThread() == mTargetThread; }
151
ReadyState()152 uint16_t ReadyState() {
153 auto lock = mSharedData.Lock();
154 if (lock->mEventSource) {
155 return lock->mEventSource->mReadyState;
156 }
157 // EventSourceImpl keeps EventSource alive. If mEventSource is null, it
158 // means that the EventSource has been closed.
159 return CLOSED;
160 }
161
SetReadyState(uint16_t aReadyState)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;
167 }
168
IsClosed()169 bool IsClosed() { return ReadyState() == CLOSED; }
170
GetEventSource()171 RefPtr<EventSource> GetEventSource() {
172 AssertIsOnTargetThread();
173 auto lock = mSharedData.Lock();
174 return lock->mEventSource;
175 }
176
177 /**
178 * A simple state machine used to manage the event-source's line buffer
179 *
180 * PARSE_STATE_OFF -> PARSE_STATE_BEGIN_OF_STREAM
181 *
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
186 *
187 * PARSE_STATE_CR_CHAR -> PARSE_STATE_CR_CHAR |
188 * PARSE_STATE_COMMENT |
189 * PARSE_STATE_FIELD_NAME |
190 * PARSE_STATE_BEGIN_OF_LINE
191 *
192 * PARSE_STATE_COMMENT -> PARSE_STATE_CR_CHAR |
193 * PARSE_STATE_BEGIN_OF_LINE
194 *
195 * PARSE_STATE_FIELD_NAME -> PARSE_STATE_CR_CHAR |
196 * PARSE_STATE_BEGIN_OF_LINE |
197 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE
198 *
199 * PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE -> PARSE_STATE_FIELD_VALUE |
200 * PARSE_STATE_CR_CHAR |
201 * PARSE_STATE_BEGIN_OF_LINE
202 *
203 * PARSE_STATE_FIELD_VALUE -> PARSE_STATE_CR_CHAR |
204 * PARSE_STATE_BEGIN_OF_LINE
205 *
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
210 *
211 * Whenever the parser find an empty line or the end-of-file
212 * it dispatches the stacked event.
213 *
214 */
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
225 };
226
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;
234
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;
244 };
245
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;
255
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;
268
269 class EventSourceServiceNotifier final {
270 public:
EventSourceServiceNotifier(RefPtr<EventSourceImpl> && aEventSourceImpl,uint64_t aHttpChannelId,uint64_t aInnerWindowID)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();
279 }
280
ConnectionOpened()281 void ConnectionOpened() {
282 mEventSourceImpl->AssertIsOnTargetThread();
283 mService->EventSourceConnectionOpened(mHttpChannelId, mInnerWindowID);
284 mConnectionOpened = true;
285 }
286
EventReceived(const nsAString & aEventName,const nsAString & aLastEventID,const nsAString & aData,uint32_t aRetry,DOMHighResTimeStamp aTimeStamp)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);
293 }
294
~EventSourceServiceNotifier()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);
305 }
306 NS_ReleaseOnMainThread("EventSourceServiceNotifier::mService",
307 mService.forget());
308 }
309
310 private:
311 RefPtr<EventSourceEventService> mService;
312 RefPtr<EventSourceImpl> mEventSourceImpl;
313 uint64_t mHttpChannelId;
314 uint64_t mInnerWindowID;
315 bool mConnectionOpened;
316 };
317
318 struct SharedData {
319 RefPtr<EventSource> mEventSource;
320 UniquePtr<EventSourceServiceNotifier> mServiceNotifier;
321 };
322
323 DataMutex<SharedData> mSharedData;
324
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;
337
338 private:
339 nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
340
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;
347
348 // prevent bad usage
349 EventSourceImpl(const EventSourceImpl& x) = delete;
350 EventSourceImpl& operator=(const EventSourceImpl& x) = delete;
~EventSourceImpl()351 ~EventSourceImpl() {
352 if (IsClosed()) {
353 return;
354 }
355 // If we threw during Init we never called Close
356 SetReadyState(CLOSED);
357 CloseInternal();
358 }
359 };
360
NS_IMPL_ISUPPORTS(EventSourceImpl,nsIObserver,nsIStreamListener,nsIRequestObserver,nsIChannelEventSink,nsIInterfaceRequestor,nsISupportsWeakReference,nsIEventTarget,nsIThreadRetargetableStreamListener,nsITimerCallback)361 NS_IMPL_ISUPPORTS(EventSourceImpl, nsIObserver, nsIStreamListener,
362 nsIRequestObserver, nsIChannelEventSink,
363 nsIInterfaceRequestor, nsISupportsWeakReference,
364 nsIEventTarget, nsIThreadRetargetableStreamListener,
365 nsITimerCallback)
366
367 EventSourceImpl::EventSourceImpl(EventSource* aEventSource,
368 nsICookieJarSettings* aCookieJarSettings)
369 : mReconnectionTime(0),
370 mStatus(PARSE_STATE_OFF),
371 mFrozen(false),
372 mGoingToDispatchAllMessages(false),
373 mIsMainThread(NS_IsMainThread()),
374 mIsShutDown(false),
375 mSharedData(SharedData{aEventSource}, "EventSourceImpl::mSharedData"),
376 mScriptLine(0),
377 mScriptColumn(0),
378 mInnerWindowID(0),
379 mCookieJarSettings(aCookieJarSettings),
380 mTargetThread(NS_GetCurrentThread()) {
381 MOZ_ASSERT(aEventSource);
382 SetReadyState(CONNECTING);
383 }
384
385 class CleanupRunnable final : public WorkerMainThreadRunnable {
386 public:
CleanupRunnable(RefPtr<EventSourceImpl> && aEventSourceImpl)387 explicit CleanupRunnable(RefPtr<EventSourceImpl>&& aEventSourceImpl)
388 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
389 "EventSource :: Cleanup"_ns),
390 mESImpl(std::move(aEventSourceImpl)) {
391 MOZ_ASSERT(mESImpl);
392 mWorkerPrivate->AssertIsOnWorkerThread();
393 }
394
MainThreadRun()395 bool MainThreadRun() override {
396 MOZ_ASSERT(mESImpl);
397 mESImpl->CleanupOnMainThread();
398 // We want to ensure the shortest possible remaining lifetime
399 // and not depend on the Runnable's destruction.
400 mESImpl = nullptr;
401 return true;
402 }
403
404 protected:
405 RefPtr<EventSourceImpl> mESImpl;
406 };
407
Close()408 void EventSourceImpl::Close() {
409 if (IsClosed()) {
410 return;
411 }
412
413 SetReadyState(CLOSED);
414 // CloseInternal potentially kills ourself, ensure
415 // to not access any members afterwards.
416 CloseInternal();
417 }
418
CloseInternal()419 void EventSourceImpl::CloseInternal() {
420 AssertIsOnTargetThread();
421 MOZ_ASSERT(IsClosed());
422
423 RefPtr<EventSource> myES;
424 {
425 auto lock = mSharedData.Lock();
426 // We want to ensure to release ourself even if we have
427 // the shutdown case, thus we put aside a pointer
428 // to the EventSource and null it out right now.
429 myES = std::move(lock->mEventSource);
430 lock->mEventSource = nullptr;
431 lock->mServiceNotifier = nullptr;
432 }
433
434 MOZ_ASSERT(!mIsShutDown);
435 if (mIsShutDown) {
436 return;
437 }
438
439 // Invoke CleanupOnMainThread before cleaning any members. It will call
440 // ShutDown, which is supposed to be called before cleaning any members.
441 if (NS_IsMainThread()) {
442 CleanupOnMainThread();
443 } else {
444 ErrorResult rv;
445 // run CleanupOnMainThread synchronously on main thread since it touches
446 // observers and members only can be accessed on main thread.
447 RefPtr<CleanupRunnable> runnable = new CleanupRunnable(this);
448 runnable->Dispatch(Killing, rv);
449 MOZ_ASSERT(!rv.Failed());
450 ReleaseWorkerRef();
451 }
452
453 while (mMessagesToDispatch.GetSize() != 0) {
454 delete mMessagesToDispatch.PopFront();
455 }
456 mFrozen = false;
457 ResetDecoder();
458 mUnicodeDecoder = nullptr;
459 // Release the object on its owner. Don't access to any members
460 // after it.
461 myES->mESImpl = nullptr;
462 }
463
CleanupOnMainThread()464 void EventSourceImpl::CleanupOnMainThread() {
465 AssertIsOnMainThread();
466 MOZ_ASSERT(IsClosed());
467
468 // Call ShutDown before cleaning any members.
469 MOZ_ASSERT(!mIsShutDown);
470 mIsShutDown = true;
471
472 if (mIsMainThread) {
473 RemoveWindowObservers();
474 }
475
476 if (mTimer) {
477 mTimer->Cancel();
478 mTimer = nullptr;
479 }
480
481 ResetConnection();
482 mPrincipal = nullptr;
483 mSrc = nullptr;
484 }
485
486 class InitRunnable final : public WorkerMainThreadRunnable {
487 public:
InitRunnable(WorkerPrivate * aWorkerPrivate,RefPtr<EventSourceImpl> aEventSourceImpl,const nsAString & aURL)488 InitRunnable(WorkerPrivate* aWorkerPrivate,
489 RefPtr<EventSourceImpl> aEventSourceImpl, const nsAString& aURL)
490 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Init"_ns),
491 mESImpl(std::move(aEventSourceImpl)),
492 mURL(aURL),
493 mRv(NS_ERROR_NOT_INITIALIZED) {
494 MOZ_ASSERT(aWorkerPrivate);
495 aWorkerPrivate->AssertIsOnWorkerThread();
496 MOZ_ASSERT(mESImpl);
497 }
498
MainThreadRun()499 bool MainThreadRun() override {
500 // Get principal from worker's owner document or from worker.
501 WorkerPrivate* wp = mWorkerPrivate;
502 while (wp->GetParent()) {
503 wp = wp->GetParent();
504 }
505 nsPIDOMWindowInner* window = wp->GetWindow();
506 Document* doc = window ? window->GetExtantDoc() : nullptr;
507 nsCOMPtr<nsIPrincipal> principal =
508 doc ? doc->NodePrincipal() : wp->GetPrincipal();
509 if (!principal) {
510 mRv = NS_ERROR_FAILURE;
511 return true;
512 }
513 ErrorResult rv;
514 mESImpl->Init(principal, mURL, rv);
515 mRv = rv.StealNSResult();
516
517 // We want to ensure that EventSourceImpl's lifecycle
518 // does not depend on this Runnable's one.
519 mESImpl = nullptr;
520
521 return true;
522 }
523
ErrorCode() const524 nsresult ErrorCode() const { return mRv; }
525
526 private:
527 RefPtr<EventSourceImpl> mESImpl;
528 const nsAString& mURL;
529 nsresult mRv;
530 };
531
532 class ConnectRunnable final : public WorkerMainThreadRunnable {
533 public:
ConnectRunnable(WorkerPrivate * aWorkerPrivate,RefPtr<EventSourceImpl> aEventSourceImpl)534 explicit ConnectRunnable(WorkerPrivate* aWorkerPrivate,
535 RefPtr<EventSourceImpl> aEventSourceImpl)
536 : WorkerMainThreadRunnable(aWorkerPrivate, "EventSource :: Connect"_ns),
537 mESImpl(std::move(aEventSourceImpl)) {
538 MOZ_ASSERT(aWorkerPrivate);
539 aWorkerPrivate->AssertIsOnWorkerThread();
540 MOZ_ASSERT(mESImpl);
541 }
542
MainThreadRun()543 bool MainThreadRun() override {
544 MOZ_ASSERT(mESImpl);
545 // We are allowed to access the event target since this runnable is
546 // synchronized with the thread the event target lives on.
547 mESImpl->InitChannelAndRequestEventSource(true);
548 // We want to ensure the shortest possible remaining lifetime
549 // and not depend on the Runnable's destruction.
550 mESImpl = nullptr;
551 return true;
552 }
553
554 private:
555 RefPtr<EventSourceImpl> mESImpl;
556 };
557
ParseURL(const nsAString & aURL)558 nsresult EventSourceImpl::ParseURL(const nsAString& aURL) {
559 AssertIsOnMainThread();
560 MOZ_ASSERT(!mIsShutDown);
561 // get the src
562 nsCOMPtr<nsIURI> baseURI;
563 nsresult rv = GetBaseURI(getter_AddRefs(baseURI));
564 NS_ENSURE_SUCCESS(rv, rv);
565
566 nsCOMPtr<nsIURI> srcURI;
567 rv = NS_NewURI(getter_AddRefs(srcURI), aURL, nullptr, baseURI);
568 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);
569
570 nsAutoString origin;
571 rv = nsContentUtils::GetUTFOrigin(srcURI, origin);
572 NS_ENSURE_SUCCESS(rv, rv);
573
574 nsAutoCString spec;
575 rv = srcURI->GetSpec(spec);
576 NS_ENSURE_SUCCESS(rv, rv);
577
578 // This assignment doesn't require extra synchronization because this function
579 // is only ever called from EventSourceImpl::Init(), which is either called
580 // directly if mEventSource was created on the main thread, or via a
581 // synchronous runnable if it was created on a worker thread.
582 {
583 // We can't use GetEventSource() here because it would modify the refcount,
584 // and that's not allowed off the owning thread.
585 auto lock = mSharedData.Lock();
586 lock->mEventSource->mOriginalURL = NS_ConvertUTF8toUTF16(spec);
587 }
588 mSrc = srcURI;
589 mOrigin = origin;
590 return NS_OK;
591 }
592
AddWindowObservers()593 nsresult EventSourceImpl::AddWindowObservers() {
594 AssertIsOnMainThread();
595 MOZ_ASSERT(mIsMainThread);
596 MOZ_ASSERT(!mIsShutDown);
597 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
598 NS_ENSURE_STATE(os);
599
600 nsresult rv = os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
601 NS_ENSURE_SUCCESS(rv, rv);
602 rv = os->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
603 NS_ENSURE_SUCCESS(rv, rv);
604 rv = os->AddObserver(this, DOM_WINDOW_THAWED_TOPIC, true);
605 NS_ENSURE_SUCCESS(rv, rv);
606 return NS_OK;
607 }
608
RemoveWindowObservers()609 void EventSourceImpl::RemoveWindowObservers() {
610 AssertIsOnMainThread();
611 MOZ_ASSERT(mIsMainThread);
612 MOZ_ASSERT(IsClosed());
613 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
614 if (os) {
615 os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
616 os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
617 os->RemoveObserver(this, DOM_WINDOW_THAWED_TOPIC);
618 }
619 }
620
Init(nsIPrincipal * aPrincipal,const nsAString & aURL,ErrorResult & aRv)621 void EventSourceImpl::Init(nsIPrincipal* aPrincipal, const nsAString& aURL,
622 ErrorResult& aRv) {
623 AssertIsOnMainThread();
624 MOZ_ASSERT(aPrincipal);
625 MOZ_ASSERT(ReadyState() == CONNECTING);
626 mPrincipal = aPrincipal;
627 aRv = ParseURL(aURL);
628 if (NS_WARN_IF(aRv.Failed())) {
629 return;
630 }
631 // The conditional here is historical and not necessarily sane.
632 if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) {
633 nsJSUtils::GetCallingLocation(cx, mScriptFile, &mScriptLine,
634 &mScriptColumn);
635 mInnerWindowID = nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx);
636 }
637
638 if (mIsMainThread) {
639 // we observe when the window freezes and thaws
640 aRv = AddWindowObservers();
641 if (NS_WARN_IF(aRv.Failed())) {
642 return;
643 }
644 }
645
646 mReconnectionTime =
647 Preferences::GetInt("dom.server-events.default-reconnection-time",
648 DEFAULT_RECONNECTION_TIME_VALUE);
649
650 mUnicodeDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
651 }
652
653 //-----------------------------------------------------------------------------
654 // EventSourceImpl::nsIObserver
655 //-----------------------------------------------------------------------------
656
657 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)658 EventSourceImpl::Observe(nsISupports* aSubject, const char* aTopic,
659 const char16_t* aData) {
660 AssertIsOnMainThread();
661 if (IsClosed()) {
662 return NS_OK;
663 }
664
665 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aSubject);
666 MOZ_ASSERT(mIsMainThread);
667 {
668 auto lock = mSharedData.Lock();
669 if (!lock->mEventSource->GetOwner() ||
670 window != lock->mEventSource->GetOwner()) {
671 return NS_OK;
672 }
673 }
674
675 DebugOnly<nsresult> rv;
676 if (strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) {
677 rv = Freeze();
678 MOZ_ASSERT(NS_SUCCEEDED(rv), "Freeze() failed");
679 } else if (strcmp(aTopic, DOM_WINDOW_THAWED_TOPIC) == 0) {
680 rv = Thaw();
681 MOZ_ASSERT(NS_SUCCEEDED(rv), "Thaw() failed");
682 } else if (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0) {
683 Close();
684 }
685
686 return NS_OK;
687 }
688
689 //-----------------------------------------------------------------------------
690 // EventSourceImpl::nsIStreamListener
691 //-----------------------------------------------------------------------------
692
693 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)694 EventSourceImpl::OnStartRequest(nsIRequest* aRequest) {
695 AssertIsOnMainThread();
696 if (IsClosed()) {
697 return NS_ERROR_ABORT;
698 }
699 nsresult rv = CheckHealthOfRequestCallback(aRequest);
700 NS_ENSURE_SUCCESS(rv, rv);
701
702 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
703 NS_ENSURE_SUCCESS(rv, rv);
704
705 nsresult status;
706 rv = aRequest->GetStatus(&status);
707 NS_ENSURE_SUCCESS(rv, rv);
708
709 if (NS_FAILED(status)) {
710 // EventSource::OnStopRequest will evaluate if it shall either reestablish
711 // or fail the connection
712 return NS_ERROR_ABORT;
713 }
714
715 uint32_t httpStatus;
716 rv = httpChannel->GetResponseStatus(&httpStatus);
717 NS_ENSURE_SUCCESS(rv, rv);
718
719 if (httpStatus != 200) {
720 DispatchFailConnection();
721 return NS_ERROR_ABORT;
722 }
723
724 nsAutoCString contentType;
725 rv = httpChannel->GetContentType(contentType);
726 NS_ENSURE_SUCCESS(rv, rv);
727
728 if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
729 DispatchFailConnection();
730 return NS_ERROR_ABORT;
731 }
732
733 if (!mIsMainThread) {
734 // Try to retarget to worker thread, otherwise fall back to main thread.
735 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(httpChannel);
736 if (rr) {
737 rv = rr->RetargetDeliveryTo(this);
738 if (NS_WARN_IF(NS_FAILED(rv))) {
739 NS_WARNING("Retargeting failed");
740 }
741 }
742 }
743
744 {
745 auto lock = mSharedData.Lock();
746 lock->mServiceNotifier = MakeUnique<EventSourceServiceNotifier>(
747 this, mHttpChannel->ChannelId(), mInnerWindowID);
748 }
749 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::AnnounceConnection",
750 this, &EventSourceImpl::AnnounceConnection),
751 NS_DISPATCH_NORMAL);
752 NS_ENSURE_SUCCESS(rv, rv);
753 mStatus = PARSE_STATE_BEGIN_OF_STREAM;
754 return NS_OK;
755 }
756
757 // this method parses the characters as they become available instead of
758 // buffering them.
StreamReaderFunc(nsIInputStream * aInputStream,void * aClosure,const char * aFromRawSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * aWriteCount)759 nsresult EventSourceImpl::StreamReaderFunc(nsIInputStream* aInputStream,
760 void* aClosure,
761 const char* aFromRawSegment,
762 uint32_t aToOffset, uint32_t aCount,
763 uint32_t* aWriteCount) {
764 // The EventSourceImpl instance is hold alive on the
765 // synchronously calling stack, so raw pointer is fine here.
766 EventSourceImpl* thisObject = static_cast<EventSourceImpl*>(aClosure);
767 if (!thisObject || !aWriteCount) {
768 NS_WARNING(
769 "EventSource cannot read from stream: no aClosure or aWriteCount");
770 return NS_ERROR_FAILURE;
771 }
772 thisObject->AssertIsOnTargetThread();
773 MOZ_ASSERT(!thisObject->mIsShutDown);
774 thisObject->ParseSegment((const char*)aFromRawSegment, aCount);
775 *aWriteCount = aCount;
776 return NS_OK;
777 }
778
ParseSegment(const char * aBuffer,uint32_t aLength)779 void EventSourceImpl::ParseSegment(const char* aBuffer, uint32_t aLength) {
780 AssertIsOnTargetThread();
781 if (IsClosed()) {
782 return;
783 }
784 char16_t buffer[1024];
785 auto dst = Span(buffer);
786 auto src = AsBytes(Span(aBuffer, aLength));
787 // XXX EOF handling is https://bugzilla.mozilla.org/show_bug.cgi?id=1369018
788 for (;;) {
789 uint32_t result;
790 size_t read;
791 size_t written;
792 bool hadErrors;
793 Tie(result, read, written, hadErrors) =
794 mUnicodeDecoder->DecodeToUTF16(src, dst, false);
795 Unused << hadErrors;
796 for (auto c : dst.To(written)) {
797 nsresult rv = ParseCharacter(c);
798 NS_ENSURE_SUCCESS_VOID(rv);
799 }
800 if (result == kInputEmpty) {
801 return;
802 }
803 src = src.From(read);
804 }
805 }
806
807 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)808 EventSourceImpl::OnDataAvailable(nsIRequest* aRequest,
809 nsIInputStream* aInputStream, uint64_t aOffset,
810 uint32_t aCount) {
811 AssertIsOnTargetThread();
812 NS_ENSURE_ARG_POINTER(aInputStream);
813 if (IsClosed()) {
814 return NS_ERROR_ABORT;
815 }
816
817 nsresult rv = CheckHealthOfRequestCallback(aRequest);
818 NS_ENSURE_SUCCESS(rv, rv);
819
820 uint32_t totalRead;
821 return aInputStream->ReadSegments(EventSourceImpl::StreamReaderFunc, this,
822 aCount, &totalRead);
823 }
824
825 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)826 EventSourceImpl::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
827 AssertIsOnMainThread();
828
829 if (IsClosed()) {
830 return NS_ERROR_ABORT;
831 }
832 MOZ_ASSERT(mSrc);
833 // "Network errors that prevents the connection from being established in the
834 // first place (e.g. DNS errors), must cause the user agent to asynchronously
835 // reestablish the connection.
836 //
837 // (...) the cancelation of the fetch algorithm by the user agent (e.g. in
838 // response to window.stop() or the user canceling the network connection
839 // manually) must cause the user agent to fail the connection.
840
841 if (NS_FAILED(aStatusCode) && aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
842 aStatusCode != NS_ERROR_NET_TIMEOUT &&
843 aStatusCode != NS_ERROR_NET_RESET &&
844 aStatusCode != NS_ERROR_NET_INTERRUPT &&
845 aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
846 aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
847 DispatchFailConnection();
848 return NS_ERROR_ABORT;
849 }
850
851 nsresult rv = CheckHealthOfRequestCallback(aRequest);
852 NS_ENSURE_SUCCESS(rv, rv);
853
854 rv =
855 Dispatch(NewRunnableMethod("dom::EventSourceImpl::ReestablishConnection",
856 this, &EventSourceImpl::ReestablishConnection),
857 NS_DISPATCH_NORMAL);
858 NS_ENSURE_SUCCESS(rv, rv);
859
860 return NS_OK;
861 }
862
863 //-----------------------------------------------------------------------------
864 // EventSourceImpl::nsIChannelEventSink
865 //-----------------------------------------------------------------------------
866
867 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * aCallback)868 EventSourceImpl::AsyncOnChannelRedirect(
869 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
870 nsIAsyncVerifyRedirectCallback* aCallback) {
871 AssertIsOnMainThread();
872 if (IsClosed()) {
873 return NS_ERROR_ABORT;
874 }
875 nsCOMPtr<nsIRequest> aOldRequest = aOldChannel;
876 MOZ_ASSERT(aOldRequest, "Redirect from a null request?");
877
878 nsresult rv = CheckHealthOfRequestCallback(aOldRequest);
879 NS_ENSURE_SUCCESS(rv, rv);
880
881 MOZ_ASSERT(aNewChannel, "Redirect without a channel?");
882
883 nsCOMPtr<nsIURI> newURI;
884 rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
885 NS_ENSURE_SUCCESS(rv, rv);
886
887 bool isValidScheme = newURI->SchemeIs("http") || newURI->SchemeIs("https");
888
889 rv =
890 mIsMainThread ? GetEventSource()->CheckCurrentGlobalCorrectness() : NS_OK;
891 if (NS_FAILED(rv) || !isValidScheme) {
892 DispatchFailConnection();
893 return NS_ERROR_DOM_SECURITY_ERR;
894 }
895
896 // update our channel
897
898 mHttpChannel = do_QueryInterface(aNewChannel);
899 NS_ENSURE_STATE(mHttpChannel);
900
901 SetupHttpChannel();
902 // The HTTP impl already copies over the referrer info on
903 // redirects, so we don't need to SetupReferrerInfo().
904
905 if ((aFlags & nsIChannelEventSink::REDIRECT_PERMANENT) != 0) {
906 rv = NS_GetFinalChannelURI(mHttpChannel, getter_AddRefs(mSrc));
907 NS_ENSURE_SUCCESS(rv, rv);
908 }
909
910 aCallback->OnRedirectVerifyCallback(NS_OK);
911
912 return NS_OK;
913 }
914
915 //-----------------------------------------------------------------------------
916 // EventSourceImpl::nsIInterfaceRequestor
917 //-----------------------------------------------------------------------------
918
919 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)920 EventSourceImpl::GetInterface(const nsIID& aIID, void** aResult) {
921 AssertIsOnMainThread();
922
923 if (IsClosed()) {
924 return NS_ERROR_FAILURE;
925 }
926
927 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
928 *aResult = static_cast<nsIChannelEventSink*>(this);
929 NS_ADDREF_THIS();
930 return NS_OK;
931 }
932
933 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
934 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
935 nsresult rv;
936 nsCOMPtr<nsIPromptFactory> wwatch =
937 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
938 NS_ENSURE_SUCCESS(rv, rv);
939
940 nsCOMPtr<nsPIDOMWindowOuter> window;
941
942 // To avoid a data race we may only access the event target if it lives on
943 // the main thread.
944 if (mIsMainThread) {
945 auto lock = mSharedData.Lock();
946 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
947 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
948
949 if (lock->mEventSource->GetOwner()) {
950 window = lock->mEventSource->GetOwner()->GetOuterWindow();
951 }
952 }
953
954 // Get the an auth prompter for our window so that the parenting
955 // of the dialogs works as it should when using tabs.
956
957 return wwatch->GetPrompt(window, aIID, aResult);
958 }
959
960 return QueryInterface(aIID, aResult);
961 }
962
963 NS_IMETHODIMP
IsOnCurrentThread(bool * aResult)964 EventSourceImpl::IsOnCurrentThread(bool* aResult) {
965 *aResult = IsTargetThread();
966 return NS_OK;
967 }
968
NS_IMETHODIMP_(bool)969 NS_IMETHODIMP_(bool)
970 EventSourceImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); }
971
GetBaseURI(nsIURI ** aBaseURI)972 nsresult EventSourceImpl::GetBaseURI(nsIURI** aBaseURI) {
973 AssertIsOnMainThread();
974 MOZ_ASSERT(!mIsShutDown);
975 NS_ENSURE_ARG_POINTER(aBaseURI);
976
977 *aBaseURI = nullptr;
978
979 nsCOMPtr<nsIURI> baseURI;
980
981 // first we try from document->GetBaseURI()
982 nsCOMPtr<Document> doc =
983 mIsMainThread ? GetEventSource()->GetDocumentIfCurrent() : nullptr;
984 if (doc) {
985 baseURI = doc->GetBaseURI();
986 }
987
988 // otherwise we get from the doc's principal
989 if (!baseURI) {
990 auto* basePrin = BasePrincipal::Cast(mPrincipal);
991 nsresult rv = basePrin->GetURI(getter_AddRefs(baseURI));
992 NS_ENSURE_SUCCESS(rv, rv);
993 }
994
995 NS_ENSURE_STATE(baseURI);
996
997 baseURI.forget(aBaseURI);
998 return NS_OK;
999 }
1000
SetupHttpChannel()1001 void EventSourceImpl::SetupHttpChannel() {
1002 AssertIsOnMainThread();
1003 MOZ_ASSERT(!mIsShutDown);
1004 nsresult rv = mHttpChannel->SetRequestMethod("GET"_ns);
1005 MOZ_ASSERT(NS_SUCCEEDED(rv));
1006
1007 /* set the http request headers */
1008
1009 rv = mHttpChannel->SetRequestHeader(
1010 "Accept"_ns, nsLiteralCString(TEXT_EVENT_STREAM), false);
1011 MOZ_ASSERT(NS_SUCCEEDED(rv));
1012
1013 // LOAD_BYPASS_CACHE already adds the Cache-Control: no-cache header
1014
1015 if (mLastEventID.IsEmpty()) {
1016 return;
1017 }
1018 NS_ConvertUTF16toUTF8 eventId(mLastEventID);
1019 rv = mHttpChannel->SetRequestHeader("Last-Event-ID"_ns, eventId, false);
1020 #ifdef DEBUG
1021 if (NS_FAILED(rv)) {
1022 MOZ_LOG(gEventSourceLog, LogLevel::Warning,
1023 ("SetupHttpChannel. rv=%x (%s)", uint32_t(rv), eventId.get()));
1024 }
1025 #endif
1026 Unused << rv;
1027 }
1028
SetupReferrerInfo(const nsCOMPtr<Document> & aDocument)1029 nsresult EventSourceImpl::SetupReferrerInfo(
1030 const nsCOMPtr<Document>& aDocument) {
1031 AssertIsOnMainThread();
1032 MOZ_ASSERT(!mIsShutDown);
1033
1034 if (aDocument) {
1035 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*aDocument);
1036 nsresult rv = mHttpChannel->SetReferrerInfoWithoutClone(referrerInfo);
1037 NS_ENSURE_SUCCESS(rv, rv);
1038 }
1039
1040 return NS_OK;
1041 }
1042
InitChannelAndRequestEventSource(const bool aEventTargetAccessAllowed)1043 nsresult EventSourceImpl::InitChannelAndRequestEventSource(
1044 const bool aEventTargetAccessAllowed) {
1045 AssertIsOnMainThread();
1046 if (IsClosed()) {
1047 return NS_ERROR_ABORT;
1048 }
1049
1050 bool isValidScheme = mSrc->SchemeIs("http") || mSrc->SchemeIs("https");
1051
1052 MOZ_ASSERT_IF(mIsMainThread, aEventTargetAccessAllowed);
1053
1054 nsresult rv = aEventTargetAccessAllowed
1055 ? [this]() {
1056 // We can't call GetEventSource() because we're not
1057 // allowed to touch the refcount off the worker thread
1058 // due to an assertion, event if it would have otherwise
1059 // been safe.
1060 auto lock = mSharedData.Lock();
1061 return lock->mEventSource->CheckCurrentGlobalCorrectness();
1062 }()
1063 : NS_OK;
1064 if (NS_FAILED(rv) || !isValidScheme) {
1065 DispatchFailConnection();
1066 return NS_ERROR_DOM_SECURITY_ERR;
1067 }
1068
1069 nsCOMPtr<Document> doc;
1070 nsSecurityFlags securityFlags =
1071 nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
1072 {
1073 auto lock = mSharedData.Lock();
1074 doc = aEventTargetAccessAllowed ? lock->mEventSource->GetDocumentIfCurrent()
1075 : nullptr;
1076
1077 if (lock->mEventSource->mWithCredentials) {
1078 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1079 }
1080 }
1081
1082 // The html spec requires we use fetch cache mode of "no-store". This
1083 // maps to LOAD_BYPASS_CACHE and LOAD_INHIBIT_CACHING in necko.
1084 nsLoadFlags loadFlags;
1085 loadFlags = nsIRequest::LOAD_BACKGROUND | nsIRequest::LOAD_BYPASS_CACHE |
1086 nsIRequest::INHIBIT_CACHING;
1087
1088 nsCOMPtr<nsIChannel> channel;
1089 // If we have the document, use it
1090 if (doc) {
1091 MOZ_ASSERT(mCookieJarSettings == doc->CookieJarSettings());
1092
1093 nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
1094 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, doc, securityFlags,
1095 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1096 nullptr, // aPerformanceStorage
1097 loadGroup,
1098 nullptr, // aCallbacks
1099 loadFlags); // aLoadFlags
1100 } else {
1101 // otherwise use the principal
1102 rv = NS_NewChannel(getter_AddRefs(channel), mSrc, mPrincipal, securityFlags,
1103 nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
1104 mCookieJarSettings,
1105 nullptr, // aPerformanceStorage
1106 nullptr, // loadGroup
1107 nullptr, // aCallbacks
1108 loadFlags); // aLoadFlags
1109 }
1110
1111 NS_ENSURE_SUCCESS(rv, rv);
1112
1113 mHttpChannel = do_QueryInterface(channel);
1114 NS_ENSURE_TRUE(mHttpChannel, NS_ERROR_NO_INTERFACE);
1115
1116 SetupHttpChannel();
1117 rv = SetupReferrerInfo(doc);
1118 NS_ENSURE_SUCCESS(rv, rv);
1119
1120 #ifdef DEBUG
1121 {
1122 nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
1123 mHttpChannel->GetNotificationCallbacks(
1124 getter_AddRefs(notificationCallbacks));
1125 MOZ_ASSERT(!notificationCallbacks);
1126 }
1127 #endif
1128
1129 mHttpChannel->SetNotificationCallbacks(this);
1130
1131 // Start reading from the channel
1132 rv = mHttpChannel->AsyncOpen(this);
1133 if (NS_FAILED(rv)) {
1134 DispatchFailConnection();
1135 return rv;
1136 }
1137
1138 return rv;
1139 }
1140
AnnounceConnection()1141 void EventSourceImpl::AnnounceConnection() {
1142 AssertIsOnTargetThread();
1143 if (ReadyState() != CONNECTING) {
1144 NS_WARNING("Unexpected mReadyState!!!");
1145 return;
1146 }
1147
1148 {
1149 auto lock = mSharedData.Lock();
1150 if (lock->mServiceNotifier) {
1151 lock->mServiceNotifier->ConnectionOpened();
1152 }
1153 }
1154
1155 // When a user agent is to announce the connection, the user agent must set
1156 // the readyState attribute to OPEN and queue a task to fire a simple event
1157 // named open at the EventSource object.
1158
1159 SetReadyState(OPEN);
1160
1161 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1162 if (NS_FAILED(rv)) {
1163 return;
1164 }
1165 // We can't hold the mutex while dispatching the event because the mutex is
1166 // not reentrant, and content might call back into our code.
1167 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"open"_ns);
1168 if (NS_FAILED(rv)) {
1169 NS_WARNING("Failed to dispatch the error event!!!");
1170 return;
1171 }
1172 }
1173
ResetConnection()1174 nsresult EventSourceImpl::ResetConnection() {
1175 AssertIsOnMainThread();
1176 if (mHttpChannel) {
1177 mHttpChannel->Cancel(NS_ERROR_ABORT);
1178 mHttpChannel = nullptr;
1179 }
1180 return NS_OK;
1181 }
1182
ResetDecoder()1183 void EventSourceImpl::ResetDecoder() {
1184 AssertIsOnTargetThread();
1185 if (mUnicodeDecoder) {
1186 UTF_8_ENCODING->NewDecoderWithBOMRemovalInto(*mUnicodeDecoder);
1187 }
1188 mStatus = PARSE_STATE_OFF;
1189 ClearFields();
1190 }
1191
1192 class CallRestartConnection final : public WorkerMainThreadRunnable {
1193 public:
CallRestartConnection(RefPtr<EventSourceImpl> && aEventSourceImpl)1194 explicit CallRestartConnection(RefPtr<EventSourceImpl>&& aEventSourceImpl)
1195 : WorkerMainThreadRunnable(aEventSourceImpl->mWorkerRef->Private(),
1196 "EventSource :: RestartConnection"_ns),
1197 mESImpl(std::move(aEventSourceImpl)) {
1198 mWorkerPrivate->AssertIsOnWorkerThread();
1199 MOZ_ASSERT(mESImpl);
1200 }
1201
MainThreadRun()1202 bool MainThreadRun() override {
1203 MOZ_ASSERT(mESImpl);
1204 mESImpl->RestartConnection();
1205 // We want to ensure the shortest possible remaining lifetime
1206 // and not depend on the Runnable's destruction.
1207 mESImpl = nullptr;
1208 return true;
1209 }
1210
1211 protected:
1212 RefPtr<EventSourceImpl> mESImpl;
1213 };
1214
RestartConnection()1215 nsresult EventSourceImpl::RestartConnection() {
1216 AssertIsOnMainThread();
1217 if (IsClosed()) {
1218 return NS_ERROR_ABORT;
1219 }
1220
1221 nsresult rv = ResetConnection();
1222 NS_ENSURE_SUCCESS(rv, rv);
1223 rv = SetReconnectionTimeout();
1224 NS_ENSURE_SUCCESS(rv, rv);
1225 return NS_OK;
1226 }
1227
ReestablishConnection()1228 void EventSourceImpl::ReestablishConnection() {
1229 AssertIsOnTargetThread();
1230 if (IsClosed()) {
1231 return;
1232 }
1233
1234 nsresult rv;
1235 if (mIsMainThread) {
1236 rv = RestartConnection();
1237 } else {
1238 RefPtr<CallRestartConnection> runnable = new CallRestartConnection(this);
1239 ErrorResult result;
1240 runnable->Dispatch(Canceling, result);
1241 MOZ_ASSERT(!result.Failed());
1242 rv = result.StealNSResult();
1243 }
1244 if (NS_FAILED(rv)) {
1245 return;
1246 }
1247
1248 rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1249 if (NS_FAILED(rv)) {
1250 return;
1251 }
1252
1253 SetReadyState(CONNECTING);
1254 ResetDecoder();
1255 // We can't hold the mutex while dispatching the event because the mutex is
1256 // not reentrant, and content might call back into our code.
1257 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1258 if (NS_FAILED(rv)) {
1259 NS_WARNING("Failed to dispatch the error event!!!");
1260 return;
1261 }
1262 }
1263
SetReconnectionTimeout()1264 nsresult EventSourceImpl::SetReconnectionTimeout() {
1265 AssertIsOnMainThread();
1266 if (IsClosed()) {
1267 return NS_ERROR_ABORT;
1268 }
1269
1270 // the timer will be used whenever the requests are going finished.
1271 if (!mTimer) {
1272 mTimer = NS_NewTimer();
1273 NS_ENSURE_STATE(mTimer);
1274 }
1275
1276 MOZ_TRY(mTimer->InitWithCallback(this, mReconnectionTime,
1277 nsITimer::TYPE_ONE_SHOT));
1278
1279 return NS_OK;
1280 }
1281
PrintErrorOnConsole(const char * aBundleURI,const char * aError,const nsTArray<nsString> & aFormatStrings)1282 nsresult EventSourceImpl::PrintErrorOnConsole(
1283 const char* aBundleURI, const char* aError,
1284 const nsTArray<nsString>& aFormatStrings) {
1285 AssertIsOnMainThread();
1286 MOZ_ASSERT(!mIsShutDown);
1287 nsCOMPtr<nsIStringBundleService> bundleService =
1288 mozilla::components::StringBundle::Service();
1289 NS_ENSURE_STATE(bundleService);
1290
1291 nsCOMPtr<nsIStringBundle> strBundle;
1292 nsresult rv =
1293 bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle));
1294 NS_ENSURE_SUCCESS(rv, rv);
1295
1296 nsCOMPtr<nsIConsoleService> console(
1297 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv));
1298 NS_ENSURE_SUCCESS(rv, rv);
1299
1300 nsCOMPtr<nsIScriptError> errObj(
1301 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv));
1302 NS_ENSURE_SUCCESS(rv, rv);
1303
1304 // Localize the error message
1305 nsAutoString message;
1306 if (!aFormatStrings.IsEmpty()) {
1307 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message);
1308 } else {
1309 rv = strBundle->GetStringFromName(aError, message);
1310 }
1311 NS_ENSURE_SUCCESS(rv, rv);
1312
1313 rv = errObj->InitWithWindowID(message, mScriptFile, u""_ns, mScriptLine,
1314 mScriptColumn, nsIScriptError::errorFlag,
1315 "Event Source", mInnerWindowID);
1316 NS_ENSURE_SUCCESS(rv, rv);
1317
1318 // print the error message directly to the JS console
1319 rv = console->LogMessage(errObj);
1320 NS_ENSURE_SUCCESS(rv, rv);
1321
1322 return NS_OK;
1323 }
1324
ConsoleError()1325 nsresult EventSourceImpl::ConsoleError() {
1326 AssertIsOnMainThread();
1327 MOZ_ASSERT(!mIsShutDown);
1328 nsAutoCString targetSpec;
1329 nsresult rv = mSrc->GetSpec(targetSpec);
1330 NS_ENSURE_SUCCESS(rv, rv);
1331
1332 AutoTArray<nsString, 1> formatStrings;
1333 CopyUTF8toUTF16(targetSpec, *formatStrings.AppendElement());
1334
1335 if (ReadyState() == CONNECTING) {
1336 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1337 "connectionFailure", formatStrings);
1338 } else {
1339 rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
1340 "netInterrupt", formatStrings);
1341 }
1342 NS_ENSURE_SUCCESS(rv, rv);
1343
1344 return NS_OK;
1345 }
1346
DispatchFailConnection()1347 void EventSourceImpl::DispatchFailConnection() {
1348 AssertIsOnMainThread();
1349 if (IsClosed()) {
1350 return;
1351 }
1352 nsresult rv = ConsoleError();
1353 if (NS_FAILED(rv)) {
1354 NS_WARNING("Failed to print to the console error");
1355 }
1356 rv = Dispatch(NewRunnableMethod("dom::EventSourceImpl::FailConnection", this,
1357 &EventSourceImpl::FailConnection),
1358 NS_DISPATCH_NORMAL);
1359 if (NS_WARN_IF(NS_FAILED(rv))) {
1360 // if the worker is shutting down, the dispatching of normal WorkerRunnables
1361 // fails.
1362 return;
1363 }
1364 }
1365
FailConnection()1366 void EventSourceImpl::FailConnection() {
1367 AssertIsOnTargetThread();
1368 if (IsClosed()) {
1369 return;
1370 }
1371 // Must change state to closed before firing event to content.
1372 SetReadyState(CLOSED);
1373 // When a user agent is to fail the connection, the user agent must set the
1374 // readyState attribute to CLOSED and queue a task to fire a simple event
1375 // named error at the EventSource object.
1376 nsresult rv = GetEventSource()->CheckCurrentGlobalCorrectness();
1377 if (NS_SUCCEEDED(rv)) {
1378 // We can't hold the mutex while dispatching the event because the mutex
1379 // is not reentrant, and content might call back into our code.
1380 rv = GetEventSource()->CreateAndDispatchSimpleEvent(u"error"_ns);
1381 if (NS_FAILED(rv)) {
1382 NS_WARNING("Failed to dispatch the error event!!!");
1383 }
1384 }
1385 // Call CloseInternal in the end of function because it may release
1386 // EventSourceImpl.
1387 CloseInternal();
1388 }
1389
Notify(nsITimer * aTimer)1390 NS_IMETHODIMP EventSourceImpl::Notify(nsITimer* aTimer) {
1391 AssertIsOnMainThread();
1392 if (IsClosed()) {
1393 return NS_OK;
1394 }
1395
1396 MOZ_ASSERT(!mHttpChannel, "the channel hasn't been cancelled!!");
1397
1398 if (!mFrozen) {
1399 nsresult rv = InitChannelAndRequestEventSource(mIsMainThread);
1400 if (NS_FAILED(rv)) {
1401 NS_WARNING("InitChannelAndRequestEventSource() failed");
1402 }
1403 }
1404 return NS_OK;
1405 }
1406
Thaw()1407 nsresult EventSourceImpl::Thaw() {
1408 AssertIsOnMainThread();
1409 if (IsClosed() || !mFrozen) {
1410 return NS_OK;
1411 }
1412
1413 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1414
1415 mFrozen = false;
1416 nsresult rv;
1417 if (!mGoingToDispatchAllMessages && mMessagesToDispatch.GetSize() > 0) {
1418 nsCOMPtr<nsIRunnable> event =
1419 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1420 this, &EventSourceImpl::DispatchAllMessageEvents);
1421 NS_ENSURE_STATE(event);
1422
1423 mGoingToDispatchAllMessages = true;
1424
1425 rv = Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1426 NS_ENSURE_SUCCESS(rv, rv);
1427 }
1428
1429 rv = InitChannelAndRequestEventSource(mIsMainThread);
1430 NS_ENSURE_SUCCESS(rv, rv);
1431
1432 return NS_OK;
1433 }
1434
Freeze()1435 nsresult EventSourceImpl::Freeze() {
1436 AssertIsOnMainThread();
1437 if (IsClosed() || mFrozen) {
1438 return NS_OK;
1439 }
1440
1441 MOZ_ASSERT(!mHttpChannel, "the connection hasn't been closed!!!");
1442 mFrozen = true;
1443 return NS_OK;
1444 }
1445
DispatchCurrentMessageEvent()1446 nsresult EventSourceImpl::DispatchCurrentMessageEvent() {
1447 AssertIsOnTargetThread();
1448 MOZ_ASSERT(!mIsShutDown);
1449 UniquePtr<Message> message(std::move(mCurrentMessage));
1450 ClearFields();
1451
1452 if (!message || message->mData.IsEmpty()) {
1453 return NS_OK;
1454 }
1455
1456 // removes the trailing LF from mData
1457 MOZ_ASSERT(message->mData.CharAt(message->mData.Length() - 1) == LF_CHAR,
1458 "Invalid trailing character! LF was expected instead.");
1459 message->mData.SetLength(message->mData.Length() - 1);
1460
1461 if (message->mEventName.IsEmpty()) {
1462 message->mEventName.AssignLiteral("message");
1463 }
1464
1465 mMessagesToDispatch.Push(message.release());
1466
1467 if (!mGoingToDispatchAllMessages) {
1468 nsCOMPtr<nsIRunnable> event =
1469 NewRunnableMethod("dom::EventSourceImpl::DispatchAllMessageEvents",
1470 this, &EventSourceImpl::DispatchAllMessageEvents);
1471 NS_ENSURE_STATE(event);
1472
1473 mGoingToDispatchAllMessages = true;
1474
1475 return Dispatch(event.forget(), NS_DISPATCH_NORMAL);
1476 }
1477
1478 return NS_OK;
1479 }
1480
DispatchAllMessageEvents()1481 void EventSourceImpl::DispatchAllMessageEvents() {
1482 AssertIsOnTargetThread();
1483 mGoingToDispatchAllMessages = false;
1484
1485 if (IsClosed() || mFrozen) {
1486 return;
1487 }
1488
1489 nsresult rv;
1490 AutoJSAPI jsapi;
1491 {
1492 auto lock = mSharedData.Lock();
1493 rv = lock->mEventSource->CheckCurrentGlobalCorrectness();
1494 if (NS_FAILED(rv)) {
1495 return;
1496 }
1497
1498 if (NS_WARN_IF(!jsapi.Init(lock->mEventSource->GetOwnerGlobal()))) {
1499 return;
1500 }
1501 }
1502
1503 JSContext* cx = jsapi.cx();
1504
1505 while (mMessagesToDispatch.GetSize() > 0) {
1506 UniquePtr<Message> message(mMessagesToDispatch.PopFront());
1507
1508 if (message->mLastEventID.isSome()) {
1509 mLastEventID.Assign(message->mLastEventID.value());
1510 }
1511
1512 if (message->mLastEventID.isNothing() && !mLastEventID.IsEmpty()) {
1513 message->mLastEventID = Some(mLastEventID);
1514 }
1515
1516 {
1517 auto lock = mSharedData.Lock();
1518 if (lock->mServiceNotifier) {
1519 lock->mServiceNotifier->EventReceived(message->mEventName, mLastEventID,
1520 message->mData, mReconnectionTime,
1521 PR_Now());
1522 }
1523 }
1524
1525 // Now we can turn our string into a jsval
1526 JS::Rooted<JS::Value> jsData(cx);
1527 {
1528 JSString* jsString;
1529 jsString = JS_NewUCStringCopyN(cx, message->mData.get(),
1530 message->mData.Length());
1531 NS_ENSURE_TRUE_VOID(jsString);
1532
1533 jsData.setString(jsString);
1534 }
1535
1536 // create an event that uses the MessageEvent interface,
1537 // which does not bubble, is not cancelable, and has no default action
1538
1539 RefPtr<EventSource> eventSource = GetEventSource();
1540 RefPtr<MessageEvent> event =
1541 new MessageEvent(eventSource, nullptr, nullptr);
1542
1543 event->InitMessageEvent(nullptr, message->mEventName, CanBubble::eNo,
1544 Cancelable::eNo, jsData, mOrigin, mLastEventID,
1545 nullptr, Sequence<OwningNonNull<MessagePort>>());
1546 event->SetTrusted(true);
1547
1548 // We can't hold the mutex while dispatching the event because the mutex is
1549 // not reentrant, and content might call back into our code.
1550 IgnoredErrorResult err;
1551 eventSource->DispatchEvent(*event, err);
1552 if (err.Failed()) {
1553 NS_WARNING("Failed to dispatch the message event!!!");
1554 return;
1555 }
1556
1557 if (IsClosed() || mFrozen) {
1558 return;
1559 }
1560 }
1561 }
1562
ClearFields()1563 void EventSourceImpl::ClearFields() {
1564 AssertIsOnTargetThread();
1565 mCurrentMessage = nullptr;
1566 mLastFieldName.Truncate();
1567 mLastFieldValue.Truncate();
1568 }
1569
SetFieldAndClear()1570 nsresult EventSourceImpl::SetFieldAndClear() {
1571 MOZ_ASSERT(!mIsShutDown);
1572 AssertIsOnTargetThread();
1573 if (mLastFieldName.IsEmpty()) {
1574 mLastFieldValue.Truncate();
1575 return NS_OK;
1576 }
1577 if (!mCurrentMessage) {
1578 mCurrentMessage = MakeUnique<Message>();
1579 }
1580 char16_t first_char;
1581 first_char = mLastFieldName.CharAt(0);
1582
1583 // with no case folding performed
1584 switch (first_char) {
1585 case char16_t('d'):
1586 if (mLastFieldName.EqualsLiteral("data")) {
1587 // If the field name is "data" append the field value to the data
1588 // buffer, then append a single U+000A LINE FEED (LF) character
1589 // to the data buffer.
1590 mCurrentMessage->mData.Append(mLastFieldValue);
1591 mCurrentMessage->mData.Append(LF_CHAR);
1592 }
1593 break;
1594
1595 case char16_t('e'):
1596 if (mLastFieldName.EqualsLiteral("event")) {
1597 mCurrentMessage->mEventName.Assign(mLastFieldValue);
1598 }
1599 break;
1600
1601 case char16_t('i'):
1602 if (mLastFieldName.EqualsLiteral("id")) {
1603 mCurrentMessage->mLastEventID = Some(mLastFieldValue);
1604 }
1605 break;
1606
1607 case char16_t('r'):
1608 if (mLastFieldName.EqualsLiteral("retry")) {
1609 uint32_t newValue = 0;
1610 uint32_t i = 0; // we must ensure that there are only digits
1611 bool assign = true;
1612 for (i = 0; i < mLastFieldValue.Length(); ++i) {
1613 if (mLastFieldValue.CharAt(i) < (char16_t)'0' ||
1614 mLastFieldValue.CharAt(i) > (char16_t)'9') {
1615 assign = false;
1616 break;
1617 }
1618 newValue = newValue * 10 + (((uint32_t)mLastFieldValue.CharAt(i)) -
1619 ((uint32_t)((char16_t)'0')));
1620 }
1621
1622 if (assign) {
1623 if (newValue < MIN_RECONNECTION_TIME_VALUE) {
1624 mReconnectionTime = MIN_RECONNECTION_TIME_VALUE;
1625 } else if (newValue > MAX_RECONNECTION_TIME_VALUE) {
1626 mReconnectionTime = MAX_RECONNECTION_TIME_VALUE;
1627 } else {
1628 mReconnectionTime = newValue;
1629 }
1630 }
1631 break;
1632 }
1633 break;
1634 }
1635
1636 mLastFieldName.Truncate();
1637 mLastFieldValue.Truncate();
1638
1639 return NS_OK;
1640 }
1641
CheckHealthOfRequestCallback(nsIRequest * aRequestCallback)1642 nsresult EventSourceImpl::CheckHealthOfRequestCallback(
1643 nsIRequest* aRequestCallback) {
1644 // This function could be run on target thread if http channel support
1645 // nsIThreadRetargetableRequest. otherwise, it's run on main thread.
1646
1647 // check if we have been closed or if the request has been canceled
1648 // or if we have been frozen
1649 if (IsClosed() || mFrozen || !mHttpChannel) {
1650 return NS_ERROR_ABORT;
1651 }
1652
1653 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequestCallback);
1654 NS_ENSURE_STATE(httpChannel);
1655
1656 if (httpChannel != mHttpChannel) {
1657 NS_WARNING("wrong channel from request callback");
1658 return NS_ERROR_ABORT;
1659 }
1660
1661 return NS_OK;
1662 }
1663
ParseCharacter(char16_t aChr)1664 nsresult EventSourceImpl::ParseCharacter(char16_t aChr) {
1665 AssertIsOnTargetThread();
1666 nsresult rv;
1667
1668 if (IsClosed()) {
1669 return NS_ERROR_ABORT;
1670 }
1671
1672 switch (mStatus) {
1673 case PARSE_STATE_OFF:
1674 NS_ERROR("Invalid state");
1675 return NS_ERROR_FAILURE;
1676 break;
1677
1678 case PARSE_STATE_BEGIN_OF_STREAM:
1679 if (aChr == CR_CHAR) {
1680 mStatus = PARSE_STATE_CR_CHAR;
1681 } else if (aChr == LF_CHAR) {
1682 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1683 } else if (aChr == COLON_CHAR) {
1684 mStatus = PARSE_STATE_COMMENT;
1685 } else {
1686 mLastFieldName += aChr;
1687 mStatus = PARSE_STATE_FIELD_NAME;
1688 }
1689 break;
1690
1691 case PARSE_STATE_CR_CHAR:
1692 if (aChr == CR_CHAR) {
1693 rv = DispatchCurrentMessageEvent(); // there is an empty line (CRCR)
1694 NS_ENSURE_SUCCESS(rv, rv);
1695 } else if (aChr == LF_CHAR) {
1696 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1697 } else if (aChr == COLON_CHAR) {
1698 mStatus = PARSE_STATE_COMMENT;
1699 } else {
1700 mLastFieldName += aChr;
1701 mStatus = PARSE_STATE_FIELD_NAME;
1702 }
1703
1704 break;
1705
1706 case PARSE_STATE_COMMENT:
1707 if (aChr == CR_CHAR) {
1708 mStatus = PARSE_STATE_CR_CHAR;
1709 } else if (aChr == LF_CHAR) {
1710 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1711 }
1712
1713 break;
1714
1715 case PARSE_STATE_FIELD_NAME:
1716 if (aChr == CR_CHAR) {
1717 rv = SetFieldAndClear();
1718 NS_ENSURE_SUCCESS(rv, rv);
1719
1720 mStatus = PARSE_STATE_CR_CHAR;
1721 } else if (aChr == LF_CHAR) {
1722 rv = SetFieldAndClear();
1723 NS_ENSURE_SUCCESS(rv, rv);
1724
1725 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1726 } else if (aChr == COLON_CHAR) {
1727 mStatus = PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE;
1728 } else {
1729 mLastFieldName += aChr;
1730 }
1731
1732 break;
1733
1734 case PARSE_STATE_FIRST_CHAR_OF_FIELD_VALUE:
1735 if (aChr == CR_CHAR) {
1736 rv = SetFieldAndClear();
1737 NS_ENSURE_SUCCESS(rv, rv);
1738
1739 mStatus = PARSE_STATE_CR_CHAR;
1740 } else if (aChr == LF_CHAR) {
1741 rv = SetFieldAndClear();
1742 NS_ENSURE_SUCCESS(rv, rv);
1743
1744 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1745 } else if (aChr == SPACE_CHAR) {
1746 mStatus = PARSE_STATE_FIELD_VALUE;
1747 } else {
1748 mLastFieldValue += aChr;
1749 mStatus = PARSE_STATE_FIELD_VALUE;
1750 }
1751
1752 break;
1753
1754 case PARSE_STATE_FIELD_VALUE:
1755 if (aChr == CR_CHAR) {
1756 rv = SetFieldAndClear();
1757 NS_ENSURE_SUCCESS(rv, rv);
1758
1759 mStatus = PARSE_STATE_CR_CHAR;
1760 } else if (aChr == LF_CHAR) {
1761 rv = SetFieldAndClear();
1762 NS_ENSURE_SUCCESS(rv, rv);
1763
1764 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1765 } else if (aChr != 0) {
1766 // Avoid appending the null char to the field value.
1767 mLastFieldValue += aChr;
1768 } else if (mLastFieldName.EqualsLiteral("id")) {
1769 // Ignore the whole id field if aChr is null
1770 mStatus = PARSE_STATE_IGNORE_FIELD_VALUE;
1771 mLastFieldValue.Truncate();
1772 }
1773
1774 break;
1775
1776 case PARSE_STATE_IGNORE_FIELD_VALUE:
1777 if (aChr == CR_CHAR) {
1778 mStatus = PARSE_STATE_CR_CHAR;
1779 } else if (aChr == LF_CHAR) {
1780 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1781 }
1782 break;
1783
1784 case PARSE_STATE_BEGIN_OF_LINE:
1785 if (aChr == CR_CHAR) {
1786 rv = DispatchCurrentMessageEvent(); // there is an empty line
1787 NS_ENSURE_SUCCESS(rv, rv);
1788
1789 mStatus = PARSE_STATE_CR_CHAR;
1790 } else if (aChr == LF_CHAR) {
1791 rv = DispatchCurrentMessageEvent(); // there is an empty line
1792 NS_ENSURE_SUCCESS(rv, rv);
1793
1794 mStatus = PARSE_STATE_BEGIN_OF_LINE;
1795 } else if (aChr == COLON_CHAR) {
1796 mStatus = PARSE_STATE_COMMENT;
1797 } else if (aChr != 0) {
1798 // Avoid appending the null char to the field name.
1799 mLastFieldName += aChr;
1800 mStatus = PARSE_STATE_FIELD_NAME;
1801 }
1802
1803 break;
1804 }
1805
1806 return NS_OK;
1807 }
1808
1809 namespace {
1810
1811 class WorkerRunnableDispatcher final : public WorkerRunnable {
1812 RefPtr<EventSourceImpl> mEventSourceImpl;
1813
1814 public:
WorkerRunnableDispatcher(RefPtr<EventSourceImpl> && aImpl,WorkerPrivate * aWorkerPrivate,already_AddRefed<nsIRunnable> aEvent)1815 WorkerRunnableDispatcher(RefPtr<EventSourceImpl>&& aImpl,
1816 WorkerPrivate* aWorkerPrivate,
1817 already_AddRefed<nsIRunnable> aEvent)
1818 : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
1819 mEventSourceImpl(std::move(aImpl)),
1820 mEvent(std::move(aEvent)) {}
1821
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)1822 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1823 aWorkerPrivate->AssertIsOnWorkerThread();
1824 return !NS_FAILED(mEvent->Run());
1825 }
1826
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)1827 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
1828 bool aRunResult) override {
1829 // Ensure we drop the RefPtr on the worker thread
1830 // and to not keep us alive longer than needed.
1831 mEventSourceImpl = nullptr;
1832 }
1833
PreDispatch(WorkerPrivate * aWorkerPrivate)1834 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
1835 // We don't call WorkerRunnable::PreDispatch because it would assert the
1836 // wrong thing about which thread we're on. We're on whichever thread the
1837 // channel implementation is running on (probably the main thread or
1838 // transport thread).
1839 return true;
1840 }
1841
PostDispatch(WorkerPrivate * aWorkerPrivate,bool aDispatchResult)1842 void PostDispatch(WorkerPrivate* aWorkerPrivate,
1843 bool aDispatchResult) override {
1844 // We don't call WorkerRunnable::PostDispatch because it would assert the
1845 // wrong thing about which thread we're on. We're on whichever thread the
1846 // channel implementation is running on (probably the main thread or
1847 // transport thread).
1848 }
1849
1850 private:
1851 nsCOMPtr<nsIRunnable> mEvent;
1852 };
1853
1854 } // namespace
1855
CreateWorkerRef(WorkerPrivate * aWorkerPrivate)1856 bool EventSourceImpl::CreateWorkerRef(WorkerPrivate* aWorkerPrivate) {
1857 MOZ_ASSERT(!mWorkerRef);
1858 MOZ_ASSERT(aWorkerPrivate);
1859 aWorkerPrivate->AssertIsOnWorkerThread();
1860
1861 if (mIsShutDown) {
1862 return false;
1863 }
1864
1865 RefPtr<EventSourceImpl> self = this;
1866 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
1867 aWorkerPrivate, "EventSource", [self]() { self->Close(); });
1868
1869 if (NS_WARN_IF(!workerRef)) {
1870 return false;
1871 }
1872
1873 mWorkerRef = new ThreadSafeWorkerRef(workerRef);
1874 return true;
1875 }
1876
ReleaseWorkerRef()1877 void EventSourceImpl::ReleaseWorkerRef() {
1878 MOZ_ASSERT(IsClosed());
1879 MOZ_ASSERT(IsCurrentThreadRunningWorker());
1880 mWorkerRef = nullptr;
1881 }
1882
1883 //-----------------------------------------------------------------------------
1884 // EventSourceImpl::nsIEventTarget
1885 //-----------------------------------------------------------------------------
1886 NS_IMETHODIMP
DispatchFromScript(nsIRunnable * aEvent,uint32_t aFlags)1887 EventSourceImpl::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) {
1888 nsCOMPtr<nsIRunnable> event(aEvent);
1889 return Dispatch(event.forget(), aFlags);
1890 }
1891
1892 NS_IMETHODIMP
Dispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aFlags)1893 EventSourceImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent,
1894 uint32_t aFlags) {
1895 nsCOMPtr<nsIRunnable> event_ref(aEvent);
1896 if (mIsMainThread) {
1897 return NS_DispatchToMainThread(event_ref.forget());
1898 }
1899
1900 if (mIsShutDown) {
1901 // We want to avoid clutter about errors in our shutdown logs,
1902 // so just report NS_OK (we have no explicit return value
1903 // for shutdown).
1904 return NS_OK;
1905 }
1906
1907 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher
1908 // runnable.
1909 RefPtr<WorkerRunnableDispatcher> event = new WorkerRunnableDispatcher(
1910 this, mWorkerRef->Private(), event_ref.forget());
1911
1912 if (!event->Dispatch()) {
1913 return NS_ERROR_FAILURE;
1914 }
1915 return NS_OK;
1916 }
1917
1918 NS_IMETHODIMP
DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,uint32_t aDelayMs)1919 EventSourceImpl::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
1920 uint32_t aDelayMs) {
1921 return NS_ERROR_NOT_IMPLEMENTED;
1922 }
1923
1924 //-----------------------------------------------------------------------------
1925 // EventSourceImpl::nsIThreadRetargetableStreamListener
1926 //-----------------------------------------------------------------------------
1927 NS_IMETHODIMP
CheckListenerChain()1928 EventSourceImpl::CheckListenerChain() {
1929 MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread!");
1930 return NS_OK;
1931 }
1932 ////////////////////////////////////////////////////////////////////////////////
1933 // EventSource
1934 ////////////////////////////////////////////////////////////////////////////////
1935
EventSource(nsIGlobalObject * aGlobal,nsICookieJarSettings * aCookieJarSettings,bool aWithCredentials)1936 EventSource::EventSource(nsIGlobalObject* aGlobal,
1937 nsICookieJarSettings* aCookieJarSettings,
1938 bool aWithCredentials)
1939 : DOMEventTargetHelper(aGlobal),
1940 mWithCredentials(aWithCredentials),
1941 mIsMainThread(NS_IsMainThread()) {
1942 MOZ_ASSERT(aGlobal);
1943 MOZ_ASSERT(aCookieJarSettings);
1944 mESImpl = new EventSourceImpl(this, aCookieJarSettings);
1945 }
1946
1947 EventSource::~EventSource() = default;
1948
CreateAndDispatchSimpleEvent(const nsAString & aName)1949 nsresult EventSource::CreateAndDispatchSimpleEvent(const nsAString& aName) {
1950 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1951 // it doesn't bubble, and it isn't cancelable
1952 event->InitEvent(aName, false, false);
1953 event->SetTrusted(true);
1954 ErrorResult rv;
1955 DispatchEvent(*event, rv);
1956 return rv.StealNSResult();
1957 }
1958
1959 /* static */
Constructor(const GlobalObject & aGlobal,const nsAString & aURL,const EventSourceInit & aEventSourceInitDict,ErrorResult & aRv)1960 already_AddRefed<EventSource> EventSource::Constructor(
1961 const GlobalObject& aGlobal, const nsAString& aURL,
1962 const EventSourceInit& aEventSourceInitDict, ErrorResult& aRv) {
1963 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1964 if (NS_WARN_IF(!global)) {
1965 aRv.Throw(NS_ERROR_FAILURE);
1966 return nullptr;
1967 }
1968
1969 nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
1970 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global);
1971 if (ownerWindow) {
1972 Document* doc = ownerWindow->GetExtantDoc();
1973 if (NS_WARN_IF(!doc)) {
1974 aRv.Throw(NS_ERROR_FAILURE);
1975 return nullptr;
1976 }
1977
1978 cookieJarSettings = doc->CookieJarSettings();
1979 } else {
1980 // Worker side.
1981 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1982 MOZ_ASSERT(workerPrivate);
1983 cookieJarSettings = workerPrivate->CookieJarSettings();
1984 }
1985
1986 RefPtr<EventSource> eventSource = new EventSource(
1987 global, cookieJarSettings, aEventSourceInitDict.mWithCredentials);
1988
1989 if (NS_IsMainThread()) {
1990 // Get principal from document and init EventSourceImpl
1991 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
1992 do_QueryInterface(aGlobal.GetAsSupports());
1993 if (!scriptPrincipal) {
1994 aRv.Throw(NS_ERROR_FAILURE);
1995 return nullptr;
1996 }
1997 nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
1998 if (!principal) {
1999 aRv.Throw(NS_ERROR_FAILURE);
2000 return nullptr;
2001 }
2002 eventSource->mESImpl->Init(principal, aURL, aRv);
2003 if (NS_WARN_IF(aRv.Failed())) {
2004 return nullptr;
2005 }
2006
2007 eventSource->mESImpl->InitChannelAndRequestEventSource(true);
2008 return eventSource.forget();
2009 }
2010
2011 // Worker side.
2012 {
2013 // Scope for possible failures that need cleanup
2014 auto guardESImpl = MakeScopeExit([&] { eventSource->mESImpl = nullptr; });
2015
2016 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2017 MOZ_ASSERT(workerPrivate);
2018
2019 eventSource->mESImpl->mInnerWindowID = workerPrivate->WindowID();
2020
2021 RefPtr<InitRunnable> initRunnable =
2022 new InitRunnable(workerPrivate, eventSource->mESImpl, aURL);
2023 initRunnable->Dispatch(Canceling, aRv);
2024 if (NS_WARN_IF(aRv.Failed())) {
2025 return nullptr;
2026 }
2027
2028 aRv = initRunnable->ErrorCode();
2029 if (NS_WARN_IF(aRv.Failed())) {
2030 return nullptr;
2031 }
2032
2033 // In workers we have to keep the worker alive using a WorkerRef in order
2034 // to dispatch messages correctly.
2035 if (!eventSource->mESImpl->CreateWorkerRef(workerPrivate)) {
2036 // The worker is already shutting down. Let's return an already closed
2037 // object, but marked as Connecting.
2038 // mESImpl is nulled by this call such that EventSourceImpl is
2039 // released before returning the object, otherwise
2040 // it will set EventSource to a CLOSED state in its DTOR..
2041 eventSource->mESImpl->Close();
2042 eventSource->mReadyState = EventSourceImpl::CONNECTING;
2043
2044 return eventSource.forget();
2045 }
2046
2047 // Let's connect to the server.
2048 RefPtr<ConnectRunnable> connectRunnable =
2049 new ConnectRunnable(workerPrivate, eventSource->mESImpl);
2050 connectRunnable->Dispatch(Canceling, aRv);
2051 if (NS_WARN_IF(aRv.Failed())) {
2052 return nullptr;
2053 }
2054
2055 // End of scope for possible failures
2056 guardESImpl.release();
2057 }
2058
2059 return eventSource.forget();
2060 }
2061
2062 // nsWrapperCache
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)2063 JSObject* EventSource::WrapObject(JSContext* aCx,
2064 JS::Handle<JSObject*> aGivenProto) {
2065 return EventSource_Binding::Wrap(aCx, this, aGivenProto);
2066 }
2067
Close()2068 void EventSource::Close() {
2069 AssertIsOnTargetThread();
2070 if (mESImpl) {
2071 // Close potentially kills ourself, ensure
2072 // to not access any members afterwards.
2073 mESImpl->Close();
2074 }
2075 }
2076
2077 //-----------------------------------------------------------------------------
2078 // EventSource::nsISupports
2079 //-----------------------------------------------------------------------------
2080
2081 NS_IMPL_CYCLE_COLLECTION_CLASS(EventSource)
2082
2083 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(EventSource,
2084 DOMEventTargetHelper)
2085 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2086
2087 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(EventSource,
2088 DOMEventTargetHelper)
2089 if (tmp->mESImpl) {
2090 // IsCertainlyaliveForCC will return true and cause the cycle
2091 // collector to skip this instance when mESImpl is non-null and
2092 // points back to ourself.
2093 // mESImpl is initialized to be non-null in the constructor
2094 // and should have been wiped out in our close function.
2095 MOZ_ASSERT_UNREACHABLE("Paranoia cleanup that should never happen.");
2096 tmp->Close();
2097 }
2098 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2099
IsCertainlyAliveForCC() const2100 bool EventSource::IsCertainlyAliveForCC() const {
2101 // Until we are double linked forth and back, we want to stay alive.
2102 if (!mESImpl) {
2103 return false;
2104 }
2105 auto lock = mESImpl->mSharedData.Lock();
2106 return lock->mEventSource == this;
2107 }
2108
2109 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EventSource)
2110 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
2111
2112 NS_IMPL_ADDREF_INHERITED(EventSource, DOMEventTargetHelper)
2113 NS_IMPL_RELEASE_INHERITED(EventSource, DOMEventTargetHelper)
2114
2115 } // namespace mozilla::dom
2116