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/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