omnibox_edit_model.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright 2012 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/ui/omnibox/omnibox_edit_model.h"
6
7#include <string>
8
9#include "base/auto_reset.h"
10#include "base/format_macros.h"
11#include "base/metrics/histogram.h"
12#include "base/prefs/pref_service.h"
13#include "base/string_util.h"
14#include "base/stringprintf.h"
15#include "base/utf_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"
17#include "chrome/browser/autocomplete/autocomplete_classifier.h"
18#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
19#include "chrome/browser/autocomplete/autocomplete_input.h"
20#include "chrome/browser/autocomplete/autocomplete_log.h"
21#include "chrome/browser/autocomplete/autocomplete_provider.h"
22#include "chrome/browser/autocomplete/extension_app_provider.h"
23#include "chrome/browser/autocomplete/history_url_provider.h"
24#include "chrome/browser/autocomplete/keyword_provider.h"
25#include "chrome/browser/autocomplete/search_provider.h"
26#include "chrome/browser/bookmarks/bookmark_utils.h"
27#include "chrome/browser/command_updater.h"
28#include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
29#include "chrome/browser/google/google_url_tracker.h"
30#include "chrome/browser/net/predictor.h"
31#include "chrome/browser/net/url_fixer_upper.h"
32#include "chrome/browser/predictors/autocomplete_action_predictor.h"
33#include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
34#include "chrome/browser/prerender/prerender_field_trial.h"
35#include "chrome/browser/prerender/prerender_manager.h"
36#include "chrome/browser/prerender/prerender_manager_factory.h"
37#include "chrome/browser/profiles/profile.h"
38#include "chrome/browser/search/search.h"
39#include "chrome/browser/search_engines/template_url.h"
40#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
41#include "chrome/browser/search_engines/template_url_service.h"
42#include "chrome/browser/search_engines/template_url_service_factory.h"
43#include "chrome/browser/sessions/session_tab_helper.h"
44#include "chrome/browser/ui/browser_list.h"
45#include "chrome/browser/ui/omnibox/omnibox_current_page_delegate_impl.h"
46#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
47#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
48#include "chrome/browser/ui/omnibox/omnibox_popup_view.h"
49#include "chrome/browser/ui/omnibox/omnibox_view.h"
50#include "chrome/browser/ui/search/instant_controller.h"
51#include "chrome/browser/ui/search/search_tab_helper.h"
52#include "chrome/common/chrome_notification_types.h"
53#include "chrome/common/chrome_switches.h"
54#include "chrome/common/pref_names.h"
55#include "chrome/common/url_constants.h"
56#include "content/public/browser/notification_service.h"
57#include "content/public/browser/render_view_host.h"
58#include "content/public/browser/user_metrics.h"
59#include "extensions/common/constants.h"
60#include "googleurl/src/url_util.h"
61#include "ui/gfx/image/image.h"
62
63using content::UserMetricsAction;
64using predictors::AutocompleteActionPredictor;
65using predictors::AutocompleteActionPredictorFactory;
66
67namespace {
68
69// Histogram name which counts the number of times that the user text is
70// cleared.  IME users are sometimes in the situation that IME was
71// unintentionally turned on and failed to input latin alphabets (ASCII
72// characters) or the opposite case.  In that case, users may delete all
73// the text and the user text gets cleared.  We'd like to measure how often
74// this scenario happens.
75//
76// Note that since we don't currently correlate "text cleared" events with
77// IME usage, this also captures many other cases where users clear the text;
78// though it explicitly doesn't log deleting all the permanent text as
79// the first action of an editing sequence (see comments in
80// OnAfterPossibleChange()).
81const char kOmniboxUserTextClearedHistogram[] = "Omnibox.UserTextCleared";
82
83enum UserTextClearedType {
84  OMNIBOX_USER_TEXT_CLEARED_BY_EDITING = 0,
85  OMNIBOX_USER_TEXT_CLEARED_WITH_ESCAPE = 1,
86  OMNIBOX_USER_TEXT_CLEARED_NUM_OF_ITEMS,
87};
88
89}  // namespace
90
91///////////////////////////////////////////////////////////////////////////////
92// OmniboxEditModel::State
93
94OmniboxEditModel::State::State(bool user_input_in_progress,
95                               const string16& user_text,
96                               const string16& keyword,
97                               bool is_keyword_hint,
98                               OmniboxFocusState focus_state)
99    : user_input_in_progress(user_input_in_progress),
100      user_text(user_text),
101      keyword(keyword),
102      is_keyword_hint(is_keyword_hint),
103      focus_state(focus_state) {
104}
105
106OmniboxEditModel::State::~State() {
107}
108
109///////////////////////////////////////////////////////////////////////////////
110// OmniboxEditModel
111
112OmniboxEditModel::OmniboxEditModel(OmniboxView* view,
113                                   OmniboxEditController* controller,
114                                   Profile* profile)
115    : view_(view),
116      popup_(NULL),
117      controller_(controller),
118      focus_state_(OMNIBOX_FOCUS_NONE),
119      user_input_in_progress_(false),
120      just_deleted_text_(false),
121      has_temporary_text_(false),
122      is_temporary_text_set_by_instant_(false),
123      paste_state_(NONE),
124      control_key_state_(UP),
125      is_keyword_hint_(false),
126      profile_(profile),
127      in_revert_(false),
128      in_escape_handler_(false),
129      allow_exact_keyword_match_(false) {
130  // Use a restricted subset of the autocomplete providers if we're using the
131  // Instant Extended API, as it doesn't support them all.
132  autocomplete_controller_.reset(new AutocompleteController(profile, this,
133      chrome::search::IsInstantExtendedAPIEnabled() ?
134          AutocompleteClassifier::kInstantExtendedOmniboxProviders :
135          AutocompleteClassifier::kDefaultOmniboxProviders));
136  delegate_.reset(new OmniboxCurrentPageDelegateImpl(controller, profile));
137}
138
139OmniboxEditModel::~OmniboxEditModel() {
140}
141
142const OmniboxEditModel::State OmniboxEditModel::GetStateForTabSwitch() {
143  // Like typing, switching tabs "accepts" the temporary text as the user
144  // text, because it makes little sense to have temporary text when the
145  // popup is closed.
146  if (user_input_in_progress_) {
147    // Weird edge case to match other browsers: if the edit is empty, revert to
148    // the permanent text (so the user can get it back easily) but select it (so
149    // on switching back, typing will "just work").
150    const string16 user_text(UserTextFromDisplayText(view_->GetText()));
151    if (user_text.empty()) {
152      view_->RevertAll();
153      view_->SelectAll(true);
154    } else {
155      InternalSetUserText(user_text);
156    }
157  }
158
159  return State(user_input_in_progress_, user_text_, keyword_, is_keyword_hint_,
160               focus_state_);
161}
162
163void OmniboxEditModel::RestoreState(const State& state) {
164  SetFocusState(state.focus_state, OMNIBOX_FOCUS_CHANGE_TAB_SWITCH);
165  // Restore any user editing.
166  if (state.user_input_in_progress) {
167    // NOTE: Be sure and set keyword-related state BEFORE invoking
168    // DisplayTextFromUserText(), as its result depends upon this state.
169    keyword_ = state.keyword;
170    is_keyword_hint_ = state.is_keyword_hint;
171    view_->SetUserText(state.user_text,
172        DisplayTextFromUserText(state.user_text), false);
173  }
174}
175
176AutocompleteMatch OmniboxEditModel::CurrentMatch() {
177  AutocompleteMatch match;
178  GetInfoForCurrentText(&match, NULL);
179  return match;
180}
181
182bool OmniboxEditModel::UpdatePermanentText(const string16& new_permanent_text) {
183  // When there's a new URL, and the user is not editing anything or the edit
184  // doesn't have focus, we want to revert the edit to show the new URL.  (The
185  // common case where the edit doesn't have focus is when the user has started
186  // an edit and then abandoned it and clicked a link on the page.)
187  const bool visibly_changed_permanent_text =
188      (permanent_text_ != new_permanent_text) &&
189      (!user_input_in_progress_ || !has_focus());
190
191  permanent_text_ = new_permanent_text;
192  return visibly_changed_permanent_text;
193}
194
195GURL OmniboxEditModel::PermanentURL() {
196  return URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_), std::string());
197}
198
199void OmniboxEditModel::SetUserText(const string16& text) {
200  SetInputInProgress(true);
201  InternalSetUserText(text);
202  paste_state_ = NONE;
203  has_temporary_text_ = false;
204  is_temporary_text_set_by_instant_ = false;
205}
206
207void OmniboxEditModel::FinalizeInstantQuery(const string16& input_text,
208                                            const InstantSuggestion& suggestion,
209                                            bool skip_inline_autocomplete) {
210  if (skip_inline_autocomplete) {
211    const string16 final_text = input_text + suggestion.text;
212    view_->OnBeforePossibleChange();
213    view_->SetWindowTextAndCaretPos(final_text, final_text.length(), false,
214        false);
215    view_->OnAfterPossibleChange();
216  } else if (popup_->IsOpen()) {
217    SearchProvider* search_provider =
218        autocomplete_controller_->search_provider();
219    // There may be no providers during testing; guard against that.
220    if (search_provider)
221      search_provider->FinalizeInstantQuery(input_text, suggestion);
222  }
223}
224
225void OmniboxEditModel::SetInstantSuggestion(
226    const InstantSuggestion& suggestion) {
227  switch (suggestion.behavior) {
228    case INSTANT_COMPLETE_NOW:
229      view_->SetInstantSuggestion(string16());
230      if (!suggestion.text.empty())
231        FinalizeInstantQuery(view_->GetText(), suggestion, false);
232      break;
233
234    case INSTANT_COMPLETE_NEVER:
235      DCHECK_EQ(INSTANT_SUGGESTION_SEARCH, suggestion.type);
236      view_->SetInstantSuggestion(suggestion.text);
237      break;
238
239    case INSTANT_COMPLETE_REPLACE: {
240      const bool save_original_selection = !has_temporary_text_;
241      view_->SetInstantSuggestion(string16());
242      has_temporary_text_ = true;
243      is_temporary_text_set_by_instant_ = true;
244      // Instant suggestions are never a keyword.
245      keyword_ = string16();
246      is_keyword_hint_ = false;
247      view_->OnTemporaryTextMaybeChanged(suggestion.text,
248                                         save_original_selection, true);
249      break;
250    }
251  }
252}
253
254bool OmniboxEditModel::CommitSuggestedText(bool skip_inline_autocomplete) {
255  if (!controller_->GetInstant())
256    return false;
257
258  const string16 suggestion = view_->GetInstantSuggestion();
259  if (suggestion.empty())
260    return false;
261
262  // Assume that the gray text we are committing is a search suggestion.
263  FinalizeInstantQuery(view_->GetText(),
264                       InstantSuggestion(suggestion,
265                                         INSTANT_COMPLETE_NOW,
266                                         INSTANT_SUGGESTION_SEARCH,
267                                         string16()),
268                       skip_inline_autocomplete);
269  return true;
270}
271
272void OmniboxEditModel::OnChanged() {
273  // Don't call CurrentMatch() when there's no editing, as in this case we'll
274  // never actually use it.  This avoids running the autocomplete providers (and
275  // any systems they then spin up) during startup.
276  const AutocompleteMatch& current_match = user_input_in_progress_ ?
277      CurrentMatch() : AutocompleteMatch();
278
279  AutocompleteActionPredictor::Action recommended_action =
280      AutocompleteActionPredictor::ACTION_NONE;
281  AutocompleteActionPredictor* action_predictor =
282      user_input_in_progress_ ?
283      AutocompleteActionPredictorFactory::GetForProfile(profile_) : NULL;
284  if (action_predictor) {
285    action_predictor->RegisterTransitionalMatches(user_text_, result());
286    // Confer with the AutocompleteActionPredictor to determine what action, if
287    // any, we should take. Get the recommended action here even if we don't
288    // need it so we can get stats for anyone who is opted in to UMA, but only
289    // get it if the user has actually typed something to avoid constructing it
290    // before it's needed. Note: This event is triggered as part of startup when
291    // the initial tab transitions to the start page.
292    recommended_action =
293        action_predictor->RecommendAction(user_text_, current_match);
294  }
295
296  UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.Action",
297                            recommended_action,
298                            AutocompleteActionPredictor::LAST_PREDICT_ACTION);
299
300  if (!DoInstant(current_match)) {
301    // Hide any suggestions we might be showing.
302    view_->SetInstantSuggestion(string16());
303
304    // No need to wait any longer for Instant.
305    FinalizeInstantQuery(string16(), InstantSuggestion(), false);
306  }
307
308  switch (recommended_action) {
309    case AutocompleteActionPredictor::ACTION_PRERENDER:
310      // It's possible that there is no current page, for instance if the tab
311      // has been closed or on return from a sleep state.
312      // (http://crbug.com/105689)
313      if (!delegate_->CurrentPageExists())
314        break;
315      // Ask for prerendering if the destination URL is different than the
316      // current URL.
317      if (current_match.destination_url != PermanentURL())
318        delegate_->DoPrerender(current_match);
319      break;
320    case AutocompleteActionPredictor::ACTION_PRECONNECT:
321      DoPreconnect(current_match);
322      break;
323    case AutocompleteActionPredictor::ACTION_NONE:
324      break;
325  }
326
327  controller_->OnChanged();
328}
329
330void OmniboxEditModel::GetDataForURLExport(GURL* url,
331                                           string16* title,
332                                           gfx::Image* favicon) {
333  AutocompleteMatch match;
334  GetInfoForCurrentText(&match, NULL);
335  *url = match.destination_url;
336  if (*url == URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_),
337                                      std::string())) {
338    *title = controller_->GetTitle();
339    *favicon = controller_->GetFavicon();
340  }
341}
342
343bool OmniboxEditModel::UseVerbatimInstant() {
344#if defined(OS_MACOSX)
345  // TODO(suzhe): Fix Mac port to display Instant suggest in a separated NSView,
346  // so that we can display Instant suggest along with composition text.
347  const AutocompleteInput& input = autocomplete_controller_->input();
348  if (input.prevent_inline_autocomplete())
349    return true;
350#endif
351
352  // The value of input.prevent_inline_autocomplete() is determined by the
353  // following conditions:
354  // 1. If the caret is at the end of the text.
355  // 2. If it's in IME composition mode.
356  // We send the caret position to Instant (so it can determine #1 itself), and
357  // we use a separated widget for displaying the Instant suggest (so it doesn't
358  // interfere with #2). So, we don't need to care about the value of
359  // input.prevent_inline_autocomplete() here.
360  return view_->DeleteAtEndPressed() || popup_->selected_line() != 0 ||
361      just_deleted_text_;
362}
363
364bool OmniboxEditModel::CurrentTextIsURL() const {
365  if (view_->toolbar_model()->WouldReplaceSearchURLWithSearchTerms())
366    return false;
367
368  // If current text is not composed of replaced search terms and
369  // !user_input_in_progress_, then permanent text is showing and should be a
370  // URL, so no further checking is needed.  By avoiding checking in this case,
371  // we avoid calling into the autocomplete providers, and thus initializing the
372  // history system, as long as possible, which speeds startup.
373  if (!user_input_in_progress_)
374    return true;
375
376  AutocompleteMatch match;
377  GetInfoForCurrentText(&match, NULL);
378  return !AutocompleteMatch::IsSearchType(match.type);
379}
380
381AutocompleteMatch::Type OmniboxEditModel::CurrentTextType() const {
382  AutocompleteMatch match;
383  GetInfoForCurrentText(&match, NULL);
384  return match.type;
385}
386
387void OmniboxEditModel::AdjustTextForCopy(int sel_min,
388                                         bool is_all_selected,
389                                         string16* text,
390                                         GURL* url,
391                                         bool* write_url) {
392  *write_url = false;
393
394  // Do not adjust if selection did not start at the beginning of the field, or
395  // if the URL was replaced by search terms.
396  if (sel_min != 0 ||
397      view_->toolbar_model()->WouldReplaceSearchURLWithSearchTerms())
398    return;
399
400  if (!user_input_in_progress_ && is_all_selected) {
401    // The user selected all the text and has not edited it. Use the url as the
402    // text so that if the scheme was stripped it's added back, and the url
403    // is unescaped (we escape parts of the url for display).
404    *url = PermanentURL();
405    *text = UTF8ToUTF16(url->spec());
406    *write_url = true;
407    return;
408  }
409
410  // We can't use CurrentTextIsURL() or GetDataForURLExport() because right now
411  // the user is probably holding down control to cause the copy, which will
412  // screw up our calculation of the desired_tld.
413  AutocompleteMatch match;
414  AutocompleteClassifierFactory::GetForProfile(profile_)->Classify(*text,
415      KeywordIsSelected(), true, &match, NULL);
416  if (AutocompleteMatch::IsSearchType(match.type))
417    return;
418  *url = match.destination_url;
419
420  // Prefix the text with 'http://' if the text doesn't start with 'http://',
421  // the text parses as a url with a scheme of http, the user selected the
422  // entire host, and the user hasn't edited the host or manually removed the
423  // scheme.
424  GURL perm_url(PermanentURL());
425  if (perm_url.SchemeIs(chrome::kHttpScheme) &&
426      url->SchemeIs(chrome::kHttpScheme) && perm_url.host() == url->host()) {
427    *write_url = true;
428    string16 http = ASCIIToUTF16(chrome::kHttpScheme) +
429        ASCIIToUTF16(content::kStandardSchemeSeparator);
430    if (text->compare(0, http.length(), http) != 0)
431      *text = http + *text;
432  }
433}
434
435void OmniboxEditModel::SetInputInProgress(bool in_progress) {
436  if (user_input_in_progress_ == in_progress)
437    return;
438
439  user_input_in_progress_ = in_progress;
440  if (user_input_in_progress_) {
441    time_user_first_modified_omnibox_ = base::TimeTicks::Now();
442    content::RecordAction(content::UserMetricsAction("OmniboxInputInProgress"));
443    autocomplete_controller_->ResetSession();
444  }
445  controller_->OnInputInProgress(in_progress);
446
447  delegate_->NotifySearchTabHelper(user_input_in_progress_, !in_revert_);
448}
449
450void OmniboxEditModel::Revert() {
451  SetInputInProgress(false);
452  paste_state_ = NONE;
453  InternalSetUserText(string16());
454  keyword_.clear();
455  is_keyword_hint_ = false;
456  has_temporary_text_ = false;
457  is_temporary_text_set_by_instant_ = false;
458  view_->SetWindowTextAndCaretPos(permanent_text_,
459                                  has_focus() ? permanent_text_.length() : 0,
460                                  false, true);
461  AutocompleteActionPredictor* action_predictor =
462      AutocompleteActionPredictorFactory::GetForProfile(profile_);
463  if (action_predictor)
464    action_predictor->ClearTransitionalMatches();
465}
466
467void OmniboxEditModel::StartAutocomplete(
468    bool has_selected_text,
469    bool prevent_inline_autocomplete) const {
470  ClearPopupKeywordMode();
471
472  bool keyword_is_selected = KeywordIsSelected();
473  popup_->SetHoveredLine(OmniboxPopupModel::kNoMatch);
474
475  size_t cursor_position;
476  if (inline_autocomplete_text_.empty()) {
477    // Cursor position is equivalent to the current selection's end.
478    size_t start;
479    view_->GetSelectionBounds(&start, &cursor_position);
480    // Adjust cursor position taking into account possible keyword in the user
481    // text.  We rely on DisplayTextFromUserText() method which is consistent
482    // with keyword extraction done in KeywordProvider/SearchProvider.
483    const size_t cursor_offset =
484        user_text_.length() - DisplayTextFromUserText(user_text_).length();
485    cursor_position += cursor_offset;
486  } else {
487    // There are some cases where StartAutocomplete() may be called
488    // with non-empty |inline_autocomplete_text_|.  In such cases, we cannot
489    // use the current selection, because it could result with the cursor
490    // position past the last character from the user text.  Instead,
491    // we assume that the cursor is simply at the end of input.
492    // One example is when user presses Ctrl key while having a highlighted
493    // inline autocomplete text.
494    // TODO: Rethink how we are going to handle this case to avoid
495    // inconsistent behavior when user presses Ctrl key.
496    // See http://crbug.com/165961 and http://crbug.com/165968 for more details.
497    cursor_position = user_text_.length();
498  }
499
500  // We don't explicitly clear OmniboxPopupModel::manually_selected_match, as
501  // Start ends up invoking OmniboxPopupModel::OnResultChanged which clears it.
502  autocomplete_controller_->Start(AutocompleteInput(
503      user_text_, cursor_position, string16(), GURL(),
504      prevent_inline_autocomplete || just_deleted_text_ ||
505      (has_selected_text && inline_autocomplete_text_.empty()) ||
506      (paste_state_ != NONE), keyword_is_selected,
507      keyword_is_selected || allow_exact_keyword_match_,
508      AutocompleteInput::ALL_MATCHES));
509}
510
511void OmniboxEditModel::StopAutocomplete() {
512  autocomplete_controller_->Stop(true);
513}
514
515bool OmniboxEditModel::CanPasteAndGo(const string16& text) const {
516  if (!view_->command_updater()->IsCommandEnabled(IDC_OPEN_CURRENT_URL))
517    return false;
518
519  AutocompleteMatch match;
520  ClassifyStringForPasteAndGo(text, &match, NULL);
521  return match.destination_url.is_valid();
522}
523
524void OmniboxEditModel::PasteAndGo(const string16& text) {
525  DCHECK(CanPasteAndGo(text));
526  view_->RevertAll();
527  AutocompleteMatch match;
528  GURL alternate_nav_url;
529  ClassifyStringForPasteAndGo(text, &match, &alternate_nav_url);
530  view_->OpenMatch(match, CURRENT_TAB, alternate_nav_url,
531                   OmniboxPopupModel::kNoMatch);
532}
533
534bool OmniboxEditModel::IsPasteAndSearch(const string16& text) const {
535  AutocompleteMatch match;
536  ClassifyStringForPasteAndGo(text, &match, NULL);
537  return AutocompleteMatch::IsSearchType(match.type);
538}
539
540void OmniboxEditModel::AcceptInput(WindowOpenDisposition disposition,
541                                   bool for_drop) {
542  // Get the URL and transition type for the selected entry.
543  AutocompleteMatch match;
544  GURL alternate_nav_url;
545  GetInfoForCurrentText(&match, &alternate_nav_url);
546
547  // If CTRL is down it means the user wants to append ".com" to the text he
548  // typed. If we can successfully generate a URL_WHAT_YOU_TYPED match doing
549  // that, then we use this.
550  if (control_key_state_ == DOWN_WITHOUT_CHANGE && !KeywordIsSelected()) {
551    // Generate a new AutocompleteInput, copying the latest one but using "com"
552    // as the desired TLD. Then use this autocomplete input to generate a
553    // URL_WHAT_YOU_TYPED AutocompleteMatch. Note that using the most recent
554    // input instead of the currently visible text means we'll ignore any
555    // visible inline autocompletion: if a user types "foo" and is autocompleted
556    // to "foodnetwork.com", ctrl-enter will  navigate to "foo.com", not
557    // "foodnetwork.com".  At the time of writing, this behavior matches
558    // Internet Explorer, but not Firefox.
559    const AutocompleteInput& old_input = autocomplete_controller_->input();
560    AutocompleteInput input(
561      old_input.text(), old_input.cursor_position(), ASCIIToUTF16("com"),
562      GURL(), old_input.prevent_inline_autocomplete(),
563      old_input.prefer_keyword(), old_input.allow_exact_keyword_match(),
564      old_input.matches_requested());
565    AutocompleteMatch url_match =
566        HistoryURLProvider::SuggestExactInput(match.provider, input, true);
567
568    if (url_match.destination_url.is_valid()) {
569      // We have a valid URL, we use this newly generated AutocompleteMatch.
570      match = url_match;
571      alternate_nav_url = GURL();
572    }
573  }
574
575  if (!match.destination_url.is_valid())
576    return;
577
578  if ((match.transition == content::PAGE_TRANSITION_TYPED) &&
579      (match.destination_url ==
580       URLFixerUpper::FixupURL(UTF16ToUTF8(permanent_text_), std::string()))) {
581    // When the user hit enter on the existing permanent URL, treat it like a
582    // reload for scoring purposes.  We could detect this by just checking
583    // user_input_in_progress_, but it seems better to treat "edits" that end
584    // up leaving the URL unchanged (e.g. deleting the last character and then
585    // retyping it) as reloads too.  We exclude non-TYPED transitions because if
586    // the transition is GENERATED, the user input something that looked
587    // different from the current URL, even if it wound up at the same place
588    // (e.g. manually retyping the same search query), and it seems wrong to
589    // treat this as a reload.
590    match.transition = content::PAGE_TRANSITION_RELOAD;
591  } else if (for_drop || ((paste_state_ != NONE) &&
592                          match.is_history_what_you_typed_match)) {
593    // When the user pasted in a URL and hit enter, score it like a link click
594    // rather than a normal typed URL, so it doesn't get inline autocompleted
595    // as aggressively later.
596    match.transition = content::PAGE_TRANSITION_LINK;
597  }
598
599  const TemplateURL* template_url = match.GetTemplateURL(profile_, false);
600  if (template_url && template_url->url_ref().HasGoogleBaseURLs())
601    GoogleURLTracker::GoogleURLSearchCommitted(profile_);
602
603  view_->OpenMatch(match, disposition, alternate_nav_url,
604                   OmniboxPopupModel::kNoMatch);
605}
606
607void OmniboxEditModel::OpenMatch(const AutocompleteMatch& match,
608                                 WindowOpenDisposition disposition,
609                                 const GURL& alternate_nav_url,
610                                 size_t index) {
611  // We only care about cases where there is a selection (i.e. the popup is
612  // open).
613  if (popup_->IsOpen()) {
614    const base::TimeTicks& now(base::TimeTicks::Now());
615    // TODO(sreeram): Handle is_temporary_text_set_by_instant_ correctly.
616    AutocompleteLog log(
617        autocomplete_controller_->input().text(),
618        just_deleted_text_,
619        autocomplete_controller_->input().type(),
620        popup_->selected_line(),
621        -1,  // don't yet know tab ID; set later if appropriate
622        delegate_->CurrentPageExists() ? ClassifyPage(delegate_->GetURL()) :
623            metrics::OmniboxEventProto_PageClassification_OTHER,
624        now - time_user_first_modified_omnibox_,
625        string16::npos,  // completed_length; possibly set later
626        now - autocomplete_controller_->last_time_default_match_changed(),
627        result());
628    DCHECK(user_input_in_progress_ ||
629           match.provider->type() == AutocompleteProvider::TYPE_ZERO_SUGGEST)
630        << "We didn't get here through the expected series of calls. "
631        << "time_user_first_modified_omnibox_ is not set correctly and other "
632        << "things may be wrong. Match provider: " << match.provider->GetName();
633    DCHECK(log.elapsed_time_since_user_first_modified_omnibox >=
634           log.elapsed_time_since_last_change_to_default_match)
635        << "We should've got the notification that the user modified the "
636        << "omnibox text at same time or before the most recent time the "
637        << "default match changed.";
638    if (index != OmniboxPopupModel::kNoMatch)
639      log.selected_index = index;
640    if (match.inline_autocomplete_offset != string16::npos) {
641      DCHECK_GE(match.fill_into_edit.length(),
642                match.inline_autocomplete_offset);
643      log.completed_length =
644          match.fill_into_edit.length() - match.inline_autocomplete_offset;
645    }
646
647    if ((disposition == CURRENT_TAB) && delegate_->CurrentPageExists()) {
648      // If we know the destination is being opened in the current tab,
649      // we can easily get the tab ID.  (If it's being opened in a new
650      // tab, we don't know the tab ID yet.)
651      log.tab_id = delegate_->GetSessionID().id();
652    }
653    autocomplete_controller_->AddProvidersInfo(&log.providers_info);
654    content::NotificationService::current()->Notify(
655        chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
656        content::Source<Profile>(profile_),
657        content::Details<AutocompleteLog>(&log));
658    HISTOGRAM_ENUMERATION("Omnibox.EventCount", 1, 2);
659  }
660
661  TemplateURL* template_url = match.GetTemplateURL(profile_, false);
662  if (template_url) {
663    if (match.transition == content::PAGE_TRANSITION_KEYWORD) {
664      // The user is using a non-substituting keyword or is explicitly in
665      // keyword mode.
666
667      AutocompleteMatch current_match;
668      GetInfoForCurrentText(&current_match, NULL);
669      const AutocompleteMatch& match = (index == OmniboxPopupModel::kNoMatch) ?
670          current_match : result().match_at(index);
671
672      // Don't increment usage count for extension keywords.
673      if (delegate_->ProcessExtensionKeyword(template_url, match)) {
674        view_->RevertAll();
675        return;
676      }
677
678      content::RecordAction(UserMetricsAction("AcceptedKeyword"));
679      TemplateURLServiceFactory::GetForProfile(profile_)->IncrementUsageCount(
680          template_url);
681    } else {
682      DCHECK_EQ(content::PAGE_TRANSITION_GENERATED, match.transition);
683      // NOTE: We purposefully don't increment the usage count of the default
684      // search engine here like we do for explicit keywords above; see comments
685      // in template_url.h.
686    }
687
688    // NOTE: Non-prepopulated engines will all have ID 0, which is fine as
689    // the prepopulate IDs start at 1.  Distribution-specific engines will
690    // all have IDs above the maximum, and will be automatically lumped
691    // together in an "overflow" bucket in the histogram.
692    UMA_HISTOGRAM_ENUMERATION("Omnibox.SearchEngine",
693        template_url->prepopulate_id(),
694        TemplateURLPrepopulateData::kMaxPrepopulatedEngineID);
695  }
696
697  if (disposition != NEW_BACKGROUND_TAB) {
698    base::AutoReset<bool> tmp(&in_revert_, true);
699    view_->RevertAll();  // Revert the box to its unedited state
700  }
701
702  if (match.type == AutocompleteMatch::EXTENSION_APP) {
703    ExtensionAppProvider::LaunchAppFromOmnibox(match, profile_, disposition);
704  } else {
705    base::TimeDelta query_formulation_time =
706        base::TimeTicks::Now() - time_user_first_modified_omnibox_;
707    const GURL destination_url = autocomplete_controller_->
708        GetDestinationURL(match, query_formulation_time);
709    // This calls RevertAll again.
710    base::AutoReset<bool> tmp(&in_revert_, true);
711    controller_->OnAutocompleteAccept(destination_url, disposition,
712                                      match.transition, alternate_nav_url);
713  }
714
715  if (match.starred)
716    bookmark_utils::RecordBookmarkLaunch(bookmark_utils::LAUNCH_OMNIBOX);
717}
718
719bool OmniboxEditModel::AcceptKeyword() {
720  DCHECK(is_keyword_hint_ && !keyword_.empty());
721
722  autocomplete_controller_->Stop(false);
723  is_keyword_hint_ = false;
724
725  if (popup_->IsOpen())
726    popup_->SetSelectedLineState(OmniboxPopupModel::KEYWORD);
727  else
728    StartAutocomplete(false, true);
729
730  // Ensure the current selection is saved before showing keyword mode
731  // so that moving to another line and then reverting the text will restore
732  // the current state properly.
733  bool save_original_selection = !has_temporary_text_;
734  has_temporary_text_ = true;
735  is_temporary_text_set_by_instant_ = false;
736  view_->OnTemporaryTextMaybeChanged(
737      DisplayTextFromUserText(CurrentMatch().fill_into_edit),
738      save_original_selection, true);
739
740  content::RecordAction(UserMetricsAction("AcceptedKeywordHint"));
741  return true;
742}
743
744void OmniboxEditModel::ClearKeyword(const string16& visible_text) {
745  autocomplete_controller_->Stop(false);
746  ClearPopupKeywordMode();
747
748  const string16 window_text(keyword_ + visible_text);
749
750  // Only reset the result if the edit text has changed since the
751  // keyword was accepted, or if the popup is closed.
752  if (just_deleted_text_ || !visible_text.empty() || !popup_->IsOpen()) {
753    view_->OnBeforePossibleChange();
754    view_->SetWindowTextAndCaretPos(window_text.c_str(), keyword_.length(),
755        false, false);
756    keyword_.clear();
757    is_keyword_hint_ = false;
758    view_->OnAfterPossibleChange();
759    just_deleted_text_ = true;  // OnAfterPossibleChange() fails to clear this
760                                // since the edit contents have actually grown
761                                // longer.
762  } else {
763    is_keyword_hint_ = true;
764    view_->SetWindowTextAndCaretPos(window_text.c_str(), keyword_.length(),
765        false, true);
766  }
767}
768
769const AutocompleteResult& OmniboxEditModel::result() const {
770  return autocomplete_controller_->result();
771}
772
773void OmniboxEditModel::OnSetFocus(bool control_down) {
774  // If the omnibox lost focus while the caret was hidden and then regained
775  // focus, OnSetFocus() is called and should restore visibility. Note that
776  // focus can be regained without an accompanying call to
777  // OmniboxView::SetFocus(), e.g. by tabbing in.
778  SetFocusState(OMNIBOX_FOCUS_VISIBLE, OMNIBOX_FOCUS_CHANGE_EXPLICIT);
779  control_key_state_ = control_down ? DOWN_WITHOUT_CHANGE : UP;
780
781  if (delegate_->CurrentPageExists()) {
782    // TODO(jered): We may want to merge this into Start() and just call that
783    // here rather than having a special entry point for zero-suggest.  Note
784    // that we avoid PermanentURL() here because it's not guaranteed to give us
785    // the actual underlying current URL, e.g. if we're on the NTP and the
786    // |permanent_text_| is empty.
787    autocomplete_controller_->StartZeroSuggest(delegate_->GetURL(),
788                                               user_text_);
789  }
790
791  delegate_->NotifySearchTabHelper(user_input_in_progress_, !in_revert_);
792}
793
794void OmniboxEditModel::SetCaretVisibility(bool visible) {
795  // Caret visibility only matters if the omnibox has focus.
796  if (focus_state_ != OMNIBOX_FOCUS_NONE) {
797    SetFocusState(visible ? OMNIBOX_FOCUS_VISIBLE : OMNIBOX_FOCUS_INVISIBLE,
798                  OMNIBOX_FOCUS_CHANGE_EXPLICIT);
799  }
800}
801
802void OmniboxEditModel::OnWillKillFocus(gfx::NativeView view_gaining_focus) {
803  InstantController* instant = controller_->GetInstant();
804  if (instant) {
805    instant->OmniboxFocusChanged(OMNIBOX_FOCUS_NONE,
806                                 OMNIBOX_FOCUS_CHANGE_EXPLICIT,
807                                 view_gaining_focus);
808  }
809
810  SetInstantSuggestion(InstantSuggestion());
811
812  // TODO(jered): Rip this out along with StartZeroSuggest.
813  autocomplete_controller_->StopZeroSuggest();
814  delegate_->NotifySearchTabHelper(user_input_in_progress_, !in_revert_);
815}
816
817void OmniboxEditModel::OnKillFocus() {
818  // TODO(samarth): determine if it is safe to move the call to
819  // OmniboxFocusChanged() from OnWillKillFocus() to here, which would let us
820  // just call SetFocusState() to handle the state change.
821  focus_state_ = OMNIBOX_FOCUS_NONE;
822  control_key_state_ = UP;
823  paste_state_ = NONE;
824}
825
826bool OmniboxEditModel::OnEscapeKeyPressed() {
827  if (has_temporary_text_) {
828    AutocompleteMatch match;
829    InfoForCurrentSelection(&match, NULL);
830    if (match.destination_url != original_url_) {
831      RevertTemporaryText(true);
832      return true;
833    }
834  }
835
836  // We do not clear the pending entry from the omnibox when a load is first
837  // stopped.  If the user presses Escape while stopped, we clear it.
838  if (delegate_->CurrentPageExists() && !delegate_->IsLoading()) {
839    delegate_->GetNavigationController().DiscardNonCommittedEntries();
840    view_->Update(NULL);
841  }
842
843  // If the user wasn't editing, but merely had focus in the edit, allow <esc>
844  // to be processed as an accelerator, so it can still be used to stop a load.
845  // When the permanent text isn't all selected we still fall through to the
846  // SelectAll() call below so users can arrow around in the text and then hit
847  // <esc> to quickly replace all the text; this matches IE.
848  if (!user_input_in_progress_ && view_->IsSelectAll())
849    return false;
850
851  in_escape_handler_ = true;
852  if (!user_text_.empty()) {
853    UMA_HISTOGRAM_ENUMERATION(kOmniboxUserTextClearedHistogram,
854                              OMNIBOX_USER_TEXT_CLEARED_WITH_ESCAPE,
855                              OMNIBOX_USER_TEXT_CLEARED_NUM_OF_ITEMS);
856  }
857  view_->RevertAll();
858  in_escape_handler_ = false;
859  view_->SelectAll(true);
860  return true;
861}
862
863void OmniboxEditModel::OnControlKeyChanged(bool pressed) {
864  // Don't change anything unless the key state is actually toggling.
865  if (pressed == (control_key_state_ == UP)) {
866    ControlKeyState old_state = control_key_state_;
867    control_key_state_ = pressed ? DOWN_WITHOUT_CHANGE : UP;
868    if ((control_key_state_ == DOWN_WITHOUT_CHANGE) && has_temporary_text_) {
869      // Arrowing down and then hitting control accepts the temporary text as
870      // the input text.
871      InternalSetUserText(UserTextFromDisplayText(view_->GetText()));
872      has_temporary_text_ = false;
873      is_temporary_text_set_by_instant_ = false;
874    }
875    if ((old_state != DOWN_WITH_CHANGE) && popup_->IsOpen()) {
876      // Autocomplete history provider results may change, so refresh the
877      // popup.  This will force user_input_in_progress_ to true, but if the
878      // popup is open, that should have already been the case.
879      view_->UpdatePopup();
880    }
881  }
882}
883
884void OmniboxEditModel::OnUpOrDownKeyPressed(int count) {
885  // NOTE: This purposefully doesn't trigger any code that resets paste_state_.
886  if (!popup_->IsOpen()) {
887    if (!query_in_progress()) {
888      // The popup is neither open nor working on a query already.  So, start an
889      // autocomplete query for the current text.  This also sets
890      // user_input_in_progress_ to true, which we want: if the user has started
891      // to interact with the popup, changing the permanent_text_ shouldn't
892      // change the displayed text.
893      // Note: This does not force the popup to open immediately.
894      // TODO(pkasting): We should, in fact, force this particular query to open
895      // the popup immediately.
896      if (!user_input_in_progress_)
897        InternalSetUserText(permanent_text_);
898      view_->UpdatePopup();
899    } else {
900      // TODO(pkasting): The popup is working on a query but is not open.  We
901      // should force it to open immediately.
902    }
903  } else {
904    InstantController* instant = controller_->GetInstant();
905    if (instant && instant->OnUpOrDownKeyPressed(count)) {
906      // If Instant handles the key press, it's showing a list of suggestions
907      // that it's stepping through. In that case, our popup model is
908      // irrelevant, so don't process the key press ourselves. However, do stop
909      // the autocomplete system from changing the results.
910      autocomplete_controller_->Stop(false);
911    } else {
912      // The popup is open, so the user should be able to interact with it
913      // normally.
914      popup_->Move(count);
915    }
916  }
917}
918
919void OmniboxEditModel::OnPopupDataChanged(
920    const string16& text,
921    GURL* destination_for_temporary_text_change,
922    const string16& keyword,
923    bool is_keyword_hint) {
924  // Update keyword/hint-related local state.
925  bool keyword_state_changed = (keyword_ != keyword) ||
926      ((is_keyword_hint_ != is_keyword_hint) && !keyword.empty());
927  if (keyword_state_changed) {
928    keyword_ = keyword;
929    is_keyword_hint_ = is_keyword_hint;
930
931    // |is_keyword_hint_| should always be false if |keyword_| is empty.
932    DCHECK(!keyword_.empty() || !is_keyword_hint_);
933  }
934
935  // Handle changes to temporary text.
936  if (destination_for_temporary_text_change != NULL) {
937    const bool save_original_selection = !has_temporary_text_;
938    if (save_original_selection) {
939      // Save the original selection and URL so it can be reverted later.
940      has_temporary_text_ = true;
941      is_temporary_text_set_by_instant_ = false;
942      original_url_ = *destination_for_temporary_text_change;
943      inline_autocomplete_text_.clear();
944    }
945    if (control_key_state_ == DOWN_WITHOUT_CHANGE) {
946      // Arrowing around the popup cancels control-enter.
947      control_key_state_ = DOWN_WITH_CHANGE;
948      // Now things are a bit screwy: the desired_tld has changed, but if we
949      // update the popup, the new order of entries won't match the old, so the
950      // user's selection gets screwy; and if we don't update the popup, and the
951      // user reverts, then the selected item will be as if control is still
952      // pressed, even though maybe it isn't any more.  There is no obvious
953      // right answer here :(
954    }
955    view_->OnTemporaryTextMaybeChanged(DisplayTextFromUserText(text),
956                                       save_original_selection, true);
957    return;
958  }
959
960  bool call_controller_onchanged = true;
961  inline_autocomplete_text_ = text;
962
963  if (keyword_state_changed && KeywordIsSelected()) {
964    // If we reach here, the user most likely entered keyword mode by inserting
965    // a space between a keyword name and a search string (as pressing space or
966    // tab after the keyword name alone would have been be handled in
967    // MaybeAcceptKeywordBySpace() by calling AcceptKeyword(), which won't reach
968    // here).  In this case, we don't want to call
969    // OnInlineAutocompleteTextMaybeChanged() as normal, because that will
970    // correctly change the text (to the search string alone) but move the caret
971    // to the end of the string; instead we want the caret at the start of the
972    // search string since that's where it was in the original input.  So we set
973    // the text and caret position directly.
974    //
975    // It may also be possible to reach here if we're reverting from having
976    // temporary text back to a default match that's a keyword search, but in
977    // that case the RevertTemporaryText() call below will reset the caret or
978    // selection correctly so the caret positioning we do here won't matter.
979    view_->SetWindowTextAndCaretPos(DisplayTextFromUserText(user_text_), 0,
980                                                            false, false);
981  } else if (view_->OnInlineAutocompleteTextMaybeChanged(
982             DisplayTextFromUserText(user_text_ + inline_autocomplete_text_),
983             DisplayTextFromUserText(user_text_).length())) {
984    call_controller_onchanged = false;
985  }
986
987  // If |has_temporary_text_| is true, then we previously had a manual selection
988  // but now don't (or |destination_for_temporary_text_change| would have been
989  // non-NULL). This can happen when deleting the selected item in the popup.
990  // In this case, we've already reverted the popup to the default match, so we
991  // need to revert ourselves as well.
992  if (has_temporary_text_) {
993    RevertTemporaryText(false);
994    call_controller_onchanged = false;
995  }
996
997  // We need to invoke OnChanged in case the destination url changed (as could
998  // happen when control is toggled).
999  if (call_controller_onchanged)
1000    OnChanged();
1001}
1002
1003bool OmniboxEditModel::OnAfterPossibleChange(const string16& old_text,
1004                                             const string16& new_text,
1005                                             size_t selection_start,
1006                                             size_t selection_end,
1007                                             bool selection_differs,
1008                                             bool text_differs,
1009                                             bool just_deleted_text,
1010                                             bool allow_keyword_ui_change) {
1011  // Update the paste state as appropriate: if we're just finishing a paste
1012  // that replaced all the text, preserve that information; otherwise, if we've
1013  // made some other edit, clear paste tracking.
1014  if (paste_state_ == PASTING)
1015    paste_state_ = PASTED;
1016  else if (text_differs)
1017    paste_state_ = NONE;
1018
1019  // Restore caret visibility whenever the user changes text or selection in the
1020  // omnibox.
1021  if (text_differs || selection_differs)
1022    SetFocusState(OMNIBOX_FOCUS_VISIBLE, OMNIBOX_FOCUS_CHANGE_TYPING);
1023
1024  // Modifying the selection counts as accepting the autocompleted text.
1025  const bool user_text_changed =
1026      text_differs || (selection_differs && !inline_autocomplete_text_.empty());
1027
1028  // If something has changed while the control key is down, prevent
1029  // "ctrl-enter" until the control key is released.  When we do this, we need
1030  // to update the popup if it's open, since the desired_tld will have changed.
1031  if ((text_differs || selection_differs) &&
1032      (control_key_state_ == DOWN_WITHOUT_CHANGE)) {
1033    control_key_state_ = DOWN_WITH_CHANGE;
1034    if (!text_differs && !popup_->IsOpen())
1035      return false;  // Don't open the popup for no reason.
1036  } else if (!user_text_changed) {
1037    return false;
1038  }
1039
1040  // If the user text has not changed, we do not want to change the model's
1041  // state associated with the text.  Otherwise, we can get surprising behavior
1042  // where the autocompleted text unexpectedly reappears, e.g. crbug.com/55983
1043  if (user_text_changed) {
1044    InternalSetUserText(UserTextFromDisplayText(new_text));
1045    has_temporary_text_ = false;
1046    is_temporary_text_set_by_instant_ = false;
1047
1048    // Track when the user has deleted text so we won't allow inline
1049    // autocomplete.
1050    just_deleted_text_ = just_deleted_text;
1051
1052    if (user_input_in_progress_ && user_text_.empty()) {
1053      // Log cases where the user started editing and then subsequently cleared
1054      // all the text.  Note that this explicitly doesn't catch cases like
1055      // "hit ctrl-l to select whole edit contents, then hit backspace", because
1056      // in such cases, |user_input_in_progress| won't be true here.
1057      UMA_HISTOGRAM_ENUMERATION(kOmniboxUserTextClearedHistogram,
1058                                OMNIBOX_USER_TEXT_CLEARED_BY_EDITING,
1059                                OMNIBOX_USER_TEXT_CLEARED_NUM_OF_ITEMS);
1060    }
1061  }
1062
1063  const bool no_selection = selection_start == selection_end;
1064
1065  // Update the popup for the change, in the process changing to keyword mode
1066  // if the user hit space in mid-string after a keyword.
1067  // |allow_exact_keyword_match_| will be used by StartAutocomplete() method,
1068  // which will be called by |view_->UpdatePopup()|; so after that returns we
1069  // can safely reset this flag.
1070  allow_exact_keyword_match_ = text_differs && allow_keyword_ui_change &&
1071      !just_deleted_text && no_selection &&
1072      CreatedKeywordSearchByInsertingSpaceInMiddle(old_text, user_text_,
1073                                                   selection_start);
1074  view_->UpdatePopup();
1075  allow_exact_keyword_match_ = false;
1076
1077  // Change to keyword mode if the user is now pressing space after a keyword
1078  // name.  Note that if this is the case, then even if there was no keyword
1079  // hint when we entered this function (e.g. if the user has used space to
1080  // replace some selected text that was adjoined to this keyword), there will
1081  // be one now because of the call to UpdatePopup() above; so it's safe for
1082  // MaybeAcceptKeywordBySpace() to look at |keyword_| and |is_keyword_hint_| to
1083  // determine what keyword, if any, is applicable.
1084  //
1085  // If MaybeAcceptKeywordBySpace() accepts the keyword and returns true, that
1086  // will have updated our state already, so in that case we don't also return
1087  // true from this function.
1088  return !(text_differs && allow_keyword_ui_change && !just_deleted_text &&
1089           no_selection && (selection_start == user_text_.length()) &&
1090           MaybeAcceptKeywordBySpace(user_text_));
1091}
1092
1093void OmniboxEditModel::OnPopupBoundsChanged(const gfx::Rect& bounds) {
1094  InstantController* instant = controller_->GetInstant();
1095  if (instant)
1096    instant->SetPopupBounds(bounds);
1097}
1098
1099void OmniboxEditModel::OnResultChanged(bool default_match_changed) {
1100  const bool was_open = popup_->IsOpen();
1101  if (default_match_changed) {
1102    string16 inline_autocomplete_text;
1103    string16 keyword;
1104    bool is_keyword_hint = false;
1105    const AutocompleteResult& result = this->result();
1106    const AutocompleteResult::const_iterator match(result.default_match());
1107    if (match != result.end()) {
1108      if ((match->inline_autocomplete_offset != string16::npos) &&
1109          (match->inline_autocomplete_offset <
1110           match->fill_into_edit.length())) {
1111        inline_autocomplete_text =
1112            match->fill_into_edit.substr(match->inline_autocomplete_offset);
1113      }
1114
1115      if (!prerender::IsOmniboxEnabled(profile_))
1116        DoPreconnect(*match);
1117
1118      // We could prefetch the alternate nav URL, if any, but because there
1119      // can be many of these as a user types an initial series of characters,
1120      // the OS DNS cache could suffer eviction problems for minimal gain.
1121
1122      match->GetKeywordUIState(profile_, &keyword, &is_keyword_hint);
1123    }
1124
1125    popup_->OnResultChanged();
1126    OnPopupDataChanged(inline_autocomplete_text, NULL, keyword,
1127                       is_keyword_hint);
1128  } else {
1129    popup_->OnResultChanged();
1130  }
1131
1132  if (popup_->IsOpen()) {
1133    OnPopupBoundsChanged(popup_->view()->GetTargetBounds());
1134  } else if (was_open) {
1135    // Accepts the temporary text as the user text, because it makes little
1136    // sense to have temporary text when the popup is closed.
1137    InternalSetUserText(UserTextFromDisplayText(view_->GetText()));
1138    has_temporary_text_ = false;
1139    is_temporary_text_set_by_instant_ = false;
1140    OnPopupBoundsChanged(gfx::Rect());
1141    delegate_->NotifySearchTabHelper(user_input_in_progress_, !in_revert_);
1142  }
1143
1144  InstantController* instant = controller_->GetInstant();
1145  if (instant && !in_revert_)
1146    instant->HandleAutocompleteResults(*autocomplete_controller_->providers());
1147}
1148
1149bool OmniboxEditModel::query_in_progress() const {
1150  return !autocomplete_controller_->done();
1151}
1152
1153void OmniboxEditModel::InternalSetUserText(const string16& text) {
1154  user_text_ = text;
1155  just_deleted_text_ = false;
1156  inline_autocomplete_text_.clear();
1157}
1158
1159bool OmniboxEditModel::KeywordIsSelected() const {
1160  return !is_keyword_hint_ && !keyword_.empty();
1161}
1162
1163void OmniboxEditModel::ClearPopupKeywordMode() const {
1164  if (popup_->IsOpen() &&
1165      popup_->selected_line_state() == OmniboxPopupModel::KEYWORD)
1166    popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL);
1167}
1168
1169string16 OmniboxEditModel::DisplayTextFromUserText(const string16& text) const {
1170  return KeywordIsSelected() ?
1171      KeywordProvider::SplitReplacementStringFromInput(text, false) : text;
1172}
1173
1174string16 OmniboxEditModel::UserTextFromDisplayText(const string16& text) const {
1175  return KeywordIsSelected() ? (keyword_ + char16(' ') + text) : text;
1176}
1177
1178void OmniboxEditModel::InfoForCurrentSelection(AutocompleteMatch* match,
1179                                               GURL* alternate_nav_url) const {
1180  DCHECK(match != NULL);
1181  const AutocompleteResult& result = this->result();
1182  if (!autocomplete_controller_->done()) {
1183    // It's technically possible for |result| to be empty if no provider returns
1184    // a synchronous result but the query has not completed synchronously;
1185    // pratically, however, that should never actually happen.
1186    if (result.empty())
1187      return;
1188    // The user cannot have manually selected a match, or the query would have
1189    // stopped.  So the default match must be the desired selection.
1190    *match = *result.default_match();
1191  } else {
1192    CHECK(popup_->IsOpen());
1193    // If there are no results, the popup should be closed (so we should have
1194    // failed the CHECK above), and URLsForDefaultMatch() should have been
1195    // called instead.
1196    CHECK(!result.empty());
1197    CHECK(popup_->selected_line() < result.size());
1198    *match = result.match_at(popup_->selected_line());
1199  }
1200  if (alternate_nav_url && popup_->manually_selected_match().empty())
1201    *alternate_nav_url = result.alternate_nav_url();
1202}
1203
1204void OmniboxEditModel::GetInfoForCurrentText(AutocompleteMatch* match,
1205                                             GURL* alternate_nav_url) const {
1206  // If there's temporary text and it has been set by Instant, we won't find it
1207  // in the popup model, so classify the text anew.
1208  if ((popup_->IsOpen() || query_in_progress()) &&
1209      !is_temporary_text_set_by_instant_) {
1210    InfoForCurrentSelection(match, alternate_nav_url);
1211  } else {
1212    AutocompleteClassifierFactory::GetForProfile(profile_)->Classify(
1213        UserTextFromDisplayText(view_->GetText()), KeywordIsSelected(), true,
1214        match, alternate_nav_url);
1215  }
1216}
1217
1218void OmniboxEditModel::RevertTemporaryText(bool revert_popup) {
1219  // The user typed something, then selected a different item.  Restore the
1220  // text they typed and change back to the default item.
1221  // NOTE: This purposefully does not reset paste_state_.
1222  bool notify_instant = is_temporary_text_set_by_instant_;
1223  just_deleted_text_ = false;
1224  has_temporary_text_ = false;
1225  is_temporary_text_set_by_instant_ = false;
1226
1227  InstantController* instant = controller_->GetInstant();
1228  if (instant && notify_instant) {
1229    // Normally, popup_->ResetToDefaultMatch() will cause the view text to be
1230    // updated. In Instant Extended mode however, the popup_ is not used, so it
1231    // won't do anything. So, update the view ourselves. Even if Instant is not
1232    // in extended mode (i.e., it's enabled in non-extended mode, or disabled
1233    // altogether), this is okay to do, since the call to
1234    // popup_->ResetToDefaultMatch() will just override whatever we do here.
1235    //
1236    // The two "false" arguments make sure that our shenanigans don't cause any
1237    // previously saved selection to be erased nor OnChanged() to be called.
1238    view_->OnTemporaryTextMaybeChanged(user_text_ + inline_autocomplete_text_,
1239        false, false);
1240    AutocompleteResult::const_iterator match(result().default_match());
1241    instant->OnCancel(match != result().end() ? *match : AutocompleteMatch(),
1242                      user_text_,
1243                      user_text_ + inline_autocomplete_text_);
1244  }
1245  if (revert_popup)
1246    popup_->ResetToDefaultMatch();
1247  view_->OnRevertTemporaryText();
1248}
1249
1250bool OmniboxEditModel::MaybeAcceptKeywordBySpace(const string16& new_text) {
1251  size_t keyword_length = new_text.length() - 1;
1252  return (paste_state_ == NONE) && is_keyword_hint_ && !keyword_.empty() &&
1253      inline_autocomplete_text_.empty() &&
1254      (keyword_.length() == keyword_length) &&
1255      IsSpaceCharForAcceptingKeyword(new_text[keyword_length]) &&
1256      !new_text.compare(0, keyword_length, keyword_, 0, keyword_length) &&
1257      AcceptKeyword();
1258}
1259
1260bool OmniboxEditModel::CreatedKeywordSearchByInsertingSpaceInMiddle(
1261    const string16& old_text,
1262    const string16& new_text,
1263    size_t caret_position) const {
1264  DCHECK_GE(new_text.length(), caret_position);
1265
1266  // Check simple conditions first.
1267  if ((paste_state_ != NONE) || (caret_position < 2) ||
1268      (old_text.length() < caret_position) ||
1269      (new_text.length() == caret_position))
1270    return false;
1271  size_t space_position = caret_position - 1;
1272  if (!IsSpaceCharForAcceptingKeyword(new_text[space_position]) ||
1273      IsWhitespace(new_text[space_position - 1]) ||
1274      new_text.compare(0, space_position, old_text, 0, space_position) ||
1275      !new_text.compare(space_position, new_text.length() - space_position,
1276                        old_text, space_position,
1277                        old_text.length() - space_position)) {
1278    return false;
1279  }
1280
1281  // Then check if the text before the inserted space matches a keyword.
1282  string16 keyword;
1283  TrimWhitespace(new_text.substr(0, space_position), TRIM_LEADING, &keyword);
1284  // TODO(sreeram): Once the Instant extended API supports keywords properly,
1285  // keyword_provider() should never be NULL. Remove that clause.
1286  return !keyword.empty() && autocomplete_controller_->keyword_provider() &&
1287      !autocomplete_controller_->keyword_provider()->
1288          GetKeywordForText(keyword).empty();
1289}
1290
1291bool OmniboxEditModel::DoInstant(const AutocompleteMatch& match) {
1292  InstantController* instant = controller_->GetInstant();
1293  if (!instant || in_revert_)
1294    return false;
1295
1296  // Don't call Update() if the change is a result of a
1297  // INSTANT_COMPLETE_REPLACE instant suggestion.
1298  if (has_temporary_text_ && is_temporary_text_set_by_instant_)
1299    return false;
1300
1301  // The two pieces of text we want to send Instant, viz., what the user has
1302  // typed, and the full omnibox text including any inline autocompletion.
1303  string16 user_text = has_temporary_text_ ?
1304      match.fill_into_edit : DisplayTextFromUserText(user_text_);
1305  string16 full_text = view_->GetText();
1306
1307  // Remove "?" if we're in forced query mode.
1308  AutocompleteInput::RemoveForcedQueryStringIfNecessary(
1309      autocomplete_controller_->input().type(), &user_text);
1310  AutocompleteInput::RemoveForcedQueryStringIfNecessary(
1311      autocomplete_controller_->input().type(), &full_text);
1312
1313  size_t start, end;
1314  view_->GetSelectionBounds(&start, &end);
1315
1316  return instant->Update(match, user_text, full_text, start, end,
1317      UseVerbatimInstant(), user_input_in_progress_, popup_->IsOpen(),
1318      in_escape_handler_, KeywordIsSelected());
1319}
1320
1321void OmniboxEditModel::DoPreconnect(const AutocompleteMatch& match) {
1322  if (!match.destination_url.SchemeIs(extensions::kExtensionScheme)) {
1323    // Warm up DNS Prefetch cache, or preconnect to a search service.
1324    UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type,
1325                              AutocompleteMatch::NUM_TYPES);
1326    if (profile_->GetNetworkPredictor()) {
1327      profile_->GetNetworkPredictor()->AnticipateOmniboxUrl(
1328          match.destination_url,
1329          AutocompleteActionPredictor::IsPreconnectable(match));
1330    }
1331    // We could prefetch the alternate nav URL, if any, but because there
1332    // can be many of these as a user types an initial series of characters,
1333    // the OS DNS cache could suffer eviction problems for minimal gain.
1334  }
1335}
1336
1337//  static
1338bool OmniboxEditModel::IsSpaceCharForAcceptingKeyword(wchar_t c) {
1339  switch (c) {
1340    case 0x0020:  // Space
1341    case 0x3000:  // Ideographic Space
1342      return true;
1343    default:
1344      return false;
1345  }
1346}
1347
1348metrics::OmniboxEventProto::PageClassification
1349    OmniboxEditModel::ClassifyPage(const GURL& gurl) const {
1350  if (!gurl.is_valid())
1351    return metrics::OmniboxEventProto_PageClassification_INVALID_SPEC;
1352  const std::string& url = gurl.spec();
1353  if (url == chrome::kChromeUINewTabURL)
1354    return metrics::OmniboxEventProto_PageClassification_NEW_TAB_PAGE;
1355  if (url == chrome::kAboutBlankURL)
1356    return metrics::OmniboxEventProto_PageClassification_BLANK;
1357  if (url == profile()->GetPrefs()->GetString(prefs::kHomePage))
1358    return metrics::OmniboxEventProto_PageClassification_HOMEPAGE;
1359  return metrics::OmniboxEventProto_PageClassification_OTHER;
1360}
1361
1362void OmniboxEditModel::ClassifyStringForPasteAndGo(
1363    const string16& text,
1364    AutocompleteMatch* match,
1365    GURL* alternate_nav_url) const {
1366  DCHECK(match);
1367  AutocompleteClassifierFactory::GetForProfile(profile_)->Classify(text,
1368      false, false, match, alternate_nav_url);
1369}
1370
1371void OmniboxEditModel::SetFocusState(OmniboxFocusState state,
1372                                     OmniboxFocusChangeReason reason) {
1373  if (state == focus_state_)
1374    return;
1375
1376  InstantController* instant = controller_->GetInstant();
1377  if (instant)
1378    instant->OmniboxFocusChanged(state, reason, NULL);
1379
1380  // Update state and notify view if the omnibox has focus and the caret
1381  // visibility changed.
1382  const bool was_caret_visible = is_caret_visible();
1383  focus_state_ = state;
1384  if (focus_state_ != OMNIBOX_FOCUS_NONE &&
1385      is_caret_visible() != was_caret_visible)
1386    view_->ApplyCaretVisibility();
1387}
1388