diff options
author | rouslan@chromium.org <rouslan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-25 03:32:10 +0000 |
---|---|---|
committer | rouslan@chromium.org <rouslan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-25 03:32:10 +0000 |
commit | e0c45d7f8ab8e057702a31239266367b467e5913 (patch) | |
tree | 5ef7579ac1a983552f219f692138721b41f75fbe | |
parent | 86f1a7369e976faba6581eaca7d8a38d7c0aa6c6 (diff) | |
download | chromium_src-e0c45d7f8ab8e057702a31239266367b467e5913.zip chromium_src-e0c45d7f8ab8e057702a31239266367b467e5913.tar.gz chromium_src-e0c45d7f8ab8e057702a31239266367b467e5913.tar.bz2 |
Send IN_DICTIONARY feedback when user has misspelling in custom dictionary
When a user types a word that is misspelled and is in the custom dictionary,
then the spelling service client will generate a spellcheck result for the
misspelling anyway. The feedback sender sends an IN_DICTIONARY message to the
spelling service to indicate that the user cannot see this spellcheck result
and, therefore, cannot provide any other feedback on the spellcheck suggestion
quality.
BUG=170514
Review URL: https://chromiumcodereview.appspot.com/15897005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208380 0039d316-1c4b-4281-b951-d872f2087c98
8 files changed, 294 insertions, 190 deletions
diff --git a/chrome/browser/spellchecker/feedback_sender.cc b/chrome/browser/spellchecker/feedback_sender.cc index 3b72c69..c250cf4 100644 --- a/chrome/browser/spellchecker/feedback_sender.cc +++ b/chrome/browser/spellchecker/feedback_sender.cc @@ -155,6 +155,8 @@ FeedbackSender::~FeedbackSender() { void FeedbackSender::SelectedSuggestion(uint32 hash, int suggestion_index) { Misspelling* misspelling = feedback_.GetMisspelling(hash); + // GetMisspelling() returns null for flushed feedback. Feedback is flushed + // when the session expires every |kSessionHours| hours. if (!misspelling) return; misspelling->action.type = SpellcheckAction::TYPE_SELECT; @@ -164,6 +166,8 @@ void FeedbackSender::SelectedSuggestion(uint32 hash, int suggestion_index) { void FeedbackSender::AddedToDictionary(uint32 hash) { Misspelling* misspelling = feedback_.GetMisspelling(hash); + // GetMisspelling() returns null for flushed feedback. Feedback is flushed + // when the session expires every |kSessionHours| hours. if (!misspelling) return; misspelling->action.type = SpellcheckAction::TYPE_ADD_TO_DICT; @@ -181,8 +185,19 @@ void FeedbackSender::AddedToDictionary(uint32 hash) { } } +void FeedbackSender::RecordInDictionary(uint32 hash) { + Misspelling* misspelling = feedback_.GetMisspelling(hash); + // GetMisspelling() returns null for flushed feedback. Feedback is flushed + // when the session expires every |kSessionHours| hours. + if (!misspelling) + return; + misspelling->action.type = SpellcheckAction::TYPE_IN_DICTIONARY; +} + void FeedbackSender::IgnoredSuggestions(uint32 hash) { Misspelling* misspelling = feedback_.GetMisspelling(hash); + // GetMisspelling() returns null for flushed feedback. Feedback is flushed + // when the session expires every |kSessionHours| hours. if (!misspelling) return; misspelling->action.type = SpellcheckAction::TYPE_PENDING_IGNORE; @@ -192,6 +207,8 @@ void FeedbackSender::IgnoredSuggestions(uint32 hash) { void FeedbackSender::ManuallyCorrected(uint32 hash, const string16& correction) { Misspelling* misspelling = feedback_.GetMisspelling(hash); + // GetMisspelling() returns null for flushed feedback. Feedback is flushed + // when the session expires every |kSessionHours| hours. if (!misspelling) return; misspelling->action.type = SpellcheckAction::TYPE_MANUALLY_CORRECTED; diff --git a/chrome/browser/spellchecker/feedback_sender.h b/chrome/browser/spellchecker/feedback_sender.h index dbcaba2..5a89f7b 100644 --- a/chrome/browser/spellchecker/feedback_sender.h +++ b/chrome/browser/spellchecker/feedback_sender.h @@ -18,7 +18,6 @@ #include "net/url_request/url_fetcher_delegate.h" class SpellCheckMarker; -class SpellingServiceFeedbackTest; struct SpellCheckResult; namespace net { @@ -58,6 +57,10 @@ class FeedbackSender : public base::SupportsWeakPtr<FeedbackSender>, // the list of suggestions. void ManuallyCorrected(uint32 hash, const string16& correction); + // Records that user has the misspelling in the custom dictionary. The user + // will never see the spellcheck suggestions for the misspelling. + void RecordInDictionary(uint32 hash); + // Receives document markers for renderer with process ID |render_process_id| // when the renderer responds to a RequestDocumentMarkers() call. Finalizes // feedback for the markers that are gone from the renderer. Sends feedback diff --git a/chrome/browser/spellchecker/feedback_sender_unittest.cc b/chrome/browser/spellchecker/feedback_sender_unittest.cc index 262b009..5504005 100644 --- a/chrome/browser/spellchecker/feedback_sender_unittest.cc +++ b/chrome/browser/spellchecker/feedback_sender_unittest.cc @@ -43,6 +43,18 @@ SpellCheckResult BuildSpellCheckResult() { ASCIIToUTF16("Hello")); } +// Returns the number of times that |needle| appears in |haystack| without +// overlaps. For example, CountOccurences("bananana", "nana") returns 1. +int CountOccurences(const std::string& haystack, const std::string& needle) { + int number_of_occurrences = 0; + for (size_t pos = haystack.find(needle); + pos != std::string::npos; + pos = haystack.find(needle, pos + needle.length())) { + ++number_of_occurrences; + } + return number_of_occurrences; +} + } // namespace class FeedbackSenderTest : public testing::Test { @@ -70,6 +82,7 @@ class FeedbackSenderTest : public testing::Test { content::TestBrowserThread ui_thread_; scoped_ptr<base::FieldTrialList> field_trial_list_; scoped_refptr<base::FieldTrial> field_trial_; + net::TestURLFetcherFactory fetchers_; protected: uint32 AddPendingFeedback() { @@ -85,30 +98,55 @@ class FeedbackSenderTest : public testing::Test { base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours); } - net::TestURLFetcher* GetFetcher() { - return fetchers_.GetFetcherByID(kUrlFetcherId); + bool UploadDataContains(const std::string& data) const { + const net::TestURLFetcher* fetcher = + fetchers_.GetFetcherByID(kUrlFetcherId); + return fetcher && CountOccurences(fetcher->upload_data(), data) > 0; + } + + bool UploadDataContains(const std::string& data, + int number_of_occurrences) const { + const net::TestURLFetcher* fetcher = + fetchers_.GetFetcherByID(kUrlFetcherId); + return fetcher && CountOccurences(fetcher->upload_data(), data) == + number_of_occurrences; + } + + // Returns true if the feedback sender would be uploading data now. Otherwise + // returns false. The test does not open network connections. + bool IsUploadingData() const { + return !!fetchers_.GetFetcherByID(kUrlFetcherId); + } + + void ClearUploadData() { + fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + } + + std::string GetUploadData() const { + const net::TestURLFetcher* fetcher = + fetchers_.GetFetcherByID(kUrlFetcherId); + return fetcher ? fetcher->upload_data() : std::string(); } - net::TestURLFetcherFactory fetchers_; scoped_ptr<spellcheck::FeedbackSender> feedback_; }; // Do not send data if there's no feedback. TEST_F(FeedbackSenderTest, NoFeedback) { - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); } // Do not send data if not aware of which markers are still in the document. TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) { - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); uint32 hash = AddPendingFeedback(); - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); static const int kSuggestionIndex = 1; feedback_->SelectedSuggestion(hash, kSuggestionIndex); - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); } // Send PENDING feedback message if the marker is still in the document, and the @@ -117,10 +155,7 @@ TEST_F(FeedbackSenderTest, PendingFeedback) { uint32 hash = AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>(1, hash)); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); } // Send NO_ACTION feedback message if the marker has been removed from the @@ -129,26 +164,18 @@ TEST_F(FeedbackSenderTest, NoActionFeedback) { AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); } // Send SELECT feedback message if the user has selected a spelling suggestion. TEST_F(FeedbackSenderTest, SelectFeedback) { uint32 hash = AddPendingFeedback(); - static const int kSuggestion = 0; - feedback_->SelectedSuggestion(hash, kSuggestion); + static const int kSuggestionIndex = 0; + feedback_->SelectedSuggestion(hash, kSuggestionIndex); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"SELECT\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionTargetIndex\":" + kSuggestion)) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"SELECT\"")); + EXPECT_TRUE(UploadDataContains("\"actionTargetIndex\":" + kSuggestionIndex)); } // Send ADD_TO_DICT feedback message if the user has added the misspelled word @@ -158,10 +185,17 @@ TEST_F(FeedbackSenderTest, AddToDictFeedback) { feedback_->AddedToDictionary(hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"ADD_TO_DICT\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"ADD_TO_DICT\"")); +} + +// Send IN_DICTIONARY feedback message if the user has the misspelled word in +// the custom dictionary. +TEST_F(FeedbackSenderTest, InDictionaryFeedback) { + uint32 hash = AddPendingFeedback(); + feedback_->RecordInDictionary(hash); + feedback_->OnReceiveDocumentMarkers(kRendererProcessId, + std::vector<uint32>()); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"IN_DICTIONARY\"")); } // Send PENDING feedback message if the user saw the spelling suggestion, but @@ -171,10 +205,7 @@ TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) { feedback_->IgnoredSuggestions(hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>(1, hash)); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); } // Send IGNORE feedback message if the user saw the spelling suggestion, but @@ -184,10 +215,7 @@ TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) { feedback_->IgnoredSuggestions(hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"IGNORE\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"IGNORE\"")); } // Send MANUALLY_CORRECTED feedback message if the user manually corrected the @@ -198,15 +226,9 @@ TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) { feedback_->ManuallyCorrected(hash, ASCIIToUTF16(kManualCorrection)); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE( - std::string::npos, - fetcher->upload_data().find("\"actionType\":\"MANUALLY_CORRECTED\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionTargetValue\":\"" + - kManualCorrection + "\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"MANUALLY_CORRECTED\"")); + EXPECT_TRUE(UploadDataContains("\"actionTargetValue\":\"" + + kManualCorrection + "\"")); } // Send feedback messages in batch. @@ -226,11 +248,7 @@ TEST_F(FeedbackSenderTest, BatchFeedback) { &results, kRendererProcessId, kText, std::vector<SpellCheckMarker>()); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - size_t pos = fetcher->upload_data().find("\"actionType\":\"NO_ACTION\""); - EXPECT_NE(std::string::npos, pos) << fetcher->upload_data(); - pos = fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"", pos + 1); - EXPECT_NE(std::string::npos, pos) << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"", 2)); } // Send a series of PENDING feedback messages and one final NO_ACTION feedback @@ -240,37 +258,25 @@ TEST_F(FeedbackSenderTest, SameHashFeedback) { std::vector<uint32> remaining_markers(1, hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); std::string hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", hash); - EXPECT_NE(std::string::npos, fetcher->upload_data().find(hash_string)) - << fetcher->upload_data(); - fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + EXPECT_TRUE(UploadDataContains(hash_string)); + ClearUploadData(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, fetcher->upload_data().find(hash_string)) - << fetcher->upload_data(); - fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); + EXPECT_TRUE(UploadDataContains(hash_string)); + ClearUploadData(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, fetcher->upload_data().find(hash_string)) - << fetcher->upload_data(); - fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); + EXPECT_TRUE(UploadDataContains(hash_string)); + ClearUploadData(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); } // When a session expires: @@ -291,40 +297,26 @@ TEST_F(FeedbackSenderTest, SessionExpirationFeedback) { std::vector<uint32> remaining_markers(1, original_hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_EQ(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); + EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); std::string original_hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", original_hash); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find(original_hash_string)) - << fetcher->upload_data(); - fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + EXPECT_TRUE(UploadDataContains(original_hash_string)); + ClearUploadData(); ExpireSession(); // Last message batch in the current session has only finalized messages. feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"")) - << fetcher->upload_data(); - EXPECT_EQ(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find(original_hash_string)) - << fetcher->upload_data(); - fetchers_.RemoveFetcherFromMap(kUrlFetcherId); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); + EXPECT_FALSE(UploadDataContains("\"actionType\":\"PENDING\"")); + EXPECT_TRUE(UploadDataContains(original_hash_string)); + ClearUploadData(); // The next session starts on the next spellchecker request. Until then, // there's no more feedback sent. feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - EXPECT_EQ(NULL, GetFetcher()); + EXPECT_FALSE(IsUploadingData()); // The first spellcheck request after session expiration creates different // document marker hash identifiers. @@ -343,20 +335,12 @@ TEST_F(FeedbackSenderTest, SessionExpirationFeedback) { // The first feedback message batch in session |i + 1| has the new document // marker hash identifiers. feedback_->OnReceiveDocumentMarkers(kRendererProcessId, remaining_markers); - fetcher = GetFetcher(); - EXPECT_EQ(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"NO_ACTION\"")) - << fetcher->upload_data(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"actionType\":\"PENDING\"")) - << fetcher->upload_data(); - EXPECT_EQ(std::string::npos, - fetcher->upload_data().find(original_hash_string)) - << fetcher->upload_data(); + EXPECT_FALSE(UploadDataContains("\"actionType\":\"NO_ACTION\"")); + EXPECT_TRUE(UploadDataContains("\"actionType\":\"PENDING\"")); + EXPECT_FALSE(UploadDataContains(original_hash_string)); std::string updated_hash_string = base::StringPrintf("\"suggestionId\":\"%u\"", updated_hash); - EXPECT_NE(std::string::npos, fetcher->upload_data().find(updated_hash_string)) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains(updated_hash_string)); } // First message in session has an indicator. @@ -365,19 +349,13 @@ TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) { AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"isFirstInSession\":true")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true")); // Session 1, message 2 AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"isFirstInSession\":false")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); ExpireSession(); @@ -385,45 +363,29 @@ TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) { AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"isFirstInSession\":false")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); // Session 2, message 1 AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"isFirstInSession\":true")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":true")); // Session 2, message 2 AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"isFirstInSession\":false")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"isFirstInSession\":false")); } // Flush all feedback when the spellcheck language and country change. TEST_F(FeedbackSenderTest, OnLanguageCountryChange) { AddPendingFeedback(); feedback_->OnLanguageCountryChange("pt", "BR"); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"language\":\"en\"")) - << fetcher->upload_data(); - + EXPECT_TRUE(UploadDataContains("\"language\":\"en\"")); AddPendingFeedback(); feedback_->OnLanguageCountryChange("en", "US"); - fetcher = GetFetcher(); - EXPECT_NE(std::string::npos, - fetcher->upload_data().find("\"language\":\"pt\"")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("\"language\":\"pt\"")); } // The field names and types should correspond to the API. @@ -431,7 +393,7 @@ TEST_F(FeedbackSenderTest, FeedbackAPI) { AddPendingFeedback(); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - std::string actual_data = GetFetcher()->upload_data(); + std::string actual_data = GetUploadData(); scoped_ptr<base::DictionaryValue> actual( static_cast<base::DictionaryValue*>(base::JSONReader::Read(actual_data))); actual->SetString("params.key", "TestDummyKey"); @@ -481,22 +443,6 @@ TEST_F(FeedbackSenderTest, MatchDupliateResultsWithExistingMarkers) { EXPECT_EQ(hash, results[0].hash); } -namespace { - -// Returns the number of times that |needle| appears in |haystack| without -// overlaps. For example, CountOccurences("bananana", "nana") returns 1. -int CountOccurences(const std::string& haystack, const std::string& needle) { - int number_of_occurences = 0; - size_t pos = haystack.find(needle); - while (pos != std::string::npos) { - ++number_of_occurences; - pos = haystack.find(needle, pos + needle.length()); - } - return number_of_occurences; -} - -} // namespace - // Adding a word to dictionary should trigger ADD_TO_DICT feedback for every // occurrence of that word. TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) { @@ -525,20 +471,14 @@ TEST_F(FeedbackSenderTest, MultipleAddToDictFeedback) { remaining_markers.push_back(results[i].hash); feedback_->OnReceiveDocumentMarkers(last_renderer_process_id, remaining_markers); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_EQ(2, CountOccurences(fetcher->upload_data(), "PENDING")) - << fetcher->upload_data(); - EXPECT_EQ(0, CountOccurences(fetcher->upload_data(), "ADD_TO_DICT")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("PENDING", 2)); + EXPECT_FALSE(UploadDataContains("ADD_TO_DICT")); feedback_->AddedToDictionary(results[0].hash); feedback_->OnReceiveDocumentMarkers(last_renderer_process_id, remaining_markers); - fetcher = GetFetcher(); - EXPECT_EQ(0, CountOccurences(fetcher->upload_data(), "PENDING")) - << fetcher->upload_data(); - EXPECT_EQ(2, CountOccurences(fetcher->upload_data(), "ADD_TO_DICT")) - << fetcher->upload_data(); + EXPECT_FALSE(UploadDataContains("PENDING")); + EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2)); } // ADD_TO_DICT feedback for multiple occurrences of a word should trigger only @@ -551,11 +491,8 @@ TEST_F(FeedbackSenderTest, AddToDictOnlyPending) { feedback_->AddedToDictionary(add_to_dict_hash); feedback_->OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>()); - net::TestURLFetcher* fetcher = GetFetcher(); - EXPECT_EQ(1, CountOccurences(fetcher->upload_data(), "SELECT")) - << fetcher->upload_data(); - EXPECT_EQ(2, CountOccurences(fetcher->upload_data(), "ADD_TO_DICT")) - << fetcher->upload_data(); + EXPECT_TRUE(UploadDataContains("SELECT", 1)); + EXPECT_TRUE(UploadDataContains("ADD_TO_DICT", 2)); } } // namespace spellcheck diff --git a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc index 0cd0c7c..c3d2484 100644 --- a/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc +++ b/chrome/browser/spellchecker/spellcheck_custom_dictionary.cc @@ -261,7 +261,7 @@ bool SpellcheckCustomDictionary::RemoveWord(const std::string& word) { return result == VALID_CHANGE; } -bool SpellcheckCustomDictionary::HasWord(const std::string& word) { +bool SpellcheckCustomDictionary::HasWord(const std::string& word) const { return !!words_.count(word); } diff --git a/chrome/browser/spellchecker/spellcheck_custom_dictionary.h b/chrome/browser/spellchecker/spellcheck_custom_dictionary.h index e54b6fa..848022a 100644 --- a/chrome/browser/spellchecker/spellcheck_custom_dictionary.h +++ b/chrome/browser/spellchecker/spellcheck_custom_dictionary.h @@ -91,7 +91,7 @@ class SpellcheckCustomDictionary : public SpellcheckDictionary, bool RemoveWord(const std::string& word); // Returns true if the dictionary contains |word|. Otherwise returns false. - bool HasWord(const std::string& word); + bool HasWord(const std::string& word) const; // Adds |observer| to be notified of dictionary events and changes. void AddObserver(Observer* observer); diff --git a/chrome/browser/spellchecker/spellcheck_message_filter.cc b/chrome/browser/spellchecker/spellcheck_message_filter.cc index 091a66e..e58be59 100644 --- a/chrome/browser/spellchecker/spellcheck_message_filter.cc +++ b/chrome/browser/spellchecker/spellcheck_message_filter.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/prefs/pref_service.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_host_metrics.h" @@ -87,23 +88,18 @@ void SpellCheckMessageFilter::OnSpellCheckerRequestDictionary() { void SpellCheckMessageFilter::OnNotifyChecked(const string16& word, bool misspelled) { - content::RenderProcessHost* host = - content::RenderProcessHost::FromID(render_process_id_); - if (!host) - return; // Teardown. - // Delegates to SpellCheckHost which tracks the stats of our spellchecker. - Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext()); - SpellcheckService* spellcheck_service = - SpellcheckServiceFactory::GetForProfile(profile); - DCHECK(spellcheck_service); - if (spellcheck_service->GetMetrics()) - spellcheck_service->GetMetrics()->RecordCheckedWordStats(word, misspelled); + SpellcheckService* spellcheck = GetSpellcheckService(); + // Spellcheck service may not be available for a renderer process that is + // shutting down. + if (!spellcheck) + return; + if (spellcheck->GetMetrics()) + spellcheck->GetMetrics()->RecordCheckedWordStats(word, misspelled); } void SpellCheckMessageFilter::OnRespondDocumentMarkers( const std::vector<uint32>& markers) { - SpellcheckService* spellcheck = - SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_); + SpellcheckService* spellcheck = GetSpellcheckService(); // Spellcheck service may not be available for a renderer process that is // shutting down. if (!spellcheck) @@ -137,12 +133,34 @@ void SpellCheckMessageFilter::OnTextCheckComplete( bool success, const string16& text, const std::vector<SpellCheckResult>& results) { - SpellcheckService* spellcheck = - SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_); - DCHECK(spellcheck); + SpellcheckService* spellcheck = GetSpellcheckService(); + // Spellcheck service may not be available for a renderer process that is + // shutting down. + if (!spellcheck) + return; std::vector<SpellCheckResult> results_copy = results; spellcheck->GetFeedbackSender()->OnSpellcheckResults( &results_copy, render_process_id_, text, markers); + + // Erase custom dictionary words from the spellcheck results and record + // in-dictionary feedback. + std::vector<SpellCheckResult>::iterator write_iter; + std::vector<SpellCheckResult>::iterator iter; + std::string text_copy = UTF16ToUTF8(text); + for (iter = write_iter = results_copy.begin(); + iter != results_copy.end(); + ++iter) { + if (spellcheck->GetCustomDictionary()->HasWord( + text_copy.substr(iter->location, iter->length))) { + spellcheck->GetFeedbackSender()->RecordInDictionary(iter->hash); + } else { + if (write_iter != iter) + *write_iter = *iter; + ++write_iter; + } + } + results_copy.erase(write_iter, results_copy.end()); + Send(new SpellCheckMsg_RespondSpellingService( route_id, identifier, success, text, results_copy)); } @@ -171,3 +189,7 @@ void SpellCheckMessageFilter::CallSpellingService( markers)); } #endif + +SpellcheckService* SpellCheckMessageFilter::GetSpellcheckService() const { + return SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_); +} diff --git a/chrome/browser/spellchecker/spellcheck_message_filter.h b/chrome/browser/spellchecker/spellcheck_message_filter.h index 77c8306..885aab1 100644 --- a/chrome/browser/spellchecker/spellcheck_message_filter.h +++ b/chrome/browser/spellchecker/spellcheck_message_filter.h @@ -11,6 +11,7 @@ #include "content/public/browser/browser_message_filter.h" class SpellCheckMarker; +class SpellcheckService; struct SpellCheckResult; // A message filter implementation that receives spell checker requests from @@ -27,6 +28,8 @@ class SpellCheckMessageFilter : public content::BrowserMessageFilter { bool* message_was_ok) OVERRIDE; private: + friend class TestingSpellCheckMessageFilter; + virtual ~SpellCheckMessageFilter(); void OnSpellCheckerRequestDictionary(); @@ -61,6 +64,9 @@ class SpellCheckMessageFilter : public content::BrowserMessageFilter { const std::vector<SpellCheckMarker>& markers); #endif + // Can be overridden for testing. + virtual SpellcheckService* GetSpellcheckService() const; + int render_process_id_; // A JSON-RPC client that calls the Spelling service in the background. diff --git a/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc b/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc index 3407dbd..d0fb26f 100644 --- a/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc +++ b/chrome/browser/spellchecker/spellcheck_message_filter_unittest.cc @@ -2,12 +2,58 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/spellchecker/spellcheck_factory.h" #include "chrome/browser/spellchecker/spellcheck_message_filter.h" +#include "chrome/browser/spellchecker/spellcheck_service.h" +#include "chrome/common/spellcheck_marker.h" #include "chrome/common/spellcheck_messages.h" -#include "content/public/browser/browser_thread.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread.h" #include "ipc/ipc_message.h" #include "testing/gtest/include/gtest/gtest.h" +class TestingSpellCheckMessageFilter : public SpellCheckMessageFilter { + public: + TestingSpellCheckMessageFilter() + : SpellCheckMessageFilter(0), + ui_thread_(content::BrowserThread::UI, &message_loop_), + spellcheck_(new SpellcheckService(&profile_)) {} + + virtual bool Send(IPC::Message* message) OVERRIDE { + sent_messages.push_back(message); + return true; + } + + virtual SpellcheckService* GetSpellcheckService() const OVERRIDE { + return spellcheck_.get(); + } + +#if !defined(OS_MACOSX) + void OnTextCheckComplete(int route_id, + int identifier, + const std::vector<SpellCheckMarker>& markers, + bool success, + const string16& text, + const std::vector<SpellCheckResult>& results) { + SpellCheckMessageFilter::OnTextCheckComplete( + route_id, identifier, markers, success, text, results); + } +#endif + + ScopedVector<IPC::Message> sent_messages; + + private: + virtual ~TestingSpellCheckMessageFilter() {} + + base::MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + TestingProfile profile_; + scoped_ptr<SpellcheckService> spellcheck_; + + DISALLOW_COPY_AND_ASSIGN(TestingSpellCheckMessageFilter); +}; + TEST(SpellCheckMessageFilterTest, TestOverrideThread) { static const uint32 kSpellcheckMessages[] = { SpellCheckHostMsg_RequestDictionary::ID, @@ -17,9 +63,10 @@ TEST(SpellCheckMessageFilterTest, TestOverrideThread) { SpellCheckHostMsg_CallSpellingService::ID, #endif }; - scoped_refptr<SpellCheckMessageFilter> filter(new SpellCheckMessageFilter(0)); content::BrowserThread::ID thread; IPC::Message message; + scoped_refptr<TestingSpellCheckMessageFilter> filter( + new TestingSpellCheckMessageFilter); for (size_t i = 0; i < arraysize(kSpellcheckMessages); ++i) { message.SetHeaderValues( 0, kSpellcheckMessages[i], IPC::Message::PRIORITY_NORMAL); @@ -28,3 +75,75 @@ TEST(SpellCheckMessageFilterTest, TestOverrideThread) { EXPECT_EQ(content::BrowserThread::UI, thread); } } + +#if !defined(OS_MACOSX) +TEST(SpellCheckMessageFilterTest, OnTextCheckCompleteTestCustomDictionary) { + static const std::string kCustomWord = "Helllo"; + static const int kRouteId = 0; + static const int kCallbackId = 0; + static const std::vector<SpellCheckMarker> kMarkers; + static const string16 kText = ASCIIToUTF16("Helllo warld."); + static const bool kSuccess = true; + static const SpellCheckResult::Type kType = SpellCheckResult::SPELLING; + static const int kLocation = 7; + static const int kLength = 5; + static const string16 kReplacement = ASCIIToUTF16("world"); + + std::vector<SpellCheckResult> results; + results.push_back(SpellCheckResult( + SpellCheckResult::SPELLING, 0, 6, ASCIIToUTF16("Hello"))); + results.push_back(SpellCheckResult(kType, kLocation, kLength, kReplacement)); + + scoped_refptr<TestingSpellCheckMessageFilter> filter( + new TestingSpellCheckMessageFilter); + filter->GetSpellcheckService()->GetCustomDictionary()->AddWord(kCustomWord); + filter->OnTextCheckComplete( + kRouteId, kCallbackId, kMarkers, kSuccess, kText, results); + EXPECT_EQ(static_cast<size_t>(1), filter->sent_messages.size()); + + int sent_identifier = -1; + bool sent_success = false; + string16 sent_text; + std::vector<SpellCheckResult> sent_results; + bool ok = SpellCheckMsg_RespondSpellingService::Read(filter->sent_messages[0], + &sent_identifier, + &sent_success, + &sent_text, + &sent_results); + EXPECT_TRUE(ok); + EXPECT_EQ(kCallbackId, sent_identifier); + EXPECT_EQ(kSuccess, sent_success); + EXPECT_EQ(kText, sent_text); + EXPECT_EQ(static_cast<size_t>(1), sent_results.size()); + EXPECT_EQ(kType, sent_results[0].type); + EXPECT_EQ(kLocation, sent_results[0].location); + EXPECT_EQ(kLength, sent_results[0].length); + EXPECT_EQ(kReplacement, sent_results[0].replacement); +} + +TEST(SpellCheckMessageFilterTest, OnTextCheckCompleteTest) { + std::vector<SpellCheckResult> results; + results.push_back(SpellCheckResult( + SpellCheckResult::SPELLING, 0, 6, ASCIIToUTF16("Hello"))); + results.push_back(SpellCheckResult( + SpellCheckResult::SPELLING, 7, 7, ASCIIToUTF16("world"))); + + scoped_refptr<TestingSpellCheckMessageFilter> filter( + new TestingSpellCheckMessageFilter); + filter->OnTextCheckComplete(1, 1, std::vector<SpellCheckMarker>(), + true, ASCIIToUTF16("Helllo walrd"), results); + EXPECT_EQ(static_cast<size_t>(1), filter->sent_messages.size()); + + int sent_identifier = -1; + bool sent_success = false; + string16 sent_text; + std::vector<SpellCheckResult> sent_results; + bool ok = SpellCheckMsg_RespondSpellingService::Read(filter->sent_messages[0], + &sent_identifier, + &sent_success, + &sent_text, + &sent_results); + EXPECT_TRUE(ok); + EXPECT_EQ(static_cast<size_t>(2), sent_results.size()); +} +#endif |