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