blob: e82446cd90c44069fece820999ae097a511a0ea6 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2005 Alexey Proskuryakov.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/editing/iterators/TextIterator.h"
#include <unicode/utf16.h>
#include "core/dom/Document.h"
#include "core/dom/ShadowRoot.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/Position.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleUnits.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/forms/HTMLInputElement.h"
#include "core/html/forms/TextControlElement.h"
#include "core/html_names.h"
#include "core/input_type_names.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutTableRow.h"
#include "platform/fonts/Font.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
using namespace HTMLNames;
namespace {
template <typename Strategy>
TextIteratorBehavior AdjustBehaviorFlags(const TextIteratorBehavior&);
template <>
TextIteratorBehavior AdjustBehaviorFlags<EditingStrategy>(
const TextIteratorBehavior& behavior) {
if (!behavior.ForSelectionToString())
return behavior;
return TextIteratorBehavior::Builder(behavior)
.SetExcludeAutofilledValue(true)
.Build();
}
template <>
TextIteratorBehavior AdjustBehaviorFlags<EditingInFlatTreeStrategy>(
const TextIteratorBehavior& behavior) {
return TextIteratorBehavior::Builder(behavior)
.SetExcludeAutofilledValue(behavior.ForSelectionToString() ||
behavior.ExcludeAutofilledValue())
.SetEntersOpenShadowRoots(false)
.SetEntersTextControls(false)
.Build();
}
static inline bool HasDisplayContents(const Node& node) {
return node.IsElementNode() && ToElement(node).HasDisplayContentsStyle();
}
// Checks if |advance()| skips the descendants of |node|, which is the case if
// |node| is neither a shadow root nor the owner of a layout object.
static bool NotSkipping(const Node& node) {
return node.GetLayoutObject() || HasDisplayContents(node) ||
(node.IsShadowRoot() && node.OwnerShadowHost()->GetLayoutObject());
}
template <typename Strategy>
const Node* StartNode(const Node* start_container, unsigned start_offset) {
if (start_container->IsCharacterDataNode())
return start_container;
if (Node* child = Strategy::ChildAt(*start_container, start_offset))
return child;
if (!start_offset)
return start_container;
return Strategy::NextSkippingChildren(*start_container);
}
template <typename Strategy>
const Node* EndNode(const Node& end_container, unsigned end_offset) {
if (!end_container.IsCharacterDataNode() && end_offset)
return Strategy::ChildAt(end_container, end_offset - 1);
return nullptr;
}
// This function is like Range::PastLastNode, except for the fact that it can
// climb up out of shadow trees and ignores all nodes that will be skipped in
// |advance()|.
template <typename Strategy>
const Node* PastLastNode(const Node& range_end_container,
unsigned range_end_offset) {
if (!range_end_container.IsCharacterDataNode() &&
NotSkipping(range_end_container)) {
for (Node* next = Strategy::ChildAt(range_end_container, range_end_offset);
next; next = Strategy::NextSibling(*next)) {
if (NotSkipping(*next))
return next;
}
}
for (const Node* node = &range_end_container; node;) {
const Node* parent = ParentCrossingShadowBoundaries<Strategy>(*node);
if (parent && NotSkipping(*parent)) {
if (Node* next = Strategy::NextSibling(*node))
return next;
}
node = parent;
}
return nullptr;
}
// Figure out the initial value of m_shadowDepth: the depth of startContainer's
// tree scope from the common ancestor tree scope.
template <typename Strategy>
unsigned ShadowDepthOf(const Node& start_container, const Node& end_container);
template <>
unsigned ShadowDepthOf<EditingStrategy>(const Node& start_container,
const Node& end_container) {
const TreeScope* common_ancestor_tree_scope =
start_container.GetTreeScope().CommonAncestorTreeScope(
end_container.GetTreeScope());
DCHECK(common_ancestor_tree_scope);
unsigned shadow_depth = 0;
for (const TreeScope* tree_scope = &start_container.GetTreeScope();
tree_scope != common_ancestor_tree_scope;
tree_scope = tree_scope->ParentTreeScope())
++shadow_depth;
return shadow_depth;
}
template <>
unsigned ShadowDepthOf<EditingInFlatTreeStrategy>(const Node& start_container,
const Node& end_container) {
return 0;
}
bool IsRenderedAsTable(const Node* node) {
if (!node || !node->IsElementNode())
return false;
LayoutObject* layout_object = node->GetLayoutObject();
return layout_object && layout_object->IsTable();
}
} // namespace
template <typename Strategy>
TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm(
const EphemeralRangeTemplate<Strategy>& range,
const TextIteratorBehavior& behavior)
: TextIteratorAlgorithm(range.StartPosition(),
range.EndPosition(),
behavior) {}
template <typename Strategy>
TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm(
const PositionTemplate<Strategy>& start,
const PositionTemplate<Strategy>& end,
const TextIteratorBehavior& behavior)
: start_container_(start.ComputeContainerNode()),
start_offset_(start.ComputeOffsetInContainerNode()),
end_container_(end.ComputeContainerNode()),
end_offset_(end.ComputeOffsetInContainerNode()),
end_node_(EndNode<Strategy>(*end_container_, end_offset_)),
past_end_node_(PastLastNode<Strategy>(*end_container_, end_offset_)),
node_(StartNode<Strategy>(start_container_, start_offset_)),
iteration_progress_(kHandledNone),
shadow_depth_(
ShadowDepthOf<Strategy>(*start_container_, *end_container_)),
behavior_(AdjustBehaviorFlags<Strategy>(behavior)),
text_state_(behavior_),
text_node_handler_(behavior_, &text_state_) {
DCHECK(start_container_);
DCHECK(end_container_);
// TODO(dglazkov): TextIterator should not be created for documents that don't
// have a frame, but it currently still happens in some cases. See
// http://crbug.com/591877 for details.
DCHECK(!start.GetDocument()->View() ||
!start.GetDocument()->View()->NeedsLayout());
DCHECK(!start.GetDocument()->NeedsLayoutTreeUpdate());
// To avoid renderer hang, we use |CHECK_LE()| to catch the bad callers
// in release build.
CHECK_LE(start, end);
if (!node_)
return;
fully_clipped_stack_.SetUpFullyClippedStack(node_);
// Identify the first run.
Advance();
}
template <typename Strategy>
TextIteratorAlgorithm<Strategy>::~TextIteratorAlgorithm() {
if (!handle_shadow_root_)
return;
Document* document = OwnerDocument();
if (!document)
return;
if (behavior_.ForInnerText())
UseCounter::Count(document, WebFeature::kInnerTextWithShadowTree);
if (behavior_.ForSelectionToString())
UseCounter::Count(document, WebFeature::kSelectionToStringWithShadowTree);
if (behavior_.ForWindowFind())
UseCounter::Count(document, WebFeature::kWindowFindWithShadowTree);
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::IsInsideAtomicInlineElement() const {
if (AtEnd() || length() != 1 || !node_)
return false;
LayoutObject* layout_object = node_->GetLayoutObject();
return layout_object && layout_object->IsAtomicInlineLevel();
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::HandleRememberedProgress() {
// Handle remembered node that needed a newline after the text node's newline
if (needs_another_newline_) {
// Emit the extra newline, and position it *inside* m_node, after m_node's
// contents, in case it's a block, in the same way that we position the
// first newline. The range for the emitted newline should start where the
// line break begins.
// FIXME: It would be cleaner if we emitted two newlines during the last
// iteration, instead of using m_needsAnotherNewline.
Node* last_child = Strategy::LastChild(*node_);
const Node* base_node = last_child ? last_child : node_.Get();
SpliceBuffer('\n', Strategy::Parent(*base_node), base_node, 1, 1);
needs_another_newline_ = false;
return true;
}
if (needs_handle_replaced_element_) {
HandleReplacedElement();
if (text_state_.PositionNode())
return true;
}
// Try to emit more text runs if we are handling a text node.
return text_node_handler_.HandleRemainingTextRuns();
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::Advance() {
if (should_stop_)
return;
if (node_)
DCHECK(!node_->GetDocument().NeedsLayoutTreeUpdate()) << node_;
text_state_.ResetRunInformation();
if (HandleRememberedProgress())
return;
while (node_ && (node_ != past_end_node_ || shadow_depth_)) {
if (!should_stop_ && StopsOnFormControls() &&
HTMLFormControlElement::EnclosingFormControlElement(node_))
should_stop_ = true;
// if the range ends at offset 0 of an element, represent the
// position, but not the content, of that element e.g. if the
// node is a blockflow element, emit a newline that
// precedes the element
if (node_ == end_container_ && !end_offset_) {
RepresentNodeOffsetZero();
node_ = nullptr;
return;
}
LayoutObject* layout_object = node_->GetLayoutObject();
if (!layout_object) {
if (node_->IsShadowRoot() || HasDisplayContents(*node_)) {
// Shadow roots or display: contents elements don't have LayoutObjects,
// but we want to visit children anyway.
iteration_progress_ = iteration_progress_ < kHandledNode
? kHandledNode
: iteration_progress_;
handle_shadow_root_ = node_->IsShadowRoot();
} else {
iteration_progress_ = kHandledChildren;
}
} else {
// Enter author shadow roots, from youngest, if any and if necessary.
if (iteration_progress_ < kHandledOpenShadowRoots) {
if (EntersOpenShadowRoots() && node_->IsElementNode() &&
ToElement(node_)->OpenShadowRoot()) {
ShadowRoot* youngest_shadow_root = ToElement(node_)->OpenShadowRoot();
DCHECK(youngest_shadow_root->GetType() == ShadowRootType::V0 ||
youngest_shadow_root->GetType() == ShadowRootType::kOpen);
node_ = youngest_shadow_root;
iteration_progress_ = kHandledNone;
++shadow_depth_;
fully_clipped_stack_.PushFullyClippedState(node_);
continue;
}
iteration_progress_ = kHandledOpenShadowRoots;
}
// Enter user-agent shadow root, if necessary.
if (iteration_progress_ < kHandledUserAgentShadowRoot) {
if (EntersTextControls() && layout_object->IsTextControl()) {
ShadowRoot* user_agent_shadow_root =
ToElement(node_)->UserAgentShadowRoot();
DCHECK(user_agent_shadow_root->IsUserAgent());
node_ = user_agent_shadow_root;
iteration_progress_ = kHandledNone;
++shadow_depth_;
fully_clipped_stack_.PushFullyClippedState(node_);
continue;
}
iteration_progress_ = kHandledUserAgentShadowRoot;
}
// Handle the current node according to its type.
if (iteration_progress_ < kHandledNode) {
if (!SkipsUnselectableContent() || layout_object->IsSelectable()) {
if (layout_object->IsText() &&
node_->getNodeType() ==
Node::kTextNode) { // FIXME: What about kCdataSectionNode?
if (!fully_clipped_stack_.Top() || IgnoresStyleVisibility())
HandleTextNode();
} else if (layout_object &&
(layout_object->IsImage() ||
layout_object->IsLayoutEmbeddedContent() ||
(node_ && node_->IsHTMLElement() &&
(IsHTMLFormControlElement(ToHTMLElement(*node_)) ||
IsHTMLLegendElement(ToHTMLElement(*node_)) ||
IsHTMLImageElement(ToHTMLElement(*node_)) ||
IsHTMLMeterElement(ToHTMLElement(*node_)) ||
IsHTMLProgressElement(ToHTMLElement(*node_)))))) {
HandleReplacedElement();
} else {
HandleNonTextNode();
}
}
iteration_progress_ = kHandledNode;
if (text_state_.PositionNode())
return;
}
}
// Find a new current node to handle in depth-first manner,
// calling exitNode() as we come back thru a parent node.
//
// 1. Iterate over child nodes, if we haven't done yet.
// To support |TextIteratorEmitsImageAltText|, we don't traversal child
// nodes, in flat tree.
Node* next =
iteration_progress_ < kHandledChildren && !IsHTMLImageElement(*node_)
? Strategy::FirstChild(*node_)
: nullptr;
if (!next) {
// 2. If we've already iterated children or they are not available, go to
// the next sibling node.
next = Strategy::NextSibling(*node_);
if (!next) {
// 3. If we are at the last child, go up the node tree until we find a
// next sibling.
ContainerNode* parent_node = Strategy::Parent(*node_);
while (!next && parent_node) {
if (node_ == end_node_ ||
Strategy::IsDescendantOf(*end_container_, *parent_node))
return;
bool have_layout_object = node_->GetLayoutObject();
node_ = parent_node;
fully_clipped_stack_.Pop();
parent_node = Strategy::Parent(*node_);
if (have_layout_object)
ExitNode();
if (text_state_.PositionNode()) {
iteration_progress_ = kHandledChildren;
return;
}
next = Strategy::NextSibling(*node_);
}
if (!next && !parent_node && shadow_depth_) {
// 4. Reached the top of a shadow root. If it's created by author,
// then try to visit the next
// sibling shadow root, if any.
if (!node_->IsShadowRoot()) {
NOTREACHED();
should_stop_ = true;
return;
}
const ShadowRoot* shadow_root = ToShadowRoot(node_);
if (shadow_root->GetType() == ShadowRootType::V0 ||
shadow_root->GetType() == ShadowRootType::kOpen) {
ShadowRoot* next_shadow_root = shadow_root->OlderShadowRoot();
if (next_shadow_root &&
next_shadow_root->GetType() == ShadowRootType::V0) {
fully_clipped_stack_.Pop();
node_ = next_shadow_root;
iteration_progress_ = kHandledNone;
// m_shadowDepth is unchanged since we exit from a shadow root and
// enter another.
fully_clipped_stack_.PushFullyClippedState(node_);
} else {
// We are the last shadow root; exit from here and go back to
// where we were.
node_ = &shadow_root->host();
iteration_progress_ = kHandledOpenShadowRoots;
--shadow_depth_;
fully_clipped_stack_.Pop();
}
} else {
// If we are in a closed or user-agent shadow root, then go back to
// the host.
// TODO(kochi): Make sure we treat closed shadow as user agent
// shadow here.
DCHECK(shadow_root->GetType() == ShadowRootType::kClosed ||
shadow_root->IsUserAgent());
node_ = &shadow_root->host();
iteration_progress_ = kHandledUserAgentShadowRoot;
--shadow_depth_;
fully_clipped_stack_.Pop();
}
continue;
}
}
fully_clipped_stack_.Pop();
}
// set the new current node
node_ = next;
if (node_)
fully_clipped_stack_.PushFullyClippedState(node_);
iteration_progress_ = kHandledNone;
// how would this ever be?
if (text_state_.PositionNode())
return;
}
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::HandleTextNode() {
if (ExcludesAutofilledValue()) {
TextControlElement* control = EnclosingTextControl(node_);
// For security reason, we don't expose suggested value if it is
// auto-filled.
if (control && control->IsAutofilled())
return;
}
DCHECK_NE(last_text_node_, node_)
<< "We should never call HandleTextNode on the same node twice";
const Text* text = ToText(node_);
last_text_node_ = text;
// TODO(editing-dev): Introduce a |DOMOffsetRange| class so that we can pass
// an offset range with unbounded endpoint(s) in an easy but still clear way.
if (node_ != start_container_) {
if (node_ != end_container_)
text_node_handler_.HandleTextNodeWhole(text);
else
text_node_handler_.HandleTextNodeEndAt(text, end_offset_);
return;
}
if (node_ != end_container_) {
text_node_handler_.HandleTextNodeStartFrom(text, start_offset_);
return;
}
text_node_handler_.HandleTextNodeInRange(text, start_offset_, end_offset_);
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::SupportsAltText(const Node& node) {
if (!node.IsHTMLElement())
return false;
const HTMLElement& element = ToHTMLElement(node);
// FIXME: Add isSVGImageElement.
if (IsHTMLImageElement(element))
return true;
if (IsHTMLInputElement(element) &&
ToHTMLInputElement(node).type() == InputTypeNames::image)
return true;
return false;
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::HandleReplacedElement() {
needs_handle_replaced_element_ = false;
if (fully_clipped_stack_.Top())
return;
LayoutObject* layout_object = node_->GetLayoutObject();
if (layout_object->Style()->Visibility() != EVisibility::kVisible &&
!IgnoresStyleVisibility()) {
return;
}
if (EmitsObjectReplacementCharacter()) {
SpliceBuffer(kObjectReplacementCharacter, Strategy::Parent(*node_), node_,
0, 1);
return;
}
DCHECK_EQ(last_text_node_, text_node_handler_.GetNode());
if (last_text_node_) {
if (text_node_handler_.FixLeadingWhiteSpaceForReplacedElement(
Strategy::Parent(*last_text_node_))) {
needs_handle_replaced_element_ = true;
return;
}
}
if (EntersTextControls() && layout_object->IsTextControl()) {
// The shadow tree should be already visited.
return;
}
if (EmitsCharactersBetweenAllVisiblePositions()) {
// We want replaced elements to behave like punctuation for boundary
// finding, and to simply take up space for the selection preservation
// code in moveParagraphs, so we use a comma.
SpliceBuffer(',', Strategy::Parent(*node_), node_, 0, 1);
return;
}
text_state_.UpdateForReplacedElement(node_);
if (EmitsImageAltText() && TextIterator::SupportsAltText(*node_)) {
text_state_.EmitAltText(node_);
if (text_state_.length())
return;
}
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldEmitTabBeforeNode(
const Node& node) {
LayoutObject* r = node.GetLayoutObject();
// Table cells are delimited by tabs.
if (!r || !IsTableCell(&node))
return false;
// Want a tab before every cell other than the first one
LayoutTableCell* rc = ToLayoutTableCell(r);
LayoutTable* t = rc->Table();
return t && (t->CellPreceding(*rc) || t->CellAbove(*rc));
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineForNode(
const Node& node,
bool emits_original_text) {
LayoutObject* layout_object = node.GetLayoutObject();
if (layout_object ? !layout_object->IsBR() : !IsHTMLBRElement(node))
return false;
return emits_original_text || !(node.IsInShadowTree() &&
IsHTMLInputElement(*node.OwnerShadowHost()));
}
static bool ShouldEmitNewlinesBeforeAndAfterNode(const Node& node) {
// Block flow (versus inline flow) is represented by having
// a newline both before and after the element.
LayoutObject* r = node.GetLayoutObject();
if (!r) {
return (node.HasTagName(blockquoteTag) || node.HasTagName(ddTag) ||
node.HasTagName(divTag) || node.HasTagName(dlTag) ||
node.HasTagName(dtTag) || node.HasTagName(h1Tag) ||
node.HasTagName(h2Tag) || node.HasTagName(h3Tag) ||
node.HasTagName(h4Tag) || node.HasTagName(h5Tag) ||
node.HasTagName(h6Tag) || node.HasTagName(hrTag) ||
node.HasTagName(liTag) || node.HasTagName(listingTag) ||
node.HasTagName(olTag) || node.HasTagName(pTag) ||
node.HasTagName(preTag) || node.HasTagName(trTag) ||
node.HasTagName(ulTag));
}
// Need to make an exception for option and optgroup, because we want to
// keep the legacy behavior before we added layoutObjects to them.
if (IsHTMLOptionElement(node) || IsHTMLOptGroupElement(node))
return false;
// Need to make an exception for table cells, because they are blocks, but we
// want them tab-delimited rather than having newlines before and after.
if (IsTableCell(&node))
return false;
// Need to make an exception for table row elements, because they are neither
// "inline" or "LayoutBlock", but we want newlines for them.
if (r->IsTableRow()) {
LayoutTable* t = ToLayoutTableRow(r)->Table();
if (t && !t->IsInline())
return true;
}
return !r->IsInline() && r->IsLayoutBlock() &&
!r->IsFloatingOrOutOfFlowPositioned() && !r->IsBody() &&
!r->IsRubyText();
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineAfterNode(
const Node& node) {
// FIXME: It should be better but slower to create a VisiblePosition here.
if (!ShouldEmitNewlinesBeforeAndAfterNode(node))
return false;
// Check if this is the very last layoutObject in the document.
// If so, then we should not emit a newline.
const Node* next = &node;
do {
next = Strategy::NextSkippingChildren(*next);
if (next && next->GetLayoutObject())
return true;
} while (next);
return false;
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldEmitNewlineBeforeNode(
const Node& node) {
return ShouldEmitNewlinesBeforeAndAfterNode(node);
}
static bool ShouldEmitExtraNewlineForNode(const Node* node) {
// https://html.spec.whatwg.org/multipage/dom.html#the-innertext-idl-attribute
// Append two required linebreaks after a P element.
LayoutObject* r = node->GetLayoutObject();
if (!r || !r->IsBox())
return false;
return node->HasTagName(pTag);
}
// Whether or not we should emit a character as we enter m_node (if it's a
// container) or as we hit it (if it's atomic).
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldRepresentNodeOffsetZero() {
if (EmitsCharactersBetweenAllVisiblePositions() && IsRenderedAsTable(node_))
return true;
// Leave element positioned flush with start of a paragraph
// (e.g. do not insert tab before a table cell at the start of a paragraph)
if (text_state_.LastCharacter() == '\n')
return false;
// Otherwise, show the position if we have emitted any characters
if (text_state_.HasEmitted())
return true;
// We've not emitted anything yet. Generally, there is no need for any
// positioning then. The only exception is when the element is visually not in
// the same line as the start of the range (e.g. the range starts at the end
// of the previous paragraph).
// NOTE: Creating VisiblePositions and comparing them is relatively expensive,
// so we make quicker checks to possibly avoid that. Another check that we
// could make is is whether the inline vs block flow changed since the
// previous visible element. I think we're already in a special enough case
// that that won't be needed, tho.
// No character needed if this is the first node in the range.
if (node_ == start_container_)
return false;
// If we are outside the start container's subtree, assume we need to emit.
// FIXME: m_startContainer could be an inline block
if (!Strategy::IsDescendantOf(*node_, *start_container_))
return true;
// If we started as m_startContainer offset 0 and the current node is a
// descendant of the start container, we already had enough context to
// correctly decide whether to emit after a preceding block. We chose not to
// emit (m_hasEmitted is false), so don't second guess that now.
// NOTE: Is this really correct when m_node is not a leftmost descendant?
// Probably immaterial since we likely would have already emitted something by
// now.
if (!start_offset_)
return false;
// If this node is unrendered or invisible the VisiblePosition checks below
// won't have much meaning.
// Additionally, if the range we are iterating over contains huge sections of
// unrendered content, we would create VisiblePositions on every call to this
// function without this check.
if (!node_->GetLayoutObject() ||
node_->GetLayoutObject()->Style()->Visibility() !=
EVisibility::kVisible ||
(node_->GetLayoutObject()->IsLayoutBlockFlow() &&
!ToLayoutBlock(node_->GetLayoutObject())->Size().Height() &&
!IsHTMLBodyElement(*node_)))
return false;
// The startPos.isNotNull() check is needed because the start could be before
// the body, and in that case we'll get null. We don't want to put in newlines
// at the start in that case.
// The currPos.isNotNull() check is needed because positions in non-HTML
// content (like SVG) do not have visible positions, and we don't want to emit
// for them either.
VisiblePosition start_pos =
CreateVisiblePosition(Position(start_container_, start_offset_));
VisiblePosition curr_pos = VisiblePosition::BeforeNode(*node_);
return start_pos.IsNotNull() && curr_pos.IsNotNull() &&
!InSameLine(start_pos, curr_pos);
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::ShouldEmitSpaceBeforeAndAfterNode(
const Node& node) {
return IsRenderedAsTable(&node) &&
(node.GetLayoutObject()->IsInline() ||
EmitsCharactersBetweenAllVisiblePositions());
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::RepresentNodeOffsetZero() {
// Emit a character to show the positioning of m_node.
// When we haven't been emitting any characters,
// shouldRepresentNodeOffsetZero() can create VisiblePositions, which is
// expensive. So, we perform the inexpensive checks on m_node to see if it
// necessitates emitting a character first and will early return before
// encountering shouldRepresentNodeOffsetZero()s worse case behavior.
if (ShouldEmitTabBeforeNode(*node_)) {
if (ShouldRepresentNodeOffsetZero())
SpliceBuffer('\t', Strategy::Parent(*node_), node_, 0, 0);
} else if (ShouldEmitNewlineBeforeNode(*node_)) {
if (ShouldRepresentNodeOffsetZero())
SpliceBuffer('\n', Strategy::Parent(*node_), node_, 0, 0);
} else if (ShouldEmitSpaceBeforeAndAfterNode(*node_)) {
if (ShouldRepresentNodeOffsetZero())
SpliceBuffer(kSpaceCharacter, Strategy::Parent(*node_), node_, 0, 0);
}
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::HandleNonTextNode() {
if (ShouldEmitNewlineForNode(*node_, EmitsOriginalText()))
SpliceBuffer('\n', Strategy::Parent(*node_), node_, 0, 1);
else if (EmitsCharactersBetweenAllVisiblePositions() &&
node_->GetLayoutObject() && node_->GetLayoutObject()->IsHR())
SpliceBuffer(kSpaceCharacter, Strategy::Parent(*node_), node_, 0, 1);
else
RepresentNodeOffsetZero();
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::ExitNode() {
// prevent emitting a newline when exiting a collapsed block at beginning of
// the range
// FIXME: !m_hasEmitted does not necessarily mean there was a collapsed
// block... it could have been an hr (e.g.). Also, a collapsed block could
// have height (e.g. a table) and therefore look like a blank line.
if (!text_state_.HasEmitted())
return;
// Emit with a position *inside* m_node, after m_node's contents, in
// case it is a block, because the run should start where the
// emitted character is positioned visually.
Node* last_child = Strategy::LastChild(*node_);
const Node* base_node = last_child ? last_child : node_.Get();
// FIXME: This shouldn't require the m_lastTextNode to be true, but we can't
// change that without making the logic in _web_attributedStringFromRange
// match. We'll get that for free when we switch to use TextIterator in
// _web_attributedStringFromRange. See <rdar://problem/5428427> for an example
// of how this mismatch will cause problems.
if (last_text_node_ && ShouldEmitNewlineAfterNode(*node_)) {
// use extra newline to represent margin bottom, as needed
const bool add_newline = !behavior_.SuppressesExtraNewlineEmission() &&
ShouldEmitExtraNewlineForNode(node_);
// FIXME: We need to emit a '\n' as we leave an empty block(s) that
// contain a VisiblePosition when doing selection preservation.
if (text_state_.LastCharacter() != '\n') {
// insert a newline with a position following this block's contents.
SpliceBuffer(kNewlineCharacter, Strategy::Parent(*base_node), base_node,
1, 1);
// remember whether to later add a newline for the current node
DCHECK(!needs_another_newline_);
needs_another_newline_ = add_newline;
} else if (add_newline) {
// insert a newline with a position following this block's contents.
SpliceBuffer(kNewlineCharacter, Strategy::Parent(*base_node), base_node,
1, 1);
}
}
// If nothing was emitted, see if we need to emit a space.
if (!text_state_.PositionNode() && ShouldEmitSpaceBeforeAndAfterNode(*node_))
SpliceBuffer(kSpaceCharacter, Strategy::Parent(*base_node), base_node, 1,
1);
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::SpliceBuffer(UChar c,
const Node* text_node,
const Node* offset_base_node,
unsigned text_start_offset,
unsigned text_end_offset) {
text_state_.SpliceBuffer(c, text_node, offset_base_node, text_start_offset,
text_end_offset);
text_node_handler_.ResetCollapsedWhiteSpaceFixup();
}
template <typename Strategy>
EphemeralRangeTemplate<Strategy> TextIteratorAlgorithm<Strategy>::Range()
const {
// use the current run information, if we have it
if (text_state_.PositionNode()) {
return EphemeralRangeTemplate<Strategy>(StartPositionInCurrentContainer(),
EndPositionInCurrentContainer());
}
// otherwise, return the end of the overall range we were given
if (end_container_)
return EphemeralRangeTemplate<Strategy>(
PositionTemplate<Strategy>(end_container_, end_offset_));
return EphemeralRangeTemplate<Strategy>();
}
template <typename Strategy>
Document* TextIteratorAlgorithm<Strategy>::OwnerDocument() const {
if (text_state_.PositionNode())
return &text_state_.PositionNode()->GetDocument();
if (end_container_)
return &end_container_->GetDocument();
return nullptr;
}
template <typename Strategy>
const Node* TextIteratorAlgorithm<Strategy>::GetNode() const {
if (text_state_.PositionNode() || end_container_) {
const Node* node = CurrentContainer();
if (node->IsCharacterDataNode())
return node;
return Strategy::ChildAt(*node, StartOffsetInCurrentContainer());
}
return nullptr;
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::StartOffsetInCurrentContainer() const {
if (text_state_.PositionNode()) {
text_state_.FlushPositionOffsets();
return text_state_.PositionStartOffset();
}
DCHECK(end_container_);
return end_offset_;
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::EndOffsetInCurrentContainer() const {
if (text_state_.PositionNode()) {
text_state_.FlushPositionOffsets();
return text_state_.PositionEndOffset();
}
DCHECK(end_container_);
return end_offset_;
}
template <typename Strategy>
const Node* TextIteratorAlgorithm<Strategy>::CurrentContainer() const {
if (text_state_.PositionNode()) {
return text_state_.PositionNode();
}
DCHECK(end_container_);
return end_container_;
}
template <typename Strategy>
PositionTemplate<Strategy>
TextIteratorAlgorithm<Strategy>::StartPositionInCurrentContainer() const {
return PositionTemplate<Strategy>::EditingPositionOf(
CurrentContainer(), StartOffsetInCurrentContainer());
}
template <typename Strategy>
PositionTemplate<Strategy>
TextIteratorAlgorithm<Strategy>::EndPositionInCurrentContainer() const {
return PositionTemplate<Strategy>::EditingPositionOf(
CurrentContainer(), EndOffsetInCurrentContainer());
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::RangeLength(
const PositionTemplate<Strategy>& start,
const PositionTemplate<Strategy>& end,
const TextIteratorBehavior& behavior) {
DCHECK(start.GetDocument());
DocumentLifecycle::DisallowTransitionScope disallow_transition(
start.GetDocument()->Lifecycle());
int length = 0;
for (TextIteratorAlgorithm<Strategy> it(start, end, behavior); !it.AtEnd();
it.Advance())
length += it.length();
return length;
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::RangeLength(
const EphemeralRangeTemplate<Strategy>& range,
const TextIteratorBehavior& behavior) {
return RangeLength(range.StartPosition(), range.EndPosition(), behavior);
}
template <typename Strategy>
bool TextIteratorAlgorithm<Strategy>::IsBetweenSurrogatePair(
unsigned position) const {
return position > 0 && position < static_cast<unsigned>(length()) &&
U16_IS_LEAD(CharacterAt(position - 1)) &&
U16_IS_TRAIL(CharacterAt(position));
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::CopyTextTo(ForwardsTextBuffer* output,
int position,
int min_length) const {
unsigned end = std::min(length(), position + min_length);
if (IsBetweenSurrogatePair(end))
++end;
unsigned copied_length = end - position;
CopyCodeUnitsTo(output, position, copied_length);
return copied_length;
}
template <typename Strategy>
int TextIteratorAlgorithm<Strategy>::CopyTextTo(ForwardsTextBuffer* output,
int position) const {
return CopyTextTo(output, position, length() - position);
}
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::CopyCodeUnitsTo(
ForwardsTextBuffer* output,
unsigned position,
unsigned copy_length) const {
text_state_.AppendTextTo(output, position, copy_length);
}
// --------
template <typename Strategy>
static String CreatePlainText(const EphemeralRangeTemplate<Strategy>& range,
const TextIteratorBehavior& behavior) {
if (range.IsNull())
return g_empty_string;
DocumentLifecycle::DisallowTransitionScope disallow_transition(
range.StartPosition().GetDocument()->Lifecycle());
TextIteratorAlgorithm<Strategy> it(range.StartPosition(), range.EndPosition(),
behavior);
if (it.AtEnd())
return g_empty_string;
// The initial buffer size can be critical for performance:
// https://bugs.webkit.org/show_bug.cgi?id=81192
static const unsigned kInitialCapacity = 1 << 15;
StringBuilder builder;
builder.ReserveCapacity(kInitialCapacity);
for (; !it.AtEnd(); it.Advance())
it.GetText().AppendTextToStringBuilder(builder);
if (builder.IsEmpty())
return g_empty_string;
return builder.ToString();
}
String PlainText(const EphemeralRange& range,
const TextIteratorBehavior& behavior) {
return CreatePlainText<EditingStrategy>(range, behavior);
}
String PlainText(const EphemeralRangeInFlatTree& range,
const TextIteratorBehavior& behavior) {
return CreatePlainText<EditingInFlatTreeStrategy>(range, behavior);
}
template class CORE_TEMPLATE_EXPORT TextIteratorAlgorithm<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
TextIteratorAlgorithm<EditingInFlatTreeStrategy>;
} // namespace blink