diff --git a/chromium_patches/chrome/browser/about_flags.cc b/chromium_patches/chrome/browser/about_flags.cc new file mode 100644 index 000000000..c594f4b30 --- /dev/null +++ b/chromium_patches/chrome/browser/about_flags.cc @@ -0,0 +1,16 @@ +diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc +index e80a06d6cb742..967bee782659a 100644 +--- a/chrome/browser/about_flags.cc ++++ b/chrome/browser/about_flags.cc +@@ -12068,6 +12068,11 @@ const FeatureEntry kFeatureEntries[] = { + {"bookmarks-tree-view", flag_descriptions::kBookmarksTreeViewName, + flag_descriptions::kBookmarksTreeViewDescription, kOsDesktop, + FEATURE_VALUE_TYPE(features::kBookmarksTreeView)}, ++ ++ {"enable-browseros-alpha-features", ++ flag_descriptions::kBrowserOsAlphaFeaturesName, ++ flag_descriptions::kBrowserOsAlphaFeaturesDescription, kOsDesktop, ++ FEATURE_VALUE_TYPE(features::kBrowserOsAlphaFeatures)}, + #endif + + {"enable-secure-payment-confirmation-availability-api", diff --git a/chromium_patches/chrome/browser/browser_features.cc b/chromium_patches/chrome/browser/browser_features.cc new file mode 100644 index 000000000..dc0755e49 --- /dev/null +++ b/chromium_patches/chrome/browser/browser_features.cc @@ -0,0 +1,14 @@ +diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc +index ed397bd22e079..8bdc7bcb8e779 100644 +--- a/chrome/browser/browser_features.cc ++++ b/chrome/browser/browser_features.cc +@@ -41,6 +41,9 @@ BASE_FEATURE(kBookmarkTriggerForPreconnect, base::FEATURE_DISABLED_BY_DEFAULT); + // crbug.com/413259638 for more details of Bookmark triggered prefetching. + BASE_FEATURE(kBookmarkTriggerForPrefetch, base::FEATURE_DISABLED_BY_DEFAULT); + ++// Enables BrowserOS alpha features. ++BASE_FEATURE(kBrowserOsAlphaFeatures, base::FEATURE_DISABLED_BY_DEFAULT); ++ + // Enables Certificate Transparency on Desktop and Android Browser (CT is + // disabled in Android Webview, see aw_browser_context.cc). + // Enabling CT enforcement requires maintaining a log policy, and the ability to diff --git a/chromium_patches/chrome/browser/browser_features.h b/chromium_patches/chrome/browser/browser_features.h new file mode 100644 index 000000000..bce775c09 --- /dev/null +++ b/chromium_patches/chrome/browser/browser_features.h @@ -0,0 +1,12 @@ +diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h +index 5737721a47154..67422b04809b1 100644 +--- a/chrome/browser/browser_features.h ++++ b/chrome/browser/browser_features.h +@@ -35,6 +35,7 @@ BASE_DECLARE_FEATURE(kAllowUnmutedAutoplayForTWA); + BASE_DECLARE_FEATURE(kAutocompleteActionPredictorConfidenceCutoff); + BASE_DECLARE_FEATURE(kBookmarksTreeView); + BASE_DECLARE_FEATURE(kBookmarkTriggerForPrerender2KillSwitch); ++BASE_DECLARE_FEATURE(kBrowserOsAlphaFeatures); + BASE_DECLARE_FEATURE(kBookmarkTriggerForPreconnect); + BASE_DECLARE_FEATURE(kBookmarkTriggerForPrefetch); + BASE_DECLARE_FEATURE(kCertificateTransparencyAskBeforeEnabling); diff --git a/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h b/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h new file mode 100644 index 000000000..c72ead8f3 --- /dev/null +++ b/chromium_patches/chrome/browser/extensions/browseros_extension_constants.h @@ -0,0 +1,126 @@ +diff --git a/chrome/browser/extensions/browseros_extension_constants.h b/chrome/browser/extensions/browseros_extension_constants.h +new file mode 100644 +index 0000000000000..5a3b518b224a7 +--- /dev/null ++++ b/chrome/browser/extensions/browseros_extension_constants.h +@@ -0,0 +1,120 @@ ++// 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 CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_ ++#define CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_ ++ ++#include ++#include ++#include ++#include ++ ++namespace extensions { ++namespace browseros { ++ ++// AI Agent Extension ID ++inline constexpr char kAISidePanelExtensionId[] = ++ "djhdjhlnljbjgejbndockeedocneiaei"; ++ ++// Agent V2 Extension ID ++inline constexpr char kAgentV2ExtensionId[] = ++ "bflpfmnmnokmjhmgnolecpppdbdophmk"; ++ ++// BrowserOS extension config URLs ++inline constexpr char kBrowserOSConfigUrl[] = ++ "https://cdn.browseros.com/extensions/extensions.json"; ++inline constexpr char kBrowserOSAlphaConfigUrl[] = ++ "https://cdn.browseros.com/extensions/extensions.alpha.json"; ++ ++// Bug Reporter Extension ID ++inline constexpr char kBugReporterExtensionId[] = ++ "adlpneommgkgeanpaekgoaolcpncohkf"; ++ ++// Controller Extension ID ++inline constexpr char kControllerExtensionId[] = ++ "nlnihljpboknmfagkikhkdblbedophja"; ++ ++// BrowserOS CDN update manifest URL ++// Used for extensions installed from local .crx files that don't have ++// an update_url in their manifest ++inline constexpr char kBrowserOSUpdateUrl[] = ++ "https://cdn.browseros.com/extensions/update-manifest.xml"; ++ ++struct BrowserOSExtensionInfo { ++ const char* id; ++ const char* display_name; ++ bool is_pinned; ++ bool is_labelled; ++}; ++ ++inline constexpr BrowserOSExtensionInfo kBrowserOSExtensions[] = { ++ {kAISidePanelExtensionId, "BrowserOS", true, true}, ++ {kBugReporterExtensionId, "BrowserOS/bug-reporter", true, false}, ++ {kControllerExtensionId, "BrowserOS/controller", false, false}, ++ {kAgentV2ExtensionId, "BrowserOS", true, true}, ++}; ++ ++// Allowlist of BrowserOS extension IDs that are permitted to be installed. ++// Only extensions with these IDs will be loaded from the config. ++inline constexpr const char* kAllowedExtensions[] = { ++ kBrowserOSExtensions[0].id, ++ kBrowserOSExtensions[1].id, ++ kBrowserOSExtensions[2].id, ++ kBrowserOSExtensions[3].id, ++}; ++ ++inline constexpr size_t kBrowserOSExtensionsCount = ++ sizeof(kBrowserOSExtensions) / sizeof(kBrowserOSExtensions[0]); ++ ++inline const BrowserOSExtensionInfo* FindBrowserOSExtensionInfo( ++ const std::string& extension_id) { ++ for (const auto& info : kBrowserOSExtensions) { ++ if (extension_id == info.id) ++ return &info; ++ } ++ return nullptr; ++} ++ ++// Check if an extension is a BrowserOS extension ++inline bool IsBrowserOSExtension(const std::string& extension_id) { ++ return FindBrowserOSExtensionInfo(extension_id) != nullptr; ++} ++ ++inline bool IsBrowserOSPinnedExtension(const std::string& extension_id) { ++ const BrowserOSExtensionInfo* info = ++ FindBrowserOSExtensionInfo(extension_id); ++ return info && info->is_pinned; ++} ++ ++inline bool IsBrowserOSLabelledExtension(const std::string& extension_id) { ++ const BrowserOSExtensionInfo* info = ++ FindBrowserOSExtensionInfo(extension_id); ++ return info && info->is_labelled; ++} ++ ++// Get all BrowserOS extension IDs ++inline std::vector GetBrowserOSExtensionIds() { ++ std::vector ids; ++ ids.reserve(kBrowserOSExtensionsCount); ++ for (const auto& info : kBrowserOSExtensions) ++ ids.push_back(info.id); ++ return ids; ++} ++ ++// Get display name for BrowserOS extensions in omnibox ++// Returns the display name if extension_id is a BrowserOS extension, ++// otherwise returns std::nullopt ++inline std::optional GetExtensionDisplayName( ++ const std::string& extension_id) { ++ if (const BrowserOSExtensionInfo* info = ++ FindBrowserOSExtensionInfo(extension_id)) { ++ return info->display_name; ++ } ++ return std::nullopt; ++} ++ ++} // namespace browseros ++} // namespace extensions ++ ++#endif // CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTENSION_CONSTANTS_H_ diff --git a/chromium_patches/chrome/browser/extensions/browseros_external_loader.cc b/chromium_patches/chrome/browser/extensions/browseros_external_loader.cc new file mode 100644 index 000000000..6f23041f4 --- /dev/null +++ b/chromium_patches/chrome/browser/extensions/browseros_external_loader.cc @@ -0,0 +1,763 @@ +diff --git a/chrome/browser/extensions/browseros_external_loader.cc b/chrome/browser/extensions/browseros_external_loader.cc +new file mode 100644 +index 0000000000000..8f01037dfcb90 +--- /dev/null ++++ b/chrome/browser/extensions/browseros_external_loader.cc +@@ -0,0 +1,757 @@ ++// 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 "chrome/browser/extensions/browseros_external_loader.h" ++ ++#include ++#include ++ ++#include "base/feature_list.h" ++#include "base/files/file_util.h" ++#include "base/functional/bind.h" ++#include "base/json/json_reader.h" ++#include "base/logging.h" ++#include "base/memory/ptr_util.h" ++#include "base/strings/string_util.h" ++#include "base/task/thread_pool.h" ++#include "base/task/single_thread_task_runner.h" ++#include "base/values.h" ++#include "chrome/browser/browser_features.h" ++#include "chrome/browser/browser_process.h" ++#include "chrome/browser/extensions/browseros_extension_constants.h" ++#include "chrome/browser/extensions/extension_service.h" ++#include "chrome/browser/extensions/external_provider_impl.h" ++#include "chrome/browser/extensions/updater/extension_updater.h" ++#include "chrome/browser/profiles/profile.h" ++#include "components/metrics/browseros_metrics/browseros_metrics.h" ++#include "content/public/browser/browser_context.h" ++#include "content/public/browser/storage_partition.h" ++#include "extensions/browser/disable_reason.h" ++#include "extensions/browser/extension_prefs.h" ++#include "extensions/browser/extension_registrar.h" ++#include "extensions/browser/extension_registry.h" ++#include "extensions/browser/extension_system.h" ++#include "extensions/browser/pending_extension_manager.h" ++#include "extensions/common/extension.h" ++#include "extensions/common/mojom/manifest.mojom-shared.h" ++#include "net/base/load_flags.h" ++#include "net/traffic_annotation/network_traffic_annotation.h" ++#include "services/network/public/cpp/resource_request.h" ++#include "services/network/public/cpp/simple_url_loader.h" ++#include "services/network/public/mojom/url_response_head.mojom.h" ++ ++namespace extensions { ++ ++namespace { ++ ++// Interval for periodic maintenance ++constexpr base::TimeDelta kPeriodicMaintenanceInterval = base::Minutes(15); ++ ++// Network traffic annotation for the extension configuration fetch. ++constexpr net::NetworkTrafficAnnotationTag kBrowserOSExtensionsFetchTrafficAnnotation = ++ net::DefineNetworkTrafficAnnotation("browseros_extensions_fetch", R"( ++ semantics { ++ sender: "BrowserOS External Extension Loader" ++ description: ++ "Fetches a JSON configuration file that specifies which extensions " ++ "should be installed for BrowserOS users at startup." ++ trigger: ++ "Triggered during browser startup when BrowserOS mode is enabled." ++ data: ++ "No user data is sent. Only a GET request to fetch the configuration." ++ destination: OTHER ++ destination_other: ++ "The BrowserOS configuration server specified by the config URL." ++ } ++ policy { ++ cookies_allowed: NO ++ setting: ++ "This feature can be controlled via command-line flags or " ++ "enterprise policies." ++ policy_exception_justification: ++ "Not implemented yet. This is a new feature for BrowserOS." ++ })"); ++ ++// Example JSON format: ++// { ++// "extensions": { ++// "extension_id_1": { ++// "external_update_url": "https://example.com/extension1/updates.xml" ++// }, ++// "extension_id_2": { ++// "external_crx": "https://example.com/extension2.crx", ++// "external_version": "1.0" ++// } ++// } ++// } ++ ++// Determines if a BrowserOS extension should be enabled based on the ++// kBrowserOsAlphaFeatures flag state. ++// - kAgentV2ExtensionId: enabled only when flag is ON ++// - kAISidePanelExtensionId: enabled only when flag is OFF ++// - All other extensions: always enabled ++bool ShouldExtensionBeEnabled(const std::string& extension_id) { ++ bool alpha_features_enabled = ++ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures); ++ ++ if (extension_id == browseros::kAgentV2ExtensionId) { ++ return alpha_features_enabled; ++ } ++ ++ if (extension_id == browseros::kAISidePanelExtensionId) { ++ return !alpha_features_enabled; ++ } ++ ++ // All other BrowserOS extensions are always enabled ++ return true; ++} ++ ++// Returns the appropriate config URL based on the alpha features flag. ++const char* GetConfigUrl() { ++ if (base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures)) { ++ return browseros::kBrowserOSAlphaConfigUrl; ++ } ++ return browseros::kBrowserOSConfigUrl; ++} ++ ++} // namespace ++ ++BrowserOSExternalLoader::BrowserOSExternalLoader(Profile* profile) ++ : profile_(profile) { ++ // Select config URL based on alpha features flag ++ config_url_ = GURL(GetConfigUrl()); ++ ++ // Add known BrowserOS extension IDs ++ for (const char* extension_id : browseros::kAllowedExtensions) { ++ browseros_extension_ids_.insert(extension_id); ++ } ++} ++ ++BrowserOSExternalLoader::~BrowserOSExternalLoader() = default; ++ ++void BrowserOSExternalLoader::StartLoading() { ++ LOG(INFO) << "BrowserOS external extension loader starting..."; ++ ++ if (!config_file_for_testing_.empty()) { ++ LoadFromFile(); ++ return; ++ } ++ ++ if (!config_url_.is_valid()) { ++ LOG(ERROR) << "Invalid BrowserOS extensions config URL"; ++ LoadFinished(base::Value::Dict()); ++ return; ++ } ++ ++ LOG(INFO) << "Fetching BrowserOS extensions from: " << config_url_.spec(); ++ ++ // Create the URL loader factory ++ url_loader_factory_ = profile_->GetDefaultStoragePartition() ++ ->GetURLLoaderFactoryForBrowserProcess(); ++ ++ // Create the resource request ++ auto resource_request = std::make_unique(); ++ resource_request->url = config_url_; ++ resource_request->method = "GET"; ++ resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE; ++ ++ // Create the URL loader ++ url_loader_ = network::SimpleURLLoader::Create( ++ std::move(resource_request), kBrowserOSExtensionsFetchTrafficAnnotation); ++ ++ // Start the download ++ url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( ++ url_loader_factory_.get(), ++ base::BindOnce(&BrowserOSExternalLoader::OnURLFetchComplete, ++ weak_ptr_factory_.GetWeakPtr())); ++} ++ ++void BrowserOSExternalLoader::OnURLFetchComplete( ++ std::unique_ptr response_body) { ++ if (!response_body) { ++ LOG(ERROR) << "Failed to fetch BrowserOS extensions config from " ++ << config_url_.spec(); ++ LoadFinished(base::Value::Dict()); ++ return; ++ } ++ ++ ParseConfiguration(*response_body); ++} ++ ++void BrowserOSExternalLoader::ParseConfiguration( ++ const std::string& json_content) { ++ std::optional parsed_json = base::JSONReader::Read(json_content); ++ ++ if (!parsed_json || !parsed_json->is_dict()) { ++ LOG(ERROR) << "Failed to parse BrowserOS extensions config JSON"; ++ LoadFinished(base::Value::Dict()); ++ return; ++ } ++ ++ const base::Value::Dict* extensions_dict = ++ parsed_json->GetDict().FindDict("extensions"); ++ ++ if (!extensions_dict) { ++ LOG(ERROR) << "No 'extensions' key found in BrowserOS config"; ++ LoadFinished(base::Value::Dict()); ++ return; ++ } ++ ++ // Create the prefs dictionary in the format expected by ExternalProviderImpl ++ base::Value::Dict prefs; ++ ++ for (const auto [extension_id, extension_config] : *extensions_dict) { ++ if (!extension_config.is_dict()) { ++ LOG(WARNING) << "Invalid config for extension " << extension_id; ++ continue; ++ } ++ ++ const base::Value::Dict& config_dict = extension_config.GetDict(); ++ base::Value::Dict extension_prefs; ++ ++ // Copy supported fields ++ if (const std::string* update_url = ++ config_dict.FindString(ExternalProviderImpl::kExternalUpdateUrl)) { ++ extension_prefs.Set(ExternalProviderImpl::kExternalUpdateUrl, *update_url); ++ } ++ ++ if (const std::string* crx_path = ++ config_dict.FindString(ExternalProviderImpl::kExternalCrx)) { ++ extension_prefs.Set(ExternalProviderImpl::kExternalCrx, *crx_path); ++ } ++ ++ if (const std::string* version = ++ config_dict.FindString(ExternalProviderImpl::kExternalVersion)) { ++ extension_prefs.Set(ExternalProviderImpl::kExternalVersion, *version); ++ } ++ ++ // Add other supported fields as needed ++ std::optional keep_if_present = ++ config_dict.FindBool(ExternalProviderImpl::kKeepIfPresent); ++ if (keep_if_present.has_value()) { ++ extension_prefs.Set(ExternalProviderImpl::kKeepIfPresent, ++ keep_if_present.value()); ++ } ++ ++ if (!extension_prefs.empty()) { ++ prefs.Set(extension_id, std::move(extension_prefs)); ++ } ++ } ++ ++ LOG(INFO) << "Loaded " << prefs.size() << " extensions from BrowserOS config"; ++ ++ // Track the extension IDs we're managing ++ for (const auto [extension_id, _] : prefs) { ++ browseros_extension_ids_.insert(extension_id); ++ } ++ ++ // Store the initial config for comparison ++ if (!extensions_dict->empty()) { ++ last_config_ = extensions_dict->Clone(); ++ } ++ ++ // Pass the prefs to the external provider system ++ LoadFinished(std::move(prefs)); ++ ++ // Immediately trigger high-priority installation of all BrowserOS extensions ++ // This ensures they get installed right away instead of waiting for Chrome's ++ // default external extension installation process ++ if (!browseros_extension_ids_.empty()) { ++ LOG(INFO) << "browseros: Triggering immediate high-priority installation for " ++ << browseros_extension_ids_.size() << " BrowserOS extensions"; ++ ++ // Use a delayed task to ensure the extension system is fully initialized ++ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( ++ FROM_HERE, ++ base::BindOnce(&BrowserOSExternalLoader::TriggerImmediateInstallation, ++ weak_ptr_factory_.GetWeakPtr()), ++ base::Seconds(2)); // Small delay to ensure extension system is ready ++ } ++ ++ // Start periodic checking after initial load ++ StartPeriodicCheck(); ++ ++ // Log initial extension state at startup ++ CheckAndLogExtensionState("startup"); ++} ++ ++void BrowserOSExternalLoader::StartPeriodicCheck() { ++ LOG(INFO) << "browseros: Starting periodic maintenance (every " ++ << kPeriodicMaintenanceInterval.InMinutes() << " minutes)"; ++ ++ // Schedule the periodic maintenance ++ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( ++ FROM_HERE, ++ base::BindOnce(&BrowserOSExternalLoader::PeriodicMaintenance, ++ weak_ptr_factory_.GetWeakPtr()), ++ kPeriodicMaintenanceInterval); ++} ++ ++void BrowserOSExternalLoader::PeriodicMaintenance() { ++ LOG(INFO) << "browseros: Running periodic maintenance"; ++ ++ if (!profile_) { ++ // Schedule next check even if profile isn't ready ++ StartPeriodicCheck(); ++ return; ++ } ++ ++ // 1. Check for and reinstall any uninstalled BrowserOS extensions ++ ReinstallUninstalledExtensions(); ++ ++ // 2. Re-enable any disabled BrowserOS extensions (respects flag state) ++ ReenableDisabledExtensions(); ++ ++ // 3. Enforce correct enabled/disabled state based on alpha features flag ++ EnforceExtensionStateBasedOnFlag(); ++ ++ // 4. Fetch latest config and check for changes ++ FetchAndCheckConfig(); ++ ++ // 5. Force immediate update check for all BrowserOS extensions ++ ForceUpdateCheck(); ++ ++ // 6. Log extension state after all maintenance attempts ++ CheckAndLogExtensionState("periodic_maintenance"); ++ ++ // Schedule the next maintenance ++ StartPeriodicCheck(); ++} ++ ++void BrowserOSExternalLoader::ReinstallUninstalledExtensions() { ++ ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service(); ++ if (!service) { ++ return; ++ } ++ ++ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); ++ PendingExtensionManager* pending_manager = PendingExtensionManager::Get(profile_); ++ ++ if (!registry || !pending_manager || last_config_.empty()) { ++ return; ++ } ++ ++ for (const std::string& extension_id : browseros_extension_ids_) { ++ // Check if extension exists (installed or disabled) ++ if (registry->GetInstalledExtension(extension_id)) { ++ continue; // Extension is installed, skip to next ++ } ++ ++ LOG(INFO) << "browseros: Extension " << extension_id ++ << " was uninstalled, attempting to reinstall"; ++ ++ // Find the extension's configuration ++ const base::Value::Dict* extension_config = last_config_.FindDict(extension_id); ++ if (!extension_config) { ++ LOG(WARNING) << "browseros: No config found for " << extension_id; ++ continue; ++ } ++ ++ // Get the update URL from config ++ const std::string* update_url = ++ extension_config->FindString(ExternalProviderImpl::kExternalUpdateUrl); ++ if (!update_url) { ++ LOG(WARNING) << "browseros: No update URL found for " << extension_id; ++ continue; ++ } ++ ++ // Validate and add to pending extensions ++ GURL update_gurl(*update_url); ++ if (!update_gurl.is_valid()) { ++ LOG(WARNING) << "browseros: Invalid update URL for " << extension_id; ++ continue; ++ } ++ ++ // Add as pending extension for installation ++ pending_manager->AddFromExternalUpdateUrl( ++ extension_id, ++ std::string(), // No install param ++ update_gurl, ++ mojom::ManifestLocation::kExternalComponent, ++ Extension::WAS_INSTALLED_BY_DEFAULT, ++ false); ++ ++ LOG(INFO) << "browseros: Added " << extension_id ++ << " to pending extensions for reinstall"; ++ ++ // Trigger immediate installation ++ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_); ++ if (updater) { ++ ExtensionUpdater::CheckParams params; ++ params.ids = {extension_id}; ++ params.install_immediately = true; ++ params.fetch_priority = DownloadFetchPriority::kForeground; ++ updater->CheckNow(std::move(params)); ++ LOG(INFO) << "browseros: Triggered immediate install for " << extension_id; ++ } ++ } ++} ++ ++void BrowserOSExternalLoader::ReenableDisabledExtensions() { ++ ExtensionService* service = ExtensionSystem::Get(profile_)->extension_service(); ++ if (!service) { ++ return; ++ } ++ ++ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); ++ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); ++ ++ if (!registry || !prefs) { ++ return; ++ } ++ ++ for (const std::string& extension_id : browseros_extension_ids_) { ++ // Check if extension is disabled ++ if (!registry->disabled_extensions().Contains(extension_id)) { ++ continue; // Extension is not disabled, skip to next ++ } ++ ++ // Only re-enable if this extension should be enabled based on flag state ++ if (!ShouldExtensionBeEnabled(extension_id)) { ++ continue; // Extension should stay disabled based on flag ++ } ++ ++ // Re-enable BrowserOS extensions regardless of disable reason ++ auto* registrar = extensions::ExtensionRegistrar::Get(profile_); ++ if (!registrar) { ++ LOG(WARNING) << "browseros: Cannot re-enable " << extension_id ++ << " because ExtensionRegistrar is unavailable"; ++ continue; ++ } ++ ++ LOG(INFO) << "browseros: Re-enabling extension " << extension_id; ++ registrar->EnableExtension(extension_id); ++ } ++} ++ ++void BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag() { ++ if (!profile_) { ++ return; ++ } ++ ++ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); ++ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); ++ auto* registrar = extensions::ExtensionRegistrar::Get(profile_); ++ ++ if (!registry || !prefs || !registrar) { ++ return; ++ } ++ ++ bool alpha_features_enabled = ++ base::FeatureList::IsEnabled(features::kBrowserOsAlphaFeatures); ++ ++ LOG(INFO) << "browseros: Enforcing extension state (alpha_features=" ++ << (alpha_features_enabled ? "ON" : "OFF") << ")"; ++ ++ for (const std::string& extension_id : browseros_extension_ids_) { ++ bool should_be_enabled = ShouldExtensionBeEnabled(extension_id); ++ bool is_enabled = registry->enabled_extensions().Contains(extension_id); ++ bool is_disabled = registry->disabled_extensions().Contains(extension_id); ++ ++ // Skip if extension is not installed yet ++ if (!is_enabled && !is_disabled) { ++ continue; ++ } ++ ++ if (should_be_enabled && is_disabled) { ++ // Extension should be enabled but is disabled - enable it ++ LOG(INFO) << "browseros: Enabling extension " << extension_id ++ << " (flag state requires it to be enabled)"; ++ registrar->EnableExtension(extension_id); ++ } else if (!should_be_enabled && is_enabled) { ++ // Extension should be disabled but is enabled - disable it ++ LOG(INFO) << "browseros: Disabling extension " << extension_id ++ << " (flag state requires it to be disabled)"; ++ registrar->DisableExtension(extension_id, ++ {disable_reason::DISABLE_USER_ACTION}); ++ } ++ } ++} ++ ++void BrowserOSExternalLoader::FetchAndCheckConfig() { ++ LOG(INFO) << "browseros: Fetching latest config to check for changes"; ++ ++ if (config_file_for_testing_.empty() && config_url_.is_valid()) { ++ // Fetch from URL ++ if (!url_loader_factory_) { ++ url_loader_factory_ = profile_->GetDefaultStoragePartition() ++ ->GetURLLoaderFactoryForBrowserProcess(); ++ } ++ ++ auto resource_request = std::make_unique(); ++ resource_request->url = config_url_; ++ resource_request->method = "GET"; ++ resource_request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE; ++ ++ auto config_check_loader = network::SimpleURLLoader::Create( ++ std::move(resource_request), kBrowserOSExtensionsFetchTrafficAnnotation); ++ ++ // Store the loader to keep it alive during the request ++ auto* loader_ptr = config_check_loader.get(); ++ loader_ptr->DownloadToStringOfUnboundedSizeUntilCrashAndDie( ++ url_loader_factory_.get(), ++ base::BindOnce(&BrowserOSExternalLoader::OnConfigCheckComplete, ++ weak_ptr_factory_.GetWeakPtr(), ++ std::move(config_check_loader))); ++ } ++} ++ ++void BrowserOSExternalLoader::OnConfigCheckComplete( ++ std::unique_ptr loader, ++ std::unique_ptr response_body) { ++ if (!response_body) { ++ LOG(WARNING) << "browseros: Failed to fetch config for update check"; ++ return; ++ } ++ ++ std::optional parsed_json = base::JSONReader::Read(*response_body); ++ if (!parsed_json || !parsed_json->is_dict()) { ++ LOG(WARNING) << "browseros: Invalid config JSON during update check"; ++ return; ++ } ++ ++ const base::Value::Dict* extensions_dict = ++ parsed_json->GetDict().FindDict("extensions"); ++ if (!extensions_dict) { ++ return; ++ } ++ ++ // Check if config has changed ++ bool config_changed = false; ++ if (last_config_.empty()) { ++ config_changed = true; // First time ++ } else { ++ // Compare with last config ++ for (const auto [extension_id, new_config] : *extensions_dict) { ++ const base::Value::Dict* old_config = last_config_.FindDict(extension_id); ++ if (!old_config || *old_config != new_config.GetDict()) { ++ config_changed = true; ++ LOG(INFO) << "browseros: Config changed for extension " << extension_id; ++ break; ++ } ++ } ++ ++ // Check for removed extensions ++ for (const auto [extension_id, _] : last_config_) { ++ if (!extensions_dict->contains(extension_id)) { ++ config_changed = true; ++ LOG(INFO) << "browseros: Extension " << extension_id << " removed from config"; ++ break; ++ } ++ } ++ } ++ ++ if (config_changed) { ++ LOG(INFO) << "browseros: Config has changed, reloading extensions"; ++ ++ // Store the new config ++ last_config_ = extensions_dict->Clone(); ++ ++ // Parse and reload with new config ++ ParseConfiguration(*response_body); ++ } else { ++ LOG(INFO) << "browseros: Config unchanged"; ++ } ++} ++ ++void BrowserOSExternalLoader::TriggerImmediateInstallation() { ++ if (!profile_ || browseros_extension_ids_.empty()) { ++ return; ++ } ++ ++ LOG(INFO) << "browseros: Triggering immediate installation on first start"; ++ ++ // First, add all extensions to pending if they're not already installed ++ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); ++ PendingExtensionManager* pending_manager = PendingExtensionManager::Get(profile_); ++ ++ if (registry && pending_manager && !last_config_.empty()) { ++ for (const std::string& extension_id : browseros_extension_ids_) { ++ // Skip if already installed ++ if (registry->GetInstalledExtension(extension_id)) { ++ LOG(INFO) << "browseros: Extension " << extension_id << " already installed"; ++ continue; ++ } ++ ++ // Add to pending extensions ++ const base::Value::Dict* extension_config = last_config_.FindDict(extension_id); ++ if (extension_config) { ++ const std::string* update_url = ++ extension_config->FindString(ExternalProviderImpl::kExternalUpdateUrl); ++ if (update_url) { ++ GURL update_gurl(*update_url); ++ if (update_gurl.is_valid()) { ++ pending_manager->AddFromExternalUpdateUrl( ++ extension_id, ++ std::string(), // No install param ++ update_gurl, ++ mojom::ManifestLocation::kExternalComponent, ++ Extension::WAS_INSTALLED_BY_DEFAULT, ++ false); // Don't mark acknowledged ++ LOG(INFO) << "browseros: Added " << extension_id ++ << " to pending for immediate installation"; ++ } ++ } ++ } ++ } ++ } ++ ++ // Now trigger immediate high-priority installation ++ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_); ++ if (!updater) { ++ LOG(WARNING) << "browseros: No extension updater available for immediate installation"; ++ return; ++ } ++ ++ LOG(INFO) << "browseros: Executing CheckNow with immediate install for " ++ << browseros_extension_ids_.size() << " BrowserOS extensions"; ++ ++ // Create CheckParams for immediate foreground installation ++ ExtensionUpdater::CheckParams params; ++ params.ids = std::list(browseros_extension_ids_.begin(), ++ browseros_extension_ids_.end()); ++ params.install_immediately = true; ++ params.fetch_priority = DownloadFetchPriority::kForeground; ++ ++ // Trigger the installation ++ updater->CheckNow(std::move(params)); ++ ++ // Schedule enforcement of extension state after installation completes. ++ // This ensures the correct extension is enabled/disabled based on the ++ // kBrowserOsAlphaFeatures flag after fresh installation. ++ base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( ++ FROM_HERE, ++ base::BindOnce(&BrowserOSExternalLoader::EnforceExtensionStateBasedOnFlag, ++ weak_ptr_factory_.GetWeakPtr()), ++ base::Seconds(30)); ++} ++ ++void BrowserOSExternalLoader::ForceUpdateCheck() { ++ if (!profile_ || browseros_extension_ids_.empty()) { ++ return; ++ } ++ ++ ExtensionUpdater* updater = ExtensionUpdater::Get(profile_); ++ if (!updater) { ++ LOG(WARNING) << "browseros: No extension updater available"; ++ return; ++ } ++ ++ LOG(INFO) << "browseros: Forcing immediate update check for " ++ << browseros_extension_ids_.size() << " BrowserOS extensions"; ++ ++ // Create CheckParams for immediate foreground update ++ ExtensionUpdater::CheckParams params; ++ params.ids = std::list(browseros_extension_ids_.begin(), ++ browseros_extension_ids_.end()); ++ params.install_immediately = true; ++ params.fetch_priority = DownloadFetchPriority::kForeground; ++ ++ // Trigger the update check ++ updater->CheckNow(std::move(params)); ++} ++ ++void BrowserOSExternalLoader::LoadFromFile() { ++ // This runs on a background thread to avoid blocking the UI ++ base::ThreadPool::PostTaskAndReplyWithResult( ++ FROM_HERE, ++ {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, ++ base::BindOnce([](const base::FilePath& path) -> std::string { ++ std::string contents; ++ if (!base::ReadFileToString(path, &contents)) { ++ LOG(ERROR) << "Failed to read BrowserOS config file: " << path; ++ return std::string(); ++ } ++ return contents; ++ }, config_file_for_testing_), ++ base::BindOnce(&BrowserOSExternalLoader::ParseConfiguration, ++ weak_ptr_factory_.GetWeakPtr())); ++} ++ ++void BrowserOSExternalLoader::CheckAndLogExtensionState( ++ const std::string& context) { ++ if (!profile_) { ++ return; ++ } ++ ++ ExtensionRegistry* registry = ExtensionRegistry::Get(profile_); ++ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); ++ ++ if (!registry || !prefs) { ++ return; ++ } ++ ++ for (const std::string& extension_id : browseros_extension_ids_) { ++ // If extension is enabled, it's healthy - skip logging ++ if (registry->enabled_extensions().Contains(extension_id)) { ++ continue; ++ } ++ ++ // Extension is NOT enabled - gather diagnostic information ++ base::Value::Dict properties; ++ properties.Set("extension_id", extension_id); ++ properties.Set("context", context); ++ ++ std::string state; ++ ++ if (registry->disabled_extensions().Contains(extension_id)) { ++ state = "disabled"; ++ ++ // Get extension version if available ++ const Extension* extension = registry->disabled_extensions().GetByID(extension_id); ++ if (extension) { ++ properties.Set("version", extension->version().GetString()); ++ } ++ ++ // Get disable reasons using public API ++ DisableReasonSet disable_reasons = prefs->GetDisableReasons(extension_id); ++ ++ // Convert to bitmask by ORing all reason values ++ int bitmask = 0; ++ for (disable_reason::DisableReason reason : disable_reasons) { ++ bitmask |= static_cast(reason); ++ } ++ properties.Set("disable_reasons_bitmask", bitmask); ++ ++ // Log individual disable reason flags for easy querying ++ properties.Set("reason_user_action", ++ disable_reasons.contains(disable_reason::DISABLE_USER_ACTION)); ++ properties.Set("reason_permissions_increase", ++ disable_reasons.contains(disable_reason::DISABLE_PERMISSIONS_INCREASE)); ++ properties.Set("reason_reload", ++ disable_reasons.contains(disable_reason::DISABLE_RELOAD)); ++ properties.Set("reason_corrupted", ++ disable_reasons.contains(disable_reason::DISABLE_CORRUPTED)); ++ properties.Set("reason_greylist", ++ disable_reasons.contains(disable_reason::DISABLE_GREYLIST)); ++ properties.Set("reason_remote_install", ++ disable_reasons.contains(disable_reason::DISABLE_REMOTE_INSTALL)); ++ ++ } else if (registry->blocklisted_extensions().Contains(extension_id)) { ++ state = "blocklisted"; ++ ++ } else if (registry->blocked_extensions().Contains(extension_id)) { ++ state = "blocked"; ++ ++ } else if (registry->terminated_extensions().Contains(extension_id)) { ++ state = "terminated"; ++ ++ } else { ++ state = "not_installed"; ++ } ++ ++ properties.Set("state", state); ++ ++ // Log to metrics ++ browseros_metrics::BrowserOSMetrics::Log("ota.extension.unexpected_state", ++ std::move(properties)); ++ ++ // Also log to Chrome logs for local debugging ++ LOG(WARNING) << "browseros: Extension " << extension_id ++ << " in unexpected state: " << state ++ << " (context: " << context << ")"; ++ } ++} ++ ++} // namespace extensions diff --git a/chromium_patches/chrome/browser/extensions/browseros_external_loader.h b/chromium_patches/chrome/browser/extensions/browseros_external_loader.h new file mode 100644 index 000000000..3cafbca06 --- /dev/null +++ b/chromium_patches/chrome/browser/extensions/browseros_external_loader.h @@ -0,0 +1,134 @@ +diff --git a/chrome/browser/extensions/browseros_external_loader.h b/chrome/browser/extensions/browseros_external_loader.h +new file mode 100644 +index 0000000000000..0365a5e231215 +--- /dev/null ++++ b/chrome/browser/extensions/browseros_external_loader.h +@@ -0,0 +1,128 @@ ++// 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 CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_ ++#define CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_ ++ ++#include ++#include ++#include ++ ++#include "base/files/file_path.h" ++#include "base/memory/scoped_refptr.h" ++#include "base/memory/weak_ptr.h" ++#include "base/timer/timer.h" ++#include "chrome/browser/extensions/external_loader.h" ++#include "services/network/public/cpp/simple_url_loader.h" ++ ++class Profile; ++ ++namespace network { ++class SharedURLLoaderFactory; ++} // namespace network ++ ++namespace extensions { ++ ++// A specialization of the ExternalLoader that loads extension information ++// from a remote URL. This is designed for BrowserOS to specify a set of ++// extensions that should be installed at startup. ++class BrowserOSExternalLoader : public ExternalLoader { ++ public: ++ explicit BrowserOSExternalLoader(Profile* profile); ++ ++ BrowserOSExternalLoader(const BrowserOSExternalLoader&) = delete; ++ BrowserOSExternalLoader& operator=(const BrowserOSExternalLoader&) = delete; ++ ++ // Sets the URL from which to fetch the extension configuration. ++ // Must be called before StartLoading(). ++ void SetConfigUrl(const GURL& url) { config_url_ = url; } ++ ++ // For testing: sets a local file path instead of fetching from URL. ++ void SetConfigFileForTesting(const base::FilePath& path) { ++ config_file_for_testing_ = path; ++ } ++ ++ // Starts periodic maintenance loop (no-op if already running). ++ void StartPeriodicCheck(); ++ ++ // Periodic maintenance: re-enables disabled extensions, checks config, and forces updates ++ void PeriodicMaintenance(); ++ ++ // Fetches the latest config and checks for changes ++ void FetchAndCheckConfig(); ++ ++ // Forces immediate update check for BrowserOS extensions ++ void ForceUpdateCheck(); ++ ++ protected: ++ ~BrowserOSExternalLoader() override; ++ ++ // ExternalLoader: ++ void StartLoading() override; ++ ++ private: ++ friend class base::RefCountedThreadSafe; ++ ++ // Called when the URL fetch completes. ++ void OnURLFetchComplete(std::unique_ptr response_body); ++ ++ // Called when config check fetch completes ++ void OnConfigCheckComplete(std::unique_ptr loader, ++ std::unique_ptr response_body); ++ ++ // Parses the fetched JSON configuration and loads extensions. ++ void ParseConfiguration(const std::string& json_content); ++ ++ // Loads configuration from a local file (for testing). ++ void LoadFromFile(); ++ ++ // Checks for uninstalled BrowserOS extensions and reinstalls them ++ void ReinstallUninstalledExtensions(); ++ ++ // Re-enables BrowserOS extensions that were disabled by user action ++ void ReenableDisabledExtensions(); ++ ++ // Enforces extension enabled/disabled state based on kBrowserOsAlphaFeatures flag. ++ // - When flag is ON: enables kAgentV2ExtensionId, disables kAISidePanelExtensionId ++ // - When flag is OFF: enables kAISidePanelExtensionId, disables kAgentV2ExtensionId ++ void EnforceExtensionStateBasedOnFlag(); ++ ++ // Triggers immediate installation of all BrowserOS extensions on first start ++ void TriggerImmediateInstallation(); ++ ++ // Checks extension state and logs to metrics if not enabled ++ void CheckAndLogExtensionState(const std::string& context); ++ ++ // The profile associated with this loader. ++ raw_ptr profile_; ++ ++ // URL from which to fetch the extension configuration. ++ GURL config_url_; ++ ++ // For testing: local file path instead of URL. ++ base::FilePath config_file_for_testing_; ++ ++ // URL loader for fetching the configuration. ++ std::unique_ptr url_loader_; ++ ++ // URLLoaderFactory for making network requests. ++ scoped_refptr url_loader_factory_; ++ ++ // List of BrowserOS extension IDs to monitor ++ std::set browseros_extension_ids_; ++ ++ // Last fetched config for comparison ++ base::Value::Dict last_config_; ++ ++ // Tracks whether we have successfully applied a configuration during this session. ++ bool has_successful_config_ = false; ++ ++ base::RepeatingTimer periodic_timer_; ++ ++ base::WeakPtrFactory weak_ptr_factory_{this}; ++}; ++ ++} // namespace extensions ++ ++#endif // CHROME_BROWSER_EXTENSIONS_BROWSEROS_EXTERNAL_LOADER_H_ diff --git a/chromium_patches/chrome/browser/flag_descriptions.cc b/chromium_patches/chrome/browser/flag_descriptions.cc new file mode 100644 index 000000000..5ddfa1618 --- /dev/null +++ b/chromium_patches/chrome/browser/flag_descriptions.cc @@ -0,0 +1,15 @@ +diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc +index f9088ac701027..2b38cd3cecf26 100644 +--- a/chrome/browser/flag_descriptions.cc ++++ b/chrome/browser/flag_descriptions.cc +@@ -270,6 +270,10 @@ const char kBookmarksTreeViewName[] = "Top Chrome Bookmarks Tree View"; + const char kBookmarksTreeViewDescription[] = + "Show the bookmarks side panel in a tree view while in compact mode."; + ++const char kBrowserOsAlphaFeaturesName[] = "BrowserOS Alpha Features"; ++const char kBrowserOsAlphaFeaturesDescription[] = ++ "Enables BrowserOS alpha features."; ++ + const char kBrowsingHistoryActorIntegrationM1Name[] = + "Browsing History Actor Integration M1"; + const char kBrowsingHistoryActorIntegrationM1Description[] = diff --git a/chromium_patches/chrome/browser/flag_descriptions.h b/chromium_patches/chrome/browser/flag_descriptions.h new file mode 100644 index 000000000..498a4afc6 --- /dev/null +++ b/chromium_patches/chrome/browser/flag_descriptions.h @@ -0,0 +1,14 @@ +diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h +index fea522351cd1b..eea668db91b63 100644 +--- a/chrome/browser/flag_descriptions.h ++++ b/chrome/browser/flag_descriptions.h +@@ -188,6 +188,9 @@ extern const char kByDateHistoryInSidePanelDescription[]; + extern const char kBookmarksTreeViewName[]; + extern const char kBookmarksTreeViewDescription[]; + ++extern const char kBrowserOsAlphaFeaturesName[]; ++extern const char kBrowserOsAlphaFeaturesDescription[]; ++ + extern const char kBrowsingHistoryActorIntegrationM1Name[]; + extern const char kBrowsingHistoryActorIntegrationM1Description[]; + #endif