// Copyright 2024 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_MANIFEST_V2_HANDLER_H_
#define EXTENSIONS_BROWSER_MANIFEST_V2_HANDLER_H_

#include "base/auto_reset.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/types/pass_key.h"
#include "components/keyed_service/core/keyed_service.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/pref_types.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension_id.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class BrowserContextKeyedServiceFactory;

namespace content {
class BrowserContext;
}  // namespace content

namespace extensions {
class Extension;
class ExtensionPrefs;
class ScopedTestMV2Enabler;

// The central class responsible for managing deprecated MV2 extensions.
class ManifestV2Handler : public KeyedService,
                          public ExtensionRegistryObserver {
 public:
  explicit ManifestV2Handler(content::BrowserContext* browser_context);
  ManifestV2Handler(const ManifestV2Handler&) = delete;
  ManifestV2Handler& operator=(const ManifestV2Handler&) = delete;
  ~ManifestV2Handler() override;

  // The possible states for an MV2 extension.
  // Do not re-order entries, as these are used in histograms.
  // Exposed for testing purposes.
  enum class MV2ExtensionState {
    // Extension is unaffected by the MV2 deprecation (e.g., it's a policy-
    // installed extension with the proper enterprise policies set).
    kUnaffected = 0,
    // The extension was disabled by Chrome, but may be re-enabled by the user.
    kSoftDisabled = 1,
    // The extension was previously disabled by Chrome, but was re-enabled by
    // the user.
    kUserReEnabled = 2,
    // Any other state. This includes e.g. extensions that are disabled, but for
    // other reasons.
    kOther = 3,
    // The extension is disabled, and may not be re-enabled by the user.
    kHardDisabled = 4,
    kMaxValue = kHardDisabled,
  };

  // Stores whether the user has acknowledged the MV2 deprecation notice for the
  // unsupported stage globally.
  static const PrefMap kMV2UnsupportedAcknowledgedGloballyPref;

  // Retrieves the ManifestV2Handler associated with the given
  // `browser_context`. Note this instance is shared between on- and off-the-
  // record contexts.
  static ManifestV2Handler* Get(content::BrowserContext* browser_context);

  // Returns the singleton instance of the factory for this KeyedService.
  static BrowserContextKeyedServiceFactory* GetFactory();

  // Returns true if the given `extension` is affected by the MV2 deprecation.
  // This may be false if, e.g., the extension is policy-installed.
  bool IsExtensionAffected(const Extension& extension);

  // Returns true if a new installation with the given `manifest_version`,
  // `manifest_type`, and `manifest_location` should be blocked.
  bool ShouldBlockExtensionInstallation(
      int manifest_version,
      Manifest::Type manifest_type,
      mojom::ManifestLocation manifest_location);

  // Returns true if Chrome should disallow enabling the given `extension`.
  bool ShouldBlockExtensionEnable(const Extension& extension);

  // Returns true if the user has acknowledged the notice about deprecated
  // extensions.
  bool DidUserAcknowledgeNoticeGlobally();

  // Called to indicate the user acknowledged the notice about deprecated
  // extensions.
  void MarkNoticeAsAcknowledgedGlobally();

  // Helpers to call internal methods directly for testing purposes. These are
  // useful to have an extension that's installed in the body of a test get
  // disabled, since this normally only happens on startup.
  void DisableAffectedExtensionsForTesting();
  void EmitMetricsForProfileReadyForTesting();

  // See ScopedTestMV2Enabler for details.
  static base::AutoReset<bool> AllowMV2ExtensionsForTesting(
      base::PassKey<ScopedTestMV2Enabler> pass_key);

 private:
  // Lazily initialize and access `extension_prefs_`. We do this lazily because:
  // - This service is created on Profile creation.
  // - A bunch of unit tests override ExtensionPrefs after Profile creation, but
  //   before the "real" test starts.
  // As such, if we instantiated ExtensionPrefs in the constructor, it would be
  // the improper ExtensionPrefs object and would trigger raw_ptr violations.
  ExtensionPrefs* extension_prefs();

  // Called when the extension system has finished its initialization steps.
  void OnExtensionSystemReady();

  // Disables any Manifest V2 extensions that are in-scope for the handler.
  void DisableAffectedExtensions();

  // Loops through disabled extensions and checks if any should be re-enabled.
  void CheckDisabledExtensions();

  // Re-enables the `extension` if it should no longer be disabled by the MV2
  // deprecation (e.g., if it updated to MV3).
  void MaybeReEnableExtension(const Extension& extension);

  // Emits metrics about the state of installed extensions related to the
  // MV2 deprecation.
  void EmitMetricsForProfileReady();

  // ExtensionRegistry:
  void OnExtensionInstalled(content::BrowserContext* browser_context,
                            const Extension* extension,
                            bool is_update) override;

  // The associated ExtensionPrefs. Guaranteed to be safe to use since this
  // class depends upon them via the KeyedService infrastructure.
  raw_ptr<ExtensionPrefs> extension_prefs_;

  // The associated BrowserContext. Guaranteed to be safe to use since this is
  // a KeyedService for the context.
  raw_ptr<content::BrowserContext> browser_context_;

  base::ScopedObservation<ExtensionRegistry, ExtensionRegistryObserver>
      registry_observation_{this};

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

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_MANIFEST_V2_HANDLER_H_
