summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/spellchecker/feedback.cc143
-rw-r--r--chrome/browser/spellchecker/feedback.h82
-rw-r--r--chrome/browser/spellchecker/feedback_sender.cc306
-rw-r--r--chrome/browser/spellchecker/feedback_sender.h139
-rw-r--r--chrome/browser/spellchecker/feedback_sender_unittest.cc436
-rw-r--r--chrome/browser/spellchecker/feedback_unittest.cc165
-rw-r--r--chrome/browser/spellchecker/misspelling.cc (renamed from chrome/browser/spellchecker/spellcheck_misspelling.cc)31
-rw-r--r--chrome/browser/spellchecker/misspelling.h (renamed from chrome/browser/spellchecker/spellcheck_misspelling.h)24
-rw-r--r--chrome/browser/spellchecker/misspelling_unittest.cc (renamed from chrome/browser/spellchecker/spellcheck_misspelling_unittest.cc)10
-rw-r--r--chrome/browser/spellchecker/spellcheck_service.cc24
-rw-r--r--chrome/browser/spellchecker/spellcheck_service.h6
-rw-r--r--chrome/browser/spellchecker/spelling_service_feedback.cc35
-rw-r--r--chrome/browser/spellchecker/spelling_service_feedback.h37
-rw-r--r--chrome/chrome_browser.gypi10
-rw-r--r--chrome/chrome_tests_unit.gypi4
-rw-r--r--chrome/common/spellcheck_common.h8
-rw-r--r--chrome/common/spellcheck_result.h6
17 files changed, 1352 insertions, 114 deletions
diff --git a/chrome/browser/spellchecker/feedback.cc b/chrome/browser/spellchecker/feedback.cc
new file mode 100644
index 0000000..7e6ce5f
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/spellchecker/feedback.h"
+
+#include <algorithm>
+#include <iterator>
+#include <set>
+
+namespace spellcheck {
+
+Feedback::Feedback() {
+}
+
+Feedback::~Feedback() {
+}
+
+Misspelling* Feedback::GetMisspelling(uint32 hash) {
+ std::map<uint32, Misspelling>::iterator it = misspellings_.find(hash);
+ if (it == misspellings_.end())
+ return NULL;
+ return &it->second;
+}
+
+void Feedback::FinalizeRemovedMisspellings(
+ int renderer_process_id,
+ const std::vector<uint32>& remaining_markers) {
+ std::map<int, std::vector<uint32> >::iterator i =
+ hashes_.find(renderer_process_id);
+ if (i == hashes_.end() || i->second.empty())
+ return;
+ std::vector<uint32> remaining_copy(remaining_markers);
+ std::sort(remaining_copy.begin(), remaining_copy.end());
+ std::sort(i->second.begin(), i->second.end());
+ std::vector<uint32> removed_markers;
+ std::set_difference(i->second.begin(),
+ i->second.end(),
+ remaining_copy.begin(),
+ remaining_copy.end(),
+ std::back_inserter(removed_markers));
+ for (std::vector<uint32>::const_iterator j = removed_markers.begin();
+ j != removed_markers.end();
+ ++j) {
+ std::map<uint32, Misspelling>::iterator k = misspellings_.find(*j);
+ if (k != misspellings_.end() && !k->second.action.IsFinal())
+ k->second.action.Finalize();
+ }
+}
+
+bool Feedback::RendererHasMisspellings(int renderer_process_id) const {
+ std::map<int, std::vector<uint32> >::const_iterator it =
+ hashes_.find(renderer_process_id);
+ return it != hashes_.end() && !it->second.empty();
+}
+
+std::vector<Misspelling> Feedback::GetMisspellingsInRenderer(
+ int renderer_process_id) const {
+ std::vector<Misspelling> result;
+ std::map<int, std::vector<uint32> >::const_iterator i =
+ hashes_.find(renderer_process_id);
+ if (i == hashes_.end() || i->second.empty())
+ return result;
+ for (std::vector<uint32>::const_iterator j = i->second.begin();
+ j != i->second.end();
+ ++j) {
+ std::map<uint32, Misspelling>::const_iterator k = misspellings_.find(*j);
+ if (k != misspellings_.end())
+ result.push_back(k->second);
+ }
+ return result;
+}
+
+void Feedback::EraseFinalizedMisspellings(int renderer_process_id) {
+ std::map<int, std::vector<uint32> >::iterator i =
+ hashes_.find(renderer_process_id);
+ if (i == hashes_.end() || i->second.empty())
+ return;
+ std::vector<uint32> pending;
+ for (std::vector<uint32>::const_iterator j = i->second.begin();
+ j != i->second.end();
+ ++j) {
+ std::map<uint32, Misspelling>::iterator k = misspellings_.find(*j);
+ if (k != misspellings_.end()) {
+ if (k->second.action.IsFinal())
+ misspellings_.erase(k);
+ else
+ pending.push_back(*j);
+ }
+ }
+ i->second.swap(pending);
+}
+
+bool Feedback::HasMisspelling(uint32 hash) const {
+ return !!misspellings_.count(hash);
+}
+
+void Feedback::AddMisspelling(int renderer_process_id,
+ const Misspelling& misspelling) {
+ misspellings_[misspelling.hash] = misspelling;
+ hashes_[renderer_process_id].push_back(misspelling.hash);
+}
+
+bool Feedback::Empty() const {
+ return misspellings_.empty();
+}
+
+std::vector<int> Feedback::GetRendersWithMisspellings() const {
+ std::vector<int> result;
+ for (std::map<int, std::vector<uint32> >::const_iterator it = hashes_.begin();
+ it != hashes_.end();
+ ++it) {
+ if (!it->second.empty())
+ result.push_back(it->first);
+ }
+ return result;
+}
+
+void Feedback::FinalizeAllMisspellings() {
+ for (std::map<uint32, Misspelling>::iterator it = misspellings_.begin();
+ it != misspellings_.end();
+ ++it) {
+ if (!it->second.action.IsFinal())
+ it->second.action.Finalize();
+ }
+}
+
+std::vector<Misspelling> Feedback::GetAllMisspellings() const {
+ std::vector<Misspelling> result;
+ for (std::map<uint32, Misspelling>::const_iterator it = misspellings_.begin();
+ it != misspellings_.end();
+ ++it) {
+ result.push_back(it->second);
+ }
+ return result;
+}
+
+void Feedback::Clear() {
+ misspellings_.clear();
+ hashes_.clear();
+}
+
+} // namespace spellcheck
diff --git a/chrome/browser/spellchecker/feedback.h b/chrome/browser/spellchecker/feedback.h
new file mode 100644
index 0000000..0949002
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SPELLCHECKER_FEEDBACK_H_
+#define CHROME_BROWSER_SPELLCHECKER_FEEDBACK_H_
+
+#include <map>
+#include <vector>
+
+#include "chrome/browser/spellchecker/misspelling.h"
+
+namespace spellcheck {
+
+// Stores feedback for the spelling service in |Misspelling| objects. Each
+// |Misspelling| object is identified by a |hash| and corresponds to a document
+// marker with the same |hash| identifier in the renderer.
+class Feedback {
+ public:
+ Feedback();
+ ~Feedback();
+
+ // Returns the misspelling identified by |hash|. Returns NULL if there's no
+ // misspelling identified by |hash|. Retains the ownership of the result.
+ Misspelling* GetMisspelling(uint32 hash);
+
+ // Finalizes the user actions on misspellings that are removed from the
+ // renderer process with ID |renderer_process_id|.
+ void FinalizeRemovedMisspellings(
+ int renderer_process_id,
+ const std::vector<uint32>& remaining_markers);
+
+ // Returns true if the renderer with process ID |renderer_process_id| has
+ // misspellings. Otherwise returns false.
+ bool RendererHasMisspellings(int renderer_process_id) const;
+
+ // Returns a copy of the misspellings in renderer with process ID
+ // |renderer_process_id|.
+ std::vector<Misspelling> GetMisspellingsInRenderer(
+ int renderer_process_id) const;
+
+ // Erases the misspellings with final user actions in the renderer with
+ // process ID |renderer_process_id|.
+ void EraseFinalizedMisspellings(int renderer_process_id);
+
+ // Returns true if there's a misspelling with |hash| identifier. Otherwise
+ // returns false.
+ bool HasMisspelling(uint32 hash) const;
+
+ // Adds the |misspelling| to feedback data. If the |misspelling| has a
+ // duplicate hash, then replaces the existing misspelling with the same hash.
+ void AddMisspelling(int renderer_process_id, const Misspelling& misspelling);
+
+ // Returns true if there're no misspellings. Otherwise returns false.
+ bool Empty() const;
+
+ // Returns a list of process identifiers for renderers that have misspellings.
+ std::vector<int> GetRendersWithMisspellings() const;
+
+ // Finalizes all misspellings.
+ void FinalizeAllMisspellings();
+
+ // Returns a copy of all misspellings.
+ std::vector<Misspelling> GetAllMisspellings() const;
+
+ // Removes all misspellings.
+ void Clear();
+
+ private:
+ // A map of hashes that identify document markers to feedback data to be sent
+ // to spelling service.
+ std::map<uint32, Misspelling> misspellings_;
+
+ // A map of renderer process ID to hashes that identify misspellings.
+ std::map<int, std::vector<uint32> > hashes_;
+
+ DISALLOW_COPY_AND_ASSIGN(Feedback);
+};
+
+} // namespace spellcheck
+
+#endif // CHROME_BROWSER_SPELLCHECKER_FEEDBACK_H_
diff --git a/chrome/browser/spellchecker/feedback_sender.cc b/chrome/browser/spellchecker/feedback_sender.cc
new file mode 100644
index 0000000..c7cbdb8
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback_sender.cc
@@ -0,0 +1,306 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/spellchecker/feedback_sender.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/command_line.h"
+#include "base/hash.h"
+#include "base/json/json_writer.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "chrome/browser/spellchecker/word_trimmer.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/spellcheck_common.h"
+#include "chrome/common/spellcheck_marker.h"
+#include "chrome/common/spellcheck_messages.h"
+#include "content/public/browser/render_process_host.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace spellcheck {
+
+namespace {
+
+// The default URL where feedback data is sent.
+const char kFeedbackServiceURL[] = "https://www.googleapis.com/rpc";
+
+// Returns a hash of |session_start|, the current timestamp, and
+// |suggestion_index|.
+uint32 BuildHash(const base::Time& session_start, size_t suggestion_index) {
+ std::stringstream hash_data;
+ hash_data << session_start.ToTimeT()
+ << base::Time::Now().ToTimeT()
+ << suggestion_index;
+ return base::Hash(hash_data.str());
+}
+
+// Returns a pending feedback data structure for the spellcheck |result| and
+// |text|.
+Misspelling BuildFeedback(const SpellCheckResult& result,
+ const string16& text) {
+ size_t start = result.location;
+ string16 context = TrimWords(&start,
+ result.length,
+ text,
+ chrome::spellcheck_common::kContextWordCount);
+ return Misspelling(context,
+ start,
+ result.length,
+ std::vector<string16>(1, result.replacement),
+ result.hash);
+}
+
+// Builds suggestion info from |suggestions|. The caller owns the result.
+base::ListValue* BuildSuggestionInfo(
+ const std::vector<Misspelling>& suggestions,
+ bool is_first_feedback_batch) {
+ base::ListValue* list = new base::ListValue;
+ for (std::vector<Misspelling>::const_iterator it = suggestions.begin();
+ it != suggestions.end();
+ ++it) {
+ base::DictionaryValue* suggestion = it->Serialize();
+ suggestion->SetBoolean("isFirstInSession", is_first_feedback_batch);
+ suggestion->SetBoolean("isAutoCorrection", false);
+ list->Append(suggestion);
+ }
+ return list;
+}
+
+// Builds feedback parameters from |suggestion_info|, |language|, and |country|.
+// Takes ownership of |suggestion_list|. The caller owns the result.
+base::DictionaryValue* BuildParams(base::ListValue* suggestion_info,
+ const std::string& language,
+ const std::string& country) {
+ base::DictionaryValue* params = new base::DictionaryValue;
+ params->Set("suggestionInfo", suggestion_info);
+ params->SetString("key", google_apis::GetAPIKey());
+ params->SetString("language", language);
+ params->SetString("originCountry", country);
+ params->SetString("clientName", "Chrome");
+ return params;
+}
+
+// Builds feedback data from |params|. Takes ownership of |params|. The caller
+// owns the result.
+base::Value* BuildFeedbackValue(base::DictionaryValue* params) {
+ base::DictionaryValue* result = new base::DictionaryValue;
+ result->Set("params", params);
+ result->SetString("method", "spelling.feedback");
+ result->SetString("apiVersion", "v2");
+ return result;
+}
+
+} // namespace
+
+FeedbackSender::FeedbackSender(net::URLRequestContextGetter* request_context,
+ const std::string& language,
+ const std::string& country)
+ : request_context_(request_context),
+ language_(language),
+ country_(country),
+ misspelling_counter_(0),
+ session_start_(base::Time::Now()),
+ feedback_service_url_(kFeedbackServiceURL) {
+ // The command-line switch is for testing and temporary.
+ // TODO(rouslan): Remove the command-line switch when testing is complete by
+ // August 2013.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSpellingServiceFeedbackUrl)) {
+ feedback_service_url_ =
+ GURL(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kSpellingServiceFeedbackUrl));
+ }
+
+ timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(
+ chrome::spellcheck_common::kFeedbackIntervalSeconds),
+ this,
+ &FeedbackSender::RequestDocumentMarkers);
+}
+
+FeedbackSender::~FeedbackSender() {
+}
+
+void FeedbackSender::SelectedSuggestion(uint32 hash, int suggestion_index) {
+ Misspelling* misspelling = feedback_.GetMisspelling(hash);
+ if (!misspelling)
+ return;
+ misspelling->action.type = SpellcheckAction::TYPE_SELECT;
+ misspelling->action.index = suggestion_index;
+ misspelling->timestamp = base::Time::Now();
+}
+
+void FeedbackSender::AddedToDictionary(uint32 hash) {
+ Misspelling* misspelling = feedback_.GetMisspelling(hash);
+ if (!misspelling)
+ return;
+ misspelling->action.type = SpellcheckAction::TYPE_ADD_TO_DICT;
+ misspelling->timestamp = base::Time::Now();
+}
+
+void FeedbackSender::IgnoredSuggestions(uint32 hash) {
+ Misspelling* misspelling = feedback_.GetMisspelling(hash);
+ if (!misspelling)
+ return;
+ misspelling->action.type = SpellcheckAction::TYPE_PENDING_IGNORE;
+ misspelling->timestamp = base::Time::Now();
+}
+
+void FeedbackSender::ManuallyCorrected(uint32 hash,
+ const string16& correction) {
+ Misspelling* misspelling = feedback_.GetMisspelling(hash);
+ if (!misspelling)
+ return;
+ misspelling->action.type = SpellcheckAction::TYPE_MANUALLY_CORRECTED;
+ misspelling->action.value = correction;
+ misspelling->timestamp = base::Time::Now();
+}
+
+void FeedbackSender::OnReceiveDocumentMarkers(
+ int renderer_process_id,
+ const std::vector<uint32>& markers) {
+ if ((base::Time::Now() - session_start_).InHours() >=
+ chrome::spellcheck_common::kSessionHours) {
+ FlushFeedback();
+ return;
+ }
+
+ if (!feedback_.RendererHasMisspellings(renderer_process_id))
+ return;
+
+ feedback_.FinalizeRemovedMisspellings(renderer_process_id, markers);
+ SendFeedback(feedback_.GetMisspellingsInRenderer(renderer_process_id),
+ !renderers_sent_feedback_.count(renderer_process_id));
+ renderers_sent_feedback_.insert(renderer_process_id);
+ feedback_.EraseFinalizedMisspellings(renderer_process_id);
+}
+
+void FeedbackSender::OnSpellcheckResults(
+ std::vector<SpellCheckResult>* results,
+ int renderer_process_id,
+ const string16& text,
+ const std::vector<SpellCheckMarker>& markers) {
+ // Generate a map of marker offsets to marker hashes. This map helps to
+ // efficiently lookup feedback data based on the position of the misspelling
+ // in text
+ typedef std::map<size_t, uint32> MarkerMap;
+ MarkerMap marker_map;
+ for (size_t i = 0; i < markers.size(); ++i)
+ marker_map[markers[i].offset] = markers[i].hash;
+
+ for (std::vector<SpellCheckResult>::iterator result_iter = results->begin();
+ result_iter != results->end();
+ ++result_iter) {
+ MarkerMap::iterator marker_iter = marker_map.find(result_iter->location);
+ if (marker_iter != marker_map.end() &&
+ feedback_.HasMisspelling(marker_iter->second)) {
+ // If the renderer already has a marker for this spellcheck result, then
+ // set the hash of the spellcheck result to be the same as the marker.
+ result_iter->hash = marker_iter->second;
+ } else {
+ // If the renderer does not yet have a marker for this spellcheck result,
+ // then generate a new hash for the spellcheck result.
+ result_iter->hash = BuildHash(session_start_, ++misspelling_counter_);
+ }
+ // Save the feedback data for the spellcheck result.
+ feedback_.AddMisspelling(renderer_process_id,
+ BuildFeedback(*result_iter, text));
+ }
+}
+
+void FeedbackSender::OnLanguageCountryChange(const std::string& language,
+ const std::string& country) {
+ FlushFeedback();
+ language_ = language;
+ country_ = country;
+}
+
+void FeedbackSender::OnURLFetchComplete(const net::URLFetcher* source) {
+ for (ScopedVector<net::URLFetcher>::iterator it = senders_.begin();
+ it != senders_.end();
+ ++it) {
+ if (*it == source) {
+ senders_.erase(it);
+ break;
+ }
+ }
+}
+
+void FeedbackSender::RequestDocumentMarkers() {
+ // Request document markers from all the renderers that are still alive.
+ std::vector<int> alive_renderers;
+ for (content::RenderProcessHost::iterator it(
+ content::RenderProcessHost::AllHostsIterator());
+ !it.IsAtEnd();
+ it.Advance()) {
+ alive_renderers.push_back(it.GetCurrentValue()->GetID());
+ it.GetCurrentValue()->Send(new SpellCheckMsg_RequestDocumentMarkers());
+ }
+
+ // Asynchronously send out the feedback for all the renderers that are no
+ // longer alive.
+ std::vector<int> known_renderers = feedback_.GetRendersWithMisspellings();
+ std::sort(alive_renderers.begin(), alive_renderers.end());
+ std::sort(known_renderers.begin(), known_renderers.end());
+ std::vector<int> dead_renderers;
+ std::set_difference(known_renderers.begin(),
+ known_renderers.end(),
+ alive_renderers.begin(),
+ alive_renderers.end(),
+ std::back_inserter(dead_renderers));
+ for (std::vector<int>::const_iterator it = dead_renderers.begin();
+ it != dead_renderers.end();
+ ++it) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FeedbackSender::OnReceiveDocumentMarkers,
+ AsWeakPtr(),
+ *it,
+ std::vector<uint32>()));
+ }
+}
+
+void FeedbackSender::FlushFeedback() {
+ if (feedback_.Empty())
+ return;
+ feedback_.FinalizeAllMisspellings();
+ SendFeedback(feedback_.GetAllMisspellings(),
+ renderers_sent_feedback_.empty());
+ feedback_.Clear();
+ renderers_sent_feedback_.clear();
+ session_start_ = base::Time::Now();
+ timer_.Reset();
+}
+
+void FeedbackSender::SendFeedback(const std::vector<Misspelling>& feedback_data,
+ bool is_first_feedback_batch) {
+ scoped_ptr<base::Value> feedback_value(BuildFeedbackValue(
+ BuildParams(BuildSuggestionInfo(feedback_data, is_first_feedback_batch),
+ language_,
+ country_)));
+ std::string feedback;
+ base::JSONWriter::Write(feedback_value.get(), &feedback);
+
+ // The tests use this identifier to mock the URL fetcher.
+ static const int kUrlFetcherId = 0;
+ net::URLFetcher* sender = net::URLFetcher::Create(
+ kUrlFetcherId, feedback_service_url_, net::URLFetcher::POST, this);
+ sender->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ sender->SetUploadData("application/json", feedback);
+ senders_.push_back(sender);
+
+ // Request context is NULL in testing.
+ if (request_context_.get()) {
+ sender->SetRequestContext(request_context_.get());
+ sender->Start();
+ }
+}
+
+} // namespace spellcheck
diff --git a/chrome/browser/spellchecker/feedback_sender.h b/chrome/browser/spellchecker/feedback_sender.h
new file mode 100644
index 0000000..082ff20
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback_sender.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SPELLCHECKER_FEEDBACK_SENDER_H_
+#define CHROME_BROWSER_SPELLCHECKER_FEEDBACK_SENDER_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer.h"
+#include "chrome/browser/spellchecker/feedback.h"
+#include "chrome/browser/spellchecker/misspelling.h"
+#include "googleurl/src/gurl.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+class SpellingServiceFeedbackTest;
+struct SpellCheckMarker;
+struct SpellCheckResult;
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+namespace spellcheck {
+
+// Manages sending feedback to the spelling service.
+class FeedbackSender : public base::SupportsWeakPtr<FeedbackSender>,
+ public net::URLFetcherDelegate {
+ public:
+ FeedbackSender(net::URLRequestContextGetter* request_context,
+ const std::string& language,
+ const std::string& country);
+ virtual ~FeedbackSender();
+
+ // Records that user selected suggestion |suggestion_index| for the
+ // misspelling identified by |hash|.
+ void SelectedSuggestion(uint32 hash, int suggestion_index);
+
+ // Records that user added the misspelling identified by |hash| to the
+ // dictionary.
+ void AddedToDictionary(uint32 hash);
+
+ // Records that user right-clicked on the misspelling identified by |hash|,
+ // but did not select any suggestion.
+ void IgnoredSuggestions(uint32 hash);
+
+ // Records that user did not choose any suggestion but manually corrected the
+ // misspelling identified by |hash| to string |correction|, which is not in
+ // the list of suggestions.
+ void ManuallyCorrected(uint32 hash, const string16& correction);
+
+ // 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
+ // data for the renderer with process ID |renderer_process_id| to the spelling
+ // service. If the current session has expired, then refreshes the session
+ // start timestamp and sends out all of the feedback data.
+ void OnReceiveDocumentMarkers(int renderer_process_id,
+ const std::vector<uint32>& markers);
+
+ // Generates feedback data based on spellcheck results. The new feedback data
+ // is pending. Sets hash identifiers for |results|. Called when spelling
+ // service client receives results from the spelling service.
+ void OnSpellcheckResults(std::vector<SpellCheckResult>* results,
+ int renderer_process_id,
+ const string16& text,
+ const std::vector<SpellCheckMarker>& markers);
+
+ // Receives updated language and country code for feedback. Finalizes and
+ // sends out all of the feedback data.
+ void OnLanguageCountryChange(const std::string& language,
+ const std::string& country);
+
+ private:
+ friend class FeedbackSenderTest;
+
+ // net::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ // Requests the document markers from all of the renderers to determine which
+ // feedback can be finalized. Finalizes feedback for renderers that are gone.
+ // Called periodically when |timer_| fires.
+ void RequestDocumentMarkers();
+
+ // Sends out all feedback data. Resets the session-start timestamp to now.
+ // Restarts the timer that requests markers from the renderers.
+ void FlushFeedback();
+
+ // Sends out the |feedback_data|.
+ void SendFeedback(const std::vector<Misspelling>& feedback_data,
+ bool is_first_feedback_batch);
+
+ // Request context for the feedback senders.
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+ // The language of text. The string is a BCP 47 language tag.
+ std::string language_;
+
+ // The country of origin. The string is the ISO 3166-1 alpha-3 code.
+ std::string country_;
+
+ // Misspelling counter used to generate unique hash identifier for each
+ // misspelling.
+ size_t misspelling_counter_;
+
+ // Feedback data.
+ Feedback feedback_;
+
+ // A set of renderer process IDs for renderers that have sent out feedback in
+ // this session.
+ std::set<int> renderers_sent_feedback_;
+
+ // When the session started.
+ base::Time session_start_;
+
+ // The URL where the feedback data should be sent.
+ GURL feedback_service_url_;
+
+ // A timer to periodically request a list of document spelling markers from
+ // all of the renderers. The timer runs while an instance of this class is
+ // alive.
+ base::RepeatingTimer<FeedbackSender> timer_;
+
+ // Feedback senders that need to stay alive for the duration of sending data.
+ // If a sender is destroyed before it finishes, then sending feedback will be
+ // canceled.
+ ScopedVector<net::URLFetcher> senders_;
+
+ DISALLOW_COPY_AND_ASSIGN(FeedbackSender);
+};
+
+} // namespace spellcheck
+
+#endif // CHROME_BROWSER_SPELLCHECKER_FEEDBACK_SENDER_H_
diff --git a/chrome/browser/spellchecker/feedback_sender_unittest.cc b/chrome/browser/spellchecker/feedback_sender_unittest.cc
new file mode 100644
index 0000000..23eaf5a
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback_sender_unittest.cc
@@ -0,0 +1,436 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/spellchecker/feedback_sender.h"
+#include "chrome/common/spellcheck_common.h"
+#include "chrome/common/spellcheck_marker.h"
+#include "chrome/common/spellcheck_result.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spellcheck {
+
+namespace {
+
+static const int kMisspellingLength = 6;
+static const int kMisspellingStart = 0;
+static const int kRendererProcessId = 0;
+static const int kUrlFetcherId = 0;
+static const std::string kCountry = "USA";
+static const std::string kLanguage = "en";
+static const string16 kText = ASCIIToUTF16("Helllo world.");
+
+SpellCheckResult BuildSpellCheckResult() {
+ return SpellCheckResult(SpellCheckResult::SPELLING,
+ kMisspellingStart,
+ kMisspellingLength,
+ ASCIIToUTF16("Hello"));
+}
+
+} // namespace
+
+class FeedbackSenderTest : public testing::Test {
+ public:
+ FeedbackSenderTest()
+ : ui_thread_(content::BrowserThread::UI, &loop_),
+ feedback_(NULL, kLanguage, kCountry) {}
+ virtual ~FeedbackSenderTest() {}
+
+ private:
+ TestingProfile profile_;
+ base::MessageLoop loop_;
+ content::TestBrowserThread ui_thread_;
+
+ protected:
+ uint32 AddPendingFeedback() {
+ std::vector<SpellCheckResult> results(1, BuildSpellCheckResult());
+ feedback_.OnSpellcheckResults(
+ &results, kRendererProcessId, kText, std::vector<SpellCheckMarker>());
+ return results[0].hash;
+ }
+
+ void ExpireSession() {
+ feedback_.session_start_ =
+ base::Time::Now() -
+ base::TimeDelta::FromHours(chrome::spellcheck_common::kSessionHours);
+ }
+
+ net::TestURLFetcher* GetFetcher() {
+ return fetchers_.GetFetcherByID(kUrlFetcherId);
+ }
+
+ net::TestURLFetcherFactory fetchers_;
+ spellcheck::FeedbackSender feedback_;
+};
+
+// Do not send data if there's no feedback.
+TEST_F(FeedbackSenderTest, NoFeedback) {
+ EXPECT_EQ(NULL, GetFetcher());
+ feedback_.OnReceiveDocumentMarkers(kRendererProcessId,
+ std::vector<uint32>());
+ EXPECT_EQ(NULL, GetFetcher());
+}
+
+// Do not send data if not aware of which markers are still in the document.
+TEST_F(FeedbackSenderTest, NoDocumentMarkersReceived) {
+ EXPECT_EQ(NULL, GetFetcher());
+ uint32 hash = AddPendingFeedback();
+ EXPECT_EQ(NULL, GetFetcher());
+ static const int kSuggestionIndex = 1;
+ feedback_.SelectedSuggestion(hash, kSuggestionIndex);
+ EXPECT_EQ(NULL, GetFetcher());
+}
+
+// Send PENDING feedback message if the marker is still in the document, and the
+// user has not performed any action on it.
+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();
+}
+
+// Send NO_ACTION feedback message if the marker has been removed from the
+// document.
+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();
+}
+
+// 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);
+ 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();
+}
+
+// Send ADD_TO_DICT feedback message if the user has added the misspelled word
+// to the custom dictionary.
+TEST_F(FeedbackSenderTest, AddToDictFeedback) {
+ uint32 hash = AddPendingFeedback();
+ 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();
+}
+
+// Send PENDING feedback message if the user saw the spelling suggestion, but
+// decided to not select it, and the marker is still in the document.
+TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerInDocument) {
+ uint32 hash = AddPendingFeedback();
+ 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();
+}
+
+// Send IGNORE feedback message if the user saw the spelling suggestion, but
+// decided to not select it, and the marker is no longer in the document.
+TEST_F(FeedbackSenderTest, IgnoreFeedbackMarkerNotInDocument) {
+ uint32 hash = AddPendingFeedback();
+ 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();
+}
+
+// Send MANUALLY_CORRECTED feedback message if the user manually corrected the
+// misspelled word.
+TEST_F(FeedbackSenderTest, ManuallyCorrectedFeedback) {
+ uint32 hash = AddPendingFeedback();
+ static const std::string kManualCorrection = "Howdy";
+ 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();
+}
+
+// Send feedback messages in batch.
+TEST_F(FeedbackSenderTest, BatchFeedback) {
+ std::vector<SpellCheckResult> results;
+ results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
+ kMisspellingStart,
+ kMisspellingLength,
+ ASCIIToUTF16("Hello")));
+ static const int kSecondMisspellingStart = 7;
+ static const int kSecondMisspellingLength = 5;
+ results.push_back(SpellCheckResult(SpellCheckResult::SPELLING,
+ kSecondMisspellingStart,
+ kSecondMisspellingLength,
+ ASCIIToUTF16("world")));
+ feedback_.OnSpellcheckResults(
+ &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();
+}
+
+// Send a series of PENDING feedback messages and one final NO_ACTION feedback
+// message with the same hash identifier for a single misspelling.
+TEST_F(FeedbackSenderTest, SameHashFeedback) {
+ uint32 hash = AddPendingFeedback();
+ 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();
+ 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);
+
+ 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);
+
+ 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);
+
+ feedback_.OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>());
+ EXPECT_EQ(NULL, GetFetcher());
+}
+
+// When a session expires:
+// 1) Pending feedback is finalized and sent to the server in the last message
+// batch in the session.
+// 2) No feedback is sent until a spellcheck request happens.
+// 3) Existing markers get new hash identifiers.
+TEST_F(FeedbackSenderTest, SessionExpirationFeedback) {
+ std::vector<SpellCheckResult> results(1, SpellCheckResult(
+ SpellCheckResult::SPELLING,
+ kMisspellingStart,
+ kMisspellingLength,
+ ASCIIToUTF16("Hello")));
+ feedback_.OnSpellcheckResults(
+ &results, kRendererProcessId, kText, std::vector<SpellCheckMarker>());
+ uint32 original_hash = results[0].hash;
+ 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();
+ 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);
+
+ 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);
+
+ // 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());
+
+ // The first spellcheck request after session expiration creates different
+ // document marker hash identifiers.
+ std::vector<SpellCheckMarker> original_markers(
+ 1, SpellCheckMarker(results[0].hash, results[0].location));
+ results[0] = SpellCheckResult(SpellCheckResult::SPELLING,
+ kMisspellingStart,
+ kMisspellingLength,
+ ASCIIToUTF16("Hello"));
+ feedback_.OnSpellcheckResults(
+ &results, kRendererProcessId, kText, original_markers);
+ uint32 updated_hash = results[0].hash;
+ EXPECT_NE(updated_hash, original_hash);
+ remaining_markers[0] = updated_hash;
+
+ // 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();
+ 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();
+}
+
+// First message in session has an indicator.
+TEST_F(FeedbackSenderTest, FirstMessageInSessionIndicator) {
+ // Session 1, message 1
+ 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();
+
+ // 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();
+
+ ExpireSession();
+
+ // Session 1, message 3 (last)
+ AddPendingFeedback();
+ feedback_.OnReceiveDocumentMarkers(kRendererProcessId, std::vector<uint32>());
+ fetcher = GetFetcher();
+ EXPECT_NE(std::string::npos,
+ fetcher->upload_data().find("\"isFirstInSession\":false"))
+ << fetcher->upload_data();
+
+ // 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();
+
+ // 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();
+}
+
+// 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();
+
+ AddPendingFeedback();
+ feedback_.OnLanguageCountryChange("en", "US");
+ fetcher = GetFetcher();
+ EXPECT_NE(std::string::npos,
+ fetcher->upload_data().find("\"language\":\"pt\""))
+ << fetcher->upload_data();
+}
+
+// The field names and types should correspond to the API.
+TEST_F(FeedbackSenderTest, FeedbackAPI) {
+ AddPendingFeedback();
+ feedback_.OnReceiveDocumentMarkers(kRendererProcessId,
+ std::vector<uint32>());
+ std::string actual_data = GetFetcher()->upload_data();
+ scoped_ptr<base::DictionaryValue> actual(static_cast<base::DictionaryValue*>(
+ base::JSONReader::Read(actual_data)));
+ actual->SetString("params.key", "TestDummyKey");
+ base::ListValue* suggestions = NULL;
+ actual->GetList("params.suggestionInfo", &suggestions);
+ base::DictionaryValue* suggestion = NULL;
+ suggestions->GetDictionary(0, &suggestion);
+ suggestion->SetString("suggestionId", "42");
+ suggestion->SetString("timestamp", "9001");
+ static const std::string expected_data =
+ "{\"apiVersion\":\"v2\","
+ "\"method\":\"spelling.feedback\","
+ "\"params\":"
+ "{\"clientName\":\"Chrome\","
+ "\"originCountry\":\"USA\","
+ "\"key\":\"TestDummyKey\","
+ "\"language\":\"en\","
+ "\"suggestionInfo\":[{"
+ "\"isAutoCorrection\":false,"
+ "\"isFirstInSession\":true,"
+ "\"misspelledLength\":6,"
+ "\"misspelledStart\":0,"
+ "\"originalText\":\"Helllo world\","
+ "\"suggestionId\":\"42\","
+ "\"suggestions\":[\"Hello\"],"
+ "\"timestamp\":\"9001\","
+ "\"userActions\":[{\"actionType\":\"NO_ACTION\"}]}]}}";
+ scoped_ptr<base::Value> expected(base::JSONReader::Read(expected_data));
+ EXPECT_TRUE(expected->Equals(actual.get()))
+ << "Expected data: " << expected_data
+ << "\nActual data: " << actual_data;
+}
+
+} // namespace spellcheck
diff --git a/chrome/browser/spellchecker/feedback_unittest.cc b/chrome/browser/spellchecker/feedback_unittest.cc
new file mode 100644
index 0000000..2a8cfa2
--- /dev/null
+++ b/chrome/browser/spellchecker/feedback_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/spellchecker/feedback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace spellcheck {
+
+namespace {
+
+// Identifier for a renderer process.
+static const int kRendererProcessId = 7;
+
+// Hash identifier for a misspelling.
+static const uint32 kMisspellingHash = 42;
+
+} // namespace
+
+class FeedbackTest : public testing::Test {
+ public:
+ FeedbackTest() {}
+ virtual ~FeedbackTest() {}
+
+ protected:
+ void AddMisspelling(int renderer_process_id, uint32 hash) {
+ Misspelling misspelling;
+ misspelling.hash = hash;
+ feedback_.AddMisspelling(renderer_process_id, misspelling);
+ }
+
+ spellcheck::Feedback feedback_;
+};
+
+// Should be able to retrieve misspelling after it's added.
+TEST_F(FeedbackTest, RetreiveMisspelling) {
+ EXPECT_EQ(NULL, feedback_.GetMisspelling(kMisspellingHash));
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ Misspelling* result = feedback_.GetMisspelling(kMisspellingHash);
+ EXPECT_NE(static_cast<Misspelling*>(NULL), result);
+ EXPECT_EQ(kMisspellingHash, result->hash);
+}
+
+// Removed misspellings should be finalized.
+TEST_F(FeedbackTest, FinalizeRemovedMisspellings) {
+ static const int kRemovedMisspellingHash = 1;
+ static const int kRemainingMisspellingHash = 2;
+ AddMisspelling(kRendererProcessId, kRemovedMisspellingHash);
+ AddMisspelling(kRendererProcessId, kRemainingMisspellingHash);
+ std::vector<uint32> remaining_markers(1, kRemainingMisspellingHash);
+ feedback_.FinalizeRemovedMisspellings(kRendererProcessId, remaining_markers);
+ Misspelling* removed_misspelling =
+ feedback_.GetMisspelling(kRemovedMisspellingHash);
+ EXPECT_NE(static_cast<Misspelling*>(NULL), removed_misspelling);
+ EXPECT_TRUE(removed_misspelling->action.IsFinal());
+ Misspelling* remaining_misspelling =
+ feedback_.GetMisspelling(kRemainingMisspellingHash);
+ EXPECT_NE(static_cast<Misspelling*>(NULL), remaining_misspelling);
+ EXPECT_FALSE(remaining_misspelling->action.IsFinal());
+}
+
+// Misspellings should be associated with a renderer.
+TEST_F(FeedbackTest, RendererHasMisspellings) {
+ EXPECT_FALSE(feedback_.RendererHasMisspellings(kRendererProcessId));
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ EXPECT_TRUE(feedback_.RendererHasMisspellings(kRendererProcessId));
+}
+
+// Should be able to retrieve misspellings in renderer.
+TEST_F(FeedbackTest, GetMisspellingsInRenderer) {
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ const std::vector<Misspelling>& renderer_with_misspellings =
+ feedback_.GetMisspellingsInRenderer(kRendererProcessId);
+ EXPECT_EQ(static_cast<size_t>(1), renderer_with_misspellings.size());
+ EXPECT_EQ(kMisspellingHash, renderer_with_misspellings[0].hash);
+ const std::vector<Misspelling>& renderer_without_misspellings =
+ feedback_.GetMisspellingsInRenderer(kRendererProcessId + 1);
+ EXPECT_EQ(static_cast<size_t>(0), renderer_without_misspellings.size());
+}
+
+// Finalized misspellings should be erased.
+TEST_F(FeedbackTest, EraseFinalizedMisspellings) {
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ feedback_.FinalizeRemovedMisspellings(kRendererProcessId,
+ std::vector<uint32>());
+ EXPECT_TRUE(feedback_.RendererHasMisspellings(kRendererProcessId));
+ feedback_.EraseFinalizedMisspellings(kRendererProcessId);
+ EXPECT_FALSE(feedback_.RendererHasMisspellings(kRendererProcessId));
+}
+
+// Should be able to check for misspelling existence.
+TEST_F(FeedbackTest, HasMisspelling) {
+ EXPECT_FALSE(feedback_.HasMisspelling(kMisspellingHash));
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ EXPECT_TRUE(feedback_.HasMisspelling(kMisspellingHash));
+}
+
+// Should be able to check for feedback data presence.
+TEST_F(FeedbackTest, EmptyFeedback) {
+ EXPECT_TRUE(feedback_.Empty());
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ EXPECT_FALSE(feedback_.Empty());
+}
+
+// Should be able to retrieve a list of all renderers with misspellings.
+TEST_F(FeedbackTest, GetRendersWithMisspellings) {
+ EXPECT_TRUE(feedback_.GetRendersWithMisspellings().empty());
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ AddMisspelling(kRendererProcessId + 1, kMisspellingHash + 1);
+ std::vector<int> result = feedback_.GetRendersWithMisspellings();
+ EXPECT_EQ(static_cast<size_t>(2), result.size());
+ EXPECT_NE(result[0], result[1]);
+ EXPECT_TRUE(result[0] == kRendererProcessId ||
+ result[0] == kRendererProcessId + 1);
+ EXPECT_TRUE(result[1] == kRendererProcessId ||
+ result[1] == kRendererProcessId + 1);
+}
+
+// Should be able to finalize all misspellings.
+TEST_F(FeedbackTest, FinalizeAllMisspellings) {
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ AddMisspelling(kRendererProcessId + 1, kMisspellingHash + 1);
+ {
+ std::vector<Misspelling> pending = feedback_.GetAllMisspellings();
+ for (std::vector<Misspelling>::const_iterator it = pending.begin();
+ it != pending.end();
+ ++it) {
+ EXPECT_FALSE(it->action.IsFinal());
+ }
+ }
+ feedback_.FinalizeAllMisspellings();
+ {
+ std::vector<Misspelling> final = feedback_.GetAllMisspellings();
+ for (std::vector<Misspelling>::const_iterator it = final.begin();
+ it != final.end();
+ ++it) {
+ EXPECT_TRUE(it->action.IsFinal());
+ }
+ }
+}
+
+// Should be able to retrieve a copy of all misspellings.
+TEST_F(FeedbackTest, GetAllMisspellings) {
+ EXPECT_TRUE(feedback_.GetAllMisspellings().empty());
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ AddMisspelling(kRendererProcessId + 1, kMisspellingHash + 1);
+ const std::vector<Misspelling>& result = feedback_.GetAllMisspellings();
+ EXPECT_EQ(static_cast<size_t>(2), result.size());
+ EXPECT_NE(result[0].hash, result[1].hash);
+ EXPECT_TRUE(result[0].hash == kMisspellingHash ||
+ result[0].hash == kMisspellingHash + 1);
+ EXPECT_TRUE(result[1].hash == kMisspellingHash ||
+ result[1].hash == kMisspellingHash + 1);
+}
+
+// Should be able to clear all misspellings.
+TEST_F(FeedbackTest, ClearFeedback) {
+ AddMisspelling(kRendererProcessId, kMisspellingHash);
+ AddMisspelling(kRendererProcessId + 1, kMisspellingHash + 1);
+ EXPECT_FALSE(feedback_.Empty());
+ feedback_.Clear();
+ EXPECT_TRUE(feedback_.Empty());
+}
+
+} // namespace spellcheck
diff --git a/chrome/browser/spellchecker/spellcheck_misspelling.cc b/chrome/browser/spellchecker/misspelling.cc
index 1ca006a..4afbaa7 100644
--- a/chrome/browser/spellchecker/spellcheck_misspelling.cc
+++ b/chrome/browser/spellchecker/misspelling.cc
@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "chrome/browser/spellchecker/spellcheck_misspelling.h"
+#include "chrome/browser/spellchecker/misspelling.h"
+#include "base/strings/string_number_conversions.h"
#include "base/values.h"
namespace {
@@ -25,31 +26,35 @@ base::Value* BuildUserActionValue(const SpellcheckAction& action) {
} // namespace
-SpellcheckMisspelling::SpellcheckMisspelling()
- : location(0), length(0), timestamp(base::Time::Now()) {
+Misspelling::Misspelling()
+ : location(0), length(0), hash(0), timestamp(base::Time::Now()) {
}
-SpellcheckMisspelling::SpellcheckMisspelling(
- const string16& context,
- size_t location,
- size_t length,
- const std::vector<string16>& suggestions)
+Misspelling::Misspelling(const string16& context,
+ size_t location,
+ size_t length,
+ const std::vector<string16>& suggestions,
+ uint32 hash)
: context(context),
location(location),
length(length),
suggestions(suggestions),
+ hash(hash),
timestamp(base::Time::Now()) {
}
-SpellcheckMisspelling::~SpellcheckMisspelling() {
+Misspelling::~Misspelling() {
}
-base::DictionaryValue* SpellcheckMisspelling::Serialize() const {
+base::DictionaryValue* Misspelling::Serialize() const {
base::DictionaryValue* result = new base::DictionaryValue;
- result->SetString("originalText", context);
- result->SetInteger("misspelledStart", location);
+ result->SetString(
+ "timestamp",
+ base::Int64ToString(static_cast<long>(timestamp.ToJsTime())));
result->SetInteger("misspelledLength", length);
- result->SetDouble("timestamp", timestamp.ToJsTime());
+ result->SetInteger("misspelledStart", location);
+ result->SetString("originalText", context);
+ result->SetString("suggestionId", base::UintToString(hash));
result->Set("suggestions", BuildSuggestionsValue(suggestions));
result->Set("userActions", BuildUserActionValue(action));
return result;
diff --git a/chrome/browser/spellchecker/spellcheck_misspelling.h b/chrome/browser/spellchecker/misspelling.h
index 8497252..9a9d0fe 100644
--- a/chrome/browser/spellchecker/spellcheck_misspelling.h
+++ b/chrome/browser/spellchecker/misspelling.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_MISSPELLING_H_
-#define CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_MISSPELLING_H_
+#ifndef CHROME_BROWSER_SPELLCHECKER_MISSPELLING_H_
+#define CHROME_BROWSER_SPELLCHECKER_MISSPELLING_H_
#include <vector>
@@ -11,14 +11,15 @@
#include "chrome/browser/spellchecker/spellcheck_action.h"
// Spellcheck misspelling.
-class SpellcheckMisspelling {
+class Misspelling {
public:
- SpellcheckMisspelling();
- SpellcheckMisspelling(const string16& context,
- size_t location,
- size_t length,
- const std::vector<string16>& suggestions);
- ~SpellcheckMisspelling();
+ Misspelling();
+ Misspelling(const string16& context,
+ size_t location,
+ size_t length,
+ const std::vector<string16>& suggestions,
+ uint32 hash);
+ ~Misspelling();
// Serializes the data in this object into a dictionary value. The caller owns
// the result.
@@ -37,6 +38,9 @@ class SpellcheckMisspelling {
// Spelling suggestions.
std::vector<string16> suggestions;
+ // The hash that identifies the misspelling.
+ uint32 hash;
+
// User action.
SpellcheckAction action;
@@ -44,4 +48,4 @@ class SpellcheckMisspelling {
base::Time timestamp;
};
-#endif // CHROME_BROWSER_SPELLCHECKER_SPELLCHECK_MISSPELLING_H_
+#endif // CHROME_BROWSER_SPELLCHECKER_MISSPELLING_H_
diff --git a/chrome/browser/spellchecker/spellcheck_misspelling_unittest.cc b/chrome/browser/spellchecker/misspelling_unittest.cc
index 91bad77..175f7ad 100644
--- a/chrome/browser/spellchecker/spellcheck_misspelling_unittest.cc
+++ b/chrome/browser/spellchecker/misspelling_unittest.cc
@@ -5,22 +5,24 @@
#include "base/json/json_reader.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
-#include "chrome/browser/spellchecker/spellcheck_misspelling.h"
+#include "chrome/browser/spellchecker/misspelling.h"
#include "testing/gtest/include/gtest/gtest.h"
-TEST(SpellcheckMisspellingTest, SerializeTest) {
- SpellcheckMisspelling misspelling;
+TEST(MisspellingTest, SerializeTest) {
+ Misspelling misspelling;
misspelling.context = ASCIIToUTF16("How doe sit know");
misspelling.location = 4;
misspelling.length = 7;
misspelling.timestamp = base::Time::FromJsTime(42);
+ misspelling.hash = 9001;
misspelling.suggestions.push_back(ASCIIToUTF16("does it"));
scoped_ptr<base::Value> expected(base::JSONReader::Read(
"{\"originalText\": \"How doe sit know\","
"\"misspelledStart\": 4,"
"\"misspelledLength\": 7,"
- "\"timestamp\": 42.0,"
+ "\"timestamp\": \"42\","
+ "\"suggestionId\":\"9001\","
"\"suggestions\": [\"does it\"],"
"\"userActions\": [{\"actionType\": \"PENDING\"}]}"));
diff --git a/chrome/browser/spellchecker/spellcheck_service.cc b/chrome/browser/spellchecker/spellcheck_service.cc
index e847224..987f2ce 100644
--- a/chrome/browser/spellchecker/spellcheck_service.cc
+++ b/chrome/browser/spellchecker/spellcheck_service.cc
@@ -40,6 +40,15 @@ SpellcheckService::SpellcheckService(Profile* profile)
PrefService* prefs = profile_->GetPrefs();
pref_change_registrar_.Init(prefs);
+ std::string language_code;
+ std::string country_code;
+ chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
+ prefs->GetString(prefs::kSpellCheckDictionary),
+ &language_code,
+ &country_code);
+ feedback_sender_.reset(new spellcheck::FeedbackSender(
+ profile->GetRequestContext(), language_code, country_code));
+
pref_change_registrar_.Add(
prefs::kEnableAutoSpellCorrect,
base::Bind(&SpellcheckService::OnEnableAutoSpellCorrectChanged,
@@ -193,8 +202,8 @@ SpellcheckHunspellDictionary* SpellcheckService::GetHunspellDictionary() {
return hunspell_dictionary_.get();
}
-SpellingServiceFeedback* SpellcheckService::GetFeedbackSender() {
- return &feedback_sender_;
+spellcheck::FeedbackSender* SpellcheckService::GetFeedbackSender() {
+ return feedback_sender_.get();
}
bool SpellcheckService::LoadExternalDictionary(std::string language,
@@ -283,12 +292,17 @@ void SpellcheckService::OnEnableAutoSpellCorrectChanged() {
void SpellcheckService::OnSpellCheckDictionaryChanged() {
if (hunspell_dictionary_.get())
hunspell_dictionary_->RemoveObserver(this);
+ std::string dictionary =
+ profile_->GetPrefs()->GetString(prefs::kSpellCheckDictionary);
hunspell_dictionary_.reset(new SpellcheckHunspellDictionary(
- profile_->GetPrefs()->GetString(prefs::kSpellCheckDictionary),
- profile_->GetRequestContext(),
- this));
+ dictionary, profile_->GetRequestContext(), this));
hunspell_dictionary_->AddObserver(this);
hunspell_dictionary_->Load();
+ std::string language_code;
+ std::string country_code;
+ chrome::spellcheck_common::GetISOLanguageCountryCodeFromLocale(
+ dictionary, &language_code, &country_code);
+ feedback_sender_->OnLanguageCountryChange(language_code, country_code);
}
void SpellcheckService::OnUseSpellingServiceChanged() {
diff --git a/chrome/browser/spellchecker/spellcheck_service.h b/chrome/browser/spellchecker/spellcheck_service.h
index b278961..2689708 100644
--- a/chrome/browser/spellchecker/spellcheck_service.h
+++ b/chrome/browser/spellchecker/spellcheck_service.h
@@ -10,9 +10,9 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/prefs/pref_change_registrar.h"
+#include "chrome/browser/spellchecker/feedback_sender.h"
#include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
-#include "chrome/browser/spellchecker/spelling_service_feedback.h"
#include "chrome/common/spellcheck_common.h"
#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
#include "content/public/browser/notification_observer.h"
@@ -94,7 +94,7 @@ class SpellcheckService : public ProfileKeyedService,
SpellcheckHunspellDictionary* GetHunspellDictionary();
// Returns the instance of the spelling service feedback sender.
- SpellingServiceFeedback* GetFeedbackSender();
+ spellcheck::FeedbackSender* GetFeedbackSender();
// Load a dictionary from a given path. Format specifies how the dictionary
// is stored. Return value is true if successful.
@@ -158,7 +158,7 @@ class SpellcheckService : public ProfileKeyedService,
scoped_ptr<SpellcheckHunspellDictionary> hunspell_dictionary_;
- SpellingServiceFeedback feedback_sender_;
+ scoped_ptr<spellcheck::FeedbackSender> feedback_sender_;
base::WeakPtrFactory<SpellcheckService> weak_ptr_factory_;
diff --git a/chrome/browser/spellchecker/spelling_service_feedback.cc b/chrome/browser/spellchecker/spelling_service_feedback.cc
deleted file mode 100644
index 102d868..0000000
--- a/chrome/browser/spellchecker/spelling_service_feedback.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/spellchecker/spelling_service_feedback.h"
-
-#include "chrome/common/spellcheck_common.h"
-#include "chrome/common/spellcheck_messages.h"
-#include "content/public/browser/render_process_host.h"
-
-SpellingServiceFeedback::SpellingServiceFeedback() {
- timer_.Start(
- FROM_HERE,
- base::TimeDelta::FromSeconds(
- chrome::spellcheck_common::kFeedbackIntervalSeconds),
- this,
- &SpellingServiceFeedback::RequestDocumentMarkers);
-}
-
-SpellingServiceFeedback::~SpellingServiceFeedback() {
-}
-
-void SpellingServiceFeedback::OnReceiveDocumentMarkers(
- int render_process_id,
- const std::vector<uint32>& markers) const {
-}
-
-void SpellingServiceFeedback::RequestDocumentMarkers() {
- for (content::RenderProcessHost::iterator i(
- content::RenderProcessHost::AllHostsIterator());
- !i.IsAtEnd();
- i.Advance()) {
- i.GetCurrentValue()->Send(new SpellCheckMsg_RequestDocumentMarkers());
- }
-}
diff --git a/chrome/browser/spellchecker/spelling_service_feedback.h b/chrome/browser/spellchecker/spelling_service_feedback.h
deleted file mode 100644
index 949a9d5..0000000
--- a/chrome/browser/spellchecker/spelling_service_feedback.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SPELLCHECKER_SPELLING_SERVICE_FEEDBACK_H_
-#define CHROME_BROWSER_SPELLCHECKER_SPELLING_SERVICE_FEEDBACK_H_
-
-#include <vector>
-
-#include "base/timer.h"
-
-// Manages sending feedback to the spelling service.
-class SpellingServiceFeedback {
- public:
- SpellingServiceFeedback();
- ~SpellingServiceFeedback();
-
- // Receives document markers for renderer with process ID |render_process_id|.
- // Called when the renderer responds to RequestDocumentMarkers() call.
- void OnReceiveDocumentMarkers(
- int render_process_id,
- const std::vector<uint32>& markers) const;
-
- private:
- // Requests the document markers from all of the renderers. Called
- // periodically when |timer_| fires.
- void RequestDocumentMarkers();
-
- // A timer to periodically request a list of document spelling markers from
- // all of the renderers. The timer runs while an instance of this class is
- // alive.
- base::RepeatingTimer<SpellingServiceFeedback> timer_;
-
- DISALLOW_COPY_AND_ASSIGN(SpellingServiceFeedback);
-};
-
-#endif // CHROME_BROWSER_SPELLCHECKER_SPELLING_SERVICE_FEEDBACK_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 1d4bb6f..2b8dfd0 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1860,6 +1860,12 @@
'browser/speech/tts_message_filter.cc',
'browser/speech/tts_message_filter.h',
'browser/speech/tts_win.cc',
+ 'browser/spellchecker/feedback.cc',
+ 'browser/spellchecker/feedback.h',
+ 'browser/spellchecker/feedback_sender.cc',
+ 'browser/spellchecker/feedback_sender.h',
+ 'browser/spellchecker/misspelling.cc',
+ 'browser/spellchecker/misspelling.h',
'browser/spellchecker/spellcheck_action.cc',
'browser/spellchecker/spellcheck_action.h',
'browser/spellchecker/spellcheck_custom_dictionary.cc',
@@ -1875,16 +1881,12 @@
'browser/spellchecker/spellcheck_message_filter.h',
'browser/spellchecker/spellcheck_message_filter_mac.cc',
'browser/spellchecker/spellcheck_message_filter_mac.h',
- 'browser/spellchecker/spellcheck_misspelling.cc',
- 'browser/spellchecker/spellcheck_misspelling.h',
'browser/spellchecker/spellcheck_platform_mac.h',
'browser/spellchecker/spellcheck_platform_mac.mm',
'browser/spellchecker/spellcheck_service.cc',
'browser/spellchecker/spellcheck_service.h',
'browser/spellchecker/spelling_service_client.cc',
'browser/spellchecker/spelling_service_client.h',
- 'browser/spellchecker/spelling_service_feedback.cc',
- 'browser/spellchecker/spelling_service_feedback.h',
'browser/spellchecker/word_trimmer.cc',
'browser/spellchecker/word_trimmer.h',
'browser/ssl/ssl_add_certificate.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 968f384..eb01f92 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1110,12 +1110,14 @@
'browser/speech/extension_api/extension_manifests_tts_unittest.cc',
'browser/speech/speech_recognition_bubble_controller_unittest.cc',
'browser/speech/tts_controller_unittest.cc',
+ 'browser/spellchecker/feedback_sender_unittest.cc',
+ 'browser/spellchecker/feedback_unittest.cc',
+ 'browser/spellchecker/misspelling_unittest.cc',
'browser/spellchecker/spellcheck_action_unittest.cc',
'browser/spellchecker/spellcheck_custom_dictionary_unittest.cc',
'browser/spellchecker/spellcheck_host_metrics_unittest.cc',
'browser/spellchecker/spellcheck_message_filter_mac_unittest.cc',
'browser/spellchecker/spellcheck_message_filter_unittest.cc',
- 'browser/spellchecker/spellcheck_misspelling_unittest.cc',
'browser/spellchecker/spellcheck_platform_mac_unittest.cc',
'browser/spellchecker/spellcheck_service_unittest.cc',
'browser/spellchecker/spelling_service_client_unittest.cc',
diff --git a/chrome/common/spellcheck_common.h b/chrome/common/spellcheck_common.h
index 1bed570..a79aa1c 100644
--- a/chrome/common/spellcheck_common.h
+++ b/chrome/common/spellcheck_common.h
@@ -15,6 +15,14 @@ class FilePath;
namespace chrome {
namespace spellcheck_common {
+// The number of hours that a session of feedback for spelling service lasts.
+// After this number of hours passes, all feedback.
+static const int kSessionHours = 6;
+
+// The number of context words to keep on either side of a misspelling for
+// spelling service feedback.
+static const int kContextWordCount = 2;
+
// The number of seconds between sending feedback to spelling service.
static const int kFeedbackIntervalSeconds = 1800; // 30 minutes
diff --git a/chrome/common/spellcheck_result.h b/chrome/common/spellcheck_result.h
index 29b1118..262b06d 100644
--- a/chrome/common/spellcheck_result.h
+++ b/chrome/common/spellcheck_result.h
@@ -24,14 +24,16 @@ struct SpellCheckResult {
Type t = SPELLING,
int loc = 0,
int len = 0,
- const string16& rep = string16())
- : type(t), location(loc), length(len), replacement(rep) {
+ const string16& rep = string16(),
+ uint32 h = 0)
+ : type(t), location(loc), length(len), replacement(rep), hash(h) {
}
Type type;
int location;
int length;
string16 replacement;
+ uint32 hash;
};
#endif // CHROME_COMMON_SPELLCHECK_RESULT_H_