blob: c897a96cf9580815969ed9f8b9e5b7ace20c42b9 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/search/instant_service.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/favicon/fallback_icon_service_factory.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/favicon/large_icon_service_factory.h"
#include "chrome/browser/history/top_sites_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_io_context.h"
#include "chrome/browser/search/instant_service_observer.h"
#include "chrome/browser/search/most_visited_iframe_source.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search/suggestions/image_decoder_impl.h"
#include "chrome/browser/search/suggestions/suggestions_service_factory.h"
#include "chrome/browser/search/thumbnail_source.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/browser/thumbnails/thumbnail_list_source.h"
#include "chrome/browser/ui/search/instant_search_prerenderer.h"
#include "chrome/browser/ui/webui/fallback_icon_source.h"
#include "chrome/browser/ui/webui/favicon_source.h"
#include "chrome/browser/ui/webui/large_icon_source.h"
#include "chrome/browser/ui/webui/theme_source.h"
#include "chrome/common/render_messages.h"
#include "chrome/grit/theme_resources.h"
#include "components/favicon/core/fallback_icon_service.h"
#include "components/favicon/core/large_icon_service.h"
#include "components/history/core/browser/top_sites.h"
#include "components/image_fetcher/image_fetcher_impl.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/ntp_tiles/icon_cacher.h"
#include "components/search/search.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/url_data_source.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image_skia.h"
#if !defined(OS_ANDROID)
#include "chrome/browser/search/local_ntp_source.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#endif
namespace {
const base::Feature kNtpTilesFeature{"NTPTilesInInstantService",
base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace
InstantService::InstantService(Profile* profile)
: profile_(profile),
template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
weak_ptr_factory_(this) {
// The initialization below depends on a typical set of browser threads. Skip
// it if we are running in a unit test without the full suite.
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI))
return;
// This depends on the existence of the typical browser threads. Therefore it
// is only instantiated here (after the check for a UI thread above).
instant_io_context_ = new InstantIOContext();
previous_google_base_url_ =
GURL(UIThreadSearchTermsData(profile).GoogleBaseURLValue());
// TemplateURLService is NULL by default in tests.
if (template_url_service_) {
template_url_service_->AddObserver(this);
const TemplateURL* default_search_provider =
template_url_service_->GetDefaultSearchProvider();
if (default_search_provider) {
previous_default_search_provider_.reset(
new TemplateURLData(default_search_provider->data()));
}
}
ResetInstantSearchPrerendererIfNecessary();
registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllSources());
registrar_.Add(this,
content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllSources());
if (base::FeatureList::IsEnabled(kNtpTilesFeature)) {
most_visited_sites_ = base::MakeUnique<ntp_tiles::MostVisitedSites>(
profile_->GetPrefs(), TopSitesFactory::GetForProfile(profile_),
suggestions::SuggestionsServiceFactory::GetForProfile(profile_),
/*popular_sites=*/nullptr,
base::MakeUnique<ntp_tiles::IconCacher>(
FaviconServiceFactory::GetForProfile(
profile, ServiceAccessType::IMPLICIT_ACCESS),
base::MakeUnique<image_fetcher::ImageFetcherImpl>(
base::MakeUnique<suggestions::ImageDecoderImpl>(),
profile->GetRequestContext())),
/*supervisor=*/nullptr);
// TODO(treib): Add supervisor.
// TODO(sfiera): Share this with Android in a factory.
most_visited_sites_->SetMostVisitedURLsObserver(this, 8);
} else {
top_sites_ = TopSitesFactory::GetForProfile(profile_);
if (top_sites_) {
top_sites_->AddObserver(this);
// Immediately query the TopSites state.
TopSitesChanged(top_sites_.get(),
history::TopSitesObserver::ChangeReason::MOST_VISITED);
}
}
if (profile_ && profile_->GetResourceContext()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&InstantIOContext::SetUserDataOnIO,
profile->GetResourceContext(), instant_io_context_));
}
// Set up the data sources that Instant uses on the NTP.
// TODO(aurimas) remove this #if once instant_service.cc is no longer compiled
// on Android.
#if !defined(OS_ANDROID)
// Listen for theme installation.
registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
content::Source<ThemeService>(
ThemeServiceFactory::GetForProfile(profile_)));
content::URLDataSource::Add(profile_, new ThemeSource(profile_));
content::URLDataSource::Add(profile_, new LocalNtpSource(profile_));
content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false));
content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true));
content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
#endif // !defined(OS_ANDROID)
favicon::FallbackIconService* fallback_icon_service =
FallbackIconServiceFactory::GetForBrowserContext(profile_);
favicon::LargeIconService* large_icon_service =
LargeIconServiceFactory::GetForBrowserContext(profile_);
content::URLDataSource::Add(
profile_, new FallbackIconSource(fallback_icon_service));
content::URLDataSource::Add(
profile_, new FaviconSource(profile_, FaviconSource::FAVICON));
content::URLDataSource::Add(
profile_, new LargeIconSource(fallback_icon_service, large_icon_service));
content::URLDataSource::Add(profile_, new MostVisitedIframeSource());
}
InstantService::~InstantService() {
if (template_url_service_)
template_url_service_->RemoveObserver(this);
}
void InstantService::AddInstantProcess(int process_id) {
process_ids_.insert(process_id);
if (instant_io_context_.get()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&InstantIOContext::AddInstantProcessOnIO,
instant_io_context_, process_id));
}
}
bool InstantService::IsInstantProcess(int process_id) const {
return process_ids_.find(process_id) != process_ids_.end();
}
void InstantService::AddObserver(InstantServiceObserver* observer) {
observers_.AddObserver(observer);
}
void InstantService::RemoveObserver(InstantServiceObserver* observer) {
observers_.RemoveObserver(observer);
}
void InstantService::OnNewTabPageOpened() {
if (most_visited_sites_) {
most_visited_sites_->Refresh();
} else if (top_sites_) {
top_sites_->SyncWithHistory();
}
}
void InstantService::DeleteMostVisitedItem(const GURL& url) {
if (most_visited_sites_) {
most_visited_sites_->AddOrRemoveBlacklistedUrl(url, true);
} else if (top_sites_) {
top_sites_->AddBlacklistedURL(url);
}
}
void InstantService::UndoMostVisitedDeletion(const GURL& url) {
if (most_visited_sites_) {
most_visited_sites_->AddOrRemoveBlacklistedUrl(url, false);
} else if (top_sites_) {
top_sites_->RemoveBlacklistedURL(url);
}
}
void InstantService::UndoAllMostVisitedDeletions() {
if (most_visited_sites_) {
most_visited_sites_->ClearBlacklistedUrls();
} else if (top_sites_) {
top_sites_->ClearBlacklistedURLs();
}
}
void InstantService::UpdateThemeInfo() {
#if !defined(OS_ANDROID)
// Update theme background info.
// Initialize |theme_info| if necessary.
if (!theme_info_) {
OnThemeChanged();
} else {
for (InstantServiceObserver& observer : observers_)
observer.ThemeInfoChanged(*theme_info_);
}
#endif // !defined(OS_ANDROID)
}
void InstantService::UpdateMostVisitedItemsInfo() {
NotifyAboutMostVisitedItems();
}
void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
rph->Send(new ChromeViewMsg_SetSearchURLs(
search::GetSearchURLs(profile_), search::GetNewTabPageURL(profile_)));
}
InstantSearchPrerenderer* InstantService::GetInstantSearchPrerenderer() {
// The Instant prefetch base URL may have changed (see e.g. crbug.com/660923).
// If so, recreate the prerenderer.
ResetInstantSearchPrerendererIfNecessary();
return instant_prerenderer_.get();
}
void InstantService::Shutdown() {
process_ids_.clear();
if (instant_io_context_.get()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&InstantIOContext::ClearInstantProcessesOnIO,
instant_io_context_));
}
if (most_visited_sites_) {
most_visited_sites_.reset();
} else if (top_sites_) {
top_sites_->RemoveObserver(this);
top_sites_ = nullptr;
}
instant_io_context_ = NULL;
}
void InstantService::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
SendSearchURLsToRenderer(
content::Source<content::RenderProcessHost>(source).ptr());
break;
case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
OnRendererProcessTerminated(
content::Source<content::RenderProcessHost>(source)->GetID());
break;
#if !defined(OS_ANDROID)
case chrome::NOTIFICATION_BROWSER_THEME_CHANGED:
OnThemeChanged();
break;
#endif // !defined(OS_ANDROID)
default:
NOTREACHED() << "Unexpected notification type in InstantService.";
}
}
void InstantService::OnRendererProcessTerminated(int process_id) {
process_ids_.erase(process_id);
if (instant_io_context_.get()) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&InstantIOContext::RemoveInstantProcessOnIO,
instant_io_context_, process_id));
}
}
void InstantService::OnTopSitesReceived(
const history::MostVisitedURLList& data) {
most_visited_items_.clear();
for (const history::MostVisitedURL& mv_url : data) {
InstantMostVisitedItem item;
item.url = mv_url.url;
item.title = mv_url.title;
item.source = ntp_tiles::NTPTileSource::TOP_SITES;
most_visited_items_.push_back(item);
}
NotifyAboutMostVisitedItems();
}
void InstantService::OnMostVisitedURLsAvailable(
const ntp_tiles::NTPTilesVector& tiles) {
DCHECK(most_visited_sites_);
most_visited_items_.clear();
for (const ntp_tiles::NTPTile& tile : tiles) {
InstantMostVisitedItem item;
item.url = tile.url;
item.title = tile.title;
item.thumbnail = tile.thumbnail_url;
item.favicon = tile.favicon_url;
item.source = tile.source;
most_visited_items_.push_back(item);
}
NotifyAboutMostVisitedItems();
}
void InstantService::OnIconMadeAvailable(const GURL& site_url) {}
void InstantService::NotifyAboutMostVisitedItems() {
for (InstantServiceObserver& observer : observers_)
observer.MostVisitedItemsChanged(most_visited_items_);
}
#if !defined(OS_ANDROID)
namespace {
const int kSectionBorderAlphaTransparency = 80;
// Converts SkColor to RGBAColor
RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
RGBAColor color;
color.r = SkColorGetR(sKColor);
color.g = SkColorGetG(sKColor);
color.b = SkColorGetB(sKColor);
color.a = SkColorGetA(sKColor);
return color;
}
} // namespace
void InstantService::OnThemeChanged() {
// Get theme information from theme service.
theme_info_.reset(new ThemeBackgroundInfo());
// Get if the current theme is the default theme.
ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile_);
theme_info_->using_default_theme = theme_service->UsingDefaultTheme();
// Get theme colors.
const ui::ThemeProvider& theme_provider =
ThemeService::GetThemeProviderForProfile(profile_);
SkColor background_color =
theme_provider.GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
SkColor text_color = theme_provider.GetColor(ThemeProperties::COLOR_NTP_TEXT);
SkColor link_color = theme_provider.GetColor(ThemeProperties::COLOR_NTP_LINK);
SkColor text_color_light =
theme_provider.GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
SkColor header_color =
theme_provider.GetColor(ThemeProperties::COLOR_NTP_HEADER);
// Generate section border color from the header color.
SkColor section_border_color =
SkColorSetARGB(kSectionBorderAlphaTransparency,
SkColorGetR(header_color),
SkColorGetG(header_color),
SkColorGetB(header_color));
// Invert colors if needed.
if (color_utils::IsInvertedColorScheme()) {
background_color = color_utils::InvertColor(background_color);
text_color = color_utils::InvertColor(text_color);
link_color = color_utils::InvertColor(link_color);
text_color_light = color_utils::InvertColor(text_color_light);
header_color = color_utils::InvertColor(header_color);
section_border_color = color_utils::InvertColor(section_border_color);
}
// Set colors.
theme_info_->background_color = SkColorToRGBAColor(background_color);
theme_info_->text_color = SkColorToRGBAColor(text_color);
theme_info_->link_color = SkColorToRGBAColor(link_color);
theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
theme_info_->header_color = SkColorToRGBAColor(header_color);
theme_info_->section_border_color = SkColorToRGBAColor(section_border_color);
int logo_alternate =
theme_provider.GetDisplayProperty(ThemeProperties::NTP_LOGO_ALTERNATE);
theme_info_->logo_alternate = logo_alternate == 1;
if (theme_provider.HasCustomImage(IDR_THEME_NTP_BACKGROUND)) {
// Set theme id for theme background image url.
theme_info_->theme_id = theme_service->GetThemeID();
// Set theme background image horizontal alignment.
int alignment = theme_provider.GetDisplayProperty(
ThemeProperties::NTP_BACKGROUND_ALIGNMENT);
if (alignment & ThemeProperties::ALIGN_LEFT)
theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT;
else if (alignment & ThemeProperties::ALIGN_RIGHT)
theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT;
else
theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
// Set theme background image vertical alignment.
if (alignment & ThemeProperties::ALIGN_TOP)
theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP;
else if (alignment & ThemeProperties::ALIGN_BOTTOM)
theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM;
else
theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
// Set theme background image tiling.
int tiling = theme_provider.GetDisplayProperty(
ThemeProperties::NTP_BACKGROUND_TILING);
switch (tiling) {
case ThemeProperties::NO_REPEAT:
theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
break;
case ThemeProperties::REPEAT_X:
theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X;
break;
case ThemeProperties::REPEAT_Y:
theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y;
break;
case ThemeProperties::REPEAT:
theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT;
break;
}
// Set theme background image height.
gfx::ImageSkia* image =
theme_provider.GetImageSkiaNamed(IDR_THEME_NTP_BACKGROUND);
DCHECK(image);
theme_info_->image_height = image->height();
theme_info_->has_attribution =
theme_provider.HasCustomImage(IDR_THEME_NTP_ATTRIBUTION);
}
for (InstantServiceObserver& observer : observers_)
observer.ThemeInfoChanged(*theme_info_);
}
#endif // !defined(OS_ANDROID)
void InstantService::OnTemplateURLServiceChanged() {
// Check whether the default search provider was changed.
const TemplateURL* template_url =
template_url_service_->GetDefaultSearchProvider();
bool default_search_provider_changed = !TemplateURL::MatchesData(
template_url, previous_default_search_provider_.get(),
UIThreadSearchTermsData(profile_));
if (default_search_provider_changed) {
previous_default_search_provider_.reset(
template_url ? new TemplateURLData(template_url->data()) : NULL);
}
// Note that, even if the TemplateURL for the Default Search Provider has not
// changed, the effective URLs might change if they reference the Google base
// URL. The TemplateURLService will notify us when the effective URL changes
// in this way but it's up to us to do the work to check both.
bool google_base_url_domain_changed = false;
GURL google_base_url(UIThreadSearchTermsData(profile_).GoogleBaseURLValue());
if (google_base_url != previous_google_base_url_) {
previous_google_base_url_ = google_base_url;
if (template_url &&
template_url->HasGoogleBaseURLs(UIThreadSearchTermsData(profile_))) {
google_base_url_domain_changed = true;
}
}
if (default_search_provider_changed || google_base_url_domain_changed) {
ResetInstantSearchPrerendererIfNecessary();
for (InstantServiceObserver& observer : observers_)
observer.DefaultSearchProviderChanged(google_base_url_domain_changed);
}
}
void InstantService::TopSitesLoaded(history::TopSites* top_sites) {
DCHECK(!most_visited_sites_);
DCHECK_EQ(top_sites_.get(), top_sites);
}
void InstantService::TopSitesChanged(history::TopSites* top_sites,
ChangeReason change_reason) {
DCHECK(!most_visited_sites_);
DCHECK_EQ(top_sites_.get(), top_sites);
// As forced urls already come from tiles, we can safely ignore those updates.
if (change_reason == history::TopSitesObserver::ChangeReason::FORCED_URL)
return;
top_sites_->GetMostVisitedURLs(base::Bind(&InstantService::OnTopSitesReceived,
weak_ptr_factory_.GetWeakPtr()),
false);
}
void InstantService::ResetInstantSearchPrerendererIfNecessary() {
if (!search::ShouldPrefetchSearchResults())
return;
GURL url(search::GetSearchResultPrefetchBaseURL(profile_));
if (!instant_prerenderer_ || instant_prerenderer_->prerender_url() != url) {
instant_prerenderer_.reset(
url.is_valid() ? new InstantSearchPrerenderer(profile_, url) : nullptr);
}
}