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/search_tab_helper.h" 6 7 #include "build/build_config.h" 8 #include "chrome/browser/profiles/profile.h" 9 #include "chrome/browser/search/instant_service.h" 10 #include "chrome/browser/search/instant_service_factory.h" 11 #include "chrome/browser/search/search.h" 12 #include "chrome/browser/ui/browser.h" 13 #include "chrome/browser/ui/browser_finder.h" 14 #include "chrome/common/render_messages.h" 15 #include "chrome/common/url_constants.h" 16 #include "content/public/browser/navigation_controller.h" 17 #include "content/public/browser/navigation_details.h" 18 #include "content/public/browser/navigation_entry.h" 19 #include "content/public/browser/navigation_type.h" 20 #include "content/public/browser/notification_service.h" 21 #include "content/public/browser/notification_types.h" 22 #include "content/public/browser/render_process_host.h" 23 #include "content/public/browser/web_contents.h" 24 #include "grit/generated_resources.h" 25 #include "ui/base/l10n/l10n_util.h" 26 27 DEFINE_WEB_CONTENTS_USER_DATA_KEY(SearchTabHelper); 28 29 namespace { 30 31 bool IsNTP(const content::WebContents* contents) { 32 // We can't use WebContents::GetURL() because that uses the active entry, 33 // whereas we want the visible entry. 34 const content::NavigationEntry* entry = 35 contents->GetController().GetVisibleEntry(); 36 if (entry && entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL)) 37 return true; 38 39 return chrome::IsInstantNTP(contents); 40 } 41 42 bool IsSearchResults(const content::WebContents* contents) { 43 return !chrome::GetSearchTerms(contents).empty(); 44 } 45 46 // TODO(kmadhusu): Move this helper from anonymous namespace to chrome 47 // namespace and remove InstantPage::IsLocal(). 48 bool IsLocal(const content::WebContents* contents) { 49 return contents && 50 contents->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl); 51 } 52 53 // Returns true if |contents| are rendered inside an Instant process. 54 bool InInstantProcess(Profile* profile, 55 const content::WebContents* contents) { 56 InstantService* instant_service = 57 InstantServiceFactory::GetForProfile(profile); 58 return instant_service && 59 instant_service->IsInstantProcess( 60 contents->GetRenderProcessHost()->GetID()); 61 } 62 63 // Updates the location bar to reflect |contents| Instant support state. 64 void UpdateLocationBar(content::WebContents* contents) { 65 // iOS and Android doesn't use the Instant framework. 66 #if !defined(OS_IOS) && !defined(OS_ANDROID) 67 if (!contents) 68 return; 69 70 Browser* browser = chrome::FindBrowserWithWebContents(contents); 71 if (!browser) 72 return; 73 browser->OnWebContentsInstantSupportDisabled(contents); 74 #endif 75 } 76 77 } // namespace 78 79 SearchTabHelper::SearchTabHelper(content::WebContents* web_contents) 80 : WebContentsObserver(web_contents), 81 is_search_enabled_(chrome::IsInstantExtendedAPIEnabled()), 82 user_input_in_progress_(false), 83 web_contents_(web_contents) { 84 if (!is_search_enabled_) 85 return; 86 87 // Observe NOTIFICATION_NAV_ENTRY_COMMITTED events so we can reset state 88 // associated with the WebContents (such as mode, last known most visited 89 // items, instant support state etc). 90 registrar_.Add( 91 this, 92 content::NOTIFICATION_NAV_ENTRY_COMMITTED, 93 content::Source<content::NavigationController>( 94 &web_contents->GetController())); 95 } 96 97 SearchTabHelper::~SearchTabHelper() { 98 } 99 100 void SearchTabHelper::InitForPreloadedNTP() { 101 UpdateMode(true, true); 102 } 103 104 void SearchTabHelper::OmniboxEditModelChanged(bool user_input_in_progress, 105 bool cancelling) { 106 if (!is_search_enabled_) 107 return; 108 109 user_input_in_progress_ = user_input_in_progress; 110 if (!user_input_in_progress && !cancelling) 111 return; 112 113 UpdateMode(false, false); 114 } 115 116 void SearchTabHelper::NavigationEntryUpdated() { 117 if (!is_search_enabled_) 118 return; 119 120 UpdateMode(false, false); 121 } 122 123 void SearchTabHelper::InstantSupportChanged(bool instant_support) { 124 if (!is_search_enabled_) 125 return; 126 127 InstantSupportState new_state = instant_support ? INSTANT_SUPPORT_YES : 128 INSTANT_SUPPORT_NO; 129 130 model_.SetInstantSupportState(new_state); 131 132 content::NavigationEntry* entry = 133 web_contents_->GetController().GetVisibleEntry(); 134 if (entry) { 135 chrome::SetInstantSupportStateInNavigationEntry(new_state, entry); 136 if (!instant_support) 137 UpdateLocationBar(web_contents_); 138 } 139 } 140 141 bool SearchTabHelper::SupportsInstant() const { 142 return model_.instant_support() == INSTANT_SUPPORT_YES; 143 } 144 145 void SearchTabHelper::Observe( 146 int type, 147 const content::NotificationSource& source, 148 const content::NotificationDetails& details) { 149 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); 150 content::LoadCommittedDetails* load_details = 151 content::Details<content::LoadCommittedDetails>(details).ptr(); 152 if (!load_details->is_main_frame) 153 return; 154 155 UpdateMode(true, false); 156 157 content::NavigationEntry* entry = 158 web_contents_->GetController().GetVisibleEntry(); 159 DCHECK(entry); 160 161 // Already determined the instant support state for this page, do not reset 162 // the instant support state. 163 // 164 // When we get a navigation entry committed event, there seem to be two ways 165 // to tell whether the navigation was "in-page". Ideally, when 166 // LoadCommittedDetails::is_in_page is true, we should have 167 // LoadCommittedDetails::type to be NAVIGATION_TYPE_IN_PAGE. Unfortunately, 168 // they are different in some cases. To workaround this bug, we are checking 169 // (is_in_page || type == NAVIGATION_TYPE_IN_PAGE). Please refer to 170 // crbug.com/251330 for more details. 171 if (load_details->is_in_page || 172 load_details->type == content::NAVIGATION_TYPE_IN_PAGE) { 173 // When an "in-page" navigation happens, we will not receive a 174 // DidFinishLoad() event. Therefore, we will not determine the Instant 175 // support for the navigated page. So, copy over the Instant support from 176 // the previous entry. If the page does not support Instant, update the 177 // location bar from here to turn off search terms replacement. 178 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(), 179 entry); 180 if (model_.instant_support() == INSTANT_SUPPORT_NO) 181 UpdateLocationBar(web_contents_); 182 return; 183 } 184 185 model_.SetInstantSupportState(INSTANT_SUPPORT_UNKNOWN); 186 model_.SetVoiceSearchSupported(false); 187 chrome::SetInstantSupportStateInNavigationEntry(model_.instant_support(), 188 entry); 189 } 190 191 void SearchTabHelper::DidNavigateMainFrame( 192 const content::LoadCommittedDetails& details, 193 const content::FrameNavigateParams& params) { 194 // Always set the title on the new tab page to be the one from our UI 195 // resources. Normally, we set the title when we begin a NTP load, but it 196 // can get reset in several places (like when you press Reload). This check 197 // ensures that the title is properly set to the string defined by the Chrome 198 // UI language (rather than the server language) in all cases. 199 // 200 // We only override the title when it's nonempty to allow the page to set the 201 // title if it really wants. An empty title means to use the default. There's 202 // also a race condition between this code and the page's SetTitle call which 203 // this rule avoids. 204 content::NavigationEntry* entry = 205 web_contents_->GetController().GetActiveEntry(); 206 if (entry && entry->GetTitle().empty() && 207 (entry->GetVirtualURL() == GURL(chrome::kChromeUINewTabURL) || 208 chrome::NavEntryIsInstantNTP(web_contents_, entry))) { 209 entry->SetTitle(l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE)); 210 } 211 } 212 213 bool SearchTabHelper::OnMessageReceived(const IPC::Message& message) { 214 bool handled = true; 215 IPC_BEGIN_MESSAGE_MAP(SearchTabHelper, message) 216 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_InstantSupportDetermined, 217 OnInstantSupportDetermined) 218 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetVoiceSearchSupported, 219 OnSetVoiceSearchSupported) 220 IPC_MESSAGE_UNHANDLED(handled = false) 221 IPC_END_MESSAGE_MAP() 222 return handled; 223 } 224 225 void SearchTabHelper::DidFinishLoad( 226 int64 /* frame_id */, 227 const GURL& /* validated_url */, 228 bool is_main_frame, 229 content::RenderViewHost* /* render_view_host */) { 230 if (is_main_frame) 231 DetermineIfPageSupportsInstant(); 232 } 233 234 void SearchTabHelper::UpdateMode(bool update_origin, bool is_preloaded_ntp) { 235 SearchMode::Type type = SearchMode::MODE_DEFAULT; 236 SearchMode::Origin origin = SearchMode::ORIGIN_DEFAULT; 237 if (IsNTP(web_contents_) || is_preloaded_ntp) { 238 type = SearchMode::MODE_NTP; 239 origin = SearchMode::ORIGIN_NTP; 240 } else if (IsSearchResults(web_contents_)) { 241 type = SearchMode::MODE_SEARCH_RESULTS; 242 origin = SearchMode::ORIGIN_SEARCH; 243 } 244 if (!update_origin) 245 origin = model_.mode().origin; 246 if (user_input_in_progress_) 247 type = SearchMode::MODE_SEARCH_SUGGESTIONS; 248 model_.SetMode(SearchMode(type, origin)); 249 } 250 251 void SearchTabHelper::DetermineIfPageSupportsInstant() { 252 Profile* profile = 253 Profile::FromBrowserContext(web_contents_->GetBrowserContext()); 254 if (!InInstantProcess(profile, web_contents_)) { 255 // The page is not in the Instant process. This page does not support 256 // instant. If we send an IPC message to a page that is not in the Instant 257 // process, it will never receive it and will never respond. Therefore, 258 // return immediately. 259 InstantSupportChanged(false); 260 } else if (IsLocal(web_contents_)) { 261 // Local pages always support Instant. 262 InstantSupportChanged(true); 263 } else { 264 Send(new ChromeViewMsg_DetermineIfPageSupportsInstant(routing_id())); 265 } 266 } 267 268 void SearchTabHelper::OnInstantSupportDetermined(int page_id, 269 bool instant_support) { 270 if (!web_contents()->IsActiveEntry(page_id)) 271 return; 272 273 InstantSupportChanged(instant_support); 274 } 275 276 void SearchTabHelper::OnSetVoiceSearchSupported(int page_id, bool supported) { 277 if (web_contents()->IsActiveEntry(page_id)) 278 model_.SetVoiceSearchSupported(supported); 279 } 280