- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / search / instant_service.cc
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
5 #include "chrome/browser/search/instant_service.h"
6
7 #include <vector>
8
9 #include "base/logging.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/history/history_notifications.h"
14 #include "chrome/browser/history/most_visited_tiles_experiment.h"
15 #include "chrome/browser/history/top_sites.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/search/instant_io_context.h"
18 #include "chrome/browser/search/instant_service_factory.h"
19 #include "chrome/browser/search/instant_service_observer.h"
20 #include "chrome/browser/search/local_ntp_source.h"
21 #include "chrome/browser/search/most_visited_iframe_source.h"
22 #include "chrome/browser/search/search.h"
23 #include "chrome/browser/search_engines/template_url.h"
24 #include "chrome/browser/search_engines/template_url_service.h"
25 #include "chrome/browser/search_engines/template_url_service_factory.h"
26 #include "chrome/browser/themes/theme_properties.h"
27 #include "chrome/browser/themes/theme_service.h"
28 #include "chrome/browser/themes/theme_service_factory.h"
29 #include "chrome/browser/ui/webui/favicon_source.h"
30 #include "chrome/browser/ui/webui/ntp/thumbnail_list_source.h"
31 #include "chrome/browser/ui/webui/ntp/thumbnail_source.h"
32 #include "chrome/browser/ui/webui/theme_source.h"
33 #include "chrome/common/pref_names.h"
34 #include "chrome/common/render_messages.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_details.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/notification_types.h"
40 #include "content/public/browser/render_process_host.h"
41 #include "content/public/browser/url_data_source.h"
42 #include "grit/theme_resources.h"
43 #include "net/base/net_util.h"
44 #include "net/url_request/url_request.h"
45 #include "ui/gfx/color_utils.h"
46 #include "ui/gfx/image/image_skia.h"
47 #include "ui/gfx/sys_color_change_listener.h"
48 #include "url/gurl.h"
49
50 using content::BrowserThread;
51
52 namespace {
53
54 const int kSectionBorderAlphaTransparency = 80;
55
56 // Converts SkColor to RGBAColor
57 RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
58   RGBAColor color;
59   color.r = SkColorGetR(sKColor);
60   color.g = SkColorGetG(sKColor);
61   color.b = SkColorGetB(sKColor);
62   color.a = SkColorGetA(sKColor);
63   return color;
64 }
65
66 }  // namespace
67
68 InstantService::InstantService(Profile* profile)
69     : profile_(profile),
70       ntp_prerenderer_(profile, this, profile->GetPrefs()),
71       browser_instant_controller_object_count_(0),
72       weak_ptr_factory_(this) {
73   // Stub for unit tests.
74   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
75     return;
76
77   registrar_.Add(this,
78                  content::NOTIFICATION_RENDERER_PROCESS_CREATED,
79                  content::NotificationService::AllSources());
80   registrar_.Add(this,
81                  content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
82                  content::NotificationService::AllSources());
83
84   history::TopSites* top_sites = profile_->GetTopSites();
85   if (top_sites) {
86     registrar_.Add(this,
87                    chrome::NOTIFICATION_TOP_SITES_CHANGED,
88                    content::Source<history::TopSites>(top_sites));
89   }
90   instant_io_context_ = new InstantIOContext();
91
92   if (profile_ && profile_->GetResourceContext()) {
93     BrowserThread::PostTask(
94         BrowserThread::IO, FROM_HERE,
95         base::Bind(&InstantIOContext::SetUserDataOnIO,
96                    profile->GetResourceContext(), instant_io_context_));
97   }
98
99   // Set up the data sources that Instant uses on the NTP.
100 #if defined(ENABLE_THEMES)
101   // Listen for theme installation.
102   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
103                  content::Source<ThemeService>(
104                      ThemeServiceFactory::GetForProfile(profile_)));
105
106   content::URLDataSource::Add(profile_, new ThemeSource(profile_));
107 #endif  // defined(ENABLE_THEMES)
108
109   content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false));
110   content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true));
111   content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
112   content::URLDataSource::Add(
113       profile_, new FaviconSource(profile_, FaviconSource::FAVICON));
114   content::URLDataSource::Add(profile_, new LocalNtpSource(profile_));
115   content::URLDataSource::Add(profile_, new MostVisitedIframeSource());
116
117   profile_pref_registrar_.Init(profile_->GetPrefs());
118   profile_pref_registrar_.Add(
119       prefs::kDefaultSearchProviderID,
120       base::Bind(&InstantService::OnDefaultSearchProviderChanged,
121                  base::Unretained(this)));
122
123   registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_URL_UPDATED,
124                  content::Source<Profile>(profile_->GetOriginalProfile()));
125   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
126                  content::Source<Profile>(profile_));
127 }
128
129 InstantService::~InstantService() {
130 }
131
132 void InstantService::AddInstantProcess(int process_id) {
133   process_ids_.insert(process_id);
134
135   if (instant_io_context_.get()) {
136     BrowserThread::PostTask(BrowserThread::IO,
137                             FROM_HERE,
138                             base::Bind(&InstantIOContext::AddInstantProcessOnIO,
139                                        instant_io_context_,
140                                        process_id));
141   }
142 }
143
144 bool InstantService::IsInstantProcess(int process_id) const {
145   return process_ids_.find(process_id) != process_ids_.end();
146 }
147
148 void InstantService::AddObserver(InstantServiceObserver* observer) {
149   observers_.AddObserver(observer);
150 }
151
152 void InstantService::RemoveObserver(InstantServiceObserver* observer) {
153   observers_.RemoveObserver(observer);
154 }
155
156 void InstantService::DeleteMostVisitedItem(const GURL& url) {
157   history::TopSites* top_sites = profile_->GetTopSites();
158   if (!top_sites)
159     return;
160
161   top_sites->AddBlacklistedURL(url);
162 }
163
164 void InstantService::UndoMostVisitedDeletion(const GURL& url) {
165   history::TopSites* top_sites = profile_->GetTopSites();
166   if (!top_sites)
167     return;
168
169   top_sites->RemoveBlacklistedURL(url);
170 }
171
172 void InstantService::UndoAllMostVisitedDeletions() {
173   history::TopSites* top_sites = profile_->GetTopSites();
174   if (!top_sites)
175     return;
176
177   top_sites->ClearBlacklistedURLs();
178 }
179
180 void InstantService::UpdateThemeInfo() {
181   // Update theme background info.
182   // Initialize |theme_info| if necessary.
183   if (!theme_info_)
184     OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_));
185   else
186     OnThemeChanged(NULL);
187 }
188
189 void InstantService::UpdateMostVisitedItemsInfo() {
190   NotifyAboutMostVisitedItems();
191 }
192
193 void InstantService::Shutdown() {
194   process_ids_.clear();
195
196   if (instant_io_context_.get()) {
197     BrowserThread::PostTask(
198         BrowserThread::IO,
199         FROM_HERE,
200         base::Bind(&InstantIOContext::ClearInstantProcessesOnIO,
201                    instant_io_context_));
202   }
203   instant_io_context_ = NULL;
204 }
205
206 scoped_ptr<content::WebContents> InstantService::ReleaseNTPContents() {
207   return ntp_prerenderer_.ReleaseNTPContents();
208 }
209
210 content::WebContents* InstantService::GetNTPContents() const {
211   return ntp_prerenderer_.GetNTPContents();
212 }
213
214 void InstantService::OnBrowserInstantControllerCreated() {
215   if (profile_->IsOffTheRecord())
216     return;
217
218   ++browser_instant_controller_object_count_;
219
220   if (browser_instant_controller_object_count_ == 1)
221     ntp_prerenderer_.ReloadInstantNTP();
222 }
223
224 void InstantService::OnBrowserInstantControllerDestroyed() {
225   if (profile_->IsOffTheRecord())
226     return;
227
228   DCHECK_GT(browser_instant_controller_object_count_, 0U);
229   --browser_instant_controller_object_count_;
230
231   // All browser windows have closed, so release the InstantNTP resources to
232   // work around http://crbug.com/180810.
233   if (browser_instant_controller_object_count_ == 0)
234     ntp_prerenderer_.DeleteNTPContents();
235 }
236
237 void InstantService::Observe(int type,
238                              const content::NotificationSource& source,
239                              const content::NotificationDetails& details) {
240   switch (type) {
241     case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
242       SendSearchURLsToRenderer(
243           content::Source<content::RenderProcessHost>(source).ptr());
244       break;
245     case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
246       OnRendererProcessTerminated(
247           content::Source<content::RenderProcessHost>(source)->GetID());
248       break;
249     case chrome::NOTIFICATION_TOP_SITES_CHANGED: {
250       history::TopSites* top_sites = profile_->GetTopSites();
251       if (top_sites) {
252         top_sites->GetMostVisitedURLs(
253             base::Bind(&InstantService::OnMostVisitedItemsReceived,
254                        weak_ptr_factory_.GetWeakPtr()));
255       }
256       break;
257     }
258 #if defined(ENABLE_THEMES)
259     case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
260       OnThemeChanged(content::Source<ThemeService>(source).ptr());
261       break;
262     }
263 #endif  // defined(ENABLE_THEMES)
264     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
265       // Last chance to delete InstantNTP contents. We generally delete
266       // preloaded InstantNTP when the last BrowserInstantController object is
267       // destroyed. When the browser shutdown happens without closing browsers,
268       // there is a race condition between BrowserInstantController destruction
269       // and Profile destruction.
270       if (GetNTPContents())
271         ntp_prerenderer_.DeleteNTPContents();
272       break;
273     }
274     case chrome::NOTIFICATION_GOOGLE_URL_UPDATED: {
275       OnGoogleURLUpdated(
276           content::Source<Profile>(source).ptr(),
277           content::Details<GoogleURLTracker::UpdatedDetails>(details).ptr());
278       break;
279     }
280     default:
281       NOTREACHED() << "Unexpected notification type in InstantService.";
282   }
283 }
284
285 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
286   rph->Send(new ChromeViewMsg_SetSearchURLs(
287       chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_)));
288 }
289
290 void InstantService::OnRendererProcessTerminated(int process_id) {
291   process_ids_.erase(process_id);
292
293   if (instant_io_context_.get()) {
294     BrowserThread::PostTask(
295         BrowserThread::IO,
296         FROM_HERE,
297         base::Bind(&InstantIOContext::RemoveInstantProcessOnIO,
298                    instant_io_context_,
299                    process_id));
300   }
301 }
302
303 void InstantService::OnMostVisitedItemsReceived(
304     const history::MostVisitedURLList& data) {
305   history::MostVisitedURLList reordered_data(data);
306   history::MostVisitedTilesExperiment::MaybeShuffle(&reordered_data);
307
308   std::vector<InstantMostVisitedItem> new_most_visited_items;
309   for (size_t i = 0; i < reordered_data.size(); i++) {
310     const history::MostVisitedURL& url = reordered_data[i];
311     InstantMostVisitedItem item;
312     item.url = url.url;
313     item.title = url.title;
314     new_most_visited_items.push_back(item);
315   }
316
317   most_visited_items_ = new_most_visited_items;
318   NotifyAboutMostVisitedItems();
319 }
320
321 void InstantService::NotifyAboutMostVisitedItems() {
322   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
323                     MostVisitedItemsChanged(most_visited_items_));
324 }
325
326 void InstantService::OnThemeChanged(ThemeService* theme_service) {
327   if (!theme_service) {
328     DCHECK(theme_info_.get());
329     FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
330                       ThemeInfoChanged(*theme_info_));
331     return;
332   }
333
334   // Get theme information from theme service.
335   theme_info_.reset(new ThemeBackgroundInfo());
336
337   // Get if the current theme is the default theme.
338   theme_info_->using_default_theme = theme_service->UsingDefaultTheme();
339
340   // Get theme colors.
341   SkColor background_color =
342       theme_service->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
343   SkColor text_color =
344       theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT);
345   SkColor link_color =
346       theme_service->GetColor(ThemeProperties::COLOR_NTP_LINK);
347   SkColor text_color_light =
348       theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
349   SkColor header_color =
350       theme_service->GetColor(ThemeProperties::COLOR_NTP_HEADER);
351   // Generate section border color from the header color.
352   SkColor section_border_color =
353       SkColorSetARGB(kSectionBorderAlphaTransparency,
354                      SkColorGetR(header_color),
355                      SkColorGetG(header_color),
356                      SkColorGetB(header_color));
357
358   // Invert colors if needed.
359   if (gfx::IsInvertedColorScheme()) {
360     background_color = color_utils::InvertColor(background_color);
361     text_color = color_utils::InvertColor(text_color);
362     link_color = color_utils::InvertColor(link_color);
363     text_color_light = color_utils::InvertColor(text_color_light);
364     header_color = color_utils::InvertColor(header_color);
365     section_border_color = color_utils::InvertColor(section_border_color);
366   }
367
368   // Set colors.
369   theme_info_->background_color = SkColorToRGBAColor(background_color);
370   theme_info_->text_color = SkColorToRGBAColor(text_color);
371   theme_info_->link_color = SkColorToRGBAColor(link_color);
372   theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
373   theme_info_->header_color = SkColorToRGBAColor(header_color);
374   theme_info_->section_border_color = SkColorToRGBAColor(section_border_color);
375
376   int logo_alternate = theme_service->GetDisplayProperty(
377       ThemeProperties::NTP_LOGO_ALTERNATE);
378   theme_info_->logo_alternate = logo_alternate == 1;
379
380   if (theme_service->HasCustomImage(IDR_THEME_NTP_BACKGROUND)) {
381     // Set theme id for theme background image url.
382     theme_info_->theme_id = theme_service->GetThemeID();
383
384     // Set theme background image horizontal alignment.
385     int alignment = theme_service->GetDisplayProperty(
386         ThemeProperties::NTP_BACKGROUND_ALIGNMENT);
387     if (alignment & ThemeProperties::ALIGN_LEFT)
388       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT;
389     else if (alignment & ThemeProperties::ALIGN_RIGHT)
390       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT;
391     else
392       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
393
394     // Set theme background image vertical alignment.
395     if (alignment & ThemeProperties::ALIGN_TOP)
396       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP;
397     else if (alignment & ThemeProperties::ALIGN_BOTTOM)
398       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM;
399     else
400       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
401
402     // Set theme backgorund image tiling.
403     int tiling = theme_service->GetDisplayProperty(
404         ThemeProperties::NTP_BACKGROUND_TILING);
405     switch (tiling) {
406       case ThemeProperties::NO_REPEAT:
407         theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
408         break;
409       case ThemeProperties::REPEAT_X:
410         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X;
411         break;
412       case ThemeProperties::REPEAT_Y:
413         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y;
414         break;
415       case ThemeProperties::REPEAT:
416         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT;
417         break;
418     }
419
420     // Set theme background image height.
421     gfx::ImageSkia* image = theme_service->GetImageSkiaNamed(
422         IDR_THEME_NTP_BACKGROUND);
423     DCHECK(image);
424     theme_info_->image_height = image->height();
425
426     theme_info_->has_attribution =
427        theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION);
428   }
429
430   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
431                     ThemeInfoChanged(*theme_info_));
432 }
433
434 void InstantService::OnGoogleURLUpdated(
435     Profile* profile,
436     GoogleURLTracker::UpdatedDetails* details) {
437   GURL last_prompted_url(
438       profile->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL));
439
440   // See GoogleURLTracker::OnURLFetchComplete().
441   // last_prompted_url.is_empty() indicates very first run of Chrome. So there
442   // is no need to notify, as there won't be any old state.
443   if (last_prompted_url.is_empty())
444     return;
445
446   // Only the scheme changed. Ignore it since we do not prompt the user in this
447   // case.
448   if (net::StripWWWFromHost(details->first) ==
449       net::StripWWWFromHost(details->second))
450     return;
451
452   FOR_EACH_OBSERVER(InstantServiceObserver, observers_, GoogleURLUpdated());
453 }
454
455 void InstantService::OnDefaultSearchProviderChanged(
456     const std::string& pref_name) {
457   DCHECK_EQ(pref_name, std::string(prefs::kDefaultSearchProviderID));
458   const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile(
459       profile_)->GetDefaultSearchProvider();
460   if (!template_url) {
461     // A NULL |template_url| could mean either this notification is sent during
462     // the browser start up operation or the user now has no default search
463     // provider. There is no way for the user to reach this state using the
464     // Chrome settings. Only explicitly poking at the DB or bugs in the Sync
465     // could cause that, neither of which we support.
466     return;
467   }
468   FOR_EACH_OBSERVER(
469       InstantServiceObserver, observers_, DefaultSearchProviderChanged());
470 }
471
472 InstantNTPPrerenderer* InstantService::ntp_prerenderer() {
473   return &ntp_prerenderer_;
474 }