| Index: chrome/browser/instant/instant_controller.cc
|
| diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc
|
| index 51b26cccd8e06cd1a6732e49af2cfa874f6af195..2eefd0670edbc69df96796eeda0d7b2ef7730152 100644
|
| --- a/chrome/browser/instant/instant_controller.cc
|
| +++ b/chrome/browser/instant/instant_controller.cc
|
| @@ -4,58 +4,110 @@
|
|
|
| #include "chrome/browser/instant/instant_controller.h"
|
|
|
| -#include "base/bind.h"
|
| #include "base/command_line.h"
|
| -#include "base/message_loop.h"
|
| +#include "base/i18n/case_conversion.h"
|
| #include "base/metrics/histogram.h"
|
| -#include "build/build_config.h"
|
| #include "chrome/browser/autocomplete/autocomplete_match.h"
|
| +#include "chrome/browser/history/history.h"
|
| +#include "chrome/browser/history/history_service_factory.h"
|
| +#include "chrome/browser/history/history_tab_helper.h"
|
| #include "chrome/browser/instant/instant_controller_delegate.h"
|
| #include "chrome/browser/instant/instant_loader.h"
|
| #include "chrome/browser/platform_util.h"
|
| #include "chrome/browser/prefs/pref_service.h"
|
| -#include "chrome/browser/profiles/profile.h"
|
| -#include "chrome/browser/search_engines/template_url.h"
|
| #include "chrome/browser/search_engines/template_url_service.h"
|
| #include "chrome/browser/search_engines/template_url_service_factory.h"
|
| -#include "chrome/browser/ui/blocked_content/blocked_content_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 "chrome/common/pref_names.h"
|
| +#include "content/public/browser/favicon_status.h"
|
| +#include "content/public/browser/navigation_entry.h"
|
| #include "content/public/browser/notification_service.h"
|
| #include "content/public/browser/render_widget_host_view.h"
|
| #include "content/public/browser/web_contents.h"
|
| +#include "ui/gfx/codec/png_codec.h"
|
|
|
| #if defined(TOOLKIT_VIEWS)
|
| -#include "ui/views/focus/focus_manager.h"
|
| -#include "ui/views/view.h"
|
| #include "ui/views/widget/widget.h"
|
| #endif
|
|
|
| +namespace {
|
| +
|
| +enum PreviewUsageType {
|
| + PREVIEW_CREATED = 0,
|
| + PREVIEW_DELETED,
|
| + PREVIEW_LOADED,
|
| + PREVIEW_SHOWED,
|
| + PREVIEW_COMMITTED,
|
| + PREVIEW_NUM_TYPES,
|
| +};
|
| +
|
| +// 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;
|
| +
|
| +// The maximum number of times we'll load a non-Instant-supporting search engine
|
| +// before we give up and blacklist it for the rest of the browsing session.
|
| +const int kMaxInstantSupportFailures = 10;
|
| +
|
| +std::string ModeToString(InstantController::Mode mode) {
|
| + switch (mode) {
|
| + case InstantController::INSTANT: return "_Instant";
|
| + case InstantController::SUGGEST: return "_Suggest";
|
| + case InstantController::HIDDEN: return "_Hidden";
|
| + case InstantController::SILENT: return "_Silent";
|
| + }
|
| +
|
| + NOTREACHED();
|
| + return std::string();
|
| +}
|
| +
|
| +void AddPreviewUsageForHistogram(InstantController::Mode mode,
|
| + PreviewUsageType usage) {
|
| + DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage;
|
| + base::Histogram* histogram = base::LinearHistogram::FactoryGet(
|
| + "Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES,
|
| + PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag);
|
| + histogram->Add(usage);
|
| +}
|
| +
|
| +void AddSessionStorageHistogram(InstantController::Mode mode,
|
| + const TabContents* tab1,
|
| + const TabContents* tab2) {
|
| + base::Histogram* histogram = base::BooleanHistogram::FactoryGet(
|
| + "Instant.SessionStorageNamespace" + ModeToString(mode),
|
| + base::Histogram::kUmaTargetedHistogramFlag);
|
| + histogram->AddBoolean(
|
| + tab1->web_contents()->GetController().GetSessionStorageNamespace() ==
|
| + tab2->web_contents()->GetController().GetSessionStorageNamespace());
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| InstantController::InstantController(InstantControllerDelegate* delegate,
|
| Mode mode)
|
| : delegate_(delegate),
|
| - is_displayable_(false),
|
| - is_out_of_date_(true),
|
| - commit_on_pointer_release_(false),
|
| + mode_(mode),
|
| + last_active_tab_(NULL),
|
| + last_verbatim_(false),
|
| + last_complete_behavior_(INSTANT_COMPLETE_NOW),
|
| last_transition_type_(content::PAGE_TRANSITION_LINK),
|
| - ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
|
| - mode_(mode) {
|
| - DCHECK(mode_ == INSTANT || mode_ == SUGGEST || mode_ == HIDDEN ||
|
| - mode_ == SILENT);
|
| + is_showing_(false),
|
| + loader_processed_last_update_(false) {
|
| }
|
|
|
| InstantController::~InstantController() {
|
| + if (GetPreviewContents())
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
|
| }
|
|
|
| // static
|
| void InstantController::RegisterUserPrefs(PrefService* prefs) {
|
| - prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown,
|
| - false,
|
| + prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false,
|
| PrefService::SYNCABLE_PREF);
|
| - prefs->RegisterBooleanPref(prefs::kInstantEnabled,
|
| - false,
|
| + prefs->RegisterBooleanPref(prefs::kInstantEnabled, false,
|
| PrefService::SYNCABLE_PREF);
|
|
|
| // TODO(jamescook): Move this to search controller.
|
| @@ -65,214 +117,214 @@ void InstantController::RegisterUserPrefs(PrefService* prefs) {
|
| }
|
|
|
| // static
|
| -void InstantController::RecordMetrics(Profile* profile) {
|
| - UMA_HISTOGRAM_ENUMERATION("Instant.Status", IsEnabled(profile), 2);
|
| -}
|
| -
|
| -// static
|
| bool InstantController::IsEnabled(Profile* profile) {
|
| - const PrefService* prefs = profile->GetPrefs();
|
| + const PrefService* prefs = profile ? profile->GetPrefs() : NULL;
|
| return prefs && prefs->GetBoolean(prefs::kInstantEnabled);
|
| }
|
|
|
| -// static
|
| -void InstantController::Enable(Profile* profile) {
|
| - PrefService* prefs = profile->GetPrefs();
|
| - if (!prefs)
|
| - return;
|
| -
|
| - prefs->SetBoolean(prefs::kInstantEnabled, true);
|
| - prefs->SetBoolean(prefs::kInstantConfirmDialogShown, true);
|
| - UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 1, 2);
|
| -}
|
| -
|
| -// static
|
| -void InstantController::Disable(Profile* profile) {
|
| - PrefService* prefs = profile->GetPrefs();
|
| - if (!prefs)
|
| - return;
|
| -
|
| - prefs->SetBoolean(prefs::kInstantEnabled, false);
|
| - UMA_HISTOGRAM_ENUMERATION("Instant.Preference", 0, 2);
|
| -}
|
| -
|
| bool InstantController::Update(const AutocompleteMatch& match,
|
| const string16& user_text,
|
| bool verbatim,
|
| - string16* suggested_text) {
|
| - suggested_text->clear();
|
| + string16* suggested_text,
|
| + InstantCompleteBehavior* complete_behavior) {
|
| + const TabContents* active_tab = delegate_->GetActiveTabContents();
|
|
|
| - is_out_of_date_ = false;
|
| - commit_on_pointer_release_ = false;
|
| - last_transition_type_ = match.transition;
|
| - last_url_ = match.destination_url;
|
| - last_user_text_ = user_text;
|
| + // We could get here with no active tab if the Browser is closing.
|
| + if (!active_tab) {
|
| + Hide();
|
| + return false;
|
| + }
|
| +
|
| + std::string instant_url;
|
| + Profile* profile = active_tab->profile();
|
|
|
| - TabContents* tab_contents = delegate_->GetInstantHostTabContents();
|
| - if (!tab_contents) {
|
| + // If the match's TemplateURL isn't valid, it is likely not a query.
|
| + if (!GetInstantURL(match.GetTemplateURL(profile), &instant_url)) {
|
| Hide();
|
| return false;
|
| }
|
|
|
| - Profile* profile = tab_contents->profile();
|
| - const TemplateURL* template_url = match.GetTemplateURL(profile);
|
| - const TemplateURL* default_t_url =
|
| - TemplateURLServiceFactory::GetForProfile(profile)
|
| - ->GetDefaultSearchProvider();
|
| - if (!IsValidInstantTemplateURL(template_url) || !default_t_url ||
|
| - template_url->id() != default_t_url->id()) {
|
| + string16 full_text = user_text + *suggested_text;
|
| +
|
| + if (full_text.empty()) {
|
| Hide();
|
| return false;
|
| }
|
|
|
| - if (!loader_.get() || loader_->template_url_id() != template_url->id())
|
| - loader_.reset(new InstantLoader(this, template_url->id(), std::string()));
|
| + // The presence of any suggested_text implies verbatim.
|
| + DCHECK(suggested_text->empty() || verbatim)
|
| + << user_text << "|" << *suggested_text;
|
|
|
| - if (mode_ == SILENT) {
|
| - // For the SILENT mode, we process |user_text| at commit time.
|
| - loader_->MaybeLoadInstantURL(tab_contents, template_url);
|
| + ResetLoader(instant_url, active_tab);
|
| + last_active_tab_ = active_tab;
|
| +
|
| + // Track the non-Instant search URL for this query.
|
| + url_for_history_ = match.destination_url;
|
| + last_transition_type_ = match.transition;
|
| +
|
| + last_user_text_ = user_text;
|
| +
|
| + // Don't send an update to the loader if the query text hasn't changed.
|
| + if (full_text == last_full_text_ && verbatim == last_verbatim_) {
|
| + // Since we are updating |suggested_text|, shouldn't we also update
|
| + // |last_full_text_|? No. There's no guarantee that our suggestion will
|
| + // actually be inline autocompleted. For example, it may get trumped by
|
| + // a history suggestion. If our suggestion does make it, the omnibox will
|
| + // call Update() again, at which time we'll update |last_full_text_|.
|
| + *suggested_text = last_suggestion_;
|
| + *complete_behavior = last_complete_behavior_;
|
| +
|
| + // We need to call Show() here because of this:
|
| + // 1. User has typed a query (say Q). Instant overlay is showing results.
|
| + // 2. User arrows-down to a URL entry or erases all omnibox text. Both of
|
| + // these cause the overlay to Hide().
|
| + // 3. User arrows-up to Q or types Q again. The last text we processed is
|
| + // still Q, so we don't Update() the loader, but we do need to Show().
|
| + if (loader_processed_last_update_ && mode_ == INSTANT)
|
| + Show();
|
| return true;
|
| }
|
|
|
| - UpdateLoader(tab_contents, template_url, match.destination_url,
|
| - match.transition, user_text, verbatim, suggested_text);
|
| + last_full_text_ = full_text;
|
| + last_verbatim_ = verbatim;
|
| + loader_processed_last_update_ = false;
|
| +
|
| + // Reset the last suggestion, as it's no longer valid.
|
| + suggested_text->clear();
|
| + last_suggestion_.clear();
|
| + *complete_behavior = last_complete_behavior_ = INSTANT_COMPLETE_NOW;
|
| +
|
| + if (mode_ != SILENT) {
|
| + loader_->Update(last_full_text_, last_verbatim_);
|
| +
|
| + content::NotificationService::current()->Notify(
|
| + chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
|
| + content::Source<InstantController>(this),
|
| + content::NotificationService::NoDetails());
|
| + }
|
|
|
| - content::NotificationService::current()->Notify(
|
| - chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
|
| - content::Source<InstantController>(this),
|
| - content::NotificationService::NoDetails());
|
| return true;
|
| }
|
|
|
| +// TODO(tonyg): This method only fires when the omnibox bounds change. It also
|
| +// needs to fire when the preview bounds change (e.g.: open/close info bar).
|
| void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
|
| - if (omnibox_bounds_ == bounds)
|
| + if (omnibox_bounds_ == bounds || mode_ != INSTANT)
|
| return;
|
|
|
| - // Always track the omnibox bounds. That way if Update is later invoked the
|
| - // bounds are in sync.
|
| omnibox_bounds_ = bounds;
|
| -
|
| - if (loader_.get() && !is_out_of_date_ && mode_ == INSTANT)
|
| - loader_->SetOmniboxBounds(bounds);
|
| -}
|
| -
|
| -void InstantController::DestroyPreviewContents() {
|
| - if (!loader_.get()) {
|
| - // We're not showing anything, nothing to do.
|
| - return;
|
| + if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) {
|
| + update_bounds_timer_.Stop();
|
| + SendBoundsToPage();
|
| + } else if (!update_bounds_timer_.IsRunning()) {
|
| + update_bounds_timer_.Start(FROM_HERE,
|
| + base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this,
|
| + &InstantController::SendBoundsToPage);
|
| }
|
| +}
|
|
|
| - if (is_displayable_) {
|
| - is_displayable_ = false;
|
| - delegate_->HideInstant();
|
| - }
|
| - delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY, NULL);
|
| +TabContents* InstantController::GetPreviewContents() const {
|
| + return loader_.get() ? loader_->preview_contents() : NULL;
|
| }
|
|
|
| void InstantController::Hide() {
|
| - is_out_of_date_ = true;
|
| - commit_on_pointer_release_ = false;
|
| - if (is_displayable_) {
|
| - is_displayable_ = false;
|
| + last_active_tab_ = NULL;
|
| + if (is_showing_) {
|
| + is_showing_ = false;
|
| delegate_->HideInstant();
|
| }
|
| }
|
|
|
| bool InstantController::IsCurrent() const {
|
| - // TODO(mmenke): See if we can do something more intelligent in the
|
| - // navigation pending case.
|
| - return is_displayable_ && !loader_->IsNavigationPending() &&
|
| - !loader_->needs_reload();
|
| -}
|
| -
|
| -bool InstantController::PrepareForCommit() {
|
| - if (is_out_of_date_ || !loader_.get())
|
| - return false;
|
| -
|
| - // If we are in the visible (INSTANT) mode, return the status of the preview.
|
| - if (mode_ == INSTANT)
|
| - return IsCurrent();
|
| -
|
| - TabContents* tab_contents = delegate_->GetInstantHostTabContents();
|
| - if (!tab_contents)
|
| - return false;
|
| -
|
| - const TemplateURL* template_url =
|
| - TemplateURLServiceFactory::GetForProfile(tab_contents->profile())
|
| - ->GetDefaultSearchProvider();
|
| - if (!IsValidInstantTemplateURL(template_url) ||
|
| - loader_->template_url_id() != template_url->id() ||
|
| - loader_->IsNavigationPending() ||
|
| - loader_->is_determining_if_page_supports_instant()) {
|
| - return false;
|
| - }
|
| -
|
| - // In the SUGGEST and HIDDEN modes, we must have sent an Update() by now, so
|
| - // check if the loader failed to process it.
|
| - if ((mode_ == SUGGEST || mode_ == HIDDEN)
|
| - && (!loader_->ready() || !loader_->http_status_ok())) {
|
| - return false;
|
| - }
|
| -
|
| - // Ignore the suggested text, as we are about to commit the verbatim query.
|
| - string16 suggested_text;
|
| - UpdateLoader(tab_contents, template_url, last_url_, last_transition_type_,
|
| - last_user_text_, true, &suggested_text);
|
| - return true;
|
| + DCHECK(IsOutOfDate() || GetPreviewContents());
|
| + return !IsOutOfDate() && GetPreviewContents() && loader_->supports_instant();
|
| }
|
|
|
| TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) {
|
| - DCHECK(loader_.get());
|
| - TabContents* tab_contents = delegate_->GetInstantHostTabContents();
|
| - DCHECK(tab_contents);
|
| - TabContents* preview = ReleasePreviewContents(type, tab_contents);
|
| + const TabContents* active_tab = delegate_->GetActiveTabContents();
|
| + TabContents* preview = ReleasePreviewContents(type);
|
| + AddSessionStorageHistogram(mode_, active_tab, preview);
|
| preview->web_contents()->GetController().CopyStateFromAndPrune(
|
| - &tab_contents->web_contents()->GetController());
|
| + &active_tab->web_contents()->GetController());
|
| delegate_->CommitInstant(preview);
|
| - CompleteRelease(preview);
|
| return preview;
|
| }
|
|
|
| -bool InstantController::CommitIfCurrent() {
|
| - if (IsCurrent()) {
|
| - CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER);
|
| - return true;
|
| +TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) {
|
| + TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_);
|
| +
|
| + // If the preview page has navigated since the last Update(), we need to add
|
| + // the navigation to history ourselves. Else, the page will navigate after
|
| + // commit, and it will be added to history in the usual manner.
|
| + scoped_refptr<history::HistoryAddPageArgs> last_navigation =
|
| + loader_->last_navigation();
|
| + if (last_navigation != NULL) {
|
| + content::NavigationEntry* entry =
|
| + preview->web_contents()->GetController().GetActiveEntry();
|
| + DCHECK_EQ(last_navigation->url, entry->GetURL());
|
| +
|
| + // Add the page to history.
|
| + preview->history_tab_helper()->UpdateHistoryForNavigation(last_navigation);
|
| +
|
| + // Update the page title.
|
| + preview->history_tab_helper()->UpdateHistoryPageTitle(*entry);
|
| +
|
| + // Update the favicon.
|
| + FaviconService* favicon_service =
|
| + preview->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
|
| + if (favicon_service && entry->GetFavicon().valid &&
|
| + entry->GetFavicon().image.IsEmpty()) {
|
| + std::vector<unsigned char> image_data;
|
| + // TODO: Add all variants once the history service supports it.
|
| + gfx::PNGCodec::EncodeBGRASkBitmap(
|
| + entry->GetFavicon().image.AsBitmap(), false, &image_data);
|
| + favicon_service->SetFavicon(entry->GetURL(),
|
| + entry->GetFavicon().url,
|
| + image_data,
|
| + history::FAVICON);
|
| + }
|
| }
|
| - return false;
|
| -}
|
|
|
| -void InstantController::SetCommitOnPointerRelease() {
|
| - commit_on_pointer_release_ = true;
|
| -}
|
| + // Add a fake history entry with a non-Instant search URL, so that search
|
| + // terms extraction (for autocomplete history matches) works.
|
| + HistoryService* history = HistoryServiceFactory::GetForProfile(
|
| + preview->profile(), Profile::EXPLICIT_ACCESS);
|
| + if (history) {
|
| + history->AddPage(url_for_history_, NULL, 0, GURL(), last_transition_type_,
|
| + history::RedirectList(), history::SOURCE_BROWSED, false);
|
| + }
|
|
|
| -bool InstantController::IsPointerDownFromActivate() {
|
| - DCHECK(loader_.get());
|
| - return loader_->IsPointerDownFromActivate();
|
| -}
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED);
|
|
|
| -#if defined(OS_MACOSX)
|
| -void InstantController::OnAutocompleteLostFocus(
|
| - gfx::NativeView view_gaining_focus) {
|
| - // If |IsPointerDownFromActivate()| returns false, the RenderWidgetHostView
|
| - // did not receive a mouseDown event. Therefore, we should destroy the
|
| - // preview. Otherwise, the RWHV was clicked, so we commit the preview.
|
| - if (!IsCurrent() || !IsPointerDownFromActivate())
|
| - DestroyPreviewContents();
|
| - else
|
| - SetCommitOnPointerRelease();
|
| + // We may have gotten here from CommitInstant(), which means the loader may
|
| + // still be on the stack. So, schedule a destruction for later.
|
| + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
|
| +
|
| + // This call is here to hide the preview and reset view state. It won't
|
| + // actually delete |loader_| because it was just released to DeleteSoon().
|
| + DeleteLoader();
|
| +
|
| + return preview;
|
| }
|
| -#else
|
| +
|
| +// TODO(sreeram): Since we never delete the loader except when committing
|
| +// Instant, the loader may have a very stale page. Reload it when stale.
|
| void InstantController::OnAutocompleteLostFocus(
|
| gfx::NativeView view_gaining_focus) {
|
| - if (!IsCurrent()) {
|
| - DestroyPreviewContents();
|
| + DCHECK(!is_showing_ || GetPreviewContents());
|
| +
|
| + // If the preview is not showing, nothing to do.
|
| + if (!is_showing_ || !GetPreviewContents())
|
| return;
|
| - }
|
|
|
| +#if defined(OS_MACOSX)
|
| + if (!loader_->IsPointerDownFromActivate())
|
| + Hide();
|
| +#else
|
| content::RenderWidgetHostView* rwhv =
|
| GetPreviewContents()->web_contents()->GetRenderWidgetHostView();
|
| if (!view_gaining_focus || !rwhv) {
|
| - DestroyPreviewContents();
|
| + Hide();
|
| return;
|
| }
|
|
|
| @@ -295,20 +347,16 @@ void InstantController::OnAutocompleteLostFocus(
|
|
|
| gfx::NativeView tab_view =
|
| GetPreviewContents()->web_contents()->GetNativeView();
|
| +
|
| // Focus is going to the renderer.
|
| if (rwhv->GetNativeView() == view_gaining_focus ||
|
| tab_view == view_gaining_focus) {
|
| - if (!IsPointerDownFromActivate()) {
|
| - // If the mouse is not down, focus is not going to the renderer. Someone
|
| - // else moved focus and we shouldn't commit.
|
| - DestroyPreviewContents();
|
| - return;
|
| - }
|
|
|
| - // We're showing instant results. As instant results may shift when
|
| - // committing we commit on the mouse up. This way a slow click still works
|
| - // fine.
|
| - SetCommitOnPointerRelease();
|
| + // If the mouse is not down, focus is not going to the renderer. Someone
|
| + // else moved focus and we shouldn't commit.
|
| + if (!loader_->IsPointerDownFromActivate())
|
| + Hide();
|
| +
|
| return;
|
| }
|
|
|
| @@ -328,184 +376,214 @@ void InstantController::OnAutocompleteLostFocus(
|
| return;
|
| }
|
|
|
| - DestroyPreviewContents();
|
| -}
|
| + Hide();
|
| #endif
|
| +}
|
|
|
| void InstantController::OnAutocompleteGotFocus() {
|
| - TabContents* tab_contents = delegate_->GetInstantHostTabContents();
|
| - if (!tab_contents)
|
| - return;
|
| + const TabContents* active_tab = delegate_->GetActiveTabContents();
|
|
|
| - const TemplateURL* template_url =
|
| - TemplateURLServiceFactory::GetForProfile(tab_contents->profile())
|
| - ->GetDefaultSearchProvider();
|
| - if (!IsValidInstantTemplateURL(template_url))
|
| + // We could get here with no active tab if the Browser is closing.
|
| + if (!active_tab)
|
| return;
|
|
|
| - if (!loader_.get() || loader_->template_url_id() != template_url->id())
|
| - loader_.reset(new InstantLoader(this, template_url->id(), std::string()));
|
| - loader_->MaybeLoadInstantURL(tab_contents, template_url);
|
| -}
|
| -
|
| -TabContents* InstantController::ReleasePreviewContents(
|
| - InstantCommitType type,
|
| - TabContents* current_tab) {
|
| - if (!loader_.get())
|
| - return NULL;
|
| -
|
| - TabContents* tab = loader_->ReleasePreviewContents(type, current_tab);
|
| - ClearBlacklist();
|
| - is_out_of_date_ = true;
|
| - is_displayable_ = false;
|
| - commit_on_pointer_release_ = false;
|
| - omnibox_bounds_ = gfx::Rect();
|
| - loader_.reset();
|
| - return tab;
|
| -}
|
| + // Since we don't have any autocomplete match to work with, we'll just use
|
| + // the default search provider's Instant URL.
|
| + const TemplateURL* template_url =
|
| + TemplateURLServiceFactory::GetForProfile(active_tab->profile())->
|
| + GetDefaultSearchProvider();
|
|
|
| -void InstantController::CompleteRelease(TabContents* tab) {
|
| - tab->blocked_content_tab_helper()->SetAllContentsBlocked(false);
|
| -}
|
| + std::string instant_url;
|
| + if (!GetInstantURL(template_url, &instant_url))
|
| + return;
|
|
|
| -TabContents* InstantController::GetPreviewContents() const {
|
| - return loader_.get() ? loader_->preview_contents() : NULL;
|
| + ResetLoader(instant_url, active_tab);
|
| }
|
|
|
| -void InstantController::InstantStatusChanged(InstantLoader* loader) {
|
| - DCHECK(loader_.get());
|
| - UpdateIsDisplayable();
|
| +bool InstantController::commit_on_pointer_release() const {
|
| + return GetPreviewContents() && loader_->IsPointerDownFromActivate();
|
| }
|
|
|
| -void InstantController::SetSuggestedTextFor(
|
| +void InstantController::SetSuggestions(
|
| InstantLoader* loader,
|
| - const string16& text,
|
| + const std::vector<string16>& suggestions,
|
| InstantCompleteBehavior behavior) {
|
| - if (is_out_of_date_)
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN)
|
| return;
|
|
|
| - if (mode_ == INSTANT || mode_ == SUGGEST)
|
| - delegate_->SetSuggestedText(text, behavior);
|
| -}
|
| -
|
| -gfx::Rect InstantController::GetInstantBounds() {
|
| - return delegate_->GetInstantBounds();
|
| -}
|
| + loader_processed_last_update_ = true;
|
|
|
| -bool InstantController::ShouldCommitInstantOnPointerRelease() {
|
| - return commit_on_pointer_release_;
|
| -}
|
| + string16 suggestion;
|
| + if (!suggestions.empty())
|
| + suggestion = suggestions[0];
|
|
|
| -void InstantController::CommitInstantLoader(InstantLoader* loader) {
|
| - if (loader_.get() && loader_.get() == loader) {
|
| - CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
|
| + string16 suggestion_lower = base::i18n::ToLower(suggestion);
|
| + string16 user_text_lower = base::i18n::ToLower(last_user_text_);
|
| + if (user_text_lower.size() >= suggestion_lower.size() ||
|
| + suggestion_lower.compare(0, user_text_lower.size(), user_text_lower)) {
|
| + suggestion.clear();
|
| } else {
|
| - // This can happen if the mouse was down, we swapped out the preview and
|
| - // the mouse was released. Generally this shouldn't happen, but if it does
|
| - // revert.
|
| - DestroyPreviewContents();
|
| + suggestion.erase(0, last_user_text_.size());
|
| }
|
| +
|
| + last_suggestion_ = suggestion;
|
| + last_complete_behavior_ = behavior;
|
| + if (!last_verbatim_)
|
| + delegate_->SetSuggestedText(suggestion, behavior);
|
| +
|
| + if (mode_ != SUGGEST)
|
| + Show();
|
| }
|
|
|
| -void InstantController::InstantLoaderDoesntSupportInstant(
|
| - InstantLoader* loader) {
|
| - VLOG(1) << "provider does not support instant";
|
| +void InstantController::CommitInstantLoader(InstantLoader* loader) {
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
|
| + if (loader_ != loader || !is_showing_ || IsOutOfDate())
|
| + return;
|
| +
|
| + CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
|
| +}
|
|
|
| - // Don't attempt to use instant for this search engine again.
|
| - BlacklistFromInstant();
|
| +void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) {
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED);
|
| }
|
|
|
| -void InstantController::AddToBlacklist(InstantLoader* loader, const GURL& url) {
|
| - // Don't attempt to use instant for this search engine again.
|
| - BlacklistFromInstant();
|
| +void InstantController::InstantSupportDetermined(InstantLoader* loader,
|
| + bool supports_instant) {
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + if (supports_instant) {
|
| + blacklisted_urls_.erase(loader->instant_url());
|
| + } else {
|
| + ++blacklisted_urls_[loader->instant_url()];
|
| + if (loader_ == loader) {
|
| + if (GetPreviewContents())
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
|
| +
|
| + // Because of the state of the stack, we can't destroy the loader now.
|
| + MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
|
| + DeleteLoader();
|
| + }
|
| + }
|
| +
|
| + content::Details<const bool> details(&supports_instant);
|
| + content::NotificationService::current()->Notify(
|
| + chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED,
|
| + content::NotificationService::AllSources(),
|
| + details);
|
| }
|
|
|
| void InstantController::SwappedTabContents(InstantLoader* loader) {
|
| - if (is_displayable_)
|
| - delegate_->ShowInstant(loader->preview_contents());
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + if (loader_ == loader && is_showing_)
|
| + delegate_->ShowInstant();
|
| }
|
|
|
| -void InstantController::InstantLoaderContentsFocused() {
|
| +void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) {
|
| + DCHECK_EQ(loader_.get(), loader);
|
| + DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
|
| #if defined(USE_AURA)
|
| // On aura the omnibox only receives a focus lost if we initiate the focus
|
| // change. This does that.
|
| - if (mode_ == INSTANT)
|
| + if (is_showing_ && !IsOutOfDate())
|
| delegate_->InstantPreviewFocused();
|
| #endif
|
| }
|
|
|
| -void InstantController::UpdateIsDisplayable() {
|
| - bool displayable = !is_out_of_date_ && loader_.get() && loader_->ready() &&
|
| - loader_->http_status_ok();
|
| - if (displayable == is_displayable_ || mode_ != INSTANT)
|
| - return;
|
| +void InstantController::ResetLoader(const std::string& instant_url,
|
| + const TabContents* active_tab) {
|
| + if (GetPreviewContents() && loader_->instant_url() != instant_url)
|
| + DeleteLoader();
|
|
|
| - is_displayable_ = displayable;
|
| - if (!is_displayable_) {
|
| - delegate_->HideInstant();
|
| - } else {
|
| - delegate_->ShowInstant(loader_->preview_contents());
|
| - content::NotificationService::current()->Notify(
|
| - chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN,
|
| - content::Source<InstantController>(this),
|
| - content::NotificationService::NoDetails());
|
| + if (!GetPreviewContents()) {
|
| + loader_.reset(new InstantLoader(this, instant_url, active_tab));
|
| + loader_->Init();
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED);
|
| }
|
| }
|
|
|
| -void InstantController::UpdateLoader(TabContents* tab_contents,
|
| - const TemplateURL* template_url,
|
| - const GURL& url,
|
| - content::PageTransition transition_type,
|
| - const string16& user_text,
|
| - bool verbatim,
|
| - string16* suggested_text) {
|
| - if (mode_ == INSTANT)
|
| - loader_->SetOmniboxBounds(omnibox_bounds_);
|
| - loader_->Update(tab_contents, template_url, url, transition_type, user_text,
|
| - verbatim, suggested_text);
|
| - UpdateIsDisplayable();
|
| - // For the HIDDEN and SILENT modes, don't send back suggestions.
|
| - if (mode_ == HIDDEN || mode_ == SILENT)
|
| - suggested_text->clear();
|
| +void InstantController::DeleteLoader() {
|
| + Hide();
|
| + last_full_text_.clear();
|
| + last_user_text_.clear();
|
| + last_verbatim_ = false;
|
| + last_suggestion_.clear();
|
| + last_complete_behavior_ = INSTANT_COMPLETE_NOW;
|
| + last_transition_type_ = content::PAGE_TRANSITION_LINK;
|
| + last_omnibox_bounds_ = gfx::Rect();
|
| + url_for_history_ = GURL();
|
| + if (GetPreviewContents())
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
|
| + loader_.reset();
|
| }
|
|
|
| -// Returns true if |template_url| is a valid TemplateURL for use by instant.
|
| -bool InstantController::IsValidInstantTemplateURL(
|
| - const TemplateURL* template_url) {
|
| - return template_url && template_url->id() &&
|
| - template_url->instant_url_ref().SupportsReplacement() &&
|
| - !IsBlacklistedFromInstant(template_url->id());
|
| +void InstantController::Show() {
|
| + if (!is_showing_) {
|
| + is_showing_ = true;
|
| + delegate_->ShowInstant();
|
| + AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED);
|
| + }
|
| }
|
|
|
| -void InstantController::BlacklistFromInstant() {
|
| - if (!loader_.get())
|
| +void InstantController::SendBoundsToPage() {
|
| + if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() ||
|
| + !GetPreviewContents() || loader_->IsPointerDownFromActivate()) {
|
| return;
|
| + }
|
|
|
| - DCHECK(loader_->template_url_id());
|
| - blacklisted_ids_.insert(loader_->template_url_id());
|
| + last_omnibox_bounds_ = omnibox_bounds_;
|
| + gfx::Rect preview_bounds = delegate_->GetInstantBounds();
|
| + gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds);
|
|
|
| - // Because of the state of the stack we can't destroy the loader now.
|
| - ScheduleDestroy(loader_.release());
|
| - UpdateIsDisplayable();
|
| -}
|
| + // Translate into window coordinates.
|
| + if (!intersection.IsEmpty()) {
|
| + intersection.Offset(-preview_bounds.origin().x(),
|
| + -preview_bounds.origin().y());
|
| + }
|
|
|
| -bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) {
|
| - return blacklisted_ids_.count(id) > 0;
|
| -}
|
| + // 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 preview (for example, in
|
| + // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds).
|
| + DCHECK_LE(0, intersection.x());
|
| + DCHECK_LE(0, intersection.width());
|
| + DCHECK_LE(0, intersection.height());
|
|
|
| -void InstantController::ClearBlacklist() {
|
| - blacklisted_ids_.clear();
|
| + loader_->SetOmniboxBounds(intersection);
|
| }
|
|
|
| -void InstantController::ScheduleDestroy(InstantLoader* loader) {
|
| - loaders_to_destroy_.push_back(loader);
|
| - if (!weak_factory_.HasWeakPtrs()) {
|
| - MessageLoop::current()->PostTask(
|
| - FROM_HERE, base::Bind(&InstantController::DestroyLoaders,
|
| - weak_factory_.GetWeakPtr()));
|
| +bool InstantController::GetInstantURL(const TemplateURL* template_url,
|
| + std::string* instant_url) const {
|
| + CommandLine* command_line = CommandLine::ForCurrentProcess();
|
| + if (command_line->HasSwitch(switches::kInstantURL)) {
|
| + *instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL);
|
| + return true;
|
| + }
|
| +
|
| + if (!template_url)
|
| + return false;
|
| +
|
| + const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
|
| + if (!instant_url_ref.IsValid() || !instant_url_ref.SupportsReplacement())
|
| + return false;
|
| +
|
| + *instant_url = instant_url_ref.ReplaceSearchTerms(
|
| + TemplateURLRef::SearchTermsArgs(string16()));
|
| +
|
| + std::map<std::string, int>::const_iterator iter =
|
| + blacklisted_urls_.find(*instant_url);
|
| + if (iter != blacklisted_urls_.end() &&
|
| + iter->second > kMaxInstantSupportFailures) {
|
| + instant_url->clear();
|
| + return false;
|
| }
|
| +
|
| + return true;
|
| }
|
|
|
| -void InstantController::DestroyLoaders() {
|
| - loaders_to_destroy_.clear();
|
| +bool InstantController::IsOutOfDate() const {
|
| + return !last_active_tab_ ||
|
| + last_active_tab_ != delegate_->GetActiveTabContents();
|
| }
|
|
|