| /* |
| * 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 "bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "core/HTMLNames.h" |
| #include "core/InputTypeNames.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/FirstLetterPseudoElement.h" |
| #include "core/dom/shadow/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/editing/iterators/CharacterIterator.h" |
| #include "core/editing/iterators/WordAwareIterator.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/HTMLElement.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLTextFormControlElement.h" |
| #include "core/layout/LayoutTableCell.h" |
| #include "core/layout/LayoutTableRow.h" |
| #include "core/layout/LayoutTextFragment.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "platform/fonts/Font.h" |
| #include "wtf/text/CString.h" |
| #include "wtf/text/StringBuilder.h" |
| #include <algorithm> |
| #include <unicode/utf16.h> |
| |
| using namespace WTF::Unicode; |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| namespace { |
| |
| template <typename Strategy> |
| TextIteratorBehaviorFlags adjustBehaviorFlags(TextIteratorBehaviorFlags); |
| |
| template <> |
| TextIteratorBehaviorFlags adjustBehaviorFlags<EditingStrategy>(TextIteratorBehaviorFlags flags) |
| { |
| if (flags & TextIteratorForSelectionToString) |
| return flags | TextIteratorExcludeAutofilledValue; |
| return flags; |
| } |
| |
| template <> |
| TextIteratorBehaviorFlags adjustBehaviorFlags<EditingInFlatTreeStrategy>(TextIteratorBehaviorFlags flags) |
| { |
| if (flags & TextIteratorForSelectionToString) |
| flags |= TextIteratorExcludeAutofilledValue; |
| return flags & ~(TextIteratorEntersOpenShadowRoots | TextIteratorEntersTextControls); |
| } |
| |
| // 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.layoutObject() || (node.isShadowRoot() && node.shadowHost()->layoutObject()); |
| } |
| |
| // 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> |
| Node* pastLastNode(const Node& rangeEndContainer, int rangeEndOffset) |
| { |
| if (rangeEndOffset >= 0 && !rangeEndContainer.isCharacterDataNode() && notSkipping(rangeEndContainer)) { |
| for (Node* next = Strategy::childAt(rangeEndContainer, rangeEndOffset); next; next = Strategy::nextSibling(*next)) { |
| if (notSkipping(*next)) |
| return next; |
| } |
| } |
| for (const Node* node = &rangeEndContainer; 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> |
| int shadowDepthOf(const Node& startContainer, const Node& endContainer); |
| |
| template <> |
| int shadowDepthOf<EditingStrategy>(const Node& startContainer, const Node& endContainer) |
| { |
| const TreeScope* commonAncestorTreeScope = startContainer.treeScope().commonAncestorTreeScope(endContainer.treeScope()); |
| DCHECK(commonAncestorTreeScope); |
| int shadowDepth = 0; |
| for (const TreeScope* treeScope = &startContainer.treeScope(); treeScope != commonAncestorTreeScope; treeScope = treeScope->parentTreeScope()) |
| ++shadowDepth; |
| return shadowDepth; |
| } |
| |
| template <> |
| int shadowDepthOf<EditingInFlatTreeStrategy>(const Node& startContainer, const Node& endContainer) |
| { |
| return 0; |
| } |
| |
| } // namespace |
| |
| template<typename Strategy> |
| TextIteratorAlgorithm<Strategy>::TextIteratorAlgorithm(const PositionTemplate<Strategy>& start, const PositionTemplate<Strategy>& end, TextIteratorBehaviorFlags behavior) |
| : m_offset(0) |
| , m_startContainer(nullptr) |
| , m_startOffset(0) |
| , m_endContainer(nullptr) |
| , m_endOffset(0) |
| , m_needsAnotherNewline(false) |
| , m_textBox(nullptr) |
| , m_remainingTextBox(nullptr) |
| , m_firstLetterText(nullptr) |
| , m_lastTextNode(nullptr) |
| , m_lastTextNodeEndedWithCollapsedSpace(false) |
| , m_sortedTextBoxesPosition(0) |
| , m_behavior(adjustBehaviorFlags<Strategy>(behavior)) |
| , m_handledFirstLetter(false) |
| , m_shouldStop(false) |
| , m_handleShadowRoot(false) |
| // The call to emitsOriginalText() must occur after m_behavior is initialized. |
| , m_textState(emitsOriginalText()) |
| { |
| DCHECK(start.isNotNull()); |
| DCHECK(end.isNotNull()); |
| |
| |
| // 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.document()->view() || !start.document()->view()->needsLayout()); |
| DCHECK(!start.document()->needsLayoutTreeUpdate()); |
| |
| if (start.compareTo(end) > 0) { |
| initialize(end.computeContainerNode(), end.computeOffsetInContainerNode(), start.computeContainerNode(), start.computeOffsetInContainerNode()); |
| return; |
| } |
| initialize(start.computeContainerNode(), start.computeOffsetInContainerNode(), end.computeContainerNode(), end.computeOffsetInContainerNode()); |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::initialize(Node* startContainer, int startOffset, Node* endContainer, int endOffset) |
| { |
| DCHECK(startContainer); |
| DCHECK(endContainer); |
| |
| // Remember the range - this does not change. |
| m_startContainer = startContainer; |
| m_startOffset = startOffset; |
| m_endContainer = endContainer; |
| m_endOffset = endOffset; |
| m_endNode = endContainer && !endContainer->isCharacterDataNode() && endOffset > 0 ? Strategy::childAt(*endContainer, endOffset - 1) : nullptr; |
| |
| m_shadowDepth = shadowDepthOf<Strategy>(*startContainer, *endContainer); |
| |
| // Set up the current node for processing. |
| if (startContainer->isCharacterDataNode()) |
| m_node = startContainer; |
| else if (Node* child = Strategy::childAt(*startContainer, startOffset)) |
| m_node = child; |
| else if (!startOffset) |
| m_node = startContainer; |
| else |
| m_node = Strategy::nextSkippingChildren(*startContainer); |
| |
| if (!m_node) |
| return; |
| |
| m_fullyClippedStack.setUpFullyClippedStack(m_node); |
| m_offset = m_node == m_startContainer ? m_startOffset : 0; |
| m_iterationProgress = HandledNone; |
| |
| // Calculate first out of bounds node. |
| m_pastEndNode = endContainer? pastLastNode<Strategy>(*endContainer, endOffset) : nullptr; |
| |
| // Identify the first run. |
| advance(); |
| } |
| |
| template<typename Strategy> |
| TextIteratorAlgorithm<Strategy>::~TextIteratorAlgorithm() |
| { |
| if (!m_handleShadowRoot) |
| return; |
| Document* document = ownerDocument(); |
| if (!document) |
| return; |
| if (m_behavior & TextIteratorForInnerText) |
| UseCounter::count(document, UseCounter::InnerTextWithShadowTree); |
| if (m_behavior & TextIteratorForSelectionToString) |
| UseCounter::count(document, UseCounter::SelectionToStringWithShadowTree); |
| if (m_behavior & TextIteratorForWindowFind) |
| UseCounter::count(document, UseCounter::WindowFindWithShadowTree); |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::isInsideAtomicInlineElement() const |
| { |
| if (atEnd() || length() != 1 || !m_node) |
| return false; |
| |
| LayoutObject* layoutObject = m_node->layoutObject(); |
| return layoutObject && layoutObject->isAtomicInlineLevel(); |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::advance() |
| { |
| if (m_shouldStop) |
| return; |
| |
| if (m_node) |
| DCHECK(!m_node->document().needsLayoutTreeUpdate()) << m_node; |
| |
| m_textState.resetRunInformation(); |
| |
| // handle remembered node that needed a newline after the text node's newline |
| if (m_needsAnotherNewline) { |
| // 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* lastChild = Strategy::lastChild(*m_node); |
| Node* baseNode = lastChild ? lastChild : m_node.get(); |
| spliceBuffer('\n', Strategy::parent(*baseNode), baseNode, 1, 1); |
| m_needsAnotherNewline = false; |
| return; |
| } |
| |
| if (!m_textBox && m_remainingTextBox) { |
| m_textBox = m_remainingTextBox; |
| m_remainingTextBox = 0; |
| m_firstLetterText = nullptr; |
| m_offset = 0; |
| } |
| // handle remembered text box |
| if (m_textBox) { |
| handleTextBox(); |
| if (m_textState.positionNode()) |
| return; |
| } |
| |
| while (m_node && (m_node != m_pastEndNode || m_shadowDepth > 0)) { |
| if (!m_shouldStop && stopsOnFormControls() && HTMLFormControlElement::enclosingFormControlElement(m_node)) |
| m_shouldStop = 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 (m_node == m_endContainer && !m_endOffset) { |
| representNodeOffsetZero(); |
| m_node = nullptr; |
| return; |
| } |
| |
| LayoutObject* layoutObject = m_node->layoutObject(); |
| if (!layoutObject) { |
| if (m_node->isShadowRoot()) { |
| // A shadow root doesn't have a layoutObject, but we want to visit children anyway. |
| m_iterationProgress = m_iterationProgress < HandledNode ? HandledNode : m_iterationProgress; |
| m_handleShadowRoot = true; |
| } else { |
| m_iterationProgress = HandledChildren; |
| } |
| } else { |
| // Enter author shadow roots, from youngest, if any and if necessary. |
| if (m_iterationProgress < HandledOpenShadowRoots) { |
| if (entersOpenShadowRoots() && m_node->isElementNode() && toElement(m_node)->openShadowRoot()) { |
| ShadowRoot* youngestShadowRoot = toElement(m_node)->openShadowRoot(); |
| DCHECK(youngestShadowRoot->type() == ShadowRootType::V0 || youngestShadowRoot->type() == ShadowRootType::Open); |
| m_node = youngestShadowRoot; |
| m_iterationProgress = HandledNone; |
| ++m_shadowDepth; |
| m_fullyClippedStack.pushFullyClippedState(m_node); |
| continue; |
| } |
| |
| m_iterationProgress = HandledOpenShadowRoots; |
| } |
| |
| // Enter user-agent shadow root, if necessary. |
| if (m_iterationProgress < HandledUserAgentShadowRoot) { |
| if (entersTextControls() && layoutObject->isTextControl()) { |
| ShadowRoot* userAgentShadowRoot = toElement(m_node)->userAgentShadowRoot(); |
| DCHECK(userAgentShadowRoot->type() == ShadowRootType::UserAgent); |
| m_node = userAgentShadowRoot; |
| m_iterationProgress = HandledNone; |
| ++m_shadowDepth; |
| m_fullyClippedStack.pushFullyClippedState(m_node); |
| continue; |
| } |
| m_iterationProgress = HandledUserAgentShadowRoot; |
| } |
| |
| // Handle the current node according to its type. |
| if (m_iterationProgress < HandledNode) { |
| bool handledNode = false; |
| if (layoutObject->isText() && m_node->getNodeType() == Node::kTextNode) { // FIXME: What about kCdataSectionNode? |
| if (!m_fullyClippedStack.top() || ignoresStyleVisibility()) |
| handledNode = handleTextNode(); |
| } else if (layoutObject && (layoutObject->isImage() || layoutObject->isLayoutPart() |
| || (m_node && m_node->isHTMLElement() |
| && (isHTMLFormControlElement(toHTMLElement(*m_node)) |
| || isHTMLLegendElement(toHTMLElement(*m_node)) |
| || isHTMLImageElement(toHTMLElement(*m_node)) |
| || isHTMLMeterElement(toHTMLElement(*m_node)) |
| || isHTMLProgressElement(toHTMLElement(*m_node)))))) { |
| handledNode = handleReplacedElement(); |
| } else { |
| handledNode = handleNonTextNode(); |
| } |
| if (handledNode) |
| m_iterationProgress = HandledNode; |
| if (m_textState.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 = m_iterationProgress < HandledChildren && !isHTMLImageElement(*m_node) ? Strategy::firstChild(*m_node) : nullptr; |
| m_offset = 0; |
| if (!next) { |
| // 2. If we've already iterated children or they are not available, go to the next sibling node. |
| next = Strategy::nextSibling(*m_node); |
| if (!next) { |
| // 3. If we are at the last child, go up the node tree until we find a next sibling. |
| ContainerNode* parentNode = Strategy::parent(*m_node); |
| while (!next && parentNode) { |
| if (m_node == m_endNode || Strategy::isDescendantOf(*m_endContainer, *parentNode)) |
| return; |
| bool haveLayoutObject = m_node->layoutObject(); |
| m_node = parentNode; |
| m_fullyClippedStack.pop(); |
| parentNode = Strategy::parent(*m_node); |
| if (haveLayoutObject) |
| exitNode(); |
| if (m_textState.positionNode()) { |
| m_iterationProgress = HandledChildren; |
| return; |
| } |
| next = Strategy::nextSibling(*m_node); |
| } |
| |
| if (!next && !parentNode && m_shadowDepth > 0) { |
| // 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 (!m_node->isShadowRoot()) { |
| NOTREACHED(); |
| m_shouldStop = true; |
| return; |
| } |
| ShadowRoot* shadowRoot = toShadowRoot(m_node); |
| if (shadowRoot->type() == ShadowRootType::V0 || shadowRoot->type() == ShadowRootType::Open) { |
| ShadowRoot* nextShadowRoot = shadowRoot->olderShadowRoot(); |
| if (nextShadowRoot && nextShadowRoot->type() == ShadowRootType::V0) { |
| m_fullyClippedStack.pop(); |
| m_node = nextShadowRoot; |
| m_iterationProgress = HandledNone; |
| // m_shadowDepth is unchanged since we exit from a shadow root and enter another. |
| m_fullyClippedStack.pushFullyClippedState(m_node); |
| } else { |
| // We are the last shadow root; exit from here and go back to where we were. |
| m_node = &shadowRoot->host(); |
| m_iterationProgress = HandledOpenShadowRoots; |
| --m_shadowDepth; |
| m_fullyClippedStack.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(shadowRoot->type() == ShadowRootType::Closed || shadowRoot->type() == ShadowRootType::UserAgent); |
| m_node = &shadowRoot->host(); |
| m_iterationProgress = HandledUserAgentShadowRoot; |
| --m_shadowDepth; |
| m_fullyClippedStack.pop(); |
| } |
| m_handledFirstLetter = false; |
| m_firstLetterText = nullptr; |
| continue; |
| } |
| } |
| m_fullyClippedStack.pop(); |
| } |
| |
| // set the new current node |
| m_node = next; |
| if (m_node) |
| m_fullyClippedStack.pushFullyClippedState(m_node); |
| m_iterationProgress = HandledNone; |
| m_handledFirstLetter = false; |
| m_firstLetterText = nullptr; |
| |
| // how would this ever be? |
| if (m_textState.positionNode()) |
| return; |
| } |
| } |
| |
| static bool hasVisibleTextNode(LayoutText* layoutObject) |
| { |
| if (layoutObject->style()->visibility() == EVisibility::Visible) |
| return true; |
| |
| if (!layoutObject->isTextFragment()) |
| return false; |
| |
| LayoutTextFragment* fragment = toLayoutTextFragment(layoutObject); |
| if (!fragment->isRemainingTextLayoutObject()) |
| return false; |
| |
| DCHECK(fragment->firstLetterPseudoElement()); |
| LayoutObject* pseudoElementLayoutObject = fragment->firstLetterPseudoElement()->layoutObject(); |
| return pseudoElementLayoutObject && pseudoElementLayoutObject->style()->visibility() == EVisibility::Visible; |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::handleTextNode() |
| { |
| if (excludesAutofilledValue()) { |
| HTMLTextFormControlElement* control = enclosingTextFormControl(m_node); |
| // For security reason, we don't expose suggested value if it is |
| // auto-filled. |
| if (control && control->isAutofilled()) |
| return true; |
| } |
| |
| Text* textNode = toText(m_node); |
| LayoutText* layoutObject = textNode->layoutObject(); |
| |
| m_lastTextNode = textNode; |
| String str = layoutObject->text(); |
| |
| // handle pre-formatted text |
| if (!layoutObject->style()->collapseWhiteSpace()) { |
| int runStart = m_offset; |
| if (m_lastTextNodeEndedWithCollapsedSpace && hasVisibleTextNode(layoutObject)) { |
| if (m_behavior & TextIteratorCollapseTrailingSpace) { |
| if (runStart > 0 && str[runStart - 1] == ' ') { |
| spliceBuffer(spaceCharacter, textNode, 0, runStart, runStart); |
| return false; |
| } |
| } else { |
| spliceBuffer(spaceCharacter, textNode, 0, runStart, runStart); |
| return false; |
| } |
| } |
| if (!m_handledFirstLetter && layoutObject->isTextFragment() && !m_offset) { |
| handleTextNodeFirstLetter(toLayoutTextFragment(layoutObject)); |
| if (m_firstLetterText) { |
| String firstLetter = m_firstLetterText->text(); |
| emitText(textNode, m_firstLetterText, m_offset, m_offset + firstLetter.length()); |
| m_firstLetterText = nullptr; |
| m_textBox = 0; |
| return false; |
| } |
| } |
| if (layoutObject->style()->visibility() != EVisibility::Visible && !ignoresStyleVisibility()) |
| return false; |
| int strLength = str.length(); |
| int end = (textNode == m_endContainer) ? m_endOffset : INT_MAX; |
| int runEnd = std::min(strLength, end); |
| |
| if (runStart >= runEnd) |
| return true; |
| |
| emitText(textNode, textNode->layoutObject(), runStart, runEnd); |
| return true; |
| } |
| |
| if (layoutObject->firstTextBox()) |
| m_textBox = layoutObject->firstTextBox(); |
| |
| bool shouldHandleFirstLetter = !m_handledFirstLetter && layoutObject->isTextFragment() && !m_offset; |
| if (shouldHandleFirstLetter) |
| handleTextNodeFirstLetter(toLayoutTextFragment(layoutObject)); |
| |
| if (!layoutObject->firstTextBox() && str.length() > 0 && !shouldHandleFirstLetter) { |
| if (layoutObject->style()->visibility() != EVisibility::Visible && !ignoresStyleVisibility()) |
| return false; |
| m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space |
| return true; |
| } |
| |
| if (m_firstLetterText) |
| layoutObject = m_firstLetterText; |
| |
| // Used when text boxes are out of order (Hebrew/Arabic w/ embeded LTR text) |
| if (layoutObject->containsReversedText()) { |
| m_sortedTextBoxes.clear(); |
| for (InlineTextBox* textBox = layoutObject->firstTextBox(); textBox; textBox = textBox->nextTextBox()) { |
| m_sortedTextBoxes.append(textBox); |
| } |
| std::sort(m_sortedTextBoxes.begin(), m_sortedTextBoxes.end(), InlineTextBox::compareByStart); |
| m_sortedTextBoxesPosition = 0; |
| m_textBox = m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]; |
| } |
| |
| handleTextBox(); |
| return true; |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::handleTextBox() |
| { |
| LayoutText* layoutObject = m_firstLetterText ? m_firstLetterText : toLayoutText(m_node->layoutObject()); |
| |
| if (layoutObject->style()->visibility() != EVisibility::Visible && !ignoresStyleVisibility()) { |
| m_textBox = nullptr; |
| } else { |
| String str = layoutObject->text(); |
| unsigned start = m_offset; |
| unsigned end = (m_node == m_endContainer) ? static_cast<unsigned>(m_endOffset) : INT_MAX; |
| while (m_textBox) { |
| unsigned textBoxStart = m_textBox->start(); |
| unsigned runStart = std::max(textBoxStart, start); |
| |
| // Check for collapsed space at the start of this run. |
| InlineTextBox* firstTextBox = layoutObject->containsReversedText() ? (m_sortedTextBoxes.isEmpty() ? 0 : m_sortedTextBoxes[0]) : layoutObject->firstTextBox(); |
| bool needSpace = m_lastTextNodeEndedWithCollapsedSpace |
| || (m_textBox == firstTextBox && textBoxStart == runStart && runStart > 0); |
| if (needSpace && !layoutObject->style()->isCollapsibleWhiteSpace(m_textState.lastCharacter()) && m_textState.lastCharacter()) { |
| if (m_lastTextNode == m_node && runStart > 0 && str[runStart - 1] == ' ') { |
| unsigned spaceRunStart = runStart - 1; |
| while (spaceRunStart > 0 && str[spaceRunStart - 1] == ' ') |
| --spaceRunStart; |
| emitText(m_node, layoutObject, spaceRunStart, spaceRunStart + 1); |
| } else { |
| spliceBuffer(spaceCharacter, m_node, 0, runStart, runStart); |
| } |
| return; |
| } |
| unsigned textBoxEnd = textBoxStart + m_textBox->len(); |
| unsigned runEnd = std::min(textBoxEnd, end); |
| |
| // Determine what the next text box will be, but don't advance yet |
| InlineTextBox* nextTextBox = nullptr; |
| if (layoutObject->containsReversedText()) { |
| if (m_sortedTextBoxesPosition + 1 < m_sortedTextBoxes.size()) |
| nextTextBox = m_sortedTextBoxes[m_sortedTextBoxesPosition + 1]; |
| } else { |
| nextTextBox = m_textBox->nextTextBox(); |
| } |
| |
| // FIXME: Based on the outcome of crbug.com/446502 it's possible we can |
| // remove this block. The reason we new it now is because BIDI and |
| // FirstLetter seem to have different ideas of where things can split. |
| // FirstLetter takes the punctuation + first letter, and BIDI will |
| // split out the punctuation and possibly reorder it. |
| if (nextTextBox && !(nextTextBox->getLineLayoutItem().isEqual(layoutObject))) { |
| m_textBox = 0; |
| return; |
| } |
| DCHECK(!nextTextBox || nextTextBox->getLineLayoutItem().isEqual(layoutObject)); |
| |
| if (runStart < runEnd) { |
| // Handle either a single newline character (which becomes a space), |
| // or a run of characters that does not include a newline. |
| // This effectively translates newlines to spaces without copying the text. |
| if (str[runStart] == '\n') { |
| // We need to preserve new lines in case of PRE_LINE. |
| // See bug crbug.com/317365. |
| if (layoutObject->style()->whiteSpace() == PRE_LINE) |
| spliceBuffer('\n', m_node, 0, runStart, runStart); |
| else |
| spliceBuffer(spaceCharacter, m_node, 0, runStart, runStart + 1); |
| m_offset = runStart + 1; |
| } else { |
| size_t subrunEnd = str.find('\n', runStart); |
| if (subrunEnd == kNotFound || subrunEnd > runEnd) { |
| subrunEnd = runEnd; |
| // Restore the collapsed trailing space for copy & paste. |
| // See http://crbug.com/318925 |
| if (!nextTextBox && m_textBox->root().nextRootBox() && m_textBox->root().lastChild() == m_textBox) { |
| if (str.endsWith(' ') && subrunEnd == str.length() - 1 && str[subrunEnd - 1] != ' ') |
| ++subrunEnd; |
| } |
| } |
| |
| m_offset = subrunEnd; |
| emitText(m_node, layoutObject, runStart, subrunEnd); |
| } |
| |
| // If we are doing a subrun that doesn't go to the end of the text box, |
| // come back again to finish handling this text box; don't advance to the next one. |
| if (static_cast<unsigned>(m_textState.positionEndOffset()) < textBoxEnd) |
| return; |
| |
| // Advance and return |
| unsigned nextRunStart = nextTextBox ? nextTextBox->start() : str.length(); |
| if (nextRunStart > runEnd) |
| m_lastTextNodeEndedWithCollapsedSpace = true; // collapsed space between runs or at the end |
| |
| m_textBox = nextTextBox; |
| if (layoutObject->containsReversedText()) |
| ++m_sortedTextBoxesPosition; |
| return; |
| } |
| // Advance and continue |
| m_textBox = nextTextBox; |
| if (layoutObject->containsReversedText()) |
| ++m_sortedTextBoxesPosition; |
| } |
| } |
| |
| if (!m_textBox && m_remainingTextBox) { |
| m_textBox = m_remainingTextBox; |
| m_remainingTextBox = 0; |
| m_firstLetterText = nullptr; |
| m_offset = 0; |
| handleTextBox(); |
| } |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::handleTextNodeFirstLetter(LayoutTextFragment* layoutObject) |
| { |
| m_handledFirstLetter = true; |
| |
| if (!layoutObject->isRemainingTextLayoutObject()) |
| return; |
| |
| FirstLetterPseudoElement* firstLetterElement = layoutObject->firstLetterPseudoElement(); |
| if (!firstLetterElement) |
| return; |
| |
| LayoutObject* pseudoLayoutObject = firstLetterElement->layoutObject(); |
| if (pseudoLayoutObject->style()->visibility() != EVisibility::Visible && !ignoresStyleVisibility()) |
| return; |
| |
| LayoutObject* firstLetter = pseudoLayoutObject->slowFirstChild(); |
| DCHECK(firstLetter); |
| |
| m_remainingTextBox = m_textBox; |
| m_textBox = toLayoutText(firstLetter)->firstTextBox(); |
| m_sortedTextBoxes.clear(); |
| m_firstLetterText = toLayoutText(firstLetter); |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::supportsAltText(Node* node) |
| { |
| if (!node->isHTMLElement()) |
| return false; |
| HTMLElement& element = toHTMLElement(*node); |
| |
| // FIXME: Add isSVGImageElement. |
| if (isHTMLImageElement(element)) |
| return true; |
| if (isHTMLInputElement(toHTMLElement(*node)) && toHTMLInputElement(*node).type() == InputTypeNames::image) |
| return true; |
| return false; |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::handleReplacedElement() |
| { |
| if (m_fullyClippedStack.top()) |
| return false; |
| |
| LayoutObject* layoutObject = m_node->layoutObject(); |
| if (layoutObject->style()->visibility() != EVisibility::Visible && !ignoresStyleVisibility()) |
| return false; |
| |
| if (emitsObjectReplacementCharacter()) { |
| spliceBuffer(objectReplacementCharacter, Strategy::parent(*m_node), m_node, 0, 1); |
| return true; |
| } |
| |
| if (m_behavior & TextIteratorCollapseTrailingSpace) { |
| if (m_lastTextNode) { |
| String str = m_lastTextNode->layoutObject()->text(); |
| if (m_lastTextNodeEndedWithCollapsedSpace && m_offset > 0 && str[m_offset - 1] == ' ') { |
| spliceBuffer(spaceCharacter, Strategy::parent(*m_lastTextNode), m_lastTextNode, 1, 1); |
| return false; |
| } |
| } |
| } else if (m_lastTextNodeEndedWithCollapsedSpace) { |
| spliceBuffer(spaceCharacter, Strategy::parent(*m_lastTextNode), m_lastTextNode, 1, 1); |
| return false; |
| } |
| |
| if (entersTextControls() && layoutObject->isTextControl()) { |
| // The shadow tree should be already visited. |
| return true; |
| } |
| |
| 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(*m_node), m_node, 0, 1); |
| return true; |
| } |
| |
| m_textState.updateForReplacedElement(m_node); |
| |
| if (emitsImageAltText() && TextIterator::supportsAltText(m_node)) { |
| m_textState.emitAltText(m_node); |
| if (m_textState.length()) |
| return true; |
| } |
| |
| return true; |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::shouldEmitTabBeforeNode(Node* node) |
| { |
| LayoutObject* r = node->layoutObject(); |
| |
| // 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->cellBefore(rc) || t->cellAbove(rc)); |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::shouldEmitNewlineForNode(Node* node, bool emitsOriginalText) |
| { |
| LayoutObject* layoutObject = node->layoutObject(); |
| |
| if (layoutObject ? !layoutObject->isBR() : !isHTMLBRElement(node)) |
| return false; |
| return emitsOriginalText || !(node->isInShadowTree() && isHTMLInputElement(*node->shadowHost())); |
| } |
| |
| static bool shouldEmitNewlinesBeforeAndAfterNode(Node& node) |
| { |
| // Block flow (versus inline flow) is represented by having |
| // a newline both before and after the element. |
| LayoutObject* r = node.layoutObject(); |
| 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(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. |
| Node* next = &node; |
| do { |
| next = Strategy::nextSkippingChildren(*next); |
| if (next && next->layoutObject()) |
| return true; |
| } while (next); |
| return false; |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::shouldEmitNewlineBeforeNode(Node& node) |
| { |
| return shouldEmitNewlinesBeforeAndAfterNode(node); |
| } |
| |
| static bool shouldEmitExtraNewlineForNode(Node* node) |
| { |
| // When there is a significant collapsed bottom margin, emit an extra |
| // newline for a more realistic result. We end up getting the right |
| // result even without margin collapsing. For example: <div><p>text</p></div> |
| // will work right even if both the <div> and the <p> have bottom margins. |
| LayoutObject* r = node->layoutObject(); |
| if (!r || !r->isBox()) |
| return false; |
| |
| // NOTE: We only do this for a select set of nodes, and fwiw WinIE appears |
| // not to do this at all |
| if (node->hasTagName(h1Tag) |
| || node->hasTagName(h2Tag) |
| || node->hasTagName(h3Tag) |
| || node->hasTagName(h4Tag) |
| || node->hasTagName(h5Tag) |
| || node->hasTagName(h6Tag) |
| || node->hasTagName(pTag)) { |
| const ComputedStyle* style = r->style(); |
| if (style) { |
| int bottomMargin = toLayoutBox(r)->collapsedMarginAfter().toInt(); |
| int fontSize = style->getFontDescription().computedPixelSize(); |
| if (bottomMargin * 2 >= fontSize) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // 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() && isDisplayInsideTable(m_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 (m_textState.lastCharacter() == '\n') |
| return false; |
| |
| // Otherwise, show the position if we have emitted any characters |
| if (m_textState.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 (m_node == m_startContainer) |
| 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(*m_node, *m_startContainer)) |
| 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 (!m_startOffset) |
| 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 (!m_node->layoutObject() || m_node->layoutObject()->style()->visibility() != EVisibility::Visible |
| || (m_node->layoutObject()->isLayoutBlockFlow() && !toLayoutBlock(m_node->layoutObject())->size().height() && !isHTMLBodyElement(*m_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 startPos = createVisiblePosition(Position(m_startContainer, m_startOffset)); |
| VisiblePosition currPos = VisiblePosition::beforeNode(m_node); |
| return startPos.isNotNull() && currPos.isNotNull() && !inSameLine(startPos, currPos); |
| } |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::shouldEmitSpaceBeforeAndAfterNode(Node* node) |
| { |
| return isDisplayInsideTable(node) && (node->layoutObject()->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(m_node)) { |
| if (shouldRepresentNodeOffsetZero()) |
| spliceBuffer('\t', Strategy::parent(*m_node), m_node, 0, 0); |
| } else if (shouldEmitNewlineBeforeNode(*m_node)) { |
| if (shouldRepresentNodeOffsetZero()) |
| spliceBuffer('\n', Strategy::parent(*m_node), m_node, 0, 0); |
| } else if (shouldEmitSpaceBeforeAndAfterNode(m_node)) { |
| if (shouldRepresentNodeOffsetZero()) |
| spliceBuffer(spaceCharacter, Strategy::parent(*m_node), m_node, 0, 0); |
| } |
| } |
| |
| |
| template<typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::handleNonTextNode() |
| { |
| if (shouldEmitNewlineForNode(m_node, emitsOriginalText())) |
| spliceBuffer('\n', Strategy::parent(*m_node), m_node, 0, 1); |
| else if (emitsCharactersBetweenAllVisiblePositions() && m_node->layoutObject() && m_node->layoutObject()->isHR()) |
| spliceBuffer(spaceCharacter, Strategy::parent(*m_node), m_node, 0, 1); |
| else |
| representNodeOffsetZero(); |
| |
| return true; |
| } |
| |
| 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 (!m_textState.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* lastChild = Strategy::lastChild(*m_node); |
| Node* baseNode = lastChild ? lastChild : m_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 (m_lastTextNode && shouldEmitNewlineAfterNode(*m_node)) { |
| // use extra newline to represent margin bottom, as needed |
| bool addNewline = shouldEmitExtraNewlineForNode(m_node); |
| |
| // FIXME: We need to emit a '\n' as we leave an empty block(s) that |
| // contain a VisiblePosition when doing selection preservation. |
| if (m_textState.lastCharacter() != '\n') { |
| // insert a newline with a position following this block's contents. |
| spliceBuffer(newlineCharacter, Strategy::parent(*baseNode), baseNode, 1, 1); |
| // remember whether to later add a newline for the current node |
| DCHECK(!m_needsAnotherNewline); |
| m_needsAnotherNewline = addNewline; |
| } else if (addNewline) { |
| // insert a newline with a position following this block's contents. |
| spliceBuffer(newlineCharacter, Strategy::parent(*baseNode), baseNode, 1, 1); |
| } |
| } |
| |
| // If nothing was emitted, see if we need to emit a space. |
| if (!m_textState.positionNode() && shouldEmitSpaceBeforeAndAfterNode(m_node)) |
| spliceBuffer(spaceCharacter, Strategy::parent(*baseNode), baseNode, 1, 1); |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::spliceBuffer(UChar c, Node* textNode, Node* offsetBaseNode, int textStartOffset, int textEndOffset) |
| { |
| // Since m_lastTextNodeEndedWithCollapsedSpace seems better placed in |
| // TextIterator, but is always reset when we call spliceBuffer, we |
| // wrap TextIteratorTextState::spliceBuffer() with this function. |
| m_textState.spliceBuffer(c, textNode, offsetBaseNode, textStartOffset, textEndOffset); |
| m_lastTextNodeEndedWithCollapsedSpace = false; |
| } |
| |
| template<typename Strategy> |
| void TextIteratorAlgorithm<Strategy>::emitText(Node* textNode, LayoutText* layoutObject, int textStartOffset, int textEndOffset) |
| { |
| // Since m_lastTextNodeEndedWithCollapsedSpace seems better placed in |
| // TextIterator, but is always reset when we call spliceBuffer, we |
| // wrap TextIteratorTextState::spliceBuffer() with this function. |
| m_textState.emitText(textNode, layoutObject, textStartOffset, textEndOffset); |
| m_lastTextNodeEndedWithCollapsedSpace = false; |
| } |
| |
| template<typename Strategy> |
| EphemeralRangeTemplate<Strategy> TextIteratorAlgorithm<Strategy>::range() const |
| { |
| // use the current run information, if we have it |
| if (m_textState.positionNode()) { |
| m_textState.flushPositionOffsets(); |
| return EphemeralRangeTemplate<Strategy>(PositionTemplate<Strategy>(m_textState.positionNode(), m_textState.positionStartOffset()), PositionTemplate<Strategy>(m_textState.positionNode(), m_textState.positionEndOffset())); |
| } |
| |
| // otherwise, return the end of the overall range we were given |
| if (m_endContainer) |
| return EphemeralRangeTemplate<Strategy>(PositionTemplate<Strategy>(m_endContainer, m_endOffset)); |
| |
| return EphemeralRangeTemplate<Strategy>(); |
| } |
| |
| template<typename Strategy> |
| Document* TextIteratorAlgorithm<Strategy>::ownerDocument() const |
| { |
| if (m_textState.positionNode()) |
| return &m_textState.positionNode()->document(); |
| if (m_endContainer) |
| return &m_endContainer->document(); |
| return 0; |
| } |
| |
| template<typename Strategy> |
| Node* TextIteratorAlgorithm<Strategy>::node() const |
| { |
| if (m_textState.positionNode() || m_endContainer) { |
| Node* node = currentContainer(); |
| if (node->isCharacterDataNode()) |
| return node; |
| return Strategy::childAt(*node, startOffsetInCurrentContainer()); |
| } |
| return 0; |
| } |
| |
| template<typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::startOffsetInCurrentContainer() const |
| { |
| if (m_textState.positionNode()) { |
| m_textState.flushPositionOffsets(); |
| return m_textState.positionStartOffset(); |
| } |
| DCHECK(m_endContainer); |
| return m_endOffset; |
| } |
| |
| template<typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::endOffsetInCurrentContainer() const |
| { |
| if (m_textState.positionNode()) { |
| m_textState.flushPositionOffsets(); |
| return m_textState.positionEndOffset(); |
| } |
| DCHECK(m_endContainer); |
| return m_endOffset; |
| } |
| |
| template<typename Strategy> |
| Node* TextIteratorAlgorithm<Strategy>::currentContainer() const |
| { |
| if (m_textState.positionNode()) { |
| return m_textState.positionNode(); |
| } |
| DCHECK(m_endContainer); |
| return m_endContainer; |
| } |
| |
| 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, bool forSelectionPreservation) |
| { |
| DCHECK(start.document()); |
| DocumentLifecycle::DisallowTransitionScope disallowTransition(start.document()->lifecycle()); |
| |
| int length = 0; |
| TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; |
| if (forSelectionPreservation) |
| behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; |
| for (TextIteratorAlgorithm<Strategy> it(start, end, behaviorFlags); !it.atEnd(); it.advance()) |
| length += it.length(); |
| |
| return length; |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::isInTextSecurityMode() const |
| { |
| return isTextSecurityNode(node()); |
| } |
| |
| template <typename Strategy> |
| bool TextIteratorAlgorithm<Strategy>::isBetweenSurrogatePair(int position) const |
| { |
| DCHECK_GE(position, 0); |
| return position > 0 && position < length() && U16_IS_LEAD(characterAt(position - 1)) && U16_IS_TRAIL(characterAt(position)); |
| } |
| |
| template <typename Strategy> |
| int TextIteratorAlgorithm<Strategy>::copyTextTo(ForwardsTextBuffer* output, int position, int minLength) const |
| { |
| int end = std::min(length(), position + minLength); |
| if (isBetweenSurrogatePair(end)) |
| ++end; |
| int copiedLength = end - position; |
| copyCodeUnitsTo(output, position, copiedLength); |
| return copiedLength; |
| } |
| |
| 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, int position, int copyLength) const |
| { |
| m_textState.appendTextTo(output, position, copyLength); |
| } |
| |
| // -------- |
| |
| template <typename Strategy> |
| static String createPlainText(const EphemeralRangeTemplate<Strategy>& range, TextIteratorBehaviorFlags behavior) |
| { |
| if (range.isNull()) |
| return emptyString(); |
| |
| |
| // TODO(dglazkov): The use of updateStyleAndLayoutIgnorePendingStylesheets needs to be audited. |
| // see http://crbug.com/590369 for more details. |
| range.startPosition().document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| TextIteratorAlgorithm<Strategy> it(range.startPosition(), range.endPosition(), behavior); |
| |
| if (it.atEnd()) |
| return emptyString(); |
| |
| // The initial buffer size can be critical for performance: https://bugs.webkit.org/show_bug.cgi?id=81192 |
| static const unsigned initialCapacity = 1 << 15; |
| |
| StringBuilder builder; |
| builder.reserveCapacity(initialCapacity); |
| |
| for (; !it.atEnd(); it.advance()) |
| it.text().appendTextToStringBuilder(builder); |
| |
| if (builder.isEmpty()) |
| return emptyString(); |
| |
| return builder.toString(); |
| } |
| |
| String plainText(const EphemeralRange& range, TextIteratorBehaviorFlags behavior) |
| { |
| return createPlainText<EditingStrategy>(range, behavior); |
| } |
| |
| String plainText(const EphemeralRangeInFlatTree& range, TextIteratorBehaviorFlags behavior) |
| { |
| // TODO(xiaochengh): Move this check and the DisallowTransitionScope to |
| // |createPlainText| after we have ensured that both versions of |plainText| |
| // are called with clean layout. |
| if (range.isNull()) |
| return emptyString(); |
| DocumentLifecycle::DisallowTransitionScope disallowTransition(range.startPosition().document()->lifecycle()); |
| return createPlainText<EditingInFlatTreeStrategy>(range, behavior); |
| } |
| |
| template class CORE_TEMPLATE_EXPORT TextIteratorAlgorithm<EditingStrategy>; |
| template class CORE_TEMPLATE_EXPORT TextIteratorAlgorithm<EditingInFlatTreeStrategy>; |
| |
| } // namespace blink |