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