// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_BROWSER_BACKGROUND_SCRIPT_EXECUTOR_H_
#define EXTENSIONS_BROWSER_BACKGROUND_SCRIPT_EXECUTOR_H_

#include <memory>
#include <optional>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/common/extension_id.h"

namespace content {
class BrowserContext;
}  // namespace content

namespace extensions {
class Extension;
class ExtensionRegistry;
class ProcessManager;
class ScriptResultQueue;

// A helper class to execute a script in an extension's background context,
// either its service worker or its (possibly lazy) background page.
// Returning results:
//  Return results with chrome.test.sendScriptResult(). This can be called
//  either synchronously or asynchronously from the injected script.
//  For compatibility with legacy scripts, background page contexts can choose
//  send results via window.domAutomationController.send(). New code should not
//  do this.
// This class is designed for single-use executions and is only meant to be used
// in tests.
class BackgroundScriptExecutor {
 public:
  // The manner in which the script will use to send the result.
  enum class ResultCapture {
    // No result will be captured. The caller only cares about injecting the
    // script and may wait for another signal of execution.
    kNone,
    // Result sent with chrome.test.sendScriptResult().
    kSendScriptResult,
    // Result sent with window.domAutomationController.send().
    // DON'T USE. This is only here for backwards compatibility with tests that
    // were written before chrome.test.sendScriptResult() exists, and this
    // doesn't work with service worker contexts.
    kWindowDomAutomationController,
  };

  explicit BackgroundScriptExecutor(content::BrowserContext* browser_context);
  ~BackgroundScriptExecutor();

  // Executes the given `script` and waits for execution to complete, returning
  // the result. `script_user_activation` is used to determine whether the
  // script executes with a user gesture, and must be be `kDontActivate` for
  // service worker-based extensions.
  base::Value ExecuteScript(
      const ExtensionId& extension_id,
      const std::string& script,
      ResultCapture result_capture,
      browsertest_util::ScriptUserActivation script_user_activation =
          browsertest_util::ScriptUserActivation::kDontActivate);
  // Static variant of the above.
  static base::Value ExecuteScript(
      content::BrowserContext* browser_context,
      const ExtensionId& extension_id,
      const std::string& script,
      ResultCapture result_capture,
      browsertest_util::ScriptUserActivation script_user_activation =
          browsertest_util::ScriptUserActivation::kDontActivate);

  // Executes the given `script` and returns immediately, without waiting for
  // the script to finish. `script_user_activation` is used to determine
  // whether the script executes with a user gesture, and must be
  // `kDontActivate` for service worker-based extensions.
  bool ExecuteScriptAsync(
      const ExtensionId& extension_id,
      const std::string& script,
      ResultCapture result_capture,
      browsertest_util::ScriptUserActivation script_user_activation =
          browsertest_util::ScriptUserActivation::kDontActivate);
  // Static variant of the above. Inherently, this cannot handle a result
  // (because it is not returned synchronously and there's no exposed instance
  // of BackgroundScriptExecutor).
  static bool ExecuteScriptAsync(
      content::BrowserContext* browser_context,
      const ExtensionId& extension_id,
      const std::string& script,
      browsertest_util::ScriptUserActivation script_user_activation =
          browsertest_util::ScriptUserActivation::kDontActivate);

  // Waits for the result of the script execution; for use with
  // `ExecuteScriptAsync()`.
  base::Value WaitForResult();

 private:
  enum class BackgroundType {
    kServiceWorker,
    kPage,
  };

  // Helper method to execute the script in a service worker context.
  bool ExecuteScriptInServiceWorker();

  // Helper method to execute the script in a background page context.
  bool ExecuteScriptInBackgroundPage(
      browsertest_util::ScriptUserActivation script_user_activation);

  // Method to ADD_FAILURE() to the currently-running test with the given
  // `message` and other debugging info, like the injected script and associated
  // extension.
  void AddTestFailure(const std::string& message);

  // The associated BrowserContext. Must outlive this object.
  const raw_ptr<content::BrowserContext> browser_context_;
  // The associated ExtensionRegistry; tied to `browser_context_`.
  const raw_ptr<ExtensionRegistry> registry_;
  // The associated ProcessManager; tied to `browser_context_`.
  const raw_ptr<ProcessManager> process_manager_;

  // The type of background context the extension uses; lazily instantiated in
  // ExecuteScript*().
  std::optional<BackgroundType> background_type_;

  // The method the script will use to send the result.
  ResultCapture result_capture_method_ = ResultCapture::kNone;

  // The DOMMessageQueue used for retrieving results from background page-based
  // extensions with `ResultCapture::kWindowDomAutomationController`.
  std::unique_ptr<content::DOMMessageQueue> message_queue_;

  // The ScriptResultQueue for retrieving results from contexts using
  // `ResultCapture::kSendScriptResult`.
  std::unique_ptr<ScriptResultQueue> script_result_queue_;

  // The associated Extension.
  raw_ptr<const Extension, FlakyDanglingUntriaged> extension_ = nullptr;

  // The script to inject; cached mostly for logging purposes.
  std::string script_;
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_BACKGROUND_SCRIPT_EXECUTOR_H_