Index: chrome/browser/instant/instant_controller_impl.cc |
diff --git a/chrome/browser/instant/instant_controller_impl.cc b/chrome/browser/instant/instant_controller_impl.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e7531caf379db11adf063a9d3cefa87bae9b21ec |
--- /dev/null |
+++ b/chrome/browser/instant/instant_controller_impl.cc |
@@ -0,0 +1,409 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chrome/browser/instant/instant_controller_impl.h" |
+ |
+#include "base/string_util.h" |
+#include "base/stringprintf.h" |
+#include "base/utf_string_conversions.h" |
+#include "chrome/browser/autocomplete/autocomplete_match.h" |
+#include "chrome/browser/history/history_service.h" |
+#include "chrome/browser/history/history_service_factory.h" |
+#include "chrome/browser/instant/instant_controller_utils.h" |
+#include "chrome/browser/instant/instant_overlay.h" |
+#include "chrome/browser/instant/instant_overlay_model.h" |
+#include "chrome/browser/instant/instant_preloader.h" |
+#include "chrome/browser/instant/instant_service.h" |
+#include "chrome/browser/instant/instant_service_factory.h" |
+#include "chrome/browser/ui/browser_instant_controller.h" |
+#include "chrome/common/chrome_notification_types.h" |
+#include "content/public/browser/navigation_entry.h" |
+#include "content/public/browser/notification_service.h" |
+#include "content/public/browser/web_contents.h" |
+ |
+#if defined(OS_MACOSX) |
+#include "content/public/browser/render_widget_host_view.h" |
+#endif |
+ |
+namespace { |
+ |
+// An artificial delay (in milliseconds) we introduce before telling the Instant |
+// page about the new omnibox bounds, in cases where the bounds shrink. This is |
+// to avoid the page jumping up/down very fast in response to bounds changes. |
+const int kUpdateBoundsDelayMS = 1000; |
+ |
+} // namespace |
+ |
+InstantControllerImpl::InstantControllerImpl( |
+ chrome::BrowserInstantController* browser, |
+ Profile* profile) |
+ : browser_(browser), |
+ service_(InstantServiceFactory::GetForProfile(profile)), |
+ last_verbatim_(false), |
+ last_transition_type_(content::PAGE_TRANSITION_LINK) { |
+ service_->AddObserver(this); |
+ model_.reset(new InstantOverlayModel(service_)); |
+} |
+ |
+InstantControllerImpl::~InstantControllerImpl() { |
+ service_->RemoveObserver(this); |
+} |
+ |
+bool InstantControllerImpl::Update(const AutocompleteMatch& match, |
+ const string16& user_text, |
+ const string16& full_text, |
+ size_t selection_start, |
+ size_t selection_end, |
+ bool verbatim, |
+ bool /* user_input_in_progress */, |
+ bool omnibox_popup_is_open, |
+ bool /* escape_pressed */, |
+ bool is_keyword_search) { |
+ // If the overlay is being committed, don't change anything. |
+ // TODO(sreeram): Add a browser test for this. |
+ if (IsOverlayBeingCommitted()) |
+ return false; |
+ |
+ // No overlay for URLs and keyword searches or if the user isn't typing. |
+ if (!AutocompleteMatch::IsSearchType(match.type) || is_keyword_search || |
+ !omnibox_popup_is_open || user_text.empty() || full_text.empty()) { |
+ HideOverlay(); |
+ return false; |
+ } |
+ |
+ if (!overlay_) { |
+ // If we don't have a valid (Instant-supporting) web contents ready, bail. |
+ if (!service_->preloader()->supports_instant()) |
+ return false; |
+ |
+ overlay_.reset(new InstantOverlay( |
+ this, service_, service_->preloader()->ReleaseContents())); |
+ } |
+ |
+ // Query is verbatim not only when |verbatim| is true (which indicates that |
+ // the user pressed Delete/Backspace), but also when 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_omnibox_text_ = full_text; |
+ last_verbatim_ = verbatim; |
+ last_transition_type_ = match.transition; |
+ url_for_history_ = match.destination_url; |
+ |
+ service_->LogDebugEvent(base::StringPrintf("%p Update '%s' %s [%d,%d]", |
+ this, |
+ UTF16ToUTF8(last_omnibox_text_).c_str(), |
+ verbatim ? "verbatim" : "psychic", |
+ static_cast<int>(selection_start), |
+ static_cast<int>(selection_end))); |
+ |
+ overlay_->page()->Change(full_text, verbatim, selection_start, selection_end); |
+ |
+ content::NotificationService::current()->Notify( |
+ chrome::NOTIFICATION_INSTANT_UPDATED, |
+ content::NotificationService::AllSources(), |
+ content::NotificationService::NoDetails()); |
+ |
+ return true; |
+} |
+ |
+void InstantControllerImpl::HandleAutocompleteResults( |
+ const std::vector<AutocompleteProvider*>& /* providers */) { |
+} |
+ |
+bool InstantControllerImpl::OnUpOrDownKeyPressed(int /* count */) { |
+ return false; |
+} |
+ |
+void InstantControllerImpl::OnCancel(const AutocompleteMatch& /* match */, |
+ const string16& /* full_text */) { |
+} |
+ |
+bool InstantControllerImpl::IsOverlayingSearchResults() const { |
+ return model_->contents() != NULL; |
+} |
+ |
+content::WebContents* InstantControllerImpl::GetOverlayContents() const { |
+ return overlay_ ? overlay_->contents() : NULL; |
+} |
+ |
+const InstantOverlayModel* InstantControllerImpl::model() const { |
+ return model_.get(); |
+} |
+ |
+void InstantControllerImpl::AddOverlayModelObserver( |
+ InstantOverlayModelObserver* observer) { |
+ model_->AddObserver(observer); |
+} |
+ |
+void InstantControllerImpl::RemoveOverlayModelObserver( |
+ InstantOverlayModelObserver* observer) { |
+ model_->RemoveObserver(observer); |
+} |
+ |
+bool InstantControllerImpl::CommitIfPossible(InstantCommitType type) { |
+ if (!IsOverlayingSearchResults()) |
+ return false; |
+ |
+ service_->LogDebugEvent(base::StringPrintf("%p CommitIfPossible '%s' %s", |
+ this, |
+ UTF16ToUTF8(last_omnibox_text_).c_str(), |
+ InstantControllerUtils::CommitTypeToString(type).c_str())); |
+ |
+ if (type == INSTANT_COMMIT_FOCUS_LOST) |
+ overlay_->page()->Blur(last_omnibox_text_); |
+ else if (type != INSTANT_COMMIT_NAVIGATED) |
+ overlay_->page()->Submit(last_omnibox_text_); |
+ |
+ HistoryService* history = HistoryServiceFactory::GetForProfile( |
+ service_->profile(), 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); |
+ |
+ if (overlay_->page()->navigated_after_change() || |
+ type == INSTANT_COMMIT_NAVIGATED) { |
+ content::NavigationEntry* entry = |
+ GetOverlayContents()->GetController().GetLastCommittedEntry(); |
+ history->AddPage(entry->GetVirtualURL(), base::Time::Now(), |
+ history::SOURCE_BROWSED); |
+ history->SetPageTitle(entry->GetVirtualURL(), |
+ entry->GetTitleForDisplay("")); |
+ } |
+ } |
+ |
+ scoped_ptr<content::WebContents> contents = overlay_->ReleaseContents(); |
+ |
+ if (type == INSTANT_COMMIT_PRESSED_ALT_ENTER) { |
+ contents->GetController().PruneAllButActive(); |
+ } else { |
+ content::WebContents* active_tab = browser_->GetActiveWebContents(); |
+ contents->GetController().CopyStateFromAndPrune( |
+ &active_tab->GetController()); |
+ } |
+ |
+ browser_->CommitInstant(contents.Pass(), last_transition_type_, |
+ type == INSTANT_COMMIT_PRESSED_ALT_ENTER); |
+ |
+ service_->LogDebugEvent(base::StringPrintf("%p Committed", this)); |
+ |
+ HideOverlay(); |
+ |
+ return true; |
+} |
+ |
+scoped_ptr<content::WebContents> InstantControllerImpl::ReleaseNTPContents() { |
+ return scoped_ptr<content::WebContents>(); |
+} |
+ |
+// TODO(tonyg): This method only fires when the popup bounds change. It also |
+// needs to fire when the overlay bounds change (e.g.: open/close info bar). |
+void InstantControllerImpl::SetPopupBounds(const gfx::Rect& bounds) { |
+ if (popup_bounds_ == bounds) |
+ return; |
+ |
+ popup_bounds_ = bounds; |
+ |
+ if (bounds.height() > last_popup_bounds_.height()) { |
+ update_bounds_timer_.Stop(); |
+ SendPopupBoundsToPage(); |
+ } else if (!update_bounds_timer_.IsRunning()) { |
+ update_bounds_timer_.Start(FROM_HERE, |
+ base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), |
+ this, &InstantControllerImpl::SendPopupBoundsToPage); |
+ } |
+} |
+ |
+void InstantControllerImpl::SetOmniboxBounds(const gfx::Rect& /* bounds */) { |
+} |
+ |
+void InstantControllerImpl::OmniboxFocusChanged( |
+ OmniboxFocusState state, |
+ OmniboxFocusChangeReason reason, |
+ gfx::NativeView view_gaining_focus) { |
+ |
+ service_->LogDebugEvent(base::StringPrintf("%p OmniboxFocus %s due to %s", |
+ this, |
+ InstantControllerUtils::FocusStateToString(state).c_str(), |
+ InstantControllerUtils::FocusChangeReasonToString(reason).c_str())); |
+ |
+ if (state == OMNIBOX_FOCUS_NONE) { |
+ // Don't do anything if we don't have an overlay or if the overlay isn't |
+ // showing or if we are in the midst of committing the overlay. |
+ if (!overlay_ || !IsOverlayingSearchResults() || IsOverlayBeingCommitted()) |
+ return; |
+ |
+ view_gaining_focus = |
+ InstantControllerUtils::GetViewGainingFocus(view_gaining_focus); |
+ if (InstantControllerUtils::IsViewInContents(view_gaining_focus, |
+ GetOverlayContents())) |
+ CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); |
+ else |
+ HideOverlay(); |
+ } else { |
+ service_->preloader()->InitContents(); |
+ } |
+} |
+ |
+void InstantControllerImpl::TabDeactivated( |
+ content::WebContents* /* contents */) { |
+ if (GetOverlayContents()) |
+ HideOverlay(); |
+} |
+ |
+void InstantControllerImpl::SearchModeChanged( |
+ const chrome::search::Mode& /* old_mode */, |
+ const chrome::search::Mode& /* new_mode */) { |
+} |
+ |
+void InstantControllerImpl::ActiveTabChanged() { |
+} |
+ |
+void InstantControllerImpl::SwappedOverlayContents() { |
+ if (IsOverlayingSearchResults()) |
+ ShowOverlay(); |
+} |
+ |
+void InstantControllerImpl::FocusedOverlayContents() { |
+ if (IsOverlayingSearchResults()) |
+ browser_->InstantOverlayFocused(); |
+} |
+ |
+void InstantControllerImpl::RenderViewGone( |
+ const content::WebContents* /* contents */) { |
+ HideOverlay(); |
+} |
+ |
+void InstantControllerImpl::InitSearchBox( |
+ const content::WebContents* /* contents */) { |
+} |
+ |
+void InstantControllerImpl::InstantSupportDetermined( |
+ const content::WebContents* /* contents */) { |
+ if (!overlay_->page()->supports_instant()) |
+ HideOverlay(); |
+ service_->InstantSupportDetermined(); |
+} |
+ |
+void InstantControllerImpl::SetSuggestion( |
+ const content::WebContents* /* contents */, |
+ const InstantSuggestion& suggestion) { |
+ string16 text = suggestion.text; |
+ |
+ if (StartsWith(text, last_omnibox_text_, true)) { |
+ text.erase(0, last_omnibox_text_.size()); |
+ } else if (!InstantControllerUtils::NormalizeAndStripPrefix( |
+ &text, last_omnibox_text_)) { |
+ text.clear(); |
+ } |
+ |
+ if (last_verbatim_) |
+ text.clear(); |
+ |
+ if (!text.empty()) { |
+ service_->LogDebugEvent(base::StringPrintf("%p SetSuggestion '%s'", |
+ this, |
+ UTF16ToUTF8(text).c_str())); |
+ browser_->SetInstantSuggestion(InstantSuggestion( |
+ text, INSTANT_COMPLETE_NOW, INSTANT_SUGGESTION_SEARCH)); |
+ } |
+ |
+ ShowOverlay(); |
+} |
+ |
+void InstantControllerImpl::NavigateToURL( |
+ const content::WebContents* /* contents */, |
+ const GURL& /* url */, |
+ content::PageTransition /* transition */, |
+ WindowOpenDisposition /* disposition */) { |
+} |
+ |
+void InstantControllerImpl::ShowOverlay( |
+ const content::WebContents* /* contents */, |
+ int /* height */, |
+ InstantSizeUnits /* height_units */) { |
+} |
+ |
+void InstantControllerImpl::SetFocusState( |
+ const content::WebContents* /* contents */, |
+ OmniboxFocusState /* focus_state */) { |
+} |
+ |
+void InstantControllerImpl::DeleteMostVisitedItem( |
+ const content::WebContents* /* contents */, |
+ const GURL& /* url */) { |
+} |
+ |
+void InstantControllerImpl::UndoMostVisitedItemDeletion( |
+ const content::WebContents* /* contents */, |
+ const GURL& /* url */) { |
+} |
+ |
+void InstantControllerImpl::UndoAllMostVisitedItemDeletions( |
+ const content::WebContents* /* contents */) { |
+} |
+ |
+void InstantControllerImpl::InstantStatusChanged() { |
+ HideOverlay(); |
+} |
+ |
+void InstantControllerImpl::ThemeInfoChanged() { |
+} |
+ |
+void InstantControllerImpl::MostVisitedItemsChanged() { |
+} |
+ |
+bool InstantControllerImpl::IsOverlayBeingCommitted() const { |
+ return (overlay_ && overlay_->is_pointer_down_from_activate()) || |
+ (IsOverlayingSearchResults() && !GetOverlayContents()); |
+} |
+ |
+void InstantControllerImpl::ShowOverlay() { |
+#if defined(OS_MACOSX) |
+ content::RenderWidgetHostView* rwhv = |
+ GetOverlayContents()->GetRenderWidgetHostView(); |
+ if (rwhv) |
+ rwhv->SetTakesFocusOnlyOnMouseDown(true); |
+#endif |
+ model_->SetOverlayState(GetOverlayContents(), 100, INSTANT_SIZE_PERCENT); |
+} |
+ |
+void InstantControllerImpl::HideOverlay() { |
+ model_->SetOverlayState(NULL, 0, INSTANT_SIZE_PIXELS); |
+ last_popup_bounds_ = gfx::Rect(); |
+ if (overlay_) { |
+ overlay_->ReleaseContents(); |
+ MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); |
+ } |
+} |
+ |
+void InstantControllerImpl::SendPopupBoundsToPage() { |
+ if (!overlay_ || last_popup_bounds_ == popup_bounds_ || |
+ overlay_->is_pointer_down_from_activate()) |
+ return; |
+ |
+ last_popup_bounds_ = popup_bounds_; |
+ |
+ gfx::Rect overlay_bounds = browser_->GetInstantBounds(); |
+ gfx::Rect intersection = gfx::IntersectRects(popup_bounds_, overlay_bounds); |
+ |
+ // Translate into window coordinates. |
+ if (!intersection.IsEmpty()) { |
+ intersection.Offset(-overlay_bounds.origin().x(), |
+ -overlay_bounds.origin().y()); |
+ } |
+ |
+ // In the current Chrome UI, these must always be true so they sanity check |
+ // the above operations. In a future UI, these may be removed or adjusted. |
+ // There is no point in sanity-checking intersection.y() because the omnibox |
+ // can be placed anywhere vertically relative to the overlay (for example, in |
+ // Mac fullscreen mode, the omnibox is fully enclosed by the overlay bounds). |
+ DCHECK_LE(0, intersection.x()); |
+ DCHECK_LE(0, intersection.width()); |
+ DCHECK_LE(0, intersection.height()); |
+ |
+ overlay_->page()->PopupBounds(intersection); |
+} |