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