CVE-2026-79226
Overview
Background
- Default search engine (DSE) propagation
- when Chrome forks/creates a new profile (e.g., during Dice sign-in interception), it copies the source profile’s DSE choice data into the new profile so both share the same default search.
- `ChoiceData`
- a struct capturing the DSE choice (timestamp, Chrome version, and
TemplateURLDatafor the default engine) to be replayed into the new profile. - `default_search_provider_source()`
- reports where the active default search provider came from:
FROM_USER,FROM_FALLBACK,FROM_EXTENSION,FROM_POLICY, orFROM_POLICY_RECOMMENDED. - Extension/policy-controlled DSE
- a default search engine imposed by an installed extension or an enterprise policy rather than chosen by the user.
Root Cause Analysis
SearchEngineChoiceDialogService::GetChoiceDataFromProfile captured the active default search engine from the source profile unconditionally, returning a ChoiceData regardless of whether the default was set by the user, an extension, a policy, or the built-in fallback. SearchEngineChoiceDialogService::UpdateProfileFromChoiceData then wrote that captured engine into the newly created profile via SetUserSelectedDefaultSearchProvider, effectively converting an extension- or policy-scoped default into a user-selected default in a different profile. This broke the privilege/scope boundary: an extension’s or a specific profile’s policy-imposed search engine choice should not silently follow the user into a freshly forked profile where that extension/policy may not apply.
The fix makes GetChoiceDataFromProfile switch on default_search_provider_source() and return std::nullopt for FROM_FALLBACK, FROM_EXTENSION, FROM_POLICY, and FROM_POLICY_RECOMMENDED, propagating only genuine FROM_USER choices; UpdateProfileFromChoiceData now takes std::optional<ChoiceData> and early-returns when it is empty, so nothing is written when propagation is skipped.
default_search_provider_source() == FROM_USER and returns std::nullopt otherwise.Attack Path
- Impose a non-user default
An extension installed in the source profile (or an enterprise policy) sets the profile’s default search engine, so
default_search_provider_source()isFROM_EXTENSION(orFROM_POLICY). - Trigger profile forking
The user signs in such that Dice web-signin interception or
TurnSyncOnHelper::CreateNewSignedInProfilecreates a new profile and captures the source profile’s choice data. - Capture the imposed default
Pre-patch,
GetChoiceDataFromProfilereturns aChoiceDataholding the extension/policy-imposed engine without recording its source. - Persist as user-selected in the new profile
UpdateProfileFromChoiceDatacallsSetUserSelectedDefaultSearchProvideron the new profile, installing the imposed engine there as a user choice beyond the scope of the original extension/policy.
Impact Assessment
Search.ChoiceDebug.PropagatedDataOutcome histogram was added to measure the change.Changed Functions
| Function | Change | Notes |
|---|---|---|
GetChoiceDataFromProfilechrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc |
modified | |
ifchrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc |
modified |
Files Changed
tools/metrics/histograms/metadata/search/enums.xmlchrome/browser/ui/webui/signin/turn_sync_on_helper.ccchrome/browser/signin/dice_web_signin_interceptor.ccchrome/browser/signin/dice_web_signin_interceptor.hchrome/browser/search_engine_choice/search_engine_choice_dialog_service.cctools/metrics/histograms/metadata/search/histograms.xmlchrome/browser/search_engine_choice/search_engine_choice_dialog_service.hchrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.ccchrome/browser/ui/webui/signin/turn_sync_on_helper.h
Audit Directions
- Provenance-blind state copyingLook for code that copies user-settings state (search engine, homepage, permissions) between profiles or contexts without checking whether the setting was user-chosen versus extension/policy/fallback-imposed.
- Managed/extension-controlled prefs crossing boundariesAudit places that read an “active” value (default search, proxy, etc.) and persist it elsewhere as user-selected, bypassing the original controlling scope.
- Optional-return refactors as security fixesReview callers of
Get*FromProfile-style helpers to ensure a skipped/empty capture is handled with an early return rather than a default-constructed value being written downstream.
Patch
From 2e421e49757bf4231412a3f46d756df41df71272 Mon Sep 17 00:00:00 2001
From: Nicolas Dossou-Gbete <dgn@chromium.org>
Date: Fri, 03 Jul 2026 09:21:44 -0700
Subject: [PATCH] Reland "Avoid propagating extension or policy choice across profiles"
This is a reland of commit 1c6acb95514ada701e107ff963b8689281503d71
Fixes the test-specific compilation failure in ChromeForTesting builds,
un-guarding one of the test helper functions that the new tests start
using in CFT builds.
Original change's description:
> Avoid propagating extension or policy choice across profiles
>
> When capturing default search engine (DSE) choice data to propagate to
> forked profiles, skip the propagation for the cases where the active DSE
> is not set by the user (e.g. managed by extension or policies, or
> fallback)
>
> Adds the dedicated UMA histogram
> `Search.ChoiceDebug.PropagatedDataOutcome` to track the impact of this
> change.
>
> Bug: 513607252
> Change-Id: I86c1fdfb6cbe392e4cd7bb8d48dc179f5b07fcd8
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7986107
> Commit-Queue: Nicolas Dossou-Gbété <dgn@chromium.org>
> Reviewed-by: David Roger <droger@chromium.org>
> Reviewed-by: James Lee <ljjlee@google.com>
> Auto-Submit: Nicolas Dossou-Gbété <dgn@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1656537}
Bug: 513607252
Change-Id: I1b6cb0ec5fef5d6a1960ffb876a15064febad439
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8034248
Reviewed-by: James Lee <ljjlee@google.com>
Auto-Submit: Nicolas Dossou-Gbété <dgn@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Commit-Queue: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1656620}
---
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
index ad63ef3f..072359d2 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
@@ -292,49 +292,82 @@
}
// static
-search_engines::ChoiceData
+std::optional<search_engines::ChoiceData>
SearchEngineChoiceDialogService::GetChoiceDataFromProfile(Profile& profile) {
PrefService* pref_service = profile.GetPrefs();
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(&profile);
CHECK(template_url_service);
+
+ switch (template_url_service->default_search_provider_source()) {
+ case DefaultSearchManager::FROM_FALLBACK:
+ base::UmaHistogramEnumeration(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ CurrentDefaultPropagationOutcome::kSkippedIsFallback);
+ return std::nullopt;
+
+ case DefaultSearchManager::FROM_EXTENSION:
+ base::UmaHistogramEnumeration(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ CurrentDefaultPropagationOutcome::kSkippedIsExtension);
+ return std::nullopt;
+
+ case DefaultSearchManager::FROM_POLICY:
+ case DefaultSearchManager::FROM_POLICY_RECOMMENDED:
+ base::UmaHistogramEnumeration(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ CurrentDefaultPropagationOutcome::kSkippedDueToPolicies);
+ return std::nullopt;
+
+ case DefaultSearchManager::FROM_USER:
+ break; // Current default eligible for propagation.
+ }
+
+ CHECK(template_url_service->GetDefaultSearchProvider());
const TemplateURLData& default_search_engine =
template_url_service->GetDefaultSearchProvider()->data();
- return {.timestamp = pref_service->GetInt64(
- prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
- .chrome_version = pref_service->GetString(
- prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
- .default_search_engine = default_search_engine};
+ base::UmaHistogramEnumeration(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ CurrentDefaultPropagationOutcome::kPropagatedCurrentDefault);
+
+ return search_engines::ChoiceData{
+ .timestamp = pref_service->GetInt64(
+ prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
+ .chrome_version = pref_service->GetString(
+ prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
+ .default_search_engine = default_search_engine};
}
// static
void SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
Profile& profile,
- const search_engines::ChoiceData& choice_data) {
- PrefService* pref_service = profile.GetPrefs();
- if (choice_data.timestamp != 0) {
- pref_service->SetInt64(
- prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
- choice_data.timestamp);
- }
-
- if (!choice_data.chrome_version.empty()) {
- pref_service->SetString(
- prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
- choice_data.chrome_version);
+ const std::optional<search_engines::ChoiceData>& choice_data) {
+ if (!choice_data.has_value()) {
+ return;
}
const TemplateURLData& default_search_engine =
- choice_data.default_search_engine;
- if (!default_search_engine.keyword().empty() &&
- !default_search_engine.url().empty()) {
- TemplateURLService* template_url_service =
- TemplateURLServiceFactory::GetForProfile(&profile);
- CHECK(template_url_service);
- TemplateURL template_url(default_search_engine);
- template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
+ choice_data->default_search_engine;
+
+ PrefService* pref_service = profile.GetPrefs();
+ if (choice_data->timestamp != 0) {
+ pref_service->SetInt64(
+ prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
+ choice_data->timestamp);
}
+
+ if (!choice_data->chrome_version.empty()) {
+ pref_service->SetString(
+ prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
+ choice_data->chrome_version);
+ }
+
+ TemplateURLService* template_url_service =
+ TemplateURLServiceFactory::GetForProfile(&profile);
+ CHECK(template_url_service);
+ TemplateURL template_url(default_search_engine);
+ template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
}
TemplateURL::TemplateURLVector
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
index 6b83b61..9b9a9482 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
@@ -5,6 +5,7 @@
#ifndef CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
#define CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
+#include <optional>
#include <string>
#include "base/containers/flat_map.h"
@@ -152,12 +153,28 @@
static void SetDialogDisabledForTests(bool dialog_disabled);
// Returns a copy of the `ChoiceData` specific to `profile`.
- static search_engines::ChoiceData GetChoiceDataFromProfile(Profile& profile);
+ // LINT.IfChange(CurrentDefaultPropagationOutcome)
+ enum class CurrentDefaultPropagationOutcome {
+ kPropagatedCurrentDefault = 0,
+ kSkippedDueToPolicies = 1,
+ kSkippedIsFallback = 2,
+ kSkippedIsExtension = 3,
+ kMaxValue = kSkippedIsExtension,
+ };
+ // LINT.ThenChange(/tools/metrics/histograms/metadata/search/enums.xml:CurrentDefaultPropagationOutcome)
+
+ // Returns a copy of the `ChoiceData` specific to `profile`, or `std::nullopt`
+ // if there is no default search engine to propagate. This function ignores
+ // any extension-provided default search engine to capture the underlying user
+ // choice (or system default), and skips propagation entirely if the default
+ // search is managed by an enterprise policy.
+ static std::optional<search_engines::ChoiceData> GetChoiceDataFromProfile(
+ Profile& profile);
// Updates `profile` with the values from `choice_data`.
static void UpdateProfileFromChoiceData(
Profile& profile,
- const search_engines::ChoiceData& choice_data);
+ const std::optional<search_engines::ChoiceData>& choice_data);
private:
friend class SearchEngineChoiceDialogServiceFactory;
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index 00c14b72..c896aa38 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -413,6 +413,11 @@
DiceWebSigninInterceptor::ProfilePresets::ProfilePresets(SkColor profile_color)
: profile_color(profile_color) {}
+DiceWebSigninInterceptor::ProfilePresets::ProfilePresets(ProfilePresets&&) =
+ default;
+DiceWebSigninInterceptor::ProfilePresets&
+DiceWebSigninInterceptor::ProfilePresets::operator=(ProfilePresets&&) = default;
+
DiceWebSigninInterceptor::ProfilePresets::~ProfilePresets() = default;
// static
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index 22cc634..d81c43738 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -237,14 +237,14 @@
~ProfilePresets();
- ProfilePresets(ProfilePresets&&) = default;
- ProfilePresets& operator=(ProfilePresets&&) = default;
+ ProfilePresets(ProfilePresets&&);
+ ProfilePresets& operator=(ProfilePresets&&);
ProfilePresets(const ProfilePresets&) = delete;
ProfilePresets& operator=(ProfilePresets&) = delete;
SkColor profile_color = SK_ColorTRANSPARENT;
- search_engines::ChoiceData search_engine_choice_data;
+ std::optional<search_engines::ChoiceData> search_engine_choice_data;
};
// Cancels any current signin interception and resets the interceptor to its
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
index f86ec517..5f96c22 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
@@ -389,7 +389,7 @@
void TurnSyncOnHelper::CreateNewSignedInProfile() {
// Use the same the default search engine in the new profile.
- search_engines::ChoiceData search_engine_choice_data =
+ std::optional<search_engines::ChoiceData> search_engine_choice_data =
SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile_);
base::OnceCallback<void(Profile*)> profile_created_callback = base::BindOnce(
@@ -412,7 +412,7 @@
}
void TurnSyncOnHelper::OnNewSignedInProfileCreated(
- search_engines::ChoiceData search_engine_choice_data,
+ std::optional<search_engines::ChoiceData> search_engine_choice_data,
Profile* new_profile) {
DCHECK(dice_signed_in_profile_creator_);
dice_signed_in_profile_creator_.reset();
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
index 7b6a4b0..cc0a5d4 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_UI_WEBUI_SIGNIN_TURN_SYNC_ON_HELPER_H_
#include <memory>
+#include <optional>
#include <string>
#include "base/callback_list.h"
@@ -226,7 +227,7 @@
// Called when the new profile is created.
void OnNewSignedInProfileCreated(
- search_engines::ChoiceData search_engine_choice_data,
+ std::optional<search_engines::ChoiceData> search_engine_choice_data,
Profile* new_profile);
// Returns the SyncService, or nullptr if sync is not allowed.
diff --git a/tools/metrics/histograms/metadata/search/enums.xml b/tools/metrics/histograms/metadata/search/enums.xml
index 85abe60f..ff4c655e 100644
--- a/tools/metrics/histograms/metadata/search/enums.xml
+++ b/tools/metrics/histograms/metadata/search/enums.xml
@@ -214,6 +214,17 @@
<int value="3" label="Not seen, from Long-press"/>
</enum>
+<!-- LINT.IfChange(CurrentDefaultPropagationOutcome) -->
+
+<enum name="CurrentDefaultPropagationOutcome">
+ <int value="0" label="PropagatedCurrentDefault"/>
+ <int value="1" label="SkippedDueToPolicies"/>
+ <int value="2" label="SkippedIsFallback"/>
+ <int value="3" label="SkippedIsExtension"/>
+</enum>
+
+<!-- LINT.ThenChange(//chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h:CurrentDefaultPropagationOutcome) -->
+
<enum name="DonateResult">
<int value="0" label="No favicons available"/>
<int value="1" label="Failed"/>
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index cca1f13..749fba3b 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -386,6 +386,17 @@
</summary>
</histogram>
+<histogram name="Search.ChoiceDebug.PropagatedDataOutcome"
+ enum="CurrentDefaultPropagationOutcome" expires_after="2026-12-01">
+ <owner>dgn@chromium.org</owner>
+ <owner>chrome-regionalcapabilities@google.com</owner>
+ <summary>
+ Records the outcome of attempting to propagate the search engine choice data
+ from an existing profile to a newly created one. Recorded each time a
+ profile forks or creates a new profile while capturing preferences.
+ </summary>
+</histogram>
+
<histogram name="Search.ChoiceDebug.ScrollStateWithMoreButtonClick"
enum="SearchEngineChoiceScrollState" expires_after="2026-08-19">
<owner>dgn@chromium.org</owner>
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -19,6 +19,7 @@
#include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/country_codes/country_codes.h"
@@ -31,9 +32,11 @@
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/search_engines_test_util.h"
+#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_service.h"
#include "components/signin/public/base/signin_switches.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -63,7 +66,6 @@
constexpr CountryId kBelgiumCountryId("BE");
-#if !BUILDFLAG(CHROME_FOR_TESTING)
void SetUserSelectedDefaultSearchProvider(
TemplateURLService* template_url_service,
bool created_by_policy) {
@@ -89,6 +91,7 @@
template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
}
+#if !BUILDFLAG(CHROME_FOR_TESTING)
struct TestParam {
std::string test_suffix;
std::optional<regional_capabilities::SearchEngineCountryListOverride>
@@ -549,3 +552,141 @@
ASSERT_EQ(search_engine_choice_dialog_service, nullptr);
}
#endif // !BUILDFLAG(CHROME_FOR_TESTING)
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfilePropagatesCurrentDefault) {
+ base::HistogramTester histogram_tester;
+ TemplateURLService* source_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ source_template_url_service);
+
+ // Set a user-selected default search provider to ensure the source is
+ // FROM_USER.
+ SetUserSelectedDefaultSearchProvider(source_template_url_service,
+ /*created_by_policy=*/false);
+
+ const TemplateURL* underlying_default =
+ source_template_url_service->GetDefaultSearchProvider();
+ ASSERT_TRUE(underlying_default);
+ const std::string underlying_default_url = underlying_default->url();
+
+ std::optional<search_engines::ChoiceData> choice_data =
+ SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+ ASSERT_TRUE(choice_data.has_value());
+ EXPECT_EQ(choice_data->default_search_engine.url(), underlying_default_url);
+
+ histogram_tester.ExpectUniqueSample(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+ kPropagatedCurrentDefault,
+ 1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfileIgnoresExtensionProvidedDefault) {
+ base::HistogramTester histogram_tester;
... (file diff truncated)
Regression Test / PoC
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -19,6 +19,7 @@
#include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/country_codes/country_codes.h"
@@ -31,9 +32,11 @@
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/search_engines_test_util.h"
+#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_service.h"
#include "components/signin/public/base/signin_switches.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -63,7 +66,6 @@
constexpr CountryId kBelgiumCountryId("BE");
-#if !BUILDFLAG(CHROME_FOR_TESTING)
void SetUserSelectedDefaultSearchProvider(
TemplateURLService* template_url_service,
bool created_by_policy) {
@@ -89,6 +91,7 @@
template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
}
+#if !BUILDFLAG(CHROME_FOR_TESTING)
struct TestParam {
std::string test_suffix;
std::optional<regional_capabilities::SearchEngineCountryListOverride>
@@ -549,3 +552,141 @@
ASSERT_EQ(search_engine_choice_dialog_service, nullptr);
}
#endif // !BUILDFLAG(CHROME_FOR_TESTING)
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfilePropagatesCurrentDefault) {
+ base::HistogramTester histogram_tester;
+ TemplateURLService* source_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ source_template_url_service);
+
+ // Set a user-selected default search provider to ensure the source is
+ // FROM_USER.
+ SetUserSelectedDefaultSearchProvider(source_template_url_service,
+ /*created_by_policy=*/false);
+
+ const TemplateURL* underlying_default =
+ source_template_url_service->GetDefaultSearchProvider();
+ ASSERT_TRUE(underlying_default);
+ const std::string underlying_default_url = underlying_default->url();
+
+ std::optional<search_engines::ChoiceData> choice_data =
+ SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+ ASSERT_TRUE(choice_data.has_value());
+ EXPECT_EQ(choice_data->default_search_engine.url(), underlying_default_url);
+
+ histogram_tester.ExpectUniqueSample(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+ kPropagatedCurrentDefault,
+ 1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfileIgnoresExtensionProvidedDefault) {
+ base::HistogramTester histogram_tester;
+ TemplateURLService* source_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ source_template_url_service);
+
+ // Install an extension-provided default search engine in the source profile.
+ std::unique_ptr<TemplateURLData> extension_data =
+ GenerateDummyTemplateURLData("extension");
+ source_template_url_service->Add(std::make_unique<TemplateURL>(
+ *extension_data, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
+ "extension_id", base::Time(), /*wants_to_be_default_engine=*/true));
+ SetExtensionDefaultSearchInPrefs(profile()->GetTestingPrefService(),
+ *extension_data);
+ ASSERT_TRUE(
+ source_template_url_service->IsExtensionControlledDefaultSearch());
+
+ // The choice data captured for propagation to a new profile should be absent
+ // for extensions under the new logic.
+ std::optional<search_engines::ChoiceData> choice_data =
+ SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+ EXPECT_EQ(choice_data, std::nullopt);
+
+ histogram_tester.ExpectUniqueSample(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+ kSkippedIsExtension,
+ 1);
+
+ // Propagating absent choice data should leave the destination profile's
+ // default search engine unchanged from its own default.
+ TestingProfile* destination_profile =
+ profile_manager()->CreateTestingProfile("Profile 2");
+ TemplateURLServiceFactory::GetInstance()->SetTestingFactory(
+ destination_profile,
+ base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+ TemplateURLService* destination_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(destination_profile);
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ destination_template_url_service);
+
+ const std::string destination_default_url =
+ destination_template_url_service->GetDefaultSearchProvider()->url();
+
+ SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
+ *destination_profile, choice_data);
+ EXPECT_FALSE(
+ destination_template_url_service->IsExtensionControlledDefaultSearch());
+ EXPECT_EQ(destination_template_url_service->GetDefaultSearchProvider()->url(),
+ destination_default_url);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfileSkipsIfNoUnderlyingDefault) {
+ base::HistogramTester histogram_tester;
+ TemplateURLService* source_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ source_template_url_service);
+
+ // When no user choice is made, a fresh profile uses the fallback search
+ // engine, which skips under the new FROM_FALLBACK policy.
+ std::optional<search_engines::ChoiceData> choice_data =
+ SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+ EXPECT_EQ(choice_data, std::nullopt);
+
+ histogram_tester.ExpectUniqueSample(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+ kSkippedIsFallback,
+ 1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+ GetChoiceDataFromProfileSkipsIfManagedByPolicy) {
+ base::HistogramTester histogram_tester;
+ TemplateURLService* source_template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ search_test_utils::WaitForTemplateURLServiceToLoad(
+ source_template_url_service);
+
+ TemplateURLData data;
+ data.SetShortName(u"policy");
+ data.SetKeyword(u"policy");
+ data.SetURL("https://policy/url?bar={searchTerms}");
+ profile()->GetTestingPrefService()->SetManagedPref(
+ DefaultSearchManager::kDefaultSearchProviderDataPrefName,
+ TemplateURLDataToDictionary(data));
+
+ ASSERT_TRUE(source_template_url_service->is_default_search_managed());
+
+ std::optional<search_engines::ChoiceData> choice_data =
+ SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+ EXPECT_EQ(choice_data, std::nullopt);
+
+ histogram_tester.ExpectUniqueSample(
+ "Search.ChoiceDebug.PropagatedDataOutcome",
+ SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+ kSkippedDueToPolicies,
+ 1);
+
+ // Clear for subsequent tests.
+ profile()->GetTestingPrefService()->RemoveManagedPref(
+ DefaultSearchManager::kDefaultSearchProviderDataPrefName);
+}