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} ++ ++ ++ ++ ++
+ +