// Copyright 2015 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_EXTENSION_USER_SCRIPT_LOADER_H_
#define EXTENSIONS_BROWSER_EXTENSION_USER_SCRIPT_LOADER_H_

#include <set>

#include "base/memory/raw_ptr.h"
#include "extensions/browser/user_script_loader.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/user_script.h"

namespace content {
class BrowserContext;
}

namespace extensions {

class ContentVerifier;
class StateStore;

// UserScriptLoader for extensions. To support the scripting API, user script
// ids/metadata registered from that API are also stored.
class ExtensionUserScriptLoader : public UserScriptLoader {
 public:
  using DynamicScriptsModifiedCallback =
      base::OnceCallback<void(const std::optional<std::string>& error)>;

  struct PathAndLocaleInfo {
    PathAndLocaleInfo(
        base::FilePath file_path,
        base::Version version,
        std::string default_locale,
        extension_l10n_util::GzippedMessagesPermission gzip_permission);
    PathAndLocaleInfo(const PathAndLocaleInfo& other);
    ~PathAndLocaleInfo();

    base::FilePath file_path;
    base::Version version;
    std::string default_locale;
    extension_l10n_util::GzippedMessagesPermission gzip_permission;
  };

  // Constructs a loader associated with the given `extension`. If the
  // ExtensionSystem is not ready, it will wait for the ExtensionSystem to be
  // ready before loading scripts.
  ExtensionUserScriptLoader(content::BrowserContext* browser_context,
                            const Extension& extension,
                            StateStore* state_store);
  ExtensionUserScriptLoader(content::BrowserContext* browser_context,
                            const Extension& extension,
                            StateStore* state_store,
                            scoped_refptr<ContentVerifier> content_verifier);

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

  ~ExtensionUserScriptLoader() override;

  // Adds `script_ids` into `pending_dynamic_script_ids_` This is called before
  // the scripts the ids belong to are verified to ensure a later call
  // specifying the same script ids will be marked as a conflict.
  void AddPendingDynamicScriptIDs(std::set<std::string> script_ids);

  // Removes `script_ids` from `pending_dynamic_script_ids_`. Should only be
  // called when an API call is about to return with an error before attempting
  // to load its scripts.
  void RemovePendingDynamicScriptIDs(const std::set<std::string>& script_ids);

  // Adds manifest scripts from `extension` and calls GetDynamicScripts for
  // initial dynamic scripts, to the set of scripts managed by this loader. Once
  // `manifest_scripts` are loaded, calls `callback`. Returns whether any
  // scripts will be added for the initial load.
  bool AddScriptsForExtensionLoad(
      const Extension& extension,
      UserScriptLoader::ScriptsLoadedCallback callback);

  // Adds `scripts` to the set of scripts managed by this loader and once these
  // scripts are loaded, calls OnDynamicScriptsAdded, which also calls
  // `callback`.
  void AddDynamicScripts(UserScriptList scripts,
                         std::set<std::string> persistent_script_ids,
                         DynamicScriptsModifiedCallback callback);

  // Removes all dynamic scripts with an id specified in `ids` from
  // `pending_dynamic_script_ids_` and `loaded_dynamic_scripts_`.
  void RemoveDynamicScripts(const std::set<std::string>& ids_to_remove,
                            DynamicScriptsModifiedCallback callback);

  // Removes all dynamic scripts with `source` for the extension, including
  // loaded and pending scripts.
  void ClearDynamicScripts(UserScript::Source source,
                           DynamicScriptsModifiedCallback callback);

  // Updates `scripts` with `script_ids` from the set of scripts managed by this
  // loader, persisting the ones in `persistent_script_ids`. Invokes
  // `add_callback` once scripts are updated.
  void UpdateDynamicScripts(
      UserScriptList scripts,
      std::set<std::string> script_ids,
      std::set<std::string> persistent_script_ids,
      ExtensionUserScriptLoader::DynamicScriptsModifiedCallback add_callback);

  // Sets whether scripts with the given `source` should be enabled and
  // unloads / reloads any scripts with that source as appropriate.
  void SetSourceEnabled(UserScript::Source source, bool enabled);

  // Returns the IDs of all dynamic scripts with `source` for the extension,
  // which includes the IDs of all pending and loaded dynamic scripts.
  // Note: Some of these scripts may be inactive.
  std::set<std::string> GetDynamicScriptIDs(UserScript::Source source) const;

  // Returns the loaded dynamic scripts. Note: Some of these scripts may be
  // inactive.
  const UserScriptList& GetLoadedDynamicScripts() const;

  // Returns the IDs of all the currently-loaded persistent dynamic scripts for
  // the extension. Note that this does not include scripts currently in
  // `pending_dynamic_script_ids_`.
  std::set<std::string> GetPersistentDynamicScriptIDs() const;

  // A wrapper around the method to load user scripts. Waits for the user
  // scripts to load and returns the scripts that were loaded. Exposed only for
  // tests.
  UserScriptList LoadScriptsForTest(UserScriptList user_scripts);

 private:
  // A helper class which handles getting/setting script metadata from the
  // StateStore, and serializing/deserializing between base::Value and
  // UserScript.
  class DynamicScriptsStorageHelper {
   public:
    using DynamicScriptsReadCallback =
        base::OnceCallback<void(UserScriptList scripts)>;

