• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 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/predictors/autocomplete_action_predictor.h"
6 
7 #include <math.h>
8 
9 #include <vector>
10 
11 #include "base/bind.h"
12 #include "base/guid.h"
13 #include "base/i18n/case_conversion.h"
14 #include "base/metrics/histogram.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/autocomplete/autocomplete_match.h"
19 #include "chrome/browser/autocomplete/autocomplete_result.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/history/history_notifications.h"
22 #include "chrome/browser/history/history_service.h"
23 #include "chrome/browser/history/history_service_factory.h"
24 #include "chrome/browser/history/in_memory_database.h"
25 #include "chrome/browser/omnibox/omnibox_log.h"
26 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h"
27 #include "chrome/browser/predictors/predictor_database.h"
28 #include "chrome/browser/predictors/predictor_database_factory.h"
29 #include "chrome/browser/prerender/prerender_field_trial.h"
30 #include "chrome/browser/prerender/prerender_handle.h"
31 #include "chrome/browser/prerender/prerender_manager.h"
32 #include "chrome/browser/prerender/prerender_manager_factory.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_details.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/notification_source.h"
38 
39 namespace {
40 
41 const float kConfidenceCutoff[] = {
42   0.8f,
43   0.5f
44 };
45 
46 COMPILE_ASSERT(arraysize(kConfidenceCutoff) ==
47                predictors::AutocompleteActionPredictor::LAST_PREDICT_ACTION,
48                ConfidenceCutoff_count_mismatch);
49 
50 const size_t kMinimumUserTextLength = 1;
51 const int kMinimumNumberOfHits = 3;
52 
53 enum DatabaseAction {
54   DATABASE_ACTION_ADD,
55   DATABASE_ACTION_UPDATE,
56   DATABASE_ACTION_DELETE_SOME,
57   DATABASE_ACTION_DELETE_ALL,
58   DATABASE_ACTION_COUNT
59 };
60 
61 }  // namespace
62 
63 namespace predictors {
64 
65 const int AutocompleteActionPredictor::kMaximumDaysToKeepEntry = 14;
66 
AutocompleteActionPredictor(Profile * profile)67 AutocompleteActionPredictor::AutocompleteActionPredictor(Profile* profile)
68     : profile_(profile),
69       main_profile_predictor_(NULL),
70       incognito_predictor_(NULL),
71       initialized_(false) {
72   if (profile_->IsOffTheRecord()) {
73     main_profile_predictor_ = AutocompleteActionPredictorFactory::GetForProfile(
74         profile_->GetOriginalProfile());
75     DCHECK(main_profile_predictor_);
76     main_profile_predictor_->incognito_predictor_ = this;
77     if (main_profile_predictor_->initialized_)
78       CopyFromMainProfile();
79   } else {
80     // Request the in-memory database from the history to force it to load so
81     // it's available as soon as possible.
82     HistoryService* history_service = HistoryServiceFactory::GetForProfile(
83         profile_, Profile::EXPLICIT_ACCESS);
84     if (history_service)
85       history_service->InMemoryDatabase();
86 
87     table_ =
88         PredictorDatabaseFactory::GetForProfile(profile_)->autocomplete_table();
89 
90     // Observe all main frame loads so we can wait for the first to complete
91     // before accessing DB and IO threads to build the local cache.
92     notification_registrar_.Add(this,
93                                 content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
94                                 content::NotificationService::AllSources());
95   }
96 }
97 
~AutocompleteActionPredictor()98 AutocompleteActionPredictor::~AutocompleteActionPredictor() {
99   if (main_profile_predictor_)
100     main_profile_predictor_->incognito_predictor_ = NULL;
101   else if (incognito_predictor_)
102     incognito_predictor_->main_profile_predictor_ = NULL;
103   if (prerender_handle_.get())
104     prerender_handle_->OnCancel();
105 }
106 
RegisterTransitionalMatches(const base::string16 & user_text,const AutocompleteResult & result)107 void AutocompleteActionPredictor::RegisterTransitionalMatches(
108     const base::string16& user_text,
109     const AutocompleteResult& result) {
110   if (user_text.length() < kMinimumUserTextLength)
111     return;
112   const base::string16 lower_user_text(base::i18n::ToLower(user_text));
113 
114   // Merge this in to an existing match if we already saw |user_text|
115   std::vector<TransitionalMatch>::iterator match_it =
116       std::find(transitional_matches_.begin(), transitional_matches_.end(),
117                 lower_user_text);
118 
119   if (match_it == transitional_matches_.end()) {
120     TransitionalMatch transitional_match;
121     transitional_match.user_text = lower_user_text;
122     match_it = transitional_matches_.insert(transitional_matches_.end(),
123                                             transitional_match);
124   }
125 
126   for (AutocompleteResult::const_iterator i(result.begin()); i != result.end();
127        ++i) {
128     if (std::find(match_it->urls.begin(), match_it->urls.end(),
129                   i->destination_url) == match_it->urls.end()) {
130       match_it->urls.push_back(i->destination_url);
131     }
132   }
133 }
134 
ClearTransitionalMatches()135 void AutocompleteActionPredictor::ClearTransitionalMatches() {
136   transitional_matches_.clear();
137 }
138 
StartPrerendering(const GURL & url,const content::SessionStorageNamespaceMap & session_storage_namespace_map,const gfx::Size & size)139 void AutocompleteActionPredictor::StartPrerendering(
140     const GURL& url,
141     const content::SessionStorageNamespaceMap& session_storage_namespace_map,
142     const gfx::Size& size) {
143   // Only cancel the old prerender after starting the new one, so if the URLs
144   // are the same, the underlying prerender will be reused.
145   scoped_ptr<prerender::PrerenderHandle> old_prerender_handle(
146       prerender_handle_.release());
147   if (prerender::PrerenderManager* prerender_manager =
148           prerender::PrerenderManagerFactory::GetForProfile(profile_)) {
149     content::SessionStorageNamespace* session_storage_namespace = NULL;
150     content::SessionStorageNamespaceMap::const_iterator it =
151         session_storage_namespace_map.find(std::string());
152     if (it != session_storage_namespace_map.end())
153       session_storage_namespace = it->second.get();
154     prerender_handle_.reset(prerender_manager->AddPrerenderFromOmnibox(
155         url, session_storage_namespace, size));
156   }
157   if (old_prerender_handle)
158     old_prerender_handle->OnCancel();
159 }
160 
161 // Given a match, return a recommended action.
162 AutocompleteActionPredictor::Action
RecommendAction(const base::string16 & user_text,const AutocompleteMatch & match) const163     AutocompleteActionPredictor::RecommendAction(
164         const base::string16& user_text,
165         const AutocompleteMatch& match) const {
166   bool is_in_db = false;
167   const double confidence = CalculateConfidence(user_text, match, &is_in_db);
168   DCHECK(confidence >= 0.0 && confidence <= 1.0);
169 
170   UMA_HISTOGRAM_BOOLEAN("AutocompleteActionPredictor.MatchIsInDb", is_in_db);
171 
172   if (is_in_db) {
173     // Multiple enties with the same URL are fine as the confidence may be
174     // different.
175     tracked_urls_.push_back(std::make_pair(match.destination_url, confidence));
176     UMA_HISTOGRAM_COUNTS_100("AutocompleteActionPredictor.Confidence",
177                              confidence * 100);
178   }
179 
180   // Map the confidence to an action.
181   Action action = ACTION_NONE;
182   for (int i = 0; i < LAST_PREDICT_ACTION; ++i) {
183     if (confidence >= kConfidenceCutoff[i]) {
184       action = static_cast<Action>(i);
185       break;
186     }
187   }
188 
189   // Downgrade prerender to preconnect if this is a search match or if omnibox
190   // prerendering is disabled. There are cases when Instant will not handle a
191   // search suggestion and in those cases it would be good to prerender the
192   // search results, however search engines have not been set up to correctly
193   // handle being prerendered and until they are we should avoid it.
194   // http://crbug.com/117495
195   if (action == ACTION_PRERENDER &&
196       (AutocompleteMatch::IsSearchType(match.type) ||
197        !prerender::IsOmniboxEnabled(profile_))) {
198     action = ACTION_PRECONNECT;
199   }
200 
201   return action;
202 }
203 
204 // Return true if the suggestion type warrants a TCP/IP preconnection.
205 // i.e., it is now quite likely that the user will select the related domain.
206 // static
IsPreconnectable(const AutocompleteMatch & match)207 bool AutocompleteActionPredictor::IsPreconnectable(
208     const AutocompleteMatch& match) {
209   return AutocompleteMatch::IsSearchType(match.type);
210 }
211 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)212 void AutocompleteActionPredictor::Observe(
213     int type,
214     const content::NotificationSource& source,
215     const content::NotificationDetails& details) {
216   switch (type) {
217     case content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME:
218       CreateLocalCachesFromDatabase();
219       notification_registrar_.Remove(
220           this,
221           content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
222           content::NotificationService::AllSources());
223       break;
224 
225     case chrome::NOTIFICATION_HISTORY_URLS_DELETED: {
226       DCHECK(initialized_);
227       const content::Details<const history::URLsDeletedDetails>
228           urls_deleted_details =
229               content::Details<const history::URLsDeletedDetails>(details);
230       if (urls_deleted_details->all_history)
231         DeleteAllRows();
232       else
233         DeleteRowsWithURLs(urls_deleted_details->rows);
234       break;
235     }
236 
237     // This notification does not catch all instances of the user navigating
238     // from the Omnibox, but it does catch the cases where the dropdown is open
239     // and those are the events we're most interested in.
240     case chrome::NOTIFICATION_OMNIBOX_OPENED_URL: {
241       DCHECK(initialized_);
242 
243       // TODO(dominich): This doesn't need to be synchronous. Investigate
244       // posting it as a task to be run later.
245       OnOmniboxOpenedUrl(*content::Details<OmniboxLog>(details).ptr());
246       break;
247     }
248 
249     case chrome::NOTIFICATION_HISTORY_LOADED: {
250       TryDeleteOldEntries(content::Details<HistoryService>(details).ptr());
251 
252       notification_registrar_.Remove(this,
253                                      chrome::NOTIFICATION_HISTORY_LOADED,
254                                      content::Source<Profile>(profile_));
255       break;
256     }
257 
258     default:
259       NOTREACHED() << "Unexpected notification observed.";
260       break;
261   }
262 }
263 
CreateLocalCachesFromDatabase()264 void AutocompleteActionPredictor::CreateLocalCachesFromDatabase() {
265   // Create local caches using the database as loaded. We will garbage collect
266   // rows from the caches and the database once the history service is
267   // available.
268   std::vector<AutocompleteActionPredictorTable::Row>* rows =
269       new std::vector<AutocompleteActionPredictorTable::Row>();
270   content::BrowserThread::PostTaskAndReply(content::BrowserThread::DB,
271       FROM_HERE,
272       base::Bind(&AutocompleteActionPredictorTable::GetAllRows, table_, rows),
273       base::Bind(&AutocompleteActionPredictor::CreateCaches, AsWeakPtr(),
274                  base::Owned(rows)));
275 }
276 
DeleteAllRows()277 void AutocompleteActionPredictor::DeleteAllRows() {
278   if (!initialized_)
279     return;
280 
281   db_cache_.clear();
282   db_id_cache_.clear();
283 
284   if (table_.get()) {
285     content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
286         base::Bind(&AutocompleteActionPredictorTable::DeleteAllRows,
287                    table_));
288   }
289 
290   UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
291                             DATABASE_ACTION_DELETE_ALL, DATABASE_ACTION_COUNT);
292 }
293 
DeleteRowsWithURLs(const history::URLRows & rows)294 void AutocompleteActionPredictor::DeleteRowsWithURLs(
295     const history::URLRows& rows) {
296   if (!initialized_)
297     return;
298 
299   std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
300 
301   for (DBCacheMap::iterator it = db_cache_.begin(); it != db_cache_.end();) {
302     if (std::find_if(rows.begin(), rows.end(),
303         history::URLRow::URLRowHasURL(it->first.url)) != rows.end()) {
304       const DBIdCacheMap::iterator id_it = db_id_cache_.find(it->first);
305       DCHECK(id_it != db_id_cache_.end());
306       id_list.push_back(id_it->second);
307       db_id_cache_.erase(id_it);
308       db_cache_.erase(it++);
309     } else {
310       ++it;
311     }
312   }
313 
314   if (table_.get()) {
315     content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
316         base::Bind(&AutocompleteActionPredictorTable::DeleteRows, table_,
317                    id_list));
318   }
319 
320   UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
321                             DATABASE_ACTION_DELETE_SOME, DATABASE_ACTION_COUNT);
322 }
323 
OnOmniboxOpenedUrl(const OmniboxLog & log)324 void AutocompleteActionPredictor::OnOmniboxOpenedUrl(const OmniboxLog& log) {
325   if (log.text.length() < kMinimumUserTextLength)
326     return;
327 
328   const AutocompleteMatch& match = log.result.match_at(log.selected_index);
329 
330   UMA_HISTOGRAM_BOOLEAN(
331       base::StringPrintf("Prerender.OmniboxNavigationsCouldPrerender%s",
332                          prerender::PrerenderManager::GetModeString()).c_str(),
333       prerender::IsOmniboxEnabled(profile_));
334 
335   const GURL& opened_url = match.destination_url;
336   const base::string16 lower_user_text(base::i18n::ToLower(log.text));
337 
338   // Traverse transitional matches for those that have a user_text that is a
339   // prefix of |lower_user_text|.
340   std::vector<AutocompleteActionPredictorTable::Row> rows_to_add;
341   std::vector<AutocompleteActionPredictorTable::Row> rows_to_update;
342 
343   for (std::vector<TransitionalMatch>::const_iterator it =
344         transitional_matches_.begin(); it != transitional_matches_.end();
345         ++it) {
346     if (!StartsWith(lower_user_text, it->user_text, true))
347       continue;
348 
349     // Add entries to the database for those matches.
350     for (std::vector<GURL>::const_iterator url_it = it->urls.begin();
351           url_it != it->urls.end(); ++url_it) {
352       DCHECK(it->user_text.length() >= kMinimumUserTextLength);
353       const DBCacheKey key = { it->user_text, *url_it };
354       const bool is_hit = (*url_it == opened_url);
355 
356       AutocompleteActionPredictorTable::Row row;
357       row.user_text = key.user_text;
358       row.url = key.url;
359 
360       DBCacheMap::iterator it = db_cache_.find(key);
361       if (it == db_cache_.end()) {
362         row.id = base::GenerateGUID();
363         row.number_of_hits = is_hit ? 1 : 0;
364         row.number_of_misses = is_hit ? 0 : 1;
365 
366         rows_to_add.push_back(row);
367       } else {
368         DCHECK(db_id_cache_.find(key) != db_id_cache_.end());
369         row.id = db_id_cache_.find(key)->second;
370         row.number_of_hits = it->second.number_of_hits + (is_hit ? 1 : 0);
371         row.number_of_misses = it->second.number_of_misses + (is_hit ? 0 : 1);
372 
373         rows_to_update.push_back(row);
374       }
375     }
376   }
377   if (rows_to_add.size() > 0 || rows_to_update.size() > 0)
378     AddAndUpdateRows(rows_to_add, rows_to_update);
379 
380   ClearTransitionalMatches();
381 
382   // Check against tracked urls and log accuracy for the confidence we
383   // predicted.
384   for (std::vector<std::pair<GURL, double> >::const_iterator it =
385        tracked_urls_.begin(); it != tracked_urls_.end();
386        ++it) {
387     if (opened_url == it->first) {
388       UMA_HISTOGRAM_COUNTS_100("AutocompleteActionPredictor.AccurateCount",
389                                it->second * 100);
390     }
391   }
392   tracked_urls_.clear();
393 }
394 
AddAndUpdateRows(const AutocompleteActionPredictorTable::Rows & rows_to_add,const AutocompleteActionPredictorTable::Rows & rows_to_update)395 void AutocompleteActionPredictor::AddAndUpdateRows(
396     const AutocompleteActionPredictorTable::Rows& rows_to_add,
397     const AutocompleteActionPredictorTable::Rows& rows_to_update) {
398   if (!initialized_)
399     return;
400 
401   for (AutocompleteActionPredictorTable::Rows::const_iterator it =
402        rows_to_add.begin(); it != rows_to_add.end(); ++it) {
403     const DBCacheKey key = { it->user_text, it->url };
404     DBCacheValue value = { it->number_of_hits, it->number_of_misses };
405 
406     DCHECK(db_cache_.find(key) == db_cache_.end());
407 
408     db_cache_[key] = value;
409     db_id_cache_[key] = it->id;
410     UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
411                               DATABASE_ACTION_ADD, DATABASE_ACTION_COUNT);
412   }
413   for (AutocompleteActionPredictorTable::Rows::const_iterator it =
414        rows_to_update.begin(); it != rows_to_update.end(); ++it) {
415     const DBCacheKey key = { it->user_text, it->url };
416 
417     DBCacheMap::iterator db_it = db_cache_.find(key);
418     DCHECK(db_it != db_cache_.end());
419     DCHECK(db_id_cache_.find(key) != db_id_cache_.end());
420 
421     db_it->second.number_of_hits = it->number_of_hits;
422     db_it->second.number_of_misses = it->number_of_misses;
423     UMA_HISTOGRAM_ENUMERATION("AutocompleteActionPredictor.DatabaseAction",
424                               DATABASE_ACTION_UPDATE, DATABASE_ACTION_COUNT);
425   }
426 
427   if (table_.get()) {
428     content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
429         base::Bind(&AutocompleteActionPredictorTable::AddAndUpdateRows,
430                    table_, rows_to_add, rows_to_update));
431   }
432 }
433 
CreateCaches(std::vector<AutocompleteActionPredictorTable::Row> * rows)434 void AutocompleteActionPredictor::CreateCaches(
435     std::vector<AutocompleteActionPredictorTable::Row>* rows) {
436   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
437   DCHECK(!profile_->IsOffTheRecord());
438   DCHECK(!initialized_);
439   DCHECK(db_cache_.empty());
440   DCHECK(db_id_cache_.empty());
441 
442   for (std::vector<AutocompleteActionPredictorTable::Row>::const_iterator it =
443        rows->begin(); it != rows->end(); ++it) {
444     const DBCacheKey key = { it->user_text, it->url };
445     const DBCacheValue value = { it->number_of_hits, it->number_of_misses };
446     db_cache_[key] = value;
447     db_id_cache_[key] = it->id;
448   }
449 
450   // If the history service is ready, delete any old or invalid entries.
451   HistoryService* history_service =
452       HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
453   if (!TryDeleteOldEntries(history_service)) {
454     // Wait for the notification that the history service is ready and the URL
455     // DB is loaded.
456     notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_LOADED,
457                                 content::Source<Profile>(profile_));
458   }
459 }
460 
TryDeleteOldEntries(HistoryService * service)461 bool AutocompleteActionPredictor::TryDeleteOldEntries(HistoryService* service) {
462   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
463   DCHECK(!profile_->IsOffTheRecord());
464   DCHECK(!initialized_);
465 
466   if (!service)
467     return false;
468 
469   history::URLDatabase* url_db = service->InMemoryDatabase();
470   if (!url_db)
471     return false;
472 
473   DeleteOldEntries(url_db);
474   return true;
475 }
476 
DeleteOldEntries(history::URLDatabase * url_db)477 void AutocompleteActionPredictor::DeleteOldEntries(
478     history::URLDatabase* url_db) {
479   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
480   DCHECK(!profile_->IsOffTheRecord());
481   DCHECK(!initialized_);
482   DCHECK(table_.get());
483 
484   std::vector<AutocompleteActionPredictorTable::Row::Id> ids_to_delete;
485   DeleteOldIdsFromCaches(url_db, &ids_to_delete);
486 
487   content::BrowserThread::PostTask(content::BrowserThread::DB, FROM_HERE,
488       base::Bind(&AutocompleteActionPredictorTable::DeleteRows, table_,
489                  ids_to_delete));
490 
491   FinishInitialization();
492   if (incognito_predictor_)
493     incognito_predictor_->CopyFromMainProfile();
494 }
495 
DeleteOldIdsFromCaches(history::URLDatabase * url_db,std::vector<AutocompleteActionPredictorTable::Row::Id> * id_list)496 void AutocompleteActionPredictor::DeleteOldIdsFromCaches(
497     history::URLDatabase* url_db,
498     std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
499   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
500   DCHECK(!profile_->IsOffTheRecord());
501   DCHECK(!initialized_);
502   DCHECK(url_db);
503   DCHECK(id_list);
504 
505   id_list->clear();
506   for (DBCacheMap::iterator it = db_cache_.begin(); it != db_cache_.end();) {
507     history::URLRow url_row;
508 
509     if ((url_db->GetRowForURL(it->first.url, &url_row) == 0) ||
510         ((base::Time::Now() - url_row.last_visit()).InDays() >
511          kMaximumDaysToKeepEntry)) {
512       const DBIdCacheMap::iterator id_it = db_id_cache_.find(it->first);
513       DCHECK(id_it != db_id_cache_.end());
514       id_list->push_back(id_it->second);
515       db_id_cache_.erase(id_it);
516       db_cache_.erase(it++);
517     } else {
518       ++it;
519     }
520   }
521 }
522 
CopyFromMainProfile()523 void AutocompleteActionPredictor::CopyFromMainProfile() {
524   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
525   DCHECK(profile_->IsOffTheRecord());
526   DCHECK(!initialized_);
527   DCHECK(main_profile_predictor_);
528   DCHECK(main_profile_predictor_->initialized_);
529 
530   db_cache_ = main_profile_predictor_->db_cache_;
531   db_id_cache_ = main_profile_predictor_->db_id_cache_;
532   FinishInitialization();
533 }
534 
FinishInitialization()535 void AutocompleteActionPredictor::FinishInitialization() {
536   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
537   DCHECK(!initialized_);
538 
539   // Incognito and normal profiles should listen only to omnibox notifications
540   // from their own profile, but both should listen to history deletions from
541   // the main profile, since opening the history page in either case actually
542   // opens the non-incognito history (and lets users delete from there).
543   notification_registrar_.Add(this, chrome::NOTIFICATION_OMNIBOX_OPENED_URL,
544                               content::Source<Profile>(profile_));
545   notification_registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
546       content::Source<Profile>(profile_->GetOriginalProfile()));
547   initialized_ = true;
548 }
549 
CalculateConfidence(const base::string16 & user_text,const AutocompleteMatch & match,bool * is_in_db) const550 double AutocompleteActionPredictor::CalculateConfidence(
551     const base::string16& user_text,
552     const AutocompleteMatch& match,
553     bool* is_in_db) const {
554   const DBCacheKey key = { user_text, match.destination_url };
555 
556   *is_in_db = false;
557   if (user_text.length() < kMinimumUserTextLength)
558     return 0.0;
559 
560   const DBCacheMap::const_iterator iter = db_cache_.find(key);
561   if (iter == db_cache_.end())
562     return 0.0;
563 
564   *is_in_db = true;
565   return CalculateConfidenceForDbEntry(iter);
566 }
567 
CalculateConfidenceForDbEntry(DBCacheMap::const_iterator iter) const568 double AutocompleteActionPredictor::CalculateConfidenceForDbEntry(
569     DBCacheMap::const_iterator iter) const {
570   const DBCacheValue& value = iter->second;
571   if (value.number_of_hits < kMinimumNumberOfHits)
572     return 0.0;
573 
574   const double number_of_hits = static_cast<double>(value.number_of_hits);
575   return number_of_hits / (number_of_hits + value.number_of_misses);
576 }
577 
TransitionalMatch()578 AutocompleteActionPredictor::TransitionalMatch::TransitionalMatch() {
579 }
580 
~TransitionalMatch()581 AutocompleteActionPredictor::TransitionalMatch::~TransitionalMatch() {
582 }
583 
584 }  // namespace predictors
585