blob: d61d5d4e6e3e1b84eccce1c74cfb6c00e696e8cd [file] [log] [blame]
// 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/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/extension_web_ui.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/extensions/controlled_home_bubble_delegate.h"
#include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
#include "chrome/common/extensions/manifest_handlers/settings_overrides_handler.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/google/core/common/google_util.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_data_util.h"
#include "components/search_engines/template_url_service.h"
#include "components/url_formatter/elide_url.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_url_handler.h"
#include "extensions/browser/extension_pref_value_map.h"
#include "extensions/browser/extension_pref_value_map_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "ui/base/l10n/l10n_util.h"
namespace settings_overridden_params {
namespace {
// Returns the number of extensions that are currently enabled that override the
// default search setting.
size_t GetNumberOfExtensionsThatOverrideSearch(Profile* profile) {
const auto* const registry = extensions::ExtensionRegistry::Get(profile);
const auto overrides_search = [](auto extension) {
auto* const settings = extensions::SettingsOverrides::Get(extension.get());
return settings && settings->search_engine;
};
return std::ranges::count_if(registry->enabled_extensions(),
overrides_search);
}
// Returns true if the given |template_url| corresponds to Google search.
bool IsGoogleSearch(const TemplateURL& template_url,
const TemplateURLService& template_url_service) {
GURL search_url =
template_url.GenerateSearchURL(template_url_service.search_terms_data());
return google_util::IsGoogleSearchUrl(search_url);
}
// Returns true if Google is the default search provider.
bool GoogleIsDefaultSearchProvider(Profile* profile) {
const TemplateURLService* const template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
const TemplateURL* const default_search =
template_url_service->GetDefaultSearchProvider();
if (!default_search) {
// According to TemplateURLService, |default_search| can be null if the
// default search engine is disabled by policy.
return false;
}
return IsGoogleSearch(*default_search, *template_url_service);
}
struct SecondarySearchInfo {
enum class Type {
// Google is the secondary search engine.
kGoogle,
// The secondary search is one of the default-populated searches, but is
// not Google.
kNonGoogleInDefaultList,
// Some other search engine is the secondary search.
kOther,
};
Type type;
// The origin of the search engine. Only populated if the secondary search
// is not from another extension.
GURL origin;
// The name of the search engine; only populated when |type| is
// kNonGoogleInDefaultList.
std::u16string name;
};
// Returns details about the search that would take over, if the currently-
// controlling extension were to be disabled.
SecondarySearchInfo GetSecondarySearchInfo(Profile* profile) {
// First, check if there's another extension that would take over.
size_t num_overriding_extensions =
GetNumberOfExtensionsThatOverrideSearch(profile);
// This method should only be called when there's an extension that overrides
// the search engine.
DCHECK_GE(num_overriding_extensions, 1u);
// Another extension would take over.
std::optional<const TemplateURL> secondary_extension_search;
if (num_overriding_extensions > 1) {
const std::string& search_pref_key =
DefaultSearchManager::kDefaultSearchProviderDataPrefName;
ExtensionPrefValueMap* extension_prefs_value_map =
ExtensionPrefValueMapFactory::GetForBrowserContext(profile);
std::string primary_ext_id =
extension_prefs_value_map->GetExtensionControllingPref(search_pref_key);
const base::Value* secondary_pref =
extension_prefs_value_map->GetEffectivePrefValue(
search_pref_key, profile->IsIncognitoProfile(),
/* from_incognito= */ nullptr,
/* ignore_extension_id= */ primary_ext_id);
if (secondary_pref) {
std::unique_ptr<TemplateURLData> url_data =
TemplateURLDataFromDictionary(secondary_pref->GetDict());
if (url_data) {
secondary_extension_search.emplace(std::move(*url_data));
}
}
}
const TemplateURLService* const template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
const TemplateURL* const secondary_search =
secondary_extension_search
? &(*secondary_extension_search)
: template_url_service->GetDefaultSearchProviderIgnoringExtensions();
if (!secondary_search) {
// We couldn't find a default (this could potentially happen if e.g. the
// default search engine is disabled by policy).
// TODO(devlin): It *seems* like in that case, extensions also shouldn't be
// able to override it. Investigate.
return {SecondarySearchInfo::Type::kOther};
}
const GURL search_url = secondary_search->GenerateSearchURL(
template_url_service->search_terms_data());
const GURL origin = search_url.DeprecatedGetOriginAsURL();
if (google_util::IsGoogleSearchUrl(search_url)) {
return {SecondarySearchInfo::Type::kGoogle, origin};
}
if (!template_url_service->ShowInDefaultList(secondary_search)) {
// Found another search engine, but it's not one of the default options.
return {SecondarySearchInfo::Type::kOther, origin};
}
// The secondary search engine is another of the defaults.
return {SecondarySearchInfo::Type::kNonGoogleInDefaultList, origin,
secondary_search->short_name()};
}
} // namespace
std::optional<ExtensionSettingsOverriddenDialog::Params> GetNtpOverriddenParams(
Profile* profile) {
const GURL ntp_url(chrome::kChromeUINewTabURL);
const extensions::Extension* extension =
ExtensionWebUI::GetExtensionControllingURL(ntp_url, profile);
if (!extension) {
return std::nullopt;
}
// This preference tracks whether users have acknowledged the extension's
// control, so that they are not warned twice about the same extension.
const char* preference_name = extensions::kNtpOverridingExtensionAcknowledged;
std::vector<GURL> possible_rewrites =
content::BrowserURLHandler::GetInstance()->GetPossibleRewrites(ntp_url,
profile);
// We already know that the extension is the primary NTP controller.
DCHECK(!possible_rewrites.empty());
DCHECK_EQ(extension->url().host_piece(), possible_rewrites[0].host_piece())
<< "Unexpected NTP URL: " << possible_rewrites[0];
// Find whether the default NTP would take over if the extension were to be
// removed. This might not be the case if, e.g. an enterprise policy set the
// NTP or the default search provided its own.
bool default_ntp_is_secondary = true;
if (possible_rewrites.size() > 1) {
default_ntp_is_secondary =
possible_rewrites[1] == ntp_url ||
possible_rewrites[1] == GURL(chrome::kChromeUINewTabPageURL) ||
possible_rewrites[1] == GURL(chrome::kChromeUINewTabPageThirdPartyURL);
}
// Check if there's another extension that would take over (this isn't
// included in BrowserURLHandler::GetPossibleRewrites(), which only takes the
// highest-priority from each source).
default_ntp_is_secondary &=
ExtensionWebUI::GetNumberOfExtensionsOverridingURL(ntp_url, profile) == 1;
// We show different dialogs based on whether the NTP would return to the
// default Chrome NTP with Google search.
bool use_back_to_google_messaging =
default_ntp_is_secondary && GoogleIsDefaultSearchProvider(profile);
constexpr char kGenericDialogHistogramName[] =
"Extensions.SettingsOverridden.GenericNtpOverriddenDialogResult";
constexpr char kBackToGoogleDialogHistogramName[] =
"Extensions.SettingsOverridden.BackToGoogleNtpOverriddenDialogResult";
std::u16string dialog_title;
const char* histogram_name = nullptr;
const gfx::VectorIcon* icon = nullptr;
if (use_back_to_google_messaging) {
dialog_title = l10n_util::GetStringUTF16(
IDS_EXTENSION_NTP_OVERRIDDEN_DIALOG_TITLE_BACK_TO_GOOGLE);
histogram_name = kBackToGoogleDialogHistogramName;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
icon = &vector_icons::kGoogleGLogoIcon;
#endif
} else {
dialog_title = l10n_util::GetStringUTF16(
IDS_EXTENSION_NTP_OVERRIDDEN_DIALOG_TITLE_GENERIC);
histogram_name = kGenericDialogHistogramName;
}
DCHECK(!dialog_title.empty());
DCHECK(histogram_name);
std::u16string dialog_message = l10n_util::GetStringFUTF16(
IDS_EXTENSION_NTP_OVERRIDDEN_DIALOG_BODY_GENERIC,
extensions::util::GetFixupExtensionNameForUIDisplay(extension->name()));
return ExtensionSettingsOverriddenDialog::Params(
extension->id(), preference_name, histogram_name, std::move(dialog_title),
std::move(dialog_message), icon);
}
std::optional<ExtensionSettingsOverriddenDialog::Params>
GetSearchOverriddenParams(Profile* profile) {
const extensions::Extension* extension =
extensions::GetExtensionOverridingSearchEngine(profile);
if (!extension) {
return std::nullopt;
}
// For historical reasons, the search override preference is the same as the
// one we use for the controlled home setting. We continue this so that
// users won't see the bubble or dialog UI if they've already acknowledged
// an older version.
const char* preference_name =
ControlledHomeBubbleDelegate::kAcknowledgedPreference;
// Find the active search engine (which is provided by the extension).
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
DCHECK(template_url_service->IsExtensionControlledDefaultSearch());
const TemplateURL* default_search =
template_url_service->GetDefaultSearchProvider();
DCHECK(default_search);
DCHECK_EQ(TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
default_search->type());
// NOTE: For most TemplateURLs, there's no guarantee that search_url is a
// valid URL (it could contain placeholders, etc). However, for extension-
// provided search engines, we require they be valid URLs.
GURL search_url(default_search->url());
DCHECK(search_url.is_valid()) << default_search->url();
// Check whether the secondary search is the same search the extension set.
// This can happen if the user set a search engine, and then installed an
// extension that set the same one.
SecondarySearchInfo secondary_search = GetSecondarySearchInfo(profile);
// NOTE: Normally, we wouldn't want to use direct equality comparison of
// GURL::GetOrigin() because of edge cases like inner URLs with filesystem,
// etc. This okay here, because if the origins don't match, we'll show the
// dialog to the user. That's likely good if any extension is doing something
// as crazy as using filesystem: URLs as a search engine.
if (!secondary_search.origin.is_empty() &&
secondary_search.origin == search_url.DeprecatedGetOriginAsURL()) {
return std::nullopt;
}
// Format the URL for display.
std::u16string formatted_search_url =
url_formatter::FormatUrlForDisplayOmitSchemePathAndTrivialSubdomains(
search_url);
constexpr char kGenericDialogHistogramName[] =
"Extensions.SettingsOverridden.GenericSearchOverriddenDialogResult";
constexpr char kBackToOtherHistogramName[] =
"Extensions.SettingsOverridden.BackToOtherSearchOverriddenDialogResult";
constexpr char kBackToGoogleHistogramName[] =
"Extensions.SettingsOverridden.BackToGoogleSearchOverriddenDialogResult";
const char* histogram_name = nullptr;
const gfx::VectorIcon* icon = nullptr;
std::u16string dialog_title;
switch (secondary_search.type) {
case SecondarySearchInfo::Type::kGoogle:
histogram_name = kBackToGoogleHistogramName;
dialog_title = l10n_util::GetStringUTF16(
IDS_EXTENSION_SEARCH_OVERRIDDEN_DIALOG_TITLE_BACK_TO_GOOGLE);
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
icon = &vector_icons::kGoogleGLogoIcon;
#endif
break;
case SecondarySearchInfo::Type::kNonGoogleInDefaultList:
DCHECK(!secondary_search.name.empty());
histogram_name = kBackToOtherHistogramName;
dialog_title = l10n_util::GetStringFUTF16(
IDS_EXTENSION_SEARCH_OVERRIDDEN_DIALOG_TITLE_BACK_TO_OTHER,
secondary_search.name);
break;
case SecondarySearchInfo::Type::kOther:
histogram_name = kGenericDialogHistogramName;
dialog_title = l10n_util::GetStringUTF16(
IDS_EXTENSION_SEARCH_OVERRIDDEN_DIALOG_TITLE_GENERIC);
break;
}
std::u16string dialog_message = l10n_util::GetStringFUTF16(
IDS_EXTENSION_SEARCH_OVERRIDDEN_DIALOG_BODY_GENERIC, formatted_search_url,
extensions::util::GetFixupExtensionNameForUIDisplay(extension->name()));
return ExtensionSettingsOverriddenDialog::Params(
extension->id(), preference_name, histogram_name, std::move(dialog_title),
std::move(dialog_message), icon);
}
} // namespace settings_overridden_params