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 62 using content::BrowserThread; 63 using content::RenderViewHost; 64 using content::WebUIController; 65 66 namespace { 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. 70 const 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. 74 const char kRTLHtmlTextDirection[] = "rtl"; 75 const char kLTRHtmlTextDirection[] = "ltr"; 76 77 static base::LazyInstance<std::set<const WebUIController*> > g_live_new_tabs; 78 79 const char* GetHtmlTextDirection(const base::string16& text) { 80 if (base::i18n::IsRTL() && base::i18n::StringContainsStrongRTLChars(text)) 81 return kRTLHtmlTextDirection; 82 else 83 return kLTRHtmlTextDirection; 84 } 85 86 } // namespace 87 88 /////////////////////////////////////////////////////////////////////////////// 89 // NewTabUI 90 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)); 97 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); 102 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()); 116 117 if (MightShowApps()) { 118 ExtensionService* service = GetProfile()->GetExtensionService(); 119 // We might not have an ExtensionService (on ChromeOS when not logged in 120 // for example). 121 if (service) 122 web_ui->AddMessageHandler(new AppLauncherHandler(service)); 123 } 124 #endif 125 } 126 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()); 136 #else 137 // Android uses native UI for sync setup. 138 if (NTPLoginHandler::ShouldShow(GetProfile())) 139 web_ui->AddMessageHandler(new NTPLoginHandler()); 140 #endif 141 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 145 // earlier. 146 web_ui->AddMessageHandler(new ThemeHandler()); 147 #endif 148 149 scoped_ptr<NewTabHTMLSource> html_source(new NewTabHTMLSource( 150 GetProfile()->GetOriginalProfile())); 151 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); 158 } 159 // content::URLDataSource assumes the ownership of the html_source. 160 content::URLDataSource::Add(GetProfile(), html_source.release()); 161 162 pref_change_registrar_.Init(GetProfile()->GetPrefs()); 163 pref_change_registrar_.Add(prefs::kShowBookmarkBar, 164 base::Bind(&NewTabUI::OnShowBookmarkBarChanged, 165 base::Unretained(this))); 166 } 167 168 NewTabUI::~NewTabUI() { 169 g_live_new_tabs.Pointer()->erase(this); 170 } 171 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 int load_time_ms = static_cast<int>(load_time.InMilliseconds()); 182 content::NotificationService::current()->Notify( 183 chrome::NOTIFICATION_INITIAL_NEW_TAB_UI_LOAD, 184 content::Source<Profile>(GetProfile()), 185 content::Details<int>(&load_time_ms)); 186 UMA_HISTOGRAM_TIMES("NewTabUI load", load_time); 187 } else { 188 // Not enough quiet time has elapsed. 189 // Some more paints must've occurred since we set the timeout. 190 // Wait some more. 191 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimeoutMs), this, 192 &NewTabUI::PaintTimeout); 193 } 194 } 195 196 void NewTabUI::StartTimingPaint(RenderViewHost* render_view_host) { 197 start_ = base::TimeTicks::Now(); 198 last_paint_ = start_; 199 200 content::NotificationSource source = 201 content::Source<content::RenderWidgetHost>(render_view_host); 202 if (!registrar_.IsRegistered(this, 203 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, 204 source)) { 205 registrar_.Add( 206 this, 207 content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE, 208 source); 209 } 210 211 timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kTimeoutMs), this, 212 &NewTabUI::PaintTimeout); 213 } 214 215 void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) { 216 StartTimingPaint(render_view_host); 217 } 218 219 void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) { 220 StartTimingPaint(render_view_host); 221 } 222 223 void NewTabUI::WasHidden() { 224 EmitNtpStatistics(); 225 } 226 227 void NewTabUI::Observe(int type, 228 const content::NotificationSource& source, 229 const content::NotificationDetails& details) { 230 switch (type) { 231 case content::NOTIFICATION_RENDER_WIDGET_HOST_DID_UPDATE_BACKING_STORE: { 232 last_paint_ = base::TimeTicks::Now(); 233 break; 234 } 235 default: 236 CHECK(false) << "Unexpected notification: " << type; 237 } 238 } 239 240 void NewTabUI::EmitNtpStatistics() { 241 NTPUserDataLogger::GetOrCreateFromWebContents( 242 web_contents())->EmitNtpStatistics(); 243 } 244 245 void NewTabUI::OnShowBookmarkBarChanged() { 246 StringValue attached( 247 GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ? 248 "true" : "false"); 249 web_ui()->CallJavascriptFunction("ntp.setBookmarkBarAttached", attached); 250 } 251 252 // static 253 void NewTabUI::RegisterProfilePrefs( 254 user_prefs::PrefRegistrySyncable* registry) { 255 #if !defined(OS_ANDROID) 256 CoreAppLauncherHandler::RegisterProfilePrefs(registry); 257 NewTabPageHandler::RegisterProfilePrefs(registry); 258 if (NewTabUI::IsDiscoveryInNTPEnabled()) 259 SuggestionsHandler::RegisterProfilePrefs(registry); 260 #endif 261 MostVisitedHandler::RegisterProfilePrefs(registry); 262 browser_sync::ForeignSessionHandler::RegisterProfilePrefs(registry); 263 } 264 265 // static 266 bool NewTabUI::MightShowApps() { 267 // Android does not have apps. 268 #if defined(OS_ANDROID) 269 return false; 270 #else 271 return true; 272 #endif 273 } 274 275 // static 276 bool NewTabUI::ShouldShowApps() { 277 // Ash shows apps in app list thus should not show apps page in NTP4. 278 // Android does not have apps. 279 #if defined(OS_ANDROID) 280 return false; 281 #elif defined(USE_ASH) 282 return chrome::GetActiveDesktop() != chrome::HOST_DESKTOP_TYPE_ASH; 283 #else 284 return true; 285 #endif 286 } 287 288 // static 289 bool NewTabUI::IsDiscoveryInNTPEnabled() { 290 // TODO(beaudoin): The flag was removed during a clean-up pass. We leave that 291 // here to easily enable it back when we will explore this option again. 292 return false; 293 } 294 295 // static 296 void NewTabUI::SetUrlTitleAndDirection(DictionaryValue* dictionary, 297 const base::string16& title, 298 const GURL& gurl) { 299 dictionary->SetString("url", gurl.spec()); 300 301 bool using_url_as_the_title = false; 302 base::string16 title_to_set(title); 303 if (title_to_set.empty()) { 304 using_url_as_the_title = true; 305 title_to_set = UTF8ToUTF16(gurl.spec()); 306 } 307 308 // We set the "dir" attribute of the title, so that in RTL locales, a LTR 309 // title is rendered left-to-right and truncated from the right. For example, 310 // the title of http://msdn.microsoft.com/en-us/default.aspx is "MSDN: 311 // Microsoft developer network". In RTL locales, in the [New Tab] page, if 312 // the "dir" of this title is not specified, it takes Chrome UI's 313 // directionality. So the title will be truncated as "soft developer 314 // network". Setting the "dir" attribute as "ltr" renders the truncated title 315 // as "MSDN: Microsoft D...". As another example, the title of 316 // http://yahoo.com is "Yahoo!". In RTL locales, in the [New Tab] page, the 317 // title will be rendered as "!Yahoo" if its "dir" attribute is not set to 318 // "ltr". 319 std::string direction; 320 if (using_url_as_the_title) 321 direction = kLTRHtmlTextDirection; 322 else 323 direction = GetHtmlTextDirection(title); 324 325 dictionary->SetString("title", title_to_set); 326 dictionary->SetString("direction", direction); 327 } 328 329 // static 330 void NewTabUI::SetFullNameAndDirection(const base::string16& full_name, 331 base::DictionaryValue* dictionary) { 332 dictionary->SetString("full_name", full_name); 333 dictionary->SetString("full_name_direction", GetHtmlTextDirection(full_name)); 334 } 335 336 // static 337 NewTabUI* NewTabUI::FromWebUIController(WebUIController* ui) { 338 if (!g_live_new_tabs.Pointer()->count(ui)) 339 return NULL; 340 return static_cast<NewTabUI*>(ui); 341 } 342 343 Profile* NewTabUI::GetProfile() const { 344 return Profile::FromWebUI(web_ui()); 345 } 346 347 /////////////////////////////////////////////////////////////////////////////// 348 // NewTabHTMLSource 349 350 NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile) 351 : profile_(profile) { 352 } 353 354 std::string NewTabUI::NewTabHTMLSource::GetSource() const { 355 return chrome::kChromeUINewTabHost; 356 } 357 358 void NewTabUI::NewTabHTMLSource::StartDataRequest( 359 const std::string& path, 360 int render_process_id, 361 int render_view_id, 362 const content::URLDataSource::GotDataCallback& callback) { 363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 364 365 std::map<std::string, std::pair<std::string, int> >::iterator it = 366 resource_map_.find(path); 367 if (it != resource_map_.end()) { 368 scoped_refptr<base::RefCountedStaticMemory> resource_bytes( 369 it->second.second ? 370 ResourceBundle::GetSharedInstance().LoadDataResourceBytes( 371 it->second.second) : 372 new base::RefCountedStaticMemory); 373 callback.Run(resource_bytes.get()); 374 return; 375 } 376 377 if (!path.empty() && path[0] != '#') { 378 // A path under new-tab was requested; it's likely a bad relative 379 // URL from the new tab page, but in any case it's an error. 380 381 // TODO(dtrainor): Can remove this #if check once we update the 382 // accessibility script to no longer try to access urls like 383 // '?2314124523523'. 384 // See http://crbug.com/150252. 385 #if !defined(OS_ANDROID) 386 NOTREACHED() << path << " should not have been requested on the NTP"; 387 #endif 388 callback.Run(NULL); 389 return; 390 } 391 392 content::RenderProcessHost* render_host = 393 content::RenderProcessHost::FromID(render_process_id); 394 NTPResourceCache::WindowType win_type = NTPResourceCache::GetWindowType( 395 profile_, render_host); 396 scoped_refptr<base::RefCountedMemory> html_bytes( 397 NTPResourceCacheFactory::GetForProfile(profile_)-> 398 GetNewTabHTML(win_type)); 399 400 callback.Run(html_bytes.get()); 401 } 402 403 std::string NewTabUI::NewTabHTMLSource::GetMimeType(const std::string& resource) 404 const { 405 std::map<std::string, std::pair<std::string, int> >::const_iterator it = 406 resource_map_.find(resource); 407 if (it != resource_map_.end()) 408 return it->second.first; 409 return "text/html"; 410 } 411 412 bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const { 413 return false; 414 } 415 416 bool NewTabUI::NewTabHTMLSource::ShouldAddContentSecurityPolicy() const { 417 return false; 418 } 419 420 void NewTabUI::NewTabHTMLSource::AddResource(const char* resource, 421 const char* mime_type, 422 int resource_id) { 423 DCHECK(resource); 424 DCHECK(mime_type); 425 resource_map_[std::string(resource)] = 426 std::make_pair(std::string(mime_type), resource_id); 427 } 428 429 NewTabUI::NewTabHTMLSource::~NewTabHTMLSource() {} 430