Home | History | Annotate | Download | only in ntp
      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