blob: bd379c0c31be77a9f5e36af4496144316a5733ef [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 "core/dom/Text.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/SVGNames.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/FirstLetterPseudoElement.h"
#include "core/dom/LayoutTreeBuilder.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/events/ScopedEventQueue.h"
#include "core/layout/LayoutText.h"
#include "core/layout/LayoutTextCombine.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/api/LayoutTextItem.h"
#include "core/layout/svg/LayoutSVGInlineText.h"
#include "core/svg/SVGForeignObjectElement.h"
#include "platform/bindings/DOMDataStore.h"
#include "platform/wtf/text/CString.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
Text* Text::Create(Document& document, const String& data) {
return new Text(document, data, kCreateText);
}
Text* Text::CreateEditingText(Document& document, const String& data) {
return new 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 = this->nextSibling()) {
if (next_sibling->getNodeType() != kTextNode)
break;
Text* next_text = ToText(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(
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(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 (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()) {
Node::NodeType type = n->getNodeType();
if (type == Node::kTextNode || type == Node::kCdataSectionNode) {
t = ToText(n);
continue;
}
break;
}
return t;
}
static const Text* LatestLogicallyAdjacentTextNode(const Text* t) {
for (const Node* n = t->nextSibling(); n; n = n->nextSibling()) {
Node::NodeType type = n->getNodeType();
if (type == Node::kTextNode || type == Node::kCdataSectionNode) {
t = ToText(n);
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()) {
if (!n->IsTextNode())
continue;
const String& data = ToText(n)->data();
if (std::numeric_limits<unsigned>::max() - data.length() < result_length)
CRASH();
result_length += data.length();
}
StringBuilder result;
result.ReserveCapacity(result_length);
for (const Node* n = start_text; n != one_past_end_text;
n = n->nextSibling()) {
if (!n->IsTextNode())
continue;
result.Append(ToText(n)->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::cloneNode(bool /*deep*/, ExceptionState&) {
return CloneWithData(data());
}
static inline bool CanHaveWhitespaceChildren(const LayoutObject& parent) {
// <button> and <fieldset> should allow whitespace even though
// LayoutFlexibleBox doesn't.
if (parent.IsLayoutButton() || parent.IsFieldset())
return true;
if (parent.IsTable() || parent.IsTableRow() || parent.IsTableSection() ||
parent.IsLayoutTableCol() || parent.IsFrameSet() ||
parent.IsFlexibleBox() || parent.IsLayoutGrid() || parent.IsSVGRoot() ||
parent.IsSVGContainer() || parent.IsSVGImage() || parent.IsSVGShape()) {
return false;
}
return true;
}
bool Text::TextLayoutObjectIsNeeded(const ComputedStyle& style,
const LayoutObject& parent) const {
DCHECK(!GetDocument().ChildNeedsDistributionRecalc());
if (!parent.CanHaveChildren())
return false;
if (IsEditingText())
return true;
if (!length())
return false;
if (style.Display() == EDisplay::kNone)
return false;
if (!ContainsOnlyWhitespace())
return true;
if (!CanHaveWhitespaceChildren(parent))
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;
// Avoiding creation of a layoutObject for the text node is a non-essential
// memory optimization. So to avoid blowing up on very wide DOMs, we limit
// the number of siblings to visit.
unsigned max_siblings_to_visit = 50;
const LayoutObject* prev =
LayoutTreeBuilderTraversal::PreviousSiblingLayoutObject(
*this, max_siblings_to_visit);
if (prev && prev->IsBR()) // <span><br/> <br/></span>
return false;
if (parent.IsLayoutInline()) {
// <span><div/> <div/></span>
if (prev && !prev->IsInline() && !prev->IsFloatingOrOutOfFlowPositioned())
return false;
} else {
if (parent.IsLayoutBlock() && !parent.ChildrenInline() &&
(!prev || !prev->IsInline()))
return false;
LayoutObject* first = parent.SlowFirstChild();
for (; first && first->IsFloatingOrOutOfFlowPositioned() &&
max_siblings_to_visit;
first = first->NextSibling(), --max_siblings_to_visit) {
}
if (!first || first == GetLayoutObject() ||
LayoutTreeBuilderTraversal::NextSiblingLayoutObject(
*this, max_siblings_to_visit) == first) {
// If we're adding children to this flow our previous siblings are not in
// the layout tree yet so we cannot know if we will be the first child in
// the line and collapse away. We have to assume we need a layout object.
Node* first_child_node =
parent.GetNode()
? LayoutTreeBuilderTraversal::FirstChild(*parent.GetNode())
: nullptr;
if (first && first == GetLayoutObject() && first_child_node &&
!first_child_node->GetLayoutObject())
return true;
// Whitespace at the start of a block just goes away. Don't even
// make a layout object for this text.
return !first_child_node;
}
}
return true;
}
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() &&
!isSVGForeignObjectElement(*parent_or_shadow_host_node);
}
LayoutText* Text::CreateTextLayoutObject(const ComputedStyle& style) {
if (IsSVGText(this))
return new LayoutSVGInlineText(this, DataImpl());
if (style.HasTextCombine())
return new LayoutTextCombine(this, DataImpl());
return new LayoutText(this, DataImpl());
}
void Text::AttachLayoutTree(const AttachContext& context) {
ContainerNode* style_parent = LayoutTreeBuilderTraversal::Parent(*this);
LayoutObject* parent_layout_object =
LayoutTreeBuilderTraversal::ParentLayoutObject(*this);
if (style_parent && parent_layout_object) {
DCHECK(style_parent->GetComputedStyle());
if (TextLayoutObjectIsNeeded(*style_parent->GetComputedStyle(),
*parent_layout_object)) {
LayoutTreeBuilderForText(*this, parent_layout_object,
style_parent->MutableComputedStyle())
.CreateLayoutObject();
}
}
CharacterData::AttachLayoutTree(context);
}
void Text::ReattachLayoutTreeIfNeeded() {
bool layout_object_is_needed = false;
ContainerNode* style_parent = LayoutTreeBuilderTraversal::Parent(*this);
LayoutObject* parent_layout_object =
LayoutTreeBuilderTraversal::ParentLayoutObject(*this);
if (style_parent && parent_layout_object) {
DCHECK(style_parent->GetComputedStyle());
layout_object_is_needed = TextLayoutObjectIsNeeded(
*style_parent->GetComputedStyle(), *parent_layout_object);
}
if (layout_object_is_needed == !!GetLayoutObject())
return;
// The following is almost the same as Node::reattachLayoutTree() except that
// we create a layoutObject only if needed. Not calling reattachLayoutTree()
// to avoid repeated calls to Text::textLayoutObjectIsNeeded().
AttachContext reattach_context;
reattach_context.performing_reattach = true;
if (GetStyleChangeType() < kNeedsReattachStyleChange)
DetachLayoutTree(reattach_context);
if (layout_object_is_needed) {
LayoutTreeBuilderForText(*this, parent_layout_object,
style_parent->MutableComputedStyle())
.CreateLayoutObject();
}
CharacterData::AttachLayoutTree(reattach_context);
}
void Text::RecalcTextStyle(StyleRecalcChange change) {
if (LayoutTextItem layout_item = LayoutTextItem(this->GetLayoutObject())) {
if (change != kNoChange || NeedsStyleRecalc())
layout_item.SetStyle(
GetDocument().EnsureStyleResolver().StyleForText(this));
if (NeedsStyleRecalc())
layout_item.SetText(DataImpl());
ClearNeedsStyleRecalc();
} else if (NeedsStyleRecalc() || NeedsWhitespaceLayoutObject()) {
SetNeedsReattachLayoutTree();
}
}
void Text::RebuildTextLayoutTree(Text* next_text_sibling) {
DCHECK(!ChildNeedsStyleRecalc());
DCHECK(NeedsReattachLayoutTree());
DCHECK(parentNode());
ReattachLayoutTree();
if (GetLayoutObject())
ReattachWhitespaceSiblingsIfNeeded(next_text_sibling);
ClearNeedsReattachLayoutTree();
}
// If a whitespace node had no layoutObject and goes through a recalcStyle it
// may need to create one if the parent style now has white-space: pre.
bool Text::NeedsWhitespaceLayoutObject() {
DCHECK(!GetLayoutObject());
if (const ComputedStyle* style = ParentComputedStyle())
return style->PreserveNewline();
return false;
}
// Passing both |textNode| and its layout object because repeated calls to
// |Node::layoutObject()| 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;
// In general we do not want to branch on lifecycle states such as
// |childNeedsDistributionRecalc|, but this code tries to figure out if we can
// use an optimized code path that avoids reattach.
if (!text_node.GetDocument().ChildNeedsDistributionRecalc() &&
!text_node.TextLayoutObjectIsNeeded(*text_layout_object->Style(),
*text_layout_object->Parent())) {
return true;
}
if (text_layout_object->IsTextFragment()) {
// Changes of |textNode| may change first letter part, so we should
// reattach.
return ToLayoutTextFragment(text_layout_object)
->GetFirstLetterPseudoElement();
}
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)) {
LazyReattachIfAttached();
return;
}
text_layout_object->SetTextWithOffset(DataImpl(), offset_of_replaced_data,
length_of_replaced_data);
}
Text* Text::CloneWithData(const String& data) {
return Create(GetDocument(), data);
}
DEFINE_TRACE(Text) {
CharacterData::Trace(visitor);
}
} // namespace blink