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 54using content::BrowserThread; 55using content::RenderViewHost; 56using content::WebUIController; 57 58namespace { 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. 62const 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. 66const char kRTLHtmlTextDirection[] = "rtl"; 67const char kLTRHtmlTextDirection[] = "ltr"; 68 69static base::LazyInstance<std::set<const WebUIController*> > g_live_new_tabs; 70 71const 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 83NewTabUI::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 145NewTabUI::~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. 151void 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 168void 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 187void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) { 188 StartTimingPaint(render_view_host); 189} 190 191void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) { 192 StartTimingPaint(render_view_host); 193} 194 195void NewTabUI::WasHidden() { 196 EmitNtpStatistics(); 197} 198 199void 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 212void NewTabUI::EmitNtpStatistics() { 213 NTPUserDataLogger::GetOrCreateFromWebContents( 214 web_contents())->EmitNtpStatistics(); 215} 216 217void 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 225void 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 236bool 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 246bool 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 253void 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 287void 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 294NewTabUI* NewTabUI::FromWebUIController(WebUIController* ui) { 295 if (!g_live_new_tabs.Pointer()->count(ui)) 296 return NULL; 297 return static_cast<NewTabUI*>(ui); 298} 299 300Profile* NewTabUI::GetProfile() const { 301 return Profile::FromWebUI(web_ui()); 302} 303 304/////////////////////////////////////////////////////////////////////////////// 305// NewTabHTMLSource 306 307NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile) 308 : profile_(profile) { 309} 310 311std::string NewTabUI::NewTabHTMLSource::GetSource() const { 312 return chrome::kChromeUINewTabHost; 313} 314 315void 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 353std::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 362bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const { 363 return false; 364} 365 366bool NewTabUI::NewTabHTMLSource::ShouldAddContentSecurityPolicy() const { 367 return false; 368} 369 370void 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 379NewTabUI::NewTabHTMLSource::~NewTabHTMLSource() {} 380