| // Copyright 2020 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/ui/extensions/settings_overridden_params_providers.h" |
| |
| #include <algorithm> |
| |
| #include "base/containers/contains.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/extensions/settings_api_helpers.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/search_test_utils.h" |
| #include "components/search_engines/search_engines_test_util.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/test/test_extension_dir.h" |
| |
| class SettingsOverriddenParamsProvidersBrowserTest |
| : public extensions::ExtensionBrowserTest { |
| public: |
| void SetUpOnMainThread() override { |
| extensions::ExtensionBrowserTest::SetUpOnMainThread(); |
| search_test_utils::WaitForTemplateURLServiceToLoad( |
| TemplateURLServiceFactory::GetForProfile(browser()->profile())); |
| } |
| |
| // Installs a new extension that controls the default search engine. |
| const extensions::Extension* AddExtensionControllingSearch( |
| const char* path = "search_provider_override") { |
| const extensions::Extension* extension = |
| InstallExtensionWithPermissionsGranted(test_data_dir_.AppendASCII(path), |
| 1); |
| EXPECT_EQ(extension, |
| extensions::GetExtensionOverridingSearchEngine(profile())); |
| return extension; |
| } |
| |
| // Installs a new extension that controls the new tab page. |
| const extensions::Extension* AddExtensionControllingNewTab() { |
| const extensions::Extension* extension = |
| InstallExtensionWithPermissionsGranted( |
| test_data_dir_.AppendASCII("api_test/override/newtab"), 1); |
| EXPECT_EQ(extension, |
| extensions::GetExtensionOverridingNewTabPage(profile())); |
| return extension; |
| } |
| |
| // Sets a new default search provider. The new search provider will be one |
| // shows in the default search provider list iff |
| // |new_search_shows_in_default_list| is true. If non-null, |
| // |new_search_name_out| will be populated with the new search provider's |
| // name. |
| void SetNewDefaultSearch(bool new_search_shows_in_default_list, |
| const TemplateURL** new_turl_out) { |
| // Find a search provider that isn't Google, and set it as the default. |
| TemplateURLService* const template_url_service = GetTemplateURLService(); |
| TemplateURLService::TemplateURLVector template_urls = |
| template_url_service->GetTemplateURLs(); |
| auto iter = std::ranges::find_if( |
| template_urls, [template_url_service, new_search_shows_in_default_list]( |
| const TemplateURL* turl) { |
| return !turl->HasGoogleBaseURLs( |
| template_url_service->search_terms_data()) && |
| template_url_service->ShowInDefaultList(turl) == |
| new_search_shows_in_default_list; |
| }); |
| ASSERT_NE(template_urls.end(), iter); |
| // iter != template_urls.end()); |
| template_url_service->SetUserSelectedDefaultSearchProvider(*iter); |
| if (new_turl_out) { |
| *new_turl_out = *iter; |
| } |
| } |
| |
| TemplateURLService* GetTemplateURLService() { |
| return TemplateURLServiceFactory::GetForProfile(profile()); |
| } |
| }; |
| |
| // The chrome_settings_overrides API that allows extensions to override the |
| // default search provider is only available on Windows and Mac. |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| |
| // NOTE: It's very unfortunate that this has to be a browsertest. Unfortunately, |
| // a few bits here - the TemplateURLService in particular - don't play nicely |
| // with a unittest environment. |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| GetExtensionControllingSearch) { |
| // With no extensions installed, there should be no controlling extension. |
| EXPECT_EQ(std::nullopt, |
| settings_overridden_params::GetSearchOverriddenParams(profile())); |
| |
| // Install an extension, but not one that overrides the default search engine. |
| // There should still be no controlling extension. |
| InstallExtensionWithPermissionsGranted( |
| test_data_dir_.AppendASCII("simple_with_icon"), 1); |
| EXPECT_EQ(std::nullopt, |
| settings_overridden_params::GetSearchOverriddenParams(profile())); |
| |
| // Finally, install an extension that overrides the default search engine. |
| // It should be the controlling extension. |
| const extensions::Extension* search_extension = |
| AddExtensionControllingSearch(); |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ(search_extension->id(), params->controlling_extension_id); |
| |
| EXPECT_EQ(u"Change back to Google Search?", params->dialog_title); |
| |
| // Validate the body message, since it has a bit of formatting applied. |
| EXPECT_EQ( |
| u"The \"Search Override Extension\" extension changed search to use " |
| "example.com", |
| params->dialog_message); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| LongNameExtensionControllingSearch) { |
| // With no extensions installed, there should be no controlling extension. |
| ASSERT_EQ(std::nullopt, |
| settings_overridden_params::GetSearchOverriddenParams(profile())); |
| |
| // Install an extensions which overrides the default search engine and has a |
| // long name. |
| const extensions::Extension* search_extension = |
| AddExtensionControllingSearch("search_provider_override_long_name"); |
| std::optional<const ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ(search_extension->id(), params->controlling_extension_id); |
| |
| const std::u16string extension_name = |
| base::UTF8ToUTF16(search_extension->name()); |
| const std::u16string truncated_name = |
| extensions::util::GetFixupExtensionNameForUIDisplay(extension_name); |
| ASSERT_LT(truncated_name.size(), extension_name.size()); |
| |
| // The dialog message should contain the truncated name. |
| EXPECT_TRUE(base::Contains(params->dialog_message, truncated_name)); |
| EXPECT_FALSE(base::Contains(params->dialog_message, extension_name)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| GetExtensionControllingSearch_NonGoogleSearch) { |
| constexpr bool kNewSearchShowsInDefaultList = true; |
| const TemplateURL* new_turl = nullptr; |
| SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl); |
| ASSERT_TRUE(new_turl); |
| std::string new_search_name = base::UTF16ToUTF8(new_turl->short_name()); |
| |
| const extensions::Extension* extension = AddExtensionControllingSearch(); |
| ASSERT_TRUE(extension); |
| |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ(base::StringPrintf("Change back to %s?", new_search_name.c_str()), |
| base::UTF16ToUTF8(params->dialog_title)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| GetExtensionControllingSearch_NonDefaultSearch) { |
| // Create and set a search provider that isn't one of the built-in default |
| // options. |
| TemplateURLService* const template_url_service = GetTemplateURLService(); |
| template_url_service->Add( |
| std::make_unique<TemplateURL>(*GenerateDummyTemplateURLData("test"))); |
| |
| constexpr bool kNewSearchShowsInDefaultList = false; |
| SetNewDefaultSearch(kNewSearchShowsInDefaultList, nullptr); |
| |
| const extensions::Extension* extension = AddExtensionControllingSearch(); |
| ASSERT_TRUE(extension); |
| |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ("Did you mean to change your search provider?", |
| base::UTF16ToUTF8(params->dialog_title)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| SettingsOverriddenParamsProvidersBrowserTest, |
| GetExtensionControllingSearch_MultipleSearchProvidingExtensions) { |
| const extensions::Extension* first_extension = |
| AddExtensionControllingSearch(); |
| ASSERT_TRUE(first_extension); |
| |
| extensions::TestExtensionDir second_extension_dir; |
| second_extension_dir.WriteManifest( |
| R"({ |
| "name": "Simple Search Override", |
| "version": "0.1", |
| "manifest_version": 3, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "https://example.com/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| } |
| })"); |
| const extensions::Extension* second_extension = |
| InstallExtensionWithPermissionsGranted( |
| second_extension_dir.UnpackedPath(), 1); |
| ASSERT_TRUE(second_extension); |
| |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ(u"Did you mean to change your search provider?", |
| params->dialog_title); |
| } |
| |
| // Tests that null params are returned (indicating no dialog should be shown) |
| // when an extension overrides search to the same domain that was previously |
| // used using a prepopulated id. |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| SearchOverriddenToSameSearch_PrepopulatedId) { |
| constexpr bool kNewSearchShowsInDefaultList = true; |
| const TemplateURL* new_turl = nullptr; |
| SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl); |
| ASSERT_TRUE(new_turl); |
| // Google's ID is the lowest valid ID (1); the new engine must be greater. |
| constexpr int kGooglePrepopulateId = 1; |
| EXPECT_GT(new_turl->prepopulate_id(), kGooglePrepopulateId); |
| |
| constexpr char kManifestTemplate[] = |
| R"({ |
| "name": "Search Override Extension", |
| "version": "0.1", |
| "manifest_version": 2, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "%s/?q={searchTerms}", |
| "prepopulated_id": %d, |
| "is_default": true |
| } |
| }, |
| "permissions": ["storage"] |
| })"; |
| extensions::TestExtensionDir test_dir; |
| |
| GURL search_url = |
| new_turl->GenerateSearchURL(GetTemplateURLService()->search_terms_data()); |
| test_dir.WriteManifest(base::StringPrintf( |
| kManifestTemplate, search_url.DeprecatedGetOriginAsURL().spec().c_str(), |
| new_turl->prepopulate_id())); |
| |
| const extensions::Extension* extension = |
| InstallExtensionWithPermissionsGranted(test_dir.UnpackedPath(), 1); |
| ASSERT_TRUE(extension); |
| EXPECT_EQ(extension, |
| extensions::GetExtensionOverridingSearchEngine(profile())); |
| |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| EXPECT_FALSE(params) << "Unexpected params: " << params->dialog_title; |
| } |
| |
| // Tests that null params are returned (indicating no dialog should be shown) |
| // when an extension overrides search to the same domain that was previously |
| // used using a custom search definition. |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| SearchOverriddenToSameSearch_SameDomain) { |
| constexpr bool kNewSearchShowsInDefaultList = true; |
| const TemplateURL* new_turl = nullptr; |
| SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl); |
| ASSERT_TRUE(new_turl); |
| // Google's ID is the lowest valid ID (1); the new engine must be greater. |
| constexpr int kGooglePrepopulateId = 1; |
| EXPECT_GT(new_turl->prepopulate_id(), kGooglePrepopulateId); |
| |
| constexpr char kManifestTemplate[] = |
| R"({ |
| "name": "Search Override Extension", |
| "version": "0.1", |
| "manifest_version": 2, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "%s/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| }, |
| "permissions": ["storage"] |
| })"; |
| |
| extensions::TestExtensionDir test_dir; |
| |
| GURL search_url = |
| new_turl->GenerateSearchURL(GetTemplateURLService()->search_terms_data()); |
| test_dir.WriteManifest(base::StringPrintf( |
| kManifestTemplate, search_url.DeprecatedGetOriginAsURL().spec().c_str())); |
| |
| const extensions::Extension* extension = |
| InstallExtensionWithPermissionsGranted(test_dir.UnpackedPath(), 1); |
| ASSERT_TRUE(extension); |
| EXPECT_EQ(extension, |
| extensions::GetExtensionOverridingSearchEngine(profile())); |
| |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| EXPECT_FALSE(params) << "Unexpected params: " << params->dialog_title; |
| } |
| |
| // Tests that the settings overridden dialog isn't shown for a simple override |
| // extension, but would be if the extension is then updated to have more |
| // capabilities. |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| DialogNotShownForSimpleOverridesAndIsAfterUpdate) { |
| extensions::TestExtensionDir dir_v1; |
| static constexpr char kManifestV1[] = |
| R"({ |
| "name": "Search Override", |
| "version": "0.1", |
| "manifest_version": 3, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "https://example.com/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| } |
| })"; |
| dir_v1.WriteManifest(kManifestV1); |
| dir_v1.WriteFile(FILE_PATH_LITERAL("page.html"), "hello world!"); |
| |
| extensions::TestExtensionDir dir_v2; |
| static constexpr char kManifestV2[] = |
| R"({ |
| "name": "Search Override", |
| "version": "0.2", |
| "manifest_version": 3, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "https://example.com/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| }, |
| "permissions": ["storage"] |
| })"; |
| dir_v2.WriteManifest(kManifestV2); |
| dir_v2.WriteFile(FILE_PATH_LITERAL("page.html"), "hello world!"); |
| |
| // Borrow a .pem file to have consistent IDs in the .crx files. |
| base::FilePath pem_path = |
| test_data_dir_.AppendASCII("permissions/update.pem"); |
| |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir scoped_temp_dir; |
| EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDir()); |
| |
| base::FilePath v1_crx_path = PackExtensionWithOptions( |
| dir_v1.UnpackedPath(), scoped_temp_dir.GetPath().AppendASCII("v1.crx"), |
| pem_path, base::FilePath()); |
| base::FilePath v2_crx_path = PackExtensionWithOptions( |
| dir_v2.UnpackedPath(), scoped_temp_dir.GetPath().AppendASCII("v2.crx"), |
| pem_path, base::FilePath()); |
| |
| // Install v1 of the extension. Since this is a simple override, the dialog |
| // should not display. |
| const extensions::Extension* extension = |
| InstallExtensionWithPermissionsGranted(v1_crx_path, 1); |
| ASSERT_TRUE(extension); |
| |
| { |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| ExtensionSettingsOverriddenDialog controller(std::move(*params), profile()); |
| EXPECT_FALSE(controller.ShouldShow()); |
| } |
| |
| // Update the extension to v2. Now, the dialog *should* show, since the |
| // extension is no longer considered a simple override. |
| extension = UpdateExtension(extension->id(), v2_crx_path, 0); |
| EXPECT_TRUE(extension); |
| |
| { |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| ExtensionSettingsOverriddenDialog controller(std::move(*params), profile()); |
| EXPECT_TRUE(controller.ShouldShow()); |
| } |
| } |
| |
| // Tests that null params are returned (indicating no dialog should be shown) |
| // when an extension overrides search to the same domain that was previously |
| // set by another extension. |
| IN_PROC_BROWSER_TEST_F( |
| SettingsOverriddenParamsProvidersBrowserTest, |
| SearchOverriddenToSameSearch_SameDomainExistingExtensionOverride) { |
| // With no extensions installed, there should be no controlling extension. |
| EXPECT_EQ(std::nullopt, |
| settings_overridden_params::GetSearchOverriddenParams(profile())); |
| |
| // Install a simple search override extension, that shouldn't trigger the |
| // prompt. |
| extensions::TestExtensionDir extension_dir_1; |
| |
| extension_dir_1.WriteManifest( |
| R"({ |
| "name": "Simple Search Override", |
| "version": "0.1", |
| "manifest_version": 3, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "https://example.com/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| } |
| })"); |
| const extensions::Extension* extension_1 = |
| InstallExtensionWithPermissionsGranted(extension_dir_1.UnpackedPath(), 1); |
| |
| ASSERT_TRUE(extension_1); |
| EXPECT_EQ(extension_1, |
| extensions::GetExtensionOverridingSearchEngine(profile())); |
| { |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| ExtensionSettingsOverriddenDialog controller(std::move(*params), profile()); |
| EXPECT_FALSE(controller.ShouldShow()); |
| } |
| |
| // Install a second search override extension, that shouldn't trigger prompt |
| // since it sets the same search domain as the previous extension. |
| extensions::TestExtensionDir extension_dir_2; |
| extension_dir_2.WriteManifest( |
| R"({ |
| "name": "Second Search Override", |
| "version": "0.1", |
| "manifest_version": 3, |
| "chrome_settings_overrides": { |
| "search_provider": { |
| "search_url": "https://example.com/?q={searchTerms}", |
| "name": "New Search", |
| "keyword": "word", |
| "encoding": "UTF-8", |
| "favicon_url": "https://example.com/favicon.ico", |
| "is_default": true |
| } |
| }, |
| "permissions": ["storage"] |
| })"); |
| const extensions::Extension* extension_2 = |
| InstallExtensionWithPermissionsGranted(extension_dir_2.UnpackedPath(), 1); |
| |
| ASSERT_TRUE(extension_2); |
| EXPECT_EQ(extension_2, |
| extensions::GetExtensionOverridingSearchEngine(profile())); |
| { |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetSearchOverriddenParams(profile()); |
| EXPECT_FALSE(params) << "Unexpected params: " << params->dialog_title; |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| |
| // Tests the dialog display when the default search engine has changed; in this |
| // case, we should display the generic dialog. |
| IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest, |
| DialogParamsWithNonDefaultSearch) { |
| // Find a search provider that isn't Google, and set it as the default. |
| TemplateURLService* const template_url_service = GetTemplateURLService(); |
| TemplateURLService::TemplateURLVector template_urls = |
| template_url_service->GetTemplateURLs(); |
| auto iter = std::ranges::find_if_not( |
| template_urls, [template_url_service](const TemplateURL* turl) { |
| // For the test, we can be a bit lazier and just use HasGoogleBaseURLs() |
| // instead of getting the full search URL. |
| return turl->HasGoogleBaseURLs( |
| template_url_service->search_terms_data()); |
| }); |
| ASSERT_TRUE(iter != template_urls.end()); |
| template_url_service->SetUserSelectedDefaultSearchProvider(*iter); |
| |
| const extensions::Extension* extension = AddExtensionControllingNewTab(); |
| |
| // The dialog should be the generic version, rather than prompting to go back |
| // to the default. |
| std::optional<ExtensionSettingsOverriddenDialog::Params> params = |
| settings_overridden_params::GetNtpOverriddenParams(profile()); |
| ASSERT_TRUE(params); |
| EXPECT_EQ(extension->id(), params->controlling_extension_id); |
| EXPECT_EQ(u"Did you mean to change this page?", params->dialog_title); |
| } |