blob: 484e5225f724c3f592454dfac4521e13502e3ef4 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/editing/spellcheck/SpellCheckRequester.h"
#include "core/dom/Document.h"
#include "core/dom/Node.h"
#include "core/dom/TaskRunnerHelper.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/markers/DocumentMarkerController.h"
#include "core/editing/spellcheck/SpellChecker.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/TextControlElement.h"
#include "platform/text/TextCheckerClient.h"
namespace blink {
SpellCheckRequest::SpellCheckRequest(Range* checking_range,
const String& text,
int request_number)
: requester_(nullptr),
checking_range_(checking_range),
root_editable_element_(
blink::RootEditableElement(*checking_range_->startContainer())),
request_data_(text),
request_number_(request_number) {
DCHECK(checking_range_);
DCHECK(checking_range_->IsConnected());
DCHECK(root_editable_element_);
}
SpellCheckRequest::~SpellCheckRequest() {}
DEFINE_TRACE(SpellCheckRequest) {
visitor->Trace(requester_);
visitor->Trace(checking_range_);
visitor->Trace(root_editable_element_);
TextCheckingRequest::Trace(visitor);
}
void SpellCheckRequest::Dispose() {
if (checking_range_)
checking_range_->Dispose();
}
// static
SpellCheckRequest* SpellCheckRequest::Create(
const EphemeralRange& checking_range,
int request_number) {
if (checking_range.IsNull())
return nullptr;
if (!blink::RootEditableElement(
*checking_range.StartPosition().ComputeContainerNode()))
return nullptr;
String text =
PlainText(checking_range, TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.Build());
if (text.IsEmpty())
return nullptr;
Range* checking_range_object = CreateRange(checking_range);
return new SpellCheckRequest(checking_range_object, text, request_number);
}
const TextCheckingRequestData& SpellCheckRequest::Data() const {
return request_data_;
}
bool SpellCheckRequest::IsValid() const {
return checking_range_->IsConnected() &&
root_editable_element_->isConnected();
}
void SpellCheckRequest::DidSucceed(const Vector<TextCheckingResult>& results) {
if (!requester_)
return;
SpellCheckRequester* requester = requester_;
requester_ = nullptr;
requester->DidCheckSucceed(request_data_.Sequence(), results);
}
void SpellCheckRequest::DidCancel() {
if (!requester_)
return;
SpellCheckRequester* requester = requester_;
requester_ = nullptr;
requester->DidCheckCancel(request_data_.Sequence());
}
void SpellCheckRequest::SetCheckerAndSequence(SpellCheckRequester* requester,
int sequence) {
DCHECK(!requester_);
DCHECK_EQ(request_data_.Sequence(), kUnrequestedTextCheckingSequence);
requester_ = requester;
request_data_.SetSequence(sequence);
}
SpellCheckRequester::SpellCheckRequester(LocalFrame& frame)
: frame_(&frame),
last_request_sequence_(0),
last_processed_sequence_(0),
timer_to_process_queued_request_(
TaskRunnerHelper::Get(TaskType::kUnspecedTimer, &frame),
this,
&SpellCheckRequester::TimerFiredToProcessQueuedRequest) {}
SpellCheckRequester::~SpellCheckRequester() {}
TextCheckerClient& SpellCheckRequester::Client() const {
return GetFrame().GetSpellChecker().TextChecker();
}
void SpellCheckRequester::TimerFiredToProcessQueuedRequest(TimerBase*) {
DCHECK(!request_queue_.IsEmpty());
if (request_queue_.IsEmpty())
return;
InvokeRequest(request_queue_.TakeFirst());
}
void SpellCheckRequester::RequestCheckingFor(const EphemeralRange& range) {
RequestCheckingFor(range, 0);
}
void SpellCheckRequester::RequestCheckingFor(const EphemeralRange& range,
int request_num) {
SpellCheckRequest* request = SpellCheckRequest::Create(range, request_num);
if (!request)
return;
DCHECK_EQ(request->Data().Sequence(), kUnrequestedTextCheckingSequence);
int sequence = ++last_request_sequence_;
if (sequence == kUnrequestedTextCheckingSequence)
sequence = ++last_request_sequence_;
request->SetCheckerAndSequence(this, sequence);
if (timer_to_process_queued_request_.IsActive() || processing_request_) {
EnqueueRequest(request);
return;
}
InvokeRequest(request);
}
void SpellCheckRequester::CancelCheck() {
if (processing_request_)
processing_request_->DidCancel();
}
void SpellCheckRequester::PrepareForLeakDetection() {
timer_to_process_queued_request_.Stop();
// Empty the queue of pending requests to prevent it being a leak source.
// Pending spell checker requests are cancellable requests not representing
// leaks, just async work items waiting to be processed.
//
// Rather than somehow wait for this async queue to drain before running
// the leak detector, they're all cancelled to prevent flaky leaks being
// reported.
request_queue_.Clear();
// WebSpellCheckClient stores a set of WebTextCheckingCompletion objects,
// which may store references to already invoked requests. We should clear
// these references to prevent them from being a leak source.
Client().CancelAllPendingRequests();
}
void SpellCheckRequester::InvokeRequest(SpellCheckRequest* request) {
DCHECK(!processing_request_);
processing_request_ = request;
Client().RequestCheckingOfString(processing_request_);
}
void SpellCheckRequester::ClearProcessingRequest() {
if (!processing_request_)
return;
processing_request_->Dispose();
processing_request_.Clear();
}
void SpellCheckRequester::EnqueueRequest(SpellCheckRequest* request) {
DCHECK(request);
bool continuation = false;
if (!request_queue_.IsEmpty()) {
SpellCheckRequest* last_request = request_queue_.back();
// It's a continuation if the number of the last request got incremented in
// the new one and both apply to the same editable.
continuation =
request->RootEditableElement() == last_request->RootEditableElement() &&
request->RequestNumber() == last_request->RequestNumber() + 1;
}
// Spellcheck requests for chunks of text in the same element should not
// overwrite each other.
if (!continuation) {
RequestQueue::const_iterator same_element_request = std::find_if(
request_queue_.begin(), request_queue_.end(),
[request](const SpellCheckRequest* queued_request) -> bool {
return request->RootEditableElement() ==
queued_request->RootEditableElement();
});
if (same_element_request != request_queue_.end())
request_queue_.erase(same_element_request);
}
request_queue_.push_back(request);
}
void SpellCheckRequester::DidCheck(int sequence,
const Vector<TextCheckingResult>& results) {
DCHECK(processing_request_);
DCHECK_EQ(processing_request_->Data().Sequence(), sequence);
if (processing_request_->Data().Sequence() != sequence) {
request_queue_.Clear();
return;
}
if (results.size())
GetFrame().GetSpellChecker().MarkAndReplaceFor(processing_request_,
results);
DCHECK_LT(last_processed_sequence_, sequence);
last_processed_sequence_ = sequence;
ClearProcessingRequest();
if (!request_queue_.IsEmpty())
timer_to_process_queued_request_.StartOneShot(0, BLINK_FROM_HERE);
}
void SpellCheckRequester::DidCheckSucceed(
int sequence,
const Vector<TextCheckingResult>& results) {
// TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
GetFrame().GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
TextCheckingRequestData request_data = processing_request_->Data();
if (request_data.Sequence() == sequence) {
DocumentMarker::MarkerTypes markers =
DocumentMarker::SpellCheckClientMarkers();
if (processing_request_->IsValid()) {
Range* checking_range = processing_request_->CheckingRange();
GetFrame().GetDocument()->Markers().RemoveMarkersInRange(
EphemeralRange(checking_range), markers);
}
}
DidCheck(sequence, results);
}
void SpellCheckRequester::DidCheckCancel(int sequence) {
Vector<TextCheckingResult> results;
DidCheck(sequence, results);
}
DEFINE_TRACE(SpellCheckRequester) {
visitor->Trace(frame_);
visitor->Trace(processing_request_);
visitor->Trace(request_queue_);
}
} // namespace blink