/* 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_dom_serviceworkerop_h_
#define mozilla_dom_serviceworkerop_h_

#include <functional>

#include "ServiceWorkerEvents.h"
#include "ServiceWorkerOpPromise.h"
#include "mozilla/Maybe.h"
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/RemoteWorkerChild.h"
#include "mozilla/dom/RemoteWorkerOp.h"
#include "mozilla/dom/ServiceWorkerOpArgs.h"
#include "mozilla/dom/ServiceWorkerOpPromise.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "nsISupportsImpl.h"

namespace mozilla::dom {

using remoteworker::RemoteWorkerState;

class FetchEventOpProxyChild;

class ServiceWorkerOp : public RemoteWorkerOp {
 public:
  // `aCallback` will be called when the operation completes or is canceled.
  static already_AddRefed<ServiceWorkerOp> Create(
      ServiceWorkerOpArgs&& aArgs,
      std::function<void(const ServiceWorkerOpResult&)>&& aCallback);

  ServiceWorkerOp(
      ServiceWorkerOpArgs&& aArgs,
      std::function<void(const ServiceWorkerOpResult&)>&& aCallback);

  ServiceWorkerOp(const ServiceWorkerOp&) = delete;

  ServiceWorkerOp& operator=(const ServiceWorkerOp&) = delete;

  ServiceWorkerOp(ServiceWorkerOp&&) = default;

  ServiceWorkerOp& operator=(ServiceWorkerOp&&) = default;

  // Returns `true` if the operation has started and `false` otherwise.
  bool MaybeStart(RemoteWorkerChild* aOwner, RemoteWorkerState& aState) final;

  void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final;

  void Start(RemoteWorkerNonLifeCycleOpControllerChild* aOwner,
             RemoteWorkerState& aState) final;

  void Cancel() final;

 protected:
  ~ServiceWorkerOp();

  bool Started() const;

  bool IsTerminationOp() const;

  // Override to provide a runnable that's not a `ServiceWorkerOpRunnable.`
  virtual RefPtr<WorkerThreadRunnable> GetRunnable(
      WorkerPrivate* aWorkerPrivate);

  // Overridden by ServiceWorkerOp subclasses, it should return true when
  // the ServiceWorkerOp was executed successfully (and false if it did fail).
  // Content throwing an exception during event dispatch is still considered
  // success.
  virtual bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) = 0;

  // Override to reject any additional MozPromises that subclasses may contain.
  virtual void RejectAll(nsresult aStatus);

  ServiceWorkerOpArgs mArgs;

  // Subclasses must settle this promise when appropriate.
  MozPromiseHolder<ServiceWorkerOpPromise> mPromiseHolder;

 private:
  class ServiceWorkerOpRunnable;

  bool mStarted = false;
};

class ExtendableEventOp : public ServiceWorkerOp,
                          public ExtendableEventCallback {
  using ServiceWorkerOp::ServiceWorkerOp;

 protected:
  ~ExtendableEventOp() = default;

  void FinishedWithResult(ExtendableEventResult aResult) override;
};

class FetchEventOp final : public ExtendableEventOp,
                           public PromiseNativeHandler {
  using ExtendableEventOp::ExtendableEventOp;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  /**
   * This must be called once and only once before the first call to
   * `MaybeStart()`; `aActor` will be used for `AsyncLog()` and
   * `ReportCanceled().`
   */
  void SetActor(RefPtr<FetchEventOpProxyChild> aActor);

  void RevokeActor(FetchEventOpProxyChild* aActor);

  // This must be called at most once before the first call to `MaybeStart().`
  RefPtr<FetchEventRespondWithPromise> GetRespondWithPromise();

  // This must be called when `FetchEvent::RespondWith()` is called.
  void RespondWithCalledAt(const nsCString& aRespondWithScriptSpec,
                           uint32_t aRespondWithLineNumber,
                           uint32_t aRespondWithColumnNumber);

  void ReportCanceled(const nsCString& aPreventDefaultScriptSpec,
                      uint32_t aPreventDefaultLineNumber,
                      uint32_t aPreventDefaultColumnNumber);

 private:
  class AutoCancel;

  ~FetchEventOp();

  bool Exec(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;

  void RejectAll(nsresult aStatus) override;

  void FinishedWithResult(ExtendableEventResult aResult) override;

  /**
   * `{Resolved,Reject}Callback()` are use to handle the
   * `FetchEvent::RespondWith()` promise.
   */
  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
                        ErrorResult& aRv) override;

  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
                        ErrorResult& aRv) override;

  void MaybeFinished();

  // Requires mRespondWithClosure to be non-empty.
  void AsyncLog(const nsCString& aMessageName, nsTArray<nsString> aParams);

  void AsyncLog(const nsCString& aScriptSpec, uint32_t aLineNumber,
                uint32_t aColumnNumber, const nsCString& aMessageName,
                nsTArray<nsString> aParams);

  void GetRequestURL(nsAString& aOutRequestURL);

  // A failure code means that the dispatch failed.
  nsresult DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);

  // Worker Launcher thread only. Used for `AsyncLog().`
  RefPtr<FetchEventOpProxyChild> mActor;

  /**
   * Created on the Worker Launcher thread and settled on the worker thread.
   * If this isn't settled before `mPromiseHolder` (which it should be),
   * `FetchEventOpChild` will cancel the intercepted network request.
   */
  MozPromiseHolder<FetchEventRespondWithPromise> mRespondWithPromiseHolder;

  // Worker thread only.
  Maybe<ExtendableEventResult> mResult;
  bool mPostDispatchChecksDone = false;

  // Worker thread only; set when `FetchEvent::RespondWith()` is called.
  Maybe<FetchEventRespondWithClosure> mRespondWithClosure;

  // Must be set to `nullptr` on the worker thread because `Promise`'s
  // destructor must be called on the worker thread.
  RefPtr<Promise> mHandled;

  // Must be set to `nullptr` on the worker thread because `Promise`'s
  // destructor must be called on the worker thread.
  RefPtr<Promise> mPreloadResponse;

  // Holds the callback that resolves mPreloadResponse.
  MozPromiseRequestHolder<FetchEventPreloadResponseAvailablePromise>
      mPreloadResponseAvailablePromiseRequestHolder;
  MozPromiseRequestHolder<FetchEventPreloadResponseTimingPromise>
      mPreloadResponseTimingPromiseRequestHolder;
  MozPromiseRequestHolder<FetchEventPreloadResponseEndPromise>
      mPreloadResponseEndPromiseRequestHolder;

  TimeStamp mFetchHandlerStart;
  TimeStamp mFetchHandlerFinish;
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_serviceworkerop_h_
