| // 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_tiles/webui/ntp_tiles_internals_message_handler.h" |
| |
| #include <array> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/task_runner_util.h" |
| #include "base/values.h" |
| #include "components/favicon/core/favicon_service.h" |
| #include "components/ntp_tiles/most_visited_sites.h" |
| #include "components/ntp_tiles/pref_names.h" |
| #include "components/ntp_tiles/webui/ntp_tiles_internals_message_handler_client.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/url_formatter/url_fixer.h" |
| #include "url/gurl.h" |
| |
| namespace ntp_tiles { |
| |
| namespace { |
| |
| using FaviconResultMap = std::map<std::pair<GURL, favicon_base::IconType>, |
| favicon_base::FaviconRawBitmapResult>; |
| |
| struct IconTypeAndName { |
| favicon_base::IconType type_enum; |
| const char* type_name; |
| }; |
| |
| constexpr std::array<IconTypeAndName, 4> kIconTypesAndNames{{ |
| {favicon_base::IconType::kFavicon, "kFavicon"}, |
| {favicon_base::IconType::kTouchIcon, "kTouchIcon"}, |
| {favicon_base::IconType::kTouchPrecomposedIcon, "kTouchPrecomposedIcon"}, |
| {favicon_base::IconType::kWebManifestIcon, "kWebManifestIcon"}, |
| }}; |
| |
| std::string FormatJson(const base::Value& value) { |
| std::string pretty_printed; |
| bool ok = base::JSONWriter::WriteWithOptions( |
| value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty_printed); |
| DCHECK(ok); |
| return pretty_printed; |
| } |
| |
| } // namespace |
| |
| NTPTilesInternalsMessageHandler::NTPTilesInternalsMessageHandler( |
| favicon::FaviconService* favicon_service) |
| : favicon_service_(favicon_service), |
| client_(nullptr), |
| site_count_(8), |
| weak_ptr_factory_(this) {} |
| |
| NTPTilesInternalsMessageHandler::~NTPTilesInternalsMessageHandler() = default; |
| |
| void NTPTilesInternalsMessageHandler::RegisterMessages( |
| NTPTilesInternalsMessageHandlerClient* client) { |
| client_ = client; |
| |
| client_->RegisterMessageCallback( |
| "registerForEvents", |
| base::BindRepeating( |
| &NTPTilesInternalsMessageHandler::HandleRegisterForEvents, |
| base::Unretained(this))); |
| |
| client_->RegisterMessageCallback( |
| "update", |
| base::BindRepeating(&NTPTilesInternalsMessageHandler::HandleUpdate, |
| base::Unretained(this))); |
| |
| client_->RegisterMessageCallback( |
| "fetchSuggestions", |
| base::BindRepeating( |
| &NTPTilesInternalsMessageHandler::HandleFetchSuggestions, |
| base::Unretained(this))); |
| |
| client_->RegisterMessageCallback( |
| "viewPopularSitesJson", |
| base::BindRepeating( |
| &NTPTilesInternalsMessageHandler::HandleViewPopularSitesJson, |
| base::Unretained(this))); |
| } |
| |
| void NTPTilesInternalsMessageHandler::HandleRegisterForEvents( |
| const base::ListValue* args) { |
| if (!client_->SupportsNTPTiles()) { |
| base::DictionaryValue disabled; |
| disabled.SetBoolean("topSites", false); |
| disabled.SetBoolean("suggestionsService", false); |
| disabled.SetBoolean("popular", false); |
| disabled.SetBoolean("whitelist", false); |
| client_->CallJavascriptFunction( |
| "chrome.ntp_tiles_internals.receiveSourceInfo", disabled); |
| SendTiles(NTPTilesVector(), FaviconResultMap()); |
| return; |
| } |
| DCHECK(args->empty()); |
| |
| suggestions_status_.clear(); |
| popular_sites_json_.clear(); |
| most_visited_sites_ = client_->MakeMostVisitedSites(); |
| most_visited_sites_->SetMostVisitedURLsObserver(this, site_count_); |
| SendSourceInfo(); |
| } |
| |
| void NTPTilesInternalsMessageHandler::HandleUpdate( |
| const base::ListValue* args) { |
| if (!client_->SupportsNTPTiles()) { |
| return; |
| } |
| |
| const base::DictionaryValue* dict = nullptr; |
| DCHECK_EQ(1u, args->GetSize()); |
| args->GetDictionary(0, &dict); |
| DCHECK(dict); |
| |
| PrefService* prefs = client_->GetPrefs(); |
| |
| if (most_visited_sites_->DoesSourceExist(ntp_tiles::TileSource::POPULAR)) { |
| popular_sites_json_.clear(); |
| |
| std::string url; |
| dict->GetString("popular.overrideURL", &url); |
| if (url.empty()) { |
| prefs->ClearPref(ntp_tiles::prefs::kPopularSitesOverrideURL); |
| } else { |
| prefs->SetString(ntp_tiles::prefs::kPopularSitesOverrideURL, |
| url_formatter::FixupURL(url, std::string()).spec()); |
| } |
| |
| std::string directory; |
| dict->GetString("popular.overrideDirectory", &directory); |
| if (directory.empty()) { |
| prefs->ClearPref(ntp_tiles::prefs::kPopularSitesOverrideDirectory); |
| } else { |
| prefs->SetString(ntp_tiles::prefs::kPopularSitesOverrideDirectory, |
| directory); |
| } |
| |
| std::string country; |
| dict->GetString("popular.overrideCountry", &country); |
| if (country.empty()) { |
| prefs->ClearPref(ntp_tiles::prefs::kPopularSitesOverrideCountry); |
| } else { |
| prefs->SetString(ntp_tiles::prefs::kPopularSitesOverrideCountry, country); |
| } |
| |
| std::string version; |
| dict->GetString("popular.overrideVersion", &version); |
| if (version.empty()) { |
| prefs->ClearPref(ntp_tiles::prefs::kPopularSitesOverrideVersion); |
| } else { |
| prefs->SetString(ntp_tiles::prefs::kPopularSitesOverrideVersion, version); |
| } |
| } |
| |
| // Recreate to pick up new values. |
| // TODO(sfiera): refresh MostVisitedSites without re-creating it, as soon as |
| // that will pick up changes to the Popular Sites overrides. |
| most_visited_sites_ = client_->MakeMostVisitedSites(); |
| most_visited_sites_->SetMostVisitedURLsObserver(this, site_count_); |
| SendSourceInfo(); |
| } |
| |
| void NTPTilesInternalsMessageHandler::HandleFetchSuggestions( |
| const base::ListValue* args) { |
| DCHECK_EQ(0u, args->GetSize()); |
| if (!most_visited_sites_->DoesSourceExist( |
| ntp_tiles::TileSource::SUGGESTIONS_SERVICE)) { |
| return; |
| } |
| |
| if (most_visited_sites_->suggestions()->FetchSuggestionsData()) { |
| suggestions_status_ = "fetching..."; |
| } else { |
| suggestions_status_ = "history sync is disabled, or not yet initialized"; |
| } |
| SendSourceInfo(); |
| } |
| |
| void NTPTilesInternalsMessageHandler::HandleViewPopularSitesJson( |
| const base::ListValue* args) { |
| DCHECK_EQ(0u, args->GetSize()); |
| if (!most_visited_sites_->DoesSourceExist(ntp_tiles::TileSource::POPULAR)) { |
| return; |
| } |
| |
| popular_sites_json_ = |
| FormatJson(*most_visited_sites_->popular_sites()->GetCachedJson()); |
| SendSourceInfo(); |
| } |
| |
| void NTPTilesInternalsMessageHandler::SendSourceInfo() { |
| PrefService* prefs = client_->GetPrefs(); |
| base::DictionaryValue value; |
| |
| value.SetBoolean("topSites", |
| most_visited_sites_->DoesSourceExist(TileSource::TOP_SITES)); |
| value.SetBoolean("whitelist", |
| most_visited_sites_->DoesSourceExist(TileSource::WHITELIST)); |
| |
| if (most_visited_sites_->DoesSourceExist(TileSource::SUGGESTIONS_SERVICE)) { |
| value.SetString("suggestionsService.status", suggestions_status_); |
| } else { |
| value.SetBoolean("suggestionsService", false); |
| } |
| |
| if (most_visited_sites_->DoesSourceExist(TileSource::POPULAR)) { |
| auto* popular_sites = most_visited_sites_->popular_sites(); |
| value.SetString("popular.url", popular_sites->GetURLToFetch().spec()); |
| value.SetString("popular.directory", popular_sites->GetDirectoryToFetch()); |
| value.SetString("popular.country", popular_sites->GetCountryToFetch()); |
| value.SetString("popular.version", popular_sites->GetVersionToFetch()); |
| |
| value.SetString( |
| "popular.overrideURL", |
| prefs->GetString(ntp_tiles::prefs::kPopularSitesOverrideURL)); |
| value.SetString( |
| "popular.overrideDirectory", |
| prefs->GetString(ntp_tiles::prefs::kPopularSitesOverrideDirectory)); |
| value.SetString( |
| "popular.overrideCountry", |
| prefs->GetString(ntp_tiles::prefs::kPopularSitesOverrideCountry)); |
| value.SetString( |
| "popular.overrideVersion", |
| prefs->GetString(ntp_tiles::prefs::kPopularSitesOverrideVersion)); |
| |
| value.SetString("popular.json", popular_sites_json_); |
| } else { |
| value.SetBoolean("popular", false); |
| } |
| |
| client_->CallJavascriptFunction( |
| "chrome.ntp_tiles_internals.receiveSourceInfo", value); |
| } |
| |
| void NTPTilesInternalsMessageHandler::SendTiles( |
| const NTPTilesVector& tiles, |
| const FaviconResultMap& result_map) { |
| auto sites_list = std::make_unique<base::ListValue>(); |
| for (const NTPTile& tile : tiles) { |
| auto entry = std::make_unique<base::DictionaryValue>(); |
| entry->SetString("title", tile.title); |
| entry->SetString("url", tile.url.spec()); |
| entry->SetInteger("source", static_cast<int>(tile.source)); |
| entry->SetString("whitelistIconPath", |
| tile.whitelist_icon_path.LossyDisplayName()); |
| |
| auto icon_list = std::make_unique<base::ListValue>(); |
| for (const auto& entry : kIconTypesAndNames) { |
| FaviconResultMap::const_iterator it = result_map.find( |
| FaviconResultMap::key_type(tile.url, entry.type_enum)); |
| |
| if (it != result_map.end()) { |
| const favicon_base::FaviconRawBitmapResult& result = it->second; |
| auto icon = std::make_unique<base::DictionaryValue>(); |
| icon->SetString("url", result.icon_url.spec()); |
| icon->SetString("type", entry.type_name); |
| icon->SetBoolean("onDemand", !result.fetched_because_of_page_visit); |
| icon->SetInteger("width", result.pixel_size.width()); |
| icon->SetInteger("height", result.pixel_size.height()); |
| icon_list->Append(std::move(icon)); |
| } |
| } |
| entry->Set("icons", std::move(icon_list)); |
| |
| sites_list->Append(std::move(entry)); |
| } |
| |
| base::DictionaryValue result; |
| result.Set("sites", std::move(sites_list)); |
| client_->CallJavascriptFunction("chrome.ntp_tiles_internals.receiveSites", |
| result); |
| } |
| |
| void NTPTilesInternalsMessageHandler::OnURLsAvailable( |
| const std::map<SectionType, NTPTilesVector>& sections) { |
| cancelable_task_tracker_.TryCancelAll(); |
| |
| // TODO(fhorschig): Handle non-personalized tiles - https://crbug.com/753852. |
| const NTPTilesVector& tiles = sections.at(SectionType::PERSONALIZED); |
| if (tiles.empty()) { |
| SendTiles(tiles, FaviconResultMap()); |
| return; |
| } |
| |
| auto on_lookup_done = base::BindRepeating( |
| &NTPTilesInternalsMessageHandler::OnFaviconLookupDone, |
| // Unretained(this) is safe because of |cancelable_task_tracker_|. |
| base::Unretained(this), tiles, base::Owned(new FaviconResultMap()), |
| base::Owned(new size_t(tiles.size() * kIconTypesAndNames.size()))); |
| |
| for (const NTPTile& tile : tiles) { |
| for (const auto& entry : kIconTypesAndNames) { |
| favicon_service_->GetLargestRawFaviconForPageURL( |
| tile.url, std::vector<favicon_base::IconTypeSet>({{entry.type_enum}}), |
| /*minimum_size_in_pixels=*/0, base::Bind(on_lookup_done, tile.url), |
| &cancelable_task_tracker_); |
| } |
| } |
| } |
| |
| void NTPTilesInternalsMessageHandler::OnIconMadeAvailable( |
| const GURL& site_url) {} |
| |
| void NTPTilesInternalsMessageHandler::OnFaviconLookupDone( |
| const NTPTilesVector& tiles, |
| FaviconResultMap* result_map, |
| size_t* num_pending_lookups, |
| const GURL& page_url, |
| const favicon_base::FaviconRawBitmapResult& result) { |
| DCHECK_NE(0u, *num_pending_lookups); |
| |
| result_map->emplace( |
| std::pair<GURL, favicon_base::IconType>(page_url, result.icon_type), |
| result); |
| |
| --*num_pending_lookups; |
| if (*num_pending_lookups == 0) |
| SendTiles(tiles, *result_map); |
| } |
| |
| } // namespace ntp_tiles |