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 "EventSourceEventService.h"
8 #include "mozilla/StaticPtr.h"
9 #include "nsISupportsPrimitives.h"
10 #include "nsIObserverService.h"
11 #include "nsXULAppAPI.h"
12 #include "nsSocketTransportService2.h"
13 #include "nsThreadUtils.h"
14 #include "mozilla/Services.h"
15 
16 namespace mozilla {
17 namespace dom {
18 
19 namespace {
20 
21 StaticRefPtr<EventSourceEventService> gEventSourceEventService;
22 
23 }  // anonymous namespace
24 
25 class EventSourceBaseRunnable : public Runnable {
26  public:
EventSourceBaseRunnable(uint64_t aHttpChannelId,uint64_t aInnerWindowID)27   EventSourceBaseRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID)
28       : Runnable("dom::EventSourceBaseRunnable"),
29         mHttpChannelId(aHttpChannelId),
30         mInnerWindowID(aInnerWindowID) {}
31 
Run()32   NS_IMETHOD Run() override {
33     MOZ_ASSERT(NS_IsMainThread());
34     RefPtr<EventSourceEventService> service =
35         EventSourceEventService::GetOrCreate();
36     MOZ_ASSERT(service);
37 
38     EventSourceEventService::EventSourceListeners listeners;
39 
40     service->GetListeners(mInnerWindowID, listeners);
41 
42     for (uint32_t i = 0; i < listeners.Length(); ++i) {
43       DoWork(listeners[i]);
44     }
45 
46     return NS_OK;
47   }
48 
49  protected:
50   ~EventSourceBaseRunnable() = default;
51 
52   virtual void DoWork(nsIEventSourceEventListener* aListener) = 0;
53 
54   uint64_t mHttpChannelId;
55   uint64_t mInnerWindowID;
56 };
57 
58 class EventSourceConnectionOpenedRunnable final
59     : public EventSourceBaseRunnable {
60  public:
EventSourceConnectionOpenedRunnable(uint64_t aHttpChannelId,uint64_t aInnerWindowID)61   EventSourceConnectionOpenedRunnable(uint64_t aHttpChannelId,
62                                       uint64_t aInnerWindowID)
63       : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
64 
65  private:
DoWork(nsIEventSourceEventListener * aListener)66   virtual void DoWork(nsIEventSourceEventListener* aListener) override {
67     DebugOnly<nsresult> rv =
68         aListener->EventSourceConnectionOpened(mHttpChannelId);
69     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
70                          "EventSourceConnectionOpened failed");
71   }
72 };
73 
74 class EventSourceConnectionClosedRunnable final
75     : public EventSourceBaseRunnable {
76  public:
EventSourceConnectionClosedRunnable(uint64_t aHttpChannelId,uint64_t aInnerWindowID)77   EventSourceConnectionClosedRunnable(uint64_t aHttpChannelId,
78                                       uint64_t aInnerWindowID)
79       : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
80 
81  private:
DoWork(nsIEventSourceEventListener * aListener)82   virtual void DoWork(nsIEventSourceEventListener* aListener) override {
83     DebugOnly<nsresult> rv =
84         aListener->EventSourceConnectionClosed(mHttpChannelId);
85     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
86                          "EventSourceConnectionClosed failed");
87   }
88 };
89 
90 class EventSourceEventRunnable final : public EventSourceBaseRunnable {
91  public:
EventSourceEventRunnable(uint64_t aHttpChannelId,uint64_t aInnerWindowID,const nsAString & aEventName,const nsAString & aLastEventID,const nsAString & aData,uint32_t aRetry,DOMHighResTimeStamp aTimeStamp)92   EventSourceEventRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID,
93                            const nsAString& aEventName,
94                            const nsAString& aLastEventID,
95                            const nsAString& aData, uint32_t aRetry,
96                            DOMHighResTimeStamp aTimeStamp)
97       : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID),
98         mEventName(aEventName),
99         mLastEventID(aLastEventID),
100         mData(aData),
101         mRetry(aRetry),
102         mTimeStamp(aTimeStamp) {}
103 
104  private:
DoWork(nsIEventSourceEventListener * aListener)105   virtual void DoWork(nsIEventSourceEventListener* aListener) override {
106     DebugOnly<nsresult> rv = aListener->EventReceived(
107         mHttpChannelId, mEventName, mLastEventID, mData, mRetry, mTimeStamp);
108 
109     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Event op failed");
110   }
111 
112   nsString mEventName;
113   nsString mLastEventID;
114   nsString mData;
115   uint32_t mRetry;
116   DOMHighResTimeStamp mTimeStamp;
117 };
118 
119 /* static */
120 already_AddRefed<EventSourceEventService>
GetOrCreate()121 EventSourceEventService::GetOrCreate() {
122   MOZ_ASSERT(NS_IsMainThread());
123 
124   if (!gEventSourceEventService) {
125     gEventSourceEventService = new EventSourceEventService();
126   }
127 
128   RefPtr<EventSourceEventService> service = gEventSourceEventService.get();
129   return service.forget();
130 }
131 
132 NS_INTERFACE_MAP_BEGIN(EventSourceEventService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsIEventSourceEventService)133   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEventSourceEventService)
134   NS_INTERFACE_MAP_ENTRY(nsIObserver)
135   NS_INTERFACE_MAP_ENTRY(nsIEventSourceEventService)
136 NS_INTERFACE_MAP_END
137 
138 NS_IMPL_ADDREF(EventSourceEventService)
139 NS_IMPL_RELEASE(EventSourceEventService)
140 
141 EventSourceEventService::EventSourceEventService() : mCountListeners(0) {
142   MOZ_ASSERT(NS_IsMainThread());
143 
144   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
145   if (obs) {
146     obs->AddObserver(this, "xpcom-shutdown", false);
147     obs->AddObserver(this, "inner-window-destroyed", false);
148   }
149 }
150 
~EventSourceEventService()151 EventSourceEventService::~EventSourceEventService() {
152   MOZ_ASSERT(NS_IsMainThread());
153 }
154 
EventSourceConnectionOpened(uint64_t aHttpChannelId,uint64_t aInnerWindowID)155 void EventSourceEventService::EventSourceConnectionOpened(
156     uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
157   // Let's continue only if we have some listeners.
158   if (!HasListeners()) {
159     return;
160   }
161 
162   RefPtr<EventSourceConnectionOpenedRunnable> runnable =
163       new EventSourceConnectionOpenedRunnable(aHttpChannelId, aInnerWindowID);
164   DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
165   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
166 }
167 
EventSourceConnectionClosed(uint64_t aHttpChannelId,uint64_t aInnerWindowID)168 void EventSourceEventService::EventSourceConnectionClosed(
169     uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
170   // Let's continue only if we have some listeners.
171   if (!HasListeners()) {
172     return;
173   }
174   RefPtr<EventSourceConnectionClosedRunnable> runnable =
175       new EventSourceConnectionClosedRunnable(aHttpChannelId, aInnerWindowID);
176   DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
177   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
178 }
179 
EventReceived(uint64_t aHttpChannelId,uint64_t aInnerWindowID,const nsAString & aEventName,const nsAString & aLastEventID,const nsAString & aData,uint32_t aRetry,DOMHighResTimeStamp aTimeStamp)180 void EventSourceEventService::EventReceived(
181     uint64_t aHttpChannelId, uint64_t aInnerWindowID,
182     const nsAString& aEventName, const nsAString& aLastEventID,
183     const nsAString& aData, uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
184   // Let's continue only if we have some listeners.
185   if (!HasListeners()) {
186     return;
187   }
188 
189   RefPtr<EventSourceEventRunnable> runnable =
190       new EventSourceEventRunnable(aHttpChannelId, aInnerWindowID, aEventName,
191                                    aLastEventID, aData, aRetry, aTimeStamp);
192   DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
193   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
194 }
195 
196 NS_IMETHODIMP
AddListener(uint64_t aInnerWindowID,nsIEventSourceEventListener * aListener)197 EventSourceEventService::AddListener(uint64_t aInnerWindowID,
198                                      nsIEventSourceEventListener* aListener) {
199   MOZ_ASSERT(NS_IsMainThread());
200   if (!aListener) {
201     return NS_ERROR_FAILURE;
202   }
203   ++mCountListeners;
204 
205   WindowListener* listener = mWindows.Get(aInnerWindowID);
206   if (!listener) {
207     listener = new WindowListener();
208     mWindows.Put(aInnerWindowID, listener);
209   }
210 
211   listener->mListeners.AppendElement(aListener);
212 
213   return NS_OK;
214 }
215 
216 NS_IMETHODIMP
RemoveListener(uint64_t aInnerWindowID,nsIEventSourceEventListener * aListener)217 EventSourceEventService::RemoveListener(
218     uint64_t aInnerWindowID, nsIEventSourceEventListener* aListener) {
219   MOZ_ASSERT(NS_IsMainThread());
220 
221   if (!aListener) {
222     return NS_ERROR_FAILURE;
223   }
224 
225   WindowListener* listener = mWindows.Get(aInnerWindowID);
226   if (!listener) {
227     return NS_ERROR_FAILURE;
228   }
229 
230   if (!listener->mListeners.RemoveElement(aListener)) {
231     return NS_ERROR_FAILURE;
232   }
233 
234   // The last listener for this window.
235   if (listener->mListeners.IsEmpty()) {
236     mWindows.Remove(aInnerWindowID);
237   }
238 
239   MOZ_ASSERT(mCountListeners);
240   --mCountListeners;
241 
242   return NS_OK;
243 }
244 
245 NS_IMETHODIMP
HasListenerFor(uint64_t aInnerWindowID,bool * aResult)246 EventSourceEventService::HasListenerFor(uint64_t aInnerWindowID,
247                                         bool* aResult) {
248   MOZ_ASSERT(NS_IsMainThread());
249 
250   *aResult = mWindows.Get(aInnerWindowID);
251 
252   return NS_OK;
253 }
254 
255 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)256 EventSourceEventService::Observe(nsISupports* aSubject, const char* aTopic,
257                                  const char16_t* aData) {
258   MOZ_ASSERT(NS_IsMainThread());
259 
260   if (!strcmp(aTopic, "xpcom-shutdown")) {
261     Shutdown();
262     return NS_OK;
263   }
264 
265   if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
266     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
267     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
268 
269     uint64_t innerID;
270     nsresult rv = wrapper->GetData(&innerID);
271     NS_ENSURE_SUCCESS(rv, rv);
272 
273     WindowListener* listener = mWindows.Get(innerID);
274     if (!listener) {
275       return NS_OK;
276     }
277 
278     MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
279     mCountListeners -= listener->mListeners.Length();
280     mWindows.Remove(innerID);
281   }
282 
283   // This should not happen.
284   return NS_ERROR_FAILURE;
285 }
286 
Shutdown()287 void EventSourceEventService::Shutdown() {
288   MOZ_ASSERT(NS_IsMainThread());
289 
290   if (gEventSourceEventService) {
291     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
292     if (obs) {
293       obs->RemoveObserver(gEventSourceEventService, "xpcom-shutdown");
294       obs->RemoveObserver(gEventSourceEventService, "inner-window-destroyed");
295     }
296 
297     mWindows.Clear();
298     gEventSourceEventService = nullptr;
299   }
300 }
301 
HasListeners() const302 bool EventSourceEventService::HasListeners() const { return !!mCountListeners; }
303 
GetListeners(uint64_t aInnerWindowID,EventSourceEventService::EventSourceListeners & aListeners) const304 void EventSourceEventService::GetListeners(
305     uint64_t aInnerWindowID,
306     EventSourceEventService::EventSourceListeners& aListeners) const {
307   aListeners.Clear();
308 
309   WindowListener* listener = mWindows.Get(aInnerWindowID);
310   if (!listener) {
311     return;
312   }
313 
314   aListeners.AppendElements(listener->mListeners);
315 }
316 
317 }  // namespace dom
318 }  // namespace mozilla
319