blob: f665be1b2e6aee31d6e28a67122c60634c253a9f [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
* reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/dom/text.h"
#include <utility>
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
#include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/whitespace_attacher.h"
#include "third_party/blink/renderer/core/layout/layout_object_factory.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_text_combine.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_inline_text.h"
#include "third_party/blink/renderer/core/svg/svg_foreign_object_element.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
Text* Text::Create(Document& document, const String& data) {
return MakeGarbageCollected<Text>(document, data, kCreateText);
}
Text* Text::CreateEditingText(Document& document, const String& data) {
return MakeGarbageCollected<Text>(document, data, kCreateEditingText);
}
Node* Text::MergeNextSiblingNodesIfPossible() {
// Remove empty text nodes.
if (!length()) {
// Care must be taken to get the next node before removing the current node.
Node* next_node = NodeTraversal::NextPostOrder(*this);
remove(IGNORE_EXCEPTION_FOR_TESTING);
return next_node;
}
// Merge text nodes.
while (Node* next_sibling = nextSibling()) {
if (next_sibling->getNodeType() != kTextNode)
break;
auto* next_text = To<Text>(next_sibling);
// Remove empty text nodes.
if (!next_text->length()) {
next_text->remove(IGNORE_EXCEPTION_FOR_TESTING);
continue;
}
// Both non-empty text nodes. Merge them.
unsigned offset = length();
String next_text_data = next_text->data();
String old_text_data = data();
SetDataWithoutUpdate(data() + next_text_data);
UpdateTextLayoutObject(old_text_data.length(), 0);
GetDocument().DidMergeTextNodes(*this, *next_text, offset);
// Empty nextText for layout update.
next_text->SetDataWithoutUpdate(g_empty_string);
next_text->UpdateTextLayoutObject(0, next_text_data.length());
// Restore nextText for mutation event.
next_text->SetDataWithoutUpdate(next_text_data);
next_text->UpdateTextLayoutObject(0, 0);
GetDocument().IncDOMTreeVersion();
DidModifyData(old_text_data, CharacterData::kUpdateFromNonParser);
next_text->remove(IGNORE_EXCEPTION_FOR_TESTING);
}
return NodeTraversal::NextPostOrder(*this);
}
Text* Text::splitText(unsigned offset, ExceptionState& exception_state) {
// IndexSizeError: Raised if the specified offset is negative or greater than
// the number of 16-bit units in data.
if (offset > length()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The offset " + String::Number(offset) +
" is larger than the Text node's length.");
return nullptr;
}
EventQueueScope scope;
String old_str = data();
Text* new_text = CloneWithData(GetDocument(), old_str.Substring(offset));
SetDataWithoutUpdate(old_str.Substring(0, offset));
DidModifyData(old_str, CharacterData::kUpdateFromNonParser);
if (parentNode())
parentNode()->InsertBefore(new_text, nextSibling(), exception_state);
if (exception_state.HadException())
return nullptr;
if (GetLayoutObject()) {
GetLayoutObject()->SetTextWithOffset(DataImpl(), 0, old_str.length());
if (data().IsEmpty()) {
// To avoid |LayoutText| has empty text, we rebuild layout tree.
SetForceReattachLayoutTree();
}
}
if (parentNode())
GetDocument().DidSplitTextNode(*this);
else
GetDocument().DidRemoveText(*this, offset, old_str.length() - offset);
// [NewObject] must always create a new wrapper. Check that a wrapper
// does not exist yet.
DCHECK(
DOMDataStore::GetWrapper(new_text, v8::Isolate::GetCurrent()).IsEmpty());
return new_text;
}
static const Text* EarliestLogicallyAdjacentTextNode(const Text* t) {
for (const Node* n = t->previousSibling(); n; n = n->previousSibling()) {
if (auto* text_node = DynamicTo<Text>(n)) {
t = text_node;
continue;
}
break;
}
return t;
}
static const Text* LatestLogicallyAdjacentTextNode(const Text* t) {
for (const Node* n = t->nextSibling(); n; n = n->nextSibling()) {
if (auto* text_node = DynamicTo<Text>(n)) {
t = text_node;
continue;
}
break;
}
return t;
}
String Text::wholeText() const {
const Text* start_text = EarliestLogicallyAdjacentTextNode(this);
const Text* end_text = LatestLogicallyAdjacentTextNode(this);
Node* one_past_end_text = end_text->nextSibling();
unsigned result_length = 0;
for (const Node* n = start_text; n != one_past_end_text;
n = n->nextSibling()) {
auto* text_node = DynamicTo<Text>(n);
if (!text_node)
continue;
const String& data = text_node->data();
CHECK_GE(std::numeric_limits<unsigned>::max() - data.length(),
result_length);
result_length += data.length();
}
StringBuilder result;
result.ReserveCapacity(result_length);
for (const Node* n = start_text; n != one_past_end_text;
n = n->nextSibling()) {
auto* text_node = DynamicTo<Text>(n);
if (!text_node)
continue;
result.Append(text_node->data());
}
DCHECK_EQ(result.length(), result_length);
return result.ToString();
}
Text* Text::ReplaceWholeText(const String& new_text) {
// Remove all adjacent text nodes, and replace the contents of this one.
// Protect startText and endText against mutation event handlers removing the
// last ref
Text* start_text = const_cast<Text*>(EarliestLogicallyAdjacentTextNode(this));
Text* end_text = const_cast<Text*>(LatestLogicallyAdjacentTextNode(this));
ContainerNode* parent = parentNode(); // Protect against mutation handlers
// moving this node during traversal
for (Node* n = start_text;
n && n != this && n->IsTextNode() && n->parentNode() == parent;) {
Node* node_to_remove = n;
n = node_to_remove->nextSibling();
parent->RemoveChild(node_to_remove, IGNORE_EXCEPTION_FOR_TESTING);
}
if (this != end_text) {
Node* one_past_end_text = end_text->nextSibling();
for (Node* n = nextSibling(); n && n != one_past_end_text &&
n->IsTextNode() &&
n->parentNode() == parent;) {
Node* node_to_remove = n;
n = node_to_remove->nextSibling();
parent->RemoveChild(node_to_remove, IGNORE_EXCEPTION_FOR_TESTING);
}
}
if (new_text.IsEmpty()) {
if (parent && parentNode() == parent)
parent->RemoveChild(this, IGNORE_EXCEPTION_FOR_TESTING);
return nullptr;
}
setData(new_text);
return this;
}
String Text::nodeName() const {
return "#text";
}
Node::NodeType Text::getNodeType() const {
return kTextNode;
}
Node* Text::Clone(Document& factory, CloneChildrenFlag) const {
return CloneWithData(factory, data());
}
static inline bool EndsWithWhitespace(const String& text) {
return text.length() && IsASCIISpace(text[text.length() - 1]);
}
static inline bool CanHaveWhitespaceChildren(
const ComputedStyle& style,
const Text::AttachContext& context) {
const LayoutObject& parent = *context.parent;
// <button> and <fieldset> should allow whitespace even though
// LayoutFlexibleBox doesn't.
if (parent.IsButtonIncludingNG() || parent.IsFieldset())
return true;
if (parent.IsTable() || parent.IsTableRow() || parent.IsTableSection() ||
parent.IsLayoutTableCol() || parent.IsFrameSet() ||
parent.IsFlexibleBoxIncludingNG() || parent.IsLayoutGridIncludingNG() ||
parent.IsSVGRoot() || parent.IsSVGContainer() || parent.IsSVGImage() ||
parent.IsSVGShape()) {
if (!context.use_previous_in_flow || !context.previous_in_flow ||
!context.previous_in_flow->IsText())
return false;
return style.PreserveNewline() ||
!EndsWithWhitespace(
To<LayoutText>(context.previous_in_flow)->GetText());
}
return true;
}
bool Text::TextLayoutObjectIsNeeded(const AttachContext& context,
const ComputedStyle& style) const {
const LayoutObject& parent = *context.parent;
if (!parent.CanHaveChildren())
return false;
if (IsEditingText())
return true;
if (!length())
return false;
if (style.Display() == EDisplay::kNone)
return false;
if (!ContainsOnlyWhitespaceOrEmpty())
return true;
if (!CanHaveWhitespaceChildren(style, context))
return false;
// pre-wrap in SVG never makes layoutObject.
if (style.WhiteSpace() == EWhiteSpace::kPreWrap && parent.IsSVG())
return false;
// pre/pre-wrap/pre-line always make layoutObjects.
if (style.PreserveNewline())
return true;
if (!context.use_previous_in_flow)
return false;
if (!context.previous_in_flow)
return parent.IsLayoutInline();
if (context.previous_in_flow->IsText()) {
return !EndsWithWhitespace(
To<LayoutText>(context.previous_in_flow)->GetText());
}
return context.previous_in_flow->IsInline() &&
!context.previous_in_flow->IsBR();
}
static bool IsSVGText(Text* text) {
Node* parent_or_shadow_host_node = text->ParentOrShadowHostNode();
DCHECK(parent_or_shadow_host_node);
return parent_or_shadow_host_node->IsSVGElement() &&
!IsA<SVGForeignObjectElement>(*parent_or_shadow_host_node);
}
LayoutText* Text::CreateTextLayoutObject(const ComputedStyle& style,
LegacyLayout legacy) {
if (IsSVGText(this))
return new LayoutSVGInlineText(this, DataImpl());
if (style.HasTextCombine())
return new LayoutTextCombine(this, DataImpl());
return LayoutObjectFactory::CreateText(this, DataImpl(), legacy);
}
void Text::AttachLayoutTree(AttachContext& context) {
if (context.parent) {
ContainerNode* style_parent = LayoutTreeBuilderTraversal::Parent(*this);
if (style_parent) {
const ComputedStyle* style = style_parent->GetComputedStyle();
DCHECK(style);
if (TextLayoutObjectIsNeeded(context, *style)) {
LayoutTreeBuilderForText(*this, context, style).CreateLayoutObject();
context.previous_in_flow = GetLayoutObject();
}
}
}
CharacterData::AttachLayoutTree(context);
}
void Text::ReattachLayoutTreeIfNeeded(AttachContext& context) {
bool layout_object_is_needed = false;
ContainerNode* style_parent = LayoutTreeBuilderTraversal::Parent(*this);
if (style_parent && context.parent) {
DCHECK(style_parent->GetComputedStyle());
layout_object_is_needed =
TextLayoutObjectIsNeeded(context, *style_parent->GetComputedStyle());
}
if (layout_object_is_needed == !!GetLayoutObject())
return;
AttachContext reattach_context(context);
reattach_context.performing_reattach = true;
if (layout_object_is_needed) {
DCHECK(!GetLayoutObject());
LayoutTreeBuilderForText(*this, context, style_parent->GetComputedStyle())
.CreateLayoutObject();
} else {
DetachLayoutTree(true /* performing_reattach*/);
}
CharacterData::AttachLayoutTree(reattach_context);
}
namespace {
bool NeedsWhitespaceLayoutObject(const ComputedStyle& style) {
return style.PreserveNewline();
}
} // namespace
void Text::RecalcTextStyle(const StyleRecalcChange change) {
scoped_refptr<const ComputedStyle> new_style =
GetDocument().GetStyleResolver().StyleForText(this);
if (LayoutText* layout_text = GetLayoutObject()) {
const ComputedStyle* layout_parent_style =
GetLayoutObject()->Parent()->Style();
if (!new_style || GetForceReattachLayoutTree() ||
(new_style != layout_parent_style &&
!new_style->InheritedEqual(*layout_parent_style))) {
// The computed style or the need for an anonymous inline wrapper for a
// display:contents text child changed.
SetNeedsReattachLayoutTree();
} else {
layout_text->SetStyle(std::move(new_style));
if (NeedsStyleRecalc())
layout_text->SetTextIfNeeded(DataImpl());
}
} else if (new_style && (NeedsStyleRecalc() || change.ReattachLayoutTree() ||
GetForceReattachLayoutTree() ||
NeedsWhitespaceLayoutObject(*new_style))) {
SetNeedsReattachLayoutTree();
}
ClearNeedsStyleRecalc();
}
void Text::RebuildTextLayoutTree(WhitespaceAttacher& whitespace_attacher) {
DCHECK(!ChildNeedsStyleRecalc());
DCHECK(NeedsReattachLayoutTree());
DCHECK(parentNode());
AttachContext context;
context.parent = LayoutTreeBuilderTraversal::ParentLayoutObject(*this);
ReattachLayoutTree(context);
whitespace_attacher.DidReattachText(this);
ClearNeedsReattachLayoutTree();
}
// Passing both |text_node| and its layout object because repeated calls to
// |Node::GetLayoutObject()| are discouraged.
static bool ShouldUpdateLayoutByReattaching(const Text& text_node,
LayoutText* text_layout_object) {
DCHECK_EQ(text_node.GetLayoutObject(), text_layout_object);
if (!text_layout_object)
return true;
Node::AttachContext context;
context.parent = text_layout_object->Parent();
if (!text_node.TextLayoutObjectIsNeeded(context,
*text_layout_object->Style())) {
return true;
}
if (text_layout_object->IsTextFragment()) {
// Changes of |textNode| may change first letter part, so we should
// reattach. Note: When |textNode| is empty or holds collapsed white spaces
// |text_fragment_layout_object| represents first-letter part but it isn't
// inside first-letter-pseudo element. See http://crbug.com/978947
const auto& text_fragment_layout_object =
*To<LayoutTextFragment>(text_layout_object);
return text_fragment_layout_object.GetFirstLetterPseudoElement() ||
!text_fragment_layout_object.IsRemainingTextLayoutObject();
}
return false;
}
void Text::UpdateTextLayoutObject(unsigned offset_of_replaced_data,
unsigned length_of_replaced_data) {
if (!InActiveDocument())
return;
LayoutText* text_layout_object = GetLayoutObject();
if (ShouldUpdateLayoutByReattaching(*this, text_layout_object)) {
SetForceReattachLayoutTree();
return;
}
text_layout_object->SetTextWithOffset(DataImpl(), offset_of_replaced_data,
length_of_replaced_data);
}
Text* Text::CloneWithData(Document& factory, const String& data) const {
return Create(factory, data);
}
void Text::Trace(Visitor* visitor) const {
CharacterData::Trace(visitor);
}
} // namespace blink