diff options
author | georgey@chromium.org <georgey@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-26 01:37:23 +0000 |
---|---|---|
committer | georgey@chromium.org <georgey@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-26 01:37:23 +0000 |
commit | fc8893b93c962a89b1d3ce9a8450b68b0e6e95f3 (patch) | |
tree | f3764e23d57726380bf0d5229b6b8f795e18b393 | |
parent | 0ba43cc217485a48137ecf2ce6c1ce54cdc67844 (diff) | |
download | chromium_src-fc8893b93c962a89b1d3ce9a8450b68b0e6e95f3.zip chromium_src-fc8893b93c962a89b1d3ce9a8450b68b0e6e95f3.tar.gz chromium_src-fc8893b93c962a89b1d3ce9a8450b68b0e6e95f3.tar.bz2 |
Fix for: Autofill should not ping the server again for the same form
BUG=67039
TEST=unit-tested. Any network sniffer could be used to test that there no subsequent calls to the web for the same form.
Review URL: http://codereview.chromium.org/6366014
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@72585 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/autofill/autofill_download.cc | 72 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_download.h | 26 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_download_unittest.cc | 190 |
3 files changed, 286 insertions, 2 deletions
diff --git a/chrome/browser/autofill/autofill_download.cc b/chrome/browser/autofill/autofill_download.cc index cc7e119..2c9cc4c 100644 --- a/chrome/browser/autofill/autofill_download.cc +++ b/chrome/browser/autofill/autofill_download.cc @@ -27,6 +27,10 @@ #define AUTO_FILL_QUERY_SERVER_NAME_START_IN_HEADER "SOMESERVER/" #endif +namespace { +const size_t kMaxFormCacheSize = 16; +}; + struct AutoFillDownloadManager::FormRequestData { std::vector<std::string> form_signatures; AutoFillRequestType request_type; @@ -35,6 +39,7 @@ struct AutoFillDownloadManager::FormRequestData { AutoFillDownloadManager::AutoFillDownloadManager(Profile* profile) : profile_(profile), observer_(NULL), + max_form_cache_size_(kMaxFormCacheSize), next_query_request_(base::Time::Now()), next_upload_request_(base::Time::Now()), positive_upload_rate_(0), @@ -76,12 +81,22 @@ bool AutoFillDownloadManager::StartQueryRequest( std::string form_xml; FormRequestData request_data; if (!FormStructure::EncodeQueryRequest(forms, &request_data.form_signatures, - &form_xml)) + &form_xml)) { return false; + } request_data.request_type = AutoFillDownloadManager::REQUEST_QUERY; metric_logger.Log(AutoFillMetrics::QUERY_SENT); + std::string query_data; + if (CheckCacheForQueryRequest(request_data.form_signatures, &query_data)) { + VLOG(1) << "AutoFillDownloadManager: query request has been retrieved from" + << "the cache"; + if (observer_) + observer_->OnLoadedAutoFillHeuristics(query_data); + return true; + } + return StartRequest(form_xml, request_data); } @@ -188,6 +203,60 @@ bool AutoFillDownloadManager::StartRequest( return true; } +void AutoFillDownloadManager::CacheQueryRequest( + const std::vector<std::string>& forms_in_query, + const std::string& query_data) { + std::string signature = GetCombinedSignature(forms_in_query); + for (QueryRequestCache::iterator it = cached_forms_.begin(); + it != cached_forms_.end(); ++it) { + if (it->first == signature) { + // We hit the cache, move to the first position and return. + std::pair<std::string, std::string> data = *it; + cached_forms_.erase(it); + cached_forms_.push_front(data); + return; + } + } + std::pair<std::string, std::string> data; + data.first = signature; + data.second = query_data; + cached_forms_.push_front(data); + while (cached_forms_.size() > max_form_cache_size_) + cached_forms_.pop_back(); +} + +bool AutoFillDownloadManager::CheckCacheForQueryRequest( + const std::vector<std::string>& forms_in_query, + std::string* query_data) const { + std::string signature = GetCombinedSignature(forms_in_query); + for (QueryRequestCache::const_iterator it = cached_forms_.begin(); + it != cached_forms_.end(); ++it) { + if (it->first == signature) { + // We hit the cache, fill the data and return. + *query_data = it->second; + return true; + } + } + return false; +} + +std::string AutoFillDownloadManager::GetCombinedSignature( + const std::vector<std::string>& forms_in_query) const { + size_t total_size = forms_in_query.size(); + for (size_t i = 0; i < forms_in_query.size(); ++i) + total_size += forms_in_query[i].length(); + std::string signature; + + signature.reserve(total_size); + + for (size_t i = 0; i < forms_in_query.size(); ++i) { + if (i) + signature.append(","); + signature.append(forms_in_query[i]); + } + return signature; +} + void AutoFillDownloadManager::OnURLFetchComplete( const URLFetcher* source, const GURL& url, @@ -250,6 +319,7 @@ void AutoFillDownloadManager::OnURLFetchComplete( VLOG(1) << "AutoFillDownloadManager: " << type_of_request << " request has succeeded"; if (it->second.request_type == AutoFillDownloadManager::REQUEST_QUERY) { + CacheQueryRequest(it->second.form_signatures, data); if (observer_) observer_->OnLoadedAutoFillHeuristics(data); } else { diff --git a/chrome/browser/autofill/autofill_download.h b/chrome/browser/autofill/autofill_download.h index 8937557..157062c 100644 --- a/chrome/browser/autofill/autofill_download.h +++ b/chrome/browser/autofill/autofill_download.h @@ -6,8 +6,10 @@ #define CHROME_BROWSER_AUTOFILL_AUTOFILL_DOWNLOAD_H_ #pragma once +#include <list> #include <map> #include <string> +#include <vector> #include "base/scoped_vector.h" #include "base/time.h" @@ -96,6 +98,7 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { friend class AutoFillDownloadTestHelper; // unit-test. struct FormRequestData; + typedef std::list<std::pair<std::string, std::string> > QueryRequestCache; // Initiates request to AutoFill servers to download/upload heuristics. // |form_xml| - form structure XML to upload/download. @@ -106,6 +109,25 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { bool StartRequest(const std::string& form_xml, const FormRequestData& request_data); + // Each request is page visited. We store last |max_form_cache_size| + // request, to avoid going over the wire. Set to 16 in constructor. Warning: + // the search is linear (newest first), so do not make the constant very big. + void set_max_form_cache_size(size_t max_form_cache_size) { + max_form_cache_size_ = max_form_cache_size; + } + + // Caches query request. |forms_in_query| is a vector of form signatures in + // the query. |query_data| is the successful data returned over the wire. + void CacheQueryRequest(const std::vector<std::string>& forms_in_query, + const std::string& query_data); + // Returns true if query is in the cache, while filling |query_data|, false + // otherwise. |forms_in_query| is a vector of form signatures in the query. + bool CheckCacheForQueryRequest(const std::vector<std::string>& forms_in_query, + std::string* query_data) const; + // Concatenates |forms_in_query| into one signature. + std::string GetCombinedSignature( + const std::vector<std::string>& forms_in_query) const; + // URLFetcher::Delegate implementation: virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, @@ -123,6 +145,10 @@ class AutoFillDownloadManager : public URLFetcher::Delegate { std::map<URLFetcher*, FormRequestData> url_fetchers_; AutoFillDownloadManager::Observer *observer_; + // Cached QUERY requests. + QueryRequestCache cached_forms_; + size_t max_form_cache_size_; + // Time when next query/upload requests are allowed. If 50x HTTP received, // exponential back off is initiated, so this times will be in the future // for awhile. diff --git a/chrome/browser/autofill/autofill_download_unittest.cc b/chrome/browser/autofill/autofill_download_unittest.cc index aaf2a5e..b9967d6 100644 --- a/chrome/browser/autofill/autofill_download_unittest.cc +++ b/chrome/browser/autofill/autofill_download_unittest.cc @@ -53,6 +53,10 @@ class AutoFillDownloadTestHelper : public AutoFillDownloadManager::Observer { download_manager.SetObserver(NULL); } + void LimitCache(size_t cache_size) { + download_manager.set_max_form_cache_size(cache_size); + } + // AutoFillDownloadManager::Observer overridables: virtual void OnLoadedAutoFillHeuristics( const std::string& heuristic_xml) { @@ -61,7 +65,6 @@ class AutoFillDownloadTestHelper : public AutoFillDownloadManager::Observer { response.type_of_response = QUERY_SUCCESSFULL; responses_.push_back(response); }; - virtual void OnUploadedAutoFillHeuristics(const std::string& form_signature) { ResponseData response; response.type_of_response = UPLOAD_SUCCESSFULL; @@ -276,6 +279,16 @@ TEST(AutoFillDownloadTest, QueryAndUploadTest) { fetcher = factory.GetFetcherByID(3); EXPECT_EQ(NULL, fetcher); + // Modify form structures to miss the cache. + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("Address line 2"), + ASCIIToUTF16("address2"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + form_structure = new FormStructure(form); + form_structures.push_back(form_structure); + // Request with id 3. EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures, @@ -329,3 +342,178 @@ TEST(AutoFillDownloadTest, QueryAndUploadTest) { // Make sure consumer of URLFetcher does the right thing. URLFetcher::set_factory(NULL); } + +TEST(AutoFillDownloadTest, CacheQueryTest) { + MessageLoopForUI message_loop; + AutoFillDownloadTestHelper helper; + // Create and register factory. + TestURLFetcherFactory factory; + URLFetcher::set_factory(&factory); + + FormData form; + form.method = ASCIIToUTF16("post"); + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("username"), + ASCIIToUTF16("username"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("First Name"), + ASCIIToUTF16("firstname"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("Last Name"), + ASCIIToUTF16("lastname"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + FormStructure *form_structure = new FormStructure(form); + ScopedVector<FormStructure> form_structures0; + form_structures0.push_back(form_structure); + + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("email"), + ASCIIToUTF16("email"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + // Slightly different form - so different request. + form_structure = new FormStructure(form); + ScopedVector<FormStructure> form_structures1; + form_structures1.push_back(form_structure); + + form.fields.push_back(webkit_glue::FormField(ASCIIToUTF16("email2"), + ASCIIToUTF16("email2"), + string16(), + ASCIIToUTF16("text"), + 0, + false)); + // Slightly different form - so different request. + form_structure = new FormStructure(form); + ScopedVector<FormStructure> form_structures2; + form_structures2.push_back(form_structure); + + // Limit cache to two forms. + helper.LimitCache(2); + + const char *responses[] = { + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "</autofillqueryresponse>", + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "<field autofilltype=\"9\" />" + "</autofillqueryresponse>", + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "<field autofilltype=\"9\" />" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>", + }; + + // Request with id 0. + MockAutoFillMetrics mock_metric_logger; + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures0, + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), helper.responses_.size()); + + TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), + net::URLRequestStatus(), + 200, ResponseCookies(), + std::string(responses[0])); + ASSERT_EQ(static_cast<size_t>(1), helper.responses_.size()); + EXPECT_EQ(responses[0], helper.responses_.front().response); + + helper.responses_.clear(); + + // No actual request - should be a cache hit. + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures0, + mock_metric_logger)); + // Data is available immediately from cache - no over-the-wire trip. + ASSERT_EQ(static_cast<size_t>(1), helper.responses_.size()); + EXPECT_EQ(responses[0], helper.responses_.front().response); + helper.responses_.clear(); + + // Request with id 1. + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures1, + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), helper.responses_.size()); + + fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(fetcher); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), + net::URLRequestStatus(), + 200, ResponseCookies(), + std::string(responses[1])); + ASSERT_EQ(static_cast<size_t>(1), helper.responses_.size()); + EXPECT_EQ(responses[1], helper.responses_.front().response); + + helper.responses_.clear(); + + // Request with id 2. + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures2, + mock_metric_logger)); + + fetcher = factory.GetFetcherByID(2); + ASSERT_TRUE(fetcher); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), + net::URLRequestStatus(), + 200, ResponseCookies(), + std::string(responses[2])); + ASSERT_EQ(static_cast<size_t>(1), helper.responses_.size()); + EXPECT_EQ(responses[2], helper.responses_.front().response); + + helper.responses_.clear(); + + // No actual requests - should be a cache hit. + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures1, + mock_metric_logger)); + + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures2, + mock_metric_logger)); + + ASSERT_EQ(static_cast<size_t>(2), helper.responses_.size()); + EXPECT_EQ(responses[1], helper.responses_.front().response); + EXPECT_EQ(responses[2], helper.responses_.back().response); + helper.responses_.clear(); + + // The first structure should've expired. + // Request with id 3. + EXPECT_CALL(mock_metric_logger, Log(AutoFillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(helper.download_manager.StartQueryRequest(form_structures0, + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), helper.responses_.size()); + + fetcher = factory.GetFetcherByID(3); + ASSERT_TRUE(fetcher); + fetcher->delegate()->OnURLFetchComplete(fetcher, GURL(), + net::URLRequestStatus(), + 200, ResponseCookies(), + std::string(responses[0])); + ASSERT_EQ(static_cast<size_t>(1), helper.responses_.size()); + EXPECT_EQ(responses[0], helper.responses_.front().response); + + // Make sure consumer of URLFetcher does the right thing. + URLFetcher::set_factory(NULL); +} + |