123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- // Copyright 2016 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/ntp_snippets/content_suggestions_service.h"
- #include <algorithm>
- #include <iterator>
- #include <set>
- #include <utility>
- #include "base/bind.h"
- #include "base/containers/contains.h"
- #include "base/containers/cxx20_erase.h"
- #include "base/location.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "base/time/default_clock.h"
- #include "base/values.h"
- #include "components/favicon/core/large_icon_service.h"
- #include "components/favicon_base/fallback_icon_style.h"
- #include "components/favicon_base/favicon_types.h"
- #include "components/ntp_snippets/content_suggestions_metrics.h"
- #include "components/ntp_snippets/pref_names.h"
- #include "components/ntp_snippets/remote/remote_suggestions_provider.h"
- #include "components/prefs/pref_registry_simple.h"
- #include "components/prefs/pref_service.h"
- #include "net/traffic_annotation/network_traffic_annotation.h"
- #include "ui/gfx/image/image.h"
- namespace ntp_snippets {
- namespace {
- // Enumeration listing all possible outcomes for fetch attempts of favicons for
- // content suggestions. Used for UMA histograms, so do not change existing
- // values. Insert new values at the end, and update the histogram definition.
- // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.ntp.snippets
- enum class FaviconFetchResult {
- SUCCESS_CACHED = 0,
- SUCCESS_FETCHED = 1,
- FAILURE = 2,
- COUNT = 3
- };
- } // namespace
- ContentSuggestionsService::ContentSuggestionsService(
- State state,
- signin::IdentityManager* identity_manager,
- history::HistoryService* history_service,
- favicon::LargeIconService* large_icon_service,
- PrefService* pref_service,
- std::unique_ptr<CategoryRanker> category_ranker,
- std::unique_ptr<UserClassifier> user_classifier,
- std::unique_ptr<RemoteSuggestionsScheduler> remote_suggestions_scheduler)
- : state_(state),
- remote_suggestions_provider_(nullptr),
- large_icon_service_(large_icon_service),
- pref_service_(pref_service),
- remote_suggestions_scheduler_(std::move(remote_suggestions_scheduler)),
- user_classifier_(std::move(user_classifier)),
- category_ranker_(std::move(category_ranker)) {
- // Can be null in tests.
- if (identity_manager) {
- identity_manager_observation_.Observe(identity_manager);
- }
- if (history_service) {
- history_service_observation_.Observe(history_service);
- }
- RestoreDismissedCategoriesFromPrefs();
- }
- ContentSuggestionsService::~ContentSuggestionsService() = default;
- void ContentSuggestionsService::Shutdown() {
- remote_suggestions_provider_ = nullptr;
- remote_suggestions_scheduler_ = nullptr;
- suggestions_by_category_.clear();
- providers_by_category_.clear();
- categories_.clear();
- providers_.clear();
- state_ = State::DISABLED;
- for (Observer& observer : observers_) {
- observer.ContentSuggestionsServiceShutdown();
- }
- }
- // static
- void ContentSuggestionsService::RegisterProfilePrefs(
- PrefRegistrySimple* registry) {
- registry->RegisterListPref(prefs::kDismissedCategories);
- }
- std::vector<Category> ContentSuggestionsService::GetCategories() const {
- std::vector<Category> sorted_categories = categories_;
- std::sort(sorted_categories.begin(), sorted_categories.end(),
- [this](const Category& left, const Category& right) {
- return category_ranker_->Compare(left, right);
- });
- return sorted_categories;
- }
- CategoryStatus ContentSuggestionsService::GetCategoryStatus(
- Category category) const {
- if (state_ == State::DISABLED) {
- return CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED;
- }
- auto iterator = providers_by_category_.find(category);
- if (iterator == providers_by_category_.end()) {
- return CategoryStatus::NOT_PROVIDED;
- }
- return iterator->second->GetCategoryStatus(category);
- }
- absl::optional<CategoryInfo> ContentSuggestionsService::GetCategoryInfo(
- Category category) const {
- auto iterator = providers_by_category_.find(category);
- if (iterator == providers_by_category_.end()) {
- return absl::optional<CategoryInfo>();
- }
- return iterator->second->GetCategoryInfo(category);
- }
- const std::vector<ContentSuggestion>&
- ContentSuggestionsService::GetSuggestionsForCategory(Category category) const {
- auto iterator = suggestions_by_category_.find(category);
- if (iterator == suggestions_by_category_.end()) {
- return no_suggestions_;
- }
- return iterator->second;
- }
- void ContentSuggestionsService::FetchSuggestionImage(
- const ContentSuggestion::ID& suggestion_id,
- ImageFetchedCallback callback) {
- if (!providers_by_category_.count(suggestion_id.category())) {
- LOG(WARNING) << "Requested image for suggestion " << suggestion_id
- << " for unavailable category " << suggestion_id.category();
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
- return;
- }
- providers_by_category_[suggestion_id.category()]->FetchSuggestionImage(
- suggestion_id, std::move(callback));
- }
- void ContentSuggestionsService::FetchSuggestionImageData(
- const ContentSuggestion::ID& suggestion_id,
- ImageDataFetchedCallback callback) {
- if (!providers_by_category_.count(suggestion_id.category())) {
- LOG(WARNING) << "Requested image for suggestion " << suggestion_id
- << " for unavailable category " << suggestion_id.category();
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), std::string()));
- return;
- }
- providers_by_category_[suggestion_id.category()]->FetchSuggestionImageData(
- suggestion_id, std::move(callback));
- }
- // TODO(jkrcal): Split the favicon fetching into a separate class.
- void ContentSuggestionsService::FetchSuggestionFavicon(
- const ContentSuggestion::ID& suggestion_id,
- int minimum_size_in_pixel,
- int desired_size_in_pixel,
- ImageFetchedCallback callback) {
- const GURL& domain_with_favicon = GetFaviconDomain(suggestion_id);
- if (!domain_with_favicon.is_valid() || !large_icon_service_) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
- return;
- }
- GetFaviconFromCache(domain_with_favicon, minimum_size_in_pixel,
- desired_size_in_pixel, std::move(callback),
- /*continue_to_google_server=*/true);
- }
- GURL ContentSuggestionsService::GetFaviconDomain(
- const ContentSuggestion::ID& suggestion_id) {
- const std::vector<ContentSuggestion>& suggestions =
- suggestions_by_category_[suggestion_id.category()];
- auto position =
- std::find_if(suggestions.begin(), suggestions.end(),
- [&suggestion_id](const ContentSuggestion& suggestion) {
- return suggestion_id == suggestion.id();
- });
- if (position != suggestions.end()) {
- return position->url_with_favicon();
- }
- // Look up the URL in the archive of |remote_suggestions_provider_|.
- // TODO(jkrcal): Fix how Fetch more works or find other ways to remove this
- // hack. crbug.com/714031
- if (providers_by_category_[suggestion_id.category()] ==
- remote_suggestions_provider_) {
- return remote_suggestions_provider_->GetUrlWithFavicon(suggestion_id);
- }
- return GURL();
- }
- void ContentSuggestionsService::GetFaviconFromCache(
- const GURL& publisher_url,
- int minimum_size_in_pixel,
- int desired_size_in_pixel,
- ImageFetchedCallback callback,
- bool continue_to_google_server) {
- // TODO(jkrcal): Create a general wrapper function in LargeIconService that
- // does handle the get-from-cache-and-fallback-to-google-server functionality
- // in one shot (for all clients that do not need to react in between).
- // Use desired_size = 0 for getting the icon from the cache (so that the icon
- // is not poorly rescaled by LargeIconService).
- large_icon_service_->GetLargeIconImageOrFallbackStyleForPageUrl(
- publisher_url, minimum_size_in_pixel, /*desired_size_in_pixel=*/0,
- base::BindOnce(&ContentSuggestionsService::OnGetFaviconFromCacheFinished,
- base::Unretained(this), publisher_url,
- minimum_size_in_pixel, desired_size_in_pixel,
- std::move(callback), continue_to_google_server),
- &favicons_task_tracker_);
- }
- void ContentSuggestionsService::OnGetFaviconFromCacheFinished(
- const GURL& publisher_url,
- int minimum_size_in_pixel,
- int desired_size_in_pixel,
- ImageFetchedCallback callback,
- bool continue_to_google_server,
- const favicon_base::LargeIconImageResult& result) {
- if (!result.image.IsEmpty()) {
- std::move(callback).Run(result.image);
- // Update the time when the icon was last requested - postpone thus the
- // automatic eviction of the favicon from the favicon database.
- large_icon_service_->TouchIconFromGoogleServer(result.icon_url);
- return;
- }
- if (!continue_to_google_server ||
- (result.fallback_icon_style &&
- !result.fallback_icon_style->is_default_background_color)) {
- // We cannot download from the server if there is some small icon in the
- // cache (resulting in non-default background color) or if we already did
- // so.
- std::move(callback).Run(gfx::Image());
- return;
- }
- // Try to fetch the favicon from a Google favicon server.
- // TODO(jkrcal): Currently used only for Articles for you which have public
- // URLs. Let the provider decide whether |publisher_url| may be private or
- // not.
- net::NetworkTrafficAnnotationTag traffic_annotation =
- net::DefineNetworkTrafficAnnotation("content_suggestion_get_favicon", R"(
- semantics {
- sender: "Content Suggestion"
- description:
- "Sends a request to a Google server to retrieve the favicon bitmap "
- "for an article suggestion on the new tab page (URLs are public "
- "and provided by Google)."
- trigger:
- "A request can be sent if Chrome does not have a favicon for a "
- "particular page."
- data: "Page URL and desired icon size."
- destination: GOOGLE_OWNED_SERVICE
- }
- policy {
- cookies_allowed: NO
- setting: "This feature cannot be disabled by settings."
- policy_exception_justification: "Not implemented."
- })");
- large_icon_service_
- ->GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
- publisher_url,
- /*may_page_url_be_private=*/false,
- /*should_trim_page_url_path=*/false, traffic_annotation,
- base::BindOnce(
- &ContentSuggestionsService::OnGetFaviconFromGoogleServerFinished,
- base::Unretained(this), publisher_url, minimum_size_in_pixel,
- desired_size_in_pixel, std::move(callback)));
- }
- void ContentSuggestionsService::OnGetFaviconFromGoogleServerFinished(
- const GURL& publisher_url,
- int minimum_size_in_pixel,
- int desired_size_in_pixel,
- ImageFetchedCallback callback,
- favicon_base::GoogleFaviconServerRequestStatus status) {
- if (status != favicon_base::GoogleFaviconServerRequestStatus::SUCCESS) {
- std::move(callback).Run(gfx::Image());
- return;
- }
- GetFaviconFromCache(publisher_url, minimum_size_in_pixel,
- desired_size_in_pixel, std::move(callback),
- /*continue_to_google_server=*/false);
- }
- void ContentSuggestionsService::ClearHistory(
- base::Time begin,
- base::Time end,
- const base::RepeatingCallback<bool(const GURL& url)>& filter) {
- for (const auto& provider : providers_) {
- provider->ClearHistory(begin, end, filter);
- }
- category_ranker_->ClearHistory(begin, end);
- // This potentially removed personalized data which we shouldn't display
- // anymore.
- for (Observer& observer : observers_) {
- observer.OnFullRefreshRequired();
- }
- }
- void ContentSuggestionsService::ClearAllCachedSuggestions() {
- suggestions_by_category_.clear();
- for (const auto& provider : providers_) {
- provider->ClearCachedSuggestions();
- }
- for (Observer& observer : observers_) {
- observer.OnFullRefreshRequired();
- }
- }
- void ContentSuggestionsService::GetDismissedSuggestionsForDebugging(
- Category category,
- DismissedSuggestionsCallback callback) {
- auto iterator = providers_by_category_.find(category);
- if (iterator != providers_by_category_.end()) {
- iterator->second->GetDismissedSuggestionsForDebugging(category,
- std::move(callback));
- } else {
- std::move(callback).Run(std::vector<ContentSuggestion>());
- }
- }
- void ContentSuggestionsService::ClearDismissedSuggestionsForDebugging(
- Category category) {
- auto iterator = providers_by_category_.find(category);
- if (iterator != providers_by_category_.end()) {
- iterator->second->ClearDismissedSuggestionsForDebugging(category);
- }
- }
- void ContentSuggestionsService::DismissSuggestion(
- const ContentSuggestion::ID& suggestion_id) {
- if (!providers_by_category_.count(suggestion_id.category())) {
- LOG(WARNING) << "Dismissed suggestion " << suggestion_id
- << " for unavailable category " << suggestion_id.category();
- return;
- }
- metrics::RecordContentSuggestionDismissed();
- providers_by_category_[suggestion_id.category()]->DismissSuggestion(
- suggestion_id);
- // Remove the suggestion locally if it is present. A suggestion may be missing
- // localy e.g. if it was sent to UI through |Fetch| or it has been dismissed
- // from a different NTP.
- RemoveSuggestionByID(suggestion_id);
- }
- void ContentSuggestionsService::DismissCategory(Category category) {
- auto providers_it = providers_by_category_.find(category);
- if (providers_it == providers_by_category_.end()) {
- return;
- }
- metrics::RecordCategoryDismissed();
- ContentSuggestionsProvider* provider = providers_it->second;
- UnregisterCategory(category, provider);
- dismissed_providers_by_category_[category] = provider;
- StoreDismissedCategoriesToPrefs();
- category_ranker_->OnCategoryDismissed(category);
- }
- void ContentSuggestionsService::RestoreDismissedCategories() {
- // Make a copy as the original will be modified during iteration.
- auto dismissed_providers_by_category_copy = dismissed_providers_by_category_;
- for (const auto& category_provider_pair :
- dismissed_providers_by_category_copy) {
- RestoreDismissedCategory(category_provider_pair.first);
- }
- StoreDismissedCategoriesToPrefs();
- DCHECK(dismissed_providers_by_category_.empty());
- }
- void ContentSuggestionsService::AddObserver(Observer* observer) {
- observers_.AddObserver(observer);
- }
- void ContentSuggestionsService::RemoveObserver(Observer* observer) {
- observers_.RemoveObserver(observer);
- }
- void ContentSuggestionsService::RegisterProvider(
- std::unique_ptr<ContentSuggestionsProvider> provider) {
- DCHECK(state_ == State::ENABLED);
- providers_.push_back(std::move(provider));
- }
- void ContentSuggestionsService::Fetch(
- const Category& category,
- const std::set<std::string>& known_suggestion_ids,
- FetchDoneCallback callback) {
- auto providers_it = providers_by_category_.find(category);
- if (providers_it == providers_by_category_.end()) {
- return;
- }
- metrics::RecordFetchAction();
- providers_it->second->Fetch(category, known_suggestion_ids,
- std::move(callback));
- }
- void ContentSuggestionsService::ReloadSuggestions() {
- for (const auto& provider : providers_) {
- provider->ReloadSuggestions();
- }
- }
- bool ContentSuggestionsService::AreRemoteSuggestionsEnabled() const {
- return remote_suggestions_provider_ &&
- !remote_suggestions_provider_->IsDisabled();
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Private methods
- void ContentSuggestionsService::OnNewSuggestions(
- ContentSuggestionsProvider* provider,
- Category category,
- std::vector<ContentSuggestion> suggestions) {
- // Providers shouldn't call this when they're in a non-available state.
- DCHECK(
- IsCategoryStatusInitOrAvailable(provider->GetCategoryStatus(category)));
- if (TryRegisterProviderForCategory(provider, category)) {
- NotifyCategoryStatusChanged(category);
- } else if (IsCategoryDismissed(category)) {
- // The category has been registered as a dismissed one. We need to
- // check if the dismissal can be cleared now that we received new data.
- if (suggestions.empty()) {
- return;
- }
- RestoreDismissedCategory(category);
- StoreDismissedCategoriesToPrefs();
- NotifyCategoryStatusChanged(category);
- }
- if (!IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
- // A provider shouldn't send us suggestions while it's not available.
- DCHECK(suggestions.empty());
- return;
- }
- suggestions_by_category_[category] = std::move(suggestions);
- for (Observer& observer : observers_) {
- observer.OnNewSuggestions(category);
- }
- }
- void ContentSuggestionsService::OnCategoryStatusChanged(
- ContentSuggestionsProvider* provider,
- Category category,
- CategoryStatus new_status) {
- if (new_status == CategoryStatus::NOT_PROVIDED) {
- UnregisterCategory(category, provider);
- } else {
- if (!IsCategoryStatusAvailable(new_status)) {
- suggestions_by_category_.erase(category);
- }
- TryRegisterProviderForCategory(provider, category);
- DCHECK_EQ(new_status, provider->GetCategoryStatus(category));
- }
- if (!IsCategoryDismissed(category)) {
- NotifyCategoryStatusChanged(category);
- }
- }
- void ContentSuggestionsService::OnSuggestionInvalidated(
- ContentSuggestionsProvider* provider,
- const ContentSuggestion::ID& suggestion_id) {
- RemoveSuggestionByID(suggestion_id);
- for (Observer& observer : observers_) {
- observer.OnSuggestionInvalidated(suggestion_id);
- }
- }
- // signin::IdentityManager::Observer implementation
- void ContentSuggestionsService::OnPrimaryAccountChanged(
- const signin::PrimaryAccountChangeEvent& event_details) {
- switch (event_details.GetEventTypeFor(signin::ConsentLevel::kSync)) {
- case signin::PrimaryAccountChangeEvent::Type::kSet:
- OnSignInStateChanged(/*has_signed_in=*/true);
- break;
- case signin::PrimaryAccountChangeEvent::Type::kCleared:
- OnSignInStateChanged(/*has_signed_in=*/false);
- break;
- case signin::PrimaryAccountChangeEvent::Type::kNone:
- break;
- }
- }
- // history::HistoryServiceObserver implementation.
- void ContentSuggestionsService::OnURLsDeleted(
- history::HistoryService* history_service,
- const history::DeletionInfo& deletion_info) {
- // We don't care about expired entries.
- if (deletion_info.is_from_expiration()) {
- return;
- }
- if (deletion_info.IsAllHistory()) {
- base::RepeatingCallback<bool(const GURL& url)> filter =
- base::BindRepeating([](const GURL& url) { return true; });
- ClearHistory(base::Time(), base::Time::Max(), filter);
- } else {
- // If a user deletes a single URL, we don't consider this a clear user
- // intend to clear our data.
- // TODO(tschumann): Single URL deletions should be handled on a case-by-case
- // basis. However this depends on the provider's details and thus cannot be
- // done here. Introduce a OnURLsDeleted() method on the providers to move
- // this decision further down.
- if (deletion_info.deleted_rows().size() < 2) {
- return;
- }
- std::set<GURL> deleted_urls;
- for (const history::URLRow& row : deletion_info.deleted_rows()) {
- deleted_urls.insert(row.url());
- }
- base::RepeatingCallback<bool(const GURL& url)> filter =
- base::BindRepeating([](const std::set<GURL>& set,
- const GURL& url) { return set.count(url) != 0; },
- deleted_urls);
- // We usually don't have any time-related information (the URLRow objects
- // usually don't provide a |last_visit()| timestamp. Hence we simply clear
- // the whole history for the selected URLs.
- ClearHistory(base::Time(), base::Time::Max(), filter);
- }
- }
- void ContentSuggestionsService::HistoryServiceBeingDeleted(
- history::HistoryService* history_service) {
- DCHECK(history_service_observation_.IsObservingSource(history_service));
- history_service_observation_.Reset();
- }
- bool ContentSuggestionsService::TryRegisterProviderForCategory(
- ContentSuggestionsProvider* provider,
- Category category) {
- auto it = providers_by_category_.find(category);
- if (it != providers_by_category_.end()) {
- DCHECK_EQ(it->second, provider);
- return false;
- }
- auto dismissed_it = dismissed_providers_by_category_.find(category);
- if (dismissed_it != dismissed_providers_by_category_.end()) {
- // The initialisation of dismissed categories registers them with |nullptr|
- // for providers, we need to check for that to see if the provider is
- // already registered or not.
- if (!dismissed_it->second) {
- dismissed_it->second = provider;
- } else {
- DCHECK_EQ(dismissed_it->second, provider);
- }
- return false;
- }
- RegisterCategory(category, provider);
- return true;
- }
- void ContentSuggestionsService::RegisterCategory(
- Category category,
- ContentSuggestionsProvider* provider) {
- DCHECK(!base::Contains(providers_by_category_, category));
- DCHECK(!IsCategoryDismissed(category));
- providers_by_category_[category] = provider;
- categories_.push_back(category);
- if (IsCategoryStatusAvailable(provider->GetCategoryStatus(category))) {
- suggestions_by_category_.insert(
- std::make_pair(category, std::vector<ContentSuggestion>()));
- }
- }
- void ContentSuggestionsService::UnregisterCategory(
- Category category,
- ContentSuggestionsProvider* provider) {
- auto providers_it = providers_by_category_.find(category);
- if (providers_it == providers_by_category_.end()) {
- DCHECK(IsCategoryDismissed(category));
- return;
- }
- DCHECK_EQ(provider, providers_it->second);
- providers_by_category_.erase(providers_it);
- categories_.erase(
- std::find(categories_.begin(), categories_.end(), category));
- suggestions_by_category_.erase(category);
- }
- bool ContentSuggestionsService::RemoveSuggestionByID(
- const ContentSuggestion::ID& suggestion_id) {
- std::vector<ContentSuggestion>* suggestions =
- &suggestions_by_category_[suggestion_id.category()];
- auto position =
- std::find_if(suggestions->begin(), suggestions->end(),
- [&suggestion_id](const ContentSuggestion& suggestion) {
- return suggestion_id == suggestion.id();
- });
- if (position == suggestions->end()) {
- return false;
- }
- suggestions->erase(position);
- return true;
- }
- void ContentSuggestionsService::NotifyCategoryStatusChanged(Category category) {
- for (Observer& observer : observers_) {
- observer.OnCategoryStatusChanged(category, GetCategoryStatus(category));
- }
- }
- void ContentSuggestionsService::OnSignInStateChanged(bool has_signed_in) {
- // First notify the providers, so they can make the required changes.
- for (const auto& provider : providers_) {
- provider->OnSignInStateChanged(has_signed_in);
- }
- // Finally notify the observers so they refresh only after the backend is
- // ready.
- for (Observer& observer : observers_) {
- observer.OnFullRefreshRequired();
- }
- }
- bool ContentSuggestionsService::IsCategoryDismissed(Category category) const {
- return base::Contains(dismissed_providers_by_category_, category);
- }
- void ContentSuggestionsService::RestoreDismissedCategory(Category category) {
- auto dismissed_it = dismissed_providers_by_category_.find(category);
- DCHECK(base::Contains(dismissed_providers_by_category_, category));
- // Keep the reference to the provider and remove it from the dismissed ones,
- // because the category registration enforces that it's not dismissed.
- ContentSuggestionsProvider* provider = dismissed_it->second;
- dismissed_providers_by_category_.erase(dismissed_it);
- if (provider) {
- RegisterCategory(category, provider);
- }
- }
- void ContentSuggestionsService::RestoreDismissedCategoriesFromPrefs() {
- // This must only be called at startup.
- DCHECK(dismissed_providers_by_category_.empty());
- DCHECK(providers_by_category_.empty());
- const base::Value::List& list =
- pref_service_->GetValueList(prefs::kDismissedCategories);
- for (const base::Value& entry : list) {
- if (!entry.is_int()) {
- DLOG(WARNING) << "Invalid category pref value: " << entry;
- continue;
- }
- // When the provider is registered, it will be stored in this map.
- dismissed_providers_by_category_[Category::FromIDValue(entry.GetInt())] =
- nullptr;
- }
- }
- void ContentSuggestionsService::StoreDismissedCategoriesToPrefs() {
- base::ListValue list;
- for (const auto& category_provider_pair : dismissed_providers_by_category_) {
- list.Append(category_provider_pair.first.id());
- }
- pref_service_->Set(prefs::kDismissedCategories, list);
- }
- void ContentSuggestionsService::DestroyCategoryAndItsProvider(
- Category category) {
- // Destroying articles category is more complex and not implemented.
- DCHECK_NE(category, Category::FromKnownCategory(KnownCategories::ARTICLES));
- if (providers_by_category_.count(category) != 1) {
- return;
- }
- { // Destroy the provider and delete its mentions.
- ContentSuggestionsProvider* raw_provider = providers_by_category_[category];
- base::EraseIf(
- providers_,
- [&raw_provider](
- const std::unique_ptr<ContentSuggestionsProvider>& provider) {
- return provider.get() == raw_provider;
- });
- providers_by_category_.erase(category);
- if (dismissed_providers_by_category_.count(category) == 1) {
- dismissed_providers_by_category_[category] = nullptr;
- }
- }
- suggestions_by_category_.erase(category);
- auto it = std::find(categories_.begin(), categories_.end(), category);
- categories_.erase(it);
- // Notify observers that the category is gone.
- NotifyCategoryStatusChanged(category);
- }
- } // namespace ntp_snippets
|