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 "chrome/browser/extensions/extension_service.h" 9 #include "chrome/browser/extensions/extension_web_ui.h" 10 #include "chrome/browser/profiles/profile.h" 11 #include "chrome/browser/search/instant_service.h" 12 #include "chrome/browser/search/instant_service_factory.h" 13 #include "chrome/browser/search/search.h" 14 #include "chrome/browser/ui/browser.h" 15 #include "chrome/browser/ui/browser_window.h" 16 #include "chrome/browser/ui/omnibox/location_bar.h" 17 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 18 #include "chrome/browser/ui/omnibox/omnibox_view.h" 19 #include "chrome/browser/ui/search/instant_ntp.h" 20 #include "chrome/browser/ui/search/instant_search_prerenderer.h" 21 #include "chrome/browser/ui/search/search_model.h" 22 #include "chrome/browser/ui/search/search_tab_helper.h" 23 #include "chrome/browser/ui/tabs/tab_strip_model.h" 24 #include "chrome/browser/ui/webui/ntp/app_launcher_handler.h" 25 #include "chrome/common/url_constants.h" 26 #include "content/public/browser/render_process_host.h" 27 #include "content/public/browser/user_metrics.h" 28 #include "content/public/browser/web_contents.h" 29 #include "content/public/browser/web_contents_view.h" 30 31 using content::UserMetricsAction; 32 33 namespace { 34 35 InstantSearchPrerenderer* GetInstantSearchPrerenderer(Profile* profile) { 36 DCHECK(profile); 37 InstantService* instant_service = 38 InstantServiceFactory::GetForProfile(profile); 39 return instant_service ? instant_service->instant_search_prerenderer() : NULL; 40 } 41 42 } // namespace 43 44 //////////////////////////////////////////////////////////////////////////////// 45 // BrowserInstantController, public: 46 47 BrowserInstantController::BrowserInstantController(Browser* browser) 48 : browser_(browser), 49 instant_(this), 50 instant_unload_handler_(browser) { 51 browser_->search_model()->AddObserver(this); 52 53 InstantService* instant_service = 54 InstantServiceFactory::GetForProfile(profile()); 55 instant_service->OnBrowserInstantControllerCreated(); 56 instant_service->AddObserver(this); 57 } 58 59 BrowserInstantController::~BrowserInstantController() { 60 browser_->search_model()->RemoveObserver(this); 61 62 InstantService* instant_service = 63 InstantServiceFactory::GetForProfile(profile()); 64 instant_service->RemoveObserver(this); 65 instant_service->OnBrowserInstantControllerDestroyed(); 66 } 67 68 bool BrowserInstantController::MaybeSwapInInstantNTPContents( 69 const GURL& url, 70 content::WebContents* source_contents, 71 content::WebContents** target_contents) { 72 if (url != GURL(chrome::kChromeUINewTabURL)) 73 return false; 74 75 GURL extension_url(url); 76 if (ExtensionWebUI::HandleChromeURLOverride(&extension_url, profile())) { 77 // If there is an extension overriding the NTP do not use the Instant NTP. 78 return false; 79 } 80 81 InstantService* instant_service = 82 InstantServiceFactory::GetForProfile(profile()); 83 scoped_ptr<content::WebContents> instant_ntp = 84 instant_service->ReleaseNTPContents(); 85 if (!instant_ntp) 86 return false; 87 88 *target_contents = instant_ntp.get(); 89 if (source_contents) { 90 // If the Instant NTP hasn't yet committed an entry, we can't call 91 // CopyStateFromAndPrune. Instead, load the Local NTP URL directly in the 92 // source contents. 93 // TODO(sreeram): Always using the local URL is wrong in the case of the 94 // first tab in a window where we might want to use the remote URL. Fix. 95 if (!instant_ntp->GetController().CanPruneAllButLastCommitted()) { 96 source_contents->GetController().LoadURL(chrome::GetLocalInstantURL( 97 profile()), content::Referrer(), content::PAGE_TRANSITION_GENERATED, 98 std::string()); 99 *target_contents = source_contents; 100 } else { 101 instant_ntp->GetController().CopyStateFromAndPrune( 102 &source_contents->GetController(), false); 103 ReplaceWebContentsAt( 104 browser_->tab_strip_model()->GetIndexOfWebContents(source_contents), 105 instant_ntp.Pass()); 106 } 107 } else { 108 // If the Instant NTP hasn't yet committed an entry, we can't call 109 // PruneAllButLastCommitted. In that case, there shouldn't be any entries 110 // to prune anyway. 111 if (instant_ntp->GetController().CanPruneAllButLastCommitted()) 112 instant_ntp->GetController().PruneAllButLastCommitted(); 113 else 114 CHECK(!instant_ntp->GetController().GetLastCommittedEntry()); 115 116 // If |source_contents| is NULL, then the caller is responsible for 117 // inserting instant_ntp into the tabstrip and will take ownership. 118 ignore_result(instant_ntp.release()); 119 } 120 return true; 121 } 122 123 bool BrowserInstantController::OpenInstant(WindowOpenDisposition disposition, 124 const GURL& url) { 125 // Unsupported dispositions. 126 if (disposition == NEW_BACKGROUND_TAB || disposition == NEW_WINDOW || 127 disposition == NEW_FOREGROUND_TAB) 128 return false; 129 130 // The omnibox currently doesn't use other dispositions, so we don't attempt 131 // to handle them. If you hit this DCHECK file a bug and I'll (sky) add 132 // support for the new disposition. 133 DCHECK(disposition == CURRENT_TAB) << disposition; 134 135 // If we will not be replacing search terms from this URL, don't send to 136 // InstantController. 137 const base::string16& search_terms = 138 chrome::GetSearchTermsFromURL(browser_->profile(), url); 139 if (search_terms.empty()) 140 return false; 141 142 InstantSearchPrerenderer* prerenderer = 143 GetInstantSearchPrerenderer(profile()); 144 if (prerenderer && 145 prerenderer->CanCommitQuery(GetActiveWebContents(), search_terms)) { 146 // Submit query to render the prefetched results. Browser will swap the 147 // prerendered contents with the active tab contents. 148 prerenderer->Commit(search_terms); 149 return false; 150 } 151 152 return instant_.SubmitQuery(search_terms); 153 } 154 155 Profile* BrowserInstantController::profile() const { 156 return browser_->profile(); 157 } 158 159 void BrowserInstantController::ReplaceWebContentsAt( 160 int index, 161 scoped_ptr<content::WebContents> new_contents) { 162 DCHECK_NE(TabStripModel::kNoTab, index); 163 scoped_ptr<content::WebContents> old_contents(browser_->tab_strip_model()-> 164 ReplaceWebContentsAt(index, new_contents.release())); 165 instant_unload_handler_.RunUnloadListenersOrDestroy(old_contents.Pass(), 166 index); 167 } 168 169 content::WebContents* BrowserInstantController::GetActiveWebContents() const { 170 return browser_->tab_strip_model()->GetActiveWebContents(); 171 } 172 173 void BrowserInstantController::ActiveTabChanged() { 174 instant_.ActiveTabChanged(); 175 } 176 177 void BrowserInstantController::TabDeactivated(content::WebContents* contents) { 178 instant_.TabDeactivated(contents); 179 180 InstantSearchPrerenderer* prerenderer = 181 GetInstantSearchPrerenderer(profile()); 182 if (prerenderer) 183 prerenderer->Cancel(); 184 } 185 186 void BrowserInstantController::SetOmniboxBounds(const gfx::Rect& bounds) { 187 instant_.SetOmniboxBounds(bounds); 188 } 189 190 //////////////////////////////////////////////////////////////////////////////// 191 // BrowserInstantController, SearchModelObserver implementation: 192 193 void BrowserInstantController::ModelChanged( 194 const SearchModel::State& old_state, 195 const SearchModel::State& new_state) { 196 if (old_state.mode != new_state.mode) { 197 const SearchMode& new_mode = new_state.mode; 198 199 // Record some actions corresponding to the mode change. Note that to get 200 // the full story, it's necessary to look at other UMA actions as well, 201 // such as tab switches. 202 if (new_mode.is_search_results()) 203 content::RecordAction(UserMetricsAction("InstantExtended.ShowSRP")); 204 else if (new_mode.is_ntp()) 205 content::RecordAction(UserMetricsAction("InstantExtended.ShowNTP")); 206 207 instant_.SearchModeChanged(old_state.mode, new_mode); 208 } 209 210 if (old_state.instant_support != new_state.instant_support) 211 instant_.InstantSupportChanged(new_state.instant_support); 212 } 213 214 //////////////////////////////////////////////////////////////////////////////// 215 // BrowserInstantController, InstantServiceObserver implementation: 216 217 void BrowserInstantController::DefaultSearchProviderChanged() { 218 ReloadTabsInInstantProcess(); 219 } 220 221 void BrowserInstantController::GoogleURLUpdated() { 222 ReloadTabsInInstantProcess(); 223 } 224 225 void BrowserInstantController::ReloadTabsInInstantProcess() { 226 InstantService* instant_service = 227 InstantServiceFactory::GetForProfile(profile()); 228 if (!instant_service) 229 return; 230 231 TabStripModel* tab_model = browser_->tab_strip_model(); 232 int count = tab_model->count(); 233 for (int index = 0; index < count; ++index) { 234 content::WebContents* contents = tab_model->GetWebContentsAt(index); 235 if (!contents) 236 continue; 237 238 // Send new search URLs to the renderer. 239 content::RenderProcessHost* rph = contents->GetRenderProcessHost(); 240 instant_service->SendSearchURLsToRenderer(rph); 241 242 // Reload the contents to ensure that it gets assigned to a non-priviledged 243 // renderer. 244 if (!instant_service->IsInstantProcess(rph->GetID())) 245 continue; 246 contents->GetController().Reload(false); 247 } 248 } 249