1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/spellchecker/spellcheck_message_filter_mac.h" 6 7#include <algorithm> 8#include <functional> 9 10#include "base/barrier_closure.h" 11#include "base/bind.h" 12#include "chrome/browser/spellchecker/spellcheck_factory.h" 13#include "chrome/browser/spellchecker/spellcheck_platform_mac.h" 14#include "chrome/browser/spellchecker/spellcheck_service.h" 15#include "chrome/browser/spellchecker/spelling_service_client.h" 16#include "chrome/common/spellcheck_messages.h" 17#include "chrome/common/spellcheck_result.h" 18#include "content/public/browser/browser_context.h" 19#include "content/public/browser/render_process_host.h" 20 21using content::BrowserThread; 22using content::BrowserContext; 23 24namespace { 25 26bool CompareLocation(const SpellCheckResult& r1, 27 const SpellCheckResult& r2) { 28 return r1.location < r2.location; 29} 30 31} // namespace 32 33class SpellingRequest { 34 public: 35 SpellingRequest(SpellingServiceClient* client, 36 content::BrowserMessageFilter* destination, 37 int render_process_id); 38 39 void RequestCheck(const base::string16& text, 40 int route_id, 41 int identifier, 42 int document_tag, 43 const std::vector<SpellCheckMarker>& markers); 44 private: 45 // Request server-side checking. 46 void RequestRemoteCheck(const base::string16& text); 47 48 // Request a check from local spell checker. 49 void RequestLocalCheck(const base::string16& text, int document_tag); 50 51 // Check if all pending requests are done, send reply to render process if so. 52 void OnCheckCompleted(); 53 54 // Called when server-side checking is complete. 55 void OnRemoteCheckCompleted(bool success, 56 const base::string16& text, 57 const std::vector<SpellCheckResult>& results); 58 59 // Called when local checking is complete. 60 void OnLocalCheckCompleted(const std::vector<SpellCheckResult>& results); 61 62 std::vector<SpellCheckResult> local_results_; 63 std::vector<SpellCheckResult> remote_results_; 64 65 // Barrier closure for completion of both remote and local check. 66 base::Closure completion_barrier_; 67 bool remote_success_; 68 69 SpellingServiceClient* client_; // Owned by |destination|. 70 content::BrowserMessageFilter* destination_; // ref-counted. 71 int render_process_id_; 72 73 int route_id_; 74 int identifier_; 75 int document_tag_; 76 std::vector<SpellCheckMarker> markers_; 77}; 78 79SpellingRequest::SpellingRequest(SpellingServiceClient* client, 80 content::BrowserMessageFilter* destination, 81 int render_process_id) 82 : remote_success_(false), 83 client_(client), 84 destination_(destination), 85 render_process_id_(render_process_id), 86 route_id_(-1), 87 identifier_(-1), 88 document_tag_(-1) { 89 destination_->AddRef(); 90} 91 92void SpellingRequest::RequestCheck( 93 const base::string16& text, 94 int route_id, 95 int identifier, 96 int document_tag, 97 const std::vector<SpellCheckMarker>& markers) { 98 DCHECK(!text.empty()); 99 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 100 101 route_id_ = route_id; 102 identifier_ = identifier; 103 document_tag_ = document_tag; 104 markers_ = markers; 105 106 // Send the remote query out. The barrier owns |this|, ensuring it is deleted 107 // after completion. 108 completion_barrier_ = 109 BarrierClosure(2, 110 base::Bind(&SpellingRequest::OnCheckCompleted, 111 base::Owned(this))); 112 RequestRemoteCheck(text); 113 RequestLocalCheck(text, document_tag_); 114} 115 116void SpellingRequest::RequestRemoteCheck(const base::string16& text) { 117 BrowserContext* context = NULL; 118 content::RenderProcessHost* host = 119 content::RenderProcessHost::FromID(render_process_id_); 120 if (host) 121 context = host->GetBrowserContext(); 122 123 client_->RequestTextCheck( 124 context, 125 SpellingServiceClient::SPELLCHECK, 126 text, 127 base::Bind(&SpellingRequest::OnRemoteCheckCompleted, 128 base::Unretained(this))); 129} 130 131void SpellingRequest::RequestLocalCheck(const base::string16& text, 132 int document_tag) { 133 spellcheck_mac::RequestTextCheck( 134 document_tag, 135 text, 136 base::Bind(&SpellingRequest::OnLocalCheckCompleted, 137 base::Unretained(this))); 138} 139 140void SpellingRequest::OnCheckCompleted() { 141 // Final completion can happen on any thread - don't DCHECK thread. 142 const std::vector<SpellCheckResult>* check_results = &local_results_; 143 if (remote_success_) { 144 std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation); 145 std::sort(local_results_.begin(), local_results_.end(), CompareLocation); 146 SpellCheckMessageFilterMac::CombineResults(&remote_results_, 147 local_results_); 148 check_results = &remote_results_; 149 } 150 151 destination_->Send( 152 new SpellCheckMsg_RespondTextCheck( 153 route_id_, 154 identifier_, 155 *check_results)); 156 destination_->Release(); 157 158 // Object is self-managed - at this point, its life span is over. 159 // No need to delete, since the OnCheckCompleted callback owns |this|. 160} 161 162void SpellingRequest::OnRemoteCheckCompleted( 163 bool success, 164 const base::string16& text, 165 const std::vector<SpellCheckResult>& results) { 166 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 167 remote_success_ = success; 168 remote_results_ = results; 169 170 SpellcheckService* spellcheck_service = 171 SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_); 172 if (spellcheck_service) { 173 spellcheck_service->GetFeedbackSender()->OnSpellcheckResults( 174 render_process_id_, 175 text, 176 markers_, 177 &remote_results_); 178 } 179 180 completion_barrier_.Run(); 181} 182 183void SpellingRequest::OnLocalCheckCompleted( 184 const std::vector<SpellCheckResult>& results) { 185 // Local checking can happen on any thread - don't DCHECK thread. 186 local_results_ = results; 187 completion_barrier_.Run(); 188} 189 190 191SpellCheckMessageFilterMac::SpellCheckMessageFilterMac(int render_process_id) 192 : BrowserMessageFilter(SpellCheckMsgStart), 193 render_process_id_(render_process_id), 194 client_(new SpellingServiceClient) { 195} 196 197void SpellCheckMessageFilterMac::OverrideThreadForMessage( 198 const IPC::Message& message, BrowserThread::ID* thread) { 199 if (message.type() == SpellCheckHostMsg_RequestTextCheck::ID) 200 *thread = BrowserThread::UI; 201} 202 203bool SpellCheckMessageFilterMac::OnMessageReceived( 204 const IPC::Message& message) { 205 bool handled = true; 206 IPC_BEGIN_MESSAGE_MAP(SpellCheckMessageFilterMac, message) 207 IPC_MESSAGE_HANDLER(SpellCheckHostMsg_CheckSpelling, 208 OnCheckSpelling) 209 IPC_MESSAGE_HANDLER(SpellCheckHostMsg_FillSuggestionList, 210 OnFillSuggestionList) 211 IPC_MESSAGE_HANDLER(SpellCheckHostMsg_ShowSpellingPanel, 212 OnShowSpellingPanel) 213 IPC_MESSAGE_HANDLER(SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord, 214 OnUpdateSpellingPanelWithMisspelledWord) 215 IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RequestTextCheck, 216 OnRequestTextCheck) 217 IPC_MESSAGE_UNHANDLED(handled = false) 218 IPC_END_MESSAGE_MAP() 219 return handled; 220} 221 222// static 223void SpellCheckMessageFilterMac::CombineResults( 224 std::vector<SpellCheckResult>* remote_results, 225 const std::vector<SpellCheckResult>& local_results) { 226 std::vector<SpellCheckResult>::const_iterator local_iter( 227 local_results.begin()); 228 std::vector<SpellCheckResult>::iterator remote_iter; 229 230 for (remote_iter = remote_results->begin(); 231 remote_iter != remote_results->end(); 232 ++remote_iter) { 233 // Discard all local results occurring before remote result. 234 while (local_iter != local_results.end() && 235 local_iter->location < remote_iter->location) { 236 local_iter++; 237 } 238 239 // Unless local and remote result coincide, result is GRAMMAR. 240 remote_iter->decoration = SpellCheckResult::GRAMMAR; 241 if (local_iter != local_results.end() && 242 local_iter->location == remote_iter->location && 243 local_iter->length == remote_iter->length) { 244 remote_iter->decoration = SpellCheckResult::SPELLING; 245 } 246 } 247} 248 249SpellCheckMessageFilterMac::~SpellCheckMessageFilterMac() {} 250 251void SpellCheckMessageFilterMac::OnCheckSpelling(const base::string16& word, 252 int route_id, 253 bool* correct) { 254 *correct = spellcheck_mac::CheckSpelling(word, ToDocumentTag(route_id)); 255} 256 257void SpellCheckMessageFilterMac::OnFillSuggestionList( 258 const base::string16& word, 259 std::vector<base::string16>* suggestions) { 260 spellcheck_mac::FillSuggestionList(word, suggestions); 261} 262 263void SpellCheckMessageFilterMac::OnShowSpellingPanel(bool show) { 264 spellcheck_mac::ShowSpellingPanel(show); 265} 266 267void SpellCheckMessageFilterMac::OnUpdateSpellingPanelWithMisspelledWord( 268 const base::string16& word) { 269 spellcheck_mac::UpdateSpellingPanelWithMisspelledWord(word); 270} 271 272void SpellCheckMessageFilterMac::OnRequestTextCheck( 273 int route_id, 274 int identifier, 275 const base::string16& text, 276 std::vector<SpellCheckMarker> markers) { 277 DCHECK(!text.empty()); 278 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 279 // Erase invalid markers (with offsets out of boundaries of text length). 280 markers.erase( 281 std::remove_if( 282 markers.begin(), 283 markers.end(), 284 std::not1(SpellCheckMarker::IsValidPredicate(text.length()))), 285 markers.end()); 286 // SpellingRequest self-destructs. 287 SpellingRequest* request = 288 new SpellingRequest(client_.get(), this, render_process_id_); 289 request->RequestCheck( 290 text, route_id, identifier, ToDocumentTag(route_id), markers); 291} 292 293int SpellCheckMessageFilterMac::ToDocumentTag(int route_id) { 294 if (!tag_map_.count(route_id)) 295 tag_map_[route_id] = spellcheck_mac::GetDocumentTag(); 296 return tag_map_[route_id]; 297} 298 299// TODO(groby): We are currently not notified of retired tags. We need 300// to track destruction of RenderViewHosts on the browser process side 301// to update our mappings when a document goes away. 302void SpellCheckMessageFilterMac::RetireDocumentTag(int route_id) { 303 spellcheck_mac::CloseDocumentWithTag(ToDocumentTag(route_id)); 304 tag_map_.erase(route_id); 305} 306 307