- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / ntp / new_tab_ui.cc
1 // Copyright (c) 2012 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/ui/webui/ntp/new_tab_ui.h"
6
7 #include <set>
8
9 #include "base/i18n/rtl.h"
10 #include "base/lazy_instance.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/metrics/histogram.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/webui/metrics_handler.h"
18 #include "chrome/browser/ui/webui/ntp/favicon_webui_handler.h"
19 #include "chrome/browser/ui/webui/ntp/foreign_session_handler.h"
20 #include "chrome/browser/ui/webui/ntp/most_visited_handler.h"
21 #include "chrome/browser/ui/webui/ntp/ntp_resource_cache.h"
22 #include "chrome/browser/ui/webui/ntp/ntp_resource_cache_factory.h"
23 #include "chrome/browser/ui/webui/ntp/ntp_user_data_logger.h"
24 #include "chrome/browser/ui/webui/ntp/recently_closed_tabs_handler.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "components/user_prefs/pref_registry_syncable.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/render_process_host.h"
31 #include "content/public/browser/render_view_host.h"
32 #include "content/public/browser/url_data_source.h"
33 #include "content/public/browser/web_contents.h"
34 #include "content/public/browser/web_ui.h"
35 #include "grit/browser_resources.h"
36 #include "grit/generated_resources.h"
37 #include "ui/base/l10n/l10n_util.h"
38
39 #if !defined(OS_ANDROID)
40 #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h"
41 #include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
42 #include "chrome/browser/ui/webui/ntp/new_tab_page_handler.h"
43 #include "chrome/browser/ui/webui/ntp/new_tab_page_sync_handler.h"
44 #include "chrome/browser/ui/webui/ntp/ntp_login_handler.h"
45 #include "chrome/browser/ui/webui/ntp/suggestions_page_handler.h"
46 #else
47 #include "chrome/browser/ui/webui/ntp/android/bookmarks_handler.h"
48 #include "chrome/browser/ui/webui/ntp/android/context_menu_handler.h"
49 #include "chrome/browser/ui/webui/ntp/android/navigation_handler.h"
50 #include "chrome/browser/ui/webui/ntp/android/new_tab_page_ready_handler.h"
51 #include "chrome/browser/ui/webui/ntp/android/promo_handler.h"
52 #endif
53
54 #if defined(ENABLE_THEMES)
55 #include "chrome/browser/ui/webui/theme_handler.h"
56 #endif
57
58 #if defined(USE_ASH)
59 #include "chrome/browser/ui/host_desktop.h"
60 #endif
61
62 using content::BrowserThread;
63 using content::RenderViewHost;
64 using content::WebUIController;
65
66 namespace {
67
68 // The amount of time there must be no painting for us to consider painting
69 // finished.  Observed times are in the ~1200ms range on Windows.
70 const int kTimeoutMs = 2000;
71
72 // Strings sent to the page via jstemplates used to set the direction of the
73 // HTML document based on locale.
74 const char kRTLHtmlTextDirection[] = "rtl";
75 const char kLTRHtmlTextDirection[] = "ltr";
76
77 static base::LazyInstance<std::set<const WebUIController*> > g_live_new_tabs;
78
79 const char* GetHtmlTextDirection(const string16& text) {
80   if (base::i18n::IsRTL() && base::i18n::StringContainsStrongRTLChars(text))
81     return kRTLHtmlTextDirection;
82   else
83     return kLTRHtmlTextDirection;
84 }
85
86 }  // namespace
87
88 ///////////////////////////////////////////////////////////////////////////////
89 // NewTabUI
90
91 NewTabUI::NewTabUI(content::WebUI* web_ui)
92     : WebUIController(web_ui),
93       WebContentsObserver(web_ui->GetWebContents()),
94       showing_sync_bubble_(false) {
95   g_live_new_tabs.Pointer()->insert(this);
96   web_ui->OverrideTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE));
97
98   NTPUserDataLogger::CreateForWebContents(web_contents());
99   NTPUserDataLogger::FromWebContents(web_contents())->set_ntp_url(
100       GURL(chrome::kChromeUINewTabURL));
101
102   // We count all link clicks as AUTO_BOOKMARK, so that site can be ranked more
103   // highly. Note this means we're including clicks on not only most visited
104   // thumbnails, but also clicks on recently bookmarked.
105   web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK);
106
107   if (!GetProfile()->IsOffTheRecord()) {
108     web_ui->AddMessageHandler(new browser_sync::ForeignSessionHandler());
109     web_ui->AddMessageHandler(new MostVisitedHandler());
110     web_ui->AddMessageHandler(new RecentlyClosedTabsHandler());
111 #if !defined(OS_ANDROID)
112     web_ui->AddMessageHandler(new FaviconWebUIHandler());
113     web_ui->AddMessageHandler(new MetricsHandler());
114     web_ui->AddMessageHandler(new NewTabPageHandler());
115     web_ui->AddMessageHandler(new CoreAppLauncherHandler());
116     if (NewTabUI::IsDiscoveryInNTPEnabled())
117       web_ui->AddMessageHandler(new SuggestionsHandler());
118     // Android doesn't have a sync promo/username on NTP.
119     web_ui->AddMessageHandler(new NewTabPageSyncHandler());
120
121     if (MightShowApps()) {
122       ExtensionService* service = GetProfile()->GetExtensionService();
123       // We might not have an ExtensionService (on ChromeOS when not logged in
124       // for example).
125       if (service)
126         web_ui->AddMessageHandler(new AppLauncherHandler(service));
127     }
128 #endif
129   }
130
131 #if defined(OS_ANDROID)
132   // These handlers are specific to the Android NTP page.
133   web_ui->AddMessageHandler(new BookmarksHandler());
134   web_ui->AddMessageHandler(new ContextMenuHandler());
135   web_ui->AddMessageHandler(new FaviconWebUIHandler());
136   web_ui->AddMessageHandler(new NavigationHandler());
137   web_ui->AddMessageHandler(new NewTabPageReadyHandler());
138   if (!GetProfile()->IsOffTheRecord())
139     web_ui->AddMessageHandler(new PromoHandler());
140 #else
141   // Android uses native UI for sync setup.
142   if (NTPLoginHandler::ShouldShow(GetProfile()))
143     web_ui->AddMessageHandler(new NTPLoginHandler());
144 #endif
145
146 #if defined(ENABLE_THEMES)
147   // The theme handler can require some CPU, so do it after hooking up the most
148   // visited handler. This allows the DB query for the new tab thumbs to happen
149   // earlier.
150   web_ui->AddMessageHandler(new ThemeHandler());
151 #endif
152
153   scoped_ptr<NewTabHTMLSource> html_source(new NewTabHTMLSource(
154       GetProfile()->GetOriginalProfile()));
155
156   // These two resources should be loaded only if suggestions NTP is enabled.
157   html_source->AddResource("suggestions_page.css", "text/css",
158       NewTabUI::IsDiscoveryInNTPEnabled() ? IDR_SUGGESTIONS_PAGE_CSS : 0);
159   if (NewTabUI::IsDiscoveryInNTPEnabled()) {
160     html_source->AddResource("suggestions_page.js", "application/javascript",
161         IDR_SUGGESTIONS_PAGE_JS);
162   }
163   // content::URLDataSource assumes the ownership of the html_source.
164   content::URLDataSource::Add(GetProfile(), html_source.release());
165
166   pref_change_registrar_.Init(GetProfile()->GetPrefs());
167   pref_change_registrar_.Add(prefs::kShowBookmarkBar,
168                              base::Bind(&NewTabUI::OnShowBookmarkBarChanged,
169                                         base::Unretained(this)));
170 }
171
172 NewTabUI::~NewTabUI() {
173   g_live_new_tabs.Pointer()->erase(this);
174 }
175
176 // The timer callback.  If enough time has elapsed since the last paint
177 // message, we say we're done painting; otherwise, we keep waiting.
178 void NewTabUI::PaintTimeout() {
179   // The amount of time there must be no painting for us to consider painting
180   // finished.  Observed times are in the ~1200ms range on Windows.
181   base::TimeTicks now = base::TimeTicks::Now();
182   if ((now - last_paint_) >= base::TimeDelta::FromMilliseconds(kTimeoutMs)) {
183     // Painting has quieted down.  Log this as the full time to run.
184     base::TimeDelta load_time = last_paint_ - start_;
185     int load_time_ms = static_cast<int>(load_time.InMilliseconds());
186     content::NotificationService::current()->Notify(
187         chrome::NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD,
188         content::Source<Profile>(GetProfile()),
189         content::Details<int>(&load_time_ms));
190     UMA_HISTOGRAM_TIMES("NewTabUI load", load_time);
191   } else {
192     // Not enough quiet time has elapsed.
193     // Some more paints must've occurred since we set the timeout.
194     // Wait some more.
195     timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
196                  &NewTabUI::PaintTimeout);
197   }
198 }
199
200 void NewTabUI::StartTimingPaint(RenderViewHost* render_view_host) {
201   start_ = base::TimeTicks::Now();
202   last_paint_ = start_;
203
204   content::NotificationSource source =
205       content::Source<content::RenderWidgetHost>(render_view_host);
206   if (!registrar_.IsRegistered(this,
207           content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
208           source)) {
209     registrar_.Add(
210         this,
211         content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE,
212         source);
213   }
214
215   timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
216                &NewTabUI::PaintTimeout);
217 }
218
219 void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) {
220   StartTimingPaint(render_view_host);
221 }
222
223 void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) {
224   StartTimingPaint(render_view_host);
225 }
226
227 void NewTabUI::WasHidden() {
228   EmitMouseoverCount();
229 }
230
231 void NewTabUI::Observe(int type,
232                        const content::NotificationSource& source,
233                        const content::NotificationDetails& details) {
234   switch (type) {
235     case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: {
236       last_paint_ = base::TimeTicks::Now();
237       break;
238     }
239     default:
240       CHECK(false) << "Unexpected notification: " << type;
241   }
242 }
243
244 void NewTabUI::EmitMouseoverCount() {
245   NTPUserDataLogger* data = NTPUserDataLogger::FromWebContents(web_contents());
246   if (data->ntp_url() == GURL(chrome::kChromeUINewTabURL))
247     data->EmitMouseoverCount();
248 }
249
250 void NewTabUI::OnShowBookmarkBarChanged() {
251   StringValue attached(
252       GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ?
253           "true" : "false");
254   web_ui()->CallJavascriptFunction("ntp.setBookmarkBarAttached", attached);
255 }
256
257 // static
258 void NewTabUI::RegisterProfilePrefs(
259     user_prefs::PrefRegistrySyncable* registry) {
260 #if !defined(OS_ANDROID)
261   CoreAppLauncherHandler::RegisterProfilePrefs(registry);
262   NewTabPageHandler::RegisterProfilePrefs(registry);
263   if (NewTabUI::IsDiscoveryInNTPEnabled())
264     SuggestionsHandler::RegisterProfilePrefs(registry);
265 #endif
266   MostVisitedHandler::RegisterProfilePrefs(registry);
267   browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry);
268 }
269
270 // static
271 bool NewTabUI::MightShowApps() {
272 // Android does not have apps.
273 #if defined(OS_ANDROID)
274   return false;
275 #else
276   return true;
277 #endif
278 }
279
280 // static
281 bool NewTabUI::ShouldShowApps() {
282 // Ash shows apps in app list thus should not show apps page in NTP4.
283 // Android does not have apps.
284 #if defined(OS_ANDROID)
285   return false;
286 #elif defined(USE_ASH)
287   return chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH;
288 #else
289   return true;
290 #endif
291 }
292
293 // static
294 bool NewTabUI::IsDiscoveryInNTPEnabled() {
295   // TODO(beaudoin): The flag was removed during a clean-up pass. We leave that
296   // here to easily enable it back when we will explore this option again.
297   return false;
298 }
299
300 // static
301 void NewTabUI::SetUrlTitleAndDirection(DictionaryValue* dictionary,
302                                        const string16& title,
303                                        const GURL& gurl) {
304   dictionary->SetString("url", gurl.spec());
305
306   bool using_url_as_the_title = false;
307   string16 title_to_set(title);
308   if (title_to_set.empty()) {
309     using_url_as_the_title = true;
310     title_to_set = UTF8ToUTF16(gurl.spec());
311   }
312
313   // We set the "dir" attribute of the title, so that in RTL locales, a LTR
314   // title is rendered left-to-right and truncated from the right. For example,
315   // the title of http://msdn.microsoft.com/en-us/default.aspx is "MSDN:
316   // Microsoft developer network". In RTL locales, in the [New Tab] page, if
317   // the "dir" of this title is not specified, it takes Chrome UI's
318   // directionality. So the title will be truncated as "soft developer
319   // network". Setting the "dir" attribute as "ltr" renders the truncated title
320   // as "MSDN: Microsoft D...". As another example, the title of
321   // http://yahoo.com is "Yahoo!". In RTL locales, in the [New Tab] page, the
322   // title will be rendered as "!Yahoo" if its "dir" attribute is not set to
323   // "ltr".
324   std::string direction;
325   if (using_url_as_the_title)
326     direction = kLTRHtmlTextDirection;
327   else
328     direction = GetHtmlTextDirection(title);
329
330   dictionary->SetString("title", title_to_set);
331   dictionary->SetString("direction", direction);
332 }
333
334 // static
335 void NewTabUI::SetFullNameAndDirection(const string16& full_name,
336                                        base::DictionaryValue* dictionary) {
337   dictionary->SetString("full_name", full_name);
338   dictionary->SetString("full_name_direction", GetHtmlTextDirection(full_name));
339 }
340
341 // static
342 NewTabUI* NewTabUI::FromWebUIController(WebUIController* ui) {
343   if (!g_live_new_tabs.Pointer()->count(ui))
344     return NULL;
345   return static_cast<NewTabUI*>(ui);
346 }
347
348 Profile* NewTabUI::GetProfile() const {
349   return Profile::FromWebUI(web_ui());
350 }
351
352 ///////////////////////////////////////////////////////////////////////////////
353 // NewTabHTMLSource
354
355 NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile)
356     : profile_(profile) {
357 }
358
359 std::string NewTabUI::NewTabHTMLSource::GetSource() const {
360   return chrome::kChromeUINewTabHost;
361 }
362
363 void NewTabUI::NewTabHTMLSource::StartDataRequest(
364     const std::string& path,
365     int render_process_id,
366     int render_view_id,
367     const content::URLDataSource::GotDataCallback& callback) {
368   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
369
370   std::map<std::string, std::pair<std::string, int> >::iterator it =
371     resource_map_.find(path);
372   if (it != resource_map_.end()) {
373     scoped_refptr<base::RefCountedStaticMemory> resource_bytes(
374         it->second.second ?
375             ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
376                 it->second.second) :
377             new base::RefCountedStaticMemory);
378     callback.Run(resource_bytes.get());
379     return;
380   }
381
382   if (!path.empty() && path[0] != '#') {
383     // A path under new-tab was requested; it's likely a bad relative
384     // URL from the new tab page, but in any case it's an error.
385
386     // TODO(dtrainor): Can remove this #if check once we update the
387     // accessibility script to no longer try to access urls like
388     // '?2314124523523'.
389     // See http://crbug.com/150252.
390 #if !defined(OS_ANDROID)
391     NOTREACHED() << path << " should not have been requested on the NTP";
392 #endif
393     callback.Run(NULL);
394     return;
395   }
396
397   content::RenderProcessHost* render_host =
398       content::RenderProcessHost::FromID(render_process_id);
399   NTPResourceCache::WindowType win_type = NTPResourceCache::GetWindowType(
400       profile_, render_host);
401   scoped_refptr<base::RefCountedMemory> html_bytes(
402       NTPResourceCacheFactory::GetForProfile(profile_)->
403       GetNewTabHTML(win_type));
404
405   callback.Run(html_bytes.get());
406 }
407
408 std::string NewTabUI::NewTabHTMLSource::GetMimeType(const std::string& resource)
409     const {
410   std::map<std::string, std::pair<std::string, int> >::const_iterator it =
411       resource_map_.find(resource);
412   if (it != resource_map_.end())
413     return it->second.first;
414   return "text/html";
415 }
416
417 bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const {
418   return false;
419 }
420
421 bool NewTabUI::NewTabHTMLSource::ShouldAddContentSecurityPolicy() const {
422   return false;
423 }
424
425 void NewTabUI::NewTabHTMLSource::AddResource(const char* resource,
426                                              const char* mime_type,
427                                              int resource_id) {
428   DCHECK(resource);
429   DCHECK(mime_type);
430   resource_map_[std::string(resource)] =
431       std::make_pair(std::string(mime_type), resource_id);
432 }
433
434 NewTabUI::NewTabHTMLSource::~NewTabHTMLSource() {}