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 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 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 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. 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 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 184 void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) { 185 StartTimingPaint(render_view_host); 186 } 187 188 void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) { 189 StartTimingPaint(render_view_host); 190 } 191 192 void NewTabUI::WasHidden() { 193 EmitNtpStatistics(); 194 } 195 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 209 void NewTabUI::EmitNtpStatistics() { 210 NTPUserDataLogger::GetOrCreateFromWebContents( 211 web_contents())->EmitNtpStatistics(); 212 } 213 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 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 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 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 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 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 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 297 Profile* NewTabUI::GetProfile() const { 298 return Profile::FromWebUI(web_ui()); 299 } 300 301 /////////////////////////////////////////////////////////////////////////////// 302 // NewTabHTMLSource 303 304 NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile) 305 : profile_(profile) { 306 } 307 308 std::string NewTabUI::NewTabHTMLSource::GetSource() const { 309 return chrome::kChromeUINewTabHost; 310 } 311 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 350 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 359 bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const { 360 return false; 361 } 362 363 bool NewTabUI::NewTabHTMLSource::ShouldAddContentSecurityPolicy() const { 364 return false; 365 } 366 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 376 NewTabUI::NewTabHTMLSource::~NewTabHTMLSource() {} 377