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