 Chromium Code Reviews
 Chromium Code Reviews Issue 11421079:
  Persist the Instant API to committed search result pages.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src
    
  
    Issue 11421079:
  Persist the Instant API to committed search result pages.  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/src| Index: chrome/browser/instant/instant_controller.cc | 
| diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc | 
| index 95d7c43a8eb3a13c57ff5eed8b13b575567cb804..f6c77257a3a0fc552bcbffa261dddca92df7700b 100644 | 
| --- a/chrome/browser/instant/instant_controller.cc | 
| +++ b/chrome/browser/instant/instant_controller.cc | 
| @@ -14,12 +14,12 @@ | 
| #include "chrome/browser/history/history_service_factory.h" | 
| #include "chrome/browser/history/history_tab_helper.h" | 
| #include "chrome/browser/instant/instant_loader.h" | 
| +#include "chrome/browser/instant/instant_tab.h" | 
| #include "chrome/browser/platform_util.h" | 
| #include "chrome/browser/search_engines/template_url_service.h" | 
| #include "chrome/browser/search_engines/template_url_service_factory.h" | 
| #include "chrome/browser/ui/browser_instant_controller.h" | 
| #include "chrome/browser/ui/search/search_tab_helper.h" | 
| -#include "chrome/browser/ui/tab_contents/tab_contents.h" | 
| #include "chrome/common/chrome_notification_types.h" | 
| #include "chrome/common/chrome_switches.h" | 
| #include "content/public/browser/navigation_entry.h" | 
| @@ -48,16 +48,16 @@ const int kMaxInstantSupportFailures = 10; | 
| const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000; | 
| void AddSessionStorageHistogram(bool extended_enabled, | 
| - const TabContents* tab1, | 
| - const TabContents* tab2) { | 
| + const content::WebContents* tab1, | 
| + const content::WebContents* tab2) { | 
| base::Histogram* histogram = base::BooleanHistogram::FactoryGet( | 
| std::string("Instant.SessionStorageNamespace") + | 
| (extended_enabled ? "_Extended" : "_Instant"), | 
| base::Histogram::kUmaTargetedHistogramFlag); | 
| const content::SessionStorageNamespaceMap& session_storage_map1 = | 
| - tab1->web_contents()->GetController().GetSessionStorageNamespaceMap(); | 
| + tab1->GetController().GetSessionStorageNamespaceMap(); | 
| const content::SessionStorageNamespaceMap& session_storage_map2 = | 
| - tab2->web_contents()->GetController().GetSessionStorageNamespaceMap(); | 
| + tab2->GetController().GetSessionStorageNamespaceMap(); | 
| bool is_session_storage_the_same = | 
| session_storage_map1.size() == session_storage_map2.size(); | 
| if (is_session_storage_the_same) { | 
| @@ -90,7 +90,7 @@ string16 Normalize(const string16& str) { | 
| } | 
| bool NormalizeAndStripPrefix(string16* text, const string16& prefix) { | 
| - const string16 norm_prefix = Normalize(prefix); | 
| + string16 norm_prefix = Normalize(prefix); | 
| string16 norm_text = Normalize(*text); | 
| if (norm_prefix.size() <= norm_text.size() && | 
| norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) { | 
| @@ -111,9 +111,8 @@ gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) { | 
| views::FocusManager* focus_manager = widget->GetFocusManager(); | 
| if (focus_manager && focus_manager->is_changing_focus() && | 
| focus_manager->GetFocusedView() && | 
| - focus_manager->GetFocusedView()->GetWidget()) { | 
| + focus_manager->GetFocusedView()->GetWidget()) | 
| return focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | 
| - } | 
| } | 
| #endif | 
| return view_gaining_focus; | 
| @@ -149,10 +148,12 @@ InstantController::InstantController(chrome::BrowserInstantController* browser, | 
| extended_enabled_(extended_enabled), | 
| instant_enabled_(false), | 
| model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), | 
| + last_omnibox_text_has_inline_autocompletion_(false), | 
| last_verbatim_(false), | 
| last_transition_type_(content::PAGE_TRANSITION_LINK), | 
| last_match_was_search_(false), | 
| - is_omnibox_focused_(false) { | 
| + is_omnibox_focused_(false), | 
| + allow_preview_to_show_search_suggestions_(false) { | 
| } | 
| InstantController::~InstantController() { | 
| @@ -161,26 +162,26 @@ InstantController::~InstantController() { | 
| bool InstantController::Update(const AutocompleteMatch& match, | 
| const string16& user_text, | 
| const string16& full_text, | 
| - const bool verbatim, | 
| - const bool user_input_in_progress, | 
| - const bool omnibox_popup_is_open) { | 
| + size_t selection_start, | 
| + size_t selection_end, | 
| + bool verbatim, | 
| + bool user_input_in_progress, | 
| + bool omnibox_popup_is_open) { | 
| if (!extended_enabled_ && !instant_enabled_) | 
| return false; | 
| DVLOG(1) << "Update: " << AutocompleteMatch::TypeToString(match.type) | 
| << " user_text='" << user_text << "' full_text='" << full_text << "'" | 
| - << " verbatim=" << verbatim << " typing=" << user_input_in_progress | 
| - << " popup=" << omnibox_popup_is_open; | 
| + << " selection_start=" << selection_start << " selection_end=" | 
| + << selection_end << " verbatim=" << verbatim << " typing=" | 
| + << user_input_in_progress << " popup=" << omnibox_popup_is_open; | 
| // If the popup is open, the user has to be typing. | 
| DCHECK(!omnibox_popup_is_open || user_input_in_progress); | 
| // If the popup is closed, there should be no inline autocompletion. | 
| - DCHECK(omnibox_popup_is_open || user_text == full_text) << user_text << "|" | 
| - << full_text; | 
| - | 
| - // If there's inline autocompletion, the query has to be verbatim. | 
| - DCHECK(user_text == full_text || verbatim) << user_text << "|" << full_text; | 
| + DCHECK(omnibox_popup_is_open || user_text.empty() || user_text == full_text) | 
| + << user_text << "|" << full_text; | 
| // If there's no text in the omnibox, the user can't have typed any. | 
| DCHECK(!full_text.empty() || user_text.empty()) << user_text; | 
| @@ -197,108 +198,134 @@ bool InstantController::Update(const AutocompleteMatch& match, | 
| if (!extended_enabled_) | 
| search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | 
| - // If there's no active tab, the browser is closing. | 
| - const TabContents* const active_tab = browser_->GetActiveTabContents(); | 
| - if (!active_tab) { | 
| - Hide(true); | 
| + last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && | 
| + !user_text.empty(); | 
| + | 
| + if (!ResetLoaderForMatch(match)) { | 
| + HideLoader(); | 
| return false; | 
| } | 
| - // Legend: OPIO == |omnibox_popup_is_open|, UIIP = |user_input_in_progress|. | 
| - // | 
| - // # OPIO UIIP full_text Notes | 
| - // - ---- ---- --------- ----- | 
| - // 1 no no blank } Navigation, or user hit Escape. |full_text| is | 
| - // 2 no no non-blank } blank if the page is NTP, non-blank otherwise. | 
| - // | 
| - // 3 no yes blank User backspaced away all omnibox text. | 
| - // | 
| - // 4 no yes non-blank User switched to a tab with a partial query. | 
| - // | 
| - // 5 yes no blank } Impossible. DCHECK()ed above. | 
| - // 6 yes no non-blank } | 
| - // | 
| - // 7 yes yes blank User typed a "?" into the omnibox. | 
| - // | 
| - // 8 yes yes non-blank User typed text into the omnibox. | 
| - // | 
| - // In non-extended mode, #1 to #7 call Hide(). #8 calls loader_->Update(). | 
| - // | 
| - // In extended mode, #2 and #4 call Hide(). #1 doesn't Hide() as the preview | 
| - // may be showing custom NTP content, but doesn't Update() either. #3 calls | 
| - // Hide() unless on the NTP _and_ sends a blank query; otherwise #3 and #7 | 
| - // don't Hide(), but send a blank query to Update(). #8 calls Update(). | 
| - | 
| if (extended_enabled_) { | 
| if (!omnibox_popup_is_open) { | 
| - if (!full_text.empty() || | 
| - (user_input_in_progress && !search_mode_.is_origin_ntp())) { | 
| - Hide(true); | 
| - return false; | 
| + if (!user_input_in_progress) { | 
| + // If the user isn't typing and the omnibox popup is closed, it means a | 
| + // regular navigation, tab-switch or the user hitting Escape. | 
| + if (instant_tab_) { | 
| + // The user is on a search results page. It may be showing results for | 
| + // a partial query the user typed before they hit Escape. Send the | 
| + // omnibox text to the page to restore the original results. | 
| + // | 
| + // In a tab switch, |instant_tab_| won't have updated yet, so it may | 
| + // be pointing to the previous tab (which was a search results page). | 
| + // Ensure we don't send the omnibox text to a random webpage (the new | 
| + // tab), by comparing the old and new WebContents. | 
| + if (instant_tab_->contents() == browser_->GetActiveWebContents()) | 
| + instant_tab_->Submit(full_text); | 
| + } else if (!full_text.empty()) { | 
| + // If |full_text| is empty, the user is on the NTP. The preview may | 
| + // be showing custom NTP content; hide only if that's not the case. | 
| + HideLoader(); | 
| + } | 
| + } else if (full_text.empty()) { | 
| + // The user is typing, and backspaced away all omnibox text. Clear | 
| + // |last_omnibox_text_| so that we don't attempt to set suggestions. | 
| + last_omnibox_text_.clear(); | 
| + last_suggestion_ = InstantSuggestion(); | 
| + if (instant_tab_) { | 
| + // On a search results page, tell it to clear old results. | 
| + instant_tab_->Update(string16(), 0, 0, true); | 
| + } else if (search_mode_.is_origin_ntp()) { | 
| + // On the NTP, tell the preview to clear old results. Don't hide the | 
| + // preview so it can show a blank page or logo if it wants. | 
| + loader_->Update(string16(), 0, 0, true); | 
| + } else { | 
| + HideLoader(); | 
| + } | 
| + } else { | 
| + // The user switched to a tab with partial text already in the omnibox. | 
| + HideLoader(); | 
| + | 
| + // The new tab may or may not be a search results page; we don't know | 
| + // since SearchModeChanged() hasn't been called yet. If it later turns | 
| + // out to be, we should store |full_text| now, so that if the user hits | 
| + // Enter, we'll send the correct query to instant_tab_->Submit(). If the | 
| + // partial text is not a query (|last_match_was_search_| is false), we | 
| + // won't Submit(), so no need to worry about that. | 
| + last_omnibox_text_ = full_text; | 
| + last_suggestion_ = InstantSuggestion(); | 
| } | 
| - if (!user_input_in_progress && full_text.empty()) | 
| - return false; | 
| + return false; | 
| + } else if (full_text.empty()) { | 
| + // The user typed a solitary "?". Same as the backspace case above. | 
| + last_omnibox_text_.clear(); | 
| + last_suggestion_ = InstantSuggestion(); | 
| + if (instant_tab_) | 
| + instant_tab_->Update(string16(), 0, 0, true); | 
| + else if (search_mode_.is_origin_ntp()) | 
| + loader_->Update(string16(), 0, 0, true); | 
| + else | 
| + HideLoader(); | 
| + return false; | 
| } | 
| } else if (!omnibox_popup_is_open || full_text.empty()) { | 
| - // Update() can be called if the user clicks the preview while composing | 
| - // text with an IME. If so, we should commit on mouse up, so don't Hide(). | 
| - if (!GetPreviewContents() || !loader_->IsPointerDownFromActivate()) | 
| - Hide(true); | 
| + // In the non-extended case, hide the preview as long as the user isn't | 
| + // actively typing a non-empty query. However, Update() can be called if the | 
| + // user clicks the preview while composing text with an IME, so don't hide | 
| + // if the mouse is down, since we'll commit on mouse up later. | 
| + if (!loader_ || !loader_->is_pointer_down_from_activate()) | 
| + HideLoader(); | 
| return false; | 
| } | 
| - // Ensure we have a loader that can process this match. First, try to use the | 
| - // TemplateURL of the |match|. If that's invalid, in non-extended mode, stop. | 
| - // In extended mode, try using the default search engine, but only when the | 
| - // match is for a URL (i.e., not some other kind of non-Instant search). | 
| - // A completely blank query shows up as a search, and we do want to allow | 
| - // that, hence the "!full_text.empty()" clause. | 
| - Profile* const profile = active_tab->profile(); | 
| - const bool match_is_search = AutocompleteMatch::IsSearchType(match.type); | 
| - if (!ResetLoader(match.GetTemplateURL(profile, false), active_tab) && | 
| - (!extended_enabled_ || (match_is_search && !full_text.empty()) || | 
| - !CreateDefaultLoader())) { | 
| - Hide(true); | 
| - return false; | 
| - } | 
| + last_omnibox_text_has_inline_autocompletion_ = user_text != full_text; | 
| // If the user continues typing the same query as the suggested text is | 
| // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER). | 
| bool reused_suggestion = false; | 
| - if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER) { | 
| - if (StartsWith(last_user_text_, user_text, false) && !user_text.empty()) { | 
| + if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && | 
| + !last_omnibox_text_has_inline_autocompletion_) { | 
| + if (StartsWith(last_omnibox_text_, full_text, false)) { | 
| // The user is backspacing away characters. | 
| - last_suggestion_.text.insert(0, last_user_text_, user_text.size(), | 
| - last_user_text_.size() - user_text.size()); | 
| + last_suggestion_.text.insert(0, last_omnibox_text_, full_text.size(), | 
| + last_omnibox_text_.size() - full_text.size()); | 
| reused_suggestion = true; | 
| - } else if (StartsWith(user_text, last_user_text_, false)) { | 
| + } else if (StartsWith(full_text, last_omnibox_text_, false)) { | 
| // The user is typing forward. Normalize any added characters. | 
| reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text, | 
| - string16(user_text, last_user_text_.size())); | 
| + string16(full_text, last_omnibox_text_.size())); | 
| } | 
| } | 
| - | 
| - last_user_text_ = user_text; | 
| - last_full_text_ = full_text; | 
| - last_verbatim_ = verbatim; | 
| - | 
| if (!reused_suggestion) | 
| last_suggestion_ = InstantSuggestion(); | 
| + last_omnibox_text_ = full_text; | 
| + | 
| + if (!extended_enabled_) { | 
| + // In non-extended mode, the query is verbatim if there's any selection | 
| + // (including inline autocompletion) or if the cursor is not at the end. | 
| + verbatim = verbatim || selection_start != selection_end || | 
| + selection_start != full_text.size(); | 
| + } | 
| + last_verbatim_ = verbatim; | 
| + | 
| last_transition_type_ = match.transition; | 
| - last_match_was_search_ = match_is_search; | 
| url_for_history_ = match.destination_url; | 
| - // Store the first interaction time for use with latency histograms. | 
| - if (first_interaction_time_.is_null()) | 
| - first_interaction_time_ = base::Time::Now(); | 
| - | 
| // Allow search suggestions. In extended mode, SearchModeChanged() will set | 
| // this, but it's not called in non-extended mode, so fake it. | 
| if (!extended_enabled_) | 
| search_mode_.mode = chrome::search::Mode::MODE_SEARCH_SUGGESTIONS; | 
| - loader_->Update(extended_enabled_ ? user_text : full_text, verbatim); | 
| + if (instant_tab_) { | 
| + instant_tab_->Update(user_text, selection_start, selection_end, verbatim); | 
| + } else { | 
| + if (first_interaction_time_.is_null()) | 
| + first_interaction_time_ = base::Time::Now(); | 
| + allow_preview_to_show_search_suggestions_ = true; | 
| + loader_->Update(extended_enabled_ ? user_text : full_text, | 
| + selection_start, selection_end, verbatim); | 
| + } | 
| content::NotificationService::current()->Notify( | 
| chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | 
| @@ -312,7 +339,7 @@ bool InstantController::Update(const AutocompleteMatch& match, | 
| // Though we may have handled a URL match above, we return false here, so that | 
| // omnibox prerendering can kick in. TODO(sreeram): Remove this (and always | 
| // return true) once we are able to commit URLs as well. | 
| - return match_is_search; | 
| + return last_match_was_search_; | 
| } | 
| // TODO(tonyg): This method only fires when the omnibox bounds change. It also | 
| @@ -340,7 +367,7 @@ void InstantController::HandleAutocompleteResults( | 
| if (!extended_enabled_) | 
| return; | 
| - if (!GetPreviewContents()) | 
| + if (!instant_tab_ && !loader_) | 
| return; | 
| DVLOG(1) << "AutocompleteResults:"; | 
| @@ -362,22 +389,29 @@ void InstantController::HandleAutocompleteResults( | 
| } | 
| } | 
| - loader_->SendAutocompleteResults(results); | 
| + if (instant_tab_) | 
| + instant_tab_->SendAutocompleteResults(results); | 
| + else | 
| + loader_->SendAutocompleteResults(results); | 
| } | 
| bool InstantController::OnUpOrDownKeyPressed(int count) { | 
| if (!extended_enabled_) | 
| return false; | 
| - if (!GetPreviewContents()) | 
| + if (!instant_tab_ && !loader_) | 
| return false; | 
| - loader_->OnUpOrDownKeyPressed(count); | 
| + if (instant_tab_) | 
| + instant_tab_->UpOrDownKeyPressed(count); | 
| + else | 
| + loader_->UpOrDownKeyPressed(count); | 
| + | 
| return true; | 
| } | 
| -TabContents* InstantController::GetPreviewContents() const { | 
| - return loader_ ? loader_->preview_contents() : NULL; | 
| +content::WebContents* InstantController::GetPreviewContents() const { | 
| + return loader_ ? loader_->contents() : NULL; | 
| } | 
| bool InstantController::IsCurrent() const { | 
| @@ -388,11 +422,29 @@ bool InstantController::CommitIfCurrent(InstantCommitType type) { | 
| if (!extended_enabled_ && !instant_enabled_) | 
| return false; | 
| + DVLOG(1) << "CommitIfCurrent: type=" << type << " last_omnibox_text_='" | 
| + << last_omnibox_text_ << "' last_match_was_search_=" | 
| + << last_match_was_search_ << " instant_tab_=" << instant_tab_; | 
| + | 
| + // If we are on an already committed search results page, send a submit event | 
| + // to the page, but otherwise, nothing else to do. | 
| + if (instant_tab_) { | 
| + if (last_match_was_search_ && type == INSTANT_COMMIT_PRESSED_ENTER) { | 
| + instant_tab_->Submit(last_omnibox_text_); | 
| + return true; | 
| + } | 
| + return false; | 
| + } | 
| + | 
| if (!IsCurrent()) | 
| return false; | 
| - DVLOG(1) << "CommitIfCurrent"; | 
| - TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_); | 
| + if (type == INSTANT_COMMIT_FOCUS_LOST) | 
| + loader_->Cancel(last_omnibox_text_); | 
| + else | 
| + loader_->Submit(last_omnibox_text_); | 
| + | 
| + content::WebContents* preview = loader_->ReleaseContents(); | 
| if (extended_enabled_) { | 
| // Consider what's happening: | 
| @@ -407,16 +459,16 @@ bool InstantController::CommitIfCurrent(InstantCommitType type) { | 
| // TODO(samarth,beaudoin): Instead of this hack, we should add a new field | 
| // to NavigationEntry to keep track of what the correct query, if any, is. | 
| content::NavigationEntry* entry = | 
| - preview->web_contents()->GetController().GetVisibleEntry(); | 
| + preview->GetController().GetVisibleEntry(); | 
| std::string url = entry->GetVirtualURL().spec(); | 
| if (!google_util::IsInstantExtendedAPIGoogleSearchUrl(url) && | 
| google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN, | 
| google_util::ALLOW_NON_STANDARD_PORTS)) { | 
| entry->SetVirtualURL(GURL( | 
| url + "#q=" + | 
| - net::EscapeQueryParamValue(UTF16ToUTF8(last_full_text_), true))); | 
| - chrome::search::SearchTabHelper::FromWebContents( | 
| - preview->web_contents())->NavigationEntryUpdated(); | 
| + net::EscapeQueryParamValue(UTF16ToUTF8(last_omnibox_text_), true))); | 
| + chrome::search::SearchTabHelper::FromWebContents(preview)-> | 
| + NavigationEntryUpdated(); | 
| } | 
| } | 
| @@ -426,13 +478,12 @@ bool InstantController::CommitIfCurrent(InstantCommitType type) { | 
| const history::HistoryAddPageArgs& last_navigation = | 
| loader_->last_navigation(); | 
| if (!last_navigation.url.is_empty()) { | 
| - content::NavigationEntry* entry = | 
| - preview->web_contents()->GetController().GetActiveEntry(); | 
| + content::NavigationEntry* entry = preview->GetController().GetActiveEntry(); | 
| DCHECK_EQ(last_navigation.url, entry->GetURL()); | 
| // Add the page to history. | 
| HistoryTabHelper* history_tab_helper = | 
| - HistoryTabHelper::FromWebContents(preview->web_contents()); | 
| + HistoryTabHelper::FromWebContents(preview); | 
| history_tab_helper->UpdateHistoryForNavigation(last_navigation); | 
| // Update the page title. | 
| @@ -441,34 +492,38 @@ bool InstantController::CommitIfCurrent(InstantCommitType type) { | 
| // Add a fake history entry with a non-Instant search URL, so that search | 
| // terms extraction (for autocomplete history matches) works. | 
| - if (HistoryService* history = HistoryServiceFactory::GetForProfile( | 
| - preview->profile(), Profile::EXPLICIT_ACCESS)) { | 
| + HistoryService* history = HistoryServiceFactory::GetForProfile( | 
| + Profile::FromBrowserContext(preview->GetBrowserContext()), | 
| + Profile::EXPLICIT_ACCESS); | 
| + if (history) { | 
| history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), | 
| history::RedirectList(), last_transition_type_, | 
| history::SOURCE_BROWSED, false); | 
| } | 
| - preview->web_contents()->GetController().PruneAllButActive(); | 
| + preview->GetController().PruneAllButActive(); | 
| if (type != INSTANT_COMMIT_PRESSED_ALT_ENTER) { | 
| - const TabContents* active_tab = browser_->GetActiveTabContents(); | 
| + content::WebContents* active_tab = browser_->GetActiveWebContents(); | 
| AddSessionStorageHistogram(extended_enabled_, active_tab, preview); | 
| - preview->web_contents()->GetController().CopyStateFromAndPrune( | 
| - &active_tab->web_contents()->GetController()); | 
| + preview->GetController().CopyStateFromAndPrune( | 
| + &active_tab->GetController()); | 
| } | 
| - DeleteLoader(); | 
| - | 
| // Browser takes ownership of the preview. | 
| browser_->CommitInstant(preview, type == INSTANT_COMMIT_PRESSED_ALT_ENTER); | 
| content::NotificationService::current()->Notify( | 
| chrome::NOTIFICATION_INSTANT_COMMITTED, | 
| - content::Source<content::WebContents>(preview->web_contents()), | 
| + content::Source<content::WebContents>(preview), | 
| content::NotificationService::NoDetails()); | 
| + // Hide explicitly. See comments in HideLoader() for why. | 
| model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 
| + // Delay deletion as we could've gotten here from an InstantLoader method. | 
| + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | 
| + | 
| // Try to create another loader immediately so that it is ready for the next | 
| // user interaction. | 
| CreateDefaultLoader(); | 
| @@ -492,14 +547,14 @@ void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) { | 
| } | 
| #if defined(OS_MACOSX) | 
| - if (!loader_->IsPointerDownFromActivate()) | 
| - Hide(true); | 
| + if (!loader_->is_pointer_down_from_activate()) | 
| + HideLoader(); | 
| #else | 
| if (IsViewInContents(GetViewGainingFocus(view_gaining_focus), | 
| - GetPreviewContents()->web_contents())) | 
| + loader_->contents())) | 
| CommitIfCurrent(INSTANT_COMMIT_FOCUS_LOST); | 
| else | 
| - Hide(true); | 
| + HideLoader(); | 
| #endif | 
| } | 
| @@ -510,8 +565,7 @@ void InstantController::OmniboxGotFocus() { | 
| if (!extended_enabled_ && !instant_enabled_) | 
| return; | 
| - if (!GetPreviewContents()) | 
| - CreateDefaultLoader(); | 
| + CreateDefaultLoader(); | 
| } | 
| void InstantController::SearchModeChanged( | 
| @@ -523,18 +577,15 @@ void InstantController::SearchModeChanged( | 
| DVLOG(1) << "SearchModeChanged: [origin:mode] " << old_mode.origin << ":" | 
| << old_mode.mode << " to " << new_mode.origin << ":" | 
| << new_mode.mode; | 
| - search_mode_ = new_mode; | 
| - if (new_mode.is_search_suggestions()) { | 
| - // The preview is showing NTP content, but it's not appropriate anymore. | 
| - if (model_.mode().is_ntp() && !new_mode.is_origin_ntp()) | 
| - Hide(false); | 
| - } else { | 
| - Hide(true); | 
| - } | 
| + search_mode_ = new_mode; | 
| + if (!new_mode.is_search_suggestions()) | 
| + HideLoader(); | 
| - if (GetPreviewContents()) | 
| + if (loader_) | 
| loader_->SearchModeChanged(new_mode); | 
| + | 
| + ResetInstantTab(); | 
| } | 
| void InstantController::ActiveTabChanged() { | 
| @@ -543,32 +594,29 @@ void InstantController::ActiveTabChanged() { | 
| DVLOG(1) << "ActiveTabChanged"; | 
| - // By this time, SearchModeChanged() should've been called, so we only need to | 
| - // handle the case when the search mode does NOT change, as in the case of | 
| - // going from search_suggestions to search_suggestions (i.e., partial queries | 
| - // on both old and new tabs). | 
| - if (search_mode_.is_search_suggestions() && | 
| - model_.mode().is_search_suggestions()) | 
| - Hide(false); | 
| + // When switching tabs, always hide the preview, except if it's showing NTP | 
| + // content, and the new tab is also an NTP. | 
| + if (!search_mode_.is_ntp() || !model_.mode().is_ntp()) | 
| + HideLoader(); | 
| + | 
| + if (extended_enabled_) | 
| + ResetInstantTab(); | 
| } | 
| void InstantController::SetInstantEnabled(bool instant_enabled) { | 
| DVLOG(1) << "SetInstantEnabled: " << instant_enabled; | 
| instant_enabled_ = instant_enabled; | 
| - if (extended_enabled_) { | 
| - // Reset the loader whenever the Instant pref changes. | 
| - DeleteLoader(); | 
| + HideInternal(); | 
| + loader_.reset(); | 
| + if (extended_enabled_ || instant_enabled_) | 
| CreateDefaultLoader(); | 
| - } else if (!instant_enabled_) { | 
| - DeleteLoader(); | 
| - } | 
| } | 
| void InstantController::ThemeChanged(const ThemeBackgroundInfo& theme_info) { | 
| if (!extended_enabled_) | 
| return; | 
| - if (GetPreviewContents()) | 
| + if (loader_) | 
| loader_->SendThemeBackgroundInfo(theme_info); | 
| } | 
| @@ -576,17 +624,28 @@ void InstantController::ThemeAreaHeightChanged(int height) { | 
| if (!extended_enabled_) | 
| return; | 
| - if (GetPreviewContents()) | 
| + if (loader_) | 
| loader_->SendThemeAreaHeight(height); | 
| } | 
| void InstantController::SetSuggestions( | 
| - InstantLoader* loader, | 
| + const content::WebContents* contents, | 
| const std::vector<InstantSuggestion>& suggestions) { | 
| DVLOG(1) << "SetSuggestions"; | 
| - if (loader_ != loader || !search_mode_.is_search_suggestions()) | 
| + | 
| + // Ignore if we are not currently accepting search suggestions. | 
| + if (!search_mode_.is_search_suggestions() || last_omnibox_text_.empty()) | 
| return; | 
| + // Ignore if the message is from an unexpected source. | 
| + if (instant_tab_) { | 
| + if (instant_tab_->contents() != contents) | 
| + return; | 
| + } else if (!loader_ || loader_->contents() != contents || | 
| + !allow_preview_to_show_search_suggestions_) { | 
| + return; | 
| + } | 
| + | 
| InstantSuggestion suggestion; | 
| if (!suggestions.empty()) | 
| suggestion = suggestions[0]; | 
| @@ -596,9 +655,7 @@ void InstantController::SetSuggestions( | 
| // suggestion (so that we don't inadvertently cause the preview to change | 
| // what it's showing, as the user arrows up/down through the page-provided | 
| // suggestions). So, update these state variables here. | 
| - last_full_text_ = suggestion.text; | 
| - last_user_text_.clear(); | 
| - last_verbatim_ = true; | 
| + last_omnibox_text_ = suggestion.text; | 
| last_suggestion_ = InstantSuggestion(); | 
| last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | 
| DVLOG(1) << "SetReplaceSuggestion: text='" << suggestion.text << "'" | 
| @@ -615,10 +672,10 @@ void InstantController::SetSuggestions( | 
| if (!GURL(suggestion.text).is_valid()) | 
| suggestion = InstantSuggestion(); | 
| } | 
| - } else if (StartsWith(suggestion.text, last_user_text_, true)) { | 
| + } else if (StartsWith(suggestion.text, last_omnibox_text_, true)) { | 
| // The user typed an exact prefix of the suggestion. | 
| - suggestion.text.erase(0, last_user_text_.size()); | 
| - } else if (!NormalizeAndStripPrefix(&suggestion.text, last_user_text_)) { | 
| + suggestion.text.erase(0, last_omnibox_text_.size()); | 
| + } else if (!NormalizeAndStripPrefix(&suggestion.text, last_omnibox_text_)) { | 
| // Unicode normalize and case-fold the user text and suggestion. If the | 
| // user text is a prefix, suggest the normalized, case-folded completion; | 
| // for instance, if the user types 'i' and the suggestion is 'INSTANT', | 
| @@ -627,14 +684,10 @@ void InstantController::SetSuggestions( | 
| suggestion = InstantSuggestion(); | 
| } | 
| - // If the omnibox is blank, this suggestion is for an older query. Ignore. | 
| - if (last_user_text_.empty()) | 
| - suggestion = InstantSuggestion(); | 
| - | 
| // Don't suggest gray text if there already was inline autocompletion. | 
| // http://crbug.com/162303 | 
| if (suggestion.behavior == INSTANT_COMPLETE_NEVER && | 
| - last_user_text_ != last_full_text_) | 
| + last_omnibox_text_has_inline_autocompletion_) | 
| suggestion = InstantSuggestion(); | 
| // Don't allow inline autocompletion if the query was verbatim. | 
| @@ -651,93 +704,94 @@ void InstantController::SetSuggestions( | 
| } | 
| } | 
| - // Extended mode pages will show() when ready. | 
| + // Extended mode pages will call ShowLoader() when they are ready. | 
| if (!extended_enabled_) | 
| - Show(INSTANT_SHOWN_QUERY_SUGGESTIONS, 100, INSTANT_SIZE_PERCENT); | 
| + ShowLoader(INSTANT_SHOWN_QUERY_SUGGESTIONS, 100, INSTANT_SIZE_PERCENT); | 
| } | 
| -void InstantController::CommitInstantLoader(InstantLoader* loader) { | 
| - if (loader_ == loader) | 
| - CommitIfCurrent(INSTANT_COMMIT_FOCUS_LOST); | 
| +void InstantController::InstantSupportDetermined( | 
| + const content::WebContents* contents, | 
| + bool supports_instant) { | 
| + if (instant_tab_ && instant_tab_->contents() == contents) { | 
| + if (!supports_instant) | 
| + MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release()); | 
| + return; | 
| + } | 
| + | 
| + if (loader_ && loader_->contents() == contents) { | 
| + if (supports_instant) { | 
| + blacklisted_urls_.erase(loader_->instant_url()); | 
| + } else { | 
| + ++blacklisted_urls_[loader_->instant_url()]; | 
| + HideInternal(); | 
| + delete loader_->ReleaseContents(); | 
| + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | 
| + CreateDefaultLoader(); | 
| + } | 
| + content::NotificationService::current()->Notify( | 
| + chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, | 
| + content::Source<InstantController>(this), | 
| + content::NotificationService::NoDetails()); | 
| + } | 
| } | 
| -void InstantController::ShowInstantPreview(InstantLoader* loader, | 
| - InstantShownReason reason, | 
| +void InstantController::ShowInstantPreview(InstantShownReason reason, | 
| int height, | 
| InstantSizeUnits units) { | 
| - if (loader_ == loader && extended_enabled_) | 
| - Show(reason, height, units); | 
| -} | 
| - | 
| -void InstantController::InstantSupportDetermined(InstantLoader* loader, | 
| - bool supports_instant) { | 
| - if (supports_instant) { | 
| - blacklisted_urls_.erase(loader->instant_url()); | 
| - } else { | 
| - ++blacklisted_urls_[loader->instant_url()]; | 
| - if (loader_ == loader) | 
| - DeleteLoader(); | 
| - } | 
| - | 
| - content::NotificationService::current()->Notify( | 
| - chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, | 
| - content::Source<InstantController>(this), | 
| - content::NotificationService::NoDetails()); | 
| + if (extended_enabled_) | 
| + ShowLoader(reason, height, units); | 
| } | 
| -void InstantController::SwappedTabContents(InstantLoader* loader) { | 
| - if (loader_ == loader) | 
| - model_.SetPreviewContents(GetPreviewContents()); | 
| +void InstantController::SwappedWebContents() { | 
| + model_.SetPreviewContents(GetPreviewContents()); | 
| } | 
| -void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) { | 
| +void InstantController::InstantLoaderContentsFocused() { | 
| #if defined(USE_AURA) | 
| // On aura the omnibox only receives a focus lost if we initiate the focus | 
| // change. This does that. | 
| - if (loader_ == loader && !model_.mode().is_default()) | 
| + if (!model_.mode().is_default()) | 
| browser_->InstantPreviewFocused(); | 
| #endif | 
| } | 
| bool InstantController::ResetLoader(const TemplateURL* template_url, | 
| - const TabContents* active_tab) { | 
| + const content::WebContents* active_tab) { | 
| std::string instant_url; | 
| if (!GetInstantURL(template_url, &instant_url)) | 
| return false; | 
| - if (GetPreviewContents() && loader_->instant_url() != instant_url) | 
| - DeleteLoader(); | 
| - | 
| - if (!GetPreviewContents()) { | 
| - loader_.reset(new InstantLoader(this, instant_url, active_tab)); | 
| - loader_->Init(); | 
| + if (loader_ && loader_->instant_url() == instant_url) | 
| + return true; | 
| - // Ensure the searchbox API has the correct initial state. | 
| - if (extended_enabled_) { | 
| - browser_->UpdateThemeInfoForPreview(); | 
| - loader_->SetDisplayInstantResults(instant_enabled_); | 
| - loader_->SearchModeChanged(search_mode_); | 
| - } | 
| + HideInternal(); | 
| + loader_.reset(new InstantLoader(this, instant_url)); | 
| + loader_->InitContents(active_tab); | 
| - // Reset the loader timer. | 
| - stale_loader_timer_.Start( | 
| - FROM_HERE, | 
| - base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this, | 
| - &InstantController::OnStaleLoader); | 
| + // Ensure the searchbox API has the correct initial state. | 
| + if (extended_enabled_) { | 
| + browser_->UpdateThemeInfoForPreview(); | 
| + loader_->SetDisplayInstantResults(instant_enabled_); | 
| + loader_->SearchModeChanged(search_mode_); | 
| } | 
| + // Restart the stale loader timer. | 
| + stale_loader_timer_.Start(FROM_HERE, | 
| + base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this, | 
| + &InstantController::OnStaleLoader); | 
| + | 
| return true; | 
| } | 
| bool InstantController::CreateDefaultLoader() { | 
| // If there's no active tab, the browser is closing. | 
| - const TabContents* active_tab = browser_->GetActiveTabContents(); | 
| + const content::WebContents* active_tab = browser_->GetActiveWebContents(); | 
| if (!active_tab) | 
| return false; | 
| - const TemplateURL* template_url = | 
| - TemplateURLServiceFactory::GetForProfile(active_tab->profile())-> | 
| - GetDefaultSearchProvider(); | 
| + const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile( | 
| + Profile::FromBrowserContext(active_tab->GetBrowserContext()))-> | 
| + GetDefaultSearchProvider(); | 
| return ResetLoader(template_url, active_tab); | 
| } | 
| @@ -748,65 +802,87 @@ void InstantController::OnStaleLoader() { | 
| // omnibox loses focus. | 
| if (!stale_loader_timer_.IsRunning() && !is_omnibox_focused_ && | 
| model_.mode().is_default()) { | 
| - DeleteLoader(); | 
| + loader_.reset(); | 
| CreateDefaultLoader(); | 
| } | 
| } | 
| -void InstantController::DeleteLoader() { | 
| - // Clear all state, except |last_transition_type_| as it's used during commit. | 
| - last_user_text_.clear(); | 
| - last_full_text_.clear(); | 
| - last_verbatim_ = false; | 
| - last_suggestion_ = InstantSuggestion(); | 
| - last_match_was_search_ = false; | 
| - if (!extended_enabled_) | 
| - search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | 
| - omnibox_bounds_ = gfx::Rect(); | 
| - last_omnibox_bounds_ = gfx::Rect(); | 
| - update_bounds_timer_.Stop(); | 
| - stale_loader_timer_.Stop(); | 
| - url_for_history_ = GURL(); | 
| - first_interaction_time_ = base::Time(); | 
| - if (GetPreviewContents()) { | 
| - model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 
| - loader_->CleanupPreviewContents(); | 
| +void InstantController::ResetInstantTab() { | 
| + if (search_mode_.is_origin_search()) { | 
| + content::WebContents* active_tab = browser_->GetActiveWebContents(); | 
| + if (!instant_tab_ || active_tab != instant_tab_->contents()) { | 
| + instant_tab_.reset(new InstantTab(this, active_tab)); | 
| + instant_tab_->Init(); | 
| + } | 
| + | 
| + // Hide the |loader_| since we are now using |instant_tab_| instead. | 
| + HideLoader(); | 
| + } else { | 
| + instant_tab_.reset(); | 
| } | 
| +} | 
| - // Schedule the deletion for later, since we may have gotten here from a call | 
| - // within a |loader_| method (i.e., it's still on the stack). If we deleted | 
| - // the loader immediately, things would still be fine so long as the caller | 
| - // doesn't access any instance members after we return, but why rely on that? | 
| - MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | 
| +bool InstantController::ResetLoaderForMatch(const AutocompleteMatch& match) { | 
| + // If we are on a search results page, we'll use that instead of a loader. | 
| + // TODO(sreeram): If |instant_tab_|'s URL is not the same as the instant_url | 
| + // of |match|, we shouldn't use the committed tab. | 
| + if (instant_tab_) | 
| + return true; | 
| + | 
| + // If there's no active tab, the browser is closing. | 
| + const content::WebContents* active_tab = browser_->GetActiveWebContents(); | 
| + if (!active_tab) | 
| + return false; | 
| + | 
| + // Try to create a loader for the instant_url in the TemplateURL of |match|. | 
| + const TemplateURL* template_url = match.GetTemplateURL( | 
| + Profile::FromBrowserContext(active_tab->GetBrowserContext()), false); | 
| + if (ResetLoader(template_url, active_tab)) | 
| + return true; | 
| + | 
| + // In non-extended mode, stop if we couldn't get a loader for the |match|. | 
| + if (!extended_enabled_) | 
| + return false; | 
| + | 
| + // If the match is a query, it is for a non-Instant search engine; stop. | 
| + if (last_match_was_search_) | 
| + return false; | 
| + | 
| + // The match is a URL, or a blank query. Try the default search engine. | 
| + return CreateDefaultLoader(); | 
| } | 
| -void InstantController::Hide(bool clear_query) { | 
| - DVLOG(1) << "Hide: clear_query=" << clear_query; | 
| +void InstantController::HideLoader() { | 
| + HideInternal(); | 
| + OnStaleLoader(); | 
| +} | 
| - // The only time when the preview is not already in the desired MODE_DEFAULT | 
| - // state and GetPreviewContents() returns NULL is when we are in the commit | 
| - // path. In that case, don't change the state just yet; otherwise we may | 
| - // cause the preview to hide unnecessarily. Instead, the state will be set | 
| - // correctly after the commit is done. | 
| - if (GetPreviewContents()) | 
| - model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 
| +void InstantController::HideInternal() { | 
| + DVLOG(1) << "Hide"; | 
| - // Clear the first interaction timestamp for later use. | 
| - first_interaction_time_ = base::Time(); | 
| + // If GetPreviewContents() returns NULL, either we're already in the desired | 
| + // MODE_DEFAULT state, or we're in the commit path. For the latter, don't | 
| + // change the state just yet; else we may hide the preview unnecessarily. | 
| + // Instead, the state will be set correctly after the commit is done. | 
| + if (GetPreviewContents()) { | 
| + model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 
| + allow_preview_to_show_search_suggestions_ = false; | 
| - if (clear_query) { | 
| - if (GetPreviewContents() && !last_full_text_.empty()) | 
| - loader_->Update(string16(), true); | 
| - last_user_text_.clear(); | 
| - last_full_text_.clear(); | 
| + // Send a message asking the preview to clear out old results. | 
| + loader_->Update(string16(), 0, 0, true); | 
| } | 
| - OnStaleLoader(); | 
| + // Clear the first interaction timestamp for later use. | 
| + first_interaction_time_ = base::Time(); | 
| } | 
| -void InstantController::Show(InstantShownReason reason, | 
| - int height, | 
| - InstantSizeUnits units) { | 
| +void InstantController::ShowLoader(InstantShownReason reason, | 
| + int height, | 
| + InstantSizeUnits units) { | 
| + // If we are on a committed search results page, the |loader_| is not in use. | 
| + if (instant_tab_) | 
| + return; | 
| + | 
| DVLOG(1) << "Show: reason=" << reason << " height=" << height << " units=" | 
| << units; | 
| @@ -814,29 +890,33 @@ void InstantController::Show(InstantShownReason reason, | 
| if (reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT && !search_mode_.is_ntp()) | 
| return; | 
| - // Must have updated omnibox after most recent Hide() to show suggestions. | 
| + // Must have updated omnibox after the last HideLoader() to show suggestions. | 
| if (reason == INSTANT_SHOWN_QUERY_SUGGESTIONS && | 
| - !search_mode_.is_search_suggestions()) | 
| + !allow_preview_to_show_search_suggestions_) | 
| 
Jered
2012/12/04 19:05:03
When is allow_preview_to_show_search_suggestions !
 
sreeram
2012/12/04 19:12:37
Whenever we call HideLoader() when the search_mode
 | 
| + return; | 
| + | 
| + // The page is trying to hide itself. Hide explicitly (i.e., don't use | 
| + // HideLoader()) so that it can change its mind. | 
| + if (height == 0) { | 
| + model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 
| return; | 
| + } | 
| - // If the preview is being shown because of the first set of suggestions to | 
| - // arrive for this query editing session, record a histogram value. | 
| + // If the preview is being shown for the first time since the user started | 
| + // typing, record a histogram value. | 
| if (!first_interaction_time_.is_null() && model_.mode().is_default()) { | 
| base::TimeDelta delta = base::Time::Now() - first_interaction_time_; | 
| UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShow", delta); | 
| } | 
| // Show at 100% height except in the following cases: | 
| - // - Instant is disabled. In this case the page should only ever show | 
| - // a dropdown and we should always accept its height. | 
| - // - The page wants to hide (height=0). | 
| + // - Instant is disabled. The page needs to be able to show only a dropdown. | 
| // - The page wants to show custom NTP content. | 
| // - The page is over a website other than search or an NTP, and is not | 
| // already showing at 100% height. | 
| - const bool is_full_height = | 
| - model_.height() == 100 && model_.height_units() == INSTANT_SIZE_PERCENT; | 
| - if (height == 0 || !instant_enabled_ || | 
| - reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT || | 
| + bool is_full_height = model_.height() == 100 && | 
| + model_.height_units() == INSTANT_SIZE_PERCENT; | 
| + if (!instant_enabled_ || reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT || | 
| (search_mode_.is_origin_default() && !is_full_height)) | 
| model_.SetPreviewState(search_mode_, height, units); | 
| else | 
| @@ -844,8 +924,8 @@ void InstantController::Show(InstantShownReason reason, | 
| } | 
| void InstantController::SendBoundsToPage() { | 
| - if (last_omnibox_bounds_ == omnibox_bounds_ || | 
| - !GetPreviewContents() || loader_->IsPointerDownFromActivate()) | 
| + if (last_omnibox_bounds_ == omnibox_bounds_ || !loader_ || | 
| + loader_->is_pointer_down_from_activate()) | 
| return; | 
| last_omnibox_bounds_ = omnibox_bounds_; | 
| @@ -899,8 +979,8 @@ bool InstantController::GetInstantURL(const TemplateURL* template_url, | 
| return false; | 
| if (!url_obj.SchemeIsSecure()) { | 
| - const std::string new_scheme = "https"; | 
| - const std::string new_port = "443"; | 
| + std::string new_scheme = "https"; | 
| + std::string new_port = "443"; | 
| GURL::Replacements secure; | 
| secure.SetSchemeStr(new_scheme); | 
| secure.SetPortStr(new_port); |