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

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

Powered by Google App Engine
This is Rietveld 408576698