| /* |
| * 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/ShadowRoot.h" |
| #include "core/dom/WhitespaceAttacher.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(); |
| 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()) { |
| 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; |
| } |
| |
| static inline bool EndsWithWhitespace(const String& text) { |
| return text.length() && IsASCIISpace(text[text.length() - 1]); |
| } |
| |
| bool Text::TextLayoutObjectIsNeeded(const AttachContext& context, |
| 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 (style.Display() != EDisplay::kContents && |
| !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; |
| |
| if (!context.use_previous_in_flow) |
| return false; |
| |
| if (!context.previous_in_flow) |
| return parent.IsLayoutInline(); |
| |
| if (context.previous_in_flow->IsText()) { |
| return !EndsWithWhitespace( |
| ToLayoutText(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() && |
| !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(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(context, *style_parent->GetComputedStyle(), |
| *parent_layout_object)) { |
| LayoutTreeBuilderForText(*this, parent_layout_object, |
| style_parent->MutableComputedStyle()) |
| .CreateLayoutObject(); |
| } |
| } |
| CharacterData::AttachLayoutTree(context); |
| } |
| |
| void Text::ReattachLayoutTreeIfNeeded(const AttachContext& context) { |
| 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( |
| context, *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(WhitespaceAttacher& whitespace_attacher) { |
| DCHECK(!ChildNeedsStyleRecalc()); |
| DCHECK(NeedsReattachLayoutTree()); |
| DCHECK(parentNode()); |
| |
| ReattachLayoutTree(); |
| whitespace_attacher.DidReattachText(this); |
| 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(Node::AttachContext(), |
| *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 |