OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/instant/instant_controller.h" | 5 #include "chrome/browser/instant/instant_controller.h" |
6 | 6 |
| 7 #include <string> |
| 8 |
7 #include "base/bind.h" | 9 #include "base/bind.h" |
8 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 11 #include "base/i18n/case_conversion.h" |
9 #include "base/message_loop.h" | 12 #include "base/message_loop.h" |
10 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
| 14 #include "base/string16.h" |
11 #include "build/build_config.h" | 15 #include "build/build_config.h" |
12 #include "chrome/browser/autocomplete/autocomplete_match.h" | 16 #include "chrome/browser/autocomplete/autocomplete_match.h" |
13 #include "chrome/browser/instant/instant_controller_delegate.h" | 17 #include "chrome/browser/instant/instant_controller_delegate.h" |
14 #include "chrome/browser/instant/instant_loader.h" | 18 #include "chrome/browser/instant/instant_loader.h" |
15 #include "chrome/browser/platform_util.h" | 19 #include "chrome/browser/platform_util.h" |
16 #include "chrome/browser/prefs/pref_service.h" | 20 #include "chrome/browser/prefs/pref_service.h" |
17 #include "chrome/browser/profiles/profile.h" | 21 #include "chrome/browser/profiles/profile.h" |
18 #include "chrome/browser/search_engines/template_url.h" | 22 #include "chrome/browser/search_engines/template_url.h" |
19 #include "chrome/browser/search_engines/template_url_service.h" | 23 #include "chrome/browser/search_engines/template_url_service.h" |
20 #include "chrome/browser/search_engines/template_url_service_factory.h" | 24 #include "chrome/browser/search_engines/template_url_service_factory.h" |
21 #include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h" | 25 #include "chrome/browser/ui/search/search_model.h" |
| 26 #include "chrome/browser/ui/search/search_tab_helper.h" |
| 27 #include "chrome/browser/ui/search/search_types.h" |
22 #include "chrome/browser/ui/tab_contents/tab_contents.h" | 28 #include "chrome/browser/ui/tab_contents/tab_contents.h" |
23 #include "chrome/common/chrome_notification_types.h" | 29 #include "chrome/common/chrome_notification_types.h" |
24 #include "chrome/common/chrome_switches.h" | 30 #include "chrome/common/chrome_switches.h" |
25 #include "chrome/common/pref_names.h" | 31 #include "chrome/common/pref_names.h" |
26 #include "content/public/browser/notification_service.h" | 32 #include "content/public/browser/notification_service.h" |
27 #include "content/public/browser/render_widget_host_view.h" | 33 #include "content/public/browser/render_widget_host_view.h" |
28 #include "content/public/browser/web_contents.h" | 34 #include "content/public/browser/web_contents.h" |
29 | 35 |
30 #if defined(TOOLKIT_VIEWS) | 36 #if defined(TOOLKIT_VIEWS) |
31 #include "ui/views/focus/focus_manager.h" | 37 #include "ui/views/focus/focus_manager.h" |
32 #include "ui/views/view.h" | 38 #include "ui/views/view.h" |
33 #include "ui/views/widget/widget.h" | 39 #include "ui/views/widget/widget.h" |
34 #endif | 40 #endif |
35 | 41 |
| 42 namespace { |
| 43 |
| 44 enum PreviewUsageType { |
| 45 PREVIEW_CREATED = 0, |
| 46 PREVIEW_DELETED, |
| 47 PREVIEW_LOADED, |
| 48 PREVIEW_SHOWED, |
| 49 PREVIEW_COMMITTED, |
| 50 PREVIEW_NUM_TYPES, |
| 51 }; |
| 52 |
| 53 // An artificial delay (in milliseconds) we introduce before telling the Instant |
| 54 // page about the new omnibox bounds, in cases where the bounds shrink. This is |
| 55 // to avoid the page jumping up/down very fast in response to bounds changes. |
| 56 const int kUpdateBoundsDelayMS = 1000; |
| 57 |
| 58 // The maximum number of times we'll load a non-Instant-supporting search engine |
| 59 // before we give up and blacklist it for the rest of the browsing session. |
| 60 const int kMaxInstantSupportFailures = 10; |
| 61 |
| 62 std::string ModeToString(InstantController::Mode mode) { |
| 63 switch (mode) { |
| 64 case InstantController::INSTANT: return "Instant"; |
| 65 case InstantController::SUGGEST: return "Suggest"; |
| 66 case InstantController::HIDDEN: return "Hidden"; |
| 67 case InstantController::SILENT: return "Silent"; |
| 68 } |
| 69 |
| 70 NOTREACHED(); |
| 71 return std::string(); |
| 72 } |
| 73 |
| 74 void AddPreviewUsageForHistogram(InstantController::Mode mode, |
| 75 PreviewUsageType usage) { |
| 76 DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage; |
| 77 base::Histogram* histogram = base::LinearHistogram::FactoryGet( |
| 78 "Instant.Previews." + ModeToString(mode), 1, PREVIEW_NUM_TYPES, |
| 79 PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| 80 histogram->Add(usage); |
| 81 } |
| 82 |
| 83 } // namespace |
| 84 |
36 InstantController::InstantController(InstantControllerDelegate* delegate, | 85 InstantController::InstantController(InstantControllerDelegate* delegate, |
37 Mode mode) | 86 Mode mode) |
38 : delegate_(delegate), | 87 : delegate_(delegate), |
39 is_displayable_(false), | 88 mode_(mode), |
40 is_out_of_date_(true), | 89 last_tab_contents_(NULL), |
41 commit_on_pointer_release_(false), | 90 last_verbatim_(false), |
42 last_transition_type_(content::PAGE_TRANSITION_LINK), | 91 last_complete_behavior_(INSTANT_COMPLETE_NOW), |
43 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | 92 is_showing_(false), |
44 mode_(mode) { | 93 loader_processed_last_update_(false) { |
45 DCHECK(mode_ == INSTANT || mode_ == SUGGEST || mode_ == HIDDEN || | |
46 mode_ == SILENT); | |
47 } | 94 } |
48 | 95 |
49 InstantController::~InstantController() { | 96 InstantController::~InstantController() { |
| 97 // We do this explicitly for two reasons: To Hide() if we are showing the |
| 98 // preview, and for proper accounting of PREVIEW_DELETED in histograms. |
| 99 DeleteLoader(); |
50 } | 100 } |
51 | 101 |
52 // static | 102 // static |
53 void InstantController::RegisterUserPrefs(PrefService* prefs) { | 103 void InstantController::RegisterUserPrefs(PrefService* prefs) { |
54 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, | 104 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false, |
55 false, | |
56 PrefService::SYNCABLE_PREF); | 105 PrefService::SYNCABLE_PREF); |
57 prefs->RegisterBooleanPref(prefs::kInstantEnabled, | 106 prefs->RegisterBooleanPref(prefs::kInstantEnabled, false, |
58 false, | |
59 PrefService::SYNCABLE_PREF); | 107 PrefService::SYNCABLE_PREF); |
60 | 108 |
61 // TODO(jamescook): Move this to search controller. | 109 // TODO(jamescook): Move this to search controller. |
62 prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, | 110 prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, |
63 1.0, | 111 1.0, |
64 PrefService::UNSYNCABLE_PREF); | 112 PrefService::UNSYNCABLE_PREF); |
65 } | 113 } |
66 | 114 |
67 // static | 115 // static |
68 void InstantController::RecordMetrics(Profile* profile) { | 116 void InstantController::RecordMetrics(Profile* profile) { |
69 UMA_HISTOGRAM_ENUMERATION("Instant.Status", IsEnabled(profile), 2); | 117 UMA_HISTOGRAM_BOOLEAN("Instant.Status", IsEnabled(profile)); |
70 } | 118 } |
71 | 119 |
72 // static | 120 // static |
73 bool InstantController::IsEnabled(Profile* profile) { | 121 bool InstantController::IsEnabled(Profile* profile) { |
74 const PrefService* prefs = profile->GetPrefs(); | 122 const PrefService* prefs = profile->GetPrefs(); |
75 return prefs && prefs->GetBoolean(prefs::kInstantEnabled); | 123 return prefs && prefs->GetBoolean(prefs::kInstantEnabled); |
76 } | 124 } |
77 | 125 |
78 // static | 126 // static |
79 void InstantController::Enable(Profile* profile) { | 127 void InstantController::Enable(Profile* profile) { |
80 PrefService* prefs = profile->GetPrefs(); | 128 PrefService* prefs = profile->GetPrefs(); |
81 if (!prefs) | 129 if (prefs) { |
82 return; | 130 prefs->SetBoolean(prefs::kInstantEnabled, true); |
83 | 131 prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true); |
84 prefs->SetBoolean(prefs::kInstantEnabled, true); | 132 UMA_HISTOGRAM_BOOLEAN("Instant.Preference", true); |
85 prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true); | 133 } |
86 UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 1, 2); | |
87 } | 134 } |
88 | 135 |
89 // static | 136 // static |
90 void InstantController::Disable(Profile* profile) { | 137 void InstantController::Disable(Profile* profile) { |
91 PrefService* prefs = profile->GetPrefs(); | 138 PrefService* prefs = profile->GetPrefs(); |
92 if (!prefs) | 139 if (prefs) { |
93 return; | 140 prefs->SetBoolean(prefs::kInstantEnabled, false); |
94 | 141 UMA_HISTOGRAM_BOOLEAN("Instant.Preference", false); |
95 prefs->SetBoolean(prefs::kInstantEnabled, false); | 142 } |
96 UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 0, 2); | |
97 } | 143 } |
98 | 144 |
99 bool InstantController::Update(const AutocompleteMatch& match, | 145 bool InstantController::Update(const AutocompleteMatch& match, |
100 const string16& user_text, | 146 const string16& user_text, |
101 bool verbatim, | 147 bool verbatim, |
102 string16* suggested_text) { | 148 string16* suggested_text, |
| 149 InstantCompleteBehavior* complete_behavior) { |
| 150 const TabContents* tab_contents = delegate_->GetInstantHostTabContents(); |
| 151 DCHECK(tab_contents); |
| 152 |
| 153 GURL instant_url; |
| 154 Profile* profile = tab_contents->profile(); |
| 155 |
| 156 // If the match's TemplateURL isn't valid, it is likely not a query. |
| 157 if (!GetInstantURL(match.GetTemplateURL(profile), &instant_url)) { |
| 158 Hide(); |
| 159 return false; |
| 160 } |
| 161 |
| 162 // Strip leading "?", which indicates forced query mode. |
| 163 string16 new_user_text = !user_text.empty() && user_text[0] == '?' ? |
| 164 user_text.substr(1) : user_text; |
| 165 |
| 166 if (new_user_text.empty()) { |
| 167 Hide(); |
| 168 return false; |
| 169 } |
| 170 |
| 171 // The presence of any suggested_text implies verbatim. |
| 172 DCHECK(suggested_text->empty() || verbatim) |
| 173 << user_text << "|" << *suggested_text; |
| 174 |
| 175 ResetLoader(instant_url, tab_contents); |
| 176 |
| 177 // Track the full omnibox text, so we can send it on commit. |
| 178 last_full_text_ = new_user_text + *suggested_text; |
| 179 |
| 180 // Don't send an update to the loader if the |user_text| hasn't changed. |
| 181 if (new_user_text == last_user_text_ && verbatim == last_verbatim_) { |
| 182 // Since we are updating |suggested_text|, shouldn't we also update |
| 183 // |last_full_text_|? No. There's no guarantee that our suggestion will |
| 184 // actually be inline autocompleted. For example, it may get trumped by |
| 185 // a history suggestion. If our suggestion does make it, the omnibox will |
| 186 // call Update() again, at which time we'll update |last_full_text_|. |
| 187 *suggested_text = last_suggestion_; |
| 188 *complete_behavior = last_complete_behavior_; |
| 189 |
| 190 // We need to call Show() here because of this: |
| 191 // 1. User has typed a query (say Q). Instant overlay is showing results. |
| 192 // 2. User arrows-down to a URL entry or erases all omnibox text. Both of |
| 193 // these cause the overlay to Hide(). |
| 194 // 3. User arrows-up to Q or types Q again. |last_user_text_| is still Q, so |
| 195 // we don't need to Update() the loader, but we do need to Show(). |
| 196 if (loader_processed_last_update_ && mode_ == INSTANT) |
| 197 Show(); |
| 198 return true; |
| 199 } |
| 200 |
| 201 last_user_text_ = new_user_text; |
| 202 last_verbatim_ = verbatim; |
| 203 loader_processed_last_update_ = false; |
| 204 |
| 205 // Reset the last suggestion, as it's no longer valid. |
103 suggested_text->clear(); | 206 suggested_text->clear(); |
104 | 207 last_suggestion_.clear(); |
105 is_out_of_date_ = false; | 208 *complete_behavior = last_complete_behavior_ = INSTANT_COMPLETE_NOW; |
106 commit_on_pointer_release_ = false; | 209 |
107 last_transition_type_ = match.transition; | 210 if (mode_ != SILENT) { |
108 last_url_ = match.destination_url; | 211 loader_->Update(last_user_text_, last_verbatim_); |
109 last_user_text_ = user_text; | 212 |
110 | 213 content::NotificationService::current()->Notify( |
111 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | 214 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, |
112 if (!tab_contents) { | 215 content::Source<InstantController>(this), |
113 Hide(); | 216 content::NotificationService::NoDetails()); |
114 return false; | 217 } |
115 } | 218 |
116 | |
117 Profile* profile = tab_contents->profile(); | |
118 const TemplateURL* template_url = match.GetTemplateURL(profile); | |
119 const TemplateURL* default_t_url = | |
120 TemplateURLServiceFactory::GetForProfile(profile) | |
121 ->GetDefaultSearchProvider(); | |
122 if (!IsValidInstantTemplateURL(template_url) || !default_t_url || | |
123 template_url->id() != default_t_url->id()) { | |
124 Hide(); | |
125 return false; | |
126 } | |
127 | |
128 if (!loader_.get() || loader_->template_url_id() != template_url->id()) | |
129 loader_.reset(new InstantLoader(this, template_url->id(), std::string())); | |
130 | |
131 if (mode_ == SILENT) { | |
132 // For the SILENT mode, we process |user_text| at commit time. | |
133 loader_->MaybeLoadInstantURL(tab_contents, template_url); | |
134 return true; | |
135 } | |
136 | |
137 UpdateLoader(tab_contents, template_url, match.destination_url, | |
138 match.transition, user_text, verbatim, suggested_text); | |
139 | |
140 content::NotificationService::current()->Notify( | |
141 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | |
142 content::Source<InstantController>(this), | |
143 content::NotificationService::NoDetails()); | |
144 return true; | 219 return true; |
145 } | 220 } |
146 | 221 |
| 222 // TODO(tonyg): This method only fires when the omnibox bounds change. It also |
| 223 // needs to fire when the preview bounds change (e.g.: open/close info bar). |
147 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { | 224 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { |
148 if (omnibox_bounds_ == bounds) | 225 if (omnibox_bounds_ == bounds || mode_ != INSTANT) |
149 return; | 226 return; |
150 | 227 |
151 // Always track the omnibox bounds. That way if Update is later invoked the | |
152 // bounds are in sync. | |
153 omnibox_bounds_ = bounds; | 228 omnibox_bounds_ = bounds; |
154 | 229 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { |
155 if (loader_.get() && !is_out_of_date_ && mode_ == INSTANT) | 230 update_bounds_timer_.Stop(); |
156 loader_->SetOmniboxBounds(bounds); | 231 SendBoundsToPage(); |
157 } | 232 } else if (!update_bounds_timer_.IsRunning()) { |
158 | 233 update_bounds_timer_.Start(FROM_HERE, |
159 void InstantController::DestroyPreviewContents() { | 234 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, |
160 if (!loader_.get()) { | 235 &InstantController::SendBoundsToPage); |
161 // We're not showing anything, nothing to do. | 236 } |
162 return; | 237 } |
163 } | 238 |
164 | 239 TabContents* InstantController::GetPreviewContents() const { |
165 if (is_displayable_) { | 240 return loader_.get() ? loader_->preview_contents() : NULL; |
166 is_displayable_ = false; | 241 } |
| 242 |
| 243 void InstantController::Hide() { |
| 244 last_tab_contents_ = NULL; |
| 245 if (is_showing_) { |
| 246 is_showing_ = false; |
167 delegate_->HideInstant(); | 247 delegate_->HideInstant(); |
168 } | 248 } |
169 delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY, NULL); | |
170 } | |
171 | |
172 void InstantController::Hide() { | |
173 is_out_of_date_ = true; | |
174 commit_on_pointer_release_ = false; | |
175 if (is_displayable_) { | |
176 is_displayable_ = false; | |
177 delegate_->HideInstant(); | |
178 } | |
179 } | 249 } |
180 | 250 |
181 bool InstantController::IsCurrent() const { | 251 bool InstantController::IsCurrent() const { |
182 // TODO(mmenke): See if we can do something more intelligent in the | 252 DCHECK(IsOutOfDate() || loader_.get()); |
183 // navigation pending case. | 253 return !IsOutOfDate() && loader_.get() && loader_->supports_instant() && |
184 return is_displayable_ && !loader_->IsNavigationPending() && | 254 !last_full_text_.empty(); |
185 !loader_->needs_reload(); | |
186 } | |
187 | |
188 bool InstantController::PrepareForCommit() { | |
189 if (is_out_of_date_ || !loader_.get()) | |
190 return false; | |
191 | |
192 // If we are in the visible (INSTANT) mode, return the status of the preview. | |
193 if (mode_ == INSTANT) | |
194 return IsCurrent(); | |
195 | |
196 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | |
197 if (!tab_contents) | |
198 return false; | |
199 | |
200 const TemplateURL* template_url = | |
201 TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) | |
202 ->GetDefaultSearchProvider(); | |
203 if (!IsValidInstantTemplateURL(template_url) || | |
204 loader_->template_url_id() != template_url->id() || | |
205 loader_->IsNavigationPending() || | |
206 loader_->is_determining_if_page_supports_instant()) { | |
207 return false; | |
208 } | |
209 | |
210 // In the SUGGEST and HIDDEN modes, we must have sent an Update() by now, so | |
211 // check if the loader failed to process it. | |
212 if ((mode_ == SUGGEST || mode_ == HIDDEN) | |
213 && (!loader_->ready() || !loader_->http_status_ok())) { | |
214 return false; | |
215 } | |
216 | |
217 // Ignore the suggested text, as we are about to commit the verbatim query. | |
218 string16 suggested_text; | |
219 UpdateLoader(tab_contents, template_url, last_url_, last_transition_type_, | |
220 last_user_text_, true, &suggested_text); | |
221 return true; | |
222 } | 255 } |
223 | 256 |
224 TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) { | 257 TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) { |
225 DCHECK(loader_.get()); | 258 const TabContents* tab_contents = delegate_->GetInstantHostTabContents(); |
226 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | 259 TabContents* preview = ReleasePreviewContents(type); |
227 DCHECK(tab_contents); | |
228 TabContents* preview = ReleasePreviewContents(type, tab_contents); | |
229 preview->web_contents()->GetController().CopyStateFromAndPrune( | 260 preview->web_contents()->GetController().CopyStateFromAndPrune( |
230 &tab_contents->web_contents()->GetController()); | 261 &tab_contents->web_contents()->GetController()); |
231 delegate_->CommitInstant(preview); | 262 delegate_->CommitInstant(preview); |
232 CompleteRelease(preview); | |
233 return preview; | 263 return preview; |
234 } | 264 } |
235 | 265 |
236 bool InstantController::CommitIfCurrent() { | 266 TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) { |
237 if (IsCurrent()) { | 267 TabContents* tab_contents = loader_->ReleasePreviewContents(type, |
238 CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER); | 268 last_full_text_); |
239 return true; | 269 AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED); |
240 } | 270 // We may have gotten here from CommitInstant(), which means the loader may |
241 return false; | 271 // still be on the stack. So, schedule a destruction for later. |
242 } | 272 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); |
243 | 273 // This call is here to hide the preview and reset view state. It won't |
244 void InstantController::SetCommitOnPointerRelease() { | 274 // actually delete |loader_| because it was just released to DeleteSoon(). |
245 commit_on_pointer_release_ = true; | 275 DeleteLoader(); |
246 } | 276 return tab_contents; |
247 | 277 } |
248 bool InstantController::IsPointerDownFromActivate() { | 278 |
249 DCHECK(loader_.get()); | 279 void InstantController::OnAutocompleteLostFocus() { |
250 return loader_->IsPointerDownFromActivate(); | 280 DCHECK(!is_showing_ || loader_.get()); |
251 } | 281 |
252 | 282 // TODO(sreeram): Since we never delete the loader except when committing |
253 #if defined(OS_MACOSX) | 283 // Instant, the loader may have a very stale page. Reload it when stale. |
254 void InstantController::OnAutocompleteLostFocus( | 284 if (is_showing_ && loader_.get() && !loader_->IsPointerDownFromActivate()) |
255 gfx::NativeView view_gaining_focus) { | 285 Hide(); |
256 // If |IsPointerDownFromActivate()| returns false, the RenderWidgetHostView | 286 } |
257 // did not receive a mouseDown event. Therefore, we should destroy the | 287 |
258 // preview. Otherwise, the RWHV was clicked, so we commit the preview. | 288 void InstantController::OnAutocompleteGotFocus() { |
259 if (!IsCurrent() || !IsPointerDownFromActivate()) | 289 const TabContents* tab_contents = delegate_->GetInstantHostTabContents(); |
260 DestroyPreviewContents(); | 290 |
261 else | 291 // We could get here with no underlying tab if the Browser is in the process |
262 SetCommitOnPointerRelease(); | 292 // of closing. |
263 } | 293 if (!tab_contents) |
264 #else | 294 return; |
265 void InstantController::OnAutocompleteLostFocus( | 295 |
266 gfx::NativeView view_gaining_focus) { | 296 // Since we don't have any autocomplete match to work with, we'll just use |
267 if (!IsCurrent()) { | 297 // the default search provider's Instant URL. |
268 DestroyPreviewContents(); | 298 const TemplateURL* template_url = |
269 return; | 299 TemplateURLServiceFactory::GetForProfile(tab_contents->profile())-> |
270 } | 300 GetDefaultSearchProvider(); |
271 | 301 |
272 content::RenderWidgetHostView* rwhv = | 302 GURL instant_url; |
273 GetPreviewContents()->web_contents()->GetRenderWidgetHostView(); | 303 if (!GetInstantURL(template_url, &instant_url)) |
274 if (!view_gaining_focus || !rwhv) { | 304 return; |
275 DestroyPreviewContents(); | 305 |
276 return; | 306 ResetLoader(instant_url, tab_contents); |
277 } | 307 } |
278 | 308 |
279 #if defined(TOOLKIT_VIEWS) | 309 bool InstantController::commit_on_pointer_release() const { |
280 // For views the top level widget is always focused. If the focus change | 310 return loader_.get() && loader_->IsPointerDownFromActivate(); |
281 // originated in views determine the child Widget from the view that is being | 311 } |
282 // focused. | 312 |
283 views::Widget* widget = | 313 void InstantController::SetSuggestions( |
284 views::Widget::GetWidgetForNativeView(view_gaining_focus); | 314 InstantLoader* loader, |
285 if (widget) { | 315 const std::vector<string16>& suggestions, |
286 views::FocusManager* focus_manager = widget->GetFocusManager(); | 316 InstantCompleteBehavior behavior) { |
287 if (focus_manager && focus_manager->is_changing_focus() && | 317 DCHECK_EQ(loader_.get(), loader); |
288 focus_manager->GetFocusedView() && | 318 if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN) |
289 focus_manager->GetFocusedView()->GetWidget()) { | 319 return; |
290 view_gaining_focus = | 320 |
291 focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | 321 string16 suggestion; |
| 322 if (!suggestions.empty()) { |
| 323 suggestion = suggestions[0]; |
| 324 } |
| 325 |
| 326 string16 suggestion_lower = base::i18n::ToLower(suggestion); |
| 327 string16 user_text_lower = base::i18n::ToLower(last_user_text_); |
| 328 if (user_text_lower.size() >= suggestion_lower.size() || |
| 329 suggestion_lower.compare(0, user_text_lower.size(), user_text_lower)) { |
| 330 suggestion.clear(); |
| 331 } else { |
| 332 suggestion.erase(0, last_user_text_.size()); |
| 333 } |
| 334 last_suggestion_ = suggestion; |
| 335 last_complete_behavior_ = behavior; |
| 336 if (!last_verbatim_) |
| 337 delegate_->SetSuggestedText(suggestion, behavior); |
| 338 |
| 339 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); |
| 340 DCHECK(tab_contents); |
| 341 if (mode_ != SUGGEST && tab_contents && |
| 342 !tab_contents->search_tab_helper()->model()->mode().is_ntp()) { |
| 343 Show(); |
| 344 } |
| 345 |
| 346 loader_processed_last_update_ = true; |
| 347 } |
| 348 |
| 349 void InstantController::CommitInstantLoader(InstantLoader* loader) { |
| 350 DCHECK_EQ(loader_.get(), loader); |
| 351 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; |
| 352 if (loader_ != loader || !is_showing_ || IsOutOfDate()) |
| 353 return; |
| 354 |
| 355 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); |
| 356 } |
| 357 |
| 358 void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) { |
| 359 DCHECK_EQ(loader_.get(), loader); |
| 360 AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED); |
| 361 } |
| 362 |
| 363 void InstantController::InstantSupportDetermined(InstantLoader* loader, |
| 364 bool supports_instant) { |
| 365 DCHECK_EQ(loader_.get(), loader); |
| 366 if (supports_instant) { |
| 367 blacklisted_urls_.erase(loader->instant_url().possibly_invalid_spec()); |
| 368 } else { |
| 369 ++blacklisted_urls_[loader->instant_url().possibly_invalid_spec()]; |
| 370 if (loader_ == loader) { |
| 371 // Because of the state of the stack, we can't destroy the loader now. |
| 372 if (GetPreviewContents()) |
| 373 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
| 374 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); |
| 375 DeleteLoader(); |
292 } | 376 } |
293 } | 377 } |
294 #endif | |
295 | |
296 gfx::NativeView tab_view = | |
297 GetPreviewContents()->web_contents()->GetNativeView(); | |
298 // Focus is going to the renderer. | |
299 if (rwhv->GetNativeView() == view_gaining_focus || | |
300 tab_view == view_gaining_focus) { | |
301 if (!IsPointerDownFromActivate()) { | |
302 // If the mouse is not down, focus is not going to the renderer. Someone | |
303 // else moved focus and we shouldn't commit. | |
304 DestroyPreviewContents(); | |
305 return; | |
306 } | |
307 | |
308 // We're showing instant results. As instant results may shift when | |
309 // committing we commit on the mouse up. This way a slow click still works | |
310 // fine. | |
311 SetCommitOnPointerRelease(); | |
312 return; | |
313 } | |
314 | |
315 // Walk up the view hierarchy. If the view gaining focus is a subview of the | |
316 // WebContents view (such as a windowed plugin or http auth dialog), we want | |
317 // to keep the preview contents. Otherwise, focus has gone somewhere else, | |
318 // such as the JS inspector, and we want to cancel the preview. | |
319 gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus; | |
320 while (view_gaining_focus_ancestor && | |
321 view_gaining_focus_ancestor != tab_view) { | |
322 view_gaining_focus_ancestor = | |
323 platform_util::GetParent(view_gaining_focus_ancestor); | |
324 } | |
325 | |
326 if (view_gaining_focus_ancestor) { | |
327 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); | |
328 return; | |
329 } | |
330 | |
331 DestroyPreviewContents(); | |
332 } | |
333 #endif | |
334 | |
335 void InstantController::OnAutocompleteGotFocus() { | |
336 TabContents* tab_contents = delegate_->GetInstantHostTabContents(); | |
337 if (!tab_contents) | |
338 return; | |
339 | |
340 const TemplateURL* template_url = | |
341 TemplateURLServiceFactory::GetForProfile(tab_contents->profile()) | |
342 ->GetDefaultSearchProvider(); | |
343 if (!IsValidInstantTemplateURL(template_url)) | |
344 return; | |
345 | |
346 if (!loader_.get() || loader_->template_url_id() != template_url->id()) | |
347 loader_.reset(new InstantLoader(this, template_url->id(), std::string())); | |
348 loader_->MaybeLoadInstantURL(tab_contents, template_url); | |
349 } | |
350 | |
351 TabContents* InstantController::ReleasePreviewContents( | |
352 InstantCommitType type, | |
353 TabContents* current_tab) { | |
354 if (!loader_.get()) | |
355 return NULL; | |
356 | |
357 TabContents* tab = loader_->ReleasePreviewContents(type, current_tab); | |
358 ClearBlacklist(); | |
359 is_out_of_date_ = true; | |
360 is_displayable_ = false; | |
361 commit_on_pointer_release_ = false; | |
362 omnibox_bounds_ = gfx::Rect(); | |
363 loader_.reset(); | |
364 return tab; | |
365 } | |
366 | |
367 void InstantController::CompleteRelease(TabContents* tab) { | |
368 tab->blocked_content_tab_helper()->SetAllContentsBlocked(false); | |
369 } | |
370 | |
371 TabContents* InstantController::GetPreviewContents() const { | |
372 return loader_.get() ? loader_->preview_contents() : NULL; | |
373 } | |
374 | |
375 void InstantController::InstantStatusChanged(InstantLoader* loader) { | |
376 DCHECK(loader_.get()); | |
377 UpdateIsDisplayable(); | |
378 } | |
379 | |
380 void InstantController::SetSuggestedTextFor( | |
381 InstantLoader* loader, | |
382 const string16& text, | |
383 InstantCompleteBehavior behavior) { | |
384 if (is_out_of_date_) | |
385 return; | |
386 | |
387 if (mode_ == INSTANT || mode_ == SUGGEST) | |
388 delegate_->SetSuggestedText(text, behavior); | |
389 } | |
390 | |
391 gfx::Rect InstantController::GetInstantBounds() { | |
392 return delegate_->GetInstantBounds(); | |
393 } | |
394 | |
395 bool InstantController::ShouldCommitInstantOnPointerRelease() { | |
396 return commit_on_pointer_release_; | |
397 } | |
398 | |
399 void InstantController::CommitInstantLoader(InstantLoader* loader) { | |
400 if (loader_.get() && loader_.get() == loader) { | |
401 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST); | |
402 } else { | |
403 // This can happen if the mouse was down, we swapped out the preview and | |
404 // the mouse was released. Generally this shouldn't happen, but if it does | |
405 // revert. | |
406 DestroyPreviewContents(); | |
407 } | |
408 } | |
409 | |
410 void InstantController::InstantLoaderDoesntSupportInstant( | |
411 InstantLoader* loader) { | |
412 VLOG(1) << "provider does not support instant"; | |
413 | |
414 // Don't attempt to use instant for this search engine again. | |
415 BlacklistFromInstant(); | |
416 } | |
417 | |
418 void InstantController::AddToBlacklist(InstantLoader* loader, const GURL& url) { | |
419 // Don't attempt to use instant for this search engine again. | |
420 BlacklistFromInstant(); | |
421 } | 378 } |
422 | 379 |
423 void InstantController::SwappedTabContents(InstantLoader* loader) { | 380 void InstantController::SwappedTabContents(InstantLoader* loader) { |
424 if (is_displayable_) | 381 DCHECK_EQ(loader_.get(), loader); |
425 delegate_->ShowInstant(loader->preview_contents()); | 382 if (loader_ == loader && is_showing_) |
426 } | 383 delegate_->ShowInstant(); |
427 | 384 } |
428 void InstantController::InstantLoaderContentsFocused() { | 385 |
| 386 void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) { |
| 387 DCHECK_EQ(loader_.get(), loader); |
| 388 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_; |
429 #if defined(USE_AURA) | 389 #if defined(USE_AURA) |
430 // On aura the omnibox only receives a focus lost if we initiate the focus | 390 // On aura the omnibox only receives a focus lost if we initiate the focus |
431 // change. This does that. | 391 // change. This does that. |
432 if (mode_ == INSTANT) | 392 if (is_showing_ && !IsOutOfDate()) |
433 delegate_->InstantPreviewFocused(); | 393 delegate_->InstantPreviewFocused(); |
434 #endif | 394 #endif |
435 } | 395 } |
436 | 396 |
437 void InstantController::UpdateIsDisplayable() { | 397 void InstantController::ResetLoader(const GURL& instant_url, |
438 bool displayable = !is_out_of_date_ && loader_.get() && loader_->ready() && | 398 const TabContents* tab_contents) { |
439 loader_->http_status_ok(); | 399 if (loader_.get() && |
440 if (displayable == is_displayable_ || mode_ != INSTANT) | 400 (loader_->instant_url().possibly_invalid_spec() != |
441 return; | 401 instant_url.possibly_invalid_spec())) |
442 | 402 DeleteLoader(); |
443 is_displayable_ = displayable; | 403 |
444 if (!is_displayable_) { | 404 if (!loader_.get()) { |
445 delegate_->HideInstant(); | 405 loader_.reset( |
446 } else { | 406 new InstantLoader(this, instant_url, tab_contents)); |
447 delegate_->ShowInstant(loader_->preview_contents()); | 407 loader_->Init(); |
448 content::NotificationService::current()->Notify( | 408 AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED); |
449 chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN, | 409 } |
450 content::Source<InstantController>(this), | 410 |
451 content::NotificationService::NoDetails()); | 411 last_tab_contents_ = tab_contents; |
452 } | 412 } |
453 } | 413 |
454 | 414 void InstantController::DeleteLoader() { |
455 void InstantController::UpdateLoader(TabContents* tab_contents, | 415 Hide(); |
456 const TemplateURL* template_url, | 416 last_full_text_.clear(); |
457 const GURL& url, | 417 last_user_text_.clear(); |
458 content::PageTransition transition_type, | 418 last_verbatim_ = false; |
459 const string16& user_text, | 419 last_suggestion_.clear(); |
460 bool verbatim, | 420 last_complete_behavior_ = INSTANT_COMPLETE_NOW; |
461 string16* suggested_text) { | 421 last_omnibox_bounds_ = gfx::Rect(); |
462 if (mode_ == INSTANT) | 422 if (GetPreviewContents()) |
463 loader_->SetOmniboxBounds(omnibox_bounds_); | 423 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED); |
464 loader_->Update(tab_contents, template_url, url, transition_type, user_text, | 424 loader_.reset(); |
465 verbatim, suggested_text); | 425 } |
466 UpdateIsDisplayable(); | 426 |
467 // For the HIDDEN and SILENT modes, don't send back suggestions. | 427 void InstantController::Show() { |
468 if (mode_ == HIDDEN || mode_ == SILENT) | 428 if (!is_showing_) { |
469 suggested_text->clear(); | 429 is_showing_ = true; |
470 } | 430 delegate_->ShowInstant(); |
471 | 431 AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED); |
472 // Returns true if |template_url| is a valid TemplateURL for use by instant. | 432 } |
473 bool InstantController::IsValidInstantTemplateURL( | 433 } |
474 const TemplateURL* template_url) { | 434 |
475 return template_url && template_url->id() && | 435 void InstantController::SendBoundsToPage() { |
476 template_url->instant_url_ref().SupportsReplacement() && | 436 if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() || |
477 !IsBlacklistedFromInstant(template_url->id()); | 437 !GetPreviewContents() || loader_->IsPointerDownFromActivate()) { |
478 } | 438 return; |
479 | 439 } |
480 void InstantController::BlacklistFromInstant() { | 440 |
481 if (!loader_.get()) | 441 last_omnibox_bounds_ = omnibox_bounds_; |
482 return; | 442 gfx::Rect preview_bounds = delegate_->GetInstantBounds(); |
483 | 443 gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds); |
484 DCHECK(loader_->template_url_id()); | 444 |
485 blacklisted_ids_.insert(loader_->template_url_id()); | 445 // Translate into window coordinates. |
486 | 446 if (!intersection.IsEmpty()) { |
487 // Because of the state of the stack we can't destroy the loader now. | 447 intersection.Offset(-preview_bounds.origin().x(), |
488 ScheduleDestroy(loader_.release()); | 448 -preview_bounds.origin().y()); |
489 UpdateIsDisplayable(); | 449 } |
490 } | 450 |
491 | 451 // In the current Chrome UI, these must always be true so they sanity check |
492 bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) { | 452 // the above operations. In a future UI, these may be removed or adjusted. |
493 return blacklisted_ids_.count(id) > 0; | 453 // There is no point in sanity-checking |intersection.y()| because the omnibox |
494 } | 454 // can be placed anywhere vertically relative to the preview (for example, in |
495 | 455 // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds). |
496 void InstantController::ClearBlacklist() { | 456 DCHECK_LE(0, intersection.x()); |
497 blacklisted_ids_.clear(); | 457 DCHECK_LE(0, intersection.width()); |
498 } | 458 DCHECK_LE(0, intersection.height()); |
499 | 459 |
500 void InstantController::ScheduleDestroy(InstantLoader* loader) { | 460 loader_->SetOmniboxBounds(intersection); |
501 loaders_to_destroy_.push_back(loader); | 461 } |
502 if (!weak_factory_.HasWeakPtrs()) { | 462 |
503 MessageLoop::current()->PostTask( | 463 bool InstantController::GetInstantURL(const TemplateURL* template_url, |
504 FROM_HERE, base::Bind(&InstantController::DestroyLoaders, | 464 GURL* instant_url) const { |
505 weak_factory_.GetWeakPtr())); | 465 CommandLine* command_line = CommandLine::ForCurrentProcess(); |
506 } | 466 if (command_line->HasSwitch(switches::kInstantURL)) { |
507 } | 467 *instant_url = |
508 | 468 GURL(command_line->GetSwitchValueASCII(switches::kInstantURL)); |
509 void InstantController::DestroyLoaders() { | 469 return true; |
510 loaders_to_destroy_.clear(); | 470 } |
511 } | 471 |
| 472 if (!template_url) |
| 473 return false; |
| 474 |
| 475 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref(); |
| 476 if (!instant_url_ref.IsValid() || !instant_url_ref.SupportsReplacement()) |
| 477 return false; |
| 478 |
| 479 GURL url(instant_url_ref.ReplaceSearchTerms( |
| 480 TemplateURLRef::SearchTermsArgs(string16()))); |
| 481 |
| 482 std::map<std::string, int>::const_iterator iter = |
| 483 blacklisted_urls_.find(url.possibly_invalid_spec()); |
| 484 if (iter != blacklisted_urls_.end() && |
| 485 iter->second > kMaxInstantSupportFailures) { |
| 486 return false; |
| 487 } |
| 488 |
| 489 *instant_url = url; |
| 490 return true; |
| 491 } |
| 492 |
| 493 bool InstantController::IsOutOfDate() const { |
| 494 return !last_tab_contents_ || |
| 495 last_tab_contents_ != delegate_->GetInstantHostTabContents(); |
| 496 } |
OLD | NEW |