Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Side by Side Diff: chrome/browser/instant/instant_controller.cc

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

Powered by Google App Engine
This is Rietveld 408576698