mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			312 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			312 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "EventSourceEventService.h"
 | 
						|
#include "mozilla/StaticPtr.h"
 | 
						|
#include "nsISupportsPrimitives.h"
 | 
						|
#include "nsIObserverService.h"
 | 
						|
#include "nsXULAppAPI.h"
 | 
						|
#include "nsSocketTransportService2.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
#include "mozilla/Services.h"
 | 
						|
 | 
						|
namespace mozilla::dom {
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
StaticRefPtr<EventSourceEventService> gEventSourceEventService;
 | 
						|
 | 
						|
}  // anonymous namespace
 | 
						|
 | 
						|
class EventSourceBaseRunnable : public Runnable {
 | 
						|
 public:
 | 
						|
  EventSourceBaseRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID)
 | 
						|
      : Runnable("dom::EventSourceBaseRunnable"),
 | 
						|
        mHttpChannelId(aHttpChannelId),
 | 
						|
        mInnerWindowID(aInnerWindowID) {}
 | 
						|
 | 
						|
  NS_IMETHOD Run() override {
 | 
						|
    MOZ_ASSERT(NS_IsMainThread());
 | 
						|
    RefPtr<EventSourceEventService> service =
 | 
						|
        EventSourceEventService::GetOrCreate();
 | 
						|
    MOZ_ASSERT(service);
 | 
						|
 | 
						|
    EventSourceEventService::EventSourceListeners listeners;
 | 
						|
 | 
						|
    service->GetListeners(mInnerWindowID, listeners);
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < listeners.Length(); ++i) {
 | 
						|
      DoWork(listeners[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 protected:
 | 
						|
  ~EventSourceBaseRunnable() = default;
 | 
						|
 | 
						|
  virtual void DoWork(nsIEventSourceEventListener* aListener) = 0;
 | 
						|
 | 
						|
  uint64_t mHttpChannelId;
 | 
						|
  uint64_t mInnerWindowID;
 | 
						|
};
 | 
						|
 | 
						|
class EventSourceConnectionOpenedRunnable final
 | 
						|
    : public EventSourceBaseRunnable {
 | 
						|
 public:
 | 
						|
  EventSourceConnectionOpenedRunnable(uint64_t aHttpChannelId,
 | 
						|
                                      uint64_t aInnerWindowID)
 | 
						|
      : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual void DoWork(nsIEventSourceEventListener* aListener) override {
 | 
						|
    DebugOnly<nsresult> rv =
 | 
						|
        aListener->EventSourceConnectionOpened(mHttpChannelId);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EventSourceConnectionOpened failed");
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class EventSourceConnectionClosedRunnable final
 | 
						|
    : public EventSourceBaseRunnable {
 | 
						|
 public:
 | 
						|
  EventSourceConnectionClosedRunnable(uint64_t aHttpChannelId,
 | 
						|
                                      uint64_t aInnerWindowID)
 | 
						|
      : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual void DoWork(nsIEventSourceEventListener* aListener) override {
 | 
						|
    DebugOnly<nsresult> rv =
 | 
						|
        aListener->EventSourceConnectionClosed(mHttpChannelId);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EventSourceConnectionClosed failed");
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class EventSourceEventRunnable final : public EventSourceBaseRunnable {
 | 
						|
 public:
 | 
						|
  EventSourceEventRunnable(uint64_t aHttpChannelId, uint64_t aInnerWindowID,
 | 
						|
                           const nsAString& aEventName,
 | 
						|
                           const nsAString& aLastEventID,
 | 
						|
                           const nsAString& aData, uint32_t aRetry,
 | 
						|
                           DOMHighResTimeStamp aTimeStamp)
 | 
						|
      : EventSourceBaseRunnable(aHttpChannelId, aInnerWindowID),
 | 
						|
        mEventName(aEventName),
 | 
						|
        mLastEventID(aLastEventID),
 | 
						|
        mData(aData),
 | 
						|
        mRetry(aRetry),
 | 
						|
        mTimeStamp(aTimeStamp) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual void DoWork(nsIEventSourceEventListener* aListener) override {
 | 
						|
    DebugOnly<nsresult> rv = aListener->EventReceived(
 | 
						|
        mHttpChannelId, mEventName, mLastEventID, mData, mRetry, mTimeStamp);
 | 
						|
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Event op failed");
 | 
						|
  }
 | 
						|
 | 
						|
  nsString mEventName;
 | 
						|
  nsString mLastEventID;
 | 
						|
  nsString mData;
 | 
						|
  uint32_t mRetry;
 | 
						|
  DOMHighResTimeStamp mTimeStamp;
 | 
						|
};
 | 
						|
 | 
						|
/* static */
 | 
						|
already_AddRefed<EventSourceEventService>
 | 
						|
EventSourceEventService::GetOrCreate() {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (!gEventSourceEventService) {
 | 
						|
    gEventSourceEventService = new EventSourceEventService();
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<EventSourceEventService> service = gEventSourceEventService.get();
 | 
						|
  return service.forget();
 | 
						|
}
 | 
						|
 | 
						|
NS_INTERFACE_MAP_BEGIN(EventSourceEventService)
 | 
						|
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEventSourceEventService)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsIEventSourceEventService)
 | 
						|
NS_INTERFACE_MAP_END
 | 
						|
 | 
						|
NS_IMPL_ADDREF(EventSourceEventService)
 | 
						|
NS_IMPL_RELEASE(EventSourceEventService)
 | 
						|
 | 
						|
EventSourceEventService::EventSourceEventService() : mCountListeners(0) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | 
						|
  if (obs) {
 | 
						|
    obs->AddObserver(this, "xpcom-shutdown", false);
 | 
						|
    obs->AddObserver(this, "inner-window-destroyed", false);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
EventSourceEventService::~EventSourceEventService() {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
}
 | 
						|
 | 
						|
void EventSourceEventService::EventSourceConnectionOpened(
 | 
						|
    uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
 | 
						|
  // Let's continue only if we have some listeners.
 | 
						|
  if (!HasListeners()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<EventSourceConnectionOpenedRunnable> runnable =
 | 
						|
      new EventSourceConnectionOpenedRunnable(aHttpChannelId, aInnerWindowID);
 | 
						|
  DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
 | 
						|
}
 | 
						|
 | 
						|
void EventSourceEventService::EventSourceConnectionClosed(
 | 
						|
    uint64_t aHttpChannelId, uint64_t aInnerWindowID) {
 | 
						|
  // Let's continue only if we have some listeners.
 | 
						|
  if (!HasListeners()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  RefPtr<EventSourceConnectionClosedRunnable> runnable =
 | 
						|
      new EventSourceConnectionClosedRunnable(aHttpChannelId, aInnerWindowID);
 | 
						|
  DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
 | 
						|
}
 | 
						|
 | 
						|
void EventSourceEventService::EventReceived(
 | 
						|
    uint64_t aHttpChannelId, uint64_t aInnerWindowID,
 | 
						|
    const nsAString& aEventName, const nsAString& aLastEventID,
 | 
						|
    const nsAString& aData, uint32_t aRetry, DOMHighResTimeStamp aTimeStamp) {
 | 
						|
  // Let's continue only if we have some listeners.
 | 
						|
  if (!HasListeners()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<EventSourceEventRunnable> runnable =
 | 
						|
      new EventSourceEventRunnable(aHttpChannelId, aInnerWindowID, aEventName,
 | 
						|
                                   aLastEventID, aData, aRetry, aTimeStamp);
 | 
						|
  DebugOnly<nsresult> rv = NS_DispatchToMainThread(runnable);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventSourceEventService::AddListener(uint64_t aInnerWindowID,
 | 
						|
                                     nsIEventSourceEventListener* aListener) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
  if (!aListener) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  ++mCountListeners;
 | 
						|
 | 
						|
  WindowListener* listener = mWindows.GetOrInsertNew(aInnerWindowID);
 | 
						|
 | 
						|
  listener->mListeners.AppendElement(aListener);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventSourceEventService::RemoveListener(
 | 
						|
    uint64_t aInnerWindowID, nsIEventSourceEventListener* aListener) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (!aListener) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  WindowListener* listener = mWindows.Get(aInnerWindowID);
 | 
						|
  if (!listener) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!listener->mListeners.RemoveElement(aListener)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  // The last listener for this window.
 | 
						|
  if (listener->mListeners.IsEmpty()) {
 | 
						|
    mWindows.Remove(aInnerWindowID);
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(mCountListeners);
 | 
						|
  --mCountListeners;
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventSourceEventService::HasListenerFor(uint64_t aInnerWindowID,
 | 
						|
                                        bool* aResult) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  *aResult = mWindows.Get(aInnerWindowID);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
EventSourceEventService::Observe(nsISupports* aSubject, const char* aTopic,
 | 
						|
                                 const char16_t* aData) {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (!strcmp(aTopic, "xpcom-shutdown")) {
 | 
						|
    Shutdown();
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
 | 
						|
    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
 | 
						|
    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
 | 
						|
 | 
						|
    uint64_t innerID;
 | 
						|
    nsresult rv = wrapper->GetData(&innerID);
 | 
						|
    NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
    WindowListener* listener = mWindows.Get(innerID);
 | 
						|
    if (!listener) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    MOZ_ASSERT(mCountListeners >= listener->mListeners.Length());
 | 
						|
    mCountListeners -= listener->mListeners.Length();
 | 
						|
    mWindows.Remove(innerID);
 | 
						|
  }
 | 
						|
 | 
						|
  // This should not happen.
 | 
						|
  return NS_ERROR_FAILURE;
 | 
						|
}
 | 
						|
 | 
						|
void EventSourceEventService::Shutdown() {
 | 
						|
  MOZ_ASSERT(NS_IsMainThread());
 | 
						|
 | 
						|
  if (gEventSourceEventService) {
 | 
						|
    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
 | 
						|
    if (obs) {
 | 
						|
      obs->RemoveObserver(gEventSourceEventService, "xpcom-shutdown");
 | 
						|
      obs->RemoveObserver(gEventSourceEventService, "inner-window-destroyed");
 | 
						|
    }
 | 
						|
 | 
						|
    mWindows.Clear();
 | 
						|
    gEventSourceEventService = nullptr;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool EventSourceEventService::HasListeners() const { return !!mCountListeners; }
 | 
						|
 | 
						|
void EventSourceEventService::GetListeners(
 | 
						|
    uint64_t aInnerWindowID,
 | 
						|
    EventSourceEventService::EventSourceListeners& aListeners) const {
 | 
						|
  aListeners.Clear();
 | 
						|
 | 
						|
  WindowListener* listener = mWindows.Get(aInnerWindowID);
 | 
						|
  if (!listener) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  aListeners.AppendElements(listener->mListeners);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::dom
 |