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/chrome_notification_types.h" 19 #include "chrome/browser/history/history_notifications.h" 20 #include "chrome/browser/history/history_service.h" 21 #include "chrome/browser/history/history_service_factory.h" 22 #include "chrome/browser/omnibox/omnibox_log.h" 23 #include "chrome/browser/predictors/autocomplete_action_predictor_factory.h" 24 #include "chrome/browser/predictors/predictor_database.h" 25 #include "chrome/browser/predictors/predictor_database_factory.h" 26 #include "chrome/browser/prerender/prerender_field_trial.h" 27 #include "chrome/browser/prerender/prerender_handle.h" 28 #include "chrome/browser/prerender/prerender_manager.h" 29 #include "chrome/browser/prerender/prerender_manager_factory.h" 30 #include "chrome/browser/profiles/profile.h" 31 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 32 #include "components/history/core/browser/in_memory_database.h" 33 #include "components/omnibox/autocomplete_match.h" 34 #include "components/omnibox/autocomplete_result.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 613