| 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);
|
| +}
|
|
|