blob: 6a97ae4a32e2dffd87c11c7880b8b9b0ca5fa8af [file] [log] [blame]
// Copyright 2017 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/hot_mode_spell_check_requester.h"
#include "third_party/blink/renderer/core/editing/commands/composite_edit_command.h"
#include "third_party/blink/renderer/core/editing/commands/typing_command.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/backwards_character_iterator.h"
#include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
#include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
#include "third_party/blink/renderer/core/editing/selection_template.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_position.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
namespace blink {
namespace {
const int kHotModeCheckAllThreshold = 128;
const int kHotModeChunkSize = 1024;
EphemeralRange AdjacentWordIfExists(const Position& pos) {
const Position word_start = PreviousWordPosition(pos).GetPosition();
if (word_start.IsNull())
return EphemeralRange();
const Position word_end = EndOfWordPosition(word_start);
if (word_end.IsNull())
return EphemeralRange();
if (ComparePositions(pos, word_end) > 0)
return EphemeralRange();
return EphemeralRange(word_start, word_end);
}
EphemeralRange CurrentWordIfTypingInPartialWord(const Element& editable) {
const LocalFrame& frame = *editable.GetDocument().GetFrame();
const SelectionInDOMTree& selection =
frame.Selection().GetSelectionInDOMTree();
if (!selection.IsCaret())
return EphemeralRange();
if (RootEditableElementOf(selection.Base()) != &editable)
return EphemeralRange();
CompositeEditCommand* last_command = frame.GetEditor().LastEditCommand();
if (!last_command || !last_command->IsTypingCommand())
return EphemeralRange();
if (!last_command->EndingSelection().IsValidFor(*frame.GetDocument()))
return EphemeralRange();
if (last_command->EndingSelection().AsSelection() != selection)
return EphemeralRange();
return AdjacentWordIfExists(selection.Base());
}
EphemeralRange CalculateHotModeCheckingRange(const Element& editable,
const Position& position) {
// Check everything in |editable| if its total length is short.
const EphemeralRange& full_range = EphemeralRange::RangeOfContents(editable);
const int full_length = TextIterator::RangeLength(full_range);
// TODO(xiaochengh): There is no need to check if |full_length <= 2|, since
// we don't consider two characters as misspelled. However, a lot of layout
// tests depend on "zz" as misspelled, which should be changed.
if (full_length <= kHotModeCheckAllThreshold)
return full_range;
// Otherwise, if |position| is in a short paragraph, check the paragraph.
const EphemeralRange& paragraph_range =
ExpandToParagraphBoundary(EphemeralRange(position));
const int paragraph_length = TextIterator::RangeLength(paragraph_range);
if (paragraph_length <= kHotModeChunkSize)
return paragraph_range;
// Otherwise, check a chunk of text centered at |position|.
TextIteratorBehavior behavior = TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.Build();
BackwardsCharacterIterator backward_iterator(
EphemeralRange(full_range.StartPosition(), position), behavior);
if (!backward_iterator.AtEnd())
backward_iterator.Advance(kHotModeChunkSize / 2);
const Position& chunk_start = backward_iterator.EndPosition();
CharacterIterator forward_iterator(position, full_range.EndPosition(),
behavior);
if (!forward_iterator.AtEnd())
forward_iterator.Advance(kHotModeChunkSize / 2);
const Position& chunk_end = forward_iterator.EndPosition();
return ExpandRangeToSentenceBoundary(EphemeralRange(chunk_start, chunk_end));
}
} // namespace
HotModeSpellCheckRequester::HotModeSpellCheckRequester(
SpellCheckRequester& requester)
: requester_(requester) {}
void HotModeSpellCheckRequester::CheckSpellingAt(const Position& position) {
const Element* root_editable = RootEditableElementOf(position);
if (!root_editable || !root_editable->isConnected())
return;
if (processed_root_editables_.Contains(root_editable))
return;
processed_root_editables_.push_back(root_editable);
if (!root_editable->IsSpellCheckingEnabled() &&
!SpellChecker::IsSpellCheckingEnabledAt(position)) {
return;
}
const EphemeralRange& current_word =
CurrentWordIfTypingInPartialWord(*root_editable);
if (current_word.IsNotNull()) {
root_editable->GetDocument().Markers().RemoveMarkersInRange(
current_word, DocumentMarker::MarkerTypes::Misspelling());
return;
}
const EphemeralRange& checking_range =
CalculateHotModeCheckingRange(*root_editable, position);
requester_->RequestCheckingFor(checking_range);
}
} // namespace blink