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
GetInstantSearchPrerenderer(Profile * profile)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
BrowserInstantController(Browser * browser)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
~BrowserInstantController()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
MaybeSwapInInstantNTPContents(const GURL & url,content::WebContents * source_contents,content::WebContents ** target_contents)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
OpenInstant(WindowOpenDisposition disposition,const GURL & url)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
profile() const155 Profile* BrowserInstantController::profile() const {
156 return browser_->profile();
157 }
158
ReplaceWebContentsAt(int index,scoped_ptr<content::WebContents> new_contents)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
GetActiveWebContents() const169 content::WebContents* BrowserInstantController::GetActiveWebContents() const {
170 return browser_->tab_strip_model()->GetActiveWebContents();
171 }
172
ActiveTabChanged()173 void BrowserInstantController::ActiveTabChanged() {
174 instant_.ActiveTabChanged();
175 }
176
TabDeactivated(content::WebContents * contents)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
SetOmniboxBounds(const gfx::Rect & bounds)186 void BrowserInstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
187 instant_.SetOmniboxBounds(bounds);
188 }
189
190 ////////////////////////////////////////////////////////////////////////////////
191 // BrowserInstantController, SearchModelObserver implementation:
192
ModelChanged(const SearchModel::State & old_state,const SearchModel::State & new_state)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
DefaultSearchProviderChanged()217 void BrowserInstantController::DefaultSearchProviderChanged() {
218 ReloadTabsInInstantProcess();
219 }
220
GoogleURLUpdated()221 void BrowserInstantController::GoogleURLUpdated() {
222 ReloadTabsInInstantProcess();
223 }
224
ReloadTabsInInstantProcess()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