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