autocomplete_controller.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org// Use of this source code is governed by a BSD-style license that can be
3179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org// found in the LICENSE file.
4179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org
5179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/autocomplete_controller.h"
6179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org
7179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include <set>
8179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include <string>
9179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org
10179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "base/command_line.h"
11179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "base/format_macros.h"
12179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "base/logging.h"
13fbd97aa4c5325eace57d24b89845b9581bac9324jorlow@chromium.org#include "base/metrics/histogram.h"
14fbd97aa4c5325eace57d24b89845b9581bac9324jorlow@chromium.org#include "base/strings/string_number_conversions.h"
15179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "base/strings/stringprintf.h"
16179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "base/time/time.h"
17179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/autocomplete_controller_delegate.h"
18179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/bookmark_provider.h"
19179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/builtin_provider.h"
20179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/extension_app_provider.h"
21179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/history_quick_provider.h"
22179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/history_url_provider.h"
23179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/keyword_provider.h"
24179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/search_provider.h"
25179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "chrome/browser/autocomplete/shortcuts_provider.h"
26f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/autocomplete/zero_suggest_provider.h"
27f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/chrome_notification_types.h"
28f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/omnibox/omnibox_field_trial.h"
29f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/profiles/profile.h"
30f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/search/search.h"
31f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/browser/search_engines/template_url.h"
32f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "chrome/common/chrome_switches.h"
33179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "content/public/browser/notification_service.h"
34179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "grit/generated_resources.h"
35f85ede82f8c27a00c3120f67fbab89b2a89fe987jorlow@chromium.org#include "grit/theme_resources.h"
36179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org#include "ui/base/l10n/l10n_util.h"
37179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org
3899a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com#if defined(OS_CHROMEOS)
3999a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com#include "chrome/browser/autocomplete/contact_provider_chromeos.h"
4099a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com#include "chrome/browser/chromeos/contacts/contact_manager.h"
4199a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com#endif
4299a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com
4399a7585544fc162a5f8dd39a6add00776a981efesanjay@google.comnamespace {
4499a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com
4599a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com// Converts the given match to a type (and possibly subtype) based on the AQS
4699a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com// specification. For more details, see
47179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org// http://goto.google.com/binary-clients-logging.
48179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.orgvoid AutocompleteMatchToAssistedQuery(
49179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org    const AutocompleteMatch::Type& match, size_t* type, size_t* subtype) {
50179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org  // This type indicates a native chrome suggestion.
51179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org  *type = 69;
52179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org  // Default value, indicating no subtype.
53179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org  *subtype = string16::npos;
54179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org
5599a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com  switch (match) {
5699a7585544fc162a5f8dd39a6add00776a981efesanjay@google.com    case AutocompleteMatchType::SEARCH_SUGGEST: {
57179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org      *type = 0;
58179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org      return;
5945b9940be332834440bd5299419f396e38085ebehans@chromium.org    }
60179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org    case AutocompleteMatchType::NAVSUGGEST: {
61179be588c25dccaa963df9c9c104fc6229435483jorlow@chromium.org      *type = 5;
62      return;
63    }
64    case AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED: {
65      *subtype = 57;
66      return;
67    }
68    case AutocompleteMatchType::URL_WHAT_YOU_TYPED: {
69      *subtype = 58;
70      return;
71    }
72    case AutocompleteMatchType::SEARCH_HISTORY: {
73      *subtype = 59;
74      return;
75    }
76    case AutocompleteMatchType::HISTORY_URL: {
77      *subtype = 60;
78      return;
79    }
80    case AutocompleteMatchType::HISTORY_TITLE: {
81      *subtype = 61;
82      return;
83    }
84    case AutocompleteMatchType::HISTORY_BODY: {
85      *subtype = 62;
86      return;
87    }
88    case AutocompleteMatchType::HISTORY_KEYWORD: {
89      *subtype = 63;
90      return;
91    }
92    case AutocompleteMatchType::BOOKMARK_TITLE: {
93      *subtype = 65;
94      return;
95    }
96    default: {
97      // This value indicates a native chrome suggestion with no named subtype
98      // (yet).
99      *subtype = 64;
100    }
101  }
102}
103
104// Appends available autocompletion of the given type, subtype, and number to
105// the existing available autocompletions string, encoding according to the
106// spec.
107void AppendAvailableAutocompletion(size_t type,
108                                   size_t subtype,
109                                   int count,
110                                   std::string* autocompletions) {
111  if (!autocompletions->empty())
112    autocompletions->append("j");
113  base::StringAppendF(autocompletions, "%" PRIuS, type);
114  // Subtype is optional - string16::npos indicates no subtype.
115  if (subtype != string16::npos)
116    base::StringAppendF(autocompletions, "i%" PRIuS, subtype);
117  if (count > 1)
118    base::StringAppendF(autocompletions, "l%d", count);
119}
120
121// Returns whether the autocompletion is trivial enough that we consider it
122// an autocompletion for which the omnibox autocompletion code did not add
123// any value.
124bool IsTrivialAutocompletion(const AutocompleteMatch& match) {
125  return match.type == AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED ||
126      match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED ||
127      match.type == AutocompleteMatchType::SEARCH_OTHER_ENGINE;
128}
129
130}  // namespace
131
132const int AutocompleteController::kNoItemSelected = -1;
133
134AutocompleteController::AutocompleteController(
135    Profile* profile,
136    AutocompleteControllerDelegate* delegate,
137    int provider_types)
138    : delegate_(delegate),
139      history_url_provider_(NULL),
140      keyword_provider_(NULL),
141      search_provider_(NULL),
142      zero_suggest_provider_(NULL),
143      in_stop_timer_field_trial_(
144          OmniboxFieldTrial::InStopTimerFieldTrialExperimentGroup()),
145      done_(true),
146      in_start_(false),
147      in_zero_suggest_(false),
148      profile_(profile) {
149  // AND with the disabled providers, if any.
150  provider_types &= ~OmniboxFieldTrial::GetDisabledProviderTypes();
151  bool use_hqp = !!(provider_types & AutocompleteProvider::TYPE_HISTORY_QUICK);
152  // TODO(mrossetti): Permanently modify the HistoryURLProvider to not search
153  // titles once HQP is turned on permanently.
154  // History quick provider can be used on all platforms other than Android.
155  // TODO(jcivelli): Enable the History Quick Provider and figure out why it
156  // reports the wrong results for some pages.
157#if defined(OS_ANDROID)
158  use_hqp = false;
159#endif
160
161  if (provider_types & AutocompleteProvider::TYPE_BUILTIN)
162    providers_.push_back(new BuiltinProvider(this, profile));
163#if defined(OS_CHROMEOS)
164  if (provider_types & AutocompleteProvider::TYPE_CONTACT)
165    providers_.push_back(new ContactProvider(this, profile,
166        contacts::ContactManager::GetInstance()->GetWeakPtr()));
167#endif
168  if (provider_types & AutocompleteProvider::TYPE_EXTENSION_APP)
169    providers_.push_back(new ExtensionAppProvider(this, profile));
170  if (use_hqp)
171    providers_.push_back(new HistoryQuickProvider(this, profile));
172  if (provider_types & AutocompleteProvider::TYPE_HISTORY_URL) {
173    history_url_provider_ = new HistoryURLProvider(this, profile);
174    providers_.push_back(history_url_provider_);
175  }
176  // Search provider/"tab to search" can be used on all platforms other than
177  // Android.
178#if !defined(OS_ANDROID)
179  if (provider_types & AutocompleteProvider::TYPE_KEYWORD) {
180    keyword_provider_ = new KeywordProvider(this, profile);
181    providers_.push_back(keyword_provider_);
182  }
183#endif
184  if (provider_types & AutocompleteProvider::TYPE_SEARCH) {
185    search_provider_ = new SearchProvider(this, profile);
186    providers_.push_back(search_provider_);
187  }
188  if (provider_types & AutocompleteProvider::TYPE_SHORTCUTS)
189    providers_.push_back(new ShortcutsProvider(this, profile));
190
191  // Create ZeroSuggest if it is enabled.
192  if (provider_types & AutocompleteProvider::TYPE_ZERO_SUGGEST) {
193    zero_suggest_provider_ = ZeroSuggestProvider::Create(this, profile);
194    if (zero_suggest_provider_)
195      providers_.push_back(zero_suggest_provider_);
196  }
197
198  if ((provider_types & AutocompleteProvider::TYPE_BOOKMARK) &&
199      !CommandLine::ForCurrentProcess()->HasSwitch(
200          switches::kDisableBookmarkAutocompleteProvider))
201    providers_.push_back(new BookmarkProvider(this, profile));
202
203  for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)
204    (*i)->AddRef();
205}
206
207AutocompleteController::~AutocompleteController() {
208  // The providers may have tasks outstanding that hold refs to them.  We need
209  // to ensure they won't call us back if they outlive us.  (Practically,
210  // calling Stop() should also cancel those tasks and make it so that we hold
211  // the only refs.)  We also don't want to bother notifying anyone of our
212  // result changes here, because the notification observer is in the midst of
213  // shutdown too, so we don't ask Stop() to clear |result_| (and notify).
214  result_.Reset();  // Not really necessary.
215  Stop(false);
216
217  for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)
218    (*i)->Release();
219
220  providers_.clear();  // Not really necessary.
221}
222
223void AutocompleteController::Start(const AutocompleteInput& input) {
224  const string16 old_input_text(input_.text());
225  const AutocompleteInput::MatchesRequested old_matches_requested =
226      input_.matches_requested();
227  input_ = input;
228
229  // See if we can avoid rerunning autocomplete when the query hasn't changed
230  // much.  When the user presses or releases the ctrl key, the desired_tld
231  // changes, and when the user finishes an IME composition, inline autocomplete
232  // may no longer be prevented.  In both these cases the text itself hasn't
233  // changed since the last query, and some providers can do much less work (and
234  // get matches back more quickly).  Taking advantage of this reduces flicker.
235  //
236  // NOTE: This comes after constructing |input_| above since that construction
237  // can change the text string (e.g. by stripping off a leading '?').
238  const bool minimal_changes = (input_.text() == old_input_text) &&
239      (input_.matches_requested() == old_matches_requested);
240
241  expire_timer_.Stop();
242  stop_timer_.Stop();
243
244  // Start the new query.
245  in_zero_suggest_ = false;
246  in_start_ = true;
247  base::TimeTicks start_time = base::TimeTicks::Now();
248  for (ACProviders::iterator i(providers_.begin()); i != providers_.end();
249       ++i) {
250    // TODO(mpearson): Remove timing code once bugs 178705 / 237703 / 168933
251    // are resolved.
252    base::TimeTicks provider_start_time = base::TimeTicks::Now();
253    (*i)->Start(input_, minimal_changes);
254    if (input.matches_requested() != AutocompleteInput::ALL_MATCHES)
255      DCHECK((*i)->done());
256    base::TimeTicks provider_end_time = base::TimeTicks::Now();
257    std::string name = std::string("Omnibox.ProviderTime.") + (*i)->GetName();
258    base::HistogramBase* counter = base::Histogram::FactoryGet(
259        name, 1, 5000, 20, base::Histogram::kUmaTargetedHistogramFlag);
260    counter->Add(static_cast<int>(
261        (provider_end_time - provider_start_time).InMilliseconds()));
262  }
263  if (input.matches_requested() == AutocompleteInput::ALL_MATCHES &&
264      (input.text().length() < 6)) {
265    base::TimeTicks end_time = base::TimeTicks::Now();
266    std::string name = "Omnibox.QueryTime." + base::IntToString(
267        input.text().length());
268    base::HistogramBase* counter = base::Histogram::FactoryGet(
269        name, 1, 1000, 50, base::Histogram::kUmaTargetedHistogramFlag);
270    counter->Add(static_cast<int>((end_time - start_time).InMilliseconds()));
271  }
272  in_start_ = false;
273  CheckIfDone();
274  // The second true forces saying the default match has changed.
275  // This triggers the edit model to update things such as the inline
276  // autocomplete state.  In particular, if the user has typed a key
277  // since the last notification, and we're now re-running
278  // autocomplete, then we need to update the inline autocompletion
279  // even if the current match is for the same URL as the last run's
280  // default match.  Likewise, the controller doesn't know what's
281  // happened in the edit since the last time it ran autocomplete.
282  // The user might have selected all the text and hit delete, then
283  // typed a new character.  The selection and delete won't send any
284  // signals to the controller so it doesn't realize that anything was
285  // cleared or changed.  Even if the default match hasn't changed, we
286  // need the edit model to update the display.
287  UpdateResult(false, true);
288
289  if (!done_) {
290    StartExpireTimer();
291    StartStopTimer();
292  }
293}
294
295void AutocompleteController::Stop(bool clear_result) {
296  for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end();
297       ++i) {
298    (*i)->Stop(clear_result);
299  }
300
301  expire_timer_.Stop();
302  stop_timer_.Stop();
303  done_ = true;
304  if (clear_result && !result_.empty()) {
305    result_.Reset();
306    // NOTE: We pass in false since we're trying to only clear the popup, not
307    // touch the edit... this is all a mess and should be cleaned up :(
308    NotifyChanged(false);
309  }
310}
311
312void AutocompleteController::StartZeroSuggest(const GURL& url,
313                                              const string16& permanent_text) {
314  if (zero_suggest_provider_ != NULL) {
315    DCHECK(!in_start_);  // We should not be already running a query.
316    in_zero_suggest_ = true;
317    zero_suggest_provider_->StartZeroSuggest(url, permanent_text);
318  }
319}
320
321void AutocompleteController::StopZeroSuggest() {
322  if (zero_suggest_provider_ != NULL) {
323    DCHECK(!in_start_);  // We should not be already running a query.
324    zero_suggest_provider_->Stop(false);
325  }
326}
327
328void AutocompleteController::DeleteMatch(const AutocompleteMatch& match) {
329  DCHECK(match.deletable);
330  match.provider->DeleteMatch(match);  // This may synchronously call back to
331                                       // OnProviderUpdate().
332  // If DeleteMatch resulted in a callback to OnProviderUpdate and we're
333  // not done, we might attempt to redisplay the deleted match. Make sure
334  // we aren't displaying it by removing any old entries.
335  ExpireCopiedEntries();
336}
337
338void AutocompleteController::ExpireCopiedEntries() {
339  // The first true makes UpdateResult() clear out the results and
340  // regenerate them, thus ensuring that no results from the previous
341  // result set remain.
342  UpdateResult(true, false);
343}
344
345void AutocompleteController::OnProviderUpdate(bool updated_matches) {
346  if (in_zero_suggest_) {
347    // We got ZeroSuggest results before Start(). Show only those results,
348    // because results from other providers are stale.
349    result_.Reset();
350    result_.AppendMatches(zero_suggest_provider_->matches());
351    result_.SortAndCull(input_, profile_);
352    NotifyChanged(true);
353  } else {
354    CheckIfDone();
355    // Multiple providers may provide synchronous results, so we only update the
356    // results if we're not in Start().
357    if (!in_start_ && (updated_matches || done_))
358      UpdateResult(false, false);
359  }
360}
361
362void AutocompleteController::AddProvidersInfo(
363    ProvidersInfo* provider_info) const {
364  provider_info->clear();
365  for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end();
366       ++i) {
367    // Add per-provider info, if any.
368    (*i)->AddProviderInfo(provider_info);
369
370    // This is also a good place to put code to add info that you want to
371    // add for every provider.
372  }
373}
374
375void AutocompleteController::ResetSession() {
376  for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end();
377       ++i)
378    (*i)->ResetSession();
379  in_zero_suggest_ = false;
380}
381
382void AutocompleteController::UpdateResult(
383    bool regenerate_result,
384    bool force_notify_default_match_changed) {
385  const bool last_default_was_valid = result_.default_match() != result_.end();
386  // The following three variables are only set and used if
387  // |last_default_was_valid|.
388  string16 last_default_fill_into_edit, last_default_keyword,
389      last_default_associated_keyword;
390  if (last_default_was_valid) {
391    last_default_fill_into_edit = result_.default_match()->fill_into_edit;
392    last_default_keyword = result_.default_match()->keyword;
393    if (result_.default_match()->associated_keyword != NULL)
394      last_default_associated_keyword =
395          result_.default_match()->associated_keyword->keyword;
396  }
397
398  if (regenerate_result)
399    result_.Reset();
400
401  AutocompleteResult last_result;
402  last_result.Swap(&result_);
403
404  for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end();
405       ++i)
406    result_.AppendMatches((*i)->matches());
407
408  // Sort the matches and trim to a small number of "best" matches.
409  result_.SortAndCull(input_, profile_);
410
411  // Need to validate before invoking CopyOldMatches as the old matches are not
412  // valid against the current input.
413#ifndef NDEBUG
414  result_.Validate();
415#endif
416
417  if (!done_) {
418    // This conditional needs to match the conditional in Start that invokes
419    // StartExpireTimer.
420    result_.CopyOldMatches(input_, last_result, profile_);
421  }
422
423  UpdateKeywordDescriptions(&result_);
424  UpdateAssociatedKeywords(&result_);
425  UpdateAssistedQueryStats(&result_);
426
427  const bool default_is_valid = result_.default_match() != result_.end();
428  string16 default_associated_keyword;
429  if (default_is_valid &&
430      (result_.default_match()->associated_keyword != NULL)) {
431    default_associated_keyword =
432        result_.default_match()->associated_keyword->keyword;
433  }
434  // We've gotten async results. Send notification that the default match
435  // updated if fill_into_edit, associated_keyword, or keyword differ.  (The
436  // second can change if we've just started Chrome and the keyword database
437  // finishes loading while processing this request.  The third can change
438  // if we swapped from interpreting the input as a search--which gets
439  // labeled with the default search provider's keyword--to a URL.)
440  // We don't check the URL as that may change for the default match
441  // even though the fill into edit hasn't changed (see SearchProvider
442  // for one case of this).
443  const bool notify_default_match =
444      (last_default_was_valid != default_is_valid) ||
445      (last_default_was_valid &&
446       ((result_.default_match()->fill_into_edit !=
447          last_default_fill_into_edit) ||
448        (default_associated_keyword != last_default_associated_keyword) ||
449        (result_.default_match()->keyword != last_default_keyword)));
450  if (notify_default_match)
451    last_time_default_match_changed_ = base::TimeTicks::Now();
452
453  NotifyChanged(force_notify_default_match_changed || notify_default_match);
454}
455
456void AutocompleteController::UpdateAssociatedKeywords(
457    AutocompleteResult* result) {
458  if (!keyword_provider_)
459    return;
460
461  std::set<string16> keywords;
462  for (ACMatches::iterator match(result->begin()); match != result->end();
463       ++match) {
464    string16 keyword(match->GetSubstitutingExplicitlyInvokedKeyword(profile_));
465    if (!keyword.empty()) {
466      keywords.insert(keyword);
467      continue;
468    }
469
470    // Only add the keyword if the match does not have a duplicate keyword with
471    // a more relevant match.
472    keyword = match->associated_keyword.get() ?
473        match->associated_keyword->keyword :
474        keyword_provider_->GetKeywordForText(match->fill_into_edit);
475    if (!keyword.empty() && !keywords.count(keyword)) {
476      keywords.insert(keyword);
477
478      if (!match->associated_keyword.get())
479        match->associated_keyword.reset(new AutocompleteMatch(
480            keyword_provider_->CreateAutocompleteMatch(match->fill_into_edit,
481                                                       keyword, input_)));
482    } else {
483      match->associated_keyword.reset();
484    }
485  }
486}
487
488void AutocompleteController::UpdateAssistedQueryStats(
489    AutocompleteResult* result) {
490  if (result->empty())
491    return;
492
493  // Build the impressions string (the AQS part after ".").
494  std::string autocompletions;
495  int count = 0;
496  size_t last_type = string16::npos;
497  size_t last_subtype = string16::npos;
498  for (ACMatches::iterator match(result->begin()); match != result->end();
499       ++match) {
500    size_t type = string16::npos;
501    size_t subtype = string16::npos;
502    AutocompleteMatchToAssistedQuery(match->type, &type, &subtype);
503    if (last_type != string16::npos &&
504        (type != last_type || subtype != last_subtype)) {
505      AppendAvailableAutocompletion(
506          last_type, last_subtype, count, &autocompletions);
507      count = 1;
508    } else {
509      count++;
510    }
511    last_type = type;
512    last_subtype = subtype;
513  }
514  AppendAvailableAutocompletion(
515      last_type, last_subtype, count, &autocompletions);
516  // Go over all matches and set AQS if the match supports it.
517  for (size_t index = 0; index < result->size(); ++index) {
518    AutocompleteMatch* match = result->match_at(index);
519    const TemplateURL* template_url = match->GetTemplateURL(profile_, false);
520    if (!template_url || !match->search_terms_args.get())
521      continue;
522    std::string selected_index;
523    // Prevent trivial suggestions from getting credit for being selected.
524    if (!IsTrivialAutocompletion(*match))
525      selected_index = base::StringPrintf("%" PRIuS, index);
526    match->search_terms_args->assisted_query_stats =
527        base::StringPrintf("chrome.%s.%s",
528                           selected_index.c_str(),
529                           autocompletions.c_str());
530    match->destination_url = GURL(template_url->url_ref().ReplaceSearchTerms(
531        *match->search_terms_args));
532  }
533}
534
535GURL AutocompleteController::GetDestinationURL(
536    const AutocompleteMatch& match,
537    base::TimeDelta query_formulation_time) const {
538  GURL destination_url(match.destination_url);
539  TemplateURL* template_url = match.GetTemplateURL(profile_, false);
540
541  // Append the query formulation time (time from when the user first typed a
542  // character into the omnibox to when the user selected a query) and whether
543  // a field trial has triggered to the AQS parameter, if other AQS parameters
544  // were already populated.
545  if (template_url && match.search_terms_args.get() &&
546      !match.search_terms_args->assisted_query_stats.empty()) {
547    TemplateURLRef::SearchTermsArgs search_terms_args(*match.search_terms_args);
548    search_terms_args.assisted_query_stats += base::StringPrintf(
549        ".%" PRId64 "j%d",
550        query_formulation_time.InMilliseconds(),
551        search_provider_ &&
552        search_provider_->field_trial_triggered_in_session());
553    destination_url = GURL(template_url->url_ref().
554                           ReplaceSearchTerms(search_terms_args));
555  }
556  return destination_url;
557}
558
559void AutocompleteController::UpdateKeywordDescriptions(
560    AutocompleteResult* result) {
561  string16 last_keyword;
562  for (AutocompleteResult::iterator i(result->begin()); i != result->end();
563       ++i) {
564    if ((i->provider->type() == AutocompleteProvider::TYPE_KEYWORD &&
565         !i->keyword.empty()) ||
566        (i->provider->type() == AutocompleteProvider::TYPE_SEARCH &&
567         AutocompleteMatch::IsSearchType(i->type))) {
568      i->description.clear();
569      i->description_class.clear();
570      DCHECK(!i->keyword.empty());
571      if (i->keyword != last_keyword) {
572        const TemplateURL* template_url = i->GetTemplateURL(profile_, false);
573        if (template_url) {
574          // For extension keywords, just make the description the extension
575          // name -- don't assume that the normal search keyword description is
576          // applicable.
577          i->description = template_url->AdjustedShortNameForLocaleDirection();
578          if (!template_url->IsExtensionKeyword()) {
579            i->description = l10n_util::GetStringFUTF16(
580                IDS_AUTOCOMPLETE_SEARCH_DESCRIPTION, i->description);
581          }
582          i->description_class.push_back(
583              ACMatchClassification(0, ACMatchClassification::DIM));
584        }
585        last_keyword = i->keyword;
586      }
587    } else {
588      last_keyword.clear();
589    }
590  }
591}
592
593void AutocompleteController::NotifyChanged(bool notify_default_match) {
594  if (delegate_)
595    delegate_->OnResultChanged(notify_default_match);
596  if (done_) {
597    content::NotificationService::current()->Notify(
598        chrome::NOTIFICATION_AUTOCOMPLETE_CONTROLLER_RESULT_READY,
599        content::Source<AutocompleteController>(this),
600        content::NotificationService::NoDetails());
601  }
602}
603
604void AutocompleteController::CheckIfDone() {
605  for (ACProviders::const_iterator i(providers_.begin()); i != providers_.end();
606       ++i) {
607    if (!(*i)->done()) {
608      done_ = false;
609      return;
610    }
611  }
612  done_ = true;
613}
614
615void AutocompleteController::StartExpireTimer() {
616  // Amount of time (in ms) between when the user stops typing and
617  // when we remove any copied entries. We do this from the time the
618  // user stopped typing as some providers (such as SearchProvider)
619  // wait for the user to stop typing before they initiate a query.
620  const int kExpireTimeMS = 500;
621
622  if (result_.HasCopiedMatches())
623    expire_timer_.Start(FROM_HERE,
624                        base::TimeDelta::FromMilliseconds(kExpireTimeMS),
625                        this, &AutocompleteController::ExpireCopiedEntries);
626}
627
628void AutocompleteController::StartStopTimer() {
629  if (!in_stop_timer_field_trial_)
630    return;
631
632  // Amount of time (in ms) between when the user stops typing and
633  // when we send Stop() to every provider.  This is intended to avoid
634  // the disruptive effect of belated omnibox updates, updates that
635  // come after the user has had to time to read the whole dropdown
636  // and doesn't expect it to change.
637  const int kStopTimeMS = 1500;
638  stop_timer_.Start(FROM_HERE,
639                    base::TimeDelta::FromMilliseconds(kStopTimeMS),
640                    base::Bind(&AutocompleteController::Stop,
641                               base::Unretained(this),
642                               false));
643}
644