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