EventSourceEventService.cpp (9454B)
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 9 #include "mozilla/Services.h" 10 #include "mozilla/StaticPtr.h" 11 #include "nsIObserverService.h" 12 #include "nsISupportsPrimitives.h" 13 #include "nsSocketTransportService2.h" 14 #include "nsThreadUtils.h" 15 #include "nsXULAppAPI.h" 16 17 namespace mozilla::dom { 18 19 namespace { 20 21 StaticRefPtr<EventSourceEventService> gEventSourceEventService; 22 23 } // anonymous namespace 24 25 class EventSourceBaseRunnable : public Runnable { 26 public: 27 EventSourceBaseRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID) 28 : Runnable("dom::EventSourceBaseRunnable"), 29 mHttpChannelId(aHttpChannelId), 30 mInnerWindowID(aInnerWindowID) {} 31 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: 61 EventSourceConnectionOpenedRunnable(uint64_t aHttpChannelId, 62 uint64_t aInnerWindowID) 63 : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {} 64 65 private: 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: 77 EventSourceConnectionClosedRunnable(uint64_t aHttpChannelId, 78 uint64_t aInnerWindowID) 79 : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {} 80 81 private: 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: 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: 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> 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) 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 151 EventSourceEventService::~EventSourceEventService() { 152 MOZ_ASSERT(NS_IsMainThread()); 153 } 154 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 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 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 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.GetOrInsertNew(aInnerWindowID); 206 207 listener->mListeners.AppendElement(aListener); 208 209 return NS_OK; 210 } 211 212 NS_IMETHODIMP 213 EventSourceEventService::RemoveListener( 214 uint64_t aInnerWindowID, nsIEventSourceEventListener* aListener) { 215 MOZ_ASSERT(NS_IsMainThread()); 216 217 if (!aListener) { 218 return NS_ERROR_FAILURE; 219 } 220 221 WindowListener* listener = mWindows.Get(aInnerWindowID); 222 if (!listener) { 223 return NS_ERROR_FAILURE; 224 } 225 226 if (!listener->mListeners.RemoveElement(aListener)) { 227 return NS_ERROR_FAILURE; 228 } 229 230 // The last listener for this window. 231 if (listener->mListeners.IsEmpty()) { 232 mWindows.Remove(aInnerWindowID); 233 } 234 235 MOZ_ASSERT(mCountListeners); 236 --mCountListeners; 237 238 return NS_OK; 239 } 240 241 NS_IMETHODIMP 242 EventSourceEventService::HasListenerFor(uint64_t aInnerWindowID, 243 bool* aResult) { 244 MOZ_ASSERT(NS_IsMainThread()); 245 246 *aResult = mWindows.Get(aInnerWindowID); 247 248 return NS_OK; 249 } 250 251 NS_IMETHODIMP 252 EventSourceEventService::Observe(nsISupports* aSubject, const char* aTopic, 253 const char16_t* aData) { 254 MOZ_ASSERT(NS_IsMainThread()); 255 256 if (!strcmp(aTopic, "xpcom-shutdown")) { 257 Shutdown(); 258 return NS_OK; 259 } 260 261 if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) { 262 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); 263 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE); 264 265 uint64_t innerID; 266 nsresult rv = wrapper->GetData(&innerID); 267 NS_ENSURE_SUCCESS(rv, rv); 268 269 WindowListener* listener = mWindows.Get(innerID); 270 if (!listener) { 271 return NS_OK; 272 } 273 274 MOZ_ASSERT(mCountListeners >= listener->mListeners.Length()); 275 mCountListeners -= listener->mListeners.Length(); 276 mWindows.Remove(innerID); 277 } 278 279 // This should not happen. 280 return NS_ERROR_FAILURE; 281 } 282 283 void EventSourceEventService::Shutdown() { 284 MOZ_ASSERT(NS_IsMainThread()); 285 286 if (gEventSourceEventService) { 287 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 288 if (obs) { 289 obs->RemoveObserver(gEventSourceEventService, "xpcom-shutdown"); 290 obs->RemoveObserver(gEventSourceEventService, "inner-window-destroyed"); 291 } 292 293 mWindows.Clear(); 294 gEventSourceEventService = nullptr; 295 } 296 } 297 298 bool EventSourceEventService::HasListeners() const { return !!mCountListeners; } 299 300 void EventSourceEventService::GetListeners( 301 uint64_t aInnerWindowID, 302 EventSourceEventService::EventSourceListeners& aListeners) const { 303 aListeners.Clear(); 304 305 WindowListener* listener = mWindows.Get(aInnerWindowID); 306 if (!listener) { 307 return; 308 } 309 310 aListeners.AppendElements(listener->mListeners); 311 } 312 313 } // namespace mozilla::dom