| // 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. | 
 |  | 
 | #include "extensions/browser/user_script_world_configuration_manager.h" | 
 |  | 
 | #include "components/keyed_service/content/browser_context_dependency_manager.h" | 
 | #include "components/keyed_service/content/browser_context_keyed_service_factory.h" | 
 | #include "extensions/browser/extension_prefs.h" | 
 | #include "extensions/browser/extension_prefs_factory.h" | 
 | #include "extensions/browser/extension_registry_factory.h" | 
 | #include "extensions/browser/extensions_browser_client.h" | 
 | #include "extensions/browser/renderer_startup_helper.h" | 
 |  | 
 | namespace extensions { | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr char kDefaultUserScriptWorldKey[] = "_default"; | 
 | constexpr char kUserScriptWorldMessagingKey[] = "messaging"; | 
 | constexpr char kUserScriptWorldCspKey[] = "csp"; | 
 |  | 
 | class UserScriptWorldConfigurationManagerFactory | 
 |     : public BrowserContextKeyedServiceFactory { | 
 |  public: | 
 |   UserScriptWorldConfigurationManagerFactory() | 
 |       : BrowserContextKeyedServiceFactory( | 
 |             "UserScriptWorldConfigurationManager", | 
 |             BrowserContextDependencyManager::GetInstance()) { | 
 |     DependsOn(ExtensionPrefsFactory::GetInstance()); | 
 |     DependsOn(ExtensionRegistryFactory::GetInstance()); | 
 |     DependsOn(RendererStartupHelperFactory::GetInstance()); | 
 |   } | 
 |  | 
 |   UserScriptWorldConfigurationManagerFactory( | 
 |       const UserScriptWorldConfigurationManagerFactory&) = delete; | 
 |   UserScriptWorldConfigurationManagerFactory& operator=( | 
 |       const UserScriptWorldConfigurationManagerFactory&) = delete; | 
 |   ~UserScriptWorldConfigurationManagerFactory() override = default; | 
 |  | 
 |   UserScriptWorldConfigurationManager* GetForBrowserContext( | 
 |       content::BrowserContext* context) { | 
 |     return static_cast<UserScriptWorldConfigurationManager*>( | 
 |         GetServiceForBrowserContext(context, /*create=*/true)); | 
 |   } | 
 |  | 
 |  private: | 
 |   // BrowserContextKeyedServiceFactory: | 
 |   content::BrowserContext* GetBrowserContextToUse( | 
 |       content::BrowserContext* context) const override { | 
 |     // TODO(devlin): I wonder if it would make sense for this to have its own | 
 |     // instance in incognito. That would allow split-mode extensions to have | 
 |     // incognito-only world specifications and have them cleaned up when the | 
 |     // profile is destroyed. | 
 |     return ExtensionsBrowserClient::Get()->GetContextRedirectedToOriginal( | 
 |         context); | 
 |   } | 
 |   std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext( | 
 |       content::BrowserContext* context) const override { | 
 |     return std::make_unique<UserScriptWorldConfigurationManager>(context); | 
 |   } | 
 | }; | 
 |  | 
 | // Returns the key entry in the user script world dictionary to use for the | 
 | // provided `world_id`. | 
 | std::string_view GetUserScriptWorldKeyForWorldId( | 
 |     const std::optional<std::string>& world_id) { | 
 |   return world_id ? world_id->c_str() : kDefaultUserScriptWorldKey; | 
 | } | 
 |  | 
 | // Attempts to parse the given `dict` into a mojom::UserScriptWorldInfo. If | 
 | // `dict` is null, returns a default specification for a | 
 | // mojom::UserScriptWorldInfo. | 
 | mojom::UserScriptWorldInfoPtr ParseUserScriptWorldInfo( | 
 |     const ExtensionId& extension_id, | 
 |     const std::optional<std::string>& world_id, | 
 |     const base::Value::Dict* dict) { | 
 |   bool enable_messaging = false; | 
 |   std::optional<std::string> csp; | 
 |   if (dict) { | 
 |     enable_messaging = | 
 |         dict->FindBool(kUserScriptWorldMessagingKey).value_or(false); | 
 |     const std::string* csp_pref = dict->FindString(kUserScriptWorldCspKey); | 
 |     csp = csp_pref ? std::make_optional(*csp_pref) : std::nullopt; | 
 |   } | 
 |  | 
 |   return mojom::UserScriptWorldInfo::New(extension_id, world_id, csp, | 
 |                                          enable_messaging); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | UserScriptWorldConfigurationManager::UserScriptWorldConfigurationManager( | 
 |     content::BrowserContext* browser_context) | 
 |     : extension_prefs_(ExtensionPrefs::Get(browser_context)), | 
 |       renderer_helper_( | 
 |           RendererStartupHelperFactory::GetForBrowserContext(browser_context)) { | 
 |   registry_observation_.Observe(ExtensionRegistry::Get(browser_context)); | 
 | } | 
 |  | 
 | UserScriptWorldConfigurationManager::~UserScriptWorldConfigurationManager() = | 
 |     default; | 
 |  | 
 | void UserScriptWorldConfigurationManager::SetUserScriptWorldInfo( | 
 |     const Extension& extension, | 
 |     mojom::UserScriptWorldInfoPtr world_info) { | 
 |   CHECK(!world_info->world_id || !world_info->world_id->empty()); | 
 |   // Persist world configuratation in ExtensionPrefs. | 
 |   ExtensionPrefs::ScopedDictionaryUpdate update( | 
 |       extension_prefs_, extension.id(), kUserScriptsWorldsConfiguration.name); | 
 |   std::unique_ptr<prefs::DictionaryValueUpdate> update_dict = update.Get(); | 
 |   if (!update_dict) { | 
 |     update_dict = update.Create(); | 
 |   } | 
 |  | 
 |   base::Value::Dict world_info_dict; | 
 |   world_info_dict.Set(kUserScriptWorldMessagingKey, | 
 |                       world_info->enable_messaging); | 
 |   if (world_info->csp.has_value()) { | 
 |     world_info_dict.Set(kUserScriptWorldCspKey, *world_info->csp); | 
 |   } | 
 |  | 
 |   update_dict->SetKey(GetUserScriptWorldKeyForWorldId(world_info->world_id), | 
 |                       base::Value(std::move(world_info_dict))); | 
 |  | 
 |   renderer_helper_->SetUserScriptWorldProperties(extension, | 
 |                                                  std::move(world_info)); | 
 | } | 
 |  | 
 | void UserScriptWorldConfigurationManager::ClearUserScriptWorldInfo( | 
 |     const Extension& extension, | 
 |     const std::optional<std::string>& world_id) { | 
 |   CHECK(!world_id || !world_id->empty()); | 
 |   ExtensionPrefs::ScopedDictionaryUpdate update( | 
 |       extension_prefs_, extension.id(), kUserScriptsWorldsConfiguration.name); | 
 |   std::unique_ptr<prefs::DictionaryValueUpdate> update_dict = update.Get(); | 
 |  | 
 |   if (!update_dict) { | 
 |     return;  // No configs. | 
 |   } | 
 |  | 
 |   std::string_view world_key = GetUserScriptWorldKeyForWorldId(world_id); | 
 |   if (!update_dict->HasKey(world_key)) { | 
 |     return;  // No config for this world ID. | 
 |   } | 
 |  | 
 |   update_dict->Remove(world_key); | 
 |   renderer_helper_->ClearUserScriptWorldProperties(extension, world_id); | 
 | } | 
 |  | 
 | mojom::UserScriptWorldInfoPtr | 
 | UserScriptWorldConfigurationManager::GetUserScriptWorldInfo( | 
 |     const ExtensionId& extension_id, | 
 |     const std::optional<std::string>& world_id) { | 
 |   CHECK(!world_id || !world_id->empty()); | 
 |   const base::Value::Dict* worlds_configuration = | 
 |       extension_prefs_->ReadPrefAsDictionary(extension_id, | 
 |                                              kUserScriptsWorldsConfiguration); | 
 |   const base::Value::Dict* world_info = | 
 |       worlds_configuration ? worlds_configuration->FindDict( | 
 |                                  GetUserScriptWorldKeyForWorldId(world_id)) | 
 |                            : nullptr; | 
 |  | 
 |   return ParseUserScriptWorldInfo(extension_id, world_id, world_info); | 
 | } | 
 |  | 
 | std::vector<mojom::UserScriptWorldInfoPtr> | 
 | UserScriptWorldConfigurationManager::GetAllUserScriptWorlds( | 
 |     const ExtensionId& extension_id) { | 
 |   const base::Value::Dict* worlds_configuration = | 
 |       extension_prefs_->ReadPrefAsDictionary(extension_id, | 
 |                                              kUserScriptsWorldsConfiguration); | 
 |   if (!worlds_configuration) { | 
 |     return {}; | 
 |   } | 
 |  | 
 |   std::vector<mojom::UserScriptWorldInfoPtr> result; | 
 |   for (auto [world_id_key, world_value] : *worlds_configuration) { | 
 |     if (world_id_key.length() < 1) { | 
 |       continue;  // Invalid key. Ignore. | 
 |     } | 
 |  | 
 |     if (!world_value.is_dict()) { | 
 |       continue;  // Invalid value. Ignore. | 
 |     } | 
 |  | 
 |     std::optional<std::string> world_id; | 
 |     if (world_id_key[0] == '_') { | 
 |       if (world_id_key != kDefaultUserScriptWorldKey) { | 
 |         continue;  // Invalid key. Ignore. | 
 |       } | 
 |     } else { | 
 |       // Otherwise, the world ID is the key in the dictionary. | 
 |       world_id = world_id_key; | 
 |     } | 
 |  | 
 |     mojom::UserScriptWorldInfoPtr parsed_world = ParseUserScriptWorldInfo( | 
 |         extension_id, world_id, &world_value.GetDict()); | 
 |     if (!parsed_world) { | 
 |       continue;  // Failed to parse. Ignore. | 
 |     } | 
 |  | 
 |     result.push_back(std::move(parsed_world)); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | // static | 
 | BrowserContextKeyedServiceFactory& | 
 | UserScriptWorldConfigurationManager::GetFactory() { | 
 |   static base::NoDestructor<UserScriptWorldConfigurationManagerFactory> | 
 |       g_factory; | 
 |   return *g_factory; | 
 | } | 
 |  | 
 | // static | 
 | UserScriptWorldConfigurationManager* UserScriptWorldConfigurationManager::Get( | 
 |     content::BrowserContext* browser_context) { | 
 |   auto& factory = | 
 |       static_cast<UserScriptWorldConfigurationManagerFactory&>(GetFactory()); | 
 |   return factory.GetForBrowserContext(browser_context); | 
 | } | 
 |  | 
 | void UserScriptWorldConfigurationManager::OnExtensionWillBeInstalled( | 
 |     content::BrowserContext* browser_context, | 
 |     const Extension* extension, | 
 |     bool is_update, | 
 |     const std::string& old_name) { | 
 |   if (!is_update) { | 
 |     return; | 
 |   } | 
 |  | 
 |   extension_prefs_->UpdateExtensionPref( | 
 |       extension->id(), kUserScriptsWorldsConfiguration.name, std::nullopt); | 
 | } | 
 |  | 
 | }  // namespace extensions |