| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/omnibox/browser/zero_suggest_cache_service.h" |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "base/values.h" |
| #include "components/omnibox/browser/autocomplete_input.h" |
| #include "components/omnibox/browser/autocomplete_provider_client.h" |
| #include "components/omnibox/browser/autocomplete_scheme_classifier.h" |
| #include "components/omnibox/browser/omnibox_prefs.h" |
| #include "components/omnibox/browser/search_suggestion_parser.h" |
| #include "components/omnibox/common/omnibox_features.h" |
| #include "components/prefs/pref_service.h" |
| |
| using CacheEntry = ZeroSuggestCacheServiceInterface::CacheEntry; |
| using CacheEntrySuggestResult = |
| ZeroSuggestCacheServiceInterface::CacheEntrySuggestResult; |
| |
| ZeroSuggestCacheService::ZeroSuggestCacheService( |
| std::unique_ptr<AutocompleteSchemeClassifier> scheme_classifier, |
| PrefService* prefs, |
| size_t cache_size) |
| : scheme_classifier_(std::move(scheme_classifier)), |
| prefs_(prefs), |
| cache_(cache_size) { |
| DCHECK(prefs); |
| DCHECK_GT(cache_size, 0u); |
| |
| if (!base::FeatureList::IsEnabled(omnibox::kZeroSuggestInMemoryCaching)) { |
| return; |
| } |
| |
| // Load cached ZPS response for NTP from prior session via prefs, if any. |
| StoreZeroSuggestResponse( |
| /*page_url=*/"", prefs->GetString(omnibox::kZeroSuggestCachedResults)); |
| |
| // Load cached ZPS responses for SRP/Web from prior session via prefs, if any. |
| const auto& prefs_dict = |
| prefs->GetDict(omnibox::kZeroSuggestCachedResultsWithURL); |
| for (auto it = prefs_dict.begin(); it != prefs_dict.end(); ++it) { |
| const auto& page_url = it->first; |
| const auto* response_json_ptr = (it->second).GetIfString(); |
| if (response_json_ptr) { |
| StoreZeroSuggestResponse(page_url, *response_json_ptr); |
| } |
| } |
| } |
| |
| ZeroSuggestCacheService::~ZeroSuggestCacheService() { |
| if (!base::FeatureList::IsEnabled(omnibox::kZeroSuggestInMemoryCaching)) { |
| return; |
| } |
| |
| // Dump cached ZPS response for NTP to prefs. |
| omnibox::SetUserPreferenceForZeroSuggestCachedResponse( |
| prefs_, /*page_url=*/"", /*response=*/ntp_entry_.response_json); |
| |
| // Dump cached ZPS responses for SRP/Web to prefs. |
| base::Value::Dict prefs_dict; |
| for (const auto& item : cache_) { |
| const auto& page_url = item.first; |
| const auto& response_json = item.second.response_json; |
| prefs_dict.Set(page_url, response_json); |
| } |
| prefs_->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, |
| std::move(prefs_dict)); |
| } |
| |
| CacheEntry ZeroSuggestCacheService::ReadZeroSuggestResponse( |
| const std::string& page_url) const { |
| if (base::FeatureList::IsEnabled(omnibox::kZeroSuggestInMemoryCaching)) { |
| // Read cached ZPS response for NTP. |
| if (page_url.empty()) { |
| return ntp_entry_; |
| } |
| |
| // Read cached ZPS response for SRP/Web. |
| const auto it = cache_.Get(page_url); |
| return it != cache_.end() ? it->second : CacheEntry(); |
| } |
| |
| return CacheEntry( |
| omnibox::GetUserPreferenceForZeroSuggestCachedResponse(prefs_, page_url)); |
| } |
| |
| void ZeroSuggestCacheService::StoreZeroSuggestResponse( |
| const std::string& page_url, |
| const std::string& response_json) { |
| auto entry = CacheEntry(std::string(response_json)); |
| if (base::FeatureList::IsEnabled(omnibox::kZeroSuggestInMemoryCaching)) { |
| if (page_url.empty()) { |
| // Write ZPS response for NTP to cache. |
| ntp_entry_ = entry; |
| } else { |
| // Write ZPS response for SRP/Web to cache. |
| cache_.Put(page_url, entry); |
| } |
| |
| base::UmaHistogramCounts1M( |
| "Omnibox.ZeroSuggestProvider.CacheMemoryUsage", |
| base::trace_event::EstimateMemoryUsage(cache_) + |
| base::trace_event::EstimateMemoryUsage(ntp_entry_)); |
| } else { |
| omnibox::SetUserPreferenceForZeroSuggestCachedResponse(prefs_, page_url, |
| response_json); |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnZeroSuggestResponseUpdated(page_url, entry); |
| } |
| } |
| |
| void ZeroSuggestCacheService::ClearCache() { |
| if (base::FeatureList::IsEnabled(omnibox::kZeroSuggestInMemoryCaching)) { |
| // Clear current contents of in-memory cache. |
| ntp_entry_.response_json.clear(); |
| cache_.Clear(); |
| } |
| |
| // Clear user prefs used for cross-session persistence. |
| prefs_->SetString(omnibox::kZeroSuggestCachedResults, ""); |
| prefs_->SetDict(omnibox::kZeroSuggestCachedResultsWithURL, |
| base::Value::Dict()); |
| } |
| |
| bool ZeroSuggestCacheService::IsInMemoryCacheEmptyForTesting() const { |
| return ntp_entry_.response_json.empty() && cache_.empty(); |
| } |
| |
| void ZeroSuggestCacheService::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ZeroSuggestCacheService::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| std::vector<CacheEntrySuggestResult> ZeroSuggestCacheService::GetSuggestResults( |
| const CacheEntry& cache_entry) const { |
| auto response_data = |
| SearchSuggestionParser::DeserializeJsonData(cache_entry.response_json); |
| if (!response_data) { |
| return {}; |
| } |
| |
| AutocompleteInput input(u"", metrics::OmniboxEventProto::INVALID_SPEC, |
| *scheme_classifier_); |
| SearchSuggestionParser::Results results; |
| if (!SearchSuggestionParser::ParseSuggestResults( |
| *response_data, input, *scheme_classifier_, |
| /*default_result_relevance=*/100, /*is_keyword_result=*/false, |
| &results)) { |
| return {}; |
| } |
| |
| std::vector<CacheEntrySuggestResult> suggest_results; |
| suggest_results.reserve(results.suggest_results.size()); |
| std::ranges::transform( |
| results.suggest_results, std::back_inserter(suggest_results), |
| [](const auto& suggest_result) { |
| return CacheEntrySuggestResult{suggest_result.subtypes(), |
| suggest_result.suggestion()}; |
| }); |
| |
| return suggest_results; |
| } |