OLD | NEW |
| (Empty) |
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/instant/instant_controller.h" | |
6 | |
7 #include "base/metrics/histogram.h" | |
8 #include "base/string_util.h" | |
9 #include "base/stringprintf.h" | |
10 #include "base/utf_string_conversions.h" | |
11 #include "chrome/browser/autocomplete/autocomplete_provider.h" | |
12 #include "chrome/browser/history/history_service.h" | |
13 #include "chrome/browser/history/history_service_factory.h" | |
14 #include "chrome/browser/history/history_tab_helper.h" | |
15 #include "chrome/browser/history/top_sites.h" | |
16 #include "chrome/browser/instant/instant_ntp.h" | |
17 #include "chrome/browser/instant/instant_overlay.h" | |
18 #include "chrome/browser/instant/instant_service.h" | |
19 #include "chrome/browser/instant/instant_service_factory.h" | |
20 #include "chrome/browser/instant/instant_tab.h" | |
21 #include "chrome/browser/instant/search.h" | |
22 #include "chrome/browser/platform_util.h" | |
23 #include "chrome/browser/search_engines/search_terms_data.h" | |
24 #include "chrome/browser/search_engines/template_url_service.h" | |
25 #include "chrome/browser/search_engines/template_url_service_factory.h" | |
26 #include "chrome/browser/ui/browser_instant_controller.h" | |
27 #include "chrome/browser/ui/search/search_tab_helper.h" | |
28 #include "chrome/common/chrome_notification_types.h" | |
29 #include "chrome/common/chrome_switches.h" | |
30 #include "chrome/common/url_constants.h" | |
31 #include "content/public/browser/navigation_entry.h" | |
32 #include "content/public/browser/notification_service.h" | |
33 #include "content/public/browser/render_process_host.h" | |
34 #include "content/public/browser/render_widget_host_view.h" | |
35 #include "content/public/browser/user_metrics.h" | |
36 #include "content/public/browser/web_contents.h" | |
37 #include "content/public/browser/web_contents_view.h" | |
38 #include "net/base/escape.h" | |
39 #include "third_party/icu/public/common/unicode/normalizer2.h" | |
40 | |
41 #if defined(TOOLKIT_VIEWS) | |
42 #include "ui/views/widget/widget.h" | |
43 #endif | |
44 | |
45 namespace { | |
46 | |
47 // An artificial delay (in milliseconds) we introduce before telling the Instant | |
48 // page about the new omnibox bounds, in cases where the bounds shrink. This is | |
49 // to avoid the page jumping up/down very fast in response to bounds changes. | |
50 const int kUpdateBoundsDelayMS = 1000; | |
51 | |
52 // The maximum number of times we'll load a non-Instant-supporting search engine | |
53 // before we give up and blacklist it for the rest of the browsing session. | |
54 const int kMaxInstantSupportFailures = 10; | |
55 | |
56 // For reporting events of interest. | |
57 enum InstantControllerEvent { | |
58 INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST = 0, | |
59 INSTANT_CONTROLLER_EVENT_URL_REMOVED_FROM_BLACKLIST = 1, | |
60 INSTANT_CONTROLLER_EVENT_URL_BLOCKED_BY_BLACKLIST = 2, | |
61 INSTANT_CONTROLLER_EVENT_MAX = 3, | |
62 }; | |
63 | |
64 void RecordEventHistogram(InstantControllerEvent event) { | |
65 UMA_HISTOGRAM_ENUMERATION("Instant.InstantControllerEvent", | |
66 event, | |
67 INSTANT_CONTROLLER_EVENT_MAX); | |
68 } | |
69 | |
70 void AddSessionStorageHistogram(bool extended_enabled, | |
71 const content::WebContents* tab1, | |
72 const content::WebContents* tab2) { | |
73 base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet( | |
74 std::string("Instant.SessionStorageNamespace") + | |
75 (extended_enabled ? "_Extended" : "_Instant"), | |
76 base::HistogramBase::kUmaTargetedHistogramFlag); | |
77 const content::SessionStorageNamespaceMap& session_storage_map1 = | |
78 tab1->GetController().GetSessionStorageNamespaceMap(); | |
79 const content::SessionStorageNamespaceMap& session_storage_map2 = | |
80 tab2->GetController().GetSessionStorageNamespaceMap(); | |
81 bool is_session_storage_the_same = | |
82 session_storage_map1.size() == session_storage_map2.size(); | |
83 if (is_session_storage_the_same) { | |
84 // The size is the same, so let's check that all entries match. | |
85 for (content::SessionStorageNamespaceMap::const_iterator | |
86 it1 = session_storage_map1.begin(), | |
87 it2 = session_storage_map2.begin(); | |
88 it1 != session_storage_map1.end() && it2 != session_storage_map2.end(); | |
89 ++it1, ++it2) { | |
90 if (it1->first != it2->first || it1->second != it2->second) { | |
91 is_session_storage_the_same = false; | |
92 break; | |
93 } | |
94 } | |
95 } | |
96 histogram->AddBoolean(is_session_storage_the_same); | |
97 } | |
98 | |
99 string16 Normalize(const string16& str) { | |
100 UErrorCode status = U_ZERO_ERROR; | |
101 const icu::Normalizer2* normalizer = | |
102 icu::Normalizer2::getInstance(NULL, "nfkc_cf", UNORM2_COMPOSE, status); | |
103 if (normalizer == NULL || U_FAILURE(status)) | |
104 return str; | |
105 icu::UnicodeString norm_str(normalizer->normalize( | |
106 icu::UnicodeString(FALSE, str.c_str(), str.size()), status)); | |
107 if (U_FAILURE(status)) | |
108 return str; | |
109 return string16(norm_str.getBuffer(), norm_str.length()); | |
110 } | |
111 | |
112 bool NormalizeAndStripPrefix(string16* text, const string16& prefix) { | |
113 string16 norm_prefix = Normalize(prefix); | |
114 string16 norm_text = Normalize(*text); | |
115 if (norm_prefix.size() <= norm_text.size() && | |
116 norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) { | |
117 *text = norm_text.erase(0, norm_prefix.size()); | |
118 return true; | |
119 } | |
120 return false; | |
121 } | |
122 | |
123 // For TOOLKIT_VIEWS, the top level widget is always focused. If the focus | |
124 // change originated in views determine the child Widget from the view that is | |
125 // being focused. | |
126 gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) { | |
127 #if defined(TOOLKIT_VIEWS) | |
128 views::Widget* widget = view_gaining_focus ? | |
129 views::Widget::GetWidgetForNativeView(view_gaining_focus) : NULL; | |
130 if (widget) { | |
131 views::FocusManager* focus_manager = widget->GetFocusManager(); | |
132 if (focus_manager && focus_manager->is_changing_focus() && | |
133 focus_manager->GetFocusedView() && | |
134 focus_manager->GetFocusedView()->GetWidget()) | |
135 return focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | |
136 } | |
137 #endif | |
138 return view_gaining_focus; | |
139 } | |
140 | |
141 // Returns true if |view| is the top-level contents view or a child view in the | |
142 // view hierarchy of |contents|. | |
143 bool IsViewInContents(gfx::NativeView view, content::WebContents* contents) { | |
144 content::RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView(); | |
145 if (!view || !rwhv) | |
146 return false; | |
147 | |
148 gfx::NativeView tab_view = contents->GetView()->GetNativeView(); | |
149 if (view == rwhv->GetNativeView() || view == tab_view) | |
150 return true; | |
151 | |
152 // Walk up the view hierarchy to determine if the view is a subview of the | |
153 // WebContents view (such as a windowed plugin or http auth dialog). | |
154 while (view) { | |
155 view = platform_util::GetParent(view); | |
156 if (view == tab_view) | |
157 return true; | |
158 } | |
159 | |
160 return false; | |
161 } | |
162 | |
163 bool IsFullHeight(const InstantOverlayModel& model) { | |
164 return model.height() == 100 && model.height_units() == INSTANT_SIZE_PERCENT; | |
165 } | |
166 | |
167 bool IsContentsFrom(const InstantPage* page, | |
168 const content::WebContents* contents) { | |
169 return page && (page->contents() == contents); | |
170 } | |
171 | |
172 // Adds a transient NavigationEntry to the supplied |contents|'s | |
173 // NavigationController if the page's URL has not already been updated with the | |
174 // supplied |search_terms|. Sets the |search_terms| on the transient entry for | |
175 // search terms extraction to work correctly. | |
176 void EnsureSearchTermsAreSet(content::WebContents* contents, | |
177 const string16& search_terms) { | |
178 content::NavigationController* controller = &contents->GetController(); | |
179 | |
180 // If search terms are already correct or there is already a transient entry | |
181 // (there shouldn't be), bail out early. | |
182 if (chrome::search::GetSearchTerms(contents) == search_terms || | |
183 controller->GetTransientEntry()) | |
184 return; | |
185 | |
186 const content::NavigationEntry* active_entry = controller->GetActiveEntry(); | |
187 content::NavigationEntry* transient = controller->CreateNavigationEntry( | |
188 active_entry->GetURL(), | |
189 active_entry->GetReferrer(), | |
190 active_entry->GetTransitionType(), | |
191 false, | |
192 std::string(), | |
193 contents->GetBrowserContext()); | |
194 transient->SetExtraData(chrome::search::kInstantExtendedSearchTermsKey, | |
195 search_terms); | |
196 controller->SetTransientEntry(transient); | |
197 | |
198 chrome::search::SearchTabHelper::FromWebContents(contents)-> | |
199 NavigationEntryUpdated(); | |
200 } | |
201 | |
202 bool GetURLForMostVisitedItemId(Profile* profile, | |
203 uint64 most_visited_item_id, | |
204 GURL* url) { | |
205 InstantService* instant_service = | |
206 InstantServiceFactory::GetForProfile(profile); | |
207 if (!instant_service) | |
208 return false; | |
209 return instant_service->GetURLForMostVisitedItemId(most_visited_item_id, url); | |
210 } | |
211 | |
212 // Creates a new restriced id if one is not found. | |
213 size_t GetMostVisitedItemIDForURL(Profile* profile, const GURL& url) { | |
214 InstantService* instant_service = | |
215 InstantServiceFactory::GetForProfile(profile); | |
216 if (!instant_service) | |
217 return 0; | |
218 return instant_service->AddURL(url); | |
219 } | |
220 | |
221 } // namespace | |
222 | |
223 InstantController::InstantController(chrome::BrowserInstantController* browser, | |
224 bool extended_enabled) | |
225 : browser_(browser), | |
226 extended_enabled_(extended_enabled), | |
227 instant_enabled_(false), | |
228 use_local_overlay_only_(true), | |
229 model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), | |
230 last_omnibox_text_has_inline_autocompletion_(false), | |
231 last_verbatim_(false), | |
232 last_transition_type_(content::PAGE_TRANSITION_LINK), | |
233 last_match_was_search_(false), | |
234 omnibox_focus_state_(OMNIBOX_FOCUS_NONE), | |
235 omnibox_bounds_(-1, -1, 0, 0), | |
236 allow_overlay_to_show_search_suggestions_(false), | |
237 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | |
238 | |
239 // When the InstantController lives, the InstantService should live. | |
240 // InstantService sets up profile-level facilities such as the ThemeSource for | |
241 // the NTP. | |
242 InstantServiceFactory::GetForProfile(browser_->profile()); | |
243 } | |
244 | |
245 InstantController::~InstantController() { | |
246 } | |
247 | |
248 bool InstantController::Update(const AutocompleteMatch& match, | |
249 const string16& user_text, | |
250 const string16& full_text, | |
251 size_t selection_start, | |
252 size_t selection_end, | |
253 bool verbatim, | |
254 bool user_input_in_progress, | |
255 bool omnibox_popup_is_open, | |
256 bool escape_pressed, | |
257 bool is_keyword_search) { | |
258 if (!extended_enabled_ && !instant_enabled_) | |
259 return false; | |
260 | |
261 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
262 "Update: %s user_text='%s' full_text='%s' selection_start=%d " | |
263 "selection_end=%d verbatim=%d typing=%d popup=%d escape_pressed=%d " | |
264 "is_keyword_search=%d", | |
265 AutocompleteMatch::TypeToString(match.type).c_str(), | |
266 UTF16ToUTF8(user_text).c_str(), UTF16ToUTF8(full_text).c_str(), | |
267 static_cast<int>(selection_start), static_cast<int>(selection_end), | |
268 verbatim, user_input_in_progress, omnibox_popup_is_open, escape_pressed, | |
269 is_keyword_search)); | |
270 | |
271 // TODO(dhollowa): Complete keyword match UI. For now just hide suggestions. | |
272 // http://crbug.com/153932. Note, this early escape is happens prior to the | |
273 // DCHECKs below because |user_text| and |full_text| have different semantics | |
274 // when keyword search is in effect. | |
275 if (is_keyword_search) { | |
276 if (instant_tab_) | |
277 instant_tab_->Update(string16(), 0, 0, true); | |
278 else | |
279 HideOverlay(); | |
280 last_match_was_search_ = false; | |
281 return false; | |
282 } | |
283 | |
284 // Ignore spurious updates when the omnibox is blurred; otherwise click | |
285 // targets on the page may vanish before a click event arrives. | |
286 if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE) | |
287 return false; | |
288 | |
289 // If the popup is open, the user has to be typing. | |
290 DCHECK(!omnibox_popup_is_open || user_input_in_progress); | |
291 | |
292 // If the popup is closed, there should be no inline autocompletion. | |
293 DCHECK(omnibox_popup_is_open || user_text.empty() || user_text == full_text) | |
294 << user_text << "|" << full_text; | |
295 | |
296 // If there's no text in the omnibox, the user can't have typed any. | |
297 DCHECK(!full_text.empty() || user_text.empty()) << user_text; | |
298 | |
299 // If the user isn't typing, and the popup is closed, there can't be any | |
300 // user-typed text. | |
301 DCHECK(user_input_in_progress || omnibox_popup_is_open || user_text.empty()) | |
302 << user_text; | |
303 | |
304 // The overlay is being clicked and will commit soon. Don't change anything. | |
305 // TODO(sreeram): Add a browser test for this. | |
306 if (overlay_ && overlay_->is_pointer_down_from_activate()) | |
307 return false; | |
308 | |
309 // In non-extended mode, SearchModeChanged() is never called, so fake it. The | |
310 // mode is set to "disallow suggestions" here, so that if one of the early | |
311 // "return false" conditions is hit, suggestions will be disallowed. If the | |
312 // query is sent to the overlay, the mode is set to "allow" further below. | |
313 if (!extended_enabled_) | |
314 search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | |
315 | |
316 last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && | |
317 !user_text.empty(); | |
318 | |
319 // In non extended mode, Instant is disabled for URLs and keyword mode. | |
320 if (!extended_enabled_ && | |
321 (!last_match_was_search_ || | |
322 match.type == AutocompleteMatch::SEARCH_OTHER_ENGINE)) { | |
323 HideOverlay(); | |
324 return false; | |
325 } | |
326 | |
327 // If we have an |instant_tab_| use it, else ensure we have an overlay that is | |
328 // current or is using the local overlay. | |
329 if (!instant_tab_ && !(overlay_ && overlay_->IsUsingLocalOverlay()) && | |
330 !EnsureOverlayIsCurrent(false)) { | |
331 HideOverlay(); | |
332 return false; | |
333 } | |
334 | |
335 if (extended_enabled_) { | |
336 if (!omnibox_popup_is_open) { | |
337 if (!user_input_in_progress) { | |
338 // If the user isn't typing and the omnibox popup is closed, it means a | |
339 // regular navigation, tab-switch or the user hitting Escape. | |
340 if (instant_tab_) { | |
341 // The user is on a search results page. It may be showing results for | |
342 // a partial query the user typed before they hit Escape. Send the | |
343 // omnibox text to the page to restore the original results. | |
344 // | |
345 // In a tab switch, |instant_tab_| won't have updated yet, so it may | |
346 // be pointing to the previous tab (which was a search results page). | |
347 // Ensure we don't send the omnibox text to a random webpage (the new | |
348 // tab), by comparing the old and new WebContents. | |
349 if (escape_pressed && | |
350 instant_tab_->contents() == browser_->GetActiveWebContents()) { | |
351 instant_tab_->Submit(full_text); | |
352 } | |
353 } else if (!full_text.empty()) { | |
354 // If |full_text| is empty, the user is on the NTP. The overlay may | |
355 // be showing custom NTP content; hide only if that's not the case. | |
356 HideOverlay(); | |
357 } | |
358 } else if (full_text.empty()) { | |
359 // The user is typing, and backspaced away all omnibox text. Clear | |
360 // |last_omnibox_text_| so that we don't attempt to set suggestions. | |
361 last_omnibox_text_.clear(); | |
362 last_user_text_.clear(); | |
363 last_suggestion_ = InstantSuggestion(); | |
364 if (instant_tab_) { | |
365 // On a search results page, tell it to clear old results. | |
366 instant_tab_->Update(string16(), 0, 0, true); | |
367 } else if (search_mode_.is_origin_ntp()) { | |
368 // On the NTP, tell the overlay to clear old results. Don't hide the | |
369 // overlay so it can show a blank page or logo if it wants. | |
370 overlay_->Update(string16(), 0, 0, true); | |
371 } else { | |
372 HideOverlay(); | |
373 } | |
374 } else { | |
375 // The user switched to a tab with partial text already in the omnibox. | |
376 HideOverlay(); | |
377 | |
378 // The new tab may or may not be a search results page; we don't know | |
379 // since SearchModeChanged() hasn't been called yet. If it later turns | |
380 // out to be, we should store |full_text| now, so that if the user hits | |
381 // Enter, we'll send the correct query to instant_tab_->Submit(). If the | |
382 // partial text is not a query (|last_match_was_search_| is false), we | |
383 // won't Submit(), so no need to worry about that. | |
384 last_omnibox_text_ = full_text; | |
385 last_user_text_ = user_text; | |
386 last_suggestion_ = InstantSuggestion(); | |
387 } | |
388 return false; | |
389 } else if (full_text.empty()) { | |
390 // The user typed a solitary "?". Same as the backspace case above. | |
391 last_omnibox_text_.clear(); | |
392 last_user_text_.clear(); | |
393 last_suggestion_ = InstantSuggestion(); | |
394 if (instant_tab_) | |
395 instant_tab_->Update(string16(), 0, 0, true); | |
396 else if (search_mode_.is_origin_ntp()) | |
397 overlay_->Update(string16(), 0, 0, true); | |
398 else | |
399 HideOverlay(); | |
400 return false; | |
401 } | |
402 } else if (!omnibox_popup_is_open || full_text.empty()) { | |
403 // In the non-extended case, hide the overlay as long as the user isn't | |
404 // actively typing a non-empty query. | |
405 HideOverlay(); | |
406 return false; | |
407 } | |
408 | |
409 last_omnibox_text_has_inline_autocompletion_ = user_text != full_text; | |
410 | |
411 // If the user continues typing the same query as the suggested text is | |
412 // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER). | |
413 bool reused_suggestion = false; | |
414 if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && | |
415 !last_omnibox_text_has_inline_autocompletion_) { | |
416 if (StartsWith(last_omnibox_text_, full_text, false)) { | |
417 // The user is backspacing away characters. | |
418 last_suggestion_.text.insert(0, last_omnibox_text_, full_text.size(), | |
419 last_omnibox_text_.size() - full_text.size()); | |
420 reused_suggestion = true; | |
421 } else if (StartsWith(full_text, last_omnibox_text_, false)) { | |
422 // The user is typing forward. Normalize any added characters. | |
423 reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text, | |
424 string16(full_text, last_omnibox_text_.size())); | |
425 } | |
426 } | |
427 if (!reused_suggestion) | |
428 last_suggestion_ = InstantSuggestion(); | |
429 | |
430 last_omnibox_text_ = full_text; | |
431 last_user_text_ = user_text; | |
432 | |
433 if (!extended_enabled_) { | |
434 // In non-extended mode, the query is verbatim if there's any selection | |
435 // (including inline autocompletion) or if the cursor is not at the end. | |
436 verbatim = verbatim || selection_start != selection_end || | |
437 selection_start != full_text.size(); | |
438 } | |
439 last_verbatim_ = verbatim; | |
440 | |
441 last_transition_type_ = match.transition; | |
442 url_for_history_ = match.destination_url; | |
443 | |
444 // Allow search suggestions. In extended mode, SearchModeChanged() will set | |
445 // this, but it's not called in non-extended mode, so fake it. | |
446 if (!extended_enabled_) | |
447 search_mode_.mode = chrome::search::Mode::MODE_SEARCH_SUGGESTIONS; | |
448 | |
449 if (instant_tab_) { | |
450 instant_tab_->Update(user_text, selection_start, selection_end, verbatim); | |
451 } else { | |
452 if (first_interaction_time_.is_null()) | |
453 first_interaction_time_ = base::Time::Now(); | |
454 allow_overlay_to_show_search_suggestions_ = true; | |
455 | |
456 // For extended mode, if the loader is not ready at this point, switch over | |
457 // to a backup loader. | |
458 if (extended_enabled_ && !overlay_->supports_instant() && | |
459 !overlay_->IsUsingLocalOverlay() && browser_->GetActiveWebContents()) { | |
460 CreateOverlay(chrome::kChromeSearchLocalOmniboxPopupURL, | |
461 browser_->GetActiveWebContents()); | |
462 } | |
463 | |
464 overlay_->Update(extended_enabled_ ? user_text : full_text, | |
465 selection_start, selection_end, verbatim); | |
466 } | |
467 | |
468 content::NotificationService::current()->Notify( | |
469 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | |
470 content::Source<InstantController>(this), | |
471 content::NotificationService::NoDetails()); | |
472 | |
473 // We don't have new suggestions yet, but we can either reuse the existing | |
474 // suggestion or reset the existing "gray text". | |
475 browser_->SetInstantSuggestion(last_suggestion_); | |
476 | |
477 return true; | |
478 } | |
479 | |
480 scoped_ptr<content::WebContents> InstantController::ReleaseNTPContents() { | |
481 if (!extended_enabled_ || !ntp_) | |
482 return scoped_ptr<content::WebContents>(NULL); | |
483 | |
484 LOG_INSTANT_DEBUG_EVENT(this, "ReleaseNTPContents"); | |
485 | |
486 scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents(); | |
487 ntp_.reset(); | |
488 ResetNTP(); | |
489 return ntp_contents.Pass(); | |
490 } | |
491 | |
492 // TODO(tonyg): This method only fires when the omnibox bounds change. It also | |
493 // needs to fire when the overlay bounds change (e.g.: open/close info bar). | |
494 void InstantController::SetPopupBounds(const gfx::Rect& bounds) { | |
495 if (!extended_enabled_ && !instant_enabled_) | |
496 return; | |
497 | |
498 if (popup_bounds_ == bounds) | |
499 return; | |
500 | |
501 popup_bounds_ = bounds; | |
502 if (popup_bounds_.height() > last_popup_bounds_.height()) { | |
503 update_bounds_timer_.Stop(); | |
504 SendPopupBoundsToPage(); | |
505 } else if (!update_bounds_timer_.IsRunning()) { | |
506 update_bounds_timer_.Start(FROM_HERE, | |
507 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, | |
508 &InstantController::SendPopupBoundsToPage); | |
509 } | |
510 } | |
511 | |
512 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { | |
513 if (!extended_enabled_ || omnibox_bounds_ == bounds) | |
514 return; | |
515 | |
516 omnibox_bounds_ = bounds; | |
517 if (overlay_) | |
518 overlay_->SetOmniboxBounds(omnibox_bounds_); | |
519 if (ntp_) | |
520 ntp_->SetOmniboxBounds(omnibox_bounds_); | |
521 if (instant_tab_) | |
522 instant_tab_->SetOmniboxBounds(omnibox_bounds_); | |
523 } | |
524 | |
525 void InstantController::HandleAutocompleteResults( | |
526 const std::vector<AutocompleteProvider*>& providers) { | |
527 if (!extended_enabled_) | |
528 return; | |
529 | |
530 if (!instant_tab_ && !overlay_) | |
531 return; | |
532 | |
533 // The omnibox sends suggestions when its possibly imaginary popup closes | |
534 // as it stops autocomplete. Ignore these. | |
535 if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE) | |
536 return; | |
537 | |
538 DVLOG(1) << "AutocompleteResults:"; | |
539 std::vector<InstantAutocompleteResult> results; | |
540 for (ACProviders::const_iterator provider = providers.begin(); | |
541 provider != providers.end(); ++provider) { | |
542 // Skip SearchProvider, since it only echoes suggestions. | |
543 if ((*provider)->type() == AutocompleteProvider::TYPE_SEARCH) | |
544 continue; | |
545 for (ACMatches::const_iterator match = (*provider)->matches().begin(); | |
546 match != (*provider)->matches().end(); ++match) { | |
547 InstantAutocompleteResult result; | |
548 result.provider = UTF8ToUTF16((*provider)->GetName()); | |
549 result.type = UTF8ToUTF16(AutocompleteMatch::TypeToString(match->type)); | |
550 result.description = match->description; | |
551 result.destination_url = UTF8ToUTF16(match->destination_url.spec()); | |
552 result.transition = match->transition; | |
553 result.relevance = match->relevance; | |
554 DVLOG(1) << " " << result.relevance << " " << result.type << " " | |
555 << result.provider << " " << result.destination_url << " '" | |
556 << result.description << "' " << result.transition; | |
557 results.push_back(result); | |
558 } | |
559 } | |
560 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
561 "HandleAutocompleteResults: total_results=%d", | |
562 static_cast<int>(results.size()))); | |
563 | |
564 if (instant_tab_) | |
565 instant_tab_->SendAutocompleteResults(results); | |
566 else | |
567 overlay_->SendAutocompleteResults(results); | |
568 } | |
569 | |
570 bool InstantController::OnUpOrDownKeyPressed(int count) { | |
571 if (!extended_enabled_) | |
572 return false; | |
573 | |
574 if (!instant_tab_ && !overlay_) | |
575 return false; | |
576 | |
577 if (instant_tab_) | |
578 instant_tab_->UpOrDownKeyPressed(count); | |
579 else | |
580 overlay_->UpOrDownKeyPressed(count); | |
581 | |
582 return true; | |
583 } | |
584 | |
585 void InstantController::OnCancel(const AutocompleteMatch& match, | |
586 const string16& user_text, | |
587 const string16& full_text) { | |
588 if (!extended_enabled_) | |
589 return; | |
590 | |
591 if (!instant_tab_ && !overlay_) | |
592 return; | |
593 | |
594 // We manually reset the state here since the JS is not expected to do it. | |
595 // TODO(sreeram): Handle the case where user_text is now a URL | |
596 last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && | |
597 !full_text.empty(); | |
598 last_omnibox_text_ = full_text; | |
599 last_user_text_ = user_text; | |
600 last_suggestion_ = InstantSuggestion(); | |
601 | |
602 if (instant_tab_) | |
603 instant_tab_->CancelSelection(full_text); | |
604 else | |
605 overlay_->CancelSelection(full_text); | |
606 } | |
607 | |
608 content::WebContents* InstantController::GetOverlayContents() const { | |
609 return overlay_ ? overlay_->contents() : NULL; | |
610 } | |
611 | |
612 bool InstantController::IsOverlayingSearchResults() const { | |
613 return model_.mode().is_search_suggestions() && IsFullHeight(model_) && | |
614 (last_match_was_search_ || | |
615 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER); | |
616 } | |
617 | |
618 bool InstantController::CommitIfPossible(InstantCommitType type) { | |
619 if (!extended_enabled_ && !instant_enabled_) | |
620 return false; | |
621 | |
622 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
623 "CommitIfPossible: type=%d last_omnibox_text_='%s' " | |
624 "last_match_was_search_=%d instant_tab_=%d", type, | |
625 UTF16ToUTF8(last_omnibox_text_).c_str(), last_match_was_search_, | |
626 instant_tab_ != NULL)); | |
627 | |
628 // If we are on an already committed search results page, send a submit event | |
629 // to the page, but otherwise, nothing else to do. | |
630 if (instant_tab_) { | |
631 if (type == INSTANT_COMMIT_PRESSED_ENTER && | |
632 (last_match_was_search_ || | |
633 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER)) { | |
634 last_suggestion_.text.clear(); | |
635 instant_tab_->Submit(last_omnibox_text_); | |
636 instant_tab_->contents()->GetView()->Focus(); | |
637 EnsureSearchTermsAreSet(instant_tab_->contents(), last_omnibox_text_); | |
638 return true; | |
639 } | |
640 return false; | |
641 } | |
642 | |
643 // If the overlay is not showing at all, don't commit it. | |
644 if (!model_.mode().is_search_suggestions()) | |
645 return false; | |
646 | |
647 // If the overlay is showing at full height (with results), commit it. | |
648 // If it's showing at parial height, commit if it's navigating. | |
649 if (!IsOverlayingSearchResults() && type != INSTANT_COMMIT_NAVIGATED) | |
650 return false; | |
651 | |
652 // There may re-entrance here, from the call to browser_->CommitInstant below, | |
653 // which can cause a TabDeactivated notification which gets back here. | |
654 // In this case, overlay_->ReleaseContents() was called already. | |
655 if (!GetOverlayContents()) | |
656 return false; | |
657 | |
658 // Never commit the local overlay. | |
659 if (overlay_->IsUsingLocalOverlay()) | |
660 return false; | |
661 | |
662 if (type == INSTANT_COMMIT_FOCUS_LOST) { | |
663 // Extended mode doesn't need or use the Cancel message. | |
664 if (!extended_enabled_) | |
665 overlay_->Cancel(last_omnibox_text_); | |
666 } else if (type != INSTANT_COMMIT_NAVIGATED) { | |
667 overlay_->Submit(last_omnibox_text_); | |
668 } | |
669 | |
670 scoped_ptr<content::WebContents> overlay = overlay_->ReleaseContents(); | |
671 | |
672 // If the overlay page has navigated since the last Update(), we need to add | |
673 // the navigation to history ourselves. Else, the page will navigate after | |
674 // commit, and it will be added to history in the usual manner. | |
675 const history::HistoryAddPageArgs& last_navigation = | |
676 overlay_->last_navigation(); | |
677 if (!last_navigation.url.is_empty()) { | |
678 content::NavigationEntry* entry = overlay->GetController().GetActiveEntry(); | |
679 | |
680 // The last navigation should be the same as the active entry if the overlay | |
681 // is in search mode. During navigation, the active entry could have | |
682 // changed since DidCommitProvisionalLoadForFrame is called after the entry | |
683 // is changed. | |
684 // TODO(shishir): Should we commit the last navigation for | |
685 // INSTANT_COMMIT_NAVIGATED. | |
686 DCHECK(type == INSTANT_COMMIT_NAVIGATED || | |
687 last_navigation.url == entry->GetURL()); | |
688 | |
689 // Add the page to history. | |
690 HistoryTabHelper* history_tab_helper = | |
691 HistoryTabHelper::FromWebContents(overlay.get()); | |
692 history_tab_helper->UpdateHistoryForNavigation(last_navigation); | |
693 | |
694 // Update the page title. | |
695 history_tab_helper->UpdateHistoryPageTitle(*entry); | |
696 } | |
697 | |
698 // Add a fake history entry with a non-Instant search URL, so that search | |
699 // terms extraction (for autocomplete history matches) works. | |
700 HistoryService* history = HistoryServiceFactory::GetForProfile( | |
701 Profile::FromBrowserContext(overlay->GetBrowserContext()), | |
702 Profile::EXPLICIT_ACCESS); | |
703 if (history) { | |
704 history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), | |
705 history::RedirectList(), last_transition_type_, | |
706 history::SOURCE_BROWSED, false); | |
707 } | |
708 | |
709 if (type == INSTANT_COMMIT_PRESSED_ALT_ENTER) { | |
710 overlay->GetController().PruneAllButActive(); | |
711 } else { | |
712 content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
713 AddSessionStorageHistogram(extended_enabled_, active_tab, overlay.get()); | |
714 overlay->GetController().CopyStateFromAndPrune( | |
715 &active_tab->GetController()); | |
716 } | |
717 | |
718 if (extended_enabled_) { | |
719 // Adjust the search terms shown in the omnibox for this query. Hitting | |
720 // ENTER searches for what the user typed, so use last_omnibox_text_. | |
721 // Clicking on the overlay commits what is currently showing, so add in the | |
722 // gray text in that case. | |
723 if (type == INSTANT_COMMIT_FOCUS_LOST && | |
724 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER) { | |
725 // Update |last_omnibox_text_| so that the controller commits the proper | |
726 // query if the user focuses the omnibox and presses Enter. | |
727 last_omnibox_text_ += last_suggestion_.text; | |
728 } | |
729 | |
730 EnsureSearchTermsAreSet(overlay.get(), last_omnibox_text_); | |
731 } | |
732 | |
733 // Save notification source before we release the overlay. | |
734 content::Source<content::WebContents> notification_source(overlay.get()); | |
735 | |
736 browser_->CommitInstant(overlay.Pass(), | |
737 type == INSTANT_COMMIT_PRESSED_ALT_ENTER); | |
738 | |
739 content::NotificationService::current()->Notify( | |
740 chrome::NOTIFICATION_INSTANT_COMMITTED, | |
741 notification_source, | |
742 content::NotificationService::NoDetails()); | |
743 | |
744 // Hide explicitly. See comments in HideOverlay() for why. | |
745 model_.SetOverlayState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
746 | |
747 // Delay deletion as we could've gotten here from an InstantOverlay method. | |
748 MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); | |
749 | |
750 // Try to create another overlay immediately so that it is ready for the next | |
751 // user interaction. | |
752 EnsureOverlayIsCurrent(false); | |
753 | |
754 LOG_INSTANT_DEBUG_EVENT(this, "Committed"); | |
755 return true; | |
756 } | |
757 | |
758 void InstantController::OmniboxFocusChanged( | |
759 OmniboxFocusState state, | |
760 OmniboxFocusChangeReason reason, | |
761 gfx::NativeView view_gaining_focus) { | |
762 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
763 "OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_, | |
764 state, reason)); | |
765 | |
766 OmniboxFocusState old_focus_state = omnibox_focus_state_; | |
767 omnibox_focus_state_ = state; | |
768 if (!extended_enabled_ && !instant_enabled_) | |
769 return; | |
770 | |
771 // Tell the page if the key capture mode changed unless the focus state | |
772 // changed because of TYPING. This is because in that case, the browser hasn't | |
773 // really stopped capturing key strokes. | |
774 // | |
775 // (More practically, if we don't do this check, the page would receive | |
776 // onkeycapturechange before the corresponding onchange, and the page would | |
777 // have no way of telling whether the keycapturechange happened because of | |
778 // some actual user action or just because they started typing.) | |
779 if (extended_enabled_ && GetOverlayContents() && | |
780 reason != OMNIBOX_FOCUS_CHANGE_TYPING) { | |
781 const bool is_key_capture_enabled = | |
782 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE; | |
783 if (overlay_) | |
784 overlay_->KeyCaptureChanged(is_key_capture_enabled); | |
785 if (instant_tab_) | |
786 instant_tab_->KeyCaptureChanged(is_key_capture_enabled); | |
787 } | |
788 | |
789 // If focus went from outside the omnibox to the omnibox, preload the default | |
790 // search engine, in anticipation of the user typing a query. If the reverse | |
791 // happened, commit or discard the overlay. | |
792 if (state != OMNIBOX_FOCUS_NONE && old_focus_state == OMNIBOX_FOCUS_NONE) { | |
793 // On explicit user actions, ignore the Instant blacklist. | |
794 EnsureOverlayIsCurrent(reason == OMNIBOX_FOCUS_CHANGE_EXPLICIT); | |
795 } else if (state == OMNIBOX_FOCUS_NONE && | |
796 old_focus_state != OMNIBOX_FOCUS_NONE) { | |
797 OmniboxLostFocus(view_gaining_focus); | |
798 } | |
799 } | |
800 | |
801 void InstantController::SearchModeChanged( | |
802 const chrome::search::Mode& old_mode, | |
803 const chrome::search::Mode& new_mode) { | |
804 if (!extended_enabled_) | |
805 return; | |
806 | |
807 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
808 "SearchModeChanged: [origin:mode] %d:%d to %d:%d", old_mode.origin, | |
809 old_mode.mode, new_mode.origin, new_mode.mode)); | |
810 | |
811 search_mode_ = new_mode; | |
812 if (!new_mode.is_search_suggestions()) | |
813 HideOverlay(); | |
814 | |
815 ResetInstantTab(); | |
816 } | |
817 | |
818 void InstantController::ActiveTabChanged() { | |
819 if (!extended_enabled_ && !instant_enabled_) | |
820 return; | |
821 | |
822 LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged"); | |
823 | |
824 // When switching tabs, always hide the overlay. | |
825 HideOverlay(); | |
826 | |
827 if (extended_enabled_) | |
828 ResetInstantTab(); | |
829 } | |
830 | |
831 void InstantController::TabDeactivated(content::WebContents* contents) { | |
832 LOG_INSTANT_DEBUG_EVENT(this, "TabDeactivated"); | |
833 if (extended_enabled_ && !contents->IsBeingDestroyed()) | |
834 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
835 } | |
836 | |
837 void InstantController::SetInstantEnabled(bool instant_enabled, | |
838 bool use_local_overlay_only) { | |
839 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
840 "SetInstantEnabled: instant_enabled=%d, use_local_overlay_only=%d", | |
841 instant_enabled, use_local_overlay_only)); | |
842 | |
843 // Non extended mode does not care about |use_local_overlay_only|. | |
844 if (instant_enabled == instant_enabled_ && | |
845 (!extended_enabled_ || | |
846 use_local_overlay_only == use_local_overlay_only_)) { | |
847 return; | |
848 } | |
849 | |
850 instant_enabled_ = instant_enabled; | |
851 use_local_overlay_only_ = use_local_overlay_only; | |
852 HideInternal(); | |
853 overlay_.reset(); | |
854 if (extended_enabled_ || instant_enabled_) | |
855 EnsureOverlayIsCurrent(false); | |
856 if (extended_enabled_) | |
857 ResetNTP(); | |
858 if (instant_tab_) | |
859 instant_tab_->SetDisplayInstantResults(instant_enabled_); | |
860 } | |
861 | |
862 void InstantController::ThemeChanged(const ThemeBackgroundInfo& theme_info) { | |
863 if (!extended_enabled_) | |
864 return; | |
865 | |
866 if (overlay_) | |
867 overlay_->SendThemeBackgroundInfo(theme_info); | |
868 if (ntp_) | |
869 ntp_->SendThemeBackgroundInfo(theme_info); | |
870 if (instant_tab_) | |
871 instant_tab_->SendThemeBackgroundInfo(theme_info); | |
872 } | |
873 | |
874 void InstantController::SwappedOverlayContents() { | |
875 model_.SetOverlayContents(GetOverlayContents()); | |
876 } | |
877 | |
878 void InstantController::FocusedOverlayContents() { | |
879 #if defined(USE_AURA) | |
880 // On aura the omnibox only receives a focus lost if we initiate the focus | |
881 // change. This does that. | |
882 if (!model_.mode().is_default()) | |
883 browser_->InstantOverlayFocused(); | |
884 #endif | |
885 } | |
886 | |
887 void InstantController::ReloadOverlayIfStale() { | |
888 // The local overlay is never stale. | |
889 if (overlay_ && overlay_->IsUsingLocalOverlay()) | |
890 return; | |
891 | |
892 // If the overlay is showing or the omnibox has focus, don't delete the | |
893 // overlay. It will get refreshed the next time the overlay is hidden or the | |
894 // omnibox loses focus. | |
895 if ((!overlay_ || overlay_->is_stale()) && | |
896 omnibox_focus_state_ == OMNIBOX_FOCUS_NONE && | |
897 model_.mode().is_default()) { | |
898 overlay_.reset(); | |
899 EnsureOverlayIsCurrent(false); | |
900 } | |
901 } | |
902 | |
903 void InstantController::OverlayLoadCompletedMainFrame() { | |
904 if (overlay_->supports_instant()) | |
905 return; | |
906 InstantService* instant_service = | |
907 InstantServiceFactory::GetForProfile(browser_->profile()); | |
908 content::WebContents* contents = overlay_->contents(); | |
909 DCHECK(contents); | |
910 if (instant_service->IsInstantProcess( | |
911 contents->GetRenderProcessHost()->GetID())) { | |
912 return; | |
913 } | |
914 InstantSupportDetermined(contents, false); | |
915 } | |
916 | |
917 void InstantController::LogDebugEvent(const std::string& info) const { | |
918 DVLOG(1) << info; | |
919 | |
920 debug_events_.push_front(std::make_pair( | |
921 base::Time::Now().ToInternalValue(), info)); | |
922 static const size_t kMaxDebugEventSize = 2000; | |
923 if (debug_events_.size() > kMaxDebugEventSize) | |
924 debug_events_.pop_back(); | |
925 } | |
926 | |
927 void InstantController::ClearDebugEvents() { | |
928 debug_events_.clear(); | |
929 } | |
930 | |
931 void InstantController::DeleteMostVisitedItem(uint64 most_visited_item_id) { | |
932 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
933 if (!top_sites) | |
934 return; | |
935 | |
936 GURL url; | |
937 if (GetURLForMostVisitedItemId(browser_->profile(), | |
938 most_visited_item_id, &url)) | |
939 top_sites->AddBlacklistedURL(url); | |
940 } | |
941 | |
942 void InstantController::UndoMostVisitedDeletion(uint64 most_visited_item_id) { | |
943 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
944 if (!top_sites) | |
945 return; | |
946 | |
947 GURL url; | |
948 if (GetURLForMostVisitedItemId(browser_->profile(), | |
949 most_visited_item_id, &url)) | |
950 top_sites->RemoveBlacklistedURL(url); | |
951 } | |
952 | |
953 void InstantController::UndoAllMostVisitedDeletions() { | |
954 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
955 if (!top_sites) | |
956 return; | |
957 | |
958 top_sites->ClearBlacklistedURLs(); | |
959 } | |
960 | |
961 void InstantController::Observe(int type, | |
962 const content::NotificationSource& source, | |
963 const content::NotificationDetails& details) { | |
964 DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED); | |
965 RequestMostVisitedItems(); | |
966 } | |
967 | |
968 // TODO(shishir): We assume that the WebContent's current RenderViewHost is the | |
969 // RenderViewHost being created which is not always true. Fix this. | |
970 void InstantController::InstantPageRenderViewCreated( | |
971 const content::WebContents* contents) { | |
972 if (!extended_enabled_) | |
973 return; | |
974 | |
975 // Update theme info so that the page picks it up. | |
976 browser_->UpdateThemeInfo(); | |
977 | |
978 // Ensure the searchbox API has the correct initial state. | |
979 if (IsContentsFrom(overlay(), contents)) { | |
980 overlay_->SetDisplayInstantResults(instant_enabled_); | |
981 overlay_->KeyCaptureChanged( | |
982 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE); | |
983 overlay_->SetOmniboxBounds(omnibox_bounds_); | |
984 overlay_->InitializeFonts(); | |
985 overlay_->GrantChromeSearchAccessFromOrigin(GURL(overlay_->instant_url())); | |
986 } else if (IsContentsFrom(ntp(), contents)) { | |
987 ntp_->SetDisplayInstantResults(instant_enabled_); | |
988 ntp_->SetOmniboxBounds(omnibox_bounds_); | |
989 ntp_->InitializeFonts(); | |
990 ntp_->GrantChromeSearchAccessFromOrigin(GURL(ntp_->instant_url())); | |
991 } else { | |
992 NOTREACHED(); | |
993 } | |
994 StartListeningToMostVisitedChanges(); | |
995 } | |
996 | |
997 void InstantController::InstantSupportDetermined( | |
998 const content::WebContents* contents, | |
999 bool supports_instant) { | |
1000 if (IsContentsFrom(instant_tab(), contents)) { | |
1001 if (!supports_instant) | |
1002 MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release()); | |
1003 } else if (IsContentsFrom(ntp(), contents)) { | |
1004 if (supports_instant) | |
1005 RemoveFromBlacklist(ntp_->instant_url()); | |
1006 else | |
1007 BlacklistAndResetNTP(); | |
1008 | |
1009 content::NotificationService::current()->Notify( | |
1010 chrome::NOTIFICATION_INSTANT_NTP_SUPPORT_DETERMINED, | |
1011 content::Source<InstantController>(this), | |
1012 content::NotificationService::NoDetails()); | |
1013 | |
1014 } else if (IsContentsFrom(overlay(), contents)) { | |
1015 if (supports_instant) | |
1016 RemoveFromBlacklist(overlay_->instant_url()); | |
1017 else | |
1018 BlacklistAndResetOverlay(); | |
1019 | |
1020 content::NotificationService::current()->Notify( | |
1021 chrome::NOTIFICATION_INSTANT_OVERLAY_SUPPORT_DETERMINED, | |
1022 content::Source<InstantController>(this), | |
1023 content::NotificationService::NoDetails()); | |
1024 } | |
1025 } | |
1026 | |
1027 void InstantController::InstantPageRenderViewGone( | |
1028 const content::WebContents* contents) { | |
1029 if (IsContentsFrom(overlay(), contents)) | |
1030 BlacklistAndResetOverlay(); | |
1031 else if (IsContentsFrom(ntp(), contents)) | |
1032 BlacklistAndResetNTP(); | |
1033 else | |
1034 NOTREACHED(); | |
1035 } | |
1036 | |
1037 void InstantController::InstantPageAboutToNavigateMainFrame( | |
1038 const content::WebContents* contents, | |
1039 const GURL& url) { | |
1040 DCHECK(IsContentsFrom(overlay(), contents)); | |
1041 | |
1042 // If the page does not yet support Instant, we allow redirects and other | |
1043 // navigations to go through since the Instant URL can redirect - e.g. to | |
1044 // country specific pages. | |
1045 if (!overlay_->supports_instant()) | |
1046 return; | |
1047 | |
1048 GURL instant_url(overlay_->instant_url()); | |
1049 | |
1050 // If we are navigating to the Instant URL, do nothing. | |
1051 if (url == instant_url) | |
1052 return; | |
1053 | |
1054 // Commit the navigation if either: | |
1055 // - The page is in NTP mode (so it could only navigate on a user click) or | |
1056 // - The page is not in NTP mode and we are navigating to a URL with a | |
1057 // different host or path than the Instant URL. This enables the instant | |
1058 // page when it is showing search results to change the query parameters | |
1059 // and fragments of the URL without it navigating. | |
1060 if (model_.mode().is_ntp() || | |
1061 (url.host() != instant_url.host() || url.path() != instant_url.path())) { | |
1062 CommitIfPossible(INSTANT_COMMIT_NAVIGATED); | |
1063 } | |
1064 } | |
1065 | |
1066 void InstantController::SetSuggestions( | |
1067 const content::WebContents* contents, | |
1068 const std::vector<InstantSuggestion>& suggestions) { | |
1069 LOG_INSTANT_DEBUG_EVENT(this, "SetSuggestions"); | |
1070 | |
1071 // Ignore if the message is from an unexpected source. | |
1072 if (IsContentsFrom(ntp(), contents)) | |
1073 return; | |
1074 if (instant_tab_ && !IsContentsFrom(instant_tab(), contents)) | |
1075 return; | |
1076 if (IsContentsFrom(overlay(), contents) && | |
1077 !allow_overlay_to_show_search_suggestions_) | |
1078 return; | |
1079 | |
1080 InstantSuggestion suggestion; | |
1081 if (!suggestions.empty()) | |
1082 suggestion = suggestions[0]; | |
1083 | |
1084 if (instant_tab_ && search_mode_.is_search_results() && | |
1085 suggestion.behavior == INSTANT_COMPLETE_REPLACE) { | |
1086 // Update |last_omnibox_text_| so that the controller commits the proper | |
1087 // query if the user focuses the omnibox and presses Enter. | |
1088 last_omnibox_text_ = suggestion.text; | |
1089 last_suggestion_ = InstantSuggestion(); | |
1090 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | |
1091 // This means a committed page in state search called setValue(). We should | |
1092 // update the omnibox to reflect what the search page says. | |
1093 browser_->SetInstantSuggestion(suggestion); | |
1094 return; | |
1095 } | |
1096 | |
1097 // Ignore if we are not currently accepting search suggestions. | |
1098 if (!search_mode_.is_search_suggestions() || last_omnibox_text_.empty()) | |
1099 return; | |
1100 | |
1101 if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) { | |
1102 // We don't get an Update() when changing the omnibox due to a REPLACE | |
1103 // suggestion (so that we don't inadvertently cause the overlay to change | |
1104 // what it's showing, as the user arrows up/down through the page-provided | |
1105 // suggestions). So, update these state variables here. | |
1106 last_omnibox_text_ = suggestion.text; | |
1107 last_suggestion_ = InstantSuggestion(); | |
1108 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | |
1109 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1110 "ReplaceSuggestion text='%s' type=%d", | |
1111 UTF16ToUTF8(suggestion.text).c_str(), suggestion.type)); | |
1112 browser_->SetInstantSuggestion(suggestion); | |
1113 } else { | |
1114 if (FixSuggestion(&suggestion)) { | |
1115 last_suggestion_ = suggestion; | |
1116 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1117 "SetInstantSuggestion: text='%s' behavior=%d", | |
1118 UTF16ToUTF8(suggestion.text).c_str(), | |
1119 suggestion.behavior)); | |
1120 browser_->SetInstantSuggestion(suggestion); | |
1121 content::NotificationService::current()->Notify( | |
1122 chrome::NOTIFICATION_INSTANT_SET_SUGGESTION, | |
1123 content::Source<InstantController>(this), | |
1124 content::NotificationService::NoDetails()); | |
1125 } else { | |
1126 last_suggestion_ = InstantSuggestion(); | |
1127 } | |
1128 } | |
1129 | |
1130 // Extended mode pages will call ShowOverlay() when they are ready. | |
1131 if (!extended_enabled_) | |
1132 ShowOverlay(100, INSTANT_SIZE_PERCENT); | |
1133 } | |
1134 | |
1135 void InstantController::ShowInstantOverlay(const content::WebContents* contents, | |
1136 int height, | |
1137 InstantSizeUnits units) { | |
1138 if (extended_enabled_ && IsContentsFrom(overlay(), contents)) | |
1139 ShowOverlay(height, units); | |
1140 } | |
1141 | |
1142 void InstantController::FocusOmnibox(const content::WebContents* contents) { | |
1143 if (!extended_enabled_) | |
1144 return; | |
1145 | |
1146 DCHECK(IsContentsFrom(instant_tab(), contents)); | |
1147 browser_->FocusOmnibox(true); | |
1148 } | |
1149 | |
1150 void InstantController::StartCapturingKeyStrokes( | |
1151 const content::WebContents* contents) { | |
1152 if (!extended_enabled_) | |
1153 return; | |
1154 | |
1155 DCHECK(IsContentsFrom(instant_tab(), contents)); | |
1156 browser_->FocusOmnibox(false); | |
1157 } | |
1158 | |
1159 void InstantController::StopCapturingKeyStrokes( | |
1160 content::WebContents* contents) { | |
1161 // Nothing to do if omnibox doesn't have invisible focus. | |
1162 if (!extended_enabled_ || omnibox_focus_state_ != OMNIBOX_FOCUS_INVISIBLE) | |
1163 return; | |
1164 | |
1165 DCHECK(IsContentsFrom(instant_tab(), contents)); | |
1166 contents->GetView()->Focus(); | |
1167 } | |
1168 | |
1169 void InstantController::NavigateToURL(const content::WebContents* contents, | |
1170 const GURL& url, | |
1171 content::PageTransition transition, | |
1172 WindowOpenDisposition disposition) { | |
1173 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1174 "NavigateToURL: url='%s'", url.spec().c_str())); | |
1175 | |
1176 // TODO(samarth): handle case where contents are no longer "active" (e.g. user | |
1177 // has switched tabs). | |
1178 if (!extended_enabled_) | |
1179 return; | |
1180 if (overlay_) | |
1181 HideOverlay(); | |
1182 | |
1183 if (transition == content::PAGE_TRANSITION_AUTO_BOOKMARK) { | |
1184 content::RecordAction( | |
1185 content::UserMetricsAction("InstantExtended.MostVisitedClicked")); | |
1186 } | |
1187 browser_->OpenURL(url, transition, disposition); | |
1188 } | |
1189 | |
1190 void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) { | |
1191 // If the overlay is showing custom NTP content, don't hide it, commit it | |
1192 // (no matter where the user clicked) or try to recreate it. | |
1193 if (model_.mode().is_ntp()) | |
1194 return; | |
1195 | |
1196 if (model_.mode().is_default()) { | |
1197 // Correct search terms if the user clicked on the committed results page | |
1198 // while showing an autocomplete suggestion | |
1199 if (instant_tab_ && !last_suggestion_.text.empty() && | |
1200 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && | |
1201 IsViewInContents(GetViewGainingFocus(view_gaining_focus), | |
1202 instant_tab_->contents())) { | |
1203 // Commit the omnibox's suggested grey text as if the user had typed it. | |
1204 browser_->CommitSuggestedText(true); | |
1205 | |
1206 // Update the state so that next query from hitting Enter from the | |
1207 // omnibox is correct. | |
1208 last_omnibox_text_ += last_suggestion_.text; | |
1209 last_suggestion_ = InstantSuggestion(); | |
1210 } | |
1211 // If the overlay is not showing at all, recreate it if it's stale. | |
1212 ReloadOverlayIfStale(); | |
1213 MaybeSwitchToRemoteOverlay(); | |
1214 return; | |
1215 } | |
1216 | |
1217 // The overlay is showing search suggestions. If GetOverlayContents() is NULL, | |
1218 // we are in the commit path. Don't do anything. | |
1219 if (!GetOverlayContents()) | |
1220 return; | |
1221 | |
1222 #if defined(OS_MACOSX) | |
1223 // TODO(sreeram): See if Mac really needs this special treatment. | |
1224 if (!overlay_->is_pointer_down_from_activate()) | |
1225 HideOverlay(); | |
1226 #else | |
1227 if (IsFullHeight(model_)) | |
1228 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
1229 else if (!IsViewInContents(GetViewGainingFocus(view_gaining_focus), | |
1230 overlay_->contents())) | |
1231 HideOverlay(); | |
1232 #endif | |
1233 } | |
1234 | |
1235 void InstantController::ResetNTP() { | |
1236 ntp_.reset(); | |
1237 std::string instant_url; | |
1238 if (!GetInstantURL(browser_->profile(), false, &instant_url)) | |
1239 return; | |
1240 | |
1241 ntp_.reset(new InstantNTP(this, instant_url)); | |
1242 ntp_->InitContents(browser_->profile(), browser_->GetActiveWebContents(), | |
1243 base::Bind(&InstantController::ResetNTP, | |
1244 base::Unretained(this))); | |
1245 } | |
1246 | |
1247 bool InstantController::EnsureOverlayIsCurrent(bool ignore_blacklist) { | |
1248 // If there's no active tab, the browser is closing. | |
1249 const content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
1250 if (!active_tab) | |
1251 return false; | |
1252 | |
1253 Profile* profile = Profile::FromBrowserContext( | |
1254 active_tab->GetBrowserContext()); | |
1255 std::string instant_url; | |
1256 if (!GetInstantURL(profile, ignore_blacklist, &instant_url)) { | |
1257 // If we are in extended mode, fallback to the local overlay. | |
1258 if (extended_enabled_) | |
1259 instant_url = chrome::kChromeSearchLocalOmniboxPopupURL; | |
1260 else | |
1261 return false; | |
1262 } | |
1263 | |
1264 if (!overlay_ || overlay_->instant_url() != instant_url) | |
1265 CreateOverlay(instant_url, active_tab); | |
1266 | |
1267 return true; | |
1268 } | |
1269 | |
1270 void InstantController::CreateOverlay(const std::string& instant_url, | |
1271 const content::WebContents* active_tab) { | |
1272 HideInternal(); | |
1273 overlay_.reset(new InstantOverlay(this, instant_url)); | |
1274 overlay_->InitContents(browser_->profile(), active_tab); | |
1275 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1276 "CreateOverlay: instant_url='%s'", instant_url.c_str())); | |
1277 } | |
1278 | |
1279 void InstantController::MaybeSwitchToRemoteOverlay() { | |
1280 if (!overlay_ || omnibox_focus_state_ != OMNIBOX_FOCUS_NONE || | |
1281 !model_.mode().is_default()) { | |
1282 return; | |
1283 } | |
1284 | |
1285 EnsureOverlayIsCurrent(false); | |
1286 } | |
1287 | |
1288 void InstantController::ResetInstantTab() { | |
1289 // Do not wire up the InstantTab if Instant should only use local overlays, to | |
1290 // prevent it from sending data to the page. | |
1291 if (!search_mode_.is_origin_default() && !use_local_overlay_only_) { | |
1292 content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
1293 if (!instant_tab_ || active_tab != instant_tab_->contents()) { | |
1294 instant_tab_.reset(new InstantTab(this)); | |
1295 instant_tab_->Init(active_tab); | |
1296 // Update theme info for this tab. | |
1297 browser_->UpdateThemeInfo(); | |
1298 instant_tab_->SetDisplayInstantResults(instant_enabled_); | |
1299 instant_tab_->SetOmniboxBounds(omnibox_bounds_); | |
1300 instant_tab_->InitializeFonts(); | |
1301 instant_tab_->GrantChromeSearchAccessFromOrigin(active_tab->GetURL()); | |
1302 StartListeningToMostVisitedChanges(); | |
1303 instant_tab_->KeyCaptureChanged( | |
1304 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE); | |
1305 } | |
1306 | |
1307 // Hide the |overlay_| since we are now using |instant_tab_| instead. | |
1308 HideOverlay(); | |
1309 } else { | |
1310 instant_tab_.reset(); | |
1311 } | |
1312 } | |
1313 | |
1314 void InstantController::HideOverlay() { | |
1315 HideInternal(); | |
1316 ReloadOverlayIfStale(); | |
1317 MaybeSwitchToRemoteOverlay(); | |
1318 } | |
1319 | |
1320 void InstantController::HideInternal() { | |
1321 LOG_INSTANT_DEBUG_EVENT(this, "Hide"); | |
1322 | |
1323 // If GetOverlayContents() returns NULL, either we're already in the desired | |
1324 // MODE_DEFAULT state, or we're in the commit path. For the latter, don't | |
1325 // change the state just yet; else we may hide the overlay unnecessarily. | |
1326 // Instead, the state will be set correctly after the commit is done. | |
1327 if (GetOverlayContents()) { | |
1328 model_.SetOverlayState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
1329 allow_overlay_to_show_search_suggestions_ = false; | |
1330 | |
1331 // Send a message asking the overlay to clear out old results. | |
1332 overlay_->Update(string16(), 0, 0, true); | |
1333 } | |
1334 | |
1335 // Clear the first interaction timestamp for later use. | |
1336 first_interaction_time_ = base::Time(); | |
1337 } | |
1338 | |
1339 void InstantController::ShowOverlay(int height, InstantSizeUnits units) { | |
1340 // If we are on a committed search results page, the |overlay_| is not in use. | |
1341 if (instant_tab_) | |
1342 return; | |
1343 | |
1344 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1345 "Show: height=%d units=%d", height, units)); | |
1346 | |
1347 // Must have updated omnibox after the last HideOverlay() to show suggestions. | |
1348 if (!allow_overlay_to_show_search_suggestions_) | |
1349 return; | |
1350 | |
1351 // The page is trying to hide itself. Hide explicitly (i.e., don't use | |
1352 // HideOverlay()) so that it can change its mind. | |
1353 if (height == 0) { | |
1354 model_.SetOverlayState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
1355 return; | |
1356 } | |
1357 | |
1358 // If the overlay is being shown for the first time since the user started | |
1359 // typing, record a histogram value. | |
1360 if (!first_interaction_time_.is_null() && model_.mode().is_default()) { | |
1361 base::TimeDelta delta = base::Time::Now() - first_interaction_time_; | |
1362 UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShow", delta); | |
1363 } | |
1364 | |
1365 // Show at 100% height except in the following cases: | |
1366 // - The local overlay (omnibox popup) is being loaded. | |
1367 // - Instant is disabled. The page needs to be able to show only a dropdown. | |
1368 // - The page is over a website other than search or an NTP, and is not | |
1369 // already showing at 100% height. | |
1370 if (overlay_->IsUsingLocalOverlay() || !instant_enabled_ || | |
1371 (search_mode_.is_origin_default() && !IsFullHeight(model_))) | |
1372 model_.SetOverlayState(search_mode_, height, units); | |
1373 else | |
1374 model_.SetOverlayState(search_mode_, 100, INSTANT_SIZE_PERCENT); | |
1375 | |
1376 // If the overlay is being shown at full height and the omnibox is not | |
1377 // focused, commit right away. | |
1378 if (IsFullHeight(model_) && omnibox_focus_state_ == OMNIBOX_FOCUS_NONE) | |
1379 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
1380 } | |
1381 | |
1382 void InstantController::SendPopupBoundsToPage() { | |
1383 if (last_popup_bounds_ == popup_bounds_ || !overlay_ || | |
1384 overlay_->is_pointer_down_from_activate()) | |
1385 return; | |
1386 | |
1387 last_popup_bounds_ = popup_bounds_; | |
1388 gfx::Rect overlay_bounds = browser_->GetInstantBounds(); | |
1389 gfx::Rect intersection = gfx::IntersectRects(popup_bounds_, overlay_bounds); | |
1390 | |
1391 // Translate into window coordinates. | |
1392 if (!intersection.IsEmpty()) { | |
1393 intersection.Offset(-overlay_bounds.origin().x(), | |
1394 -overlay_bounds.origin().y()); | |
1395 } | |
1396 | |
1397 // In the current Chrome UI, these must always be true so they sanity check | |
1398 // the above operations. In a future UI, these may be removed or adjusted. | |
1399 // There is no point in sanity-checking |intersection.y()| because the omnibox | |
1400 // can be placed anywhere vertically relative to the overlay (for example, in | |
1401 // Mac fullscreen mode, the omnibox is fully enclosed by the overlay bounds). | |
1402 DCHECK_LE(0, intersection.x()); | |
1403 DCHECK_LE(0, intersection.width()); | |
1404 DCHECK_LE(0, intersection.height()); | |
1405 | |
1406 overlay_->SetPopupBounds(intersection); | |
1407 } | |
1408 | |
1409 bool InstantController::GetInstantURL(Profile* profile, | |
1410 bool ignore_blacklist, | |
1411 std::string* instant_url) const { | |
1412 DCHECK(profile); | |
1413 instant_url->clear(); | |
1414 | |
1415 if (extended_enabled_ && use_local_overlay_only_) { | |
1416 *instant_url = chrome::kChromeSearchLocalOmniboxPopupURL; | |
1417 return true; | |
1418 } | |
1419 | |
1420 const GURL instant_url_obj = | |
1421 chrome::search::GetInstantURL(profile, omnibox_bounds_.x()); | |
1422 if (!instant_url_obj.is_valid()) | |
1423 return false; | |
1424 | |
1425 *instant_url = instant_url_obj.spec(); | |
1426 | |
1427 if (!ignore_blacklist) { | |
1428 std::map<std::string, int>::const_iterator iter = | |
1429 blacklisted_urls_.find(*instant_url); | |
1430 if (iter != blacklisted_urls_.end() && | |
1431 iter->second > kMaxInstantSupportFailures) { | |
1432 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_BLOCKED_BY_BLACKLIST); | |
1433 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
1434 "GetInstantURL: Instant URL blacklisted: url=%s", | |
1435 instant_url->c_str())); | |
1436 return false; | |
1437 } | |
1438 } | |
1439 | |
1440 return true; | |
1441 } | |
1442 | |
1443 void InstantController::BlacklistAndResetNTP() { | |
1444 ++blacklisted_urls_[ntp_->instant_url()]; | |
1445 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST); | |
1446 delete ntp_->ReleaseContents().release(); | |
1447 MessageLoop::current()->DeleteSoon(FROM_HERE, ntp_.release()); | |
1448 ResetNTP(); | |
1449 } | |
1450 | |
1451 void InstantController::BlacklistAndResetOverlay() { | |
1452 ++blacklisted_urls_[overlay_->instant_url()]; | |
1453 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST); | |
1454 HideInternal(); | |
1455 delete overlay_->ReleaseContents().release(); | |
1456 MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); | |
1457 EnsureOverlayIsCurrent(false); | |
1458 } | |
1459 | |
1460 void InstantController::RemoveFromBlacklist(const std::string& url) { | |
1461 if (blacklisted_urls_.erase(url)) { | |
1462 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_REMOVED_FROM_BLACKLIST); | |
1463 } | |
1464 } | |
1465 | |
1466 void InstantController::StartListeningToMostVisitedChanges() { | |
1467 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
1468 if (top_sites) { | |
1469 if (!registrar_.IsRegistered( | |
1470 this, chrome::NOTIFICATION_TOP_SITES_CHANGED, | |
1471 content::Source<history::TopSites>(top_sites))) { | |
1472 // TopSites updates itself after a delay. This is especially noticable | |
1473 // when your profile is empty. Ask TopSites to update itself when we're | |
1474 // about to show the new tab page. | |
1475 top_sites->SyncWithHistory(); | |
1476 | |
1477 RequestMostVisitedItems(); | |
1478 | |
1479 // Register for notification when TopSites changes. | |
1480 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, | |
1481 content::Source<history::TopSites>(top_sites)); | |
1482 } else { | |
1483 // We are already registered, so just get and send the most visited data. | |
1484 RequestMostVisitedItems(); | |
1485 } | |
1486 } | |
1487 } | |
1488 | |
1489 void InstantController::RequestMostVisitedItems() { | |
1490 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
1491 if (top_sites) { | |
1492 top_sites->GetMostVisitedURLs( | |
1493 base::Bind(&InstantController::OnMostVisitedItemsReceived, | |
1494 weak_ptr_factory_.GetWeakPtr())); | |
1495 } | |
1496 } | |
1497 | |
1498 void InstantController::OnMostVisitedItemsReceived( | |
1499 const history::MostVisitedURLList& data) { | |
1500 std::vector<InstantMostVisitedItem> most_visited_items; | |
1501 for (size_t i = 0; i < data.size(); i++) { | |
1502 const history::MostVisitedURL& url = data[i]; | |
1503 | |
1504 InstantMostVisitedItem item; | |
1505 item.most_visited_item_id = GetMostVisitedItemIDForURL(browser_->profile(), | |
1506 url.url); | |
1507 item.url = url.url; | |
1508 item.title = url.title; | |
1509 | |
1510 most_visited_items.push_back(item); | |
1511 } | |
1512 SendMostVisitedItems(most_visited_items); | |
1513 } | |
1514 | |
1515 void InstantController::SendMostVisitedItems( | |
1516 const std::vector<InstantMostVisitedItem>& items) { | |
1517 if (overlay_) | |
1518 overlay_->SendMostVisitedItems(items); | |
1519 if (ntp_) | |
1520 ntp_->SendMostVisitedItems(items); | |
1521 if (instant_tab_) | |
1522 instant_tab_->SendMostVisitedItems(items); | |
1523 content::NotificationService::current()->Notify( | |
1524 chrome::NOTIFICATION_INSTANT_SENT_MOST_VISITED_ITEMS, | |
1525 content::Source<InstantController>(this), | |
1526 content::NotificationService::NoDetails()); | |
1527 } | |
1528 | |
1529 bool InstantController::FixSuggestion(InstantSuggestion* suggestion) const { | |
1530 // Don't suggest gray text if there already was inline autocompletion. | |
1531 // http://crbug.com/162303 | |
1532 if (suggestion->behavior == INSTANT_COMPLETE_NEVER && | |
1533 last_omnibox_text_has_inline_autocompletion_) | |
1534 return false; | |
1535 | |
1536 // If the page is trying to set inline autocompletion in verbatim mode, | |
1537 // instead try suggesting the exact omnibox text. This makes the omnibox | |
1538 // interpret user text as an URL if possible while preventing unwanted | |
1539 // autocompletion during backspacing. | |
1540 if (suggestion->behavior == INSTANT_COMPLETE_NOW && last_verbatim_) | |
1541 suggestion->text = last_omnibox_text_; | |
1542 | |
1543 // Suggestion text should be a full URL for URL suggestions, or the | |
1544 // completion of a query for query suggestions. | |
1545 if (suggestion->type == INSTANT_SUGGESTION_URL) { | |
1546 // If the suggestion is not a valid URL, perhaps it's something like | |
1547 // "foo.com". Try prefixing "http://". If it still isn't valid, drop it. | |
1548 if (!GURL(suggestion->text).is_valid()) { | |
1549 suggestion->text.insert(0, ASCIIToUTF16("http://")); | |
1550 if (!GURL(suggestion->text).is_valid()) | |
1551 return false; | |
1552 } | |
1553 | |
1554 // URL suggestions are only accepted if the query for which the suggestion | |
1555 // was generated is the same as |last_user_text_|. | |
1556 // | |
1557 // Any other URL suggestions--in particular suggestions for old user_text | |
1558 // lagging behind a slow IPC--are ignored. See crbug.com/181589. | |
1559 // | |
1560 // TODO(samarth): Accept stale suggestions if they would be accepted by | |
1561 // SearchProvider as an inlinable suggestion. http://crbug.com/191656. | |
1562 return suggestion->query == last_user_text_; | |
1563 } | |
1564 | |
1565 if (suggestion->type == INSTANT_SUGGESTION_SEARCH) { | |
1566 if (StartsWith(suggestion->text, last_omnibox_text_, true)) { | |
1567 // The user typed an exact prefix of the suggestion. | |
1568 suggestion->text.erase(0, last_omnibox_text_.size()); | |
1569 return true; | |
1570 } else if (NormalizeAndStripPrefix(&suggestion->text, last_omnibox_text_)) { | |
1571 // Unicode normalize and case-fold the user text and suggestion. If the | |
1572 // user text is a prefix, suggest the normalized, case-folded completion | |
1573 // for instance, if the user types 'i' and the suggestion is 'INSTANT', | |
1574 // suggest 'nstant'. Otherwise, the user text really isn't a prefix, so | |
1575 // suggest nothing. | |
1576 // TODO(samarth|jered): revisit this logic. http://crbug.com/196572. | |
1577 return true; | |
1578 } | |
1579 } | |
1580 | |
1581 return false; | |
1582 } | |
OLD | NEW |