/* 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/. */

#ifndef mozilla_extensions_ExtensionBrowser_h
#define mozilla_extensions_ExtensionBrowser_h

#include "nsCOMPtr.h"
#include "nsISupports.h"
#include "nsTHashMap.h"
#include "nsWrapperCache.h"
#include "mozilla/WeakPtr.h"
#include "xpcpublic.h"

class nsIGlobalObject;
class nsIURI;

namespace mozilla {

class ErrorResult;

namespace extensions {

class ExtensionAlarms;
class ExtensionBrowserSettings;
class ExtensionDns;
class ExtensionMockAPI;
class ExtensionPort;
class ExtensionProxy;
class ExtensionPublicSuffix;
class ExtensionRuntime;
class ExtensionScripting;
class ExtensionTest;

bool ExtensionAPIAllowed(JSContext* aCx, JSObject* aGlobal);

void CreateAndDispatchInitWorkerContextRunnable();

already_AddRefed<Runnable> CreateWorkerLoadedRunnable(
    const uint64_t aServiceWorkerDescriptorId,
    const nsCOMPtr<nsIURI>& aWorkerBaseURI);

already_AddRefed<Runnable> CreateWorkerDestroyedRunnable(
    const uint64_t aServiceWorkerDescriptorId,
    const nsCOMPtr<nsIURI>& aWorkerBaseURI);

// An HashMap used to keep track of listeners registered synchronously while
// the worker script is executing, used internally by nsIServiceWorkerManager
// wakeforExtensionAPIEvent method to resolve to true if the worker script
// spawned did have a listener subscribed for the related API event name.
class ExtensionEventWakeupMap final
    : public nsTHashMap<nsStringHashKey, uint64_t> {
  static void ToMapKey(const nsAString& aAPINamespace,
                       const nsAString& aAPIName, nsAString& aResultMapKey);

 public:
  nsresult IncrementListeners(const nsAString& aAPINamespace,
                              const nsAString& aAPIName);
  nsresult DecrementListeners(const nsAString& aAPINamespace,
                              const nsAString& aAPIName);
  bool HasListener(const nsAString& aAPINamespace, const nsAString& aAPIName);
};

class ExtensionBrowser final : public nsISupports, public nsWrapperCache {
 public:
  explicit ExtensionBrowser(nsIGlobalObject* aGlobal);

  // Helpers used for the expected behavior of the browser.runtime.lastError
  // and browser.extension.lastError.
  void SetLastError(JS::Handle<JS::Value> aLastError);
  void GetLastError(JS::MutableHandle<JS::Value> aRetVal);
  // ClearLastError is used by ChromeCompatCallbackHandler::RejectedCallback
  // to clear the lastError property. When this method returns true the
  // caller will know that the error value wasn't checked by the callback and
  // should be reported to the console
  bool ClearLastError();

  // Helpers used to keep track of the event listeners added during the
  // initial sync worker script execution.
  nsresult TrackWakeupEventListener(JSContext* aCx,
                                    const nsString& aAPINamespace,
                                    const nsString& aAPIName);
  nsresult UntrackWakeupEventListener(JSContext* aCx,
                                      const nsString& aAPINamespace,
                                      const nsString& aAPIName);
  bool HasWakeupEventListener(const nsString& aAPINamespace,
                              const nsString& aAPIName);

  // Helpers used for the ExtensionPort.

  // Get an ExtensionPort instance given its port descriptor (returns an
  // existing port if an instance is still tracked in the ports lookup table,
  // otherwise it creates a new one).
  already_AddRefed<ExtensionPort> GetPort(
      JS::Handle<JS::Value> aDescriptorValue, ErrorResult& aRv);

  // Remove the entry for an ExtensionPort tracked in the ports lookup map
  // given its portId (called from the ExtensionPort destructor when the
  // instance is being released).
  void ForgetReleasedPort(const nsAString& aPortId);

  // nsWrapperCache interface methods
  JSObject* WrapObject(JSContext* aCx,
                       JS::Handle<JSObject*> aGivenProto) override;

  // DOM bindings methods

  nsIGlobalObject* GetParentObject() const;

  ExtensionAlarms* GetExtensionAlarms();
  ExtensionBrowserSettings* GetExtensionBrowserSettings();
  ExtensionDns* GetExtensionDns();
  ExtensionMockAPI* GetExtensionMockAPI();
  ExtensionProxy* GetExtensionProxy();
  ExtensionPublicSuffix* GetExtensionPublicSuffix();
  ExtensionRuntime* GetExtensionRuntime();
  ExtensionScripting* GetExtensionScripting();
  ExtensionTest* GetExtensionTest();

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ExtensionBrowser)

 private:
  ~ExtensionBrowser() = default;

  nsCOMPtr<nsIGlobalObject> mGlobal;
  JS::Heap<JS::Value> mLastError;
  bool mCheckedLastError = false;
  nsTHashMap<nsStringHashKey, WeakPtr<ExtensionPort>> mPortsLookup;
  // `[APINamespace].[APIName]` => int64 (listeners count)
  ExtensionEventWakeupMap mExpectedEventWakeupMap;
  // NOTE: Make sure to don't forget to add for new API namespace instances
  // added to the ones listed below the `NS_IMPL_CYCLE_COLLECTION_UNLINK` and
  // `NS_IMPL_CYCLE_COLLECTION_TRAVERSE` macro calls in ExtensionBrowser.cpp,
  // forgetting it would not result in a build error but it would leak the API
  // namespace instance (and in debug builds the leak is going to hit an
  // assertion failure when `WorkerThreadPrimaryRunnable::Run` calls the
  // assertion `MOZ_ASSERT(!globalScopeAlive)`, after that
  // `nsCycleCollector_shutdown()` has been called and we don't expect anything
  // to be keeping the service worker global scope alive).
  RefPtr<ExtensionAlarms> mExtensionAlarms;
  RefPtr<ExtensionBrowserSettings> mExtensionBrowserSettings;
  RefPtr<ExtensionDns> mExtensionDns;
  RefPtr<ExtensionMockAPI> mExtensionMockAPI;
  RefPtr<ExtensionProxy> mExtensionProxy;
  RefPtr<ExtensionPublicSuffix> mExtensionPublicSuffix;
  RefPtr<ExtensionRuntime> mExtensionRuntime;
  RefPtr<ExtensionScripting> mExtensionScripting;
  RefPtr<ExtensionTest> mExtensionTest;
};

}  // namespace extensions
}  // namespace mozilla

#endif  // mozilla_extensions_ExtensionBrowser_h