    DynamicScriptsStorageHelper(content::BrowserContext* browser_context,
                                const ExtensionId& extension_id,
                                StateStore* state_store);
    ~DynamicScriptsStorageHelper();
    DynamicScriptsStorageHelper(const DynamicScriptsStorageHelper& other) =
        delete;
    DynamicScriptsStorageHelper& operator=(
        const DynamicScriptsStorageHelper& other) = delete;

    // Retrieves dynamic scripts from the StateStore. Calls
    // OnDynamicScriptsReadFromStorage when done, which then calls `callback`.
    void GetDynamicScripts(DynamicScriptsReadCallback callback);

    // Persists the metadata of the current set of loaded dynamic scripts into
    // the StateStore.
    void SetDynamicScripts(
        const UserScriptList& scripts,
        const std::set<std::string>& persistent_dynamic_script_ids);

   private:
    // Called when dynamic scripts have been retrieved from the StateStore.
    // Deserializes `value` into a UserScriptList and calls `callback` with that
    // list.
    void OnDynamicScriptsReadFromStorage(DynamicScriptsReadCallback callback,
                                         std::optional<base::Value> value);

    raw_ptr<content::BrowserContext> browser_context_;

    ExtensionId extension_id_;

    // A non-owning pointer to the StateStore which contains metadata of
    // persistent dynamic scripts owned by this extension. Outlives this
    // instance and the owning ExtensionUserScriptLoader.
    raw_ptr<StateStore> state_store_;

    base::WeakPtrFactory<DynamicScriptsStorageHelper> weak_factory_{this};
  };

  // UserScriptLoader:
  void LoadScripts(UserScriptList user_scripts,
                   const std::set<std::string>& added_script_ids,
                   LoadScriptsCallback callback) override;

  // Initiates script load when we have been waiting for the extension system
  // to be ready.
  void OnExtensionSystemReady();

  // Called when the extension's initial set of persistent dynamic scripts have
  // been fetched right after the extension has been loaded.
  void OnInitialDynamicScriptsReadFromStateStore(
      UserScriptList scripts,
      UserScriptLoader::ScriptsLoadedCallback callback,
      UserScriptList initial_dynamic_scripts);

  // Called when the extension's initial set of dynamic scripts have been
  // loaded.
  void OnInitialExtensionScriptsLoaded(UserScriptList initial_dynamic_scripts,
                                       ScriptsLoadedCallback callback,
                                       UserScriptLoader* loader,
                                       const std::optional<std::string>& error);

  // Called when the scripts added by AddDynamicScripts have been loaded. Since
  // `added_scripts` corresponds to newly loaded scripts, their IDs are removed
  // from `pending_dynamic_script_ids_` and their metadata added to
  // `loaded_dynamic_scripts_`.
  void OnDynamicScriptsAdded(UserScriptList added_scripts,
                             std::set<std::string> new_persistent_script_ids,
                             DynamicScriptsModifiedCallback callback,
                             UserScriptLoader* loader,
                             const std::optional<std::string>& error);

  // Called when the scripts to be removed in RemoveDynamicScripts are removed.
  // All scripts in `loaded_dynamic_scripts_` with their id in
  // `removed_script_ids` are removed.
  void OnDynamicScriptsRemoved(const std::set<std::string>& removed_script_ids,
                               DynamicScriptsModifiedCallback callback,
                               UserScriptLoader* loader,
                               const std::optional<std::string>& error);

  // Checks if the extension has initial dynamic scripts by checking if the
  // extension has the scripting or user scripts permission, and if URLPatterns
  // from dynamic scripts are registered in prefs.
  bool HasInitialDynamicScripts(const Extension& extension) const;

  // The IDs of dynamically registered scripts (e.g. registered by the
  // extension's API calls) that have not been loaded yet. IDs are removed from
  // the set when:
  //  - Their corresponding scripts have been loaded.
  //  - A load for the IDs has failed.
  //  - A load for the IDs will no longer be initiated.
  //  - An unregisterContentScripts call was made for one or more ids in this
  //    set.
  std::set<std::string> pending_dynamic_script_ids_;

  // The metadata of dynamic scripts from the extension that have been loaded.
  // Note: some of these scripts may be disabled; see `disabled_sources_`.
  UserScriptList loaded_dynamic_scripts_;

  // The set of sources to disallow. Scripts with these sources will still be
  // loaded in this class (in `loaded_dynamic_scripts_`) so that they are still
  // properly stored and persisted when re-writing the database; however, they
  // are not added to any renderers or injected.
  std::set<UserScript::Source> disabled_sources_;

  // The IDs of loaded dynamic scripts that persist across sessions.
  std::set<std::string> persistent_dynamic_script_ids_;

  // Contains info needed for localization for this loader's host.
  PathAndLocaleInfo host_info_;

  DynamicScriptsStorageHelper helper_;

  // Manages content verification of the loaded user scripts.
  scoped_refptr<ContentVerifier> content_verifier_;

  base::WeakPtrFactory<ExtensionUserScriptLoader> weak_factory_{this};
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_EXTENSION_USER_SCRIPT_LOADER_H_
