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/browser_instant_controller.h" 6 7#include "base/bind.h" 8#include "base/prefs/pref_service.h" 9#include "chrome/browser/extensions/extension_service.h" 10#include "chrome/browser/extensions/extension_web_ui.h" 11#include "chrome/browser/profiles/profile.h" 12#include "chrome/browser/search/instant_service.h" 13#include "chrome/browser/search/instant_service_factory.h" 14#include "chrome/browser/search/search.h" 15#include "chrome/browser/search_engines/template_url.h" 16#include "chrome/browser/search_engines/template_url_service.h" 17#include "chrome/browser/search_engines/template_url_service_factory.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_window.h" 20#include "chrome/browser/ui/omnibox/location_bar.h" 21#include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 22#include "chrome/browser/ui/omnibox/omnibox_view.h" 23#include "chrome/browser/ui/search/instant_ntp.h" 24#include "chrome/browser/ui/search/search_model.h" 25#include "chrome/browser/ui/search/search_tab_helper.h" 26#include "chrome/browser/ui/tabs/tab_strip_model.h" 27#include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" 28#include "chrome/common/pref_names.h" 29#include "chrome/common/url_constants.h" 30#include "components/user_prefs/pref_registry_syncable.h" 31#include "content/public/browser/render_process_host.h" 32#include "content/public/browser/user_metrics.h" 33#include "content/public/browser/web_contents.h" 34#include "content/public/browser/web_contents_view.h" 35 36using content::UserMetricsAction; 37 38//////////////////////////////////////////////////////////////////////////////// 39// BrowserInstantController, public: 40 41BrowserInstantController::BrowserInstantController(Browser* browser) 42 : browser_(browser), 43 instant_(this), 44 instant_unload_handler_(browser) { 45 profile_pref_registrar_.Init(profile()->GetPrefs()); 46 profile_pref_registrar_.Add( 47 prefs::kDefaultSearchProviderID, 48 base::Bind(&BrowserInstantController::OnDefaultSearchProviderChanged, 49 base::Unretained(this))); 50 browser_->search_model()->AddObserver(this); 51 52 InstantService* instant_service = 53 InstantServiceFactory::GetForProfile(profile()); 54 instant_service->OnBrowserInstantControllerCreated(); 55} 56 57BrowserInstantController::~BrowserInstantController() { 58 browser_->search_model()->RemoveObserver(this); 59 60 InstantService* instant_service = 61 InstantServiceFactory::GetForProfile(profile()); 62 instant_service->OnBrowserInstantControllerDestroyed(); 63} 64 65bool BrowserInstantController::MaybeSwapInInstantNTPContents( 66 const GURL& url, 67 content::WebContents* source_contents, 68 content::WebContents** target_contents) { 69 if (url != GURL(chrome::kChromeUINewTabURL)) 70 return false; 71 72 GURL extension_url(url); 73 if (ExtensionWebUI::HandleChromeURLOverride(&extension_url, profile())) { 74 // If there is an extension overriding the NTP do not use the Instant NTP. 75 return false; 76 } 77 78 InstantService* instant_service = 79 InstantServiceFactory::GetForProfile(profile()); 80 scoped_ptr<content::WebContents> instant_ntp = 81 instant_service->ReleaseNTPContents(); 82 if (!instant_ntp) 83 return false; 84 85 *target_contents = instant_ntp.get(); 86 if (source_contents) { 87 // If the Instant NTP hasn't yet committed an entry, we can't call 88 // CopyStateFromAndPrune. Instead, load the Local NTP URL directly in the 89 // source contents. 90 // TODO(sreeram): Always using the local URL is wrong in the case of the 91 // first tab in a window where we might want to use the remote URL. Fix. 92 if (!instant_ntp->GetController().CanPruneAllButVisible()) { 93 source_contents->GetController().LoadURL(chrome::GetLocalInstantURL( 94 profile()), content::Referrer(), content::PAGE_TRANSITION_GENERATED, 95 std::string()); 96 *target_contents = source_contents; 97 } else { 98 instant_ntp->GetController().CopyStateFromAndPrune( 99 &source_contents->GetController()); 100 ReplaceWebContentsAt( 101 browser_->tab_strip_model()->GetIndexOfWebContents(source_contents), 102 instant_ntp.Pass()); 103 } 104 } else { 105 // If the Instant NTP hasn't yet committed an entry, we can't call 106 // PruneAllButVisible. In that case, there shouldn't be any entries to 107 // prune anyway. 108 if (instant_ntp->GetController().CanPruneAllButVisible()) 109 instant_ntp->GetController().PruneAllButVisible(); 110 else 111 CHECK(!instant_ntp->GetController().GetLastCommittedEntry()); 112 113 // If |source_contents| is NULL, then the caller is responsible for 114 // inserting instant_ntp into the tabstrip and will take ownership. 115 ignore_result(instant_ntp.release()); 116 } 117 return true; 118} 119 120bool BrowserInstantController::OpenInstant(WindowOpenDisposition disposition, 121 const GURL& url) { 122 // Unsupported dispositions. 123 if (disposition == NEW_BACKGROUND_TAB || disposition == NEW_WINDOW || 124 disposition == NEW_FOREGROUND_TAB) 125 return false; 126 127 // The omnibox currently doesn't use other dispositions, so we don't attempt 128 // to handle them. If you hit this DCHECK file a bug and I'll (sky) add 129 // support for the new disposition. 130 DCHECK(disposition == CURRENT_TAB) << disposition; 131 132 // If we will not be replacing search terms from this URL, don't send to 133 // InstantController. 134 const string16& search_terms = 135 chrome::GetSearchTermsFromURL(browser_->profile(), url); 136 if (search_terms.empty()) 137 return false; 138 139 return instant_.SubmitQuery(search_terms); 140} 141 142Profile* BrowserInstantController::profile() const { 143 return browser_->profile(); 144} 145 146void BrowserInstantController::ReplaceWebContentsAt( 147 int index, 148 scoped_ptr<content::WebContents> new_contents) { 149 DCHECK_NE(TabStripModel::kNoTab, index); 150 scoped_ptr<content::WebContents> old_contents(browser_->tab_strip_model()-> 151 ReplaceWebContentsAt(index, new_contents.release())); 152 instant_unload_handler_.RunUnloadListenersOrDestroy(old_contents.Pass(), 153 index); 154} 155 156void BrowserInstantController::FocusOmnibox(OmniboxFocusState state) { 157 OmniboxView* omnibox_view = browser_->window()->GetLocationBar()-> 158 GetLocationEntry(); 159 160 // Do not add a default case in the switch block for the following reasons: 161 // (1) Explicitly handle the new states. If new states are added in the 162 // OmniboxFocusState, the compiler will warn the developer to handle the new 163 // states. 164 // (2) An attacker may control the renderer and sends the browser process a 165 // malformed IPC. This function responds to the invalid |state| values by 166 // doing nothing instead of crashing the browser process (intentional no-op). 167 switch (state) { 168 case OMNIBOX_FOCUS_VISIBLE: 169 omnibox_view->SetFocus(); 170 omnibox_view->model()->SetCaretVisibility(true); 171 break; 172 case OMNIBOX_FOCUS_INVISIBLE: 173 omnibox_view->SetFocus(); 174 omnibox_view->model()->SetCaretVisibility(false); 175 // If the user clicked on the fakebox, any text already in the omnibox 176 // should get cleared when they start typing. Selecting all the existing 177 // text is a convenient way to accomplish this. It also gives a slight 178 // visual cue to users who really understand selection state about what 179 // will happen if they start typing. 180 omnibox_view->SelectAll(false); 181 break; 182 case OMNIBOX_FOCUS_NONE: 183 // Remove focus only if the popup is closed. This will prevent someone 184 // from changing the omnibox value and closing the popup without user 185 // interaction. 186 if (!omnibox_view->model()->popup_model()->IsOpen()) { 187 content::WebContents* contents = GetActiveWebContents(); 188 if (contents) 189 contents->GetView()->Focus(); 190 } 191 break; 192 } 193} 194 195content::WebContents* BrowserInstantController::GetActiveWebContents() const { 196 return browser_->tab_strip_model()->GetActiveWebContents(); 197} 198 199void BrowserInstantController::ActiveTabChanged() { 200 instant_.ActiveTabChanged(); 201} 202 203void BrowserInstantController::TabDeactivated(content::WebContents* contents) { 204 instant_.TabDeactivated(contents); 205} 206 207void BrowserInstantController::OpenURL( 208 const GURL& url, 209 content::PageTransition transition, 210 WindowOpenDisposition disposition) { 211 browser_->OpenURL(content::OpenURLParams(url, 212 content::Referrer(), 213 disposition, 214 transition, 215 false)); 216} 217 218void BrowserInstantController::PasteIntoOmnibox(const string16& text) { 219 OmniboxView* omnibox_view = browser_->window()->GetLocationBar()-> 220 GetLocationEntry(); 221 // The first case is for right click to paste, where the text is retrieved 222 // from the clipboard already sanitized. The second case is needed to handle 223 // drag-and-drop value and it has to be sanitazed before setting it into the 224 // omnibox. 225 string16 text_to_paste = text.empty() ? 226 omnibox_view->GetClipboardText() : 227 omnibox_view->SanitizeTextForPaste(text); 228 229 if (!text_to_paste.empty()) { 230 if (!omnibox_view->model()->has_focus()) 231 omnibox_view->SetFocus(); 232 omnibox_view->OnBeforePossibleChange(); 233 omnibox_view->model()->on_paste(); 234 omnibox_view->SetUserText(text_to_paste); 235 omnibox_view->OnAfterPossibleChange(); 236 } 237} 238 239void BrowserInstantController::SetOmniboxBounds(const gfx::Rect& bounds) { 240 instant_.SetOmniboxBounds(bounds); 241} 242 243void BrowserInstantController::ToggleVoiceSearch() { 244 instant_.ToggleVoiceSearch(); 245} 246 247//////////////////////////////////////////////////////////////////////////////// 248// BrowserInstantController, SearchModelObserver implementation: 249 250void BrowserInstantController::ModelChanged( 251 const SearchModel::State& old_state, 252 const SearchModel::State& new_state) { 253 if (old_state.mode != new_state.mode) { 254 const SearchMode& new_mode = new_state.mode; 255 256 // Record some actions corresponding to the mode change. Note that to get 257 // the full story, it's necessary to look at other UMA actions as well, 258 // such as tab switches. 259 if (new_mode.is_search_results()) 260 content::RecordAction(UserMetricsAction("InstantExtended.ShowSRP")); 261 else if (new_mode.is_ntp()) 262 content::RecordAction(UserMetricsAction("InstantExtended.ShowNTP")); 263 264 instant_.SearchModeChanged(old_state.mode, new_mode); 265 } 266 267 if (old_state.instant_support != new_state.instant_support) 268 instant_.InstantSupportChanged(new_state.instant_support); 269} 270 271void BrowserInstantController::OnDefaultSearchProviderChanged( 272 const std::string& pref_name) { 273 DCHECK_EQ(pref_name, std::string(prefs::kDefaultSearchProviderID)); 274 275 Profile* browser_profile = profile(); 276 const TemplateURL* template_url = 277 TemplateURLServiceFactory::GetForProfile(browser_profile)-> 278 GetDefaultSearchProvider(); 279 if (!template_url) { 280 // A NULL |template_url| could mean either this notification is sent during 281 // the browser start up operation or the user now has no default search 282 // provider. There is no way for the user to reach this state using the 283 // Chrome settings. Only explicitly poking at the DB or bugs in the Sync 284 // could cause that, neither of which we support. 285 return; 286 } 287 288 InstantService* instant_service = 289 InstantServiceFactory::GetForProfile(browser_profile); 290 if (!instant_service) 291 return; 292 293 TabStripModel* tab_model = browser_->tab_strip_model(); 294 int count = tab_model->count(); 295 for (int index = 0; index < count; ++index) { 296 content::WebContents* contents = tab_model->GetWebContentsAt(index); 297 if (!contents) 298 continue; 299 300 if (!instant_service->IsInstantProcess( 301 contents->GetRenderProcessHost()->GetID())) 302 continue; 303 304 // Reload the contents to ensure that it gets assigned to a non-priviledged 305 // renderer. 306 contents->GetController().Reload(false); 307 } 308} 309