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