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.
5 #include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
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"
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"
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"
54 #if defined(ENABLE_THEMES)
55 #include "chrome/browser/ui/webui/theme_handler.h"
59 #include "chrome/browser/ui/host_desktop.h"
62 using content::BrowserThread
;
63 using content::RenderViewHost
;
64 using content::WebUIController
;
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;
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";
77 static base::LazyInstance
<std::set
<const WebUIController
*> > g_live_new_tabs
;
79 const char* GetHtmlTextDirection(const base::string16
& text
) {
80 if (base::i18n::IsRTL() && base::i18n::StringContainsStrongRTLChars(text
))
81 return kRTLHtmlTextDirection
;
83 return kLTRHtmlTextDirection
;
88 ///////////////////////////////////////////////////////////////////////////////
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
));
98 // We count all link clicks as AUTO_BOOKMARK, so that site can be ranked more
99 // highly. Note this means we're including clicks on not only most visited
100 // thumbnails, but also clicks on recently bookmarked.
101 web_ui
->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK
);
103 if (!GetProfile()->IsOffTheRecord()) {
104 web_ui
->AddMessageHandler(new browser_sync::ForeignSessionHandler());
105 web_ui
->AddMessageHandler(new MetricsHandler());
106 web_ui
->AddMessageHandler(new MostVisitedHandler());
107 web_ui
->AddMessageHandler(new RecentlyClosedTabsHandler());
108 #if !defined(OS_ANDROID)
109 web_ui
->AddMessageHandler(new FaviconWebUIHandler());
110 web_ui
->AddMessageHandler(new NewTabPageHandler());
111 web_ui
->AddMessageHandler(new CoreAppLauncherHandler());
112 if (NewTabUI::IsDiscoveryInNTPEnabled())
113 web_ui
->AddMessageHandler(new SuggestionsHandler());
114 // Android doesn't have a sync promo/username on NTP.
115 web_ui
->AddMessageHandler(new NewTabPageSyncHandler());
117 if (MightShowApps()) {
118 ExtensionService
* service
= GetProfile()->GetExtensionService();
119 // We might not have an ExtensionService (on ChromeOS when not logged in
122 web_ui
->AddMessageHandler(new AppLauncherHandler(service
));
127 #if defined(OS_ANDROID)
128 // These handlers are specific to the Android NTP page.
129 web_ui
->AddMessageHandler(new BookmarksHandler());
130 web_ui
->AddMessageHandler(new ContextMenuHandler());
131 web_ui
->AddMessageHandler(new FaviconWebUIHandler());
132 web_ui
->AddMessageHandler(new NavigationHandler());
133 web_ui
->AddMessageHandler(new NewTabPageReadyHandler());
134 if (!GetProfile()->IsOffTheRecord())
135 web_ui
->AddMessageHandler(new PromoHandler());
137 // Android uses native UI for sync setup.
138 if (NTPLoginHandler::ShouldShow(GetProfile()))
139 web_ui
->AddMessageHandler(new NTPLoginHandler());
142 #if defined(ENABLE_THEMES)
143 // The theme handler can require some CPU, so do it after hooking up the most
144 // visited handler. This allows the DB query for the new tab thumbs to happen
146 web_ui
->AddMessageHandler(new ThemeHandler());
149 scoped_ptr
<NewTabHTMLSource
> html_source(new NewTabHTMLSource(
150 GetProfile()->GetOriginalProfile()));
152 // These two resources should be loaded only if suggestions NTP is enabled.
153 html_source
->AddResource("suggestions_page.css", "text/css",
154 NewTabUI::IsDiscoveryInNTPEnabled() ? IDR_SUGGESTIONS_PAGE_CSS
: 0);
155 if (NewTabUI::IsDiscoveryInNTPEnabled()) {
156 html_source
->AddResource("suggestions_page.js", "application/javascript",
157 IDR_SUGGESTIONS_PAGE_JS
);
159 // content::URLDataSource assumes the ownership of the html_source.
160 content::URLDataSource::Add(GetProfile(), html_source
.release());
162 pref_change_registrar_
.Init(GetProfile()->GetPrefs());
163 pref_change_registrar_
.Add(prefs::kShowBookmarkBar
,
164 base::Bind(&NewTabUI::OnShowBookmarkBarChanged
,
165 base::Unretained(this)));
168 NewTabUI::~NewTabUI() {
169 g_live_new_tabs
.Pointer()->erase(this);
172 // The timer callback. If enough time has elapsed since the last paint
173 // message, we say we're done painting; otherwise, we keep waiting.
174 void NewTabUI::PaintTimeout() {
175 // The amount of time there must be no painting for us to consider painting
176 // finished. Observed times are in the ~1200ms range on Windows.
177 base::TimeTicks now
= base::TimeTicks::Now();
178 if ((now
- last_paint_
) >= base::TimeDelta::FromMilliseconds(kTimeoutMs
)) {
179 // Painting has quieted down. Log this as the full time to run.
180 base::TimeDelta load_time
= last_paint_
- start_
;
181 UMA_HISTOGRAM_TIMES("NewTabUI load", load_time
);
183 // Not enough quiet time has elapsed.
184 // Some more paints must've occurred since we set the timeout.
186 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kTimeoutMs
), this,
187 &NewTabUI::PaintTimeout
);
191 void NewTabUI::StartTimingPaint(RenderViewHost
* render_view_host
) {
192 start_
= base::TimeTicks::Now();
193 last_paint_
= start_
;
195 content::NotificationSource source
=
196 content::Source
<content::RenderWidgetHost
>(render_view_host
);
197 if (!registrar_
.IsRegistered(this,
198 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
202 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
,
206 timer_
.Start(FROM_HERE
, base::TimeDelta::FromMilliseconds(kTimeoutMs
), this,
207 &NewTabUI::PaintTimeout
);
210 void NewTabUI::RenderViewCreated(RenderViewHost
* render_view_host
) {
211 StartTimingPaint(render_view_host
);
214 void NewTabUI::RenderViewReused(RenderViewHost
* render_view_host
) {
215 StartTimingPaint(render_view_host
);
218 void NewTabUI::WasHidden() {
222 void NewTabUI::Observe(int type
,
223 const content::NotificationSource
& source
,
224 const content::NotificationDetails
& details
) {
226 case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE
: {
227 last_paint_
= base::TimeTicks::Now();
231 CHECK(false) << "Unexpected notification: " << type
;
235 void NewTabUI::EmitNtpStatistics() {
236 NTPUserDataLogger::GetOrCreateFromWebContents(
237 web_contents())->EmitNtpStatistics();
240 void NewTabUI::OnShowBookmarkBarChanged() {
241 base::StringValue
attached(
242 GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar
) ?
244 web_ui()->CallJavascriptFunction("ntp.setBookmarkBarAttached", attached
);
248 void NewTabUI::RegisterProfilePrefs(
249 user_prefs::PrefRegistrySyncable
* registry
) {
250 #if !defined(OS_ANDROID)
251 CoreAppLauncherHandler::RegisterProfilePrefs(registry
);
252 NewTabPageHandler::RegisterProfilePrefs(registry
);
253 if (NewTabUI::IsDiscoveryInNTPEnabled())
254 SuggestionsHandler::RegisterProfilePrefs(registry
);
256 MostVisitedHandler::RegisterProfilePrefs(registry
);
257 browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry
);
261 bool NewTabUI::MightShowApps() {
262 // Android does not have apps.
263 #if defined(OS_ANDROID)
271 bool NewTabUI::ShouldShowApps() {
272 // Ash shows apps in app list thus should not show apps page in NTP4.
273 // Android does not have apps.
274 #if defined(OS_ANDROID)
276 #elif defined(USE_ASH)
277 return chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH
;
284 bool NewTabUI::IsDiscoveryInNTPEnabled() {
285 // TODO(beaudoin): The flag was removed during a clean-up pass. We leave that
286 // here to easily enable it back when we will explore this option again.
291 void NewTabUI::SetUrlTitleAndDirection(base::DictionaryValue
* dictionary
,
292 const base::string16
& title
,
294 dictionary
->SetString("url", gurl
.spec());
296 bool using_url_as_the_title
= false;
297 base::string16
title_to_set(title
);
298 if (title_to_set
.empty()) {
299 using_url_as_the_title
= true;
300 title_to_set
= base::UTF8ToUTF16(gurl
.spec());
303 // We set the "dir" attribute of the title, so that in RTL locales, a LTR
304 // title is rendered left-to-right and truncated from the right. For example,
305 // the title of http://msdn.microsoft.com/en-us/default.aspx is "MSDN:
306 // Microsoft developer network". In RTL locales, in the [New Tab] page, if
307 // the "dir" of this title is not specified, it takes Chrome UI's
308 // directionality. So the title will be truncated as "soft developer
309 // network". Setting the "dir" attribute as "ltr" renders the truncated title
310 // as "MSDN: Microsoft D...". As another example, the title of
311 // http://yahoo.com is "Yahoo!". In RTL locales, in the [New Tab] page, the
312 // title will be rendered as "!Yahoo" if its "dir" attribute is not set to
314 std::string direction
;
315 if (using_url_as_the_title
)
316 direction
= kLTRHtmlTextDirection
;
318 direction
= GetHtmlTextDirection(title
);
320 dictionary
->SetString("title", title_to_set
);
321 dictionary
->SetString("direction", direction
);
325 void NewTabUI::SetFullNameAndDirection(const base::string16
& full_name
,
326 base::DictionaryValue
* dictionary
) {
327 dictionary
->SetString("full_name", full_name
);
328 dictionary
->SetString("full_name_direction", GetHtmlTextDirection(full_name
));
332 NewTabUI
* NewTabUI::FromWebUIController(WebUIController
* ui
) {
333 if (!g_live_new_tabs
.Pointer()->count(ui
))
335 return static_cast<NewTabUI
*>(ui
);
338 Profile
* NewTabUI::GetProfile() const {
339 return Profile::FromWebUI(web_ui());
342 ///////////////////////////////////////////////////////////////////////////////
345 NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile
* profile
)
346 : profile_(profile
) {
349 std::string
NewTabUI::NewTabHTMLSource::GetSource() const {
350 return chrome::kChromeUINewTabHost
;
353 void NewTabUI::NewTabHTMLSource::StartDataRequest(
354 const std::string
& path
,
355 int render_process_id
,
357 const content::URLDataSource::GotDataCallback
& callback
) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
360 std::map
<std::string
, std::pair
<std::string
, int> >::iterator it
=
361 resource_map_
.find(path
);
362 if (it
!= resource_map_
.end()) {
363 scoped_refptr
<base::RefCountedStaticMemory
> resource_bytes(
365 ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
367 new base::RefCountedStaticMemory
);
368 callback
.Run(resource_bytes
.get());
372 if (!path
.empty() && path
[0] != '#') {
373 // A path under new-tab was requested; it's likely a bad relative
374 // URL from the new tab page, but in any case it's an error.
376 // TODO(dtrainor): Can remove this #if check once we update the
377 // accessibility script to no longer try to access urls like
379 // See http://crbug.com/150252.
380 #if !defined(OS_ANDROID)
381 NOTREACHED() << path
<< " should not have been requested on the NTP";
387 content::RenderProcessHost
* render_host
=
388 content::RenderProcessHost::FromID(render_process_id
);
389 NTPResourceCache::WindowType win_type
= NTPResourceCache::GetWindowType(
390 profile_
, render_host
);
391 scoped_refptr
<base::RefCountedMemory
> html_bytes(
392 NTPResourceCacheFactory::GetForProfile(profile_
)->
393 GetNewTabHTML(win_type
));
395 callback
.Run(html_bytes
.get());
398 std::string
NewTabUI::NewTabHTMLSource::GetMimeType(const std::string
& resource
)
400 std::map
<std::string
, std::pair
<std::string
, int> >::const_iterator it
=
401 resource_map_
.find(resource
);
402 if (it
!= resource_map_
.end())
403 return it
->second
.first
;
407 bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const {
411 bool NewTabUI::NewTabHTMLSource::ShouldAddContentSecurityPolicy() const {
415 void NewTabUI::NewTabHTMLSource::AddResource(const char* resource
,
416 const char* mime_type
,
420 resource_map_
[std::string(resource
)] =
421 std::make_pair(std::string(mime_type
), resource_id
);
424 NewTabUI::NewTabHTMLSource::~NewTabHTMLSource() {}