blob: 614d0ca0a8ea1c361042e111333713f29b9236eb [file] [log] [blame]
// Copyright 2020 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/ime/cached_text_input_info.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
// static
TextIteratorBehavior CachedTextInputInfo::Behavior() {
return TextIteratorBehavior::Builder()
.SetEmitsObjectReplacementCharacter(true)
.SetEmitsSpaceForNbsp(true)
.Build();
}
void CachedTextInputInfo::ClearIfNeeded(const LayoutObject& layout_object) {
if (layout_object_ != &layout_object)
return;
container_ = nullptr;
layout_object_ = nullptr;
text_ = g_empty_string;
composition_.Clear();
selection_.Clear();
offset_map_.clear();
}
void CachedTextInputInfo::DidLayoutSubtree(const LayoutObject& layout_object) {
// <div style="contain:strict; ...">abc</div> reaches here.
if (!container_)
return;
Node* const node = layout_object.NonPseudoNode();
if (!node)
return;
const ContainerNode* const container =
RootEditableElementOrTreeScopeRootNodeOf(Position(node, 0));
if (!container || !container->GetLayoutObject())
return;
ClearIfNeeded(*container->GetLayoutObject());
}
void CachedTextInputInfo::DidUpdateLayout(const LayoutObject& layout_object) {
ClearIfNeeded(layout_object);
}
void CachedTextInputInfo::EnsureCached(const ContainerNode& container) const {
if (IsValidFor(container))
return;
offset_map_.clear();
container_ = &container;
layout_object_ = container.GetLayoutObject();
composition_.Clear();
selection_.Clear();
text_ = g_empty_string;
TextIteratorAlgorithm<EditingStrategy> it(
EphemeralRange::RangeOfContents(container), Behavior());
if (it.AtEnd())
return;
const bool needs_text = HasEditableStyle(*container_);
// The initial buffer size can be critical for performance:
// https://bugs.webkit.org/show_bug.cgi?id=81192
constexpr unsigned kInitialCapacity = 1 << 15;
StringBuilder builder;
if (needs_text) {
unsigned capacity = kInitialCapacity;
if (auto* block_flow =
DynamicTo<LayoutBlockFlow>(container.GetLayoutObject())) {
if (block_flow->HasNGInlineNodeData()) {
if (const auto* mapping = NGInlineNode::GetOffsetMapping(block_flow))
capacity = mapping->GetText().length();
}
}
builder.ReserveCapacity(capacity);
}
const Node* last_text_node = nullptr;
unsigned length = 0;
for (; !it.AtEnd(); it.Advance()) {
const Node* node = it.GetTextState().PositionNode();
if (last_text_node != node && IsA<Text>(node)) {
last_text_node = node;
offset_map_.insert(To<Text>(node), length);
}
if (needs_text)
it.GetTextState().AppendTextToStringBuilder(builder);
length += it.GetTextState().length();
}
if (!builder.IsEmpty())
text_ = builder.ToString();
}
PlainTextRange CachedTextInputInfo::GetComposition(
const EphemeralRange& range) const {
DCHECK(container_);
return GetPlainTextRangeWithCache(range, &composition_);
}
PlainTextRange CachedTextInputInfo::GetPlainTextRangeWithCache(
const EphemeralRange& range,
CachedPlainTextRange* text_range) const {
if (!text_range->IsValidFor(range))
text_range->Set(range, GetPlainTextRange(range));
return text_range->Get();
}
PlainTextRange CachedTextInputInfo::GetPlainTextRange(
const EphemeralRange& range) const {
if (range.IsNull())
return PlainTextRange();
const Position container_start = Position(*container_, 0);
// When selection is moved to another editable during IME composition,
// |range| may not in |container|. See http://crbug.com/1161562
if (container_start > range.StartPosition())
return PlainTextRange();
const unsigned start_offset =
RangeLength(EphemeralRange(container_start, range.StartPosition()));
const unsigned end_offset =
range.IsCollapsed()
? start_offset
: RangeLength(EphemeralRange(container_start, range.EndPosition()));
DCHECK_EQ(
static_cast<unsigned>(TextIterator::RangeLength(
EphemeralRange(container_start, range.EndPosition()), Behavior())),
end_offset);
return PlainTextRange(start_offset, end_offset);
}
PlainTextRange CachedTextInputInfo::GetSelection(
const EphemeralRange& range) const {
DCHECK(container_);
return GetPlainTextRangeWithCache(range, &selection_);
}
String CachedTextInputInfo::GetText() const {
DCHECK(container_);
DCHECK(HasEditableStyle(*container_));
return text_;
}
bool CachedTextInputInfo::IsValidFor(const ContainerNode& container) const {
return container_ == container &&
layout_object_ == container.GetLayoutObject();
}
void CachedTextInputInfo::LayoutObjectWillBeDestroyed(
const LayoutObject& layout_object) {
ClearIfNeeded(layout_object);
}
unsigned CachedTextInputInfo::RangeLength(const EphemeralRange& range) const {
const Node* const node = range.EndPosition().AnchorNode();
if (range.StartPosition() == Position(*container_, 0) && IsA<Text>(node)) {
const auto it = offset_map_.find(To<Text>(node));
if (it != offset_map_.end()) {
const unsigned length =
it->value +
TextIterator::RangeLength(
EphemeralRange(Position(node, 0), range.EndPosition()),
Behavior());
DCHECK_EQ(
static_cast<unsigned>(TextIterator::RangeLength(range, Behavior())),
length)
<< it->value << " " << range;
return length;
}
}
return TextIterator::RangeLength(range, Behavior());
}
void CachedTextInputInfo::Trace(Visitor* visitor) const {
visitor->Trace(container_);
visitor->Trace(composition_);
visitor->Trace(offset_map_);
visitor->Trace(selection_);
}
void CachedTextInputInfo::CachedPlainTextRange::Clear() {
start_ = end_ = Position();
start_offset_ = end_offset_ = kNotFound;
}
PlainTextRange CachedTextInputInfo::CachedPlainTextRange::Get() const {
if (start_offset_ == kNotFound)
return PlainTextRange();
return PlainTextRange(start_offset_, end_offset_);
}
bool CachedTextInputInfo::CachedPlainTextRange::IsValidFor(
const EphemeralRange& range) const {
return range.StartPosition() == start_ && range.EndPosition() == end_;
}
void CachedTextInputInfo::CachedPlainTextRange::Set(
const EphemeralRange& range,
const PlainTextRange& text_range) {
start_ = range.StartPosition();
end_ = range.EndPosition();
if (text_range.IsNull()) {
start_offset_ = end_offset_ = kNotFound;
} else {
start_offset_ = text_range.Start();
end_offset_ = text_range.End();
}
}
void CachedTextInputInfo::CachedPlainTextRange::Trace(Visitor* visitor) const {
visitor->Trace(start_);
visitor->Trace(end_);
}
} // namespace blink