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
42AutocompleteEditController::~AutocompleteEditController() {
43}
44
45///////////////////////////////////////////////////////////////////////////////
46// AutocompleteEditModel::State
47
48AutocompleteEditModel::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
58AutocompleteEditModel::State::~State() {
59}
60
61///////////////////////////////////////////////////////////////////////////////
62// AutocompleteEditModel
63
64AutocompleteEditModel::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
87AutocompleteEditModel::~AutocompleteEditModel() {
88}
89
90void AutocompleteEditModel::SetProfile(Profile* profile) {
91  DCHECK(profile);
92  profile_ = profile;
93  autocomplete_controller_->SetProfile(profile);
94  popup_->set_profile(profile);
95}
96
97const 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
118void 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
130AutocompleteMatch AutocompleteEditModel::CurrentMatch() {
131  AutocompleteMatch match;
132  GetInfoForCurrentText(&match, NULL);
133  return match;
134}
135
136bool 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
150GURL AutocompleteEditModel::PermanentURL() {
151  return URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_), std::string());
152}
153
154void AutocompleteEditModel::SetUserText(const string16& text) {
155  SetInputInProgress(true);
156  InternalSetUserText(text);
157  paste_state_ = NONE;
158  has_temporary_text_ = false;
159}
160
161void 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
177void 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
193bool 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
205bool AutocompleteEditModel::AcceptCurrentInstantPreview() {
206  return InstantController::CommitIfCurrent(controller_->GetInstant());
207}
208
209void 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
245void 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
258bool 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
283string16 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
301bool 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
314AutocompleteMatch::Type AutocompleteEditModel::CurrentTextType() const {
315  AutocompleteMatch match;
316  GetInfoForCurrentText(&match, NULL);
317  return match.type;
318}
319
320void 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
363void 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
371void 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
382void 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
399void 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
409bool 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
421void 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
431void 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
481void 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
548bool 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
563void 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
575const AutocompleteResult& AutocompleteEditModel::result() const {
576  return autocomplete_controller_->result();
577}
578
579void 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
588void 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
597void AutocompleteEditModel::OnKillFocus() {
598  has_focus_ = false;
599  control_key_state_ = UP;
600  paste_state_ = NONE;
601}
602
603bool 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
626void 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
648void 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
675void 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
738bool 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
805void 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.
813static 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
830void 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
876bool AutocompleteEditModel::query_in_progress() const {
877  return !autocomplete_controller_->done();
878}
879
880void AutocompleteEditModel::InternalSetUserText(const string16& text) {
881  user_text_ = text;
882  just_deleted_text_ = false;
883  inline_autocomplete_text_.clear();
884}
885
886bool AutocompleteEditModel::KeywordIsSelected() const {
887  return !is_keyword_hint_ && !keyword_.empty();
888}
889
890string16 AutocompleteEditModel::DisplayTextFromUserText(
891    const string16& text) const {
892  return KeywordIsSelected() ?
893      KeywordProvider::SplitReplacementStringFromInput(text, false) : text;
894}
895
896string16 AutocompleteEditModel::UserTextFromDisplayText(
897    const string16& text) const {
898  return KeywordIsSelected() ? (keyword_ + char16(' ') + text) : text;
899}
900
901void 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
928void 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
940bool 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
952void 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
963bool 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
976bool 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
1005bool 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