diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-05-20 09:47:09 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-06-07 11:15:42 +0000 |
commit | 189d4fd8fad9e3c776873be51938cd31a42b6177 (patch) | |
tree | 6497caeff5e383937996768766ab3bb2081a40b2 /chromium/components/search_engines | |
parent | 8bc75099d364490b22f43a7ce366b366c08f4164 (diff) | |
download | qtwebengine-chromium-189d4fd8fad9e3c776873be51938cd31a42b6177.tar.gz |
BASELINE: Update Chromium to 90.0.4430.221
Change-Id: Iff4d9d18d2fcf1a576f3b1f453010f744a232920
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/search_engines')
23 files changed, 517 insertions, 424 deletions
diff --git a/chromium/components/search_engines/BUILD.gn b/chromium/components/search_engines/BUILD.gn index d95854ac609..c2b09f07178 100644 --- a/chromium/components/search_engines/BUILD.gn +++ b/chromium/components/search_engines/BUILD.gn @@ -68,6 +68,7 @@ static_library("search_engines") { "//components/database_utils", "//components/history/core/browser", "//components/infobars/core", + "//components/lens:lens", "//components/policy:generated", "//components/policy/core/browser", "//components/pref_registry", @@ -78,6 +79,7 @@ static_library("search_engines") { "//net", "//services/data_decoder/public/cpp", "//services/network/public/cpp", + "//services/network/public/mojom", "//sql", "//third_party/metrics_proto", "//ui/base", diff --git a/chromium/components/search_engines/DEPS b/chromium/components/search_engines/DEPS index c2e314ee180..68a433c82e9 100644 --- a/chromium/components/search_engines/DEPS +++ b/chromium/components/search_engines/DEPS @@ -5,6 +5,7 @@ include_rules = [ "+components/history/core", "+components/infobars/core", "+components/keyed_service/core", + "+components/lens", "+components/policy", "+components/pref_registry", "+components/prefs", @@ -18,6 +19,7 @@ include_rules = [ "+net", "+services/data_decoder/public", "+services/network/public/cpp", + "+services/network/public/mojom", "+services/network/test", "+sql", "+ui/base", diff --git a/chromium/components/search_engines/DIR_METADATA b/chromium/components/search_engines/DIR_METADATA new file mode 100644 index 00000000000..4bbdcaebcc8 --- /dev/null +++ b/chromium/components/search_engines/DIR_METADATA @@ -0,0 +1,3 @@ +monorail { + component: "UI>Browser>Search" +} diff --git a/chromium/components/search_engines/OWNERS b/chromium/components/search_engines/OWNERS index d645718719c..7d3da37e00f 100644 --- a/chromium/components/search_engines/OWNERS +++ b/chromium/components/search_engines/OWNERS @@ -1,5 +1,3 @@ file://components/omnibox/OWNERS vasilii@chromium.org - -# COMPONENT: UI>Browser>Search diff --git a/chromium/components/search_engines/android/BUILD.gn b/chromium/components/search_engines/android/BUILD.gn index 8d711dd3a2c..e80cb104c0b 100644 --- a/chromium/components/search_engines/android/BUILD.gn +++ b/chromium/components/search_engines/android/BUILD.gn @@ -9,7 +9,7 @@ android_library("java") { "//base:base_java", "//base:jni_java", "//content/public/android:content_java", - "//third_party/android_deps:androidx_annotation_annotation_java", + "//third_party/androidx:androidx_annotation_annotation_java", "//url:gurl_java", ] annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] diff --git a/chromium/components/search_engines/android/DIR_METADATA b/chromium/components/search_engines/android/DIR_METADATA new file mode 100644 index 00000000000..4bbdcaebcc8 --- /dev/null +++ b/chromium/components/search_engines/android/DIR_METADATA @@ -0,0 +1,3 @@ +monorail { + component: "UI>Browser>Search" +} diff --git a/chromium/components/search_engines/android/OWNERS b/chromium/components/search_engines/android/OWNERS index 52b99d7e26c..697c69edec7 100644 --- a/chromium/components/search_engines/android/OWNERS +++ b/chromium/components/search_engines/android/OWNERS @@ -1,3 +1 @@ file://chrome/android/OWNERS - -# COMPONENT: UI>Browser>Search diff --git a/chromium/components/search_engines/android/template_url_service_android.cc b/chromium/components/search_engines/android/template_url_service_android.cc index ab52b89d5eb..70828e0531a 100644 --- a/chromium/components/search_engines/android/template_url_service_android.cc +++ b/chromium/components/search_engines/android/template_url_service_android.cc @@ -36,8 +36,8 @@ TemplateUrlServiceAndroid::TemplateUrlServiceAndroid( TemplateURLService* template_url_service) : template_url_service_(template_url_service) { template_url_subscription_ = template_url_service_->RegisterOnLoadedCallback( - base::Bind(&TemplateUrlServiceAndroid::OnTemplateURLServiceLoaded, - base::Unretained(this))); + base::BindOnce(&TemplateUrlServiceAndroid::OnTemplateURLServiceLoaded, + base::Unretained(this))); template_url_service_->AddObserver(this); } @@ -119,7 +119,7 @@ TemplateUrlServiceAndroid::IsSearchResultsPageFromDefaultSearchProvider( } void TemplateUrlServiceAndroid::OnTemplateURLServiceLoaded() { - template_url_subscription_.reset(); + template_url_subscription_ = {}; JNIEnv* env = base::android::AttachCurrentThread(); if (!java_ref_) return; @@ -289,8 +289,19 @@ jboolean TemplateUrlServiceAndroid::SetPlayAPISearchEngine( auto existing_play_api_turl = std::find_if( template_urls.cbegin(), template_urls.cend(), [](const TemplateURL* turl) { return turl->created_from_play_api(); }); - if (existing_play_api_turl != template_urls.cend()) + if (existing_play_api_turl != template_urls.cend()) { + // Migrate old Play API database entries that were incorrectly marked as + // safe_for_autoreplace() before M89. + // TODO(tommycli): Delete this once the below metric approaches zero. + TemplateURL* turl = *existing_play_api_turl; + if (turl->safe_for_autoreplace()) { + TemplateURLService::LogSearchTemplateURLEvent( + TemplateURLService::MIGRATE_SAFE_FOR_AUTOREPLACE_PLAY_API_ENGINE); + template_url_service_->ResetTemplateURL(turl, turl->short_name(), + turl->keyword(), turl->url()); + } return false; + } base::string16 keyword = base::android::ConvertJavaStringToUTF16(env, jkeyword); @@ -305,12 +316,14 @@ jboolean TemplateUrlServiceAndroid::SetPlayAPISearchEngine( favicon_url = base::android::ConvertJavaStringToUTF8(jfavicon_url); } - TemplateURL* t_url = - template_url_service_->CreateOrUpdateTemplateURLFromPlayAPIData( - name, keyword, search_url, suggest_url, favicon_url); + TemplateURL* t_url = template_url_service_->CreatePlayAPISearchEngine( + name, keyword, search_url, suggest_url, favicon_url); - if (set_as_default && template_url_service_->CanMakeDefault(t_url)) + // CanMakeDefault() will prevent us from taking over a policy or extension + // defined default search engine. + if (set_as_default && template_url_service_->CanMakeDefault(t_url)) { template_url_service_->SetUserSelectedDefaultSearchProvider(t_url); + } return true; } diff --git a/chromium/components/search_engines/android/template_url_service_android.h b/chromium/components/search_engines/android/template_url_service_android.h index 715bbeb2bba..4174566f3e5 100644 --- a/chromium/components/search_engines/android/template_url_service_android.h +++ b/chromium/components/search_engines/android/template_url_service_android.h @@ -122,7 +122,7 @@ class TemplateUrlServiceAndroid : public TemplateURLServiceObserver { // Pointer to the TemplateUrlService for the main profile. TemplateURLService* template_url_service_; - std::unique_ptr<TemplateURLService::Subscription> template_url_subscription_; + base::CallbackListSubscription template_url_subscription_; DISALLOW_COPY_AND_ASSIGN(TemplateUrlServiceAndroid); }; diff --git a/chromium/components/search_engines/default_search_manager.cc b/chromium/components/search_engines/default_search_manager.cc index 82f5878b84a..28c6f03ff7c 100644 --- a/chromium/components/search_engines/default_search_manager.cc +++ b/chromium/components/search_engines/default_search_manager.cc @@ -87,12 +87,12 @@ DefaultSearchManager::DefaultSearchManager( pref_change_registrar_.Init(pref_service_); pref_change_registrar_.Add( kDefaultSearchProviderDataPrefName, - base::Bind(&DefaultSearchManager::OnDefaultSearchPrefChanged, - base::Unretained(this))); + base::BindRepeating(&DefaultSearchManager::OnDefaultSearchPrefChanged, + base::Unretained(this))); pref_change_registrar_.Add( prefs::kSearchProviderOverrides, - base::Bind(&DefaultSearchManager::OnOverridesPrefChanged, - base::Unretained(this))); + base::BindRepeating(&DefaultSearchManager::OnOverridesPrefChanged, + base::Unretained(this))); } LoadPrepopulatedDefaultSearch(); LoadDefaultSearchEngineFromPrefs(); diff --git a/chromium/components/search_engines/default_search_manager.h b/chromium/components/search_engines/default_search_manager.h index 5e4c6daa3ea..8c3f4000010 100644 --- a/chromium/components/search_engines/default_search_manager.h +++ b/chromium/components/search_engines/default_search_manager.h @@ -75,7 +75,8 @@ class DefaultSearchManager { FROM_POLICY, }; - typedef base::Callback<void(const TemplateURLData*, Source)> ObserverCallback; + using ObserverCallback = + base::RepeatingCallback<void(const TemplateURLData*, Source)>; DefaultSearchManager(PrefService* pref_service, const ObserverCallback& change_observer); diff --git a/chromium/components/search_engines/keyword_web_data_service.cc b/chromium/components/search_engines/keyword_web_data_service.cc index 265bbdd8426..4f2de437237 100644 --- a/chromium/components/search_engines/keyword_web_data_service.cc +++ b/chromium/components/search_engines/keyword_web_data_service.cc @@ -122,17 +122,17 @@ WebDataServiceBase::Handle KeywordWebDataService::GetKeywords( CommitQueuedOperations(); return wdbs_->ScheduleDBTaskWithResult( - FROM_HERE, base::Bind(&GetKeywordsImpl), consumer); + FROM_HERE, base::BindOnce(&GetKeywordsImpl), consumer); } void KeywordWebDataService::SetDefaultSearchProviderID(TemplateURLID id) { wdbs_->ScheduleDBTask(FROM_HERE, - base::Bind(&SetDefaultSearchProviderIDImpl, id)); + base::BindOnce(&SetDefaultSearchProviderIDImpl, id)); } void KeywordWebDataService::SetBuiltinKeywordVersion(int version) { wdbs_->ScheduleDBTask(FROM_HERE, - base::Bind(&SetBuiltinKeywordVersionImpl, version)); + base::BindOnce(&SetBuiltinKeywordVersionImpl, version)); } void KeywordWebDataService::ShutdownOnUISequence() { @@ -167,8 +167,9 @@ void KeywordWebDataService::AdjustBatchModeLevel(bool entering_batch_mode) { void KeywordWebDataService::CommitQueuedOperations() { if (!queued_keyword_operations_.empty()) { - wdbs_->ScheduleDBTask(FROM_HERE, base::Bind(&PerformKeywordOperationsImpl, - queued_keyword_operations_)); + wdbs_->ScheduleDBTask(FROM_HERE, + base::BindOnce(&PerformKeywordOperationsImpl, + queued_keyword_operations_)); queued_keyword_operations_.clear(); } timer_.Stop(); diff --git a/chromium/components/search_engines/prepopulated_engines.json b/chromium/components/search_engines/prepopulated_engines.json index cb4bc61f0c6..269a4897ffc 100644 --- a/chromium/components/search_engines/prepopulated_engines.json +++ b/chromium/components/search_engines/prepopulated_engines.json @@ -28,7 +28,7 @@ // Increment this if you change the data in ways that mean users with // existing data should get a new version. Otherwise, existing data may // continue to be used and updates made here will not always appear. - "kCurrentDataVersion": 123 + "kCurrentDataVersion": 125 }, // The following engines are included in country lists and are added to the @@ -116,9 +116,9 @@ "name": "Google", "keyword": "google.com", "favicon_url": "https://www.google.com/images/branding/product/ico/googleg_lodp.ico", - "search_url": "{google:baseURL}search?q={searchTerms}&{google:RLZ}{google:originalQueryForSuggestion}{google:assistedQueryStats}{google:searchFieldtrialParameter}{google:iOSSearchLanguage}{google:searchClient}{google:sourceId}{google:contextualSearchVersion}ie={inputEncoding}", + "search_url": "{google:baseURL}search?q={searchTerms}&{google:RLZ}{google:originalQueryForSuggestion}{google:assistedQueryStats}{google:searchFieldtrialParameter}{google:iOSSearchLanguage}{google:prefetchSource}{google:searchClient}{google:sourceId}{google:contextualSearchVersion}ie={inputEncoding}", "suggest_url": "{google:baseSuggestURL}search?{google:searchFieldtrialParameter}client={google:suggestClient}&gs_ri={google:suggestRid}&xssi=t&q={searchTerms}&{google:inputType}{google:omniboxFocusType}{google:cursorPosition}{google:currentPageUrl}{google:pageClassification}{google:searchVersion}{google:sessionToken}{google:prefetchQuery}sugkey={google:suggestAPIKeyParameter}", - "image_url": "{google:baseURL}searchbyimage/upload", + "image_url": "{google:baseSearchByImageURL}upload", "contextual_search_url": "{google:baseURL}_/contextualsearch?{google:contextualSearchVersion}{google:contextualSearchContextData}", "image_url_post_params": "encoded_image={google:imageThumbnail},image_url={google:imageURL},sbisrc={google:imageSearchSource},original_width={google:imageOriginalWidth},original_height={google:imageOriginalHeight}", "alternate_urls": [ @@ -208,7 +208,7 @@ "alternate_urls": [ "https://m.sogou.com/web/{google:pathWildcard}?ie={inputEncoding}&keyword={searchTerms}" ], - "suggest_url": "http://api.sugg.sogou.com/su?type=addrbar&key={searchTerms}&ie={inputEncoding}", + "suggest_url": "https://sugg.sogou.com/sugg/ajaj_json.jsp?type=addrbar&key={searchTerms}&ie={inputEncoding}&from=google", "type": "SEARCH_ENGINE_SOGOU", "id": 56 }, diff --git a/chromium/components/search_engines/search_terms_data.cc b/chromium/components/search_engines/search_terms_data.cc index 0005ed87a8d..83965f45ac2 100644 --- a/chromium/components/search_engines/search_terms_data.cc +++ b/chromium/components/search_engines/search_terms_data.cc @@ -6,18 +6,26 @@ #include "base/check.h" #include "components/google/core/common/google_util.h" +#include "components/lens/lens_features.h" #include "url/gurl.h" -SearchTermsData::SearchTermsData() { -} +SearchTermsData::SearchTermsData() = default; -SearchTermsData::~SearchTermsData() { -} +SearchTermsData::~SearchTermsData() = default; std::string SearchTermsData::GoogleBaseURLValue() const { return google_util::kGoogleHomepageURL; } +std::string SearchTermsData::GoogleBaseSearchByImageURLValue() const { + const std::string kGoogleHomepageURLPath = std::string("searchbyimage/"); + + if (base::FeatureList::IsEnabled(lens::features::kLensStandalone)) { + return lens::features::GetHomepageURL(); + } + return google_util::kGoogleHomepageURL + kGoogleHomepageURLPath; +} + std::string SearchTermsData::GoogleBaseSuggestURLValue() const { // Start with the Google base URL. const GURL base_url(GoogleBaseURLValue()); diff --git a/chromium/components/search_engines/search_terms_data.h b/chromium/components/search_engines/search_terms_data.h index 6fc30745273..d6820917770 100644 --- a/chromium/components/search_engines/search_terms_data.h +++ b/chromium/components/search_engines/search_terms_data.h @@ -22,6 +22,11 @@ class SearchTermsData { // implementation simply returns the default value. virtual std::string GoogleBaseURLValue() const; + // Returns the value to use for the GOOGLE_BASE_SEARCH_BY_IMAGE_URL. Points + // at Lens if the user is enrolled in the Lens experiment, and defaults to + // Image Search otherwise. + virtual std::string GoogleBaseSearchByImageURLValue() const; + // Returns the value for the GOOGLE_BASE_SUGGEST_URL term. This // implementation simply returns the default value. std::string GoogleBaseSuggestURLValue() const; diff --git a/chromium/components/search_engines/template_url.cc b/chromium/components/search_engines/template_url.cc index f195c74444a..3e427599038 100644 --- a/chromium/components/search_engines/template_url.cc +++ b/chromium/components/search_engines/template_url.cc @@ -11,13 +11,13 @@ #include "base/base64.h" #include "base/check_op.h" #include "base/command_line.h" +#include "base/containers/contains.h" #include "base/format_macros.h" #include "base/i18n/case_conversion.h" #include "base/i18n/icu_string_conversions.h" #include "base/i18n/rtl.h" #include "base/metrics/field_trial.h" #include "base/notreached.h" -#include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" @@ -72,6 +72,8 @@ const char kDefaultCount[] = "10"; // Used if the output encoding parameter is required. const char kOutputEncodingType[] = "UTF-8"; +const size_t kMaxStringEncodeStringLength = 1'000'000; + // Attempts to encode |terms| and |original_query| in |encoding| and escape // them. |terms| may be escaped as path or query depending on |is_in_query|; // |original_query| is always escaped as query. If |force_encode| is true @@ -86,12 +88,23 @@ bool TryEncoding(const base::string16& terms, base::string16* escaped_original_query) { DCHECK(escaped_terms); DCHECK(escaped_original_query); + + // Both |base::UTF16ToCodepage()| and |net::Escape*()| invocations below + // create strings longer than their inputs. To ensure doing so does not crash, + // this truncates |terms| to |kMaxStringEncodeStringLength|. + const base::string16& truncated_terms = + terms.size() > kMaxStringEncodeStringLength + ? terms.substr(0, kMaxStringEncodeStringLength) + : terms; + base::OnStringConversionError::Type error_handling = force_encode ? base::OnStringConversionError::SKIP : base::OnStringConversionError::FAIL; std::string encoded_terms; - if (!base::UTF16ToCodepage(terms, encoding, error_handling, &encoded_terms)) + if (!base::UTF16ToCodepage(truncated_terms, encoding, error_handling, + &encoded_terms)) { return false; + } *escaped_terms = base::UTF8ToUTF16(is_in_query ? net::EscapeQueryParamValue(encoded_terms, true) : net::EscapePath(encoded_terms)); @@ -636,10 +649,11 @@ bool TemplateURLRef::ParseParameter(size_t start, length--; } - const base::StringPiece parameter(original_url.begin() + start + 1, - original_url.begin() + start + 1 + length); - const base::StringPiece full_parameter(original_url.begin() + start, - original_url.begin() + end + 1); + const auto parameter = + base::MakeStringPiece(original_url.begin() + start + 1, + original_url.begin() + start + 1 + length); + const auto full_parameter = base::MakeStringPiece( + original_url.begin() + start, original_url.begin() + end + 1); // Remove the parameter from the string. For parameters who replacement is // constant and already known, just replace them directly. For other cases, // like parameters whose values may change over time, use |replacements|. @@ -653,6 +667,9 @@ bool TemplateURLRef::ParseParameter(size_t start, replacements->push_back(Replacement(GOOGLE_ASSISTED_QUERY_STATS, start)); } else if (parameter == "google:baseURL") { replacements->push_back(Replacement(GOOGLE_BASE_URL, start)); + } else if (parameter == "google:baseSearchByImageURL") { + replacements->push_back( + Replacement(GOOGLE_BASE_SEARCH_BY_IMAGE_URL, start)); } else if (parameter == "google:baseSuggestURL") { replacements->push_back(Replacement(GOOGLE_BASE_SUGGEST_URL, start)); } else if (parameter == "google:currentPageUrl") { @@ -700,6 +717,8 @@ bool TemplateURLRef::ParseParameter(size_t start, // Do nothing, we just want the path wildcard removed from the URL. } else if (parameter == "google:prefetchQuery") { replacements->push_back(Replacement(GOOGLE_PREFETCH_QUERY, start)); + } else if (parameter == "google:prefetchSource") { + replacements->push_back(Replacement(GOOGLE_PREFETCH_SOURCE, start)); } else if (parameter == "google:RLZ") { replacements->push_back(Replacement(GOOGLE_RLZ, start)); } else if (parameter == "google:searchClient") { @@ -1043,6 +1062,13 @@ std::string TemplateURLRef::HandleReplacements( std::string(), search_terms_data.GoogleBaseURLValue(), *i, &url); break; + case GOOGLE_BASE_SEARCH_BY_IMAGE_URL: + DCHECK(!i->is_post_param); + HandleReplacement(std::string(), + search_terms_data.GoogleBaseSearchByImageURLValue(), + *i, &url); + break; + case GOOGLE_BASE_SUGGEST_URL: DCHECK(!i->is_post_param); HandleReplacement( @@ -1116,6 +1142,19 @@ std::string TemplateURLRef::HandleReplacements( break; } + case GOOGLE_PREFETCH_SOURCE: { + if (search_terms_args.is_prefetch) { + // Currently, Chrome only support "cs" for prefetches, but if new + // prefetch sources (outside of suggestions) are added, a new prefetch + // source value is needed. These should denote the source of the + // prefetch to allow the search server to treat the requests based on + // source. "cs" represents Chrome Suggestions as the source. Adding a + // new source should be supported by the Search engine. + HandleReplacement(std::string(), "pf=cs&", *i, &url); + } + break; + } + case GOOGLE_RLZ: { DCHECK(!i->is_post_param); // On platforms that don't have RLZ, we still want this branch @@ -1335,8 +1374,15 @@ bool TemplateURL::IsBetterThanEngineWithConflictingKeyword( : base::Time(), // Prefer engines that CANNOT be auto-replaced. !engine->safe_for_autoreplace(), - // More recently modified engines win. - engine->last_modified(), + // Prefer engines created by Play API. + engine->created_from_play_api(), + // Favor prepopulated engines over other auto-generated engines. + engine->prepopulate_id() > 0, + // Favor engines derived from OpenSearch descriptions over + // autogenerated engines heuristically generated from searchable forms. + engine->originating_url().is_valid(), + // More recently modified engines or created engines win. + engine->last_modified(), engine->date_created(), // TODO(tommycli): This should be a tie-breaker than provides a total // ordering of all TemplateURLs so that distributed clients resolve // conflicts identically. This sync_guid is not globally unique today, diff --git a/chromium/components/search_engines/template_url.h b/chromium/components/search_engines/template_url.h index 78a4a8fb1f0..a44e77dfabb 100644 --- a/chromium/components/search_engines/template_url.h +++ b/chromium/components/search_engines/template_url.h @@ -248,6 +248,10 @@ class TemplateURLRef { // Source of the search or suggest request. RequestSource request_source = SEARCHBOX; + // Whether the query is being fetched as a prefetch request before the user + // actually searches for the search terms. + bool is_prefetch = false; + ContextualSearchParams contextual_search_params; }; @@ -377,6 +381,7 @@ class TemplateURLRef { ENCODING, GOOGLE_ASSISTED_QUERY_STATS, GOOGLE_BASE_URL, + GOOGLE_BASE_SEARCH_BY_IMAGE_URL, GOOGLE_BASE_SUGGEST_URL, GOOGLE_CONTEXTUAL_SEARCH_VERSION, GOOGLE_CONTEXTUAL_SEARCH_CONTEXT_DATA, @@ -395,6 +400,7 @@ class TemplateURLRef { GOOGLE_ORIGINAL_QUERY_FOR_SUGGESTION, GOOGLE_PAGE_CLASSIFICATION, GOOGLE_PREFETCH_QUERY, + GOOGLE_PREFETCH_SOURCE, GOOGLE_RLZ, GOOGLE_SEARCH_CLIENT, GOOGLE_SEARCH_FIELDTRIAL_GROUP, @@ -584,11 +590,12 @@ class TemplateURL { enum Type { // Installed only on this device. Should not be synced. This is not common. LOCAL = 0, - // Regular search engine. This is the most common. + // Regular search engine. This is the most common, and the ONLY type synced. NORMAL = 1, - // Installed by extension through Override Settings API. + // Installed by extension through Override Settings API. Not synced. NORMAL_CONTROLLED_BY_EXTENSION = 2, // The keyword associated with an extension that uses the Omnibox API. + // Not synced. OMNIBOX_API_EXTENSION = 3, }; diff --git a/chromium/components/search_engines/template_url_fetcher.cc b/chromium/components/search_engines/template_url_fetcher.cc index d34bb702108..82439fd9f61 100644 --- a/chromium/components/search_engines/template_url_fetcher.cc +++ b/chromium/components/search_engines/template_url_fetcher.cc @@ -16,6 +16,7 @@ #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/simple_url_loader.h" +#include "services/network/public/mojom/fetch_api.mojom-shared.h" namespace { @@ -67,7 +68,6 @@ class TemplateURLFetcher::RequestDelegate { const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, - int resource_type, int32_t request_id); // If data contains a valid OSDD, a TemplateURL is created and added to @@ -92,7 +92,7 @@ class TemplateURLFetcher::RequestDelegate { const GURL osdd_url_; const GURL favicon_url_; - std::unique_ptr<TemplateURLService::Subscription> template_url_subscription_; + base::CallbackListSubscription template_url_subscription_; DISALLOW_COPY_AND_ASSIGN(RequestDelegate); }; @@ -105,7 +105,6 @@ TemplateURLFetcher::RequestDelegate::RequestDelegate( const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, - int resource_type, int32_t request_id) : fetcher_(fetcher), keyword_(keyword), @@ -117,8 +116,8 @@ TemplateURLFetcher::RequestDelegate::RequestDelegate( if (!model->loaded()) { // Start the model load and set-up waiting for it. template_url_subscription_ = model->RegisterOnLoadedCallback( - base::Bind(&TemplateURLFetcher::RequestDelegate::OnLoaded, - base::Unretained(this))); + base::BindOnce(&TemplateURLFetcher::RequestDelegate::OnLoaded, + base::Unretained(this))); model->Load(); } @@ -126,7 +125,11 @@ TemplateURLFetcher::RequestDelegate::RequestDelegate( resource_request->url = osdd_url; resource_request->request_initiator = initiator; resource_request->render_frame_id = render_frame_id; - resource_request->resource_type = resource_type; + // TODO(crbug.com/1059639): Remove |resource_type| once the request is handled + // with RequestDestination without ResourceType. + resource_request->resource_type = + /* blink::mojom::ResourceType::kSubResource */ 6; + resource_request->destination = network::mojom::RequestDestination::kEmpty; resource_request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES; simple_url_loader_ = network::SimpleURLLoader::Create( std::move(resource_request), kTrafficAnnotation); @@ -164,7 +167,7 @@ void TemplateURLFetcher::RequestDelegate::OnTemplateURLParsed( } void TemplateURLFetcher::RequestDelegate::OnLoaded() { - template_url_subscription_.reset(); + template_url_subscription_ = {}; if (!template_url_) return; AddSearchProvider(); @@ -195,16 +198,12 @@ void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { DCHECK(model); DCHECK(model->loaded()); - const TemplateURL* existing_url = nullptr; - if (!model->CanAddAutogeneratedKeyword(keyword_, GURL(template_url_->url()), - &existing_url)) { + if (!model->CanAddAutogeneratedKeyword(keyword_, + GURL(template_url_->url()))) { fetcher_->RequestCompleted(this); // WARNING: Deletes us! return; } - if (existing_url) - model->Remove(existing_url); - // The short name is what is shown to the user. We preserve original names // since it is better when generated keyword in many cases. TemplateURLData data(template_url_->data()); @@ -216,6 +215,7 @@ void TemplateURLFetcher::RequestDelegate::AddSearchProvider() { data.favicon_url = favicon_url_; // Mark the keyword as replaceable so it can be removed if necessary. + // Add() will automatically remove conflicting keyword replaceable engines. data.safe_for_autoreplace = true; model->Add(std::make_unique<TemplateURL>(data)); @@ -238,7 +238,6 @@ void TemplateURLFetcher::ScheduleDownload( const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, - int resource_type, int32_t request_id) { DCHECK(osdd_url.is_valid()); DCHECK(!keyword.empty()); @@ -264,7 +263,7 @@ void TemplateURLFetcher::ScheduleDownload( requests_.push_back(std::make_unique<RequestDelegate>( this, keyword, osdd_url, favicon_url, initiator, url_loader_factory, - render_frame_id, resource_type, request_id)); + render_frame_id, request_id)); } void TemplateURLFetcher::RequestCompleted(RequestDelegate* request) { diff --git a/chromium/components/search_engines/template_url_fetcher.h b/chromium/components/search_engines/template_url_fetcher.h index 448bd68b5f7..870da17d169 100644 --- a/chromium/components/search_engines/template_url_fetcher.h +++ b/chromium/components/search_engines/template_url_fetcher.h @@ -52,7 +52,6 @@ class TemplateURLFetcher : public KeyedService { const url::Origin& initiator, network::mojom::URLLoaderFactory* url_loader_factory, int render_frame_id, - int resource_type, int32_t request_id); // The current number of outstanding requests. diff --git a/chromium/components/search_engines/template_url_prepopulate_data_unittest.cc b/chromium/components/search_engines/template_url_prepopulate_data_unittest.cc index c1b926ac99d..d10abd4cbef 100644 --- a/chromium/components/search_engines/template_url_prepopulate_data_unittest.cc +++ b/chromium/components/search_engines/template_url_prepopulate_data_unittest.cc @@ -10,8 +10,8 @@ #include <utility> #include "base/command_line.h" +#include "base/containers/contains.h" #include "base/files/scoped_temp_dir.h" -#include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "components/country_codes/country_codes.h" diff --git a/chromium/components/search_engines/template_url_service.cc b/chromium/components/search_engines/template_url_service.cc index 120df608bdc..0e458474ac3 100644 --- a/chromium/components/search_engines/template_url_service.cc +++ b/chromium/components/search_engines/template_url_service.cc @@ -11,11 +11,12 @@ #include "base/base64url.h" #include "base/bind.h" #include "base/callback.h" +#include "base/containers/contains.h" #include "base/debug/crash_logging.h" #include "base/format_macros.h" #include "base/metrics/histogram_macros.h" #include "base/rand_util.h" -#include "base/stl_util.h" +#include "base/ranges/algorithm.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -58,18 +59,6 @@ enum DeleteSyncedSearchEngineEvent { DELETE_ENGINE_MAX, }; -const char kSearchTemplateURLEventsHistogramName[] = - "Search.TemplateURL.Events"; - -// Values for an enumerated histogram used to track TemplateURL edge cases. -// These are persisted. Do not re-number. -enum SearchTemplateURLEvent { - SYNC_DELETE_SUCCESS = 0, - SYNC_DELETE_FAIL_NONEXISTENT_ENGINE = 1, - SYNC_DELETE_FAIL_DEFAULT_SEARCH_PROVIDER = 2, - SEARCH_TEMPLATE_URL_EVENT_MAX, -}; - // Returns true iff the change in |change_list| at index |i| should not be sent // up to the server based on its GUIDs presence in |sync_data| or when compared // to changes after it in |change_list|. @@ -289,7 +278,7 @@ TemplateURLService::TemplateURLService( std::unique_ptr<SearchTermsData> search_terms_data, const scoped_refptr<KeywordWebDataService>& web_data_service, std::unique_ptr<TemplateURLServiceClient> client, - const base::Closure& dsp_change_callback) + const base::RepeatingClosure& dsp_change_callback) : prefs_(prefs), search_terms_data_(std::move(search_terms_data)), web_data_service_(web_data_service), @@ -318,6 +307,13 @@ TemplateURLService::~TemplateURLService() { } // static +void TemplateURLService::LogSearchTemplateURLEvent( + SearchTemplateURLEvent event) { + UMA_HISTOGRAM_ENUMERATION("Search.TemplateURL.Events", event, + SEARCH_TEMPLATE_URL_EVENT_MAX); +} + +// static void TemplateURLService::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { #if defined(OS_IOS) || defined(OS_ANDROID) @@ -345,18 +341,18 @@ base::android::ScopedJavaLocalRef<jobject> TemplateURLService::GetJavaObject() { bool TemplateURLService::CanAddAutogeneratedKeyword( const base::string16& keyword, - const GURL& url, - const TemplateURL** template_url_to_replace) { + const GURL& url) { DCHECK(!keyword.empty()); // This should only be called for non-empty // keywords. If we need to support empty kewords // the code needs to change slightly. const TemplateURL* existing_url = GetTemplateURLForKeyword(keyword); - if (template_url_to_replace) - *template_url_to_replace = existing_url; if (existing_url) { - // We already have a TemplateURL for this keyword. Only allow it to be - // replaced if the TemplateURL can be replaced. - return CanReplace(existing_url); + // TODO(tommycli): Currently, this code goes one step beyond + // safe_for_autoreplace() and also forbids automatically modifying + // prepopulated engines. That's debatable, as we already update prepopulated + // provider favicons as the user browses. See UpdateProviderFavicons(). + return existing_url->safe_for_autoreplace() && + existing_url->prepopulate_id() == 0; } // We don't have a TemplateURL with keyword. We still may not allow this @@ -481,7 +477,38 @@ TemplateURL* TemplateURLService::AddWithOverrides( } void TemplateURLService::Remove(const TemplateURL* template_url) { - DCHECK_NE(template_url, default_search_provider_); + // CHECK that we aren't trying to Remove() the default search provider. + // This has happened before, and causes permanent damage to the user Profile, + // which can then be Synced to other installations. It's better to crash + // immediately, and that's why this isn't a DCHECK. https://crbug.com/1164024 + { + const TemplateURL* default_provider = GetDefaultSearchProvider(); + + // TODO(tommycli): Once we are sure this never happens in practice, we can + // remove this CrashKeyString, but we should keep the CHECK. + static base::debug::CrashKeyString* crash_key = + base::debug::AllocateCrashKeyString("removed_turl_keyword", + base::debug::CrashKeySize::Size256); + base::debug::ScopedCrashKeyString auto_clear( + crash_key, base::UTF16ToUTF8(template_url->keyword())); + + CHECK_NE(template_url, default_provider); + + // Before we are loaded, we want to CHECK that we aren't accidentally + // removing the in-table representation of the Default Search Engine. + // + // But users in the wild do indeed have engines with duplicated sync GUIDs. + // For instance, Extensions Override Settings API used to have a bug that + // would clone GUIDs. So therefore skip the check after loading. + // https://crbug.com/1166372#c13 + if (!loaded() && default_provider && + default_provider->type() != + TemplateURL::Type::NORMAL_CONTROLLED_BY_EXTENSION && + template_url->type() != + TemplateURL::Type::NORMAL_CONTROLLED_BY_EXTENSION) { + CHECK_NE(template_url->sync_guid(), default_provider->sync_guid()); + } + } auto i = FindTemplateURL(&template_urls_, template_url); if (i == template_urls_.end()) @@ -539,25 +566,25 @@ void TemplateURLService::RemoveAutoGeneratedSince(base::Time created_after) { void TemplateURLService::RemoveAutoGeneratedBetween(base::Time created_after, base::Time created_before) { - RemoveAutoGeneratedForUrlsBetween(base::Callback<bool(const GURL&)>(), - created_after, created_before); + RemoveAutoGeneratedForUrlsBetween(base::NullCallback(), created_after, + created_before); } void TemplateURLService::RemoveAutoGeneratedForUrlsBetween( - const base::Callback<bool(const GURL&)>& url_filter, + const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time created_after, base::Time created_before) { Scoper scoper(this); for (size_t i = 0; i < template_urls_.size();) { - if (template_urls_[i]->date_created() >= created_after && - (created_before.is_null() || - template_urls_[i]->date_created() < created_before) && - CanReplace(template_urls_[i].get()) && + TemplateURL* turl = template_urls_[i].get(); + if (turl->date_created() >= created_after && + (created_before.is_null() || turl->date_created() < created_before) && + turl->safe_for_autoreplace() && turl->prepopulate_id() == 0 && + !MatchesDefaultSearchProvider(turl) && (url_filter.is_null() || - url_filter.Run( - template_urls_[i]->GenerateSearchURL(search_terms_data())))) { - Remove(template_urls_[i].get()); + url_filter.Run(turl->GenerateSearchURL(search_terms_data())))) { + Remove(turl); } else { ++i; } @@ -625,30 +652,36 @@ void TemplateURLService::ResetTemplateURL(TemplateURL* url, Update(url, TemplateURL(data)); } -TemplateURL* TemplateURLService::CreateOrUpdateTemplateURLFromPlayAPIData( +TemplateURL* TemplateURLService::CreatePlayAPISearchEngine( const base::string16& title, const base::string16& keyword, const std::string& search_url, const std::string& suggestions_url, const std::string& favicon_url) { - TemplateURL* existing_turl = FindNonExtensionTemplateURLForKeyword(keyword); + // It's the caller's responsibility to check that there are no existing + // Play API for engine, but still CHECK this to avoid polluting the database. + // Currently, we never update Play API engine data. If we ever want to do + // that, we need to change how this method behaves. + const auto match_range = keyword_to_turl_and_length_.equal_range(keyword); + for (auto it = match_range.first; it != match_range.second; ++it) { + CHECK(!it->second.first->created_from_play_api()); + } + TemplateURLData data; - if (existing_turl) - data = existing_turl->data(); data.SetShortName(title); data.SetKeyword(keyword); data.SetURL(search_url); data.suggestions_url = suggestions_url; data.favicon_url = GURL(favicon_url); - data.safe_for_autoreplace = true; data.created_from_play_api = true; - if (existing_turl) { - Update(existing_turl, TemplateURL(data)); - } else { - existing_turl = Add(std::make_unique<TemplateURL>(data)); - DCHECK(existing_turl); - } - return existing_turl; + // Play API engines are created by explicit user gesture, and should not be + // auto-replaceable by an auto-generated engine as the user browses. + data.safe_for_autoreplace = false; + + // The Play API search engine is not guaranteed to be the best engine for + // |keyword|, if there are user-defined, extension, or policy engines. + // In practice on Android, this rarely happens, as there is only policy. + return Add(std::make_unique<TemplateURL>(data)); } void TemplateURLService::UpdateProviderFavicons( @@ -840,11 +873,10 @@ void TemplateURLService::Load() { ChangeToLoadedState(); } -std::unique_ptr<TemplateURLService::Subscription> -TemplateURLService::RegisterOnLoadedCallback( - const base::RepeatingClosure& callback) { - return loaded_ ? std::unique_ptr<TemplateURLService::Subscription>() - : on_loaded_callbacks_.Add(callback); +base::CallbackListSubscription TemplateURLService::RegisterOnLoadedCallback( + base::OnceClosure callback) { + return loaded_ ? base::CallbackListSubscription() + : on_loaded_callbacks_.Add(std::move(callback)); } void TemplateURLService::OnWebDataServiceRequestDone( @@ -1007,17 +1039,10 @@ base::Optional<syncer::ModelError> TemplateURLService::ProcessSyncChanges( const std::string error_msg = "ProcessSyncChanges failed on ChangeType " + syncer::SyncChange::ChangeTypeToString(iter->change_type()); - if (iter->change_type() == syncer::SyncChange::ACTION_INVALID) { - error = sync_error_factory_->CreateAndUploadError(FROM_HERE, error_msg); - continue; - } - if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) { if (!existing_turl) { // Can't DELETE a non-existent engine, although we log it. - UMA_HISTOGRAM_ENUMERATION(kSearchTemplateURLEventsHistogramName, - SYNC_DELETE_FAIL_NONEXISTENT_ENGINE, - SEARCH_TEMPLATE_URL_EVENT_MAX); + LogSearchTemplateURLEvent(SYNC_DELETE_FAIL_NONEXISTENT_ENGINE); error = sync_error_factory_->CreateAndUploadError(FROM_HERE, error_msg); continue; } @@ -1035,59 +1060,61 @@ base::Optional<syncer::ModelError> TemplateURLService::ProcessSyncChanges( // likely a source of duplicate search engine entries. crbug.com/1022775 if (existing_turl != GetDefaultSearchProvider()) { Remove(existing_turl); - UMA_HISTOGRAM_ENUMERATION(kSearchTemplateURLEventsHistogramName, - SYNC_DELETE_SUCCESS, - SEARCH_TEMPLATE_URL_EVENT_MAX); + LogSearchTemplateURLEvent(SYNC_DELETE_SUCCESS); } else { - UMA_HISTOGRAM_ENUMERATION(kSearchTemplateURLEventsHistogramName, - SYNC_DELETE_FAIL_DEFAULT_SEARCH_PROVIDER, - SEARCH_TEMPLATE_URL_EVENT_MAX); + LogSearchTemplateURLEvent(SYNC_DELETE_FAIL_DEFAULT_SEARCH_PROVIDER); } continue; } - if ((iter->change_type() == syncer::SyncChange::ACTION_ADD && - existing_turl) || - (iter->change_type() == syncer::SyncChange::ACTION_UPDATE && - !existing_turl)) { - // Can't ADD an already-existing engine, and can't UPDATE a non-existent - // engine. Early exit here to avoid ResolvingSyncKeywordConflict(). - error = sync_error_factory_->CreateAndUploadError(FROM_HERE, error_msg); - continue; - } + // Because TemplateURLService sometimes ignores remote Sync changes which + // we cannot cleanly apply, we need to handle ADD and UPDATE together. + // Ignore what the other Sync layers THINK the change type is. Instead: + // If we have an existing engine, treat as an update. + DCHECK(iter->change_type() == syncer::SyncChange::ACTION_ADD || + iter->change_type() == syncer::SyncChange::ACTION_UPDATE); - // Explicitly don't check for conflicts against extension keywords; in this - // case the functions which modify the keyword map know how to handle the - // conflicts. - // TODO(mpcomplete): If we allow editing extension keywords, then those will - // need to undergo conflict resolution. - TemplateURL* existing_keyword_turl = - FindNonExtensionTemplateURLForKeyword(turl->keyword()); - const bool has_conflict = - existing_keyword_turl && (existing_keyword_turl != existing_turl); - if (has_conflict) { - // Resolve any conflicts with other entries so we can safely update the - // keyword. - ResolveSyncKeywordConflict(turl.get(), existing_keyword_turl, - &new_changes); - } + if (!existing_turl) { + if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE) { + // This can happen if we have silently deleted a replaceable engine due + // to keyword conflict, and Sync server sends us an UPDATE to it. + LogSearchTemplateURLEvent(SYNC_UPDATE_CONVERTED_TO_ADD); + } - if (iter->change_type() == syncer::SyncChange::ACTION_ADD) { base::AutoReset<DefaultSearchChangeOrigin> change_origin( &dsp_change_origin_, DSP_CHANGE_SYNC_ADD); // Force the local ID to kInvalidTemplateURLID so we can add it. TemplateURLData data(turl->data()); data.id = kInvalidTemplateURLID; - auto added_ptr = std::make_unique<TemplateURL>(data); - TemplateURL* added = added_ptr.get(); - if (Add(std::move(added_ptr))) + + TemplateURL* added = Add(std::make_unique<TemplateURL>(data)); + if (added) { MaybeUpdateDSEViaPrefs(added); - continue; - } - DCHECK_EQ(syncer::SyncChange::ACTION_UPDATE, iter->change_type()); - if (Update(existing_turl, *turl)) + LogSearchTemplateURLEvent(SYNC_ADD_SUCCESS); + } else { + // Currently, in practice, this means that we tried to add a replaceable + // duplicate that was worse than our existing entry, but the API doesn't + // promise that, so we just log a generic SYNC_ADD_FAIL_OTHER_ERROR. + LogSearchTemplateURLEvent(SYNC_ADD_FAIL_OTHER_ERROR); + } + } else { + if (iter->change_type() == syncer::SyncChange::ACTION_ADD) { + // This can happen if we have ignored a DELETE request in the past to + // avoid deleting the default search provider, and later on, Sync tries + // to re-ADD something it thinks it has deleted. + LogSearchTemplateURLEvent(SYNC_ADD_CONVERTED_TO_UPDATE); + } + + // Since we've already found |existing_turl| by GUID, this Update() should + // always return true, but we still don't want to crash if it fails. + DCHECK(existing_turl); + bool update_success = Update(existing_turl, *turl); + DCHECK(update_success); + MaybeUpdateDSEViaPrefs(existing_turl); + LogSearchTemplateURLEvent(SYNC_UPDATE_SUCCESS); + } } // If something went wrong, we want to prematurely exit to avoid pushing @@ -1231,7 +1258,6 @@ void TemplateURLService::ProcessTemplateURLChange( const base::Location& from_here, const TemplateURL* turl, syncer::SyncChange::SyncChangeType type) { - DCHECK_NE(type, syncer::SyncChange::ACTION_INVALID); DCHECK(turl); if (!models_associated_) @@ -1430,7 +1456,7 @@ void TemplateURLService::Init(const Initializer* initializers, pref_change_registrar_.Init(prefs_); pref_change_registrar_.Add( prefs::kSyncedDefaultSearchProviderGUID, - base::Bind( + base::BindRepeating( &TemplateURLService::OnSyncedDefaultSearchProviderGUIDChanged, base::Unretained(this))); } @@ -1568,25 +1594,6 @@ bool TemplateURLService::CanAddAutogeneratedKeywordForHost( }); } -bool TemplateURLService::CanReplace(const TemplateURL* t_url) const { - return !ShowInDefaultList(t_url) && t_url->safe_for_autoreplace(); -} - -TemplateURL* TemplateURLService::FindNonExtensionTemplateURLForKeyword( - const base::string16& keyword) { - TemplateURL* keyword_turl = GetTemplateURLForKeyword(keyword); - if (!keyword_turl || (keyword_turl->type() == TemplateURL::NORMAL)) - return keyword_turl; - // The extension keyword in the model may be hiding a replaceable - // non-extension keyword. Look for it. - for (const auto& turl : template_urls_) { - if ((turl->type() == TemplateURL::NORMAL) && - (turl->keyword() == keyword)) - return turl.get(); - } - return nullptr; -} - bool TemplateURLService::Update(TemplateURL* existing_turl, const TemplateURL& new_values) { DCHECK(existing_turl); @@ -1600,32 +1607,11 @@ bool TemplateURLService::Update(TemplateURL* existing_turl, TemplateURLID previous_id = existing_turl->id(); RemoveFromMaps(existing_turl); - // Check for new keyword conflicts with another normal engine. - // This is possible when autogeneration of the keyword for a Google default - // search provider at load time causes it to conflict with an existing - // keyword. If the conflicting engines are replaceable, we delete them. - // If they're not replaceable, we leave them alone, and trust AddToMaps() to - // choose the best engine to assign the keyword. - std::vector<TemplateURL*> turls_to_remove; - for (const auto& turl : template_urls_) { - // TODO(tommycli): Investigate also replacing TemplateURL::LOCAL engines. - if (turl.get() != existing_turl && (turl->type() == TemplateURL::NORMAL) && - (turl->keyword() == new_values.keyword()) && CanReplace(turl.get())) { - // Remove() invalidates iterators. - turls_to_remove.push_back(turl.get()); - } - } - for (TemplateURL* turl : turls_to_remove) { - Remove(turl); - } - - // Update existing turl with new values. This must happen after calling - // Remove(conflicting_keyword_turl) above, since otherwise during that - // function RemoveFromMaps() may find |existing_turl| as an alternate engine - // for the same keyword. Duplicate keyword handling is only meant for the - // case of extensions, and if done here would leave internal state - // inconsistent (e.g. |existing_turl| would already be re-added to maps before - // calling AddToMaps() below). + // Update existing turl with new values and add back to the map. + // We don't do any keyword conflict handling here, as TemplateURLService + // already can pick the best engine out of duplicates. Replaceable duplicates + // will be culled during next startup's Add() loop. We did this to keep + // Update() simple: it never fails, and never deletes |existing_engine|. existing_turl->CopyFrom(new_values); existing_turl->data_.id = previous_id; @@ -1834,6 +1820,8 @@ bool TemplateURLService::ApplyDefaultSearchChangeNoMetrics( // (2) If the user deleted the pre-populated default and we subsequently // lost their user-selected value. default_search_provider_ = Add(std::make_unique<TemplateURL>(*data)); + DCHECK(default_search_provider_) + << "Add() to repair the DSE must never fail."; } } else if (source == DefaultSearchManager::FROM_USER) { default_search_provider_ = GetTemplateURLForGUID(data->sync_guid); @@ -1847,6 +1835,8 @@ bool TemplateURLService::ApplyDefaultSearchChangeNoMetrics( } else { new_data.id = kInvalidTemplateURLID; default_search_provider_ = Add(std::make_unique<TemplateURL>(new_data)); + DCHECK(default_search_provider_) + << "Add() to repair the DSE must never fail."; } if (default_search_provider_ && prefs_) { prefs_->SetString(prefs::kSyncedDefaultSearchProviderGUID, @@ -1876,35 +1866,12 @@ TemplateURL* TemplateURLService::Add(std::unique_ptr<TemplateURL> template_url, template_url->ResetKeywordIfNecessary(search_terms_data(), false); - // If |template_url| is not created by an extension, its keyword must not - // conflict with any already in the model. - if (!IsCreatedByExtension(template_url.get())) { - TemplateURL* existing_turl = - FindNonExtensionTemplateURLForKeyword(template_url->keyword()); - - // Note that we can reach here during the loading phase while processing the - // template URLs from the web data service. In this case, - // GetTemplateURLForKeyword() will look not only at what's already in the - // model, but at the |initial_default_search_provider_|. Since this engine - // will presumably also be present in the web data, we need to double-check - // that any "pre-existing" entries we find are actually coming from - // |template_urls_|, lest we detect a "conflict" between the - // |initial_default_search_provider_| and the web data version of itself. - if (existing_turl && Contains(&template_urls_, existing_turl)) { - DCHECK_NE(existing_turl, template_url.get()); - if (CanReplace(existing_turl)) { - Remove(existing_turl); - } else if (CanReplace(template_url.get())) { - return nullptr; - } else { - // Neither engine can be replaced. Uniquify the existing keyword. - base::string16 new_keyword = UniquifyKeyword(*existing_turl, false); - ResetTemplateURL(existing_turl, existing_turl->short_name(), - new_keyword, existing_turl->url()); - DCHECK_EQ(new_keyword, existing_turl->keyword()); - } - } + // Early exit if the newly added TemplateURL was a replaceable duplicate. + // No need to inform either Sync or flag on the model-mutated in that case. + if (RemoveDuplicateReplaceableEnginesOf(template_url.get())) { + return nullptr; } + TemplateURL* template_url_ptr = template_url.get(); template_urls_.push_back(std::move(template_url)); AddToMaps(template_url_ptr); @@ -1973,6 +1940,7 @@ void TemplateURLService::UpdateProvidersCreatedByPolicy( if (new_data.sync_guid.empty()) new_data.GenerateSyncGUID(); new_data.created_by_policy = true; + new_data.safe_for_autoreplace = false; std::unique_ptr<TemplateURL> new_dse_ptr = std::make_unique<TemplateURL>(new_data); TemplateURL* new_dse = new_dse_ptr.get(); @@ -1991,85 +1959,6 @@ void TemplateURLService::ResetTemplateURLGUID(TemplateURL* url, Update(url, TemplateURL(data)); } -base::string16 TemplateURLService::UniquifyKeyword(const TemplateURL& turl, - bool force) { - DCHECK(!IsCreatedByExtension(&turl)); - if (!force) { - // Already unique. - if (!GetTemplateURLForKeyword(turl.keyword())) - return turl.keyword(); - - // First, try to return the generated keyword for the TemplateURL. - GURL gurl(turl.url()); - if (gurl.is_valid()) { - base::string16 keyword_candidate = TemplateURL::GenerateKeyword(gurl); - if (!GetTemplateURLForKeyword(keyword_candidate)) - return keyword_candidate; - } - } - - // We try to uniquify the keyword by appending a special character to the end. - // This is a best-effort approach where we try to preserve the original - // keyword and let the user do what they will after our attempt. - base::string16 keyword_candidate(turl.keyword()); - do { - keyword_candidate.append(base::ASCIIToUTF16("_")); - } while (GetTemplateURLForKeyword(keyword_candidate)); - - return keyword_candidate; -} - -bool TemplateURLService::IsLocalTemplateURLBetter( - const TemplateURL* local_turl, - const TemplateURL* sync_turl, - bool prefer_local_default) const { - DCHECK(GetTemplateURLForGUID(local_turl->sync_guid())); - return local_turl->last_modified() > sync_turl->last_modified() || - local_turl->created_by_policy() || - (prefer_local_default && local_turl == GetDefaultSearchProvider()); -} - -void TemplateURLService::ResolveSyncKeywordConflict( - TemplateURL* unapplied_sync_turl, - TemplateURL* applied_sync_turl, - syncer::SyncChangeList* change_list) { - DCHECK(loaded_); - DCHECK(unapplied_sync_turl); - DCHECK(applied_sync_turl); - DCHECK(change_list); - DCHECK_EQ(applied_sync_turl->keyword(), unapplied_sync_turl->keyword()); - DCHECK_EQ(TemplateURL::NORMAL, applied_sync_turl->type()); - - Scoper scoper(this); - - // Both |unapplied_sync_turl| and |applied_sync_turl| are known to Sync, so - // don't delete either of them. Instead, determine which is "better" and - // uniquify the other one, sending an update to the server for the updated - // entry. - const bool applied_turl_is_better = - IsLocalTemplateURLBetter(applied_sync_turl, unapplied_sync_turl); - TemplateURL* loser = applied_turl_is_better ? - unapplied_sync_turl : applied_sync_turl; - base::string16 new_keyword = UniquifyKeyword(*loser, false); - DCHECK(!GetTemplateURLForKeyword(new_keyword)); - if (applied_turl_is_better) { - // Just set the keyword of |unapplied_sync_turl|. The caller is responsible - // for adding or updating unapplied_sync_turl in the local model. - unapplied_sync_turl->data_.SetKeyword(new_keyword); - } else { - // Update |applied_sync_turl| in the local model with the new keyword. - TemplateURLData data(applied_sync_turl->data()); - data.SetKeyword(new_keyword); - Update(applied_sync_turl, TemplateURL(data)); - } - // The losing TemplateURL should have their keyword updated. Send a change to - // the server to reflect this change. - syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(*loser); - change_list->push_back(syncer::SyncChange(FROM_HERE, - syncer::SyncChange::ACTION_UPDATE, - sync_data)); -} - void TemplateURLService::MergeInSyncTemplateURL( TemplateURL* sync_turl, const SyncDataMap& sync_data, @@ -2079,49 +1968,63 @@ void TemplateURLService::MergeInSyncTemplateURL( DCHECK(!GetTemplateURLForGUID(sync_turl->sync_guid())); DCHECK(IsFromSync(sync_turl, sync_data)); - TemplateURL* conflicting_turl = - FindNonExtensionTemplateURLForKeyword(sync_turl->keyword()); bool should_add_sync_turl = true; Scoper scoper(this); - // Resolve conflicts with local TemplateURLs. - if (conflicting_turl) { - // Modify |conflicting_turl| to make room for |sync_turl|. + // First resolve conflicts with local duplicate keyword NORMAL TemplateURLs, + // working from best to worst. + DCHECK(sync_turl->type() == TemplateURL::NORMAL); + std::vector<TemplateURL*> local_duplicates; + const auto match_range = + keyword_to_turl_and_length_.equal_range(sync_turl->keyword()); + for (auto it = match_range.first; it != match_range.second; ++it) { + TemplateURL* local_turl = it->second.first; + if (local_turl->type() == TemplateURL::NORMAL) { + local_duplicates.push_back(local_turl); + } + } + base::ranges::sort(local_duplicates, [&](const auto& a, const auto& b) { + return a->IsBetterThanEngineWithConflictingKeyword(b); + }); + for (TemplateURL* conflicting_turl : local_duplicates) { if (IsFromSync(conflicting_turl, sync_data)) { // |conflicting_turl| is already known to Sync, so we're not allowed to - // remove it. In this case, we want to uniquify the worse one and send an - // update for the changed keyword to sync. We can reuse the logic from - // ResolveSyncKeywordConflict for this. - ResolveSyncKeywordConflict(sync_turl, conflicting_turl, change_list); + // remove it. Just leave it. TemplateURLService can tolerate duplicates. + // TODO(tommycli): Eventually we should figure out a way to merge + // substantively identical ones or somehow otherwise cull the herd. + continue; + } + + // |conflicting_turl| is not yet known to Sync. If it is better, then we + // want to transfer its values up to sync. Otherwise, we remove it and + // allow the entry from Sync to overtake it in the model. + const std::string guid = conflicting_turl->sync_guid(); + if (conflicting_turl == GetDefaultSearchProvider() || + conflicting_turl->IsBetterThanEngineWithConflictingKeyword(sync_turl)) { + ResetTemplateURLGUID(conflicting_turl, sync_turl->sync_guid()); + syncer::SyncData sync_data = + CreateSyncDataFromTemplateURL(*conflicting_turl); + change_list->push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data)); + // Note that in this case we do not add the Sync TemplateURL to the + // local model, since we've effectively "merged" it in by updating the + // local conflicting entry with its sync_guid. + should_add_sync_turl = false; } else { - // |conflicting_turl| is not yet known to Sync. If it is better, then we - // want to transfer its values up to sync. Otherwise, we remove it and - // allow the entry from Sync to overtake it in the model. - const std::string guid = conflicting_turl->sync_guid(); - if (IsLocalTemplateURLBetter(conflicting_turl, sync_turl)) { - ResetTemplateURLGUID(conflicting_turl, sync_turl->sync_guid()); - syncer::SyncData sync_data = - CreateSyncDataFromTemplateURL(*conflicting_turl); - change_list->push_back(syncer::SyncChange( - FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data)); - // Note that in this case we do not add the Sync TemplateURL to the - // local model, since we've effectively "merged" it in by updating the - // local conflicting entry with its sync_guid. - should_add_sync_turl = false; - } else { - // We guarantee that this isn't the local search provider. Otherwise, - // local would have won. - DCHECK(conflicting_turl != GetDefaultSearchProvider()); - Remove(conflicting_turl); - } - // This TemplateURL was either removed or overwritten in the local model. - // Remove the entry from the local data so it isn't pushed up to Sync. - local_data->erase(guid); + // We guarantee that this isn't the local search provider. Otherwise, + // local would have won. + DCHECK(conflicting_turl != GetDefaultSearchProvider()); + Remove(conflicting_turl); } - // prepopulate_id 0 effectively means unspecified; i.e. that the turl isn't - // a pre-populated one, so we want to ignore that case. - } else if (sync_turl->prepopulate_id() != 0) { + // This TemplateURL was either removed or overwritten in the local model. + // Remove the entry from the local data so it isn't pushed up to Sync. + local_data->erase(guid); + } + + // Try to take over a local prepopulated entry, assuming we haven't already + // run into a keyword conflict. + if (local_duplicates.empty() && sync_turl->prepopulate_id() != 0) { // Check for a turl with a conflicting prepopulate_id. This detects the case // where the user changes a prepopulated engine's keyword on one client, // then begins syncing on another client. We want to reflect this keyword @@ -2142,8 +2045,8 @@ void TemplateURLService::MergeInSyncTemplateURL( // the relevant changes in, we give up and leave both intact. if (conflicting_prepopulated_turl && !IsFromSync(conflicting_prepopulated_turl, sync_data) && - !IsLocalTemplateURLBetter(conflicting_prepopulated_turl, sync_turl, - false)) { + sync_turl->IsBetterThanEngineWithConflictingKeyword( + conflicting_prepopulated_turl)) { std::string guid = conflicting_prepopulated_turl->sync_guid(); if (conflicting_prepopulated_turl == default_search_provider_) { bool pref_matched = @@ -2274,3 +2177,88 @@ TemplateURL* TemplateURLService::FindMatchingDefaultExtensionTemplateURL( } return nullptr; } + +bool TemplateURLService::RemoveDuplicateReplaceableEnginesOf( + TemplateURL* candidate) { + DCHECK(candidate); + const base::string16& keyword = candidate->keyword(); + + // If there's not at least one conflicting TemplateURL, there's nothing to do. + const auto match_range = keyword_to_turl_and_length_.equal_range(keyword); + if (match_range.first == match_range.second) { + return false; + } + + // Gather the replaceable TemplateURLs to be removed. We don't do it in-place, + // because Remove() invalidates iterators. + std::vector<TemplateURL*> replaceable_turls; + for (auto it = match_range.first; it != match_range.second; ++it) { + TemplateURL* turl = it->second.first; + DCHECK_NE(turl, candidate) << "This algorithm runs BEFORE |candidate| is " + "added to the keyword map."; + + // Prepopulated engines are marked as safe_for_autoreplace(). But because + // they are shown in the Default Search Engines Settings UI, users would + // find it confusing if they were ever automatically removed. + // https://crbug.com/1164024 + if (turl->safe_for_autoreplace() && turl->prepopulate_id() == 0) { + replaceable_turls.push_back(turl); + } + } + + // Find the BEST engine for |keyword| factoring in the new |candidate|. + TemplateURL* best = GetTemplateURLForKeyword(keyword); + if (!best || candidate->IsBetterThanEngineWithConflictingKeyword(best)) { + best = candidate; + } + + // Remove all the replaceable TemplateURLs that are not the best. + for (TemplateURL* turl : replaceable_turls) { + DCHECK_NE(turl, candidate); + + // Never actually remove the DSE during this phase. This handling defers + // deleting the DSE until it's no longer set as the DSE, analagous to how + // we handle ACTION_DELETE of the DSE in ProcessSyncChanges(). + if (turl != best && !MatchesDefaultSearchProvider(turl)) { + Remove(turl); + } + } + + // Caller needs to know if |candidate| would have been deleted. + // Also always successfully add prepopulated engines, for two reasons: + // 1. The DSE repair logic in ApplyDefaultSearchChangeNoMetrics() relies on + // Add()ing back the DSE always succeeding. https://crbug.com/1164024 + // 2. If we don't do this, we have a weird order-dependence on the + // replaceability of prepopulated engines, given that we refuse to add + // prepopulated engines to the |replaceable_engines| vector. + // + // Given the necessary special casing of prepopulated engines, we may consider + // marking prepopulated engines as NOT safe_for_autoreplace(), but there's a + // few obstacles to this: + // 1. Prepopulated engines are not user-created, and therefore meet the + // definition of safe_for_autoreplace(). + // 2. If we mark them as NOT safe_for_autoreplace(), we can no longer + // distinguish between prepopulated engines that user has edited, vs. not + // edited. + // + // One more caveat: In 2019, we made prepopulated engines have a + // deterministically generated Sync GUID in order to prevent duplicate + // prepopulated engines when two clients start syncing at the same time. + // When combined with the requirement that we can never fail to add a + // prepopulated engine, this could leads to two engines having the same GUID. + // + // TODO(tommycli): After M89, we need to investigate solving the contradiction + // above. Most probably: the solution is to stop Syncing prepopulated engines + // and make the GUIDs actually globally unique again. + return candidate != best && candidate->safe_for_autoreplace() && + candidate->prepopulate_id() == 0; +} + +bool TemplateURLService::MatchesDefaultSearchProvider(TemplateURL* turl) const { + DCHECK(turl); + const TemplateURL* default_provider = GetDefaultSearchProvider(); + if (!default_provider) + return false; + + return turl->sync_guid() == default_provider->sync_guid(); +} diff --git a/chromium/components/search_engines/template_url_service.h b/chromium/components/search_engines/template_url_service.h index 7f0210819e2..c4a56815b59 100644 --- a/chromium/components/search_engines/template_url_service.h +++ b/chromium/components/search_engines/template_url_service.h @@ -14,6 +14,7 @@ #include <utility> #include <vector> +#include "base/callback.h" #include "base/callback_list.h" #include "base/gtest_prod_util.h" #include "base/macros.h" @@ -80,7 +81,6 @@ class TemplateURLService : public WebDataServiceConsumer, using TemplateURLVector = TemplateURL::TemplateURLVector; using OwnedTemplateURLVector = TemplateURL::OwnedTemplateURLVector; using SyncDataMap = std::map<std::string, syncer::SyncData>; - using Subscription = base::CallbackList<void(void)>::Subscription; // We may want to treat the keyword in a TemplateURL as being a different // length than it actually is. For example, for keywords that end in a @@ -103,6 +103,21 @@ class TemplateURLService : public WebDataServiceConsumer, bool is_keyword_transition; }; + // Values for an enumerated histogram used to track TemplateURL edge cases. + // These are persisted. Do not re-number. + enum SearchTemplateURLEvent { + SYNC_DELETE_SUCCESS = 0, + SYNC_DELETE_FAIL_NONEXISTENT_ENGINE = 1, + SYNC_DELETE_FAIL_DEFAULT_SEARCH_PROVIDER = 2, + SYNC_ADD_SUCCESS = 3, + SYNC_ADD_CONVERTED_TO_UPDATE = 4, + SYNC_ADD_FAIL_OTHER_ERROR = 5, + SYNC_UPDATE_SUCCESS = 6, + SYNC_UPDATE_CONVERTED_TO_ADD = 7, + MIGRATE_SAFE_FOR_AUTOREPLACE_PLAY_API_ENGINE = 8, + SEARCH_TEMPLATE_URL_EVENT_MAX, + }; + TemplateURLService( PrefService* prefs, std::unique_ptr<SearchTermsData> search_terms_data, @@ -113,6 +128,9 @@ class TemplateURLService : public WebDataServiceConsumer, TemplateURLService(const Initializer* initializers, const int count); ~TemplateURLService() override; + // Log a SearchTemplateURLEvent. + static void LogSearchTemplateURLEvent(SearchTemplateURLEvent event); + // Register Profile preferences in |registry|. static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); @@ -121,15 +139,12 @@ class TemplateURLService : public WebDataServiceConsumer, #endif // Returns true if there is no TemplateURL that conflicts with the - // keyword/url pair, or there is one but it can be replaced. If there is an - // existing keyword that can be replaced and template_url_to_replace is - // non-NULL, template_url_to_replace is set to the keyword to replace. + // keyword/url pair, or there is one but it can be replaced. // // |url| is the URL of the search query. This is used to prevent auto-adding // a keyword for hosts already associated with a manually-edited keyword. bool CanAddAutogeneratedKeyword(const base::string16& keyword, - const GURL& url, - const TemplateURL** template_url_to_replace); + const GURL& url); // Returns whether the engine is a "pre-existing" engine, either from the // prepopulate list or created by policy. @@ -211,7 +226,7 @@ class TemplateURLService : public WebDataServiceConsumer, // range and match |url_filter|. If |url_filter| is_null(), deletes all // auto-generated keywords in the range. void RemoveAutoGeneratedForUrlsBetween( - const base::Callback<bool(const GURL&)>& url_filter, + const base::RepeatingCallback<bool(const GURL&)>& url_filter, base::Time created_after, base::Time created_before); @@ -239,15 +254,17 @@ class TemplateURLService : public WebDataServiceConsumer, const base::string16& keyword, const std::string& search_url); - // Creates TemplateURL, populating it with data from Play API. If TemplateURL - // with matching keyword already exists then merges Play API data into it. - // Sets |created_from_play_api| flag. - TemplateURL* CreateOrUpdateTemplateURLFromPlayAPIData( - const base::string16& title, - const base::string16& keyword, - const std::string& search_url, - const std::string& suggestions_url, - const std::string& favicon_url); + // Creates a TemplateURL for |keyword| marked with created_from_play_api(). + // Returns the newly created engine. + // + // This method must NOT be called multiple times for the same |keyword|, + // because that would create duplicate engines. Caller is responsible for + // verifying there are no existing |keyword| created_from_play_api() engines. + TemplateURL* CreatePlayAPISearchEngine(const base::string16& title, + const base::string16& keyword, + const std::string& search_url, + const std::string& suggestions_url, + const std::string& favicon_url); // Updates any search providers matching |potential_search_url| with the new // favicon location |favicon_url|. @@ -319,8 +336,8 @@ class TemplateURLService : public WebDataServiceConsumer, // Registers a callback to be called when the service has loaded. // // If the service has already loaded, this function does nothing. - std::unique_ptr<Subscription> RegisterOnLoadedCallback( - const base::RepeatingClosure& callback); + base::CallbackListSubscription RegisterOnLoadedCallback( + base::OnceClosure callback); #if defined(UNIT_TEST) void set_loaded(bool value) { loaded_ = value; } @@ -440,15 +457,10 @@ class TemplateURLService : public WebDataServiceConsumer, FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, AddOmniboxExtensionKeyword); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, ExtensionsWithSameKeywords); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, - CheckEnginesWithSameKeywords); + KeywordConflictNonReplaceableEngines); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, LastVisitedTimeUpdate); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, RepairPrepopulatedSearchEngines); - FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, UniquifyKeyword); - FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, - IsLocalTemplateURLBetter); - FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, - ResolveSyncKeywordConflict); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, PreSyncDeletes); FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, MergeInSyncTemplateURL); FRIEND_TEST_ALL_PREFIXES(LocationBarModelTest, GoogleBaseURL); @@ -464,12 +476,14 @@ class TemplateURLService : public WebDataServiceConsumer, // A mapping from keywords to the corresponding TemplateURLs and their // meaningful keyword lengths. This is a multimap, so the system can // efficiently tolerate multiple engines with the same keyword, like from - // extensions. The values are not sorted from best to worst for each keyword, - // since multimaps don't sort on value. Users that want the best value for - // each key must traverse through all matching items, but we expect there to - // be below three values per key. + // extensions. + // + // The values for any given keyword are not sorted. Users that want the best + // value for each key must traverse through all matching items. The vast + // majority of keywords should only have one item. using KeywordToTURLAndMeaningfulLength = std::multimap<base::string16, TURLAndMeaningfulLength>; + // Declaration of values to be used in an enumerated histogram to tally // changes to the default search provider from various entry points. In // particular, we use this to see what proportion of changes are from Sync @@ -543,16 +557,6 @@ class TemplateURLService : public WebDataServiceConsumer, // specified host and that TemplateURL has been manually modified. bool CanAddAutogeneratedKeywordForHost(const std::string& host) const; - // Returns true if the TemplateURL is replaceable. This doesn't look at the - // uniqueness of the keyword or host and is intended to be called after those - // checks have been done. This returns true if the TemplateURL doesn't appear - // in the default list and is marked as safe_for_autoreplace. - bool CanReplace(const TemplateURL* t_url) const; - - // Like GetTemplateURLForKeyword(), but ignores extension-provided keywords. - TemplateURL* FindNonExtensionTemplateURLForKeyword( - const base::string16& keyword); - // Updates the information in |existing_turl| using the information from // |new_values|, but the ID for |existing_turl| is retained. Returns whether // |existing_turl| was found in |template_urls_| and thus could be updated. @@ -612,44 +616,6 @@ class TemplateURLService : public WebDataServiceConsumer, // to the database. This does not notify observers. void ResetTemplateURLGUID(TemplateURL* url, const std::string& guid); - // Attempts to generate a unique keyword for |turl| based on its original - // keyword. If its keyword is already unique, that is returned. Otherwise, it - // tries to return the autogenerated keyword if that is unique to the Service, - // and finally it repeatedly appends special characters to the keyword until - // it is unique to the Service. If |force| is true, then this will only - // execute the special character appending functionality. - base::string16 UniquifyKeyword(const TemplateURL& turl, bool force); - - // Returns true iff |local_turl| is considered "better" than |sync_turl| for - // the purposes of resolving conflicts. |local_turl| must be a TemplateURL - // known to the local model (though it may already be synced), and |sync_turl| - // is a new TemplateURL known to Sync but not yet known to the local model. - // The criteria for if |local_turl| is better than |sync_turl| is whether any - // of the following are true: - // * |local_turl|'s last_modified timestamp is newer than sync_turl. - // * |local_turl| is created by policy. - // * |prefer_local_default| is true and |local_turl| is the local default - // search provider - // - // TODO(tommycli): Consolidate into using - // TemplateURL::IsBetterThanEngineWithConflictingKeyword. Likely we will - // eliminate the |prefer_local_default| mechanism. - bool IsLocalTemplateURLBetter(const TemplateURL* local_turl, - const TemplateURL* sync_turl, - bool prefer_local_default = true) const; - - // Given two synced TemplateURLs with a conflicting keyword, one of which - // needs to be added to or updated in the local model (|unapplied_sync_turl|) - // and one which is already known to the local model (|applied_sync_turl|), - // prepares the local model so that |unapplied_sync_turl| can be added to it, - // or applied as an update to an existing TemplateURL. - // Since both entries are known to Sync and one of their keywords will change, - // an ACTION_UPDATE will be appended to |change_list| to reflect this change. - // Note that |applied_sync_turl| must not be an extension keyword. - void ResolveSyncKeywordConflict(TemplateURL* unapplied_sync_turl, - TemplateURL* applied_sync_turl, - syncer::SyncChangeList* change_list); - // Adds |sync_turl| into the local model, possibly removing or updating a // local TemplateURL to make room for it. This expects |sync_turl| to be a new // entry from Sync, not currently known to the local model. |sync_data| should @@ -661,8 +627,6 @@ class TemplateURLService : public WebDataServiceConsumer, // model during MergeDataAndStartSyncing. If |sync_turl| replaces a local // entry, that entry is removed from |initial_data| to prevent it from being // sent up to Sync. - // |merge_result| tracks the changes made to the local model. Added/modified/ - // deleted are updated depending on how the |sync_turl| is merged in. // This should only be called from MergeDataAndStartSyncing. void MergeInSyncTemplateURL(TemplateURL* sync_turl, const SyncDataMap& sync_data, @@ -699,6 +663,26 @@ class TemplateURLService : public WebDataServiceConsumer, TemplateURL* FindMatchingDefaultExtensionTemplateURL( const TemplateURLData& data); + // This method removes all TemplateURLs that meet all three criteria: + // - Duplicate: Shares the same keyword as |candidate|. + // - Replaceable: Engine is eligible for automatic removal. See CanReplace(). + // - Worse: There exists a better engine with the same keyword. + // + // This method must run BEFORE |candidate| is added to the engine list / map. + // It would be simpler to run the algorithm AFTER |candidate| is added, but + // that makes extra sync updates, observer notifications, and database churn. + // + // This method returns true if |candidate| ITSELF is rendundant. + // But notably, this method NEVER calls Remove() on |candidate|, leaving the + // correct handling to its caller. + bool RemoveDuplicateReplaceableEnginesOf(TemplateURL* candidate); + + // Returns true if |turl| matches the default search provider. This method + // does both a GUID comparison, because while the model is being loaded, the + // DSE may be sourced from prefs, and we still want to consider the + // corresponding database entry a match. https://crbug.com/1164024 + bool MatchesDefaultSearchProvider(TemplateURL* turl) const; + // ---------- Browser state related members --------------------------------- PrefService* prefs_ = nullptr; @@ -750,6 +734,11 @@ class TemplateURLService : public WebDataServiceConsumer, // Once loaded, the default search provider. This is a pointer to a // TemplateURL owned by |template_urls_|. + // + // TODO(tommycli): Can we combine this with initial_default_search_provider_? + // Essentially all direct usages of this variable need to first check that + // |loading_| is true, and should call GetDefaultSearchProvider() instead. + // Example of a regression due to this mistake: https://crbug.com/1164024. TemplateURL* default_search_provider_ = nullptr; // A temporary location for the DSE until Web Data has been loaded and it can @@ -799,7 +788,7 @@ class TemplateURLService : public WebDataServiceConsumer, DefaultSearchChangeOrigin dsp_change_origin_ = DSP_CHANGE_OTHER; // Stores a list of callbacks to be run after TemplateURLService has loaded. - base::CallbackList<void(void)> on_loaded_callbacks_; + base::OnceClosureList on_loaded_callbacks_; // Similar to |on_loaded_callbacks_| but used for WaitUntilReadyToSync(). base::OnceClosure on_loaded_callback_for_sync_; diff --git a/chromium/components/search_engines/template_url_unittest.cc b/chromium/components/search_engines/template_url_unittest.cc index ef435ac629a..f9bc1ec8dff 100644 --- a/chromium/components/search_engines/template_url_unittest.cc +++ b/chromium/components/search_engines/template_url_unittest.cc @@ -766,6 +766,37 @@ TEST_F(TemplateURLTest, ReplaceOmniboxFocusType) { } } +// Tests replacing prefetch source (&pf=). +TEST_F(TemplateURLTest, ReplaceIsPrefetch) { + struct TestData { + const base::string16 search_term; + bool is_prefetch; + const std::string url; + const std::string expected_result; + } test_data[] = { + {ASCIIToUTF16("foo"), false, + "{google:baseURL}?{searchTerms}&{google:prefetchSource}", + "http://www.google.com/?foo&"}, + {ASCIIToUTF16("foo"), true, + "{google:baseURL}?{searchTerms}&{google:prefetchSource}", + "http://www.google.com/?foo&pf=cs&"}, + }; + TemplateURLData data; + data.input_encodings.push_back("UTF-8"); + for (size_t i = 0; i < base::size(test_data); ++i) { + data.SetURL(test_data[i].url); + TemplateURL url(data); + EXPECT_TRUE(url.url_ref().IsValid(search_terms_data_)); + ASSERT_TRUE(url.url_ref().SupportsReplacement(search_terms_data_)); + TemplateURLRef::SearchTermsArgs search_terms_args(test_data[i].search_term); + search_terms_args.is_prefetch = test_data[i].is_prefetch; + GURL result(url.url_ref().ReplaceSearchTerms(search_terms_args, + search_terms_data_)); + ASSERT_TRUE(result.is_valid()); + EXPECT_EQ(test_data[i].expected_result, result.spec()); + } +} + // Tests replacing currentPageUrl. TEST_F(TemplateURLTest, ReplaceCurrentPageUrl) { struct TestData { |