From 860536f6f49120f3469a2abbf73a3d043cdfc88e Mon Sep 17 00:00:00 2001 From: Carmelo Messina Date: Thu, 28 Dec 2023 16:53:56 +0100 Subject: [PATCH] #514 Introduce Chrome web store protection The amount of information sent is minimized without disabling the ability to install extensions (which is under user control) It became possible to activate the autoupdate of extensions by sending the minimum of values. Installation with the chrome web store is allowed, but the website by default cannot retrieve the list of installed extensions and their status (under flag) All http calls related to requesting and downloading updates are cookie-free, but the ip and the list of extensions are traceable, that information cannot be omitted. Auto-update is disabled by default: it is possible to activate it with chrome://extensions/ --- build/cromite_patches_list.txt | 5 +- .../00Chrome-web-store-protection.patch | 1118 +++++++++++++++++ ...um-data-to-enable-install-extensions.patch | 72 -- .../Do-not-hide-component-extensions.patch | 24 - ...t_run-deactivate-autoupdate-globally.patch | 31 - 5 files changed, 1119 insertions(+), 131 deletions(-) create mode 100644 build/patches/00Chrome-web-store-protection.patch delete mode 100644 build/patches/00WIN-minimum-data-to-enable-install-extensions.patch delete mode 100644 build/patches/Do-not-hide-component-extensions.patch delete mode 100644 build/patches/first_run-deactivate-autoupdate-globally.patch diff --git a/build/cromite_patches_list.txt b/build/cromite_patches_list.txt index 0584a283..8b76d8c0 100644 --- a/build/cromite_patches_list.txt +++ b/build/cromite_patches_list.txt @@ -23,7 +23,6 @@ Battery-API-return-nothing.patch updater-disable-updater-pings.patch Disable-omission-of-URL-elements-in-Omnibox.patch Modify-default-preferences.patch -Do-not-hide-component-extensions.patch Do-not-store-passwords-by-default.patch Disable-NTP-remote-suggestions-by-default.patch Disable-references-to-fonts.googleapis.com.patch @@ -69,7 +68,6 @@ profile-resetter-disable-send-settings.patch Do-not-build-API-keys-infobar.patch autofill-miscellaneous.patch Enable-native-Android-autofill.patch -first_run-deactivate-autoupdate-globally.patch translate-disable-fetching-of-languages-from-server.patch Offer-builtin-autocomplete-for-chrome-flags.patch Use-4-tile-rows-never-show-logo.patch @@ -189,7 +187,6 @@ Add-kill-switch-for-unsupported-clangd-flags.patch 00WIN-enable-file-system-access-blocklist.patch 00WIN-Disable-TabHoverCard-images.patch 00WIN-Fix-log-to-file.patch -00WIN-minimum-data-to-enable-install-extensions.patch 00WIN-Disable-updater.patch 00WIN-Disable-first-run.patch 00WIN-Add-some-prefs-to-secure-preferences.patch @@ -276,13 +273,13 @@ Timezone-customization.patch 00Add-cromite-flags-support.patch 00Add-PartialLowEndModeOnMidRangeDevices-flag.patch 00Use-list-mode-for-tab-switcher.patch +00Chrome-web-store-protection.patch 00Temp-PerformanceNavigationTiming-privacy-fix.patch 00Temp-disable-predictive-back-gesture.patch 00TEMP-Add-a-log-to-track-strange-behavior.patch 00Temp-guard-FileSystemAccessPersistentPermissions.patch 00Fix-chromium-build-bugs.patch - eyeo-beta-118.0.5993.48-base.patch eyeo-beta-118.0.5993.48-chrome_integration.patch eyeo-beta-118.0.5993.48-android_api.patch diff --git a/build/patches/00Chrome-web-store-protection.patch b/build/patches/00Chrome-web-store-protection.patch new file mode 100644 index 00000000..15de63c2 --- /dev/null +++ b/build/patches/00Chrome-web-store-protection.patch @@ -0,0 +1,1118 @@ +From: uazo +Date: Thu, 28 Dec 2023 14:58:09 +0000 +Subject: Chrome web store protection + +The amount of information sent is minimized without disabling +the ability to install extensions (which is under user control) +It became possible to activate the autoupdate of extensions by sending the minimum of values. +Installation with the chrome web store is allowed, but the website +by default cannot retrieve the list of installed extensions and their status (under flag) +All http calls related to requesting and downloading updates are cookie-free, +but the ip and the list of extensions are traceable, that information cannot be omitted. +Auto-update is disabled by default: it is possible to activate it with chrome://extensions/ + +License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html +--- + chrome/app/extensions_strings.grdp | 6 +++ + .../developer_private_api.cc | 36 ++++++++++++- + .../developer_private/developer_private_api.h | 3 ++ + .../webstore_private/webstore_private_api.cc | 12 ++++- + .../extensions/extension_system_impl.cc | 6 +++ + .../chrome_extension_downloader_factory.cc | 1 - + .../extensions/updater/extension_updater.cc | 33 ++++++------ + .../extensions/updater/extension_updater.h | 2 + + .../extensions/webstore_install_helper.cc | 2 +- + .../browser/extensions/webstore_installer.cc | 2 +- + .../browser/resources/extensions/manager.html | 1 + + .../browser/resources/extensions/manager.ts | 7 +++ + .../browser/resources/extensions/service.ts | 5 ++ + .../browser/resources/extensions/toolbar.html | 12 +++++ + .../browser/resources/extensions/toolbar.ts | 20 +++++++ + .../resources/webstore_app/manifest.json | 7 +-- + .../ui/webui/extensions/extensions_ui.cc | 8 +++ + .../chrome_update_query_params_delegate.cc | 8 +-- + .../extensions/api/developer_private.idl | 2 + + .../update_client/update_query_params.cc | 7 ++- + .../about_flags_cc/Webstore-protection.inc | 19 +++++++ + .../browser/api/management/management_api.cc | 50 +++++++++++------ + .../browser/updater/extension_downloader.cc | 53 +++++++------------ + .../browser/updater/extension_downloader.h | 1 - + .../browser/updater/manifest_fetch_data.cc | 8 +-- + .../browser/updater/safe_manifest_parser.cc | 1 + + extensions/common/extension_features.cc | 15 ++++++ + extensions/common/extension_features.h | 4 ++ + .../definitions/developer_private.d.ts | 4 +- + 29 files changed, 243 insertions(+), 92 deletions(-) + create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Webstore-protection.inc + +diff --git a/chrome/app/extensions_strings.grdp b/chrome/app/extensions_strings.grdp +--- a/chrome/app/extensions_strings.grdp ++++ b/chrome/app/extensions_strings.grdp +@@ -76,6 +76,12 @@ + + Developer mode + ++ ++ Enable Auto-Update ++ ++ ++ Need restart ++ + + This extension is outdated and disabled by enterprise policy. It might become enabled automatically when a newer version is available. + +diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc +--- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc ++++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc +@@ -123,6 +123,12 @@ + #include "url/gurl.h" + #include "url/origin.h" + ++#include "chrome/browser/about_flags.h" ++#include "chrome/browser/browser_process.h" ++#include "components/flags_ui/pref_service_flags_storage.h" ++#include "components/flags_ui/feature_entry.h" ++#include "components/flags_ui/flags_storage.h" ++ + #if BUILDFLAG(ENABLE_SUPERVISED_USERS) + #include "chrome/browser/supervised_user/supervised_user_service_factory.h" + #include "components/supervised_user/core/browser/supervised_user_service.h" +@@ -520,9 +526,32 @@ std::unique_ptr DeveloperPrivateAPI::CreateProfileInfo( + info->can_load_unpacked = + ExtensionManagementFactory::GetForBrowserContext(profile) + ->HasAllowlistedExtension(); ++ info->is_extension_autoupdate_enabled = DeveloperPrivateAPI::IsExtensionAutoupdateEnabled(); + return info; + } + ++// static ++bool DeveloperPrivateAPI::IsExtensionAutoupdateEnabled() { ++ const std::string enabled_entry = "enable-extension-autoupdate@1"; ++ flags_ui::PrefServiceFlagsStorage flags_storage( ++ g_browser_process->local_state()); ++ std::set entries = flags_storage.GetFlags(); ++ return entries.count(enabled_entry) > 0; ++} ++ ++// static ++void DeveloperPrivateAPI::SetExtensionAutoupdateEnabled(bool enable) { ++ flags_ui::PrefServiceFlagsStorage flags_storage( ++ g_browser_process->local_state()); ++ if (enable) { ++ about_flags::SetFeatureEntryEnabled( ++ &flags_storage, "enable-extension-autoupdate@1", true); ++ } else { ++ about_flags::SetFeatureEntryEnabled( ++ &flags_storage, "enable-extension-autoupdate", false); ++ } ++} ++ + template <> + void BrowserContextKeyedAPIFactory< + DeveloperPrivateAPI>::DeclareFactoryDependencies() { +@@ -1089,7 +1118,7 @@ DeveloperPrivateUpdateProfileConfigurationFunction::Run() { + + const developer::ProfileConfigurationUpdate& update = params->update; + +- if (update.in_developer_mode) { ++ if (update.in_developer_mode.has_value() && update.in_developer_mode) { + Profile* profile = Profile::FromBrowserContext(browser_context()); + + #if BUILDFLAG(ENABLE_SUPERVISED_USERS) +@@ -1103,6 +1132,11 @@ DeveloperPrivateUpdateProfileConfigurationFunction::Run() { + util::SetDeveloperModeForProfile(profile, *update.in_developer_mode); + } + ++ if (update.is_extension_autoupdate_enabled.has_value()) { ++ DeveloperPrivateAPI::SetExtensionAutoupdateEnabled( ++ *update.is_extension_autoupdate_enabled); ++ } ++ + return RespondNow(NoArguments()); + } + +diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chrome/browser/extensions/api/developer_private/developer_private_api.h +--- a/chrome/browser/extensions/api/developer_private/developer_private_api.h ++++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h +@@ -233,6 +233,9 @@ class DeveloperPrivateAPI : public BrowserContextKeyedAPI, + static std::unique_ptr CreateProfileInfo( + Profile* profile); + ++ static bool IsExtensionAutoupdateEnabled(); ++ static void SetExtensionAutoupdateEnabled(bool enable); ++ + // Convenience method to get the DeveloperPrivateAPI for a profile. + static DeveloperPrivateAPI* Get(content::BrowserContext* context); + +diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc ++++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +@@ -60,6 +60,7 @@ + #include "extensions/browser/extension_system.h" + #include "extensions/browser/extension_util.h" + #include "extensions/common/extension.h" ++#include "extensions/common/extension_features.h" + #include "extensions/common/manifest.h" + #include "extensions/common/manifest_constants.h" + #include "extensions/common/manifest_handlers/permissions_parser.h" +@@ -231,12 +232,16 @@ WebstorePrivateApi::Delegate* test_delegate = nullptr; + // there was previously stored data, or an empty string otherwise. The Set will + // overwrite any previous login. + std::string GetWebstoreLogin(Profile* profile) { ++ if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) ++ return std::string(); + if (profile->GetPrefs()->HasPrefPath(kWebstoreLogin)) + return profile->GetPrefs()->GetString(kWebstoreLogin); + return std::string(); + } + + void SetWebstoreLogin(Profile* profile, const std::string& login) { ++ if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) ++ return; + profile->GetPrefs()->SetString(kWebstoreLogin, login); + } + +@@ -246,6 +251,8 @@ void RecordWebstoreExtensionInstallResult(bool success) { + + api::webstore_private::ExtensionInstallStatus + ConvertExtensionInstallStatusForAPI(ExtensionInstallStatus status) { ++ if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) ++ return api::webstore_private::EXTENSION_INSTALL_STATUS_INSTALLABLE; + switch (status) { + case kCanRequest: + return api::webstore_private::ExtensionInstallStatus:: +@@ -1178,7 +1185,8 @@ ExtensionFunction::ResponseAction + WebstorePrivateIsInIncognitoModeFunction::Run() { + Profile* profile = Profile::FromBrowserContext(browser_context()); + return RespondNow(ArgumentList(IsInIncognitoMode::Results::Create( +- profile != profile->GetOriginalProfile()))); ++ base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore) ++ && profile != profile->GetOriginalProfile()))); + } + + WebstorePrivateIsPendingCustodianApprovalFunction:: +@@ -1280,6 +1288,8 @@ WebstorePrivateGetReferrerChainFunction::Run() { + recent_navigations_to_collect); + + std::string serialized_referrer_proto = request.SerializeAsString(); ++ if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) ++ serialized_referrer_proto = ""; + // Base64 encode the proto to avoid issues with base::Value rejecting strings + // which are not valid UTF8. + base::Base64Encode(serialized_referrer_proto, &serialized_referrer_proto); +diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc +--- a/chrome/browser/extensions/extension_system_impl.cc ++++ b/chrome/browser/extensions/extension_system_impl.cc +@@ -57,6 +57,7 @@ + #include "extensions/browser/updater/uninstall_ping_sender.h" + #include "extensions/browser/user_script_manager.h" + #include "extensions/common/constants.h" ++#include "extensions/common/extension_features.h" + #include "extensions/common/features/feature_channel.h" + #include "extensions/common/manifest_url_handlers.h" + #include "ui/message_center/public/cpp/notifier_id.h" +@@ -84,6 +85,7 @@ UninstallPingSender::FilterResult ShouldSendUninstallPing( + Profile* profile, + const Extension* extension, + UninstallReason reason) { ++ if ((true)) return UninstallPingSender::DO_NOT_SEND_PING; + ExtensionManagement* extension_management = + ExtensionManagementFactory::GetForBrowserContext(profile); + if (extension && (extension->from_webstore() || +@@ -201,6 +203,10 @@ void ExtensionSystemImpl::Shared::Init(bool extensions_enabled) { + + bool autoupdate_enabled = + !profile_->IsGuestSession() && !profile_->IsSystemProfile(); ++ if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionAutoupdate)) { ++ autoupdate_enabled = false; ++ LOG(INFO) << "Extensions autoupdate is disabled."; ++ } + #if BUILDFLAG(IS_CHROMEOS_ASH) + if (!extensions_enabled || + ash::ProfileHelper::IsLockScreenAppProfile(profile_)) { +diff --git a/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc b/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc +--- a/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc ++++ b/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc +@@ -49,7 +49,6 @@ ChromeExtensionDownloaderFactory::CreateForURLLoaderFactory( + manifest_query_params += "&testrequest=1"; + } + downloader->set_manifest_query_params(manifest_query_params); +- downloader->set_ping_enabled_domain("google.com"); + return downloader; + } + +diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc +--- a/chrome/browser/extensions/updater/extension_updater.cc ++++ b/chrome/browser/extensions/updater/extension_updater.cc +@@ -197,6 +197,10 @@ void ExtensionUpdater::Start() { + DCHECK(!weak_ptr_factory_.HasWeakPtrs()); + DCHECK(registry_); + alive_ = true; ++ if (first_start_) { ++ first_start_ = false; ++ CheckSoon(); ++ } + // Check soon, and set up the first delayed check. + if (!g_skip_scheduled_checks_for_tests) { + if (g_should_immediately_update) +@@ -329,7 +333,8 @@ void ExtensionUpdater::AddToDownloader( + const Extension& extension = **extension_iter; + const ExtensionId& extension_id = extension.id(); + if (!Manifest::IsAutoUpdateableLocation(extension.location())) { +- VLOG(2) << "Extension " << extension_id << " is not auto updateable"; ++ LOG(INFO) << "Extension " << extension_id << " is not auto updateable: " ++ << "location=" << extension.location(); + continue; + } + // An extension might be overwritten by policy, and have its update url +@@ -343,7 +348,7 @@ void ExtensionUpdater::AddToDownloader( + continue; + } + +- if (CanUseUpdateService(extension_id)) { ++ if ((false) && CanUseUpdateService(extension_id)) { + update_check_params->update_info[extension_id] = ExtensionUpdateData(); + } else if (AddExtensionToDownloader(extension, request_id, + fetch_priority)) { +@@ -389,7 +394,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + + int request_id = next_request_id_++; + +- VLOG(2) << "Starting update check " << request_id; ++ LOG(INFO) << "Starting extensions update check id: " << request_id; + if (params.ids.empty()) + NotifyStarted(); + +@@ -461,7 +466,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + << " is not a corrupt reinstall"; + update_check_params.update_info[pending_id] = ExtensionUpdateData(); + } else if (!Manifest::IsAutoUpdateableLocation(info->install_source())) { +- VLOG(2) << "Extension " << pending_id << " is not auto updateable"; ++ LOG(INFO) << "Extension " << pending_id << " is not auto updateable"; + continue; + } + // We have to mark high-priority extensions (such as policy-forced +@@ -471,7 +476,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + // See https://crbug.com/904600 and https://crbug.com/965686. + const bool is_high_priority_extension_pending = + pending_extension_manager->HasHighPriorityPendingExtension(); +- if (CanUseUpdateService(pending_id)) { ++ if ((false) && CanUseUpdateService(pending_id)) { + update_check_params.update_info[pending_id].is_corrupt_reinstall = + is_corrupt_reinstall; + if (is_corrupt_reinstall) { +@@ -492,6 +497,8 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + LOG(WARNING) << "Corrupt extension with id " << pending_id + << " will be reinstalled with ExtensionDownloader."; + } ++ LOG(INFO) << "Extension " << pending_id << " is auto updateable " ++ << "from " << info->update_url(); + } else { + InstallStageTracker::Get(profile_)->ReportFailure( + pending_id, +@@ -504,13 +511,6 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + AddToDownloader(®istry_->disabled_extensions(), pending_ids, request_id, + params.fetch_priority, &update_check_params); + ExtensionSet remotely_disabled_extensions; +- for (auto extension : registry_->blocklisted_extensions()) { +- if (blocklist_prefs::HasOmahaBlocklistState( +- extension->id(), BitMapBlocklistState::BLOCKLISTED_MALWARE, +- extension_prefs_)) { +- remotely_disabled_extensions.Insert(extension); +- } +- } + AddToDownloader(&remotely_disabled_extensions, pending_ids, request_id, + params.fetch_priority, &update_check_params); + } else { +@@ -518,7 +518,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { + const Extension* extension = registry_->GetExtensionById( + id, extensions::ExtensionRegistry::EVERYTHING); + if (extension) { +- if (CanUseUpdateService(id)) { ++ if ((false) && CanUseUpdateService(id)) { + update_check_params.update_info[id] = ExtensionUpdateData(); + } else if (AddExtensionToDownloader(*extension, request_id, + params.fetch_priority)) { +@@ -659,7 +659,7 @@ void ExtensionUpdater::OnExtensionDownloadFinished( + file.extension_id, InstallStageTracker::Stage::INSTALLING); + UpdatePingData(file.extension_id, ping); + +- VLOG(2) << download_url << " written to " << file.path.value(); ++ LOG(INFO) << download_url << " written to " << file.path.value(); + + FetchedCRXFile fetched(file, file_ownership_passed, request_ids, + std::move(callback)); +@@ -670,6 +670,7 @@ void ExtensionUpdater::OnExtensionDownloadFinished( + + bool ExtensionUpdater::GetPingDataForExtension(const ExtensionId& id, + DownloadPingData* ping_data) { ++ if ((true)) return false; + DCHECK(alive_); + ping_data->rollcall_days = + CalculatePingDaysForExtension(extension_prefs_->LastPingDay(id)); +@@ -768,7 +769,7 @@ bool ExtensionUpdater::CanUseUpdateService( + void ExtensionUpdater::InstallCRXFile(FetchedCRXFile crx_file) { + std::set request_ids; + +- VLOG(2) << "updating " << crx_file.info.extension_id << " with " ++ LOG(INFO) << "Updating " << crx_file.info.extension_id << " with " + << crx_file.info.path.value(); + + // The ExtensionService is now responsible for cleaning up the temp file +@@ -877,7 +878,7 @@ void ExtensionUpdater::NotifyIfFinished(int request_id) { + InProgressCheck& request = requests_in_progress_[request_id]; + if (!request.in_progress_ids.empty() || request.awaiting_update_service) + return; // This request is not done yet. +- VLOG(2) << "Finished update check " << request_id; ++ LOG(INFO) << "Finished update check id: " << request_id; + if (!request.callback.is_null()) + std::move(request.callback).Run(); + requests_in_progress_.erase(request_id); +diff --git a/chrome/browser/extensions/updater/extension_updater.h b/chrome/browser/extensions/updater/extension_updater.h +--- a/chrome/browser/extensions/updater/extension_updater.h ++++ b/chrome/browser/extensions/updater/extension_updater.h +@@ -327,6 +327,8 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate { + base::TimeDelta frequency_; + bool will_check_soon_ = false; + ++ bool first_start_ = true; ++ + raw_ptr extension_prefs_ = nullptr; + raw_ptr prefs_ = nullptr; + raw_ptr profile_ = nullptr; +diff --git a/chrome/browser/extensions/webstore_install_helper.cc b/chrome/browser/extensions/webstore_install_helper.cc +--- a/chrome/browser/extensions/webstore_install_helper.cc ++++ b/chrome/browser/extensions/webstore_install_helper.cc +@@ -46,7 +46,7 @@ void WebstoreInstallHelper::Start( + data_decoder::DataDecoder::ParseJsonIsolated( + manifest_, base::BindOnce(&WebstoreInstallHelper::OnJSONParsed, this)); + +- if (icon_url_.is_empty()) { ++ if ((true) || icon_url_.is_empty()) { + icon_decode_complete_ = true; + } else { + // No existing |icon_fetcher_| to avoid unbalanced AddRef(). +diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc +--- a/chrome/browser/extensions/webstore_installer.cc ++++ b/chrome/browser/extensions/webstore_installer.cc +@@ -624,7 +624,7 @@ void WebstoreInstaller::StartDownload(const std::string& extension_id, + download_url_, render_process_host_id, render_frame_host->GetRoutingID(), + traffic_annotation)); + params->set_file_path(file); +- if (controller.GetVisibleEntry()) { ++ if ((false) && controller.GetVisibleEntry()) { + content::Referrer referrer = content::Referrer::SanitizeForRequest( + download_url_, + content::Referrer(controller.GetVisibleEntry()->GetURL(), +diff --git a/chrome/browser/resources/extensions/manager.html b/chrome/browser/resources/extensions/manager.html +--- a/chrome/browser/resources/extensions/manager.html ++++ b/chrome/browser/resources/extensions/manager.html +@@ -64,6 +64,7 @@ + + + loadTimeData.getBoolean('inDevMode'), + }, + ++ isExtUpdateEnabled: { ++ type: Boolean, ++ value: () => loadTimeData.getBoolean('isExtUpdateEnabled'), ++ }, ++ + showActivityLog: { + type: Boolean, + value: () => loadTimeData.getBoolean('showActivityLog'), +@@ -222,6 +227,7 @@ export class ExtensionsManagerElement extends ExtensionsManagerElementBase { + canLoadUnpacked: boolean; + delegate: Service; + inDevMode: boolean; ++ isExtUpdateEnabled: boolean; + showActivityLog: boolean; + enableEnhancedSiteControls: boolean; + devModeControlledByPolicy: boolean; +@@ -292,6 +298,7 @@ export class ExtensionsManagerElement extends ExtensionsManagerElementBase { + profileInfo.isDeveloperModeControlledByPolicy; + this.inDevMode = profileInfo.inDeveloperMode; + this.canLoadUnpacked = profileInfo.canLoadUnpacked; ++ this.isExtUpdateEnabled = profileInfo.isExtensionAutoupdateEnabled; + }; + service.getProfileStateChangedTarget().addListener(onProfileStateChanged); + service.getProfileConfiguration().then(onProfileStateChanged); +diff --git a/chrome/browser/resources/extensions/service.ts b/chrome/browser/resources/extensions/service.ts +--- a/chrome/browser/resources/extensions/service.ts ++++ b/chrome/browser/resources/extensions/service.ts +@@ -283,6 +283,11 @@ export class Service implements ServiceInterface { + {inDeveloperMode: inDevMode}); + } + ++ setExtUpdateEnabled(enabled: boolean) { ++ chrome.developerPrivate.updateProfileConfiguration( ++ {isExtensionAutoupdateEnabled: enabled}); ++ } ++ + loadUnpacked(): Promise { + return this.loadUnpackedHelper_(); + } +diff --git a/chrome/browser/resources/extensions/toolbar.html b/chrome/browser/resources/extensions/toolbar.html +--- a/chrome/browser/resources/extensions/toolbar.html ++++ b/chrome/browser/resources/extensions/toolbar.html +@@ -63,6 +63,11 @@ + margin-inline-end: 16px; + } + ++ #need-update { ++ color: red; ++ margin-inline: 0px; ++ } ++ + cr-toolbar { + --cr-toolbar-center-basis: 680px; + --cr-toolbar-field-max-width: var(--cr-toolbar-center-basis); +@@ -87,6 +92,13 @@ + checked="[[inDevMode]]" aria-labelledby="devModeLabel"> + + ++
++ $i18n{toolbarExtensionUpdateEnabled} ++ ++ ++ ++ ++
+ +