Home | History | Annotate | Download | only in autocomplete
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/autocomplete/autocomplete_edit.h"
      6 
      7 #include <string>
      8 
      9 #include "base/basictypes.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/string_util.h"
     12 #include "base/utf_string_conversions.h"
     13 #include "chrome/app/chrome_command_ids.h"
     14 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
     15 #include "chrome/browser/autocomplete/autocomplete_edit_view.h"
     16 #include "chrome/browser/autocomplete/autocomplete_match.h"
     17 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
     18 #include "chrome/browser/autocomplete/autocomplete_popup_view.h"
     19 #include "chrome/browser/autocomplete/keyword_provider.h"
     20 #include "chrome/browser/autocomplete/search_provider.h"
     21 #include "chrome/browser/command_updater.h"
     22 #include "chrome/browser/extensions/extension_omnibox_api.h"
     23 #include "chrome/browser/google/google_url_tracker.h"
     24 #include "chrome/browser/instant/instant_controller.h"
     25 #include "chrome/browser/metrics/user_metrics.h"
     26 #include "chrome/browser/net/predictor_api.h"
     27 #include "chrome/browser/net/url_fixer_upper.h"
     28 #include "chrome/browser/profiles/profile.h"
     29 #include "chrome/browser/search_engines/template_url.h"
     30 #include "chrome/browser/search_engines/template_url_model.h"
     31 #include "chrome/browser/ui/browser_list.h"
     32 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
     33 #include "chrome/common/url_constants.h"
     34 #include "content/common/notification_service.h"
     35 #include "googleurl/src/gurl.h"
     36 #include "googleurl/src/url_util.h"
     37 #include "third_party/skia/include/core/SkBitmap.h"
     38 
     39 ///////////////////////////////////////////////////////////////////////////////
     40 // AutocompleteEditController
     41 
     42 AutocompleteEditController::~AutocompleteEditController() {
     43 }
     44 
     45 ///////////////////////////////////////////////////////////////////////////////
     46 // AutocompleteEditModel::State
     47 
     48 AutocompleteEditModel::State::State(bool user_input_in_progress,
     49                                     const string16& user_text,
     50                                     const string16& keyword,
     51                                     bool is_keyword_hint)
     52     : user_input_in_progress(user_input_in_progress),
     53       user_text(user_text),
     54       keyword(keyword),
     55       is_keyword_hint(is_keyword_hint) {
     56 }
     57 
     58 AutocompleteEditModel::State::~State() {
     59 }
     60 
     61 ///////////////////////////////////////////////////////////////////////////////
     62 // AutocompleteEditModel
     63 
     64 AutocompleteEditModel::AutocompleteEditModel(
     65     AutocompleteEditView* view,
     66     AutocompleteEditController* controller,
     67     Profile* profile)
     68     : ALLOW_THIS_IN_INITIALIZER_LIST(
     69         autocomplete_controller_(new AutocompleteController(profile, this))),
     70       view_(view),
     71       popup_(NULL),
     72       controller_(controller),
     73       has_focus_(false),
     74       user_input_in_progress_(false),
     75       just_deleted_text_(false),
     76       has_temporary_text_(false),
     77       paste_state_(NONE),
     78       control_key_state_(UP),
     79       is_keyword_hint_(false),
     80       paste_and_go_transition_(PageTransition::TYPED),
     81       profile_(profile),
     82       update_instant_(true),
     83       allow_exact_keyword_match_(false),
     84       instant_complete_behavior_(INSTANT_COMPLETE_DELAYED) {
     85 }
     86 
     87 AutocompleteEditModel::~AutocompleteEditModel() {
     88 }
     89 
     90 void AutocompleteEditModel::SetProfile(Profile* profile) {
     91   DCHECK(profile);
     92   profile_ = profile;
     93   autocomplete_controller_->SetProfile(profile);
     94   popup_->set_profile(profile);
     95 }
     96 
     97 const AutocompleteEditModel::State
     98     AutocompleteEditModel::GetStateForTabSwitch() {
     99   // Like typing, switching tabs "accepts" the temporary text as the user
    100   // text, because it makes little sense to have temporary text when the
    101   // popup is closed.
    102   if (user_input_in_progress_) {
    103     // Weird edge case to match other browsers: if the edit is empty, revert to
    104     // the permanent text (so the user can get it back easily) but select it (so
    105     // on switching back, typing will "just work").
    106     const string16 user_text(UserTextFromDisplayText(view_->GetText()));
    107     if (user_text.empty()) {
    108       view_->RevertAll();
    109       view_->SelectAll(true);
    110     } else {
    111       InternalSetUserText(user_text);
    112     }
    113   }
    114 
    115   return State(user_input_in_progress_, user_text_, keyword_, is_keyword_hint_);
    116 }
    117 
    118 void AutocompleteEditModel::RestoreState(const State& state) {
    119   // Restore any user editing.
    120   if (state.user_input_in_progress) {
    121     // NOTE: Be sure and set keyword-related state BEFORE invoking
    122     // DisplayTextFromUserText(), as its result depends upon this state.
    123     keyword_ = state.keyword;
    124     is_keyword_hint_ = state.is_keyword_hint;
    125     view_->SetUserText(state.user_text,
    126         DisplayTextFromUserText(state.user_text), false);
    127   }
    128 }
    129 
    130 AutocompleteMatch AutocompleteEditModel::CurrentMatch() {
    131   AutocompleteMatch match;
    132   GetInfoForCurrentText(&match, NULL);
    133   return match;
    134 }
    135 
    136 bool AutocompleteEditModel::UpdatePermanentText(
    137     const string16& new_permanent_text) {
    138   // When there's a new URL, and the user is not editing anything or the edit
    139   // doesn't have focus, we want to revert the edit to show the new URL.  (The
    140   // common case where the edit doesn't have focus is when the user has started
    141   // an edit and then abandoned it and clicked a link on the page.)
    142   const bool visibly_changed_permanent_text =
    143       (permanent_text_ != new_permanent_text) &&
    144       (!user_input_in_progress_ || !has_focus_);
    145 
    146   permanent_text_ = new_permanent_text;
    147   return visibly_changed_permanent_text;
    148 }
    149 
    150 GURL AutocompleteEditModel::PermanentURL() {
    151   return URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_), std::string());
    152 }
    153 
    154 void AutocompleteEditModel::SetUserText(const string16& text) {
    155   SetInputInProgress(true);
    156   InternalSetUserText(text);
    157   paste_state_ = NONE;
    158   has_temporary_text_ = false;
    159 }
    160 
    161 void AutocompleteEditModel::FinalizeInstantQuery(
    162     const string16& input_text,
    163     const string16& suggest_text,
    164     bool skip_inline_autocomplete) {
    165   if (skip_inline_autocomplete) {
    166     const string16 final_text = input_text + suggest_text;
    167     view_->OnBeforePossibleChange();
    168     view_->SetWindowTextAndCaretPos(final_text, final_text.length());
    169     view_->OnAfterPossibleChange();
    170   } else if (popup_->IsOpen()) {
    171     SearchProvider* search_provider =
    172         autocomplete_controller_->search_provider();
    173     search_provider->FinalizeInstantQuery(input_text, suggest_text);
    174   }
    175 }
    176 
    177 void AutocompleteEditModel::SetSuggestedText(
    178     const string16& text,
    179     InstantCompleteBehavior behavior) {
    180   instant_complete_behavior_ = behavior;
    181   if (instant_complete_behavior_ == INSTANT_COMPLETE_NOW) {
    182     if (!text.empty())
    183       FinalizeInstantQuery(view_->GetText(), text, false);
    184     else
    185       view_->SetInstantSuggestion(text, false);
    186   } else {
    187     DCHECK((behavior == INSTANT_COMPLETE_DELAYED) ||
    188            (behavior == INSTANT_COMPLETE_NEVER));
    189     view_->SetInstantSuggestion(text, behavior == INSTANT_COMPLETE_DELAYED);
    190   }
    191 }
    192 
    193 bool AutocompleteEditModel::CommitSuggestedText(bool skip_inline_autocomplete) {
    194   if (!controller_->GetInstant())
    195     return false;
    196 
    197   const string16 suggestion = view_->GetInstantSuggestion();
    198   if (suggestion.empty())
    199     return false;
    200 
    201   FinalizeInstantQuery(view_->GetText(), suggestion, skip_inline_autocomplete);
    202   return true;
    203 }
    204 
    205 bool AutocompleteEditModel::AcceptCurrentInstantPreview() {
    206   return InstantController::CommitIfCurrent(controller_->GetInstant());
    207 }
    208 
    209 void AutocompleteEditModel::OnChanged() {
    210   InstantController* instant = controller_->GetInstant();
    211   string16 suggested_text;
    212   TabContentsWrapper* tab = controller_->GetTabContentsWrapper();
    213   bool might_support_instant = false;
    214   if (update_instant_ && instant && tab) {
    215     if (user_input_in_progress() && popup_->IsOpen()) {
    216       AutocompleteMatch current_match = CurrentMatch();
    217       if (current_match.destination_url == PermanentURL()) {
    218         // The destination is the same as the current url. This typically
    219         // happens if the user presses the down error in the omnibox, in which
    220         // case we don't want to load a preview.
    221         instant->DestroyPreviewContentsAndLeaveActive();
    222       } else {
    223         instant->Update(tab, CurrentMatch(), view_->GetText(),
    224                         UseVerbatimInstant(), &suggested_text);
    225       }
    226     } else {
    227       instant->DestroyPreviewContents();
    228     }
    229     might_support_instant = instant->MightSupportInstant();
    230   }
    231 
    232   if (!might_support_instant) {
    233     // Hide any suggestions we might be showing.
    234     view_->SetInstantSuggestion(string16(), false);
    235 
    236     // No need to wait any longer for instant.
    237     FinalizeInstantQuery(string16(), string16(), false);
    238   } else {
    239     SetSuggestedText(suggested_text, instant_complete_behavior_);
    240   }
    241 
    242   controller_->OnChanged();
    243 }
    244 
    245 void AutocompleteEditModel::GetDataForURLExport(GURL* url,
    246                                                 string16* title,
    247                                                 SkBitmap* favicon) {
    248   AutocompleteMatch match;
    249   GetInfoForCurrentText(&match, NULL);
    250   *url = match.destination_url;
    251   if (*url == URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_),
    252                                       std::string())) {
    253     *title = controller_->GetTitle();
    254     *favicon = controller_->GetFavicon();
    255   }
    256 }
    257 
    258 bool AutocompleteEditModel::UseVerbatimInstant() {
    259 #if defined(OS_MACOSX)
    260   // TODO(suzhe): Fix Mac port to display Instant suggest in a separated NSView,
    261   // so that we can display instant suggest along with composition text.
    262   const AutocompleteInput& input = autocomplete_controller_->input();
    263   if (input.initial_prevent_inline_autocomplete())
    264     return true;
    265 #endif
    266 
    267   // The value of input.initial_prevent_inline_autocomplete() is determined by
    268   // following conditions:
    269   // 1. If the caret is at the end of the text (checked below).
    270   // 2. If it's in IME composition mode.
    271   // As we use a separated widget for displaying the instant suggest, it won't
    272   // interfere with IME composition, so we don't need to care about the value of
    273   // input.initial_prevent_inline_autocomplete() here.
    274   if (view_->DeleteAtEndPressed() || (popup_->selected_line() != 0) ||
    275       just_deleted_text_)
    276     return true;
    277 
    278   string16::size_type start, end;
    279   view_->GetSelectionBounds(&start, &end);
    280   return (start != end) || (start != view_->GetText().size());
    281 }
    282 
    283 string16 AutocompleteEditModel::GetDesiredTLD() const {
    284   // Tricky corner case: The user has typed "foo" and currently sees an inline
    285   // autocomplete suggestion of "foo.net".  He now presses ctrl-a (e.g. to
    286   // select all, on Windows).  If we treat the ctrl press as potentially for the
    287   // sake of ctrl-enter, then we risk "www.foo.com" being promoted as the best
    288   // match.  This would make the autocompleted text disappear, leaving our user
    289   // feeling very confused when the wrong text gets highlighted.
    290   //
    291   // Thus, we only treat the user as pressing ctrl-enter when the user presses
    292   // ctrl without any fragile state built up in the omnibox:
    293   // * the contents of the omnibox have not changed since the keypress,
    294   // * there is no autocompleted text visible, and
    295   // * the user is not typing a keyword query.
    296   return (control_key_state_ == DOWN_WITHOUT_CHANGE &&
    297           inline_autocomplete_text_.empty() && !KeywordIsSelected())?
    298     ASCIIToUTF16("com") : string16();
    299 }
    300 
    301 bool AutocompleteEditModel::CurrentTextIsURL() const {
    302   // If !user_input_in_progress_, the permanent text is showing, which should
    303   // always be a URL, so no further checking is needed.  By avoiding checking in
    304   // this case, we avoid calling into the autocomplete providers, and thus
    305   // initializing the history system, as long as possible, which speeds startup.
    306   if (!user_input_in_progress_)
    307     return true;
    308 
    309   AutocompleteMatch match;
    310   GetInfoForCurrentText(&match, NULL);
    311   return match.transition == PageTransition::TYPED;
    312 }
    313 
    314 AutocompleteMatch::Type AutocompleteEditModel::CurrentTextType() const {
    315   AutocompleteMatch match;
    316   GetInfoForCurrentText(&match, NULL);
    317   return match.type;
    318 }
    319 
    320 void AutocompleteEditModel::AdjustTextForCopy(int sel_min,
    321                                               bool is_all_selected,
    322                                               string16* text,
    323                                               GURL* url,
    324                                               bool* write_url) {
    325   *write_url = false;
    326 
    327   if (sel_min != 0)
    328     return;
    329 
    330   // We can't use CurrentTextIsURL() or GetDataForURLExport() because right now
    331   // the user is probably holding down control to cause the copy, which will
    332   // screw up our calculation of the desired_tld.
    333   if (!GetURLForText(*text, url))
    334     return;  // Can't be parsed as a url, no need to adjust text.
    335 
    336   if (!user_input_in_progress() && is_all_selected) {
    337     // The user selected all the text and has not edited it. Use the url as the
    338     // text so that if the scheme was stripped it's added back, and the url
    339     // is unescaped (we escape parts of the url for display).
    340     *text = UTF8ToUTF16(url->spec());
    341     *write_url = true;
    342     return;
    343   }
    344 
    345   // Prefix the text with 'http://' if the text doesn't start with 'http://',
    346   // the text parses as a url with a scheme of http, the user selected the
    347   // entire host, and the user hasn't edited the host or manually removed the
    348   // scheme.
    349   GURL perm_url;
    350   if (GetURLForText(permanent_text_, &perm_url) &&
    351       perm_url.SchemeIs(chrome::kHttpScheme) &&
    352       url->SchemeIs(chrome::kHttpScheme) &&
    353       perm_url.host() == url->host()) {
    354     *write_url = true;
    355 
    356     string16 http = ASCIIToUTF16(chrome::kHttpScheme) +
    357         ASCIIToUTF16(chrome::kStandardSchemeSeparator);
    358     if (text->compare(0, http.length(), http) != 0)
    359       *text = http + *text;
    360   }
    361 }
    362 
    363 void AutocompleteEditModel::SetInputInProgress(bool in_progress) {
    364   if (user_input_in_progress_ == in_progress)
    365     return;
    366 
    367   user_input_in_progress_ = in_progress;
    368   controller_->OnInputInProgress(in_progress);
    369 }
    370 
    371 void AutocompleteEditModel::Revert() {
    372   SetInputInProgress(false);
    373   paste_state_ = NONE;
    374   InternalSetUserText(string16());
    375   keyword_.clear();
    376   is_keyword_hint_ = false;
    377   has_temporary_text_ = false;
    378   view_->SetWindowTextAndCaretPos(permanent_text_,
    379                                   has_focus_ ? permanent_text_.length() : 0);
    380 }
    381 
    382 void AutocompleteEditModel::StartAutocomplete(
    383     bool has_selected_text,
    384     bool prevent_inline_autocomplete) const {
    385   bool keyword_is_selected = KeywordIsSelected();
    386   popup_->SetHoveredLine(AutocompletePopupModel::kNoMatch);
    387   // We don't explicitly clear AutocompletePopupModel::manually_selected_match,
    388   // as Start ends up invoking AutocompletePopupModel::OnResultChanged which
    389   // clears it.
    390   autocomplete_controller_->Start(
    391       user_text_, GetDesiredTLD(),
    392       prevent_inline_autocomplete || just_deleted_text_ ||
    393       (has_selected_text && inline_autocomplete_text_.empty()) ||
    394       (paste_state_ != NONE), keyword_is_selected,
    395       keyword_is_selected || allow_exact_keyword_match_,
    396       AutocompleteInput::ALL_MATCHES);
    397 }
    398 
    399 void AutocompleteEditModel::StopAutocomplete() {
    400   if (popup_->IsOpen() && update_instant_) {
    401     InstantController* instant = controller_->GetInstant();
    402     if (instant && !instant->commit_on_mouse_up())
    403       instant->DestroyPreviewContents();
    404   }
    405 
    406   autocomplete_controller_->Stop(true);
    407 }
    408 
    409 bool AutocompleteEditModel::CanPasteAndGo(const string16& text) const {
    410   if (!view_->GetCommandUpdater()->IsCommandEnabled(IDC_OPEN_CURRENT_URL))
    411     return false;
    412 
    413   AutocompleteMatch match;
    414   profile_->GetAutocompleteClassifier()->Classify(text, string16(), false,
    415       &match, &paste_and_go_alternate_nav_url_);
    416   paste_and_go_url_ = match.destination_url;
    417   paste_and_go_transition_ = match.transition;
    418   return paste_and_go_url_.is_valid();
    419 }
    420 
    421 void AutocompleteEditModel::PasteAndGo() {
    422   // The final parameter to OpenURL, keyword, is not quite correct here: it's
    423   // possible to "paste and go" a string that contains a keyword.  This is
    424   // enough of an edge case that we ignore this possibility.
    425   view_->RevertAll();
    426   view_->OpenURL(paste_and_go_url_, CURRENT_TAB, paste_and_go_transition_,
    427       paste_and_go_alternate_nav_url_, AutocompletePopupModel::kNoMatch,
    428       string16());
    429 }
    430 
    431 void AutocompleteEditModel::AcceptInput(WindowOpenDisposition disposition,
    432                                         bool for_drop) {
    433   // Get the URL and transition type for the selected entry.
    434   AutocompleteMatch match;
    435   GURL alternate_nav_url;
    436   GetInfoForCurrentText(&match, &alternate_nav_url);
    437 
    438   if (!match.destination_url.is_valid())
    439     return;
    440 
    441   if ((match.transition == PageTransition::TYPED) && (match.destination_url ==
    442       URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_), std::string()))) {
    443     // When the user hit enter on the existing permanent URL, treat it like a
    444     // reload for scoring purposes.  We could detect this by just checking
    445     // user_input_in_progress_, but it seems better to treat "edits" that end
    446     // up leaving the URL unchanged (e.g. deleting the last character and then
    447     // retyping it) as reloads too.  We exclude non-TYPED transitions because if
    448     // the transition is GENERATED, the user input something that looked
    449     // different from the current URL, even if it wound up at the same place
    450     // (e.g. manually retyping the same search query), and it seems wrong to
    451     // treat this as a reload.
    452     match.transition = PageTransition::RELOAD;
    453   } else if (for_drop || ((paste_state_ != NONE) &&
    454                           match.is_history_what_you_typed_match)) {
    455     // When the user pasted in a URL and hit enter, score it like a link click
    456     // rather than a normal typed URL, so it doesn't get inline autocompleted
    457     // as aggressively later.
    458     match.transition = PageTransition::LINK;
    459   }
    460 
    461   if (match.type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
    462       match.type == AutocompleteMatch::SEARCH_HISTORY ||
    463       match.type == AutocompleteMatch::SEARCH_SUGGEST) {
    464     const TemplateURL* default_provider =
    465         profile_->GetTemplateURLModel()->GetDefaultSearchProvider();
    466     if (default_provider && default_provider->url() &&
    467         default_provider->url()->HasGoogleBaseURLs()) {
    468       GoogleURLTracker::GoogleURLSearchCommitted();
    469 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
    470       // TODO(pastarmovj): Remove these metrics once we have proven that (close
    471       // to) none searches that should have RLZ are sent out without one.
    472       default_provider->url()->CollectRLZMetrics();
    473 #endif
    474     }
    475   }
    476   view_->OpenURL(match.destination_url, disposition, match.transition,
    477                  alternate_nav_url, AutocompletePopupModel::kNoMatch,
    478                  is_keyword_hint_ ? string16() : keyword_);
    479 }
    480 
    481 void AutocompleteEditModel::OpenURL(const GURL& url,
    482                                     WindowOpenDisposition disposition,
    483                                     PageTransition::Type transition,
    484                                     const GURL& alternate_nav_url,
    485                                     size_t index,
    486                                     const string16& keyword) {
    487   // We only care about cases where there is a selection (i.e. the popup is
    488   // open).
    489   if (popup_->IsOpen()) {
    490     AutocompleteLog log(autocomplete_controller_->input().text(),
    491                         autocomplete_controller_->input().type(),
    492                         popup_->selected_line(), 0, result());
    493     if (index != AutocompletePopupModel::kNoMatch)
    494       log.selected_index = index;
    495     else if (!has_temporary_text_)
    496       log.inline_autocompleted_length = inline_autocomplete_text_.length();
    497     NotificationService::current()->Notify(
    498         NotificationType::OMNIBOX_OPENED_URL, Source<Profile>(profile_),
    499         Details<AutocompleteLog>(&log));
    500   }
    501 
    502   TemplateURLModel* template_url_model = profile_->GetTemplateURLModel();
    503   if (template_url_model && !keyword.empty()) {
    504     const TemplateURL* const template_url =
    505         template_url_model->GetTemplateURLForKeyword(keyword);
    506 
    507     // Special case for extension keywords. Don't increment usage count for
    508     // these.
    509     if (template_url && template_url->IsExtensionKeyword()) {
    510       AutocompleteMatch current_match;
    511       GetInfoForCurrentText(&current_match, NULL);
    512 
    513       const AutocompleteMatch& match =
    514           index == AutocompletePopupModel::kNoMatch ?
    515               current_match : result().match_at(index);
    516 
    517       // Strip the keyword + leading space off the input.
    518       size_t prefix_length = match.template_url->keyword().size() + 1;
    519       ExtensionOmniboxEventRouter::OnInputEntered(
    520           profile_, match.template_url->GetExtensionId(),
    521           UTF16ToUTF8(match.fill_into_edit.substr(prefix_length)));
    522       view_->RevertAll();
    523       return;
    524     }
    525 
    526     if (template_url) {
    527       UserMetrics::RecordAction(UserMetricsAction("AcceptedKeyword"), profile_);
    528       template_url_model->IncrementUsageCount(template_url);
    529     }
    530 
    531     // NOTE: We purposefully don't increment the usage count of the default
    532     // search engine, if applicable; see comments in template_url.h.
    533   }
    534 
    535   if (disposition != NEW_BACKGROUND_TAB) {
    536     update_instant_ = false;
    537     view_->RevertAll();  // Revert the box to its unedited state
    538   }
    539   controller_->OnAutocompleteAccept(url, disposition, transition,
    540                                     alternate_nav_url);
    541 
    542   InstantController* instant = controller_->GetInstant();
    543   if (instant && !popup_->IsOpen())
    544     instant->DestroyPreviewContents();
    545   update_instant_ = true;
    546 }
    547 
    548 bool AutocompleteEditModel::AcceptKeyword() {
    549   DCHECK(is_keyword_hint_ && !keyword_.empty());
    550 
    551   view_->OnBeforePossibleChange();
    552   view_->SetWindowTextAndCaretPos(string16(), 0);
    553   is_keyword_hint_ = false;
    554   view_->OnAfterPossibleChange();
    555   just_deleted_text_ = false;  // OnAfterPossibleChange() erroneously sets this
    556                                // since the edit contents have disappeared.  It
    557                                // doesn't really matter, but we clear it to be
    558                                // consistent.
    559   UserMetrics::RecordAction(UserMetricsAction("AcceptedKeywordHint"), profile_);
    560   return true;
    561 }
    562 
    563 void AutocompleteEditModel::ClearKeyword(const string16& visible_text) {
    564   view_->OnBeforePossibleChange();
    565   const string16 window_text(keyword_ + visible_text);
    566   view_->SetWindowTextAndCaretPos(window_text.c_str(), keyword_.length());
    567   keyword_.clear();
    568   is_keyword_hint_ = false;
    569   view_->OnAfterPossibleChange();
    570   just_deleted_text_ = true;  // OnAfterPossibleChange() fails to clear this
    571                               // since the edit contents have actually grown
    572                               // longer.
    573 }
    574 
    575 const AutocompleteResult& AutocompleteEditModel::result() const {
    576   return autocomplete_controller_->result();
    577 }
    578 
    579 void AutocompleteEditModel::OnSetFocus(bool control_down) {
    580   has_focus_ = true;
    581   control_key_state_ = control_down ? DOWN_WITHOUT_CHANGE : UP;
    582   NotificationService::current()->Notify(
    583       NotificationType::AUTOCOMPLETE_EDIT_FOCUSED,
    584       Source<AutocompleteEditModel>(this),
    585       NotificationService::NoDetails());
    586 }
    587 
    588 void AutocompleteEditModel::OnWillKillFocus(
    589     gfx::NativeView view_gaining_focus) {
    590   SetSuggestedText(string16(), INSTANT_COMPLETE_NOW);
    591 
    592   InstantController* instant = controller_->GetInstant();
    593   if (instant)
    594     instant->OnAutocompleteLostFocus(view_gaining_focus);
    595 }
    596 
    597 void AutocompleteEditModel::OnKillFocus() {
    598   has_focus_ = false;
    599   control_key_state_ = UP;
    600   paste_state_ = NONE;
    601 }
    602 
    603 bool AutocompleteEditModel::OnEscapeKeyPressed() {
    604   if (has_temporary_text_) {
    605     AutocompleteMatch match;
    606     InfoForCurrentSelection(&match, NULL);
    607     if (match.destination_url != original_url_) {
    608       RevertTemporaryText(true);
    609       return true;
    610     }
    611   }
    612 
    613   // If the user wasn't editing, but merely had focus in the edit, allow <esc>
    614   // to be processed as an accelerator, so it can still be used to stop a load.
    615   // When the permanent text isn't all selected we still fall through to the
    616   // SelectAll() call below so users can arrow around in the text and then hit
    617   // <esc> to quickly replace all the text; this matches IE.
    618   if (!user_input_in_progress_ && view_->IsSelectAll())
    619     return false;
    620 
    621   view_->RevertAll();
    622   view_->SelectAll(true);
    623   return true;
    624 }
    625 
    626 void AutocompleteEditModel::OnControlKeyChanged(bool pressed) {
    627   // Don't change anything unless the key state is actually toggling.
    628   if (pressed == (control_key_state_ == UP)) {
    629     ControlKeyState old_state = control_key_state_;
    630     control_key_state_ = pressed ? DOWN_WITHOUT_CHANGE : UP;
    631     if ((control_key_state_ == DOWN_WITHOUT_CHANGE) && has_temporary_text_) {
    632       // Arrowing down and then hitting control accepts the temporary text as
    633       // the input text.
    634       InternalSetUserText(UserTextFromDisplayText(view_->GetText()));
    635       has_temporary_text_ = false;
    636       if (KeywordIsSelected())
    637         AcceptKeyword();
    638     }
    639     if ((old_state != DOWN_WITH_CHANGE) && popup_->IsOpen()) {
    640       // Autocomplete history provider results may change, so refresh the
    641       // popup.  This will force user_input_in_progress_ to true, but if the
    642       // popup is open, that should have already been the case.
    643       view_->UpdatePopup();
    644     }
    645   }
    646 }
    647 
    648 void AutocompleteEditModel::OnUpOrDownKeyPressed(int count) {
    649   // NOTE: This purposefully don't trigger any code that resets paste_state_.
    650 
    651   if (!popup_->IsOpen()) {
    652     if (!query_in_progress()) {
    653       // The popup is neither open nor working on a query already.  So, start an
    654       // autocomplete query for the current text.  This also sets
    655       // user_input_in_progress_ to true, which we want: if the user has started
    656       // to interact with the popup, changing the permanent_text_ shouldn't
    657       // change the displayed text.
    658       // Note: This does not force the popup to open immediately.
    659       // TODO(pkasting): We should, in fact, force this particular query to open
    660       // the popup immediately.
    661       if (!user_input_in_progress_)
    662         InternalSetUserText(permanent_text_);
    663       view_->UpdatePopup();
    664     } else {
    665       // TODO(pkasting): The popup is working on a query but is not open.  We
    666       // should force it to open immediately.
    667     }
    668   } else {
    669     // The popup is open, so the user should be able to interact with it
    670     // normally.
    671     popup_->Move(count);
    672   }
    673 }
    674 
    675 void AutocompleteEditModel::OnPopupDataChanged(
    676     const string16& text,
    677     GURL* destination_for_temporary_text_change,
    678     const string16& keyword,
    679     bool is_keyword_hint) {
    680   // Update keyword/hint-related local state.
    681   bool keyword_state_changed = (keyword_ != keyword) ||
    682       ((is_keyword_hint_ != is_keyword_hint) && !keyword.empty());
    683   if (keyword_state_changed) {
    684     keyword_ = keyword;
    685     is_keyword_hint_ = is_keyword_hint;
    686 
    687     // |is_keyword_hint_| should always be false if |keyword_| is empty.
    688     DCHECK(!keyword_.empty() || !is_keyword_hint_);
    689   }
    690 
    691   // Handle changes to temporary text.
    692   if (destination_for_temporary_text_change != NULL) {
    693     const bool save_original_selection = !has_temporary_text_;
    694     if (save_original_selection) {
    695       // Save the original selection and URL so it can be reverted later.
    696       has_temporary_text_ = true;
    697       original_url_ = *destination_for_temporary_text_change;
    698       inline_autocomplete_text_.clear();
    699     }
    700     if (control_key_state_ == DOWN_WITHOUT_CHANGE) {
    701       // Arrowing around the popup cancels control-enter.
    702       control_key_state_ = DOWN_WITH_CHANGE;
    703       // Now things are a bit screwy: the desired_tld has changed, but if we
    704       // update the popup, the new order of entries won't match the old, so the
    705       // user's selection gets screwy; and if we don't update the popup, and the
    706       // user reverts, then the selected item will be as if control is still
    707       // pressed, even though maybe it isn't any more.  There is no obvious
    708       // right answer here :(
    709     }
    710     view_->OnTemporaryTextMaybeChanged(DisplayTextFromUserText(text),
    711                                        save_original_selection);
    712     return;
    713   }
    714 
    715   bool call_controller_onchanged = true;
    716   inline_autocomplete_text_ = text;
    717   if (view_->OnInlineAutocompleteTextMaybeChanged(
    718       DisplayTextFromUserText(user_text_ + inline_autocomplete_text_),
    719       DisplayTextFromUserText(user_text_).length()))
    720     call_controller_onchanged = false;
    721 
    722   // If |has_temporary_text_| is true, then we previously had a manual selection
    723   // but now don't (or |destination_for_temporary_text_change| would have been
    724   // non-NULL). This can happen when deleting the selected item in the popup.
    725   // In this case, we've already reverted the popup to the default match, so we
    726   // need to revert ourselves as well.
    727   if (has_temporary_text_) {
    728     RevertTemporaryText(false);
    729     call_controller_onchanged = false;
    730   }
    731 
    732   // We need to invoke OnChanged in case the destination url changed (as could
    733   // happen when control is toggled).
    734   if (call_controller_onchanged)
    735     OnChanged();
    736 }
    737 
    738 bool AutocompleteEditModel::OnAfterPossibleChange(
    739     const string16& new_text,
    740     size_t selection_start,
    741     size_t selection_end,
    742     bool selection_differs,
    743     bool text_differs,
    744     bool just_deleted_text,
    745     bool allow_keyword_ui_change) {
    746   // Update the paste state as appropriate: if we're just finishing a paste
    747   // that replaced all the text, preserve that information; otherwise, if we've
    748   // made some other edit, clear paste tracking.
    749   if (paste_state_ == PASTING)
    750     paste_state_ = PASTED;
    751   else if (text_differs)
    752     paste_state_ = NONE;
    753 
    754   // Modifying the selection counts as accepting the autocompleted text.
    755   const bool user_text_changed =
    756       text_differs || (selection_differs && !inline_autocomplete_text_.empty());
    757 
    758   // If something has changed while the control key is down, prevent
    759   // "ctrl-enter" until the control key is released.  When we do this, we need
    760   // to update the popup if it's open, since the desired_tld will have changed.
    761   if ((text_differs || selection_differs) &&
    762       (control_key_state_ == DOWN_WITHOUT_CHANGE)) {
    763     control_key_state_ = DOWN_WITH_CHANGE;
    764     if (!text_differs && !popup_->IsOpen())
    765       return false;  // Don't open the popup for no reason.
    766   } else if (!user_text_changed) {
    767     return false;
    768   }
    769 
    770   const string16 old_user_text = user_text_;
    771   // If the user text has not changed, we do not want to change the model's
    772   // state associated with the text.  Otherwise, we can get surprising behavior
    773   // where the autocompleted text unexpectedly reappears, e.g. crbug.com/55983
    774   if (user_text_changed) {
    775     InternalSetUserText(UserTextFromDisplayText(new_text));
    776     has_temporary_text_ = false;
    777 
    778     // Track when the user has deleted text so we won't allow inline
    779     // autocomplete.
    780     just_deleted_text_ = just_deleted_text;
    781   }
    782 
    783   const bool no_selection = selection_start == selection_end;
    784 
    785   // Update the popup for the change, in the process changing to keyword mode
    786   // if the user hit space in mid-string after a keyword.
    787   // |allow_exact_keyword_match_| will be used by StartAutocomplete() method,
    788   // which will be called by |view_->UpdatePopup()|. So we can safely clear
    789   // this flag afterwards.
    790   allow_exact_keyword_match_ =
    791       text_differs && allow_keyword_ui_change &&
    792       !just_deleted_text && no_selection &&
    793       ShouldAllowExactKeywordMatch(old_user_text, user_text_, selection_start);
    794   view_->UpdatePopup();
    795   allow_exact_keyword_match_ = false;
    796 
    797   // Change to keyword mode if the user has typed a keyword name and is now
    798   // pressing space after the name. Accepting the keyword will update our
    799   // state, so in that case there's no need to also return true here.
    800   return !(text_differs && allow_keyword_ui_change && !just_deleted_text &&
    801            no_selection && selection_start == user_text_.length() &&
    802            MaybeAcceptKeywordBySpace(old_user_text, user_text_));
    803 }
    804 
    805 void AutocompleteEditModel::PopupBoundsChangedTo(const gfx::Rect& bounds) {
    806   InstantController* instant = controller_->GetInstant();
    807   if (instant)
    808     instant->SetOmniboxBounds(bounds);
    809 }
    810 
    811 // Return true if the suggestion type warrants a TCP/IP preconnection.
    812 // i.e., it is now highly likely that the user will select the related domain.
    813 static bool IsPreconnectable(AutocompleteMatch::Type type) {
    814   UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", type,
    815                             AutocompleteMatch::NUM_TYPES);
    816   switch (type) {
    817     // Matches using the user's default search engine.
    818     case AutocompleteMatch::SEARCH_WHAT_YOU_TYPED:
    819     case AutocompleteMatch::SEARCH_HISTORY:
    820     case AutocompleteMatch::SEARCH_SUGGEST:
    821     // A match that uses a non-default search engine (e.g. for tab-to-search).
    822     case AutocompleteMatch::SEARCH_OTHER_ENGINE:
    823       return true;
    824 
    825     default:
    826       return false;
    827   }
    828 }
    829 
    830 void AutocompleteEditModel::OnResultChanged(bool default_match_changed) {
    831   const bool was_open = popup_->IsOpen();
    832   if (default_match_changed) {
    833     string16 inline_autocomplete_text;
    834     string16 keyword;
    835     bool is_keyword_hint = false;
    836     const AutocompleteResult& result = this->result();
    837     const AutocompleteResult::const_iterator match(result.default_match());
    838     if (match != result.end()) {
    839       if ((match->inline_autocomplete_offset != string16::npos) &&
    840           (match->inline_autocomplete_offset <
    841            match->fill_into_edit.length())) {
    842         inline_autocomplete_text =
    843             match->fill_into_edit.substr(match->inline_autocomplete_offset);
    844       }
    845 
    846       if (!match->destination_url.SchemeIs(chrome::kExtensionScheme)) {
    847         // Warm up DNS Prefetch cache, or preconnect to a search service.
    848         chrome_browser_net::AnticipateOmniboxUrl(match->destination_url,
    849                                                  IsPreconnectable(match->type));
    850       }
    851 
    852       // We could prefetch the alternate nav URL, if any, but because there
    853       // can be many of these as a user types an initial series of characters,
    854       // the OS DNS cache could suffer eviction problems for minimal gain.
    855 
    856       is_keyword_hint = popup_->GetKeywordForMatch(*match, &keyword);
    857     }
    858     popup_->OnResultChanged();
    859     OnPopupDataChanged(inline_autocomplete_text, NULL, keyword,
    860                        is_keyword_hint);
    861   } else {
    862     popup_->OnResultChanged();
    863   }
    864 
    865   if (popup_->IsOpen()) {
    866     PopupBoundsChangedTo(popup_->view()->GetTargetBounds());
    867   } else if (was_open) {
    868     // Accepts the temporary text as the user text, because it makes little
    869     // sense to have temporary text when the popup is closed.
    870     InternalSetUserText(UserTextFromDisplayText(view_->GetText()));
    871     has_temporary_text_ = false;
    872     PopupBoundsChangedTo(gfx::Rect());
    873   }
    874 }
    875 
    876 bool AutocompleteEditModel::query_in_progress() const {
    877   return !autocomplete_controller_->done();
    878 }
    879 
    880 void AutocompleteEditModel::InternalSetUserText(const string16& text) {
    881   user_text_ = text;
    882   just_deleted_text_ = false;
    883   inline_autocomplete_text_.clear();
    884 }
    885 
    886 bool AutocompleteEditModel::KeywordIsSelected() const {
    887   return !is_keyword_hint_ && !keyword_.empty();
    888 }
    889 
    890 string16 AutocompleteEditModel::DisplayTextFromUserText(
    891     const string16& text) const {
    892   return KeywordIsSelected() ?
    893       KeywordProvider::SplitReplacementStringFromInput(text, false) : text;
    894 }
    895 
    896 string16 AutocompleteEditModel::UserTextFromDisplayText(
    897     const string16& text) const {
    898   return KeywordIsSelected() ? (keyword_ + char16(' ') + text) : text;
    899 }
    900 
    901 void AutocompleteEditModel::InfoForCurrentSelection(
    902     AutocompleteMatch* match,
    903     GURL* alternate_nav_url) const {
    904   DCHECK(match != NULL);
    905   const AutocompleteResult& result = this->result();
    906   if (!autocomplete_controller_->done()) {
    907     // It's technically possible for |result| to be empty if no provider returns
    908     // a synchronous result but the query has not completed synchronously;
    909     // pratically, however, that should never actually happen.
    910     if (result.empty())
    911       return;
    912     // The user cannot have manually selected a match, or the query would have
    913     // stopped.  So the default match must be the desired selection.
    914     *match = *result.default_match();
    915   } else {
    916     CHECK(popup_->IsOpen());
    917     // If there are no results, the popup should be closed (so we should have
    918     // failed the CHECK above), and URLsForDefaultMatch() should have been
    919     // called instead.
    920     CHECK(!result.empty());
    921     CHECK(popup_->selected_line() < result.size());
    922     *match = result.match_at(popup_->selected_line());
    923   }
    924   if (alternate_nav_url && popup_->manually_selected_match().empty())
    925     *alternate_nav_url = result.alternate_nav_url();
    926 }
    927 
    928 void AutocompleteEditModel::GetInfoForCurrentText(
    929     AutocompleteMatch* match,
    930     GURL* alternate_nav_url) const {
    931   if (popup_->IsOpen() || query_in_progress()) {
    932     InfoForCurrentSelection(match, alternate_nav_url);
    933   } else {
    934     profile_->GetAutocompleteClassifier()->Classify(
    935         UserTextFromDisplayText(view_->GetText()), GetDesiredTLD(), true,
    936         match, alternate_nav_url);
    937   }
    938 }
    939 
    940 bool AutocompleteEditModel::GetURLForText(const string16& text,
    941                                           GURL* url) const {
    942   GURL parsed_url;
    943   const AutocompleteInput::Type type = AutocompleteInput::Parse(
    944       UserTextFromDisplayText(text), string16(), NULL, NULL, &parsed_url);
    945   if (type != AutocompleteInput::URL)
    946     return false;
    947 
    948   *url = parsed_url;
    949   return true;
    950 }
    951 
    952 void AutocompleteEditModel::RevertTemporaryText(bool revert_popup) {
    953   // The user typed something, then selected a different item.  Restore the
    954   // text they typed and change back to the default item.
    955   // NOTE: This purposefully does not reset paste_state_.
    956   just_deleted_text_ = false;
    957   has_temporary_text_ = false;
    958   if (revert_popup)
    959     popup_->ResetToDefaultMatch();
    960   view_->OnRevertTemporaryText();
    961 }
    962 
    963 bool AutocompleteEditModel::MaybeAcceptKeywordBySpace(
    964     const string16& old_user_text,
    965     const string16& new_user_text) {
    966   return (paste_state_ == NONE) && is_keyword_hint_ && !keyword_.empty() &&
    967       inline_autocomplete_text_.empty() && new_user_text.length() >= 2 &&
    968       IsSpaceCharForAcceptingKeyword(*new_user_text.rbegin()) &&
    969       !IsWhitespace(*(new_user_text.rbegin() + 1)) &&
    970       (old_user_text.length() + 1 >= new_user_text.length()) &&
    971       !new_user_text.compare(0, new_user_text.length() - 1, old_user_text,
    972                              0, new_user_text.length() - 1) &&
    973       AcceptKeyword();
    974 }
    975 
    976 bool AutocompleteEditModel::ShouldAllowExactKeywordMatch(
    977     const string16& old_user_text,
    978     const string16& new_user_text,
    979     size_t caret_position) {
    980   // Check simple conditions first.
    981   if (paste_state_ != NONE || caret_position < 2 ||
    982       new_user_text.length() <= caret_position ||
    983       old_user_text.length() < caret_position ||
    984       !IsSpaceCharForAcceptingKeyword(new_user_text[caret_position - 1]) ||
    985       IsSpaceCharForAcceptingKeyword(new_user_text[caret_position - 2]) ||
    986       new_user_text.compare(0, caret_position - 1, old_user_text,
    987                             0, caret_position - 1) ||
    988       !new_user_text.compare(caret_position - 1,
    989                              new_user_text.length() - caret_position + 1,
    990                              old_user_text, caret_position - 1,
    991                              old_user_text.length() - caret_position + 1)) {
    992     return false;
    993   }
    994 
    995   // Then check if the text before the inserted space matches a keyword.
    996   string16 keyword;
    997   TrimWhitespace(new_user_text.substr(0, caret_position - 1),
    998                  TRIM_LEADING, &keyword);
    999 
   1000   // Only allow exact keyword match if |keyword| represents a keyword hint.
   1001   return keyword.length() && popup_->GetKeywordForText(keyword, &keyword);
   1002 }
   1003 
   1004 //  static
   1005 bool AutocompleteEditModel::IsSpaceCharForAcceptingKeyword(wchar_t c) {
   1006   switch (c) {
   1007     case 0x0020:  // Space
   1008     case 0x3000:  // Ideographic Space
   1009       return true;
   1010     default:
   1011       return false;
   1012   }
   1013 }
   1014