1 // Copyright 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/search/instant_controller.h" 6 7 #include "base/prefs/pref_service.h" 8 #include "base/strings/stringprintf.h" 9 #include "chrome/browser/chrome_notification_types.h" 10 #include "chrome/browser/content_settings/content_settings_provider.h" 11 #include "chrome/browser/content_settings/host_content_settings_map.h" 12 #include "chrome/browser/platform_util.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/search/instant_service.h" 15 #include "chrome/browser/search/instant_service_factory.h" 16 #include "chrome/browser/search/search.h" 17 #include "chrome/browser/search_engines/search_terms_data.h" 18 #include "chrome/browser/search_engines/template_url_service.h" 19 #include "chrome/browser/search_engines/template_url_service_factory.h" 20 #include "chrome/browser/ui/browser_instant_controller.h" 21 #include "chrome/browser/ui/search/instant_search_prerenderer.h" 22 #include "chrome/browser/ui/search/instant_tab.h" 23 #include "chrome/browser/ui/search/search_tab_helper.h" 24 #include "chrome/common/chrome_switches.h" 25 #include "chrome/common/content_settings_types.h" 26 #include "chrome/common/pref_names.h" 27 #include "chrome/common/search_urls.h" 28 #include "chrome/common/url_constants.h" 29 #include "components/sessions/serialized_navigation_entry.h" 30 #include "content/public/browser/navigation_entry.h" 31 #include "content/public/browser/notification_service.h" 32 #include "content/public/browser/render_process_host.h" 33 #include "content/public/browser/render_widget_host_view.h" 34 #include "content/public/browser/web_contents.h" 35 #include "content/public/browser/web_contents_view.h" 36 #include "net/base/escape.h" 37 #include "net/base/network_change_notifier.h" 38 #include "url/gurl.h" 39 40 #if defined(TOOLKIT_VIEWS) 41 #include "ui/views/widget/widget.h" 42 #endif 43 44 namespace { 45 46 bool IsContentsFrom(const InstantPage* page, 47 const content::WebContents* contents) { 48 return page && (page->contents() == contents); 49 } 50 51 // Adds a transient NavigationEntry to the supplied |contents|'s 52 // NavigationController if the page's URL has not already been updated with the 53 // supplied |search_terms|. Sets the |search_terms| on the transient entry for 54 // search terms extraction to work correctly. 55 void EnsureSearchTermsAreSet(content::WebContents* contents, 56 const base::string16& search_terms) { 57 content::NavigationController* controller = &contents->GetController(); 58 59 // If search terms are already correct or there is already a transient entry 60 // (there shouldn't be), bail out early. 61 if (chrome::GetSearchTerms(contents) == search_terms || 62 controller->GetTransientEntry()) 63 return; 64 65 const content::NavigationEntry* entry = controller->GetLastCommittedEntry(); 66 content::NavigationEntry* transient = controller->CreateNavigationEntry( 67 entry->GetURL(), 68 entry->GetReferrer(), 69 entry->GetTransitionType(), 70 false, 71 std::string(), 72 contents->GetBrowserContext()); 73 transient->SetExtraData(sessions::kSearchTermsKey, search_terms); 74 controller->SetTransientEntry(transient); 75 76 SearchTabHelper::FromWebContents(contents)->NavigationEntryUpdated(); 77 } 78 79 } // namespace 80 81 InstantController::InstantController(BrowserInstantController* browser) 82 : browser_(browser), 83 omnibox_focus_state_(OMNIBOX_FOCUS_NONE), 84 omnibox_focus_change_reason_(OMNIBOX_FOCUS_CHANGE_EXPLICIT), 85 omnibox_bounds_(-1, -1, 0, 0) { 86 } 87 88 InstantController::~InstantController() { 89 } 90 91 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { 92 if (omnibox_bounds_ == bounds) 93 return; 94 95 omnibox_bounds_ = bounds; 96 if (instant_tab_) 97 instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_); 98 } 99 100 void InstantController::SetSuggestionToPrefetch( 101 const InstantSuggestion& suggestion) { 102 if (instant_tab_ && 103 SearchTabHelper::FromWebContents(instant_tab_->contents())-> 104 IsSearchResultsPage()) { 105 if (chrome::ShouldPrefetchSearchResultsOnSRP() || 106 chrome::ShouldPrefetchSearchResults()) { 107 SearchTabHelper::FromWebContents(instant_tab_->contents())-> 108 SetSuggestionToPrefetch(suggestion); 109 } 110 } else { 111 if (chrome::ShouldPrefetchSearchResults()) { 112 InstantSearchPrerenderer* prerenderer = 113 InstantSearchPrerenderer::GetForProfile(profile()); 114 if (prerenderer) 115 prerenderer->Prerender(suggestion); 116 } 117 } 118 } 119 120 void InstantController::InstantPageLoadFailed(content::WebContents* contents) { 121 DCHECK(IsContentsFrom(instant_tab(), contents)); 122 123 // Verify we're not already on a local page and that the URL precisely 124 // equals the instant_url (minus the query params, as those will be filled 125 // in by template values). This check is necessary to make sure we don't 126 // inadvertently redirect to the local NTP if someone, say, reloads a SRP 127 // while offline, as a committed results page still counts as an instant 128 // url. We also check to make sure there's no forward history, as if 129 // someone hits the back button a lot when offline and returns to a NTP 130 // we don't want to redirect and nuke their forward history stack. 131 const GURL& current_url = contents->GetURL(); 132 GURL instant_url = chrome::GetInstantURL(profile(), 133 chrome::kDisableStartMargin, false); 134 if (instant_tab_->IsLocal() || 135 !search::MatchesOriginAndPath(instant_url, current_url) || 136 !current_url.ref().empty() || 137 contents->GetController().CanGoForward()) 138 return; 139 LOG_INSTANT_DEBUG_EVENT(this, "InstantPageLoadFailed: instant_tab"); 140 RedirectToLocalNTP(contents); 141 } 142 143 bool InstantController::SubmitQuery(const base::string16& search_terms) { 144 if (instant_tab_ && instant_tab_->supports_instant() && 145 search_mode_.is_origin_search()) { 146 // Use |instant_tab_| to run the query if we're already on a search results 147 // page. (NOTE: in particular, we do not send the query to NTPs.) 148 SearchTabHelper::FromWebContents(instant_tab_->contents())->Submit( 149 search_terms); 150 instant_tab_->contents()->GetView()->Focus(); 151 EnsureSearchTermsAreSet(instant_tab_->contents(), search_terms); 152 return true; 153 } 154 return false; 155 } 156 157 void InstantController::OmniboxFocusChanged( 158 OmniboxFocusState state, 159 OmniboxFocusChangeReason reason, 160 gfx::NativeView view_gaining_focus) { 161 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( 162 "OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_, 163 state, reason)); 164 165 omnibox_focus_state_ = state; 166 if (!instant_tab_) 167 return; 168 169 content::NotificationService::current()->Notify( 170 chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED, 171 content::Source<InstantController>(this), 172 content::NotificationService::NoDetails()); 173 174 instant_tab_->sender()->FocusChanged(omnibox_focus_state_, reason); 175 // Don't send oninputstart/oninputend updates in response to focus changes 176 // if there's a navigation in progress. This prevents Chrome from sending 177 // a spurious oninputend when the user accepts a match in the omnibox. 178 if (instant_tab_->contents()->GetController().GetPendingEntry() == NULL) 179 instant_tab_->sender()->SetInputInProgress(IsInputInProgress()); 180 } 181 182 void InstantController::SearchModeChanged(const SearchMode& old_mode, 183 const SearchMode& new_mode) { 184 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( 185 "SearchModeChanged: [origin:mode] %d:%d to %d:%d", old_mode.origin, 186 old_mode.mode, new_mode.origin, new_mode.mode)); 187 188 search_mode_ = new_mode; 189 ResetInstantTab(); 190 191 if (instant_tab_ && old_mode.is_ntp() != new_mode.is_ntp()) 192 instant_tab_->sender()->SetInputInProgress(IsInputInProgress()); 193 } 194 195 void InstantController::ActiveTabChanged() { 196 LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged"); 197 ResetInstantTab(); 198 } 199 200 void InstantController::TabDeactivated(content::WebContents* contents) { 201 // If user is deactivating an NTP tab, log the number of mouseovers for this 202 // NTP session. 203 if (chrome::IsInstantNTP(contents)) 204 InstantTab::EmitNtpStatistics(contents); 205 } 206 207 void InstantController::LogDebugEvent(const std::string& info) const { 208 DVLOG(1) << info; 209 210 debug_events_.push_front(std::make_pair( 211 base::Time::Now().ToInternalValue(), info)); 212 static const size_t kMaxDebugEventSize = 2000; 213 if (debug_events_.size() > kMaxDebugEventSize) 214 debug_events_.pop_back(); 215 } 216 217 void InstantController::ClearDebugEvents() { 218 debug_events_.clear(); 219 } 220 221 Profile* InstantController::profile() const { 222 return browser_->profile(); 223 } 224 225 InstantTab* InstantController::instant_tab() const { 226 return instant_tab_.get(); 227 } 228 229 void InstantController::InstantSupportChanged( 230 InstantSupportState instant_support) { 231 // Handle INSTANT_SUPPORT_YES here because InstantPage is not hooked up to the 232 // active tab. Search model changed listener in InstantPage will handle other 233 // cases. 234 if (instant_support != INSTANT_SUPPORT_YES) 235 return; 236 237 ResetInstantTab(); 238 } 239 240 void InstantController::InstantSupportDetermined( 241 const content::WebContents* contents, 242 bool supports_instant) { 243 DCHECK(IsContentsFrom(instant_tab(), contents)); 244 245 if (!supports_instant) 246 base::MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release()); 247 248 content::NotificationService::current()->Notify( 249 chrome::NOTIFICATION_INSTANT_TAB_SUPPORT_DETERMINED, 250 content::Source<InstantController>(this), 251 content::NotificationService::NoDetails()); 252 } 253 254 void InstantController::InstantPageAboutToNavigateMainFrame( 255 const content::WebContents* contents, 256 const GURL& url) { 257 DCHECK(IsContentsFrom(instant_tab(), contents)); 258 259 // The Instant tab navigated. Send it the data it needs to display 260 // properly. 261 UpdateInfoForInstantTab(); 262 } 263 264 void InstantController::ResetInstantTab() { 265 if (!search_mode_.is_origin_default()) { 266 content::WebContents* active_tab = browser_->GetActiveWebContents(); 267 if (!instant_tab_ || active_tab != instant_tab_->contents()) { 268 instant_tab_.reset(new InstantTab(this, browser_->profile())); 269 instant_tab_->Init(active_tab); 270 UpdateInfoForInstantTab(); 271 } 272 } else { 273 instant_tab_.reset(); 274 } 275 } 276 277 void InstantController::UpdateInfoForInstantTab() { 278 if (instant_tab_) { 279 instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_); 280 281 // Update theme details. 282 InstantService* instant_service = GetInstantService(); 283 if (instant_service) { 284 instant_service->UpdateThemeInfo(); 285 instant_service->UpdateMostVisitedItemsInfo(); 286 } 287 288 instant_tab_->sender()->FocusChanged(omnibox_focus_state_, 289 omnibox_focus_change_reason_); 290 instant_tab_->sender()->SetInputInProgress(IsInputInProgress()); 291 } 292 } 293 294 bool InstantController::IsInputInProgress() const { 295 return !search_mode_.is_ntp() && 296 omnibox_focus_state_ == OMNIBOX_FOCUS_VISIBLE; 297 } 298 299 void InstantController::RedirectToLocalNTP(content::WebContents* contents) { 300 contents->GetController().LoadURL( 301 GURL(chrome::kChromeSearchLocalNtpUrl), 302 content::Referrer(), 303 content::PAGE_TRANSITION_SERVER_REDIRECT, 304 std::string()); // No extra headers. 305 // TODO(dcblack): Remove extraneous history entry caused by 404s. 306 // Note that the base case of a 204 being returned doesn't push a history 307 // entry. 308 } 309 310 InstantService* InstantController::GetInstantService() const { 311 return InstantServiceFactory::GetForProfile(profile()); 312 } 313