most_visited_sites.cc 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ntp_tiles/most_visited_sites.h"
  5. #include <algorithm>
  6. #include <cctype>
  7. #include <iterator>
  8. #include <memory>
  9. #include <utility>
  10. #include "base/bind.h"
  11. #include "base/callback.h"
  12. #include "base/callback_helpers.h"
  13. #include "base/feature_list.h"
  14. #include "base/logging.h"
  15. #include "base/metrics/user_metrics.h"
  16. #include "base/observer_list.h"
  17. #include "base/strings/string_split.h"
  18. #include "base/strings/string_util.h"
  19. #include "base/strings/utf_string_conversions.h"
  20. #include "build/build_config.h"
  21. #include "components/ntp_tiles/constants.h"
  22. #include "components/ntp_tiles/deleted_tile_type.h"
  23. #include "components/ntp_tiles/features.h"
  24. #include "components/ntp_tiles/icon_cacher.h"
  25. #include "components/ntp_tiles/metrics.h"
  26. #include "components/ntp_tiles/pref_names.h"
  27. #include "components/ntp_tiles/switches.h"
  28. #include "components/pref_registry/pref_registry_syncable.h"
  29. #include "components/prefs/pref_service.h"
  30. #include "components/search/ntp_features.h"
  31. #include "components/webapps/common/constants.h"
  32. #include "extensions/buildflags/buildflags.h"
  33. #if BUILDFLAG(ENABLE_EXTENSIONS)
  34. // GN doesn't understand conditional includes, so we need nogncheck here.
  35. #include "extensions/common/constants.h" // nogncheck
  36. #endif
  37. using history::TopSites;
  38. namespace ntp_tiles {
  39. namespace {
  40. // URL host prefixes. Hosts with these prefixes often redirect to each other, or
  41. // have the same content.
  42. // Popular sites are excluded if the user has visited a page whose host only
  43. // differs by one of these prefixes. Even if the URL does not point to the exact
  44. // same page, the user will have a personalized suggestion that is more likely
  45. // to be of use for them.
  46. // A cleaner way could be checking the history for redirects but this requires
  47. // the page to be visited on the device.
  48. const char* kKnownGenericPagePrefixes[] = {
  49. "m.", "mobile.", // Common prefixes among popular sites.
  50. "edition.", // Used among news papers (CNN, Independent, ...)
  51. "www.", // Usually no-www domains redirect to www or vice-versa.
  52. // The following entry MUST REMAIN LAST as it is prefix of every string!
  53. ""}; // The no-www domain matches domains on same level .
  54. // Determine whether we need any tiles from PopularSites to fill up a grid of
  55. // |num_tiles| tiles.
  56. bool NeedPopularSites(const PrefService* prefs, int num_tiles) {
  57. return prefs->GetInteger(prefs::kNumPersonalTiles) < num_tiles;
  58. }
  59. bool HasHomeTile(const NTPTilesVector& tiles) {
  60. for (const auto& tile : tiles) {
  61. if (tile.source == TileSource::HOMEPAGE)
  62. return true;
  63. }
  64. return false;
  65. }
  66. std::string StripFirstGenericPrefix(const std::string& host) {
  67. for (const char* prefix : kKnownGenericPagePrefixes) {
  68. if (base::StartsWith(host, prefix, base::CompareCase::INSENSITIVE_ASCII)) {
  69. return std::string(
  70. base::TrimString(host, prefix, base::TrimPositions::TRIM_LEADING));
  71. }
  72. }
  73. return host;
  74. }
  75. bool ShouldShowPopularSites() {
  76. return base::FeatureList::IsEnabled(kUsePopularSitesSuggestions);
  77. }
  78. // Generate a short title for Most Visited items before they're converted to
  79. // custom links.
  80. std::u16string GenerateShortTitle(const std::u16string& title) {
  81. // Empty title only happened in the unittests.
  82. if (title.empty())
  83. return std::u16string();
  84. std::vector<std::u16string> short_title_list = SplitString(
  85. title, u"-:|;", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  86. // Make sure it doesn't crash when the title only contains spaces.
  87. if (short_title_list.empty())
  88. return std::u16string();
  89. std::u16string short_title_front = short_title_list.front();
  90. std::u16string short_title_back = short_title_list.back();
  91. std::u16string short_title = short_title_front;
  92. if (short_title_front != short_title_back) {
  93. int words_in_front =
  94. SplitString(short_title_front, base::kWhitespaceASCIIAs16,
  95. base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)
  96. .size();
  97. int words_in_back =
  98. SplitString(short_title_back, base::kWhitespaceASCIIAs16,
  99. base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)
  100. .size();
  101. if (words_in_front >= 3 && words_in_back >= 1 && words_in_back <= 3) {
  102. short_title = short_title_back;
  103. }
  104. }
  105. return short_title;
  106. }
  107. } // namespace
  108. MostVisitedSites::MostVisitedSites(
  109. PrefService* prefs,
  110. scoped_refptr<history::TopSites> top_sites,
  111. std::unique_ptr<PopularSites> popular_sites,
  112. std::unique_ptr<CustomLinksManager> custom_links,
  113. std::unique_ptr<IconCacher> icon_cacher,
  114. std::unique_ptr<MostVisitedSitesSupervisor> supervisor,
  115. bool is_default_chrome_app_migrated)
  116. : prefs_(prefs),
  117. top_sites_(top_sites),
  118. popular_sites_(std::move(popular_sites)),
  119. custom_links_(std::move(custom_links)),
  120. icon_cacher_(std::move(icon_cacher)),
  121. supervisor_(std::move(supervisor)),
  122. is_default_chrome_app_migrated_(is_default_chrome_app_migrated),
  123. max_num_sites_(0u),
  124. mv_source_(TileSource::TOP_SITES),
  125. is_observing_(false) {
  126. DCHECK(prefs_);
  127. // top_sites_ can be null in tests.
  128. // TODO(sfiera): have iOS use a dummy TopSites in its tests.
  129. if (supervisor_)
  130. supervisor_->SetObserver(this);
  131. }
  132. MostVisitedSites::~MostVisitedSites() {
  133. if (supervisor_)
  134. supervisor_->SetObserver(nullptr);
  135. observers_.Clear();
  136. }
  137. // static
  138. bool MostVisitedSites::IsHostOrMobilePageKnown(
  139. const std::set<std::string>& hosts_to_skip,
  140. const std::string& host) {
  141. std::string no_prefix_host = StripFirstGenericPrefix(host);
  142. for (const char* prefix : kKnownGenericPagePrefixes) {
  143. if (hosts_to_skip.count(prefix + no_prefix_host) ||
  144. hosts_to_skip.count(prefix + host)) {
  145. return true;
  146. }
  147. }
  148. return false;
  149. }
  150. bool MostVisitedSites::DoesSourceExist(TileSource source) const {
  151. switch (source) {
  152. case TileSource::TOP_SITES:
  153. return top_sites_ != nullptr;
  154. case TileSource::POPULAR_BAKED_IN:
  155. case TileSource::POPULAR:
  156. return popular_sites_ != nullptr;
  157. case TileSource::HOMEPAGE:
  158. return homepage_client_ != nullptr;
  159. case TileSource::ALLOWLIST:
  160. return supervisor_ != nullptr;
  161. case TileSource::CUSTOM_LINKS:
  162. return custom_links_ != nullptr;
  163. case TileSource::EXPLORE:
  164. return explore_sites_client_ != nullptr;
  165. }
  166. NOTREACHED();
  167. return false;
  168. }
  169. void MostVisitedSites::SetHomepageClient(
  170. std::unique_ptr<HomepageClient> client) {
  171. DCHECK(client);
  172. homepage_client_ = std::move(client);
  173. }
  174. void MostVisitedSites::SetExploreSitesClient(
  175. std::unique_ptr<ExploreSitesClient> client) {
  176. explore_sites_client_ = std::move(client);
  177. }
  178. void MostVisitedSites::AddMostVisitedURLsObserver(Observer* observer,
  179. size_t max_num_sites) {
  180. observers_.AddObserver(observer);
  181. // All observer must provide the same |max_num_sites| value.
  182. DCHECK(max_num_sites_ == 0u || max_num_sites_ == max_num_sites);
  183. max_num_sites_ = max_num_sites;
  184. // Starts observing the following sources when the first observer is added.
  185. if (!is_observing_) {
  186. is_observing_ = true;
  187. // The order for this condition is important, ShouldShowPopularSites()
  188. // should always be called last to keep metrics as relevant as possible.
  189. if (popular_sites_ && NeedPopularSites(prefs_, GetMaxNumSites()) &&
  190. ShouldShowPopularSites()) {
  191. popular_sites_->MaybeStartFetch(
  192. false, base::BindOnce(&MostVisitedSites::OnPopularSitesDownloaded,
  193. base::Unretained(this)));
  194. }
  195. if (top_sites_) {
  196. // Register as TopSitesObserver so that we can update ourselves when the
  197. // TopSites changes.
  198. top_sites_observation_.Observe(top_sites_.get());
  199. }
  200. if (custom_links_) {
  201. custom_links_subscription_ =
  202. custom_links_->RegisterCallbackForOnChanged(base::BindRepeating(
  203. &MostVisitedSites::OnCustomLinksChanged, base::Unretained(this)));
  204. }
  205. }
  206. // Immediately build the current set of tiles, getting suggestions from
  207. // TopSites.
  208. BuildCurrentTiles();
  209. // Also start a request for fresh suggestions.
  210. Refresh();
  211. }
  212. void MostVisitedSites::RemoveMostVisitedURLsObserver(Observer* observer) {
  213. observers_.RemoveObserver(observer);
  214. }
  215. void MostVisitedSites::Refresh() {
  216. if (top_sites_) {
  217. // TopSites updates itself after a delay. To ensure up-to-date results,
  218. // force an update now.
  219. top_sites_->SyncWithHistory();
  220. }
  221. }
  222. void MostVisitedSites::RefreshTiles() {
  223. BuildCurrentTiles();
  224. }
  225. void MostVisitedSites::InitializeCustomLinks() {
  226. if (!custom_links_ || !current_tiles_.has_value() || !IsCustomLinksEnabled())
  227. return;
  228. if (custom_links_->Initialize(current_tiles_.value()))
  229. custom_links_action_count_ = 0;
  230. }
  231. void MostVisitedSites::UninitializeCustomLinks() {
  232. if (!custom_links_ || !IsCustomLinksEnabled())
  233. return;
  234. custom_links_action_count_ = -1;
  235. custom_links_->Uninitialize();
  236. BuildCurrentTiles();
  237. }
  238. bool MostVisitedSites::IsCustomLinksInitialized() {
  239. if (!custom_links_ || !IsCustomLinksEnabled())
  240. return false;
  241. return custom_links_->IsInitialized();
  242. }
  243. void MostVisitedSites::EnableCustomLinks(bool enable) {
  244. if (is_custom_links_enabled_ != enable) {
  245. is_custom_links_enabled_ = enable;
  246. BuildCurrentTiles();
  247. }
  248. }
  249. bool MostVisitedSites::IsCustomLinksEnabled() const {
  250. return is_custom_links_enabled_;
  251. }
  252. void MostVisitedSites::SetShortcutsVisible(bool visible) {
  253. if (is_shortcuts_visible_ != visible) {
  254. is_shortcuts_visible_ = visible;
  255. BuildCurrentTiles();
  256. }
  257. }
  258. bool MostVisitedSites::IsShortcutsVisible() const {
  259. return is_shortcuts_visible_;
  260. }
  261. bool MostVisitedSites::AddCustomLink(const GURL& url,
  262. const std::u16string& title) {
  263. if (!custom_links_ || !IsCustomLinksEnabled())
  264. return false;
  265. bool is_first_action = !custom_links_->IsInitialized();
  266. // Initialize custom links if they have not been initialized yet.
  267. InitializeCustomLinks();
  268. bool success = custom_links_->AddLink(url, title);
  269. if (success) {
  270. if (custom_links_action_count_ != -1)
  271. custom_links_action_count_++;
  272. BuildCurrentTiles();
  273. } else if (is_first_action) {
  274. // We don't want to keep custom links initialized if the first action after
  275. // initialization failed.
  276. UninitializeCustomLinks();
  277. }
  278. return success;
  279. }
  280. bool MostVisitedSites::UpdateCustomLink(const GURL& url,
  281. const GURL& new_url,
  282. const std::u16string& new_title) {
  283. if (!custom_links_ || !IsCustomLinksEnabled())
  284. return false;
  285. bool is_first_action = !custom_links_->IsInitialized();
  286. // Initialize custom links if they have not been initialized yet.
  287. InitializeCustomLinks();
  288. bool success = custom_links_->UpdateLink(url, new_url, new_title);
  289. if (success) {
  290. if (custom_links_action_count_ != -1)
  291. custom_links_action_count_++;
  292. BuildCurrentTiles();
  293. } else if (is_first_action) {
  294. // We don't want to keep custom links initialized if the first action after
  295. // initialization failed.
  296. UninitializeCustomLinks();
  297. }
  298. return success;
  299. }
  300. bool MostVisitedSites::ReorderCustomLink(const GURL& url, size_t new_pos) {
  301. if (!custom_links_ || !IsCustomLinksEnabled())
  302. return false;
  303. bool is_first_action = !custom_links_->IsInitialized();
  304. // Initialize custom links if they have not been initialized yet.
  305. InitializeCustomLinks();
  306. bool success = custom_links_->ReorderLink(url, new_pos);
  307. if (success) {
  308. if (custom_links_action_count_ != -1)
  309. custom_links_action_count_++;
  310. BuildCurrentTiles();
  311. } else if (is_first_action) {
  312. // We don't want to keep custom links initialized if the first action after
  313. // initialization failed.
  314. UninitializeCustomLinks();
  315. }
  316. return success;
  317. }
  318. bool MostVisitedSites::DeleteCustomLink(const GURL& url) {
  319. if (!custom_links_ || !IsCustomLinksEnabled())
  320. return false;
  321. bool is_first_action = !custom_links_->IsInitialized();
  322. // Initialize custom links if they have not been initialized yet.
  323. InitializeCustomLinks();
  324. bool success = custom_links_->DeleteLink(url);
  325. if (success) {
  326. if (custom_links_action_count_ != -1)
  327. custom_links_action_count_++;
  328. BuildCurrentTiles();
  329. } else if (is_first_action) {
  330. // We don't want to keep custom links initialized if the first action after
  331. // initialization failed.
  332. UninitializeCustomLinks();
  333. }
  334. return success;
  335. }
  336. void MostVisitedSites::UndoCustomLinkAction() {
  337. if (!custom_links_ || !IsCustomLinksEnabled())
  338. return;
  339. // If this is undoing the first action after initialization, uninitialize
  340. // custom links.
  341. if (custom_links_action_count_-- == 1)
  342. UninitializeCustomLinks();
  343. else if (custom_links_->UndoAction())
  344. BuildCurrentTiles();
  345. }
  346. size_t MostVisitedSites::GetCustomLinkNum() {
  347. return custom_links_->GetLinks().size();
  348. }
  349. void MostVisitedSites::AddOrRemoveBlockedUrl(const GURL& url, bool add_url) {
  350. if (add_url) {
  351. base::RecordAction(base::UserMetricsAction("Suggestions.Site.Removed"));
  352. } else {
  353. base::RecordAction(
  354. base::UserMetricsAction("Suggestions.Site.RemovalUndone"));
  355. }
  356. if (top_sites_) {
  357. if (add_url)
  358. top_sites_->AddBlockedUrl(url);
  359. else
  360. top_sites_->RemoveBlockedUrl(url);
  361. }
  362. }
  363. void MostVisitedSites::ClearBlockedUrls() {
  364. if (top_sites_)
  365. top_sites_->ClearBlockedUrls();
  366. }
  367. void MostVisitedSites::OnBlockedSitesChanged() {
  368. BuildCurrentTiles();
  369. }
  370. // static
  371. void MostVisitedSites::RegisterProfilePrefs(
  372. user_prefs::PrefRegistrySyncable* registry) {
  373. registry->RegisterIntegerPref(prefs::kNumPersonalTiles, 0);
  374. }
  375. // static
  376. void MostVisitedSites::ResetProfilePrefs(PrefService* prefs) {
  377. prefs->SetInteger(prefs::kNumPersonalTiles, 0);
  378. }
  379. size_t MostVisitedSites::GetMaxNumSites() const {
  380. return max_num_sites_ + (custom_links_ && IsCustomLinksEnabled() ? 1 : 0);
  381. }
  382. void MostVisitedSites::InitiateTopSitesQuery() {
  383. if (!top_sites_)
  384. return;
  385. if (top_sites_weak_ptr_factory_.HasWeakPtrs())
  386. return; // Ongoing query.
  387. top_sites_->GetMostVisitedURLs(
  388. base::BindOnce(&MostVisitedSites::OnMostVisitedURLsAvailable,
  389. top_sites_weak_ptr_factory_.GetWeakPtr()));
  390. }
  391. void MostVisitedSites::OnMostVisitedURLsAvailable(
  392. const history::MostVisitedURLList& visited_list) {
  393. // Ignore the event if tiles are provided by custom links, which take
  394. // precedence.
  395. if (IsCustomLinksInitialized()) {
  396. return;
  397. }
  398. NTPTilesVector tiles;
  399. size_t num_tiles = std::min(visited_list.size(), GetMaxNumSites());
  400. for (size_t i = 0; i < num_tiles; ++i) {
  401. const history::MostVisitedURL& visited = visited_list[i];
  402. if (visited.url.is_empty())
  403. break; // This is the signal that there are no more real visited sites.
  404. if (supervisor_ && supervisor_->IsBlocked(visited.url))
  405. continue;
  406. NTPTile tile;
  407. tile.title =
  408. custom_links_ ? GenerateShortTitle(visited.title) : visited.title;
  409. tile.url = visited.url;
  410. tile.source = TileSource::TOP_SITES;
  411. // MostVisitedURL.title is either the title or the URL which is treated
  412. // exactly as the title. Differentiating here is not worth the overhead.
  413. tile.title_source = TileTitleSource::TITLE_TAG;
  414. // TODO(crbug.com/773278): Populate |data_generation_time| here in order to
  415. // log UMA metrics of age.
  416. tiles.push_back(std::move(tile));
  417. }
  418. mv_source_ = TileSource::TOP_SITES;
  419. InitiateNotificationForNewTiles(std::move(tiles));
  420. }
  421. void MostVisitedSites::BuildCurrentTiles() {
  422. if (IsCustomLinksInitialized()) {
  423. BuildCustomLinks(custom_links_->GetLinks());
  424. return;
  425. }
  426. mv_source_ = TileSource::TOP_SITES;
  427. InitiateTopSitesQuery();
  428. }
  429. std::map<SectionType, NTPTilesVector>
  430. MostVisitedSites::CreatePopularSitesSections(
  431. const std::set<std::string>& used_hosts,
  432. size_t num_actual_tiles) {
  433. std::map<SectionType, NTPTilesVector> sections = {
  434. std::make_pair(SectionType::PERSONALIZED, NTPTilesVector())};
  435. // For child accounts popular sites tiles will not be added.
  436. if (supervisor_ && supervisor_->IsChildProfile()) {
  437. return sections;
  438. }
  439. if (!popular_sites_ || !ShouldShowPopularSites()) {
  440. return sections;
  441. }
  442. const std::set<std::string> no_hosts;
  443. for (const auto& section_type_and_sites : popular_sites()->sections()) {
  444. SectionType type = section_type_and_sites.first;
  445. const PopularSites::SitesVector& sites = section_type_and_sites.second;
  446. if (type == SectionType::PERSONALIZED) {
  447. size_t num_required_tiles = GetMaxNumSites() - num_actual_tiles;
  448. sections[type] =
  449. CreatePopularSitesTiles(/*popular_sites=*/sites,
  450. /*hosts_to_skip=*/used_hosts,
  451. /*num_max_tiles=*/num_required_tiles);
  452. } else {
  453. sections[type] =
  454. CreatePopularSitesTiles(/*popular_sites=*/sites,
  455. /*hosts_to_skip=*/no_hosts,
  456. /*num_max_tiles=*/GetMaxNumSites());
  457. }
  458. }
  459. return sections;
  460. }
  461. NTPTilesVector MostVisitedSites::CreatePopularSitesTiles(
  462. const PopularSites::SitesVector& sites_vector,
  463. const std::set<std::string>& hosts_to_skip,
  464. size_t num_max_tiles) {
  465. // Collect non-blocked popular suggestions, skipping those already present
  466. // in the personal suggestions.
  467. NTPTilesVector popular_sites_tiles;
  468. for (const PopularSites::Site& popular_site : sites_vector) {
  469. if (popular_sites_tiles.size() >= num_max_tiles) {
  470. break;
  471. }
  472. // Skip blocked sites.
  473. if (top_sites_ && top_sites_->IsBlocked(popular_site.url))
  474. continue;
  475. const std::string& host = popular_site.url.host();
  476. if (IsHostOrMobilePageKnown(hosts_to_skip, host)) {
  477. continue;
  478. }
  479. NTPTile tile;
  480. tile.title = popular_site.title;
  481. tile.url = GURL(popular_site.url);
  482. tile.title_source = popular_site.title_source;
  483. tile.source = popular_site.baked_in ? TileSource::POPULAR_BAKED_IN
  484. : TileSource::POPULAR;
  485. popular_sites_tiles.push_back(std::move(tile));
  486. icon_cacher_->StartFetchPopularSites(
  487. popular_site,
  488. base::BindOnce(&MostVisitedSites::OnIconMadeAvailable,
  489. base::Unretained(this), popular_site.url),
  490. base::BindOnce(&MostVisitedSites::OnIconMadeAvailable,
  491. base::Unretained(this), popular_site.url));
  492. }
  493. return popular_sites_tiles;
  494. }
  495. void MostVisitedSites::OnHomepageTitleDetermined(
  496. NTPTilesVector tiles,
  497. const absl::optional<std::u16string>& title) {
  498. if (!title.has_value())
  499. return; // If there is no title, the most recent tile was already sent out.
  500. MergeMostVisitedTiles(InsertHomeTile(std::move(tiles), title.value()));
  501. }
  502. NTPTilesVector MostVisitedSites::InsertHomeTile(
  503. NTPTilesVector tiles,
  504. const std::u16string& title) const {
  505. DCHECK(homepage_client_);
  506. DCHECK_GT(GetMaxNumSites(), 0u);
  507. const GURL& homepage_url = homepage_client_->GetHomepageUrl();
  508. NTPTilesVector new_tiles;
  509. bool homepage_tile_added = false;
  510. for (auto& tile : tiles) {
  511. if (new_tiles.size() >= GetMaxNumSites()) {
  512. break;
  513. }
  514. // If there's a tile has the same host name with homepage, insert the tile
  515. // to the first position of the list. This is also a deduplication.
  516. if (tile.url.host() == homepage_url.host() && !homepage_tile_added) {
  517. tile.source = TileSource::HOMEPAGE;
  518. homepage_tile_added = true;
  519. new_tiles.insert(new_tiles.begin(), std::move(tile));
  520. continue;
  521. }
  522. new_tiles.push_back(std::move(tile));
  523. }
  524. if (!homepage_tile_added) {
  525. // Make room for the homepage tile.
  526. if (new_tiles.size() >= GetMaxNumSites()) {
  527. new_tiles.pop_back();
  528. }
  529. NTPTile homepage_tile;
  530. homepage_tile.url = homepage_url;
  531. homepage_tile.title = title;
  532. homepage_tile.source = TileSource::HOMEPAGE;
  533. homepage_tile.title_source = TileTitleSource::TITLE_TAG;
  534. // Always insert |homepage_tile| to the front of |new_tiles| to ensure it's
  535. // the first tile.
  536. new_tiles.insert(new_tiles.begin(), std::move(homepage_tile));
  537. }
  538. return new_tiles;
  539. }
  540. absl::optional<NTPTile> MostVisitedSites::CreateExploreSitesTile() {
  541. if (!explore_sites_client_)
  542. return absl::nullopt;
  543. NTPTile explore_sites_tile;
  544. explore_sites_tile.url = explore_sites_client_->GetExploreSitesUrl();
  545. explore_sites_tile.title = explore_sites_client_->GetExploreSitesTitle();
  546. explore_sites_tile.source = TileSource::EXPLORE;
  547. explore_sites_tile.title_source = TileTitleSource::UNKNOWN;
  548. return explore_sites_tile;
  549. }
  550. void MostVisitedSites::OnCustomLinksChanged() {
  551. DCHECK(custom_links_);
  552. if (!IsCustomLinksEnabled())
  553. return;
  554. if (custom_links_->IsInitialized()) {
  555. BuildCustomLinks(custom_links_->GetLinks());
  556. } else {
  557. // Since custom links have been uninitialized (e.g. through Chrome sync), we
  558. // should show the regular Most Visited tiles.
  559. BuildCurrentTiles();
  560. }
  561. }
  562. void MostVisitedSites::BuildCustomLinks(
  563. const std::vector<CustomLinksManager::Link>& links) {
  564. DCHECK(custom_links_);
  565. NTPTilesVector tiles;
  566. // The maximum number of custom links that can be shown is independent of the
  567. // maximum number of Most Visited sites that can be shown.
  568. size_t num_tiles = std::min(links.size(), kMaxNumCustomLinks);
  569. for (size_t i = 0; i < num_tiles; ++i) {
  570. const CustomLinksManager::Link& link = links.at(i);
  571. if (supervisor_ && supervisor_->IsBlocked(link.url))
  572. continue;
  573. NTPTile tile;
  574. tile.title = link.title;
  575. tile.url = link.url;
  576. tile.source = TileSource::CUSTOM_LINKS;
  577. tile.from_most_visited = link.is_most_visited;
  578. tiles.push_back(std::move(tile));
  579. }
  580. mv_source_ = TileSource::CUSTOM_LINKS;
  581. SaveTilesAndNotify(std::move(tiles), std::map<SectionType, NTPTilesVector>());
  582. }
  583. void MostVisitedSites::InitiateNotificationForNewTiles(
  584. NTPTilesVector new_tiles) {
  585. if (ShouldAddHomeTile() && !HasHomeTile(new_tiles)) {
  586. homepage_client_->QueryHomepageTitle(
  587. base::BindOnce(&MostVisitedSites::OnHomepageTitleDetermined,
  588. base::Unretained(this), new_tiles));
  589. GURL homepage_url = homepage_client_->GetHomepageUrl();
  590. icon_cacher_->StartFetchMostLikely(
  591. homepage_url,
  592. base::BindRepeating(&MostVisitedSites::OnIconMadeAvailable,
  593. base::Unretained(this), homepage_url));
  594. // Don't wait for the homepage title from history but immediately serve a
  595. // copy of new tiles.
  596. new_tiles = InsertHomeTile(std::move(new_tiles), std::u16string());
  597. }
  598. MergeMostVisitedTiles(std::move(new_tiles));
  599. }
  600. void MostVisitedSites::MergeMostVisitedTiles(NTPTilesVector personal_tiles) {
  601. std::set<std::string> used_hosts;
  602. absl::optional<NTPTile> explore_tile = CreateExploreSitesTile();
  603. size_t num_actual_tiles = explore_tile ? 1 : 0;
  604. // The explore sites tile may have taken a space that was utilized by the
  605. // personal tiles.
  606. if (!personal_tiles.empty() &&
  607. personal_tiles.size() + num_actual_tiles > GetMaxNumSites()) {
  608. personal_tiles.pop_back();
  609. }
  610. AddToHostsAndTotalCount(personal_tiles, &used_hosts, &num_actual_tiles);
  611. std::map<SectionType, NTPTilesVector> sections =
  612. CreatePopularSitesSections(used_hosts, num_actual_tiles);
  613. AddToHostsAndTotalCount(sections[SectionType::PERSONALIZED], &used_hosts,
  614. &num_actual_tiles);
  615. NTPTilesVector new_tiles =
  616. MergeTiles(std::move(personal_tiles),
  617. std::move(sections[SectionType::PERSONALIZED]), explore_tile);
  618. SaveTilesAndNotify(std::move(new_tiles), std::move(sections));
  619. }
  620. void MostVisitedSites::SaveTilesAndNotify(
  621. NTPTilesVector new_tiles,
  622. std::map<SectionType, NTPTilesVector> sections) {
  623. // TODO(https://crbug.com/1266574):
  624. // Remove this after preinstalled apps are migrated.
  625. NTPTilesVector fixed_tiles = is_default_chrome_app_migrated_
  626. ? RemoveInvalidPreinstallApps(new_tiles)
  627. : new_tiles;
  628. if (fixed_tiles.size() != new_tiles.size()) {
  629. metrics::RecordsMigratedDefaultAppDeleted(
  630. DeletedTileType::kMostVisitedSite);
  631. }
  632. if (!current_tiles_.has_value() || (*current_tiles_ != fixed_tiles)) {
  633. current_tiles_.emplace(std::move(fixed_tiles));
  634. int num_personal_tiles = 0;
  635. for (const auto& tile : *current_tiles_) {
  636. if (tile.source != TileSource::POPULAR &&
  637. tile.source != TileSource::POPULAR_BAKED_IN) {
  638. num_personal_tiles++;
  639. }
  640. }
  641. prefs_->SetInteger(prefs::kNumPersonalTiles, num_personal_tiles);
  642. }
  643. if (observers_.empty())
  644. return;
  645. sections[SectionType::PERSONALIZED] = *current_tiles_;
  646. for (auto& observer : observers_)
  647. observer.OnURLsAvailable(sections);
  648. }
  649. // static
  650. bool MostVisitedSites::IsNtpTileFromPreinstalledApp(GURL url) {
  651. #if BUILDFLAG(ENABLE_EXTENSIONS)
  652. return url.is_valid() && url.SchemeIs(extensions::kExtensionScheme) &&
  653. extension_misc::IsPreinstalledAppId(url.host());
  654. #else
  655. return false;
  656. #endif
  657. }
  658. // static
  659. bool MostVisitedSites::WasNtpAppMigratedToWebApp(PrefService* prefs, GURL url) {
  660. const base::Value::List& migrated_apps =
  661. prefs->GetValueList(webapps::kWebAppsMigratedPreinstalledApps);
  662. for (const auto& val : migrated_apps) {
  663. if (val.is_string() && val.GetString() == url.host())
  664. return true;
  665. }
  666. return false;
  667. }
  668. NTPTilesVector MostVisitedSites::RemoveInvalidPreinstallApps(
  669. NTPTilesVector new_tiles) {
  670. new_tiles.erase(
  671. std::remove_if(new_tiles.begin(), new_tiles.end(),
  672. [this](NTPTile ntp_tile) {
  673. return MostVisitedSites::IsNtpTileFromPreinstalledApp(
  674. ntp_tile.url) &&
  675. MostVisitedSites::WasNtpAppMigratedToWebApp(
  676. prefs_, ntp_tile.url);
  677. }),
  678. new_tiles.end());
  679. return new_tiles;
  680. }
  681. NTPTilesVector MostVisitedSites::MergeTiles(
  682. NTPTilesVector personal_tiles,
  683. NTPTilesVector popular_tiles,
  684. absl::optional<NTPTile> explore_tile) {
  685. NTPTilesVector merged_tiles;
  686. std::move(personal_tiles.begin(), personal_tiles.end(),
  687. std::back_inserter(merged_tiles));
  688. std::move(popular_tiles.begin(), popular_tiles.end(),
  689. std::back_inserter(merged_tiles));
  690. if (explore_tile)
  691. merged_tiles.push_back(*explore_tile);
  692. return merged_tiles;
  693. }
  694. void MostVisitedSites::OnPopularSitesDownloaded(bool success) {
  695. if (!success) {
  696. LOG(WARNING) << "Download of popular sites failed";
  697. return;
  698. }
  699. for (const auto& section : popular_sites_->sections()) {
  700. for (const PopularSites::Site& site : section.second) {
  701. // Ignore callback; these icons will be seen on the *next* NTP.
  702. icon_cacher_->StartFetchPopularSites(site, base::NullCallback(),
  703. base::NullCallback());
  704. }
  705. }
  706. }
  707. void MostVisitedSites::OnIconMadeAvailable(const GURL& site_url) {
  708. for (auto& observer : observers_)
  709. observer.OnIconMadeAvailable(site_url);
  710. }
  711. void MostVisitedSites::TopSitesLoaded(TopSites* top_sites) {}
  712. void MostVisitedSites::TopSitesChanged(TopSites* top_sites,
  713. ChangeReason change_reason) {
  714. if (mv_source_ == TileSource::TOP_SITES) {
  715. // The displayed tiles are invalidated.
  716. InitiateTopSitesQuery();
  717. }
  718. }
  719. bool MostVisitedSites::ShouldAddHomeTile() const {
  720. return GetMaxNumSites() > 0u &&
  721. homepage_client_ && // No platform-specific implementation - no tile.
  722. homepage_client_->IsHomepageTileEnabled() &&
  723. !homepage_client_->GetHomepageUrl().is_empty() &&
  724. !(top_sites_ &&
  725. top_sites_->IsBlocked(homepage_client_->GetHomepageUrl()));
  726. }
  727. void MostVisitedSites::AddToHostsAndTotalCount(const NTPTilesVector& new_tiles,
  728. std::set<std::string>* hosts,
  729. size_t* total_tile_count) const {
  730. for (const auto& tile : new_tiles) {
  731. hosts->insert(tile.url.host());
  732. }
  733. *total_tile_count += new_tiles.size();
  734. DCHECK_LE(*total_tile_count, GetMaxNumSites());
  735. }
  736. } // namespace ntp_tiles