Home | History | Annotate | Download | only in search
      1 // Copyright 2013 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/search/instant_ntp_prerenderer.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/bind.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "build/build_config.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/content_settings/content_settings_provider.h"
     14 #include "chrome/browser/content_settings/host_content_settings_map.h"
     15 #include "chrome/browser/profiles/profile.h"
     16 #include "chrome/browser/search/instant_service.h"
     17 #include "chrome/browser/search/instant_service_factory.h"
     18 #include "chrome/browser/search/search.h"
     19 #include "chrome/browser/ui/browser.h"
     20 #include "chrome/browser/ui/browser_finder.h"
     21 #include "chrome/browser/ui/host_desktop.h"
     22 #include "chrome/browser/ui/search/instant_ntp.h"
     23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     24 #include "chrome/common/content_settings.h"
     25 #include "chrome/common/pref_names.h"
     26 #include "chrome/common/search_urls.h"
     27 #include "content/public/browser/notification_service.h"
     28 #include "content/public/browser/render_process_host.h"
     29 #include "content/public/browser/web_contents.h"
     30 #include "net/base/network_change_notifier.h"
     31 
     32 namespace {
     33 
     34 void DeleteNTPSoon(scoped_ptr<InstantNTP> ntp) {
     35   if (!ntp)
     36     return;
     37 
     38   if (ntp->contents()) {
     39     base::MessageLoop::current()->DeleteSoon(
     40         FROM_HERE, ntp->ReleaseContents().release());
     41   }
     42   base::MessageLoop::current()->DeleteSoon(FROM_HERE, ntp.release());
     43 }
     44 
     45 }  // namespace
     46 
     47 
     48 InstantNTPPrerenderer::InstantNTPPrerenderer(Profile* profile,
     49                                              InstantService* instant_service,
     50                                              PrefService* prefs)
     51     : profile_(profile) {
     52   DCHECK(profile);
     53 
     54   // In unit tests, prefs may be NULL.
     55   if (prefs) {
     56     profile_pref_registrar_.Init(prefs);
     57     profile_pref_registrar_.Add(
     58         prefs::kSearchSuggestEnabled,
     59         base::Bind(&InstantNTPPrerenderer::ReloadInstantNTP,
     60                    base::Unretained(this)));
     61   }
     62   net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
     63 
     64   // Allow instant_service to be null for unit tets.
     65   if (instant_service)
     66     instant_service->AddObserver(this);
     67 }
     68 
     69 InstantNTPPrerenderer::~InstantNTPPrerenderer() {
     70   InstantService* instant_service =
     71       InstantServiceFactory::GetForProfile(profile_);
     72   if (instant_service)
     73     instant_service->RemoveObserver(this);
     74   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
     75 }
     76 
     77 void InstantNTPPrerenderer::ReloadInstantNTP() {
     78   ResetNTP(GetInstantURL());
     79 }
     80 
     81 scoped_ptr<content::WebContents> InstantNTPPrerenderer::ReleaseNTPContents() {
     82   if (!profile_ || profile_->IsOffTheRecord() ||
     83       !chrome::ShouldShowInstantNTP())
     84     return scoped_ptr<content::WebContents>();
     85 
     86   if (ShouldSwitchToLocalNTP())
     87     ResetNTP(GetLocalInstantURL());
     88 
     89   scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents();
     90 
     91   // Preload a new InstantNTP.
     92   ReloadInstantNTP();
     93   return ntp_contents.Pass();
     94 }
     95 
     96 content::WebContents* InstantNTPPrerenderer::GetNTPContents() const {
     97   return ntp() ? ntp()->contents() : NULL;
     98 }
     99 
    100 void InstantNTPPrerenderer::DeleteNTPContents() {
    101   if (ntp_)
    102     ntp_.reset();
    103 }
    104 
    105 void InstantNTPPrerenderer::RenderProcessGone() {
    106   DeleteNTPSoon(ntp_.Pass());
    107 }
    108 
    109 void InstantNTPPrerenderer::LoadCompletedMainFrame() {
    110   if (!ntp_ || ntp_->supports_instant())
    111     return;
    112 
    113   content::WebContents* ntp_contents = ntp_->contents();
    114   DCHECK(ntp_contents);
    115 
    116   InstantService* instant_service =
    117       InstantServiceFactory::GetForProfile(profile());
    118   if (instant_service &&
    119       instant_service->IsInstantProcess(
    120           ntp_contents->GetRenderProcessHost()->GetID())) {
    121     return;
    122   }
    123   InstantSupportDetermined(ntp_contents, false);
    124 }
    125 
    126 std::string InstantNTPPrerenderer::GetLocalInstantURL() const {
    127   return chrome::GetLocalInstantURL(profile_).spec();
    128 }
    129 
    130 std::string InstantNTPPrerenderer::GetInstantURL() const {
    131   if (net::NetworkChangeNotifier::IsOffline())
    132     return GetLocalInstantURL();
    133 
    134   // TODO(kmadhusu): Remove start margin param from chrome::GetInstantURL().
    135   const GURL instant_url = chrome::GetInstantURL(profile_,
    136                                                  chrome::kDisableStartMargin,
    137                                                  false);
    138   if (!instant_url.is_valid())
    139     return GetLocalInstantURL();
    140 
    141   return instant_url.spec();
    142 }
    143 
    144 bool InstantNTPPrerenderer::IsJavascriptEnabled() const {
    145   GURL instant_url(GetInstantURL());
    146   GURL origin(instant_url.GetOrigin());
    147   ContentSetting js_setting = profile_->GetHostContentSettingsMap()->
    148       GetContentSetting(origin, origin, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
    149                         NO_RESOURCE_IDENTIFIER);
    150   // Javascript can be disabled either in content settings or via a WebKit
    151   // preference, so check both. Disabling it through the Settings page affects
    152   // content settings. I'm not sure how to disable the WebKit preference, but
    153   // it's theoretically possible some users have it off.
    154   bool js_content_enabled =
    155       js_setting == CONTENT_SETTING_DEFAULT ||
    156       js_setting == CONTENT_SETTING_ALLOW;
    157   bool js_webkit_enabled = profile_->GetPrefs()->GetBoolean(
    158       prefs::kWebKitJavascriptEnabled);
    159   return js_content_enabled && js_webkit_enabled;
    160 }
    161 
    162 bool InstantNTPPrerenderer::InStartup() const {
    163 #if !defined(OS_ANDROID)
    164   // TODO(kmadhusu): This is not completely reliable. Find a better way to
    165   // detect startup time.
    166   Browser* browser = chrome::FindBrowserWithProfile(profile_,
    167                                                     chrome::GetActiveDesktop());
    168   return !browser || !browser->tab_strip_model()->GetActiveWebContents();
    169 #endif
    170   return false;
    171 }
    172 
    173 InstantNTP* InstantNTPPrerenderer::ntp() const {
    174   return ntp_.get();
    175 }
    176 
    177 void InstantNTPPrerenderer::OnNetworkChanged(
    178     net::NetworkChangeNotifier::ConnectionType type) {
    179   // Not interested in events conveying change to offline.
    180   if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
    181     return;
    182 
    183   if (!ntp() || ntp()->IsLocal())
    184     ReloadInstantNTP();
    185 }
    186 
    187 void InstantNTPPrerenderer::InstantSupportDetermined(
    188     const content::WebContents* contents,
    189     bool supports_instant) {
    190   DCHECK(ntp() && ntp()->contents() == contents);
    191 
    192   if (!supports_instant) {
    193     bool is_local = ntp()->IsLocal();
    194     DeleteNTPSoon(ntp_.Pass());
    195     if (!is_local)
    196       ResetNTP(GetLocalInstantURL());
    197   }
    198 
    199   content::NotificationService::current()->Notify(
    200       chrome::NOTIFICATION_INSTANT_NTP_SUPPORT_DETERMINED,
    201       content::Source<InstantNTPPrerenderer>(this),
    202       content::NotificationService::NoDetails());
    203 }
    204 
    205 void InstantNTPPrerenderer::InstantPageAboutToNavigateMainFrame(
    206     const content::WebContents* /* contents */,
    207     const GURL& /* url */) {
    208   NOTREACHED();
    209 }
    210 
    211 void InstantNTPPrerenderer::InstantPageLoadFailed(
    212     content::WebContents* contents) {
    213   DCHECK(ntp() && ntp()->contents() == contents);
    214 
    215   bool is_local = ntp()->IsLocal();
    216   DeleteNTPSoon(ntp_.Pass());
    217   if (!is_local)
    218     ResetNTP(GetLocalInstantURL());
    219 }
    220 
    221 void InstantNTPPrerenderer::ResetNTP(const std::string& instant_url) {
    222   // Instant NTP is only used in extended mode so we should always have a
    223   // non-empty URL to use.
    224   DCHECK(!instant_url.empty());
    225   if (!chrome::ShouldUseCacheableNTP()) {
    226     ntp_.reset(new InstantNTP(this, instant_url, profile_));
    227     ntp_->InitContents(base::Bind(&InstantNTPPrerenderer::ReloadInstantNTP,
    228                                   base::Unretained(this)));
    229   }
    230 }
    231 
    232 bool InstantNTPPrerenderer::PageIsCurrent() const {
    233   const std::string& instant_url = GetInstantURL();
    234   if (instant_url.empty() ||
    235       !search::MatchesOriginAndPath(GURL(ntp()->instant_url()),
    236                                     GURL(instant_url)))
    237     return false;
    238 
    239   return ntp()->supports_instant();
    240 }
    241 
    242 bool InstantNTPPrerenderer::ShouldSwitchToLocalNTP() const {
    243   if (!ntp())
    244     return true;
    245 
    246   // Assume users with Javascript disabled do not want the online experience.
    247   if (!IsJavascriptEnabled())
    248     return true;
    249 
    250   // Already a local page. Not calling IsLocal() because we want to distinguish
    251   // between the Google-specific and generic local NTP.
    252   if (ntp()->instant_url() == GetLocalInstantURL())
    253     return false;
    254 
    255   if (PageIsCurrent())
    256     return false;
    257 
    258   // The preloaded NTP does not support instant yet. If we're not in startup,
    259   // always fall back to the local NTP. If we are in startup, use the local NTP.
    260   return !InStartup();
    261 }
    262 
    263 void InstantNTPPrerenderer::DefaultSearchProviderChanged() {
    264   ReloadInstantNTP();
    265 }
    266 
    267 void InstantNTPPrerenderer::GoogleURLUpdated() {
    268   ReloadInstantNTP();
    269 }
    270