| /* |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. |
| * |
| * 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 "config.h" |
| #include "core/editing/VisibleUnits.h" |
| |
| #include "HTMLNames.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "bindings/v8/ExceptionState.h" |
| #include "bindings/v8/ExceptionStatePlaceholder.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/Position.h" |
| #include "core/dom/Text.h" |
| #include "core/editing/RenderedPosition.h" |
| #include "core/editing/TextIterator.h" |
| #include "core/editing/VisiblePosition.h" |
| #include "core/editing/htmlediting.h" |
| #include "core/rendering/InlineTextBox.h" |
| #include "core/rendering/RenderBlock.h" |
| #include "core/rendering/RenderObject.h" |
| #include "platform/text/TextBoundaries.h" |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| using namespace WTF::Unicode; |
| |
| static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) |
| { |
| bool editable = node->rendererIsEditable(editableType); |
| node = node->previousLeafNode(); |
| while (node) { |
| if (editable == node->rendererIsEditable(editableType)) |
| return node; |
| node = node->previousLeafNode(); |
| } |
| return 0; |
| } |
| |
| static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) |
| { |
| if (!node) |
| return 0; |
| |
| bool editable = node->rendererIsEditable(editableType); |
| node = node->nextLeafNode(); |
| while (node) { |
| if (editable == node->rendererIsEditable(editableType)) |
| return node; |
| node = node->nextLeafNode(); |
| } |
| return 0; |
| } |
| |
| // FIXME: consolidate with code in previousLinePosition. |
| static Position previousRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
| { |
| Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
| Node* previousNode = previousLeafWithSameEditability(node, editableType); |
| |
| while (previousNode && (!previousNode->renderer() || inSameLine(firstPositionInOrBeforeNode(previousNode), visiblePosition))) |
| previousNode = previousLeafWithSameEditability(previousNode, editableType); |
| |
| while (previousNode && !previousNode->isShadowRoot()) { |
| if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), editableType) != highestRoot) |
| break; |
| |
| Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) : |
| createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode)); |
| |
| if (pos.isCandidate()) |
| return pos; |
| |
| previousNode = previousLeafWithSameEditability(previousNode, editableType); |
| } |
| return Position(); |
| } |
| |
| static Position nextRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
| { |
| Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
| Node* nextNode = nextLeafWithSameEditability(node, editableType); |
| while (nextNode && (!nextNode->renderer() || inSameLine(firstPositionInOrBeforeNode(nextNode), visiblePosition))) |
| nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); |
| |
| while (nextNode && !nextNode->isShadowRoot()) { |
| if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), editableType) != highestRoot) |
| break; |
| |
| Position pos; |
| pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode)); |
| |
| if (pos.isCandidate()) |
| return pos; |
| |
| nextNode = nextLeafWithSameEditability(nextNode, editableType); |
| } |
| return Position(); |
| } |
| |
| class CachedLogicallyOrderedLeafBoxes { |
| public: |
| CachedLogicallyOrderedLeafBoxes(); |
| |
| const InlineTextBox* previousTextBox(const RootInlineBox*, const InlineTextBox*); |
| const InlineTextBox* nextTextBox(const RootInlineBox*, const InlineTextBox*); |
| |
| size_t size() const { return m_leafBoxes.size(); } |
| const InlineBox* firstBox() const { return m_leafBoxes[0]; } |
| |
| private: |
| const Vector<InlineBox*>& collectBoxes(const RootInlineBox*); |
| int boxIndexInLeaves(const InlineTextBox*) const; |
| |
| const RootInlineBox* m_rootInlineBox; |
| Vector<InlineBox*> m_leafBoxes; |
| }; |
| |
| CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes() : m_rootInlineBox(0) { }; |
| |
| const InlineTextBox* CachedLogicallyOrderedLeafBoxes::previousTextBox(const RootInlineBox* root, const InlineTextBox* box) |
| { |
| if (!root) |
| return 0; |
| |
| collectBoxes(root); |
| |
| // If box is null, root is box's previous RootInlineBox, and previousBox is the last logical box in root. |
| int boxIndex = m_leafBoxes.size() - 1; |
| if (box) |
| boxIndex = boxIndexInLeaves(box) - 1; |
| |
| for (int i = boxIndex; i >= 0; --i) { |
| if (m_leafBoxes[i]->isInlineTextBox()) |
| return toInlineTextBox(m_leafBoxes[i]); |
| } |
| |
| return 0; |
| } |
| |
| const InlineTextBox* CachedLogicallyOrderedLeafBoxes::nextTextBox(const RootInlineBox* root, const InlineTextBox* box) |
| { |
| if (!root) |
| return 0; |
| |
| collectBoxes(root); |
| |
| // If box is null, root is box's next RootInlineBox, and nextBox is the first logical box in root. |
| // Otherwise, root is box's RootInlineBox, and nextBox is the next logical box in the same line. |
| size_t nextBoxIndex = 0; |
| if (box) |
| nextBoxIndex = boxIndexInLeaves(box) + 1; |
| |
| for (size_t i = nextBoxIndex; i < m_leafBoxes.size(); ++i) { |
| if (m_leafBoxes[i]->isInlineTextBox()) |
| return toInlineTextBox(m_leafBoxes[i]); |
| } |
| |
| return 0; |
| } |
| |
| const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::collectBoxes(const RootInlineBox* root) |
| { |
| if (m_rootInlineBox != root) { |
| m_rootInlineBox = root; |
| m_leafBoxes.clear(); |
| root->collectLeafBoxesInLogicalOrder(m_leafBoxes); |
| } |
| return m_leafBoxes; |
| } |
| |
| int CachedLogicallyOrderedLeafBoxes::boxIndexInLeaves(const InlineTextBox* box) const |
| { |
| for (size_t i = 0; i < m_leafBoxes.size(); ++i) { |
| if (box == m_leafBoxes[i]) |
| return i; |
| } |
| return 0; |
| } |
| |
| static const InlineTextBox* logicallyPreviousBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
| bool& previousBoxInDifferentBlock, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
| { |
| const InlineBox* startBox = textBox; |
| |
| const InlineTextBox* previousBox = leafBoxes.previousTextBox(startBox->root(), textBox); |
| if (previousBox) |
| return previousBox; |
| |
| previousBox = leafBoxes.previousTextBox(startBox->root()->prevRootBox(), 0); |
| if (previousBox) |
| return previousBox; |
| |
| while (1) { |
| Node* startNode = startBox->renderer() ? startBox->renderer()->nonPseudoNode() : 0; |
| if (!startNode) |
| break; |
| |
| Position position = previousRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
| if (position.isNull()) |
| break; |
| |
| RenderedPosition renderedPosition(position, DOWNSTREAM); |
| RootInlineBox* previousRoot = renderedPosition.rootBox(); |
| if (!previousRoot) |
| break; |
| |
| previousBox = leafBoxes.previousTextBox(previousRoot, 0); |
| if (previousBox) { |
| previousBoxInDifferentBlock = true; |
| return previousBox; |
| } |
| |
| if (!leafBoxes.size()) |
| break; |
| startBox = leafBoxes.firstBox(); |
| } |
| return 0; |
| } |
| |
| |
| static const InlineTextBox* logicallyNextBox(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
| bool& nextBoxInDifferentBlock, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
| { |
| const InlineBox* startBox = textBox; |
| |
| const InlineTextBox* nextBox = leafBoxes.nextTextBox(startBox->root(), textBox); |
| if (nextBox) |
| return nextBox; |
| |
| nextBox = leafBoxes.nextTextBox(startBox->root()->nextRootBox(), 0); |
| if (nextBox) |
| return nextBox; |
| |
| while (1) { |
| Node* startNode = startBox->renderer() ? startBox->renderer()->nonPseudoNode() : 0; |
| if (!startNode) |
| break; |
| |
| Position position = nextRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
| if (position.isNull()) |
| break; |
| |
| RenderedPosition renderedPosition(position, DOWNSTREAM); |
| RootInlineBox* nextRoot = renderedPosition.rootBox(); |
| if (!nextRoot) |
| break; |
| |
| nextBox = leafBoxes.nextTextBox(nextRoot, 0); |
| if (nextBox) { |
| nextBoxInDifferentBlock = true; |
| return nextBox; |
| } |
| |
| if (!leafBoxes.size()) |
| break; |
| startBox = leafBoxes.firstBox(); |
| } |
| return 0; |
| } |
| |
| static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
| int& previousBoxLength, bool& previousBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
| { |
| previousBoxInDifferentBlock = false; |
| |
| // FIXME: Handle the case when we don't have an inline text box. |
| const InlineTextBox* previousBox = logicallyPreviousBox(visiblePosition, textBox, previousBoxInDifferentBlock, leafBoxes); |
| |
| int len = 0; |
| string.clear(); |
| if (previousBox) { |
| previousBoxLength = previousBox->len(); |
| previousBox->textRenderer()->text().appendTo(string, previousBox->start(), previousBoxLength); |
| len += previousBoxLength; |
| } |
| textBox->textRenderer()->text().appendTo(string, textBox->start(), textBox->len()); |
| len += textBox->len(); |
| |
| return wordBreakIterator(string.data(), len); |
| } |
| |
| static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, |
| bool& nextBoxInDifferentBlock, Vector<UChar, 1024>& string, CachedLogicallyOrderedLeafBoxes& leafBoxes) |
| { |
| nextBoxInDifferentBlock = false; |
| |
| // FIXME: Handle the case when we don't have an inline text box. |
| const InlineTextBox* nextBox = logicallyNextBox(visiblePosition, textBox, nextBoxInDifferentBlock, leafBoxes); |
| |
| int len = 0; |
| string.clear(); |
| textBox->textRenderer()->text().appendTo(string, textBox->start(), textBox->len()); |
| len += textBox->len(); |
| if (nextBox) { |
| nextBox->textRenderer()->text().appendTo(string, nextBox->start(), nextBox->len()); |
| len += nextBox->len(); |
| } |
| |
| return wordBreakIterator(string.data(), len); |
| } |
| |
| static bool isLogicalStartOfWord(TextBreakIterator* iter, int position, bool hardLineBreak) |
| { |
| bool boundary = hardLineBreak ? true : iter->isBoundary(position); |
| if (!boundary) |
| return false; |
| |
| iter->following(position); |
| // isWordTextBreak returns true after moving across a word and false after moving across a punctuation/space. |
| return isWordTextBreak(iter); |
| } |
| |
| static bool islogicalEndOfWord(TextBreakIterator* iter, int position, bool hardLineBreak) |
| { |
| bool boundary = iter->isBoundary(position); |
| return (hardLineBreak || boundary) && isWordTextBreak(iter); |
| } |
| |
| enum CursorMovementDirection { MoveLeft, MoveRight }; |
| |
| static VisiblePosition visualWordPosition(const VisiblePosition& visiblePosition, CursorMovementDirection direction, |
| bool skipsSpaceWhenMovingRight) |
| { |
| if (visiblePosition.isNull()) |
| return VisiblePosition(); |
| |
| TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
| InlineBox* previouslyVisitedBox = 0; |
| VisiblePosition current = visiblePosition; |
| TextBreakIterator* iter = 0; |
| |
| CachedLogicallyOrderedLeafBoxes leafBoxes; |
| Vector<UChar, 1024> string; |
| |
| while (1) { |
| VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true); |
| if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull()) |
| return VisiblePosition(); |
| |
| InlineBox* box; |
| int offsetInBox; |
| adjacentCharacterPosition.deepEquivalent().getInlineBoxAndOffset(UPSTREAM, box, offsetInBox); |
| |
| if (!box) |
| break; |
| if (!box->isInlineTextBox()) { |
| current = adjacentCharacterPosition; |
| continue; |
| } |
| |
| InlineTextBox* textBox = toInlineTextBox(box); |
| int previousBoxLength = 0; |
| bool previousBoxInDifferentBlock = false; |
| bool nextBoxInDifferentBlock = false; |
| bool movingIntoNewBox = previouslyVisitedBox != box; |
| |
| if (offsetInBox == box->caretMinOffset()) |
| iter = wordBreakIteratorForMinOffsetBoundary(visiblePosition, textBox, previousBoxLength, previousBoxInDifferentBlock, string, leafBoxes); |
| else if (offsetInBox == box->caretMaxOffset()) |
| iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, textBox, nextBoxInDifferentBlock, string, leafBoxes); |
| else if (movingIntoNewBox) { |
| iter = wordBreakIterator(textBox->textRenderer()->text(), textBox->start(), textBox->len()); |
| previouslyVisitedBox = box; |
| } |
| |
| if (!iter) |
| break; |
| |
| iter->first(); |
| int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength; |
| |
| bool isWordBreak; |
| bool boxHasSameDirectionalityAsBlock = box->direction() == blockDirection; |
| bool movingBackward = (direction == MoveLeft && box->direction() == LTR) || (direction == MoveRight && box->direction() == RTL); |
| if ((skipsSpaceWhenMovingRight && boxHasSameDirectionalityAsBlock) |
| || (!skipsSpaceWhenMovingRight && movingBackward)) { |
| bool logicalStartInRenderer = offsetInBox == static_cast<int>(textBox->start()) && previousBoxInDifferentBlock; |
| isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInRenderer); |
| } else { |
| bool logicalEndInRenderer = offsetInBox == static_cast<int>(textBox->start() + textBox->len()) && nextBoxInDifferentBlock; |
| isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer); |
| } |
| |
| if (isWordBreak) |
| return adjacentCharacterPosition; |
| |
| current = adjacentCharacterPosition; |
| } |
| return VisiblePosition(); |
| } |
| |
| VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) |
| { |
| VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight); |
| leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak); |
| |
| // FIXME: How should we handle a non-editable position? |
| if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { |
| TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
| leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition); |
| } |
| return leftWordBreak; |
| } |
| |
| VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) |
| { |
| VisiblePosition rightWordBreak = visualWordPosition(visiblePosition, MoveRight, skipsSpaceWhenMovingRight); |
| rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak); |
| |
| // FIXME: How should we handle a non-editable position? |
| if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { |
| TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); |
| rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition); |
| } |
| return rightWordBreak; |
| } |
| |
| |
| enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; |
| |
| typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); |
| |
| static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) |
| { |
| Position pos = c.deepEquivalent(); |
| Node* boundary = pos.parentEditingBoundary(); |
| if (!boundary) |
| return VisiblePosition(); |
| |
| Document& d = boundary->document(); |
| Position start = createLegacyEditingPosition(boundary, 0).parentAnchoredEquivalent(); |
| Position end = pos.parentAnchoredEquivalent(); |
| RefPtr<Range> searchRange = Range::create(d); |
| |
| Vector<UChar, 1024> string; |
| unsigned suffixLength = 0; |
| |
| TrackExceptionState es; |
| if (requiresContextForWordBoundary(c.characterBefore())) { |
| RefPtr<Range> forwardsScanRange(d.createRange()); |
| forwardsScanRange->setEndAfter(boundary, es); |
| forwardsScanRange->setStart(end.deprecatedNode(), end.deprecatedEditingOffset(), es); |
| TextIterator forwardsIterator(forwardsScanRange.get()); |
| while (!forwardsIterator.atEnd()) { |
| Vector<UChar, 1024> characters; |
| forwardsIterator.appendTextTo(characters); |
| int i = endOfFirstWordBoundaryContext(characters.data(), characters.size()); |
| string.append(characters.data(), i); |
| suffixLength += i; |
| if (static_cast<unsigned>(i) < characters.size()) |
| break; |
| forwardsIterator.advance(); |
| } |
| } |
| |
| searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), es); |
| searchRange->setEnd(end.deprecatedNode(), end.deprecatedEditingOffset(), es); |
| |
| ASSERT(!es.hadException()); |
| if (es.hadException()) |
| return VisiblePosition(); |
| |
| SimplifiedBackwardsTextIterator it(searchRange.get()); |
| unsigned next = 0; |
| bool needMoreContext = false; |
| while (!it.atEnd()) { |
| bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style()->textSecurity() != TSNONE; |
| // iterate to get chunks until the searchFunction returns a non-zero value. |
| if (!inTextSecurityMode) |
| it.prependTextTo(string); |
| else { |
| // Treat bullets used in the text security mode as regular characters when looking for boundaries |
| Vector<UChar, 1024> iteratorString; |
| iteratorString.fill('x', it.length()); |
| string.prepend(iteratorString.data(), iteratorString.size()); |
| } |
| next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); |
| if (next) |
| break; |
| it.advance(); |
| } |
| if (needMoreContext) { |
| // The last search returned the beginning of the buffer and asked for more context, |
| // but there is no earlier text. Force a search with what's available. |
| next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); |
| ASSERT(!needMoreContext); |
| } |
| |
| if (!next) |
| return VisiblePosition(it.atEnd() ? it.range()->startPosition() : pos, DOWNSTREAM); |
| |
| Node* node = it.range()->startContainer(); |
| if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next)) |
| // The next variable contains a usable index into a text node |
| return VisiblePosition(createLegacyEditingPosition(node, next), DOWNSTREAM); |
| |
| // Use the character iterator to translate the next value into a DOM position. |
| BackwardsCharacterIterator charIt(searchRange.get()); |
| charIt.advance(string.size() - suffixLength - next); |
| // FIXME: charIt can get out of shadow host. |
| return VisiblePosition(charIt.range()->endPosition(), DOWNSTREAM); |
| } |
| |
| static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) |
| { |
| Position pos = c.deepEquivalent(); |
| Node* boundary = pos.parentEditingBoundary(); |
| if (!boundary) |
| return VisiblePosition(); |
| |
| Document& d = boundary->document(); |
| RefPtr<Range> searchRange(d.createRange()); |
| Position start(pos.parentAnchoredEquivalent()); |
| |
| Vector<UChar, 1024> string; |
| unsigned prefixLength = 0; |
| |
| if (requiresContextForWordBoundary(c.characterAfter())) { |
| RefPtr<Range> backwardsScanRange(d.createRange()); |
| backwardsScanRange->setEnd(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION); |
| SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); |
| while (!backwardsIterator.atEnd()) { |
| Vector<UChar, 1024> characters; |
| backwardsIterator.prependTextTo(characters); |
| int length = characters.size(); |
| int i = startOfLastWordBoundaryContext(characters.data(), length); |
| string.prepend(characters.data() + i, length - i); |
| prefixLength += length - i; |
| if (i > 0) |
| break; |
| backwardsIterator.advance(); |
| } |
| } |
| |
| searchRange->selectNodeContents(boundary, IGNORE_EXCEPTION); |
| searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), IGNORE_EXCEPTION); |
| TextIterator it(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
| const unsigned invalidOffset = static_cast<unsigned>(-1); |
| unsigned next = invalidOffset; |
| bool needMoreContext = false; |
| while (!it.atEnd()) { |
| // Keep asking the iterator for chunks until the search function |
| // returns an end value not equal to the length of the string passed to it. |
| bool inTextSecurityMode = it.node() && it.node()->renderer() && it.node()->renderer()->style()->textSecurity() != TSNONE; |
| if (!inTextSecurityMode) |
| it.appendTextTo(string); |
| else { |
| // Treat bullets used in the text security mode as regular characters when looking for boundaries |
| Vector<UChar, 1024> iteratorString; |
| iteratorString.fill('x', it.length()); |
| string.append(iteratorString.data(), iteratorString.size()); |
| } |
| next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext); |
| if (next != string.size()) |
| break; |
| it.advance(); |
| } |
| if (needMoreContext) { |
| // The last search returned the end of the buffer and asked for more context, |
| // but there is no further text. Force a search with what's available. |
| next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); |
| ASSERT(!needMoreContext); |
| } |
| |
| if (it.atEnd() && next == string.size()) { |
| pos = it.range()->startPosition(); |
| } else if (next != invalidOffset && next != prefixLength) { |
| // Use the character iterator to translate the next value into a DOM position. |
| CharacterIterator charIt(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
| charIt.advance(next - prefixLength - 1); |
| RefPtr<Range> characterRange = charIt.range(); |
| pos = characterRange->endPosition(); |
| |
| if (charIt.characterAt(0) == '\n') { |
| // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) |
| VisiblePosition visPos = VisiblePosition(pos); |
| if (visPos == VisiblePosition(characterRange->startPosition())) { |
| charIt.advance(1); |
| pos = charIt.range()->startPosition(); |
| } |
| } |
| } |
| |
| // generate VisiblePosition, use UPSTREAM affinity if possible |
| return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
| } |
| |
| // --------- |
| |
| static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| ASSERT(offset); |
| if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { |
| needMoreContext = true; |
| return 0; |
| } |
| needMoreContext = false; |
| int start, end; |
| U16_BACK_1(characters, 0, offset); |
| findWordBoundary(characters, length, offset, &start, &end); |
| return start; |
| } |
| |
| VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) |
| { |
| // FIXME: This returns a null VP for c at the start of the document |
| // and side == LeftWordIfOnBoundary |
| VisiblePosition p = c; |
| if (side == RightWordIfOnBoundary) { |
| // at paragraph end, the startofWord is the current position |
| if (isEndOfParagraph(c)) |
| return c; |
| |
| p = c.next(); |
| if (p.isNull()) |
| return c; |
| } |
| return previousBoundary(p, startWordBoundary); |
| } |
| |
| static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| ASSERT(offset <= length); |
| if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) { |
| needMoreContext = true; |
| return length; |
| } |
| needMoreContext = false; |
| int start, end; |
| findWordBoundary(characters, length, offset, &start, &end); |
| return end; |
| } |
| |
| VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) |
| { |
| VisiblePosition p = c; |
| if (side == LeftWordIfOnBoundary) { |
| if (isStartOfParagraph(c)) |
| return c; |
| |
| p = c.previous(); |
| if (p.isNull()) |
| return c; |
| } else if (isEndOfParagraph(c)) |
| return c; |
| |
| return nextBoundary(p, endWordBoundary); |
| } |
| |
| static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { |
| needMoreContext = true; |
| return 0; |
| } |
| needMoreContext = false; |
| return findNextWordFromIndex(characters, length, offset, false); |
| } |
| |
| VisiblePosition previousWordPosition(const VisiblePosition &c) |
| { |
| VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary); |
| return c.honorEditingBoundaryAtOrBefore(prev); |
| } |
| |
| static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) { |
| needMoreContext = true; |
| return length; |
| } |
| needMoreContext = false; |
| return findNextWordFromIndex(characters, length, offset, true); |
| } |
| |
| VisiblePosition nextWordPosition(const VisiblePosition &c) |
| { |
| VisiblePosition next = nextBoundary(c, nextWordPositionBoundary); |
| return c.honorEditingBoundaryAtOrAfter(next); |
| } |
| |
| bool isStartOfWord(const VisiblePosition& p) |
| { |
| return p.isNotNull() && p == startOfWord(p, RightWordIfOnBoundary); |
| } |
| |
| // --------- |
| |
| enum LineEndpointComputationMode { UseLogicalOrdering, UseInlineBoxOrdering }; |
| static VisiblePosition startPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
| { |
| if (c.isNull()) |
| return VisiblePosition(); |
| |
| RootInlineBox* rootBox = RenderedPosition(c).rootBox(); |
| if (!rootBox) { |
| // There are VisiblePositions at offset 0 in blocks without |
| // RootInlineBoxes, like empty editable blocks and bordered blocks. |
| Position p = c.deepEquivalent(); |
| if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) |
| return c; |
| |
| return VisiblePosition(); |
| } |
| |
| Node* startNode; |
| InlineBox* startBox; |
| if (mode == UseLogicalOrdering) { |
| startNode = rootBox->getLogicalStartBoxWithNode(startBox); |
| if (!startNode) |
| return VisiblePosition(); |
| } else { |
| // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, |
| // and so cannot be represented by a VisiblePosition. Use whatever follows instead. |
| startBox = rootBox->firstLeafChild(); |
| while (true) { |
| if (!startBox) |
| return VisiblePosition(); |
| |
| RenderObject* startRenderer = startBox->renderer(); |
| if (!startRenderer) |
| return VisiblePosition(); |
| |
| startNode = startRenderer->nonPseudoNode(); |
| if (startNode) |
| break; |
| |
| startBox = startBox->nextLeafChild(); |
| } |
| } |
| |
| return startNode->isTextNode() ? Position(toText(startNode), toInlineTextBox(startBox)->start()) |
| : positionBeforeNode(startNode); |
| } |
| |
| static VisiblePosition startOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
| { |
| // TODO: this is the current behavior that might need to be fixed. |
| // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. |
| VisiblePosition visPos = startPositionForLine(c, mode); |
| |
| if (mode == UseLogicalOrdering) { |
| if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { |
| if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) |
| return firstPositionInNode(editableRoot); |
| } |
| } |
| |
| return c.honorEditingBoundaryAtOrBefore(visPos); |
| } |
| |
| // FIXME: Rename this function to reflect the fact it ignores bidi levels. |
| VisiblePosition startOfLine(const VisiblePosition& currentPosition) |
| { |
| return startOfLine(currentPosition, UseInlineBoxOrdering); |
| } |
| |
| VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition) |
| { |
| return startOfLine(currentPosition, UseLogicalOrdering); |
| } |
| |
| static VisiblePosition endPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
| { |
| if (c.isNull()) |
| return VisiblePosition(); |
| |
| RootInlineBox* rootBox = RenderedPosition(c).rootBox(); |
| if (!rootBox) { |
| // There are VisiblePositions at offset 0 in blocks without |
| // RootInlineBoxes, like empty editable blocks and bordered blocks. |
| Position p = c.deepEquivalent(); |
| if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) |
| return c; |
| return VisiblePosition(); |
| } |
| |
| Node* endNode; |
| InlineBox* endBox; |
| if (mode == UseLogicalOrdering) { |
| endNode = rootBox->getLogicalEndBoxWithNode(endBox); |
| if (!endNode) |
| return VisiblePosition(); |
| } else { |
| // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, |
| // and so cannot be represented by a VisiblePosition. Use whatever precedes instead. |
| endBox = rootBox->lastLeafChild(); |
| while (true) { |
| if (!endBox) |
| return VisiblePosition(); |
| |
| RenderObject* endRenderer = endBox->renderer(); |
| if (!endRenderer) |
| return VisiblePosition(); |
| |
| endNode = endRenderer->nonPseudoNode(); |
| if (endNode) |
| break; |
| |
| endBox = endBox->prevLeafChild(); |
| } |
| } |
| |
| Position pos; |
| if (endNode->hasTagName(brTag)) |
| pos = positionBeforeNode(endNode); |
| else if (endBox->isInlineTextBox() && endNode->isTextNode()) { |
| InlineTextBox* endTextBox = toInlineTextBox(endBox); |
| int endOffset = endTextBox->start(); |
| if (!endTextBox->isLineBreak()) |
| endOffset += endTextBox->len(); |
| pos = Position(toText(endNode), endOffset); |
| } else |
| pos = positionAfterNode(endNode); |
| |
| return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
| } |
| |
| static bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b) |
| { |
| return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b); |
| } |
| |
| static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) |
| { |
| // TODO: this is the current behavior that might need to be fixed. |
| // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. |
| VisiblePosition visPos = endPositionForLine(c, mode); |
| |
| if (mode == UseLogicalOrdering) { |
| // Make sure the end of line is at the same line as the given input position. For a wrapping line, the logical end |
| // position for the not-last-2-lines might incorrectly hand back the logical beginning of the next line. |
| // For example, <div contenteditable dir="rtl" style="line-break:before-white-space">abcdefg abcdefg abcdefg |
| // a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg </div> |
| // In this case, use the previous position of the computed logical end position. |
| if (!inSameLogicalLine(c, visPos)) |
| visPos = visPos.previous(); |
| |
| if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { |
| if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) |
| return lastPositionInNode(editableRoot); |
| } |
| |
| return c.honorEditingBoundaryAtOrAfter(visPos); |
| } |
| |
| // Make sure the end of line is at the same line as the given input position. Else use the previous position to |
| // obtain end of line. This condition happens when the input position is before the space character at the end |
| // of a soft-wrapped non-editable line. In this scenario, endPositionForLine would incorrectly hand back a position |
| // in the next line instead. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space style |
| // versus lines without that style, which would break before a space by default. |
| if (!inSameLine(c, visPos)) { |
| visPos = c.previous(); |
| if (visPos.isNull()) |
| return VisiblePosition(); |
| visPos = endPositionForLine(visPos, UseInlineBoxOrdering); |
| } |
| |
| return c.honorEditingBoundaryAtOrAfter(visPos); |
| } |
| |
| // FIXME: Rename this function to reflect the fact it ignores bidi levels. |
| VisiblePosition endOfLine(const VisiblePosition& currentPosition) |
| { |
| return endOfLine(currentPosition, UseInlineBoxOrdering); |
| } |
| |
| VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition) |
| { |
| return endOfLine(currentPosition, UseLogicalOrdering); |
| } |
| |
| bool inSameLine(const VisiblePosition &a, const VisiblePosition &b) |
| { |
| return a.isNotNull() && startOfLine(a) == startOfLine(b); |
| } |
| |
| bool isStartOfLine(const VisiblePosition &p) |
| { |
| return p.isNotNull() && p == startOfLine(p); |
| } |
| |
| bool isEndOfLine(const VisiblePosition &p) |
| { |
| return p.isNotNull() && p == endOfLine(p); |
| } |
| |
| static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, int lineDirectionPoint) |
| { |
| ASSERT(root); |
| RenderBlock* containingBlock = root->block(); |
| FloatPoint absoluteBlockPoint = containingBlock->localToAbsolute(FloatPoint()); |
| if (containingBlock->hasOverflowClip()) |
| absoluteBlockPoint -= containingBlock->scrolledContentOffset(); |
| |
| if (root->block()->isHorizontalWritingMode()) |
| return IntPoint(lineDirectionPoint - absoluteBlockPoint.x(), root->blockDirectionPointInLine()); |
| |
| return IntPoint(root->blockDirectionPointInLine(), lineDirectionPoint - absoluteBlockPoint.y()); |
| } |
| |
| VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) |
| { |
| Position p = visiblePosition.deepEquivalent(); |
| Node* node = p.deprecatedNode(); |
| |
| if (!node) |
| return VisiblePosition(); |
| |
| node->document().updateLayoutIgnorePendingStylesheets(); |
| |
| RenderObject* renderer = node->renderer(); |
| if (!renderer) |
| return VisiblePosition(); |
| |
| RootInlineBox* root = 0; |
| InlineBox* box; |
| int ignoredCaretOffset; |
| visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); |
| if (box) { |
| root = box->root()->prevRootBox(); |
| // We want to skip zero height boxes. |
| // This could happen in case it is a TrailingFloatsRootInlineBox. |
| if (!root || !root->logicalHeight() || !root->firstLeafChild()) |
| root = 0; |
| } |
| |
| if (!root) { |
| Position position = previousRootInlineBoxCandidatePosition(node, visiblePosition, editableType); |
| if (position.isNotNull()) { |
| RenderedPosition renderedPosition(position); |
| root = renderedPosition.rootBox(); |
| if (!root) |
| return position; |
| } |
| } |
| |
| if (root) { |
| // FIXME: Can be wrong for multi-column layout and with transforms. |
| IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); |
| RenderObject* renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); |
| Node* node = renderer->node(); |
| if (node && editingIgnoresContent(node)) |
| return positionInParentBeforeNode(node); |
| return VisiblePosition(renderer->positionForPoint(pointInLine)); |
| } |
| |
| // Could not find a previous line. This means we must already be on the first line. |
| // Move to the start of the content in this block, which effectively moves us |
| // to the start of the line we're on. |
| Element* rootElement = node->rendererIsEditable(editableType) ? node->rootEditableElement(editableType) : node->document().documentElement(); |
| if (!rootElement) |
| return VisiblePosition(); |
| return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); |
| } |
| |
| VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) |
| { |
| Position p = visiblePosition.deepEquivalent(); |
| Node* node = p.deprecatedNode(); |
| |
| if (!node) |
| return VisiblePosition(); |
| |
| node->document().updateLayoutIgnorePendingStylesheets(); |
| |
| RenderObject* renderer = node->renderer(); |
| if (!renderer) |
| return VisiblePosition(); |
| |
| RootInlineBox* root = 0; |
| InlineBox* box; |
| int ignoredCaretOffset; |
| visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); |
| if (box) { |
| root = box->root()->nextRootBox(); |
| // We want to skip zero height boxes. |
| // This could happen in case it is a TrailingFloatsRootInlineBox. |
| if (!root || !root->logicalHeight() || !root->firstLeafChild()) |
| root = 0; |
| } |
| |
| if (!root) { |
| // FIXME: We need do the same in previousLinePosition. |
| Node* child = node->childNode(p.deprecatedEditingOffset()); |
| node = child ? child : &node->lastDescendant(); |
| Position position = nextRootInlineBoxCandidatePosition(node, visiblePosition, editableType); |
| if (position.isNotNull()) { |
| RenderedPosition renderedPosition(position); |
| root = renderedPosition.rootBox(); |
| if (!root) |
| return position; |
| } |
| } |
| |
| if (root) { |
| // FIXME: Can be wrong for multi-column layout and with transforms. |
| IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); |
| RenderObject* renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); |
| Node* node = renderer->node(); |
| if (node && editingIgnoresContent(node)) |
| return positionInParentBeforeNode(node); |
| return VisiblePosition(renderer->positionForPoint(pointInLine)); |
| } |
| |
| // Could not find a next line. This means we must already be on the last line. |
| // Move to the end of the content in this block, which effectively moves us |
| // to the end of the line we're on. |
| Element* rootElement = node->rendererIsEditable(editableType) ? node->rootEditableElement(editableType) : node->document().documentElement(); |
| if (!rootElement) |
| return VisiblePosition(); |
| return VisiblePosition(lastPositionInNode(rootElement), DOWNSTREAM); |
| } |
| |
| // --------- |
| |
| static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) |
| { |
| TextBreakIterator* iterator = sentenceBreakIterator(characters, length); |
| // FIXME: The following function can return -1; we don't handle that. |
| return iterator->preceding(length); |
| } |
| |
| VisiblePosition startOfSentence(const VisiblePosition &c) |
| { |
| return previousBoundary(c, startSentenceBoundary); |
| } |
| |
| static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) |
| { |
| TextBreakIterator* iterator = sentenceBreakIterator(characters, length); |
| return iterator->next(); |
| } |
| |
| // FIXME: This includes the space after the punctuation that marks the end of the sentence. |
| VisiblePosition endOfSentence(const VisiblePosition &c) |
| { |
| return nextBoundary(c, endSentenceBoundary); |
| } |
| |
| static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) |
| { |
| // FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right. |
| TextBreakIterator* iterator = sentenceBreakIterator(characters, length); |
| // FIXME: The following function can return -1; we don't handle that. |
| return iterator->preceding(length); |
| } |
| |
| VisiblePosition previousSentencePosition(const VisiblePosition &c) |
| { |
| VisiblePosition prev = previousBoundary(c, previousSentencePositionBoundary); |
| return c.honorEditingBoundaryAtOrBefore(prev); |
| } |
| |
| static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) |
| { |
| // FIXME: This is identical to endSentenceBoundary. This isn't right, it needs to |
| // move to the equivlant position in the following sentence. |
| TextBreakIterator* iterator = sentenceBreakIterator(characters, length); |
| return iterator->following(0); |
| } |
| |
| VisiblePosition nextSentencePosition(const VisiblePosition &c) |
| { |
| VisiblePosition next = nextBoundary(c, nextSentencePositionBoundary); |
| return c.honorEditingBoundaryAtOrAfter(next); |
| } |
| |
| VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| Position p = c.deepEquivalent(); |
| Node* startNode = p.deprecatedNode(); |
| |
| if (!startNode) |
| return VisiblePosition(); |
| |
| if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
| return positionBeforeNode(startNode); |
| |
| Node* startBlock = enclosingBlock(startNode); |
| |
| Node* node = startNode; |
| Node* highestRoot = highestEditableRoot(p); |
| int offset = p.deprecatedEditingOffset(); |
| Position::AnchorType type = p.anchorType(); |
| |
| Node* n = startNode; |
| while (n) { |
| if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->rendererIsEditable() != startNode->rendererIsEditable()) |
| break; |
| if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
| while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) |
| n = NodeTraversal::previousPostOrder(n, startBlock); |
| if (!n || !n->isDescendantOf(highestRoot)) |
| break; |
| } |
| RenderObject* r = n->renderer(); |
| if (!r) { |
| n = NodeTraversal::previousPostOrder(n, startBlock); |
| continue; |
| } |
| RenderStyle* style = r->style(); |
| if (style->visibility() != VISIBLE) { |
| n = NodeTraversal::previousPostOrder(n, startBlock); |
| continue; |
| } |
| |
| if (r->isBR() || isBlock(n)) |
| break; |
| |
| if (r->isText() && toRenderText(r)->renderedTextLength()) { |
| ASSERT_WITH_SECURITY_IMPLICATION(n->isTextNode()); |
| type = Position::PositionIsOffsetInAnchor; |
| if (style->preserveNewline()) { |
| RenderText* text = toRenderText(r); |
| int i = text->textLength(); |
| int o = offset; |
| if (n == startNode && o < i) |
| i = max(0, o); |
| while (--i >= 0) { |
| if ((*text)[i] == '\n') |
| return VisiblePosition(Position(toText(n), i + 1), DOWNSTREAM); |
| } |
| } |
| node = n; |
| offset = 0; |
| n = NodeTraversal::previousPostOrder(n, startBlock); |
| } else if (editingIgnoresContent(n) || isRenderedTable(n)) { |
| node = n; |
| type = Position::PositionIsBeforeAnchor; |
| n = n->previousSibling() ? n->previousSibling() : NodeTraversal::previousPostOrder(n, startBlock); |
| } else |
| n = NodeTraversal::previousPostOrder(n, startBlock); |
| } |
| |
| if (type == Position::PositionIsOffsetInAnchor) { |
| ASSERT(type == Position::PositionIsOffsetInAnchor || !offset); |
| return VisiblePosition(Position(node, offset, type), DOWNSTREAM); |
| } |
| |
| return VisiblePosition(Position(node, type), DOWNSTREAM); |
| } |
| |
| VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| if (c.isNull()) |
| return VisiblePosition(); |
| |
| Position p = c.deepEquivalent(); |
| Node* startNode = p.deprecatedNode(); |
| |
| if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
| return positionAfterNode(startNode); |
| |
| Node* startBlock = enclosingBlock(startNode); |
| Node* stayInsideBlock = startBlock; |
| |
| Node* node = startNode; |
| Node* highestRoot = highestEditableRoot(p); |
| int offset = p.deprecatedEditingOffset(); |
| Position::AnchorType type = p.anchorType(); |
| |
| Node* n = startNode; |
| while (n) { |
| if (boundaryCrossingRule == CannotCrossEditingBoundary && !Position::nodeIsUserSelectAll(n) && n->rendererIsEditable() != startNode->rendererIsEditable()) |
| break; |
| if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
| while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) |
| n = NodeTraversal::next(n, stayInsideBlock); |
| if (!n || !n->isDescendantOf(highestRoot)) |
| break; |
| } |
| |
| RenderObject* r = n->renderer(); |
| if (!r) { |
| n = NodeTraversal::next(n, stayInsideBlock); |
| continue; |
| } |
| RenderStyle* style = r->style(); |
| if (style->visibility() != VISIBLE) { |
| n = NodeTraversal::next(n, stayInsideBlock); |
| continue; |
| } |
| |
| if (r->isBR() || isBlock(n)) |
| break; |
| |
| // FIXME: We avoid returning a position where the renderer can't accept the caret. |
| if (r->isText() && toRenderText(r)->renderedTextLength()) { |
| ASSERT_WITH_SECURITY_IMPLICATION(n->isTextNode()); |
| int length = toRenderText(r)->textLength(); |
| type = Position::PositionIsOffsetInAnchor; |
| if (style->preserveNewline()) { |
| RenderText* text = toRenderText(r); |
| int o = n == startNode ? offset : 0; |
| for (int i = o; i < length; ++i) { |
| if ((*text)[i] == '\n') |
| return VisiblePosition(Position(toText(n), i), DOWNSTREAM); |
| } |
| } |
| node = n; |
| offset = r->caretMaxOffset(); |
| n = NodeTraversal::next(n, stayInsideBlock); |
| } else if (editingIgnoresContent(n) || isRenderedTable(n)) { |
| node = n; |
| type = Position::PositionIsAfterAnchor; |
| n = NodeTraversal::nextSkippingChildren(n, stayInsideBlock); |
| } else |
| n = NodeTraversal::next(n, stayInsideBlock); |
| } |
| |
| if (type == Position::PositionIsOffsetInAnchor) |
| return VisiblePosition(Position(node, offset, type), DOWNSTREAM); |
| |
| return VisiblePosition(Position(node, type), DOWNSTREAM); |
| } |
| |
| // FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true |
| VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) |
| { |
| VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary)); |
| VisiblePosition afterParagraphEnd(paragraphEnd.next(CannotCrossEditingBoundary)); |
| // The position after the last position in the last cell of a table |
| // is not the start of the next paragraph. |
| if (isFirstPositionAfterTable(afterParagraphEnd)) |
| return afterParagraphEnd.next(CannotCrossEditingBoundary); |
| return afterParagraphEnd; |
| } |
| |
| bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule) == startOfParagraph(b, boundaryCrossingRule); |
| } |
| |
| bool isStartOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return pos.isNotNull() && pos == startOfParagraph(pos, boundaryCrossingRule); |
| } |
| |
| bool isEndOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return pos.isNotNull() && pos == endOfParagraph(pos, boundaryCrossingRule); |
| } |
| |
| VisiblePosition previousParagraphPosition(const VisiblePosition& p, int x) |
| { |
| VisiblePosition pos = p; |
| do { |
| VisiblePosition n = previousLinePosition(pos, x); |
| if (n.isNull() || n == pos) |
| break; |
| pos = n; |
| } while (inSameParagraph(p, pos)); |
| return pos; |
| } |
| |
| VisiblePosition nextParagraphPosition(const VisiblePosition& p, int x) |
| { |
| VisiblePosition pos = p; |
| do { |
| VisiblePosition n = nextLinePosition(pos, x); |
| if (n.isNull() || n == pos) |
| break; |
| pos = n; |
| } while (inSameParagraph(p, pos)); |
| return pos; |
| } |
| |
| // --------- |
| |
| VisiblePosition startOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| Position position = visiblePosition.deepEquivalent(); |
| Node* startBlock; |
| if (!position.containerNode() || !(startBlock = enclosingBlock(position.containerNode(), rule))) |
| return VisiblePosition(); |
| return firstPositionInNode(startBlock); |
| } |
| |
| VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| Position position = visiblePosition.deepEquivalent(); |
| Node* endBlock; |
| if (!position.containerNode() || !(endBlock = enclosingBlock(position.containerNode(), rule))) |
| return VisiblePosition(); |
| return lastPositionInNode(endBlock); |
| } |
| |
| bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b) |
| { |
| return !a.isNull() && enclosingBlock(a.deepEquivalent().containerNode()) == enclosingBlock(b.deepEquivalent().containerNode()); |
| } |
| |
| bool isStartOfBlock(const VisiblePosition &pos) |
| { |
| return pos.isNotNull() && pos == startOfBlock(pos, CanCrossEditingBoundary); |
| } |
| |
| bool isEndOfBlock(const VisiblePosition &pos) |
| { |
| return pos.isNotNull() && pos == endOfBlock(pos, CanCrossEditingBoundary); |
| } |
| |
| // --------- |
| |
| VisiblePosition startOfDocument(const Node* node) |
| { |
| if (!node || !node->document().documentElement()) |
| return VisiblePosition(); |
| |
| return VisiblePosition(firstPositionInNode(node->document().documentElement()), DOWNSTREAM); |
| } |
| |
| VisiblePosition startOfDocument(const VisiblePosition &c) |
| { |
| return startOfDocument(c.deepEquivalent().deprecatedNode()); |
| } |
| |
| VisiblePosition endOfDocument(const Node* node) |
| { |
| if (!node || !node->document().documentElement()) |
| return VisiblePosition(); |
| |
| Element* doc = node->document().documentElement(); |
| return VisiblePosition(lastPositionInNode(doc), DOWNSTREAM); |
| } |
| |
| VisiblePosition endOfDocument(const VisiblePosition &c) |
| { |
| return endOfDocument(c.deepEquivalent().deprecatedNode()); |
| } |
| |
| bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b) |
| { |
| Position ap = a.deepEquivalent(); |
| Node* an = ap.deprecatedNode(); |
| if (!an) |
| return false; |
| Position bp = b.deepEquivalent(); |
| Node* bn = bp.deprecatedNode(); |
| if (an == bn) |
| return true; |
| |
| return an->document() == bn->document(); |
| } |
| |
| bool isStartOfDocument(const VisiblePosition &p) |
| { |
| return p.isNotNull() && p.previous(CanCrossEditingBoundary).isNull(); |
| } |
| |
| bool isEndOfDocument(const VisiblePosition &p) |
| { |
| return p.isNotNull() && p.next(CanCrossEditingBoundary).isNull(); |
| } |
| |
| // --------- |
| |
| VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) |
| { |
| Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
| if (!highestRoot) |
| return VisiblePosition(); |
| |
| return firstPositionInNode(highestRoot); |
| } |
| |
| VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) |
| { |
| Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
| if (!highestRoot) |
| return VisiblePosition(); |
| |
| return lastPositionInNode(highestRoot); |
| } |
| |
| bool isEndOfEditableOrNonEditableContent(const VisiblePosition &p) |
| { |
| return p.isNotNull() && p.next().isNull(); |
| } |
| |
| VisiblePosition leftBoundaryOfLine(const VisiblePosition& c, TextDirection direction) |
| { |
| return direction == LTR ? logicalStartOfLine(c) : logicalEndOfLine(c); |
| } |
| |
| VisiblePosition rightBoundaryOfLine(const VisiblePosition& c, TextDirection direction) |
| { |
| return direction == LTR ? logicalEndOfLine(c) : logicalStartOfLine(c); |
| } |
| |
| } |