blob: 0acffa8ae7faa099932b078cfca74f74a73d8588 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/renderer/core/editing/spellcheck/idle_spell_check_controller.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/dom/idle_request_options.h"
#include "third_party/blink/renderer/core/editing/commands/undo_stack.h"
#include "third_party/blink/renderer/core/editing/commands/undo_step.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/spellcheck/cold_mode_spell_check_requester.h"
#include "third_party/blink/renderer/core/editing/spellcheck/hot_mode_spell_check_requester.h"
#include "third_party/blink/renderer/core/editing/spellcheck/spell_check_requester.h"
#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
#include "third_party/blink/renderer/core/editing/visible_selection.h"
#include "third_party/blink/renderer/core/editing/visible_units.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
namespace {
constexpr TimeDelta kColdModeTimerInterval = TimeDelta::FromMilliseconds(1000);
constexpr TimeDelta kConsecutiveColdModeTimerInterval =
TimeDelta::FromMilliseconds(200);
const int kHotModeRequestTimeoutMS = 200;
const int kInvalidHandle = -1;
const int kDummyHandleForForcedInvocation = -2;
constexpr TimeDelta kIdleSpellcheckTestTimeout = TimeDelta::FromSeconds(10);
} // namespace
class IdleSpellCheckController::IdleCallback final
: public ScriptedIdleTaskController::IdleTask {
public:
static IdleCallback* Create(IdleSpellCheckController* controller) {
return new IdleCallback(controller);
}
void Trace(blink::Visitor* visitor) final {
visitor->Trace(controller_);
ScriptedIdleTaskController::IdleTask::Trace(visitor);
}
private:
explicit IdleCallback(IdleSpellCheckController* controller)
: controller_(controller) {}
void invoke(IdleDeadline* deadline) final { controller_->Invoke(deadline); }
const Member<IdleSpellCheckController> controller_;
DISALLOW_COPY_AND_ASSIGN(IdleCallback);
};
IdleSpellCheckController::~IdleSpellCheckController() = default;
void IdleSpellCheckController::Trace(blink::Visitor* visitor) {
visitor->Trace(frame_);
visitor->Trace(cold_mode_requester_);
DocumentShutdownObserver::Trace(visitor);
}
IdleSpellCheckController* IdleSpellCheckController::Create(LocalFrame& frame) {
return new IdleSpellCheckController(frame);
}
IdleSpellCheckController::IdleSpellCheckController(LocalFrame& frame)
: state_(State::kInactive),
idle_callback_handle_(kInvalidHandle),
frame_(frame),
last_processed_undo_step_sequence_(0),
cold_mode_requester_(ColdModeSpellCheckRequester::Create(frame)),
cold_mode_timer_(frame.GetTaskRunner(TaskType::kInternalDefault),
this,
&IdleSpellCheckController::ColdModeTimerFired) {}
SpellCheckRequester& IdleSpellCheckController::GetSpellCheckRequester() const {
return GetFrame().GetSpellChecker().GetSpellCheckRequester();
}
bool IdleSpellCheckController::IsSpellCheckingEnabled() const {
return GetFrame().GetSpellChecker().IsSpellCheckingEnabled();
}
void IdleSpellCheckController::DisposeIdleCallback() {
if (idle_callback_handle_ != kInvalidHandle && IsAvailable())
GetDocument().CancelIdleCallback(idle_callback_handle_);
idle_callback_handle_ = kInvalidHandle;
}
void IdleSpellCheckController::Deactivate() {
state_ = State::kInactive;
if (cold_mode_timer_.IsActive())
cold_mode_timer_.Stop();
cold_mode_requester_->ClearProgress();
DisposeIdleCallback();
}
void IdleSpellCheckController::SetNeedsInvocation() {
if (!IsSpellCheckingEnabled() || !IsAvailable()) {
Deactivate();
return;
}
if (state_ == State::kHotModeRequested)
return;
cold_mode_requester_->ClearProgress();
if (state_ == State::kColdModeTimerStarted) {
DCHECK(cold_mode_timer_.IsActive());
cold_mode_timer_.Stop();
}
if (state_ == State::kColdModeRequested)
DisposeIdleCallback();
IdleRequestOptions options;
options.setTimeout(kHotModeRequestTimeoutMS);
idle_callback_handle_ =
GetDocument().RequestIdleCallback(IdleCallback::Create(this), options);
state_ = State::kHotModeRequested;
}
void IdleSpellCheckController::SetNeedsColdModeInvocation() {
if (!RuntimeEnabledFeatures::IdleTimeColdModeSpellCheckingEnabled() ||
!IsSpellCheckingEnabled()) {
Deactivate();
return;
}
if (state_ != State::kInactive && state_ != State::kInHotModeInvocation &&
state_ != State::kInColdModeInvocation)
return;
DCHECK(!cold_mode_timer_.IsActive());
TimeDelta interval = state_ == State::kInColdModeInvocation
? kConsecutiveColdModeTimerInterval
: kColdModeTimerInterval;
cold_mode_timer_.StartOneShot(interval, FROM_HERE);
state_ = State::kColdModeTimerStarted;
}
void IdleSpellCheckController::ColdModeTimerFired(TimerBase*) {
DCHECK(RuntimeEnabledFeatures::IdleTimeColdModeSpellCheckingEnabled());
DCHECK_EQ(State::kColdModeTimerStarted, state_);
if (!IsSpellCheckingEnabled() || !IsAvailable()) {
Deactivate();
return;
}
idle_callback_handle_ = GetDocument().RequestIdleCallback(
IdleCallback::Create(this), IdleRequestOptions());
state_ = State::kColdModeRequested;
}
void IdleSpellCheckController::HotModeInvocation(IdleDeadline* deadline) {
TRACE_EVENT0("blink", "IdleSpellCheckController::hotModeInvocation");
// TODO(xiaochengh): Figure out if this has any performance impact.
GetDocument().UpdateStyleAndLayout();
HotModeSpellCheckRequester requester(GetSpellCheckRequester());
requester.CheckSpellingAt(
GetFrame().Selection().GetSelectionInDOMTree().Extent());
const uint64_t watermark = last_processed_undo_step_sequence_;
for (const UndoStep* step :
GetFrame().GetEditor().GetUndoStack().UndoSteps()) {
if (step->SequenceNumber() <= watermark)
break;
last_processed_undo_step_sequence_ =
std::max(step->SequenceNumber(), last_processed_undo_step_sequence_);
if (deadline->timeRemaining() == 0)
break;
// The ending selection stored in undo stack can be invalid, disconnected
// or have been moved to another document, so we should check its validity
// before using it.
if (!step->EndingSelection().IsValidFor(GetDocument()))
continue;
requester.CheckSpellingAt(step->EndingSelection().Extent());
}
}
void IdleSpellCheckController::Invoke(IdleDeadline* deadline) {
DCHECK_NE(idle_callback_handle_, kInvalidHandle);
idle_callback_handle_ = kInvalidHandle;
if (!IsSpellCheckingEnabled() || !IsAvailable()) {
Deactivate();
return;
}
if (state_ == State::kHotModeRequested) {
state_ = State::kInHotModeInvocation;
HotModeInvocation(deadline);
SetNeedsColdModeInvocation();
} else if (state_ == State::kColdModeRequested) {
DCHECK(RuntimeEnabledFeatures::IdleTimeColdModeSpellCheckingEnabled());
state_ = State::kInColdModeInvocation;
cold_mode_requester_->Invoke(deadline);
if (cold_mode_requester_->FullyChecked())
state_ = State::kInactive;
else
SetNeedsColdModeInvocation();
} else {
NOTREACHED();
}
}
void IdleSpellCheckController::DocumentAttached(Document* document) {
SetContext(document);
}
void IdleSpellCheckController::ContextDestroyed(Document*) {
Deactivate();
}
void IdleSpellCheckController::ForceInvocationForTesting() {
if (!IsSpellCheckingEnabled())
return;
IdleDeadline* deadline =
IdleDeadline::Create(CurrentTimeTicks() + kIdleSpellcheckTestTimeout,
IdleDeadline::CallbackType::kCalledWhenIdle);
switch (state_) {
case State::kColdModeTimerStarted:
cold_mode_timer_.Stop();
state_ = State::kColdModeRequested;
idle_callback_handle_ = kDummyHandleForForcedInvocation;
Invoke(deadline);
break;
case State::kHotModeRequested:
case State::kColdModeRequested:
GetDocument().CancelIdleCallback(idle_callback_handle_);
Invoke(deadline);
break;
case State::kInactive:
case State::kInHotModeInvocation:
case State::kInColdModeInvocation:
NOTREACHED();
}
}
void IdleSpellCheckController::SkipColdModeTimerForTesting() {
DCHECK(cold_mode_timer_.IsActive());
cold_mode_timer_.Stop();
ColdModeTimerFired(&cold_mode_timer_);
}
void IdleSpellCheckController::SetNeedsMoreColdModeInvocationForTesting() {
cold_mode_requester_->SetNeedsMoreInvocationForTesting();
}
} // namespace blink