| /* |
| * 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 "core/editing/VisibleUnits.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/FirstLetterPseudoElement.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/Text.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/Position.h" |
| #include "core/editing/PositionIterator.h" |
| #include "core/editing/RenderedPosition.h" |
| #include "core/editing/TextAffinity.h" |
| #include "core/editing/VisiblePosition.h" |
| #include "core/editing/iterators/BackwardsCharacterIterator.h" |
| #include "core/editing/iterators/BackwardsTextBuffer.h" |
| #include "core/editing/iterators/CharacterIterator.h" |
| #include "core/editing/iterators/ForwardsTextBuffer.h" |
| #include "core/editing/iterators/SimplifiedBackwardsTextIterator.h" |
| #include "core/editing/iterators/TextIterator.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLBRElement.h" |
| #include "core/html/HTMLTextFormControlElement.h" |
| #include "core/layout/HitTestRequest.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutInline.h" |
| #include "core/layout/LayoutObject.h" |
| #include "core/layout/LayoutTextFragment.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutItem.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/layout/api/LineLayoutAPIShim.h" |
| #include "core/layout/api/LineLayoutItem.h" |
| #include "core/layout/line/InlineIterator.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "platform/Logging.h" |
| #include "platform/heap/Handle.h" |
| #include "platform/text/TextBoundaries.h" |
| #include "platform/text/TextBreakIterator.h" |
| |
| namespace blink { |
| |
| template <typename PositionType> |
| static PositionType canonicalizeCandidate(const PositionType& candidate) |
| { |
| if (candidate.isNull()) |
| return PositionType(); |
| DCHECK(isVisuallyEquivalentCandidate(candidate)); |
| PositionType upstream = mostBackwardCaretPosition(candidate); |
| if (isVisuallyEquivalentCandidate(upstream)) |
| return upstream; |
| return candidate; |
| } |
| |
| template <typename PositionType> |
| static PositionType canonicalPosition(const PositionType& passedPosition) |
| { |
| // Sometimes updating selection positions can be extremely expensive and |
| // occur frequently. Often calling preventDefault on mousedown events can |
| // avoid doing unnecessary text selection work. http://crbug.com/472258. |
| TRACE_EVENT0("input", "VisibleUnits::canonicalPosition"); |
| |
| // The updateLayout call below can do so much that even the position passed |
| // in to us might get changed as a side effect. Specifically, there are code |
| // paths that pass selection endpoints, and updateLayout can change the |
| // selection. |
| PositionType position = passedPosition; |
| |
| // FIXME (9535): Canonicalizing to the leftmost candidate means that if |
| // we're at a line wrap, we will ask layoutObjects to paint downstream |
| // carets for other layoutObjects. To fix this, we need to either a) add |
| // code to all paintCarets to pass the responsibility off to the appropriate |
| // layoutObject for VisiblePosition's like these, or b) canonicalize to the |
| // rightmost candidate unless the affinity is upstream. |
| if (position.isNull()) |
| return PositionType(); |
| |
| DCHECK(position.document()); |
| position.document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| Node* node = position.computeContainerNode(); |
| |
| PositionType candidate = mostBackwardCaretPosition(position); |
| if (isVisuallyEquivalentCandidate(candidate)) |
| return candidate; |
| candidate = mostForwardCaretPosition(position); |
| if (isVisuallyEquivalentCandidate(candidate)) |
| return candidate; |
| |
| // When neither upstream or downstream gets us to a candidate |
| // (upstream/downstream won't leave blocks or enter new ones), we search |
| // forward and backward until we find one. |
| PositionType next = canonicalizeCandidate(nextCandidate(position)); |
| PositionType prev = canonicalizeCandidate(previousCandidate(position)); |
| Node* nextNode = next.anchorNode(); |
| Node* prevNode = prev.anchorNode(); |
| |
| // The new position must be in the same editable element. Enforce that |
| // first. Unless the descent is from a non-editable html element to an |
| // editable body. |
| if (node && node->document().documentElement() == node && !hasEditableStyle(*node) && node->document().body() && hasEditableStyle(*node->document().body())) |
| return next.isNotNull() ? next : prev; |
| |
| Element* editingRoot = rootEditableElementOf(position); |
| |
| // If the html element is editable, descending into its body will look like |
| // a descent from non-editable to editable content since |
| // |rootEditableElementOf()| always stops at the body. |
| if ((editingRoot && editingRoot->document().documentElement() == editingRoot) || position.anchorNode()->isDocumentNode()) |
| return next.isNotNull() ? next : prev; |
| |
| bool prevIsInSameEditableElement = prevNode && rootEditableElementOf(prev) == editingRoot; |
| bool nextIsInSameEditableElement = nextNode && rootEditableElementOf(next) == editingRoot; |
| if (prevIsInSameEditableElement && !nextIsInSameEditableElement) |
| return prev; |
| |
| if (nextIsInSameEditableElement && !prevIsInSameEditableElement) |
| return next; |
| |
| if (!nextIsInSameEditableElement && !prevIsInSameEditableElement) |
| return PositionType(); |
| |
| // The new position should be in the same block flow element. Favor that. |
| Element* originalBlock = node ? enclosingBlockFlowElement(*node) : 0; |
| bool nextIsOutsideOriginalBlock = !nextNode->isDescendantOf(originalBlock) && nextNode != originalBlock; |
| bool prevIsOutsideOriginalBlock = !prevNode->isDescendantOf(originalBlock) && prevNode != originalBlock; |
| if (nextIsOutsideOriginalBlock && !prevIsOutsideOriginalBlock) |
| return prev; |
| |
| return next; |
| } |
| |
| Position canonicalPositionOf(const Position& position) |
| { |
| return canonicalPosition(position); |
| } |
| |
| PositionInFlatTree canonicalPositionOf(const PositionInFlatTree& position) |
| { |
| return canonicalPosition(position); |
| } |
| |
| template <typename Strategy> |
| static PositionWithAffinityTemplate<Strategy> honorEditingBoundaryAtOrBefore(const PositionWithAffinityTemplate<Strategy>& pos, const PositionTemplate<Strategy>& anchor) |
| { |
| if (pos.isNull()) |
| return pos; |
| |
| ContainerNode* highestRoot = highestEditableRoot(anchor); |
| |
| // Return empty position if |pos| is not somewhere inside the editable |
| // region containing this position |
| if (highestRoot && !pos.position().anchorNode()->isDescendantOf(highestRoot)) |
| return PositionWithAffinityTemplate<Strategy>(); |
| |
| // Return |pos| itself if the two are from the very same editable region, or |
| // both are non-editable |
| // TODO(yosin) In the non-editable case, just because the new position is |
| // non-editable doesn't mean movement to it is allowed. |
| // |VisibleSelection::adjustForEditableContent()| has this problem too. |
| if (highestEditableRoot(pos.position()) == highestRoot) |
| return pos; |
| |
| // Return empty position if this position is non-editable, but |pos| is |
| // editable. |
| // TODO(yosin) Move to the previous non-editable region. |
| if (!highestRoot) |
| return PositionWithAffinityTemplate<Strategy>(); |
| |
| // Return the last position before |pos| that is in the same editable region |
| // as this position |
| return lastEditablePositionBeforePositionInRoot(pos.position(), *highestRoot); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> honorEditingBoundaryAtOrBefore(const VisiblePositionTemplate<Strategy>& pos, const PositionTemplate<Strategy>& anchor) |
| { |
| return createVisiblePosition(honorEditingBoundaryAtOrBefore(pos.toPositionWithAffinity(), anchor)); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> honorEditingBoundaryAtOrAfter(const VisiblePositionTemplate<Strategy>& pos, const PositionTemplate<Strategy>& anchor) |
| { |
| if (pos.isNull()) |
| return pos; |
| |
| ContainerNode* highestRoot = highestEditableRoot(anchor); |
| |
| // Return empty position if |pos| is not somewhere inside the editable |
| // region containing this position |
| if (highestRoot && !pos.deepEquivalent().anchorNode()->isDescendantOf(highestRoot)) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| // Return |pos| itself if the two are from the very same editable region, or |
| // both are non-editable |
| // TODO(yosin) In the non-editable case, just because the new position is |
| // non-editable doesn't mean movement to it is allowed. |
| // |VisibleSelection::adjustForEditableContent()| has this problem too. |
| if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) |
| return pos; |
| |
| // Return empty position if this position is non-editable, but |pos| is |
| // editable. |
| // TODO(yosin) Move to the next non-editable region. |
| if (!highestRoot) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| // Return the next position after |pos| that is in the same editable region |
| // as this position |
| return firstEditableVisiblePositionAfterPositionInRoot(pos.deepEquivalent(), *highestRoot); |
| } |
| |
| static bool hasEditableStyle(const Node& node, EditableType editableType) |
| { |
| if (editableType == HasEditableAXRole) { |
| if (AXObjectCache* cache = node.document().existingAXObjectCache()) { |
| if (cache->rootAXEditableElement(&node)) |
| return true; |
| } |
| } |
| |
| return hasEditableStyle(node); |
| } |
| |
| static Element* rootEditableElement(const Node& node, EditableType editableType) |
| { |
| if (editableType == HasEditableAXRole) { |
| if (AXObjectCache* cache = node.document().existingAXObjectCache()) |
| return const_cast<Element*>(cache->rootAXEditableElement(&node)); |
| } |
| |
| return rootEditableElement(node); |
| } |
| |
| static Element* rootAXEditableElementOf(const Position& position) |
| { |
| Node* node = position.computeContainerNode(); |
| if (!node) |
| return 0; |
| |
| if (isDisplayInsideTable(node)) |
| node = node->parentNode(); |
| |
| return rootEditableElement(*node, HasEditableAXRole); |
| } |
| |
| static bool hasAXEditableStyle(const Node& node) |
| { |
| return hasEditableStyle(node, HasEditableAXRole); |
| } |
| |
| static ContainerNode* highestEditableRoot(const Position& position, EditableType editableType) |
| { |
| if (editableType == HasEditableAXRole) |
| return highestEditableRoot(position, rootAXEditableElementOf, hasAXEditableStyle); |
| |
| return highestEditableRoot(position); |
| } |
| |
| static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) |
| { |
| bool editable = hasEditableStyle(*node, editableType); |
| node = previousAtomicLeafNode(*node); |
| while (node) { |
| if (editable == hasEditableStyle(*node, editableType)) |
| return node; |
| node = previousAtomicLeafNode(*node); |
| } |
| return 0; |
| } |
| |
| static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) |
| { |
| if (!node) |
| return 0; |
| |
| bool editable = hasEditableStyle(*node, editableType); |
| node = nextAtomicLeafNode(*node); |
| while (node) { |
| if (editable == hasEditableStyle(*node, editableType)) |
| return node; |
| node = nextAtomicLeafNode(*node); |
| } |
| return 0; |
| } |
| |
| // FIXME: consolidate with code in previousLinePosition. |
| static Position previousRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
| { |
| ContainerNode* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
| Node* previousNode = previousLeafWithSameEditability(node, editableType); |
| |
| while (previousNode && (!previousNode->layoutObject() || inSameLine(createVisiblePosition(firstPositionInOrBeforeNode(previousNode)), visiblePosition))) |
| previousNode = previousLeafWithSameEditability(previousNode, editableType); |
| |
| while (previousNode && !previousNode->isShadowRoot()) { |
| if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), editableType) != highestRoot) |
| break; |
| |
| Position pos = isHTMLBRElement(*previousNode) ? Position::beforeNode(previousNode) : |
| Position::editingPositionOf(previousNode, caretMaxOffset(previousNode)); |
| |
| if (isVisuallyEquivalentCandidate(pos)) |
| return pos; |
| |
| previousNode = previousLeafWithSameEditability(previousNode, editableType); |
| } |
| return Position(); |
| } |
| |
| static Position nextRootInlineBoxCandidatePosition(Node* node, const VisiblePosition& visiblePosition, EditableType editableType) |
| { |
| ContainerNode* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), editableType); |
| Node* nextNode = nextLeafWithSameEditability(node, editableType); |
| while (nextNode && (!nextNode->layoutObject() || inSameLine(createVisiblePosition(firstPositionInOrBeforeNode(nextNode)), visiblePosition))) |
| nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); |
| |
| while (nextNode && !nextNode->isShadowRoot()) { |
| if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), editableType) != highestRoot) |
| break; |
| |
| Position pos; |
| pos = Position::editingPositionOf(nextNode, caretMinOffset(nextNode)); |
| |
| if (isVisuallyEquivalentCandidate(pos)) |
| 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->getLineLayoutItem().nonPseudoNode(); |
| if (!startNode) |
| break; |
| |
| Position position = previousRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
| if (position.isNull()) |
| break; |
| |
| RenderedPosition renderedPosition(position, TextAffinity::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->getLineLayoutItem().nonPseudoNode(); |
| if (!startNode) |
| break; |
| |
| Position position = nextRootInlineBoxCandidatePosition(startNode, visiblePosition, ContentIsEditable); |
| if (position.isNull()) |
| break; |
| |
| RenderedPosition renderedPosition(position, TextAffinity::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->getLineLayoutItem().text().appendTo(string, previousBox->start(), previousBoxLength); |
| len += previousBoxLength; |
| } |
| textBox->getLineLayoutItem().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->getLineLayoutItem().text().appendTo(string, textBox->start(), textBox->len()); |
| len += textBox->len(); |
| if (nextBox) { |
| nextBox->getLineLayoutItem().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 ? rightPositionOf(current) : leftPositionOf(current); |
| if (adjacentCharacterPosition.deepEquivalent() == current.deepEquivalent() || adjacentCharacterPosition.isNull()) |
| return VisiblePosition(); |
| |
| InlineBoxPosition boxPosition = computeInlineBoxPosition(adjacentCharacterPosition.deepEquivalent(), TextAffinity::Upstream); |
| InlineBox* box = boxPosition.inlineBox; |
| int offsetInBox = boxPosition.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->getLineLayoutItem().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 logicalStartInLayoutObject = offsetInBox == static_cast<int>(textBox->start()) && previousBoxInDifferentBlock; |
| isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInLayoutObject); |
| } else { |
| bool logicalEndInLayoutObject = offsetInBox == static_cast<int>(textBox->start() + textBox->len()) && nextBoxInDifferentBlock; |
| isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInLayoutObject); |
| } |
| |
| if (isWordBreak) |
| return adjacentCharacterPosition; |
| |
| current = adjacentCharacterPosition; |
| } |
| return VisiblePosition(); |
| } |
| |
| VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) |
| { |
| VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight); |
| leftWordBreak = honorEditingBoundaryAtOrBefore(leftWordBreak, visiblePosition.deepEquivalent()); |
| |
| // 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 = honorEditingBoundaryAtOrBefore(rightWordBreak, visiblePosition.deepEquivalent()); |
| |
| // 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; |
| } |
| |
| template <typename Strategy> |
| static ContainerNode* nonShadowBoundaryParentNode(Node* node) |
| { |
| ContainerNode* parent = Strategy::parent(*node); |
| return parent && !parent->isShadowRoot() ? parent : nullptr; |
| } |
| |
| template <typename Strategy> |
| static Node* parentEditingBoundary(const PositionTemplate<Strategy>& position) |
| { |
| Node* const anchorNode = position.anchorNode(); |
| if (!anchorNode) |
| return nullptr; |
| |
| Node* documentElement = anchorNode->document().documentElement(); |
| if (!documentElement) |
| return nullptr; |
| |
| Node* boundary = position.computeContainerNode(); |
| while (boundary != documentElement && nonShadowBoundaryParentNode<Strategy>(boundary) && hasEditableStyle(*anchorNode) == hasEditableStyle(*Strategy::parent(*boundary))) |
| boundary = nonShadowBoundaryParentNode<Strategy>(boundary); |
| |
| return boundary; |
| } |
| |
| enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; |
| |
| typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> previousBoundary(const VisiblePositionTemplate<Strategy>& c, BoundarySearchFunction searchFunction) |
| { |
| const PositionTemplate<Strategy> pos = c.deepEquivalent(); |
| Node* boundary = parentEditingBoundary(pos); |
| if (!boundary) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| const PositionTemplate<Strategy> start = PositionTemplate<Strategy>::editingPositionOf(boundary, 0).parentAnchoredEquivalent(); |
| const PositionTemplate<Strategy> end = pos.parentAnchoredEquivalent(); |
| |
| ForwardsTextBuffer suffixString; |
| if (requiresContextForWordBoundary(characterBefore(c))) { |
| TextIteratorAlgorithm<Strategy> forwardsIterator(end, PositionTemplate<Strategy>::afterNode(boundary)); |
| while (!forwardsIterator.atEnd()) { |
| forwardsIterator.copyTextTo(&suffixString); |
| int contextEndIndex = endOfFirstWordBoundaryContext(suffixString.data() + suffixString.size() - forwardsIterator.length(), forwardsIterator.length()); |
| if (contextEndIndex < forwardsIterator.length()) { |
| suffixString.shrink(forwardsIterator.length() - contextEndIndex); |
| break; |
| } |
| forwardsIterator.advance(); |
| } |
| } |
| |
| unsigned suffixLength = suffixString.size(); |
| BackwardsTextBuffer string; |
| string.pushRange(suffixString.data(), suffixString.size()); |
| |
| SimplifiedBackwardsTextIteratorAlgorithm<Strategy> it(start, end); |
| int remainingLength = 0; |
| unsigned next = 0; |
| bool needMoreContext = false; |
| while (!it.atEnd()) { |
| bool inTextSecurityMode = it.isInTextSecurityMode(); |
| // iterate to get chunks until the searchFunction returns a non-zero |
| // value. |
| if (!inTextSecurityMode) { |
| int runOffset = 0; |
| do { |
| runOffset += it.copyTextTo(&string, runOffset, string.capacity()); |
| // TODO(xiaochengh): The following line takes O(string.size()) time, |
| // which makes quadratic overall running time in the worst case. |
| // Should improve it in some way. |
| next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); |
| } while (!next && runOffset < it.length()); |
| if (next) { |
| remainingLength = it.length() - runOffset; |
| break; |
| } |
| } else { |
| // Treat bullets used in the text security mode as regular |
| // characters when looking for boundaries |
| string.pushCharacters('x', it.length()); |
| next = 0; |
| } |
| 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. |
| // TODO(xiaochengh): Do we have to search the whole string? |
| next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); |
| DCHECK(!needMoreContext); |
| } |
| |
| if (!next) |
| return createVisiblePosition(it.atEnd() ? it.startPosition() : pos); |
| |
| Node* node = it.startContainer(); |
| int boundaryOffset = remainingLength + next; |
| if (node->isTextNode() && boundaryOffset <= node->maxCharacterOffset()) { |
| // The next variable contains a usable index into a text node |
| return createVisiblePosition(PositionTemplate<Strategy>(node, boundaryOffset)); |
| } |
| |
| // Use the character iterator to translate the next value into a DOM |
| // position. |
| BackwardsCharacterIteratorAlgorithm<Strategy> charIt(start, end); |
| charIt.advance(string.size() - suffixLength - next); |
| // TODO(yosin) charIt can get out of shadow host. |
| return createVisiblePosition(charIt.endPosition()); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> nextBoundary(const VisiblePositionTemplate<Strategy>& c, BoundarySearchFunction searchFunction) |
| { |
| PositionTemplate<Strategy> pos = c.deepEquivalent(); |
| Node* boundary = parentEditingBoundary(pos); |
| if (!boundary) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| Document& d = boundary->document(); |
| const PositionTemplate<Strategy> start(pos.parentAnchoredEquivalent()); |
| |
| BackwardsTextBuffer prefixString; |
| if (requiresContextForWordBoundary(characterAfter(c))) { |
| SimplifiedBackwardsTextIteratorAlgorithm<Strategy> backwardsIterator(PositionTemplate<Strategy>::firstPositionInNode(&d), start); |
| while (!backwardsIterator.atEnd()) { |
| backwardsIterator.copyTextTo(&prefixString); |
| int contextStartIndex = startOfLastWordBoundaryContext(prefixString.data(), backwardsIterator.length()); |
| if (contextStartIndex > 0) { |
| prefixString.shrink(contextStartIndex); |
| break; |
| } |
| backwardsIterator.advance(); |
| } |
| } |
| |
| unsigned prefixLength = prefixString.size(); |
| ForwardsTextBuffer string; |
| string.pushRange(prefixString.data(), prefixString.size()); |
| |
| const PositionTemplate<Strategy> searchStart = PositionTemplate<Strategy>::editingPositionOf(start.anchorNode(), start.offsetInContainerNode()); |
| const PositionTemplate<Strategy> searchEnd = PositionTemplate<Strategy>::lastPositionInNode(boundary); |
| TextIteratorAlgorithm<Strategy> it(searchStart, searchEnd, TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
| const unsigned invalidOffset = static_cast<unsigned>(-1); |
| unsigned next = invalidOffset; |
| unsigned offset = prefixLength; |
| 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.isInTextSecurityMode(); |
| if (!inTextSecurityMode) { |
| int runOffset = 0; |
| do { |
| runOffset += it.copyTextTo(&string, runOffset, string.capacity()); |
| next = searchFunction(string.data(), string.size(), offset, MayHaveMoreContext, needMoreContext); |
| if (!needMoreContext) { |
| // When the search does not need more context, skip all examined |
| // characters except the last one, in case it is a boundary. |
| offset = string.size(); |
| U16_BACK_1(string.data(), 0, offset); |
| } |
| } while (next == string.size() && runOffset < it.length()); |
| if (next != string.size()) |
| break; |
| } else { |
| // Treat bullets used in the text security mode as regular |
| // characters when looking for boundaries |
| string.pushCharacters('x', it.length()); |
| next = string.size(); |
| } |
| 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. |
| // TODO(xiaochengh): Do we still have to search the whole string? |
| next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); |
| DCHECK(!needMoreContext); |
| } |
| |
| if (it.atEnd() && next == string.size()) { |
| pos = it.startPositionInCurrentContainer(); |
| } else if (next != invalidOffset && next != prefixLength) { |
| // TODO(dglazkov): The use of updateStyleAndLayoutIgnorePendingStylesheets needs to be audited. |
| // see http://crbug.com/590369 for more details. |
| searchStart.document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| // Use the character iterator to translate the next value into a DOM |
| // position. |
| CharacterIteratorAlgorithm<Strategy> charIt(searchStart, searchEnd, TextIteratorEmitsCharactersBetweenAllVisiblePositions); |
| charIt.advance(next - prefixLength - 1); |
| pos = charIt.endPosition(); |
| |
| if (charIt.characterAt(0) == '\n') { |
| // TODO(yosin) workaround for collapsed range (where only start |
| // position is correct) emitted for some emitted newlines |
| // (see rdar://5192593) |
| const VisiblePositionTemplate<Strategy> visPos = createVisiblePosition(pos); |
| if (visPos.deepEquivalent() == createVisiblePosition(charIt.startPosition()).deepEquivalent()) { |
| charIt.advance(1); |
| pos = charIt.startPosition(); |
| } |
| } |
| } |
| |
| // generate VisiblePosition, use TextAffinity::Upstream affinity if possible |
| return createVisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
| } |
| |
| // --------- |
| |
| static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| TRACE_EVENT0("blink", "startWordBoundary"); |
| DCHECK(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; |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> startOfWordAlgorithm(const VisiblePositionTemplate<Strategy>& c, EWordSide side) |
| { |
| // TODO(yosin) This returns a null VP for c at the start of the document |
| // and |side| == |LeftWordIfOnBoundary| |
| VisiblePositionTemplate<Strategy> p = c; |
| if (side == RightWordIfOnBoundary) { |
| // at paragraph end, the startofWord is the current position |
| if (isEndOfParagraph(c)) |
| return c; |
| |
| p = nextPositionOf(c); |
| if (p.isNull()) |
| return c; |
| } |
| return previousBoundary(p, startWordBoundary); |
| } |
| |
| VisiblePosition startOfWord(const VisiblePosition& c, EWordSide side) |
| { |
| return startOfWordAlgorithm<EditingStrategy>(c, side); |
| } |
| |
| VisiblePositionInFlatTree startOfWord(const VisiblePositionInFlatTree& c, EWordSide side) |
| { |
| return startOfWordAlgorithm<EditingInFlatTreeStrategy>(c, side); |
| } |
| |
| static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) |
| { |
| DCHECK_LE(offset, length); |
| if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) { |
| needMoreContext = true; |
| return length; |
| } |
| needMoreContext = false; |
| return findWordEndBoundary(characters, length, offset); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endOfWordAlgorithm(const VisiblePositionTemplate<Strategy>& c, EWordSide side) |
| { |
| VisiblePositionTemplate<Strategy> p = c; |
| if (side == LeftWordIfOnBoundary) { |
| if (isStartOfParagraph(c)) |
| return c; |
| |
| p = previousPositionOf(c); |
| if (p.isNull()) |
| return c; |
| } else if (isEndOfParagraph(c)) { |
| return c; |
| } |
| |
| return nextBoundary(p, endWordBoundary); |
| } |
| |
| VisiblePosition endOfWord(const VisiblePosition& c, EWordSide side) |
| { |
| return endOfWordAlgorithm<EditingStrategy>(c, side); |
| } |
| |
| VisiblePositionInFlatTree endOfWord(const VisiblePositionInFlatTree& c, EWordSide side) |
| { |
| return endOfWordAlgorithm<EditingInFlatTreeStrategy>(c, side); |
| } |
| |
| 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 honorEditingBoundaryAtOrBefore(prev, c.deepEquivalent()); |
| } |
| |
| 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 honorEditingBoundaryAtOrAfter(next, c.deepEquivalent()); |
| } |
| |
| // --------- |
| |
| enum LineEndpointComputationMode { UseLogicalOrdering, UseInlineBoxOrdering }; |
| template <typename Strategy> |
| static PositionWithAffinityTemplate<Strategy> startPositionForLine(const PositionWithAffinityTemplate<Strategy>& c, LineEndpointComputationMode mode) |
| { |
| if (c.isNull()) |
| return PositionWithAffinityTemplate<Strategy>(); |
| |
| RootInlineBox* rootBox = RenderedPosition(c.position(), c.affinity()).rootBox(); |
| if (!rootBox) { |
| // There are VisiblePositions at offset 0 in blocks without |
| // RootInlineBoxes, like empty editable blocks and bordered blocks. |
| PositionTemplate<Strategy> p = c.position(); |
| if (p.anchorNode()->layoutObject() && p.anchorNode()->layoutObject()->isLayoutBlock() && !p.computeEditingOffset()) |
| return c; |
| |
| return PositionWithAffinityTemplate<Strategy>(); |
| } |
| |
| Node* startNode; |
| InlineBox* startBox; |
| if (mode == UseLogicalOrdering) { |
| startNode = rootBox->getLogicalStartBoxWithNode(startBox); |
| if (!startNode) |
| return PositionWithAffinityTemplate<Strategy>(); |
| } 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 PositionWithAffinityTemplate<Strategy>(); |
| |
| startNode = startBox->getLineLayoutItem().nonPseudoNode(); |
| if (startNode) |
| break; |
| |
| startBox = startBox->nextLeafChild(); |
| } |
| } |
| |
| return PositionWithAffinityTemplate<Strategy>(startNode->isTextNode() ? PositionTemplate<Strategy>(toText(startNode), toInlineTextBox(startBox)->start()) : PositionTemplate<Strategy>::beforeNode(startNode)); |
| } |
| |
| template <typename Strategy> |
| static PositionWithAffinityTemplate<Strategy> startOfLineAlgorithm(const PositionWithAffinityTemplate<Strategy>& c) |
| { |
| // 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. |
| PositionWithAffinityTemplate<Strategy> visPos = startPositionForLine(c, UseInlineBoxOrdering); |
| return honorEditingBoundaryAtOrBefore(visPos, c.position()); |
| } |
| |
| static PositionWithAffinity startOfLine(const PositionWithAffinity& currentPosition) |
| { |
| return startOfLineAlgorithm<EditingStrategy>(currentPosition); |
| } |
| |
| static PositionInFlatTreeWithAffinity startOfLine(const PositionInFlatTreeWithAffinity& currentPosition) |
| { |
| return startOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition); |
| } |
| |
| // FIXME: Rename this function to reflect the fact it ignores bidi levels. |
| VisiblePosition startOfLine(const VisiblePosition& currentPosition) |
| { |
| return createVisiblePosition(startOfLine(currentPosition.toPositionWithAffinity())); |
| } |
| |
| VisiblePositionInFlatTree startOfLine(const VisiblePositionInFlatTree& currentPosition) |
| { |
| return createVisiblePosition(startOfLine(currentPosition.toPositionWithAffinity())); |
| } |
| |
| template <typename Strategy> |
| static PositionWithAffinityTemplate<Strategy> logicalStartOfLineAlgorithm(const PositionWithAffinityTemplate<Strategy>& c) |
| { |
| // 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. |
| PositionWithAffinityTemplate<Strategy> visPos = startPositionForLine(c, UseLogicalOrdering); |
| |
| if (ContainerNode* editableRoot = highestEditableRoot(c.position())) { |
| if (!editableRoot->contains(visPos.position().computeContainerNode())) |
| return PositionWithAffinityTemplate<Strategy>(PositionTemplate<Strategy>::firstPositionInNode(editableRoot)); |
| } |
| |
| return honorEditingBoundaryAtOrBefore(visPos, c.position()); |
| } |
| |
| VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition) |
| { |
| return createVisiblePosition(logicalStartOfLineAlgorithm<EditingStrategy>(currentPosition.toPositionWithAffinity())); |
| } |
| |
| VisiblePositionInFlatTree logicalStartOfLine(const VisiblePositionInFlatTree& currentPosition) |
| { |
| return createVisiblePosition(logicalStartOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition.toPositionWithAffinity())); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endPositionForLine(const VisiblePositionTemplate<Strategy>& c, LineEndpointComputationMode mode) |
| { |
| if (c.isNull()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| RootInlineBox* rootBox = RenderedPosition(c).rootBox(); |
| if (!rootBox) { |
| // There are VisiblePositions at offset 0 in blocks without |
| // RootInlineBoxes, like empty editable blocks and bordered blocks. |
| const PositionTemplate<Strategy> p = c.deepEquivalent(); |
| if (p.anchorNode()->layoutObject() && p.anchorNode()->layoutObject()->isLayoutBlock() && !p.computeEditingOffset()) |
| return c; |
| return VisiblePositionTemplate<Strategy>(); |
| } |
| |
| Node* endNode; |
| InlineBox* endBox; |
| if (mode == UseLogicalOrdering) { |
| endNode = rootBox->getLogicalEndBoxWithNode(endBox); |
| if (!endNode) |
| return VisiblePositionTemplate<Strategy>(); |
| } else { |
| // Generated content (e.g. list markers and CSS :before and :after |
| // pseudo elements) 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 VisiblePositionTemplate<Strategy>(); |
| |
| endNode = endBox->getLineLayoutItem().nonPseudoNode(); |
| if (endNode) |
| break; |
| |
| endBox = endBox->prevLeafChild(); |
| } |
| } |
| |
| PositionTemplate<Strategy> pos; |
| if (isHTMLBRElement(*endNode)) { |
| pos = PositionTemplate<Strategy>::beforeNode(endNode); |
| } else if (endBox->isInlineTextBox() && endNode->isTextNode()) { |
| InlineTextBox* endTextBox = toInlineTextBox(endBox); |
| int endOffset = endTextBox->start(); |
| if (!endTextBox->isLineBreak()) |
| endOffset += endTextBox->len(); |
| pos = PositionTemplate<Strategy>(toText(endNode), endOffset); |
| } else { |
| pos = PositionTemplate<Strategy>::afterNode(endNode); |
| } |
| |
| return createVisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); |
| } |
| |
| // TODO(yosin) Rename this function to reflect the fact it ignores bidi levels. |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& currentPosition) |
| { |
| // TODO(yosin) 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. |
| VisiblePositionTemplate<Strategy> visPos = endPositionForLine(currentPosition, UseInlineBoxOrdering); |
| |
| // 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(currentPosition, visPos)) { |
| visPos = previousPositionOf(currentPosition); |
| if (visPos.isNull()) |
| return VisiblePositionTemplate<Strategy>(); |
| visPos = endPositionForLine(visPos, UseInlineBoxOrdering); |
| } |
| |
| return honorEditingBoundaryAtOrAfter(visPos, currentPosition.deepEquivalent()); |
| } |
| |
| // TODO(yosin) Rename this function to reflect the fact it ignores bidi levels. |
| VisiblePosition endOfLine(const VisiblePosition& currentPosition) |
| { |
| return endOfLineAlgorithm<EditingStrategy>(currentPosition); |
| } |
| |
| VisiblePositionInFlatTree endOfLine(const VisiblePositionInFlatTree& currentPosition) |
| { |
| return endOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition); |
| } |
| |
| template <typename Strategy> |
| static bool inSameLogicalLine(const VisiblePositionTemplate<Strategy>& a, const VisiblePositionTemplate<Strategy>& b) |
| { |
| return a.isNotNull() && logicalStartOfLine(a).deepEquivalent() == logicalStartOfLine(b).deepEquivalent(); |
| } |
| |
| template <typename Strategy> |
| VisiblePositionTemplate<Strategy> logicalEndOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& currentPosition) |
| { |
| // TODO(yosin) 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. |
| VisiblePositionTemplate<Strategy> visPos = endPositionForLine(currentPosition, 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">xyz |
| // a xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz </div> |
| // In this case, use the previous position of the computed logical end |
| // position. |
| if (!inSameLogicalLine(currentPosition, visPos)) |
| visPos = previousPositionOf(visPos); |
| |
| if (ContainerNode* editableRoot = highestEditableRoot(currentPosition.deepEquivalent())) { |
| if (!editableRoot->contains(visPos.deepEquivalent().computeContainerNode())) |
| return createVisiblePosition(PositionTemplate<Strategy>::lastPositionInNode(editableRoot)); |
| } |
| |
| return honorEditingBoundaryAtOrAfter(visPos, currentPosition.deepEquivalent()); |
| } |
| |
| VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition) |
| { |
| return logicalEndOfLineAlgorithm<EditingStrategy>(currentPosition); |
| } |
| |
| VisiblePositionInFlatTree logicalEndOfLine(const VisiblePositionInFlatTree& currentPosition) |
| { |
| return logicalEndOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition); |
| } |
| |
| template <typename Strategy> |
| bool inSameLineAlgorithm(const PositionWithAffinityTemplate<Strategy>& position1, const PositionWithAffinityTemplate<Strategy>& position2) |
| { |
| if (position1.isNull() || position2.isNull()) |
| return false; |
| PositionWithAffinityTemplate<Strategy> startOfLine1 = startOfLine(position1); |
| PositionWithAffinityTemplate<Strategy> startOfLine2 = startOfLine(position2); |
| if (startOfLine1 == startOfLine2) |
| return true; |
| PositionTemplate<Strategy> canonicalized1 = canonicalPositionOf(startOfLine1.position()); |
| if (canonicalized1 == startOfLine2.position()) |
| return true; |
| return canonicalized1 == canonicalPositionOf(startOfLine2.position()); |
| } |
| |
| bool inSameLine(const PositionWithAffinity& a, const PositionWithAffinity& b) |
| { |
| return inSameLineAlgorithm<EditingStrategy>(a, b); |
| } |
| |
| bool inSameLine(const PositionInFlatTreeWithAffinity& position1, const PositionInFlatTreeWithAffinity& position2) |
| { |
| return inSameLineAlgorithm<EditingInFlatTreeStrategy>(position1, position2); |
| } |
| |
| bool inSameLine(const VisiblePosition& position1, const VisiblePosition& position2) |
| { |
| return inSameLine(position1.toPositionWithAffinity(), position2.toPositionWithAffinity()); |
| } |
| |
| bool inSameLine(const VisiblePositionInFlatTree& position1, const VisiblePositionInFlatTree& position2) |
| { |
| return inSameLine(position1.toPositionWithAffinity(), position2.toPositionWithAffinity()); |
| } |
| |
| template <typename Strategy> |
| bool isStartOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& p) |
| { |
| return p.isNotNull() && p.deepEquivalent() == startOfLine(p).deepEquivalent(); |
| } |
| |
| bool isStartOfLine(const VisiblePosition& p) |
| { |
| return isStartOfLineAlgorithm<EditingStrategy>(p); |
| } |
| |
| bool isStartOfLine(const VisiblePositionInFlatTree& p) |
| { |
| return isStartOfLineAlgorithm<EditingInFlatTreeStrategy>(p); |
| } |
| |
| template <typename Strategy> |
| bool isEndOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& p) |
| { |
| return p.isNotNull() && p.deepEquivalent() == endOfLine(p).deepEquivalent(); |
| } |
| |
| bool isEndOfLine(const VisiblePosition& p) |
| { |
| return isEndOfLineAlgorithm<EditingStrategy>(p); |
| } |
| |
| bool isEndOfLine(const VisiblePositionInFlatTree& p) |
| { |
| return isEndOfLineAlgorithm<EditingInFlatTreeStrategy>(p); |
| } |
| |
| template <typename Strategy> |
| static bool isLogicalEndOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& p) |
| { |
| return p.isNotNull() && p.deepEquivalent() == logicalEndOfLine(p).deepEquivalent(); |
| } |
| |
| bool isLogicalEndOfLine(const VisiblePosition& p) |
| { |
| return isLogicalEndOfLineAlgorithm<EditingStrategy>(p); |
| } |
| |
| bool isLogicalEndOfLine(const VisiblePositionInFlatTree& p) |
| { |
| return isLogicalEndOfLineAlgorithm<EditingInFlatTreeStrategy>(p); |
| } |
| |
| static inline LayoutPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, LayoutUnit lineDirectionPoint) |
| { |
| DCHECK(root); |
| LineLayoutBlockFlow containingBlock = root->block(); |
| FloatPoint absoluteBlockPoint = containingBlock.localToAbsolute(FloatPoint()); |
| if (containingBlock.hasOverflowClip()) |
| absoluteBlockPoint -= FloatSize(containingBlock.scrolledContentOffset()); |
| |
| if (root->block().isHorizontalWritingMode()) |
| return LayoutPoint(LayoutUnit(lineDirectionPoint - absoluteBlockPoint.x()), root->blockDirectionPointInLine()); |
| |
| return LayoutPoint(root->blockDirectionPointInLine(), LayoutUnit(lineDirectionPoint - absoluteBlockPoint.y())); |
| } |
| |
| VisiblePosition previousLinePosition(const VisiblePosition& visiblePosition, LayoutUnit lineDirectionPoint, EditableType editableType) |
| { |
| Position p = visiblePosition.deepEquivalent(); |
| Node* node = p.anchorNode(); |
| |
| if (!node) |
| return VisiblePosition(); |
| |
| node->document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| LayoutObject* layoutObject = node->layoutObject(); |
| if (!layoutObject) |
| return VisiblePosition(); |
| |
| RootInlineBox* root = 0; |
| InlineBox* box = computeInlineBoxPosition(visiblePosition).inlineBox; |
| 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((createVisiblePosition(position))); |
| root = renderedPosition.rootBox(); |
| if (!root) |
| return createVisiblePosition(position); |
| } |
| } |
| |
| if (root) { |
| // FIXME: Can be wrong for multi-column layout and with transforms. |
| LayoutPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); |
| LineLayoutItem lineLayoutItem = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->getLineLayoutItem(); |
| Node* node = lineLayoutItem.node(); |
| if (node && editingIgnoresContent(node)) |
| return VisiblePosition::inParentBeforeNode(*node); |
| return createVisiblePosition(lineLayoutItem.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 = hasEditableStyle(*node, editableType) ? rootEditableElement(*node, editableType) : node->document().documentElement(); |
| if (!rootElement) |
| return VisiblePosition(); |
| return VisiblePosition::firstPositionInNode(rootElement); |
| } |
| |
| VisiblePosition nextLinePosition(const VisiblePosition& visiblePosition, LayoutUnit lineDirectionPoint, EditableType editableType) |
| { |
| Position p = visiblePosition.deepEquivalent(); |
| Node* node = p.anchorNode(); |
| |
| if (!node) |
| return VisiblePosition(); |
| |
| node->document().updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| LayoutObject* layoutObject = node->layoutObject(); |
| if (!layoutObject) |
| return VisiblePosition(); |
| |
| RootInlineBox* root = 0; |
| InlineBox* box = computeInlineBoxPosition(visiblePosition).inlineBox; |
| 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 = NodeTraversal::childAt(*node, p.computeEditingOffset()); |
| node = child ? child : &NodeTraversal::lastWithinOrSelf(*node); |
| Position position = nextRootInlineBoxCandidatePosition(node, visiblePosition, editableType); |
| if (position.isNotNull()) { |
| RenderedPosition renderedPosition((createVisiblePosition(position))); |
| root = renderedPosition.rootBox(); |
| if (!root) |
| return createVisiblePosition(position); |
| } |
| } |
| |
| if (root) { |
| // FIXME: Can be wrong for multi-column layout and with transforms. |
| LayoutPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); |
| LineLayoutItem lineLayoutItem = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->getLineLayoutItem(); |
| Node* node = lineLayoutItem.node(); |
| if (node && editingIgnoresContent(node)) |
| return VisiblePosition::inParentBeforeNode(*node); |
| return createVisiblePosition(lineLayoutItem.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 = hasEditableStyle(*node, editableType) ? rootEditableElement(*node, editableType) : node->document().documentElement(); |
| if (!rootElement) |
| return VisiblePosition(); |
| return VisiblePosition::lastPositionInNode(rootElement); |
| } |
| |
| // --------- |
| |
| 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); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> startOfSentenceAlgorithm(const VisiblePositionTemplate<Strategy>& c) |
| { |
| return previousBoundary(c, startSentenceBoundary); |
| } |
| |
| VisiblePosition startOfSentence(const VisiblePosition& c) |
| { |
| return startOfSentenceAlgorithm<EditingStrategy>(c); |
| } |
| |
| VisiblePositionInFlatTree startOfSentence(const VisiblePositionInFlatTree& c) |
| { |
| return startOfSentenceAlgorithm<EditingInFlatTreeStrategy>(c); |
| } |
| |
| static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) |
| { |
| TextBreakIterator* iterator = sentenceBreakIterator(characters, length); |
| return iterator->next(); |
| } |
| |
| // TODO(yosin) This includes the space after the punctuation that marks the end |
| // of the sentence. |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endOfSentenceAlgorithm(const VisiblePositionTemplate<Strategy>& c) |
| { |
| return nextBoundary(c, endSentenceBoundary); |
| } |
| |
| VisiblePosition endOfSentence(const VisiblePosition& c) |
| { |
| return endOfSentenceAlgorithm<EditingStrategy>(c); |
| } |
| |
| VisiblePositionInFlatTree endOfSentence(const VisiblePositionInFlatTree& c) |
| { |
| return endOfSentenceAlgorithm<EditingInFlatTreeStrategy>(c); |
| } |
| |
| 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 honorEditingBoundaryAtOrBefore(prev, c.deepEquivalent()); |
| } |
| |
| 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 honorEditingBoundaryAtOrAfter(next, c.deepEquivalent()); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> startOfParagraphAlgorithm(const PositionTemplate<Strategy>& position, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| Node* const startNode = position.anchorNode(); |
| |
| if (!startNode) |
| return PositionTemplate<Strategy>(); |
| |
| if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
| return PositionTemplate<Strategy>::beforeNode(startNode); |
| |
| Element* const startBlock = enclosingBlock(PositionTemplate<Strategy>::firstPositionInOrBeforeNode(startNode), CannotCrossEditingBoundary); |
| ContainerNode* const highestRoot = highestEditableRoot(position); |
| const bool startNodeIsEditable = hasEditableStyle(*startNode); |
| |
| Node* candidateNode = startNode; |
| PositionAnchorType candidateType = position.anchorType(); |
| int candidateOffset = position.computeEditingOffset(); |
| |
| Node* prevousNodeIterator = startNode; |
| while (prevousNodeIterator) { |
| if (boundaryCrossingRule == CannotCrossEditingBoundary && !nodeIsUserSelectAll(prevousNodeIterator) && hasEditableStyle(*prevousNodeIterator) != startNodeIsEditable) |
| break; |
| if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
| while (prevousNodeIterator && hasEditableStyle(*prevousNodeIterator) != startNodeIsEditable) |
| prevousNodeIterator = Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| if (!prevousNodeIterator || !prevousNodeIterator->isDescendantOf(highestRoot)) |
| break; |
| } |
| |
| const LayoutItem layoutItem = LayoutItem(prevousNodeIterator->layoutObject()); |
| if (layoutItem.isNull()) { |
| prevousNodeIterator = Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| continue; |
| } |
| const ComputedStyle& style = layoutItem.styleRef(); |
| if (style.visibility() != EVisibility::Visible) { |
| prevousNodeIterator = Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| continue; |
| } |
| |
| if (layoutItem.isBR() || isEnclosingBlock(prevousNodeIterator)) |
| break; |
| |
| if (layoutItem.isText() && toLayoutText(prevousNodeIterator->layoutObject())->resolvedTextLength()) { |
| SECURITY_DCHECK(prevousNodeIterator->isTextNode()); |
| if (style.preserveNewline()) { |
| LayoutText* text = toLayoutText(prevousNodeIterator->layoutObject()); |
| int index = text->textLength(); |
| if (prevousNodeIterator == startNode && candidateOffset < index) |
| index = max(0, candidateOffset); |
| while (--index >= 0) { |
| if ((*text)[index] == '\n') |
| return PositionTemplate<Strategy>(toText(prevousNodeIterator), index + 1); |
| } |
| } |
| candidateNode = prevousNodeIterator; |
| candidateType = PositionAnchorType::OffsetInAnchor; |
| candidateOffset = 0; |
| prevousNodeIterator = Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| } else if (editingIgnoresContent(prevousNodeIterator) || isDisplayInsideTable(prevousNodeIterator)) { |
| candidateNode = prevousNodeIterator; |
| candidateType = PositionAnchorType::BeforeAnchor; |
| prevousNodeIterator = prevousNodeIterator->previousSibling() ? prevousNodeIterator->previousSibling() : Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| } else { |
| prevousNodeIterator = Strategy::previousPostOrder(*prevousNodeIterator, startBlock); |
| } |
| } |
| |
| if (candidateType == PositionAnchorType::OffsetInAnchor) |
| return PositionTemplate<Strategy>(candidateNode, candidateOffset); |
| |
| return PositionTemplate<Strategy>(candidateNode, candidateType); |
| } |
| |
| template <typename Strategy> |
| VisiblePositionTemplate<Strategy> startOfParagraphAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return createVisiblePosition(startOfParagraphAlgorithm(visiblePosition.deepEquivalent(), boundaryCrossingRule)); |
| } |
| |
| VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return startOfParagraphAlgorithm<EditingStrategy>(c, boundaryCrossingRule); |
| } |
| |
| VisiblePositionInFlatTree startOfParagraph(const VisiblePositionInFlatTree& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return startOfParagraphAlgorithm<EditingInFlatTreeStrategy>(c, boundaryCrossingRule); |
| } |
| |
| template <typename Strategy> |
| static PositionTemplate<Strategy> endOfParagraphAlgorithm(const PositionTemplate<Strategy>& position, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| Node* const startNode = position.anchorNode(); |
| |
| if (!startNode) |
| return PositionTemplate<Strategy>(); |
| |
| if (isRenderedAsNonInlineTableImageOrHR(startNode)) |
| return PositionTemplate<Strategy>::afterNode(startNode); |
| |
| Element* const startBlock = enclosingBlock(PositionTemplate<Strategy>::firstPositionInOrBeforeNode(startNode), CannotCrossEditingBoundary); |
| ContainerNode* const highestRoot = highestEditableRoot(position); |
| const bool startNodeIsEditable = hasEditableStyle(*startNode); |
| |
| Node* candidateNode = startNode; |
| PositionAnchorType candidateType = position.anchorType(); |
| int candidateOffset = position.computeEditingOffset(); |
| |
| Node* nextNodeItreator = startNode; |
| while (nextNodeItreator) { |
| if (boundaryCrossingRule == CannotCrossEditingBoundary && !nodeIsUserSelectAll(nextNodeItreator) && hasEditableStyle(*nextNodeItreator) != startNodeIsEditable) |
| break; |
| if (boundaryCrossingRule == CanSkipOverEditingBoundary) { |
| while (nextNodeItreator && hasEditableStyle(*nextNodeItreator) != startNodeIsEditable) |
| nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock); |
| if (!nextNodeItreator || !nextNodeItreator->isDescendantOf(highestRoot)) |
| break; |
| } |
| |
| LayoutObject* const layoutObject = nextNodeItreator->layoutObject(); |
| if (!layoutObject) { |
| nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock); |
| continue; |
| } |
| const ComputedStyle& style = layoutObject->styleRef(); |
| if (style.visibility() != EVisibility::Visible) { |
| nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock); |
| continue; |
| } |
| |
| if (layoutObject->isBR() || isEnclosingBlock(nextNodeItreator)) |
| break; |
| |
| // FIXME: We avoid returning a position where the layoutObject can't accept the caret. |
| if (layoutObject->isText() && toLayoutText(layoutObject)->resolvedTextLength()) { |
| SECURITY_DCHECK(nextNodeItreator->isTextNode()); |
| LayoutText* const text = toLayoutText(layoutObject); |
| if (style.preserveNewline()) { |
| const int length = toLayoutText(layoutObject)->textLength(); |
| for (int i = (nextNodeItreator == startNode ? candidateOffset : 0); i < length; ++i) { |
| if ((*text)[i] == '\n') |
| return PositionTemplate<Strategy>(toText(nextNodeItreator), i + text->textStartOffset()); |
| } |
| } |
| |
| candidateNode = nextNodeItreator; |
| candidateType = PositionAnchorType::OffsetInAnchor; |
| candidateOffset = layoutObject->caretMaxOffset() + text->textStartOffset(); |
| nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock); |
| } else if (Strategy::editingIgnoresContent(nextNodeItreator) || isDisplayInsideTable(nextNodeItreator)) { |
| candidateNode = nextNodeItreator; |
| candidateType = PositionAnchorType::AfterAnchor; |
| nextNodeItreator = Strategy::nextSkippingChildren(*nextNodeItreator, startBlock); |
| } else { |
| nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock); |
| } |
| } |
| |
| if (candidateType == PositionAnchorType::OffsetInAnchor) |
| return PositionTemplate<Strategy>(candidateNode, candidateOffset); |
| |
| return PositionTemplate<Strategy>(candidateNode, candidateType); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endOfParagraphAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return createVisiblePosition(endOfParagraphAlgorithm(visiblePosition.deepEquivalent(), boundaryCrossingRule)); |
| } |
| |
| VisiblePosition endOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return endOfParagraphAlgorithm<EditingStrategy>(c, boundaryCrossingRule); |
| } |
| |
| VisiblePositionInFlatTree endOfParagraph(const VisiblePositionInFlatTree& c, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return endOfParagraphAlgorithm<EditingInFlatTreeStrategy>(c, boundaryCrossingRule); |
| } |
| |
| // FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true |
| VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) |
| { |
| VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary)); |
| VisiblePosition afterParagraphEnd(nextPositionOf(paragraphEnd, CannotCrossEditingBoundary)); |
| // The position after the last position in the last cell of a table |
| // is not the start of the next paragraph. |
| if (tableElementJustBefore(afterParagraphEnd)) |
| return nextPositionOf(afterParagraphEnd, CannotCrossEditingBoundary); |
| return afterParagraphEnd; |
| } |
| |
| bool inSameParagraph(const VisiblePosition& a, const VisiblePosition& b, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule).deepEquivalent() == startOfParagraph(b, boundaryCrossingRule).deepEquivalent(); |
| } |
| |
| template <typename Strategy> |
| static bool isStartOfParagraphAlgorithm(const VisiblePositionTemplate<Strategy>& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return pos.isNotNull() && pos.deepEquivalent() == startOfParagraph(pos, boundaryCrossingRule).deepEquivalent(); |
| } |
| |
| bool isStartOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return isStartOfParagraphAlgorithm<EditingStrategy>(pos, boundaryCrossingRule); |
| } |
| |
| bool isStartOfParagraph(const VisiblePositionInFlatTree& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return isStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(pos, boundaryCrossingRule); |
| } |
| |
| template <typename Strategy> |
| static bool isEndOfParagraphAlgorithm(const VisiblePositionTemplate<Strategy>& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return pos.isNotNull() && pos.deepEquivalent() == endOfParagraph(pos, boundaryCrossingRule).deepEquivalent(); |
| } |
| |
| bool isEndOfParagraph(const VisiblePosition& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return isEndOfParagraphAlgorithm<EditingStrategy>(pos, boundaryCrossingRule); |
| } |
| |
| bool isEndOfParagraph(const VisiblePositionInFlatTree& pos, EditingBoundaryCrossingRule boundaryCrossingRule) |
| { |
| return isEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(pos, boundaryCrossingRule); |
| } |
| |
| VisiblePosition previousParagraphPosition(const VisiblePosition& p, LayoutUnit x) |
| { |
| VisiblePosition pos = p; |
| do { |
| VisiblePosition n = previousLinePosition(pos, x); |
| if (n.isNull() || n.deepEquivalent() == pos.deepEquivalent()) |
| break; |
| pos = n; |
| } while (inSameParagraph(p, pos)); |
| return pos; |
| } |
| |
| VisiblePosition nextParagraphPosition(const VisiblePosition& p, LayoutUnit x) |
| { |
| VisiblePosition pos = p; |
| do { |
| VisiblePosition n = nextLinePosition(pos, x); |
| if (n.isNull() || n.deepEquivalent() == pos.deepEquivalent()) |
| break; |
| pos = n; |
| } while (inSameParagraph(p, pos)); |
| return pos; |
| } |
| |
| // --------- |
| |
| VisiblePosition startOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| Position position = visiblePosition.deepEquivalent(); |
| Element* startBlock = position.computeContainerNode() ? enclosingBlock(position.computeContainerNode(), rule) : 0; |
| return startBlock ? VisiblePosition::firstPositionInNode(startBlock) : VisiblePosition(); |
| } |
| |
| VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| Position position = visiblePosition.deepEquivalent(); |
| Element* endBlock = position.computeContainerNode() ? enclosingBlock(position.computeContainerNode(), rule) : 0; |
| return endBlock ? VisiblePosition::lastPositionInNode(endBlock) : VisiblePosition(); |
| } |
| |
| bool inSameBlock(const VisiblePosition& a, const VisiblePosition& b) |
| { |
| return !a.isNull() && enclosingBlock(a.deepEquivalent().computeContainerNode()) == enclosingBlock(b.deepEquivalent().computeContainerNode()); |
| } |
| |
| bool isStartOfBlock(const VisiblePosition& pos) |
| { |
| return pos.isNotNull() && pos.deepEquivalent() == startOfBlock(pos, CanCrossEditingBoundary).deepEquivalent(); |
| } |
| |
| bool isEndOfBlock(const VisiblePosition& pos) |
| { |
| return pos.isNotNull() && pos.deepEquivalent() == endOfBlock(pos, CanCrossEditingBoundary).deepEquivalent(); |
| } |
| |
| // --------- |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> startOfDocumentAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| Node* node = visiblePosition.deepEquivalent().anchorNode(); |
| if (!node || !node->document().documentElement()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| return createVisiblePosition(PositionTemplate<Strategy>::firstPositionInNode(node->document().documentElement())); |
| } |
| |
| VisiblePosition startOfDocument(const VisiblePosition& c) |
| { |
| return startOfDocumentAlgorithm<EditingStrategy>(c); |
| } |
| |
| VisiblePositionInFlatTree startOfDocument(const VisiblePositionInFlatTree& c) |
| { |
| return startOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> endOfDocumentAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| Node* node = visiblePosition.deepEquivalent().anchorNode(); |
| if (!node || !node->document().documentElement()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| Element* doc = node->document().documentElement(); |
| return createVisiblePosition(PositionTemplate<Strategy>::lastPositionInNode(doc)); |
| } |
| |
| VisiblePosition endOfDocument(const VisiblePosition& c) |
| { |
| return endOfDocumentAlgorithm<EditingStrategy>(c); |
| } |
| |
| VisiblePositionInFlatTree endOfDocument(const VisiblePositionInFlatTree& c) |
| { |
| return endOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c); |
| } |
| |
| bool isStartOfDocument(const VisiblePosition& p) |
| { |
| return p.isNotNull() && previousPositionOf(p, CanCrossEditingBoundary).isNull(); |
| } |
| |
| bool isEndOfDocument(const VisiblePosition& p) |
| { |
| return p.isNotNull() && nextPositionOf(p, CanCrossEditingBoundary).isNull(); |
| } |
| |
| // --------- |
| |
| VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) |
| { |
| ContainerNode* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
| if (!highestRoot) |
| return VisiblePosition(); |
| |
| return VisiblePosition::firstPositionInNode(highestRoot); |
| } |
| |
| VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) |
| { |
| ContainerNode* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); |
| if (!highestRoot) |
| return VisiblePosition(); |
| |
| return VisiblePosition::lastPositionInNode(highestRoot); |
| } |
| |
| bool isEndOfEditableOrNonEditableContent(const VisiblePosition& position) |
| { |
| return position.isNotNull() && nextPositionOf(position).isNull(); |
| } |
| |
| // TODO(yosin) We should rename |isEndOfEditableOrNonEditableContent()| what |
| // this function does, e.g. |isLastVisiblePositionOrEndOfInnerEditor()|. |
| bool isEndOfEditableOrNonEditableContent(const VisiblePositionInFlatTree& position) |
| { |
| if (position.isNull()) |
| return false; |
| const VisiblePositionInFlatTree nextPosition = nextPositionOf(position); |
| if (nextPosition.isNull()) |
| return true; |
| // In DOM version, following condition, the last position of inner editor |
| // of INPUT/TEXTAREA element, by |nextPosition().isNull()|, because of |
| // an inner editor is an only leaf node. |
| if (!nextPosition.deepEquivalent().isAfterAnchor()) |
| return false; |
| return isHTMLTextFormControlElement(nextPosition.deepEquivalent().anchorNode()); |
| } |
| |
| 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); |
| } |
| |
| static bool isNonTextLeafChild(LayoutObject* object) |
| { |
| if (object->slowFirstChild()) |
| return false; |
| if (object->isText()) |
| return false; |
| return true; |
| } |
| |
| static InlineTextBox* searchAheadForBetterMatch(LayoutObject* layoutObject) |
| { |
| LayoutBlock* container = layoutObject->containingBlock(); |
| for (LayoutObject* next = layoutObject->nextInPreOrder(container); next; next = next->nextInPreOrder(container)) { |
| if (next->isLayoutBlock()) |
| return 0; |
| if (next->isBR()) |
| return 0; |
| if (isNonTextLeafChild(next)) |
| return 0; |
| if (next->isText()) { |
| InlineTextBox* match = 0; |
| int minOffset = INT_MAX; |
| for (InlineTextBox* box = toLayoutText(next)->firstTextBox(); box; box = box->nextTextBox()) { |
| int caretMinOffset = box->caretMinOffset(); |
| if (caretMinOffset < minOffset) { |
| match = box; |
| minOffset = caretMinOffset; |
| } |
| } |
| if (match) |
| return match; |
| } |
| } |
| return 0; |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> downstreamIgnoringEditingBoundaries(PositionTemplate<Strategy> position) |
| { |
| PositionTemplate<Strategy> lastPosition; |
| while (position != lastPosition) { |
| lastPosition = position; |
| position = mostForwardCaretPosition(position, CanCrossEditingBoundary); |
| } |
| return position; |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> upstreamIgnoringEditingBoundaries(PositionTemplate<Strategy> position) |
| { |
| PositionTemplate<Strategy> lastPosition; |
| while (position != lastPosition) { |
| lastPosition = position; |
| position = mostBackwardCaretPosition(position, CanCrossEditingBoundary); |
| } |
| return position; |
| } |
| |
| // Returns true if |inlineBox| starts different direction of embedded text ru. |
| // See [1] for details. |
| // [1] UNICODE BIDIRECTIONAL ALGORITHM, http://unicode.org/reports/tr9/ |
| static bool isStartOfDifferentDirection(const InlineBox* inlineBox) |
| { |
| InlineBox* prevBox = inlineBox->prevLeafChild(); |
| if (!prevBox) |
| return true; |
| if (prevBox->direction() == inlineBox->direction()) |
| return true; |
| DCHECK_NE(prevBox->bidiLevel(), inlineBox->bidiLevel()); |
| return prevBox->bidiLevel() > inlineBox->bidiLevel(); |
| } |
| |
| template <typename Strategy> |
| static InlineBoxPosition computeInlineBoxPositionTemplate(const PositionTemplate<Strategy>& position, TextAffinity affinity, TextDirection primaryDirection) |
| { |
| InlineBox* inlineBox = nullptr; |
| int caretOffset = position.computeEditingOffset(); |
| Node* const anchorNode = position.anchorNode(); |
| LayoutObject* layoutObject = anchorNode->isShadowRoot() ? toShadowRoot(anchorNode)->host().layoutObject() : anchorNode->layoutObject(); |
| |
| DCHECK(layoutObject) << position; |
| |
| if (!layoutObject->isText()) { |
| inlineBox = 0; |
| if (canHaveChildrenForEditing(anchorNode) && layoutObject->isLayoutBlockFlow() && hasRenderedNonAnonymousDescendantsWithHeight(layoutObject)) { |
| // Try a visually equivalent position with possibly opposite |
| // editability. This helps in case |this| is in an editable block |
| // but surrounded by non-editable positions. It acts to negate the |
| // logic at the beginning of |
| // |LayoutObject::createPositionWithAffinity()|. |
| PositionTemplate<Strategy> equivalent = downstreamIgnoringEditingBoundaries(position); |
| if (equivalent == position) { |
| equivalent = upstreamIgnoringEditingBoundaries(position); |
| if (equivalent == position || downstreamIgnoringEditingBoundaries(equivalent) == position) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| } |
| |
| return computeInlineBoxPosition(equivalent, TextAffinity::Upstream, primaryDirection); |
| } |
| if (layoutObject->isBox()) { |
| inlineBox = toLayoutBox(layoutObject)->inlineBoxWrapper(); |
| if (!inlineBox || (caretOffset > inlineBox->caretMinOffset() && caretOffset < inlineBox->caretMaxOffset())) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| } |
| } else { |
| LayoutText* textLayoutObject = toLayoutText(layoutObject); |
| |
| InlineTextBox* box; |
| InlineTextBox* candidate = 0; |
| |
| for (box = textLayoutObject->firstTextBox(); box; box = box->nextTextBox()) { |
| int caretMinOffset = box->caretMinOffset(); |
| int caretMaxOffset = box->caretMaxOffset(); |
| |
| if (caretOffset < caretMinOffset || caretOffset > caretMaxOffset || (caretOffset == caretMaxOffset && box->isLineBreak())) |
| continue; |
| |
| if (caretOffset > caretMinOffset && caretOffset < caretMaxOffset) |
| return InlineBoxPosition(box, caretOffset); |
| |
| if (((caretOffset == caretMaxOffset) ^ (affinity == TextAffinity::Downstream)) |
| || ((caretOffset == caretMinOffset) ^ (affinity == TextAffinity::Upstream)) |
| || (caretOffset == caretMaxOffset && box->nextLeafChild() && box->nextLeafChild()->isLineBreak())) |
| break; |
| |
| candidate = box; |
| } |
| if (candidate && candidate == textLayoutObject->lastTextBox() && affinity == TextAffinity::Downstream) { |
| box = searchAheadForBetterMatch(textLayoutObject); |
| if (box) |
| caretOffset = box->caretMinOffset(); |
| } |
| inlineBox = box ? box : candidate; |
| } |
| |
| if (!inlineBox) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| unsigned char level = inlineBox->bidiLevel(); |
| |
| if (inlineBox->direction() == primaryDirection) { |
| if (caretOffset == inlineBox->caretRightmostOffset()) { |
| InlineBox* nextBox = inlineBox->nextLeafChild(); |
| if (!nextBox || nextBox->bidiLevel() >= level) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| level = nextBox->bidiLevel(); |
| InlineBox* prevBox = inlineBox; |
| do { |
| prevBox = prevBox->prevLeafChild(); |
| } while (prevBox && prevBox->bidiLevel() > level); |
| |
| // For example, abc FED 123 ^ CBA |
| if (prevBox && prevBox->bidiLevel() == level) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| // For example, abc 123 ^ CBA |
| while (InlineBox* nextBox = inlineBox->nextLeafChild()) { |
| if (nextBox->bidiLevel() < level) |
| break; |
| inlineBox = nextBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset()); |
| } |
| |
| if (isStartOfDifferentDirection(inlineBox)) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| level = inlineBox->prevLeafChild()->bidiLevel(); |
| InlineBox* nextBox = inlineBox; |
| do { |
| nextBox = nextBox->nextLeafChild(); |
| } while (nextBox && nextBox->bidiLevel() > level); |
| |
| if (nextBox && nextBox->bidiLevel() == level) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| while (InlineBox* prevBox = inlineBox->prevLeafChild()) { |
| if (prevBox->bidiLevel() < level) |
| break; |
| inlineBox = prevBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset()); |
| } |
| |
| if (caretOffset == inlineBox->caretLeftmostOffset()) { |
| InlineBox* prevBox = inlineBox->prevLeafChildIgnoringLineBreak(); |
| if (!prevBox || prevBox->bidiLevel() < level) { |
| // Left edge of a secondary run. Set to the right edge of the entire |
| // run. |
| while (InlineBox* nextBox = inlineBox->nextLeafChildIgnoringLineBreak()) { |
| if (nextBox->bidiLevel() < level) |
| break; |
| inlineBox = nextBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset()); |
| } |
| |
| if (prevBox->bidiLevel() > level) { |
| // Right edge of a "tertiary" run. Set to the left edge of that run. |
| while (InlineBox* tertiaryBox = inlineBox->prevLeafChildIgnoringLineBreak()) { |
| if (tertiaryBox->bidiLevel() <= level) |
| break; |
| inlineBox = tertiaryBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset()); |
| } |
| return InlineBoxPosition(inlineBox, caretOffset); |
| } |
| |
| if (layoutObject && layoutObject->style()->unicodeBidi() == Plaintext) { |
| if (inlineBox->bidiLevel() < level) |
| return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset()); |
| return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset()); |
| } |
| |
| InlineBox* nextBox = inlineBox->nextLeafChildIgnoringLineBreak(); |
| if (!nextBox || nextBox->bidiLevel() < level) { |
| // Right edge of a secondary run. Set to the left edge of the entire |
| // run. |
| while (InlineBox* prevBox = inlineBox->prevLeafChildIgnoringLineBreak()) { |
| if (prevBox->bidiLevel() < level) |
| break; |
| inlineBox = prevBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset()); |
| } |
| |
| if (nextBox->bidiLevel() <= level) |
| return InlineBoxPosition(inlineBox, caretOffset); |
| |
| // Left edge of a "tertiary" run. Set to the right edge of that run. |
| while (InlineBox* tertiaryBox = inlineBox->nextLeafChildIgnoringLineBreak()) { |
| if (tertiaryBox->bidiLevel() <= level) |
| break; |
| inlineBox = tertiaryBox; |
| } |
| return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset()); |
| } |
| |
| template <typename Strategy> |
| static InlineBoxPosition computeInlineBoxPositionTemplate(const PositionTemplate<Strategy>& position, TextAffinity affinity) |
| { |
| return computeInlineBoxPositionTemplate<Strategy>(position, affinity, primaryDirectionOf(*position.anchorNode())); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const Position& position, TextAffinity affinity) |
| { |
| return computeInlineBoxPositionTemplate<EditingStrategy>(position, affinity); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const PositionInFlatTree& position, TextAffinity affinity) |
| { |
| return computeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(position, affinity); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const VisiblePosition& position) |
| { |
| return computeInlineBoxPosition(position.deepEquivalent(), position.affinity()); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const VisiblePositionInFlatTree& position) |
| { |
| return computeInlineBoxPosition(position.deepEquivalent(), position.affinity()); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const Position& position, TextAffinity affinity, TextDirection primaryDirection) |
| { |
| return computeInlineBoxPositionTemplate<EditingStrategy>(position, affinity, primaryDirection); |
| } |
| |
| InlineBoxPosition computeInlineBoxPosition(const PositionInFlatTree& position, TextAffinity affinity, TextDirection primaryDirection) |
| { |
| return computeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(position, affinity, primaryDirection); |
| } |
| |
| template <typename Strategy> |
| LayoutRect localCaretRectOfPositionTemplate(const PositionWithAffinityTemplate<Strategy>& position, LayoutObject*& layoutObject) |
| { |
| if (position.position().isNull()) { |
| layoutObject = nullptr; |
| return LayoutRect(); |
| } |
| Node* node = position.position().anchorNode(); |
| |
| layoutObject = node->layoutObject(); |
| if (!layoutObject) |
| return LayoutRect(); |
| |
| InlineBoxPosition boxPosition = computeInlineBoxPosition(position.position(), position.affinity()); |
| |
| if (boxPosition.inlineBox) |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(boxPosition.inlineBox->getLineLayoutItem()); |
| |
| return layoutObject->localCaretRect(boxPosition.inlineBox, boxPosition.offsetInBox); |
| } |
| |
| LayoutRect localCaretRectOfPosition(const PositionWithAffinity& position, LayoutObject*& layoutObject) |
| { |
| return localCaretRectOfPositionTemplate<EditingStrategy>(position, layoutObject); |
| } |
| |
| LayoutRect localCaretRectOfPosition(const PositionInFlatTreeWithAffinity& position, LayoutObject*& layoutObject) |
| { |
| return localCaretRectOfPositionTemplate<EditingInFlatTreeStrategy>(position, layoutObject); |
| } |
| |
| static LayoutUnit boundingBoxLogicalHeight(LayoutObject *o, const LayoutRect& rect) |
| { |
| return o->style()->isHorizontalWritingMode() ? rect.height() : rect.width(); |
| } |
| |
| bool hasRenderedNonAnonymousDescendantsWithHeight(LayoutObject* layoutObject) |
| { |
| LayoutObject* stop = layoutObject->nextInPreOrderAfterChildren(); |
| for (LayoutObject *o = layoutObject->slowFirstChild(); o && o != stop; o = o->nextInPreOrder()) { |
| if (o->nonPseudoNode()) { |
| if ((o->isText() && boundingBoxLogicalHeight(o, toLayoutText(o)->linesBoundingBox())) |
| || (o->isBox() && toLayoutBox(o)->pixelSnappedLogicalHeight()) |
| || (o->isLayoutInline() && isEmptyInline(LineLayoutItem(o)) && boundingBoxLogicalHeight(o, toLayoutInline(o)->linesBoundingBox()))) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| VisiblePosition visiblePositionForContentsPoint(const IntPoint& contentsPoint, LocalFrame* frame) |
| { |
| HitTestRequest request = HitTestRequest::Move | HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping; |
| HitTestResult result(request, contentsPoint); |
| frame->document()->layoutViewItem().hitTest(result); |
| |
| if (Node* node = result.innerNode()) |
| return createVisiblePosition(positionRespectingEditingBoundary(frame->selection().selection().start(), result.localPoint(), node)); |
| return VisiblePosition(); |
| } |
| |
| // TODO(yosin): We should use |associatedLayoutObjectOf()| in "VisibleUnits.cpp" |
| // where it takes |LayoutObject| from |Position|. |
| // Note about ::first-letter pseudo-element: |
| // When an element has ::first-letter pseudo-element, first letter characters |
| // are taken from |Text| node and first letter characters are considered |
| // as content of <pseudo:first-letter>. |
| // For following HTML, |
| // <style>div::first-letter {color: red}</style> |
| // <div>abc</div> |
| // we have following layout tree: |
| // LayoutBlockFlow {DIV} at (0,0) size 784x55 |
| // LayoutInline {<pseudo:first-letter>} at (0,0) size 22x53 |
| // LayoutTextFragment (anonymous) at (0,1) size 22x53 |
| // text run at (0,1) width 22: "a" |
| // LayoutTextFragment {#text} at (21,30) size 16x17 |
| // text run at (21,30) width 16: "bc" |
| // In this case, |Text::layoutObject()| for "abc" returns |LayoutTextFragment| |
| // containing "bc", and it is called remaining part. |
| // |
| // Even if |Text| node contains only first-letter characters, e.g. just "a", |
| // remaining part of |LayoutTextFragment|, with |fragmentLength()| == 0, is |
| // appeared in layout tree. |
| // |
| // When |Text| node contains only first-letter characters and whitespaces, e.g. |
| // "B\n", associated |LayoutTextFragment| is first-letter part instead of |
| // remaining part. |
| // |
| // Punctuation characters are considered as first-letter. For "(1)ab", |
| // "(1)" are first-letter part and "ab" are remaining part. |
| LayoutObject* associatedLayoutObjectOf(const Node& node, int offsetInNode) |
| { |
| DCHECK_GE(offsetInNode, 0); |
| LayoutObject* layoutObject = node.layoutObject(); |
| if (!node.isTextNode() || !layoutObject || !toLayoutText(layoutObject)->isTextFragment()) |
| return layoutObject; |
| LayoutTextFragment* layoutTextFragment = toLayoutTextFragment(layoutObject); |
| if (!layoutTextFragment->isRemainingTextLayoutObject()) { |
| DCHECK_LE(static_cast<unsigned>(offsetInNode), layoutTextFragment->start() + layoutTextFragment->fragmentLength()); |
| return layoutTextFragment; |
| } |
| if (layoutTextFragment->fragmentLength() && static_cast<unsigned>(offsetInNode) >= layoutTextFragment->start()) |
| return layoutObject; |
| LayoutObject* firstLetterLayoutObject = layoutTextFragment->firstLetterPseudoElement()->layoutObject(); |
| // TODO(yosin): We're not sure when |firstLetterLayoutObject| has |
| // multiple child layout object. |
| DCHECK_EQ(firstLetterLayoutObject->slowFirstChild(), firstLetterLayoutObject->slowLastChild()); |
| return firstLetterLayoutObject->slowFirstChild(); |
| } |
| |
| int caretMinOffset(const Node* node) |
| { |
| LayoutObject* layoutObject = associatedLayoutObjectOf(*node, 0); |
| return layoutObject ? layoutObject->caretMinOffset() : 0; |
| } |
| |
| int caretMaxOffset(const Node* n) |
| { |
| return EditingStrategy::caretMaxOffset(*n); |
| } |
| |
| template <typename Strategy> |
| static bool inRenderedText(const PositionTemplate<Strategy>& position) |
| { |
| Node* const anchorNode = position.anchorNode(); |
| if (!anchorNode || !anchorNode->isTextNode()) |
| return false; |
| |
| const int offsetInNode = position.computeEditingOffset(); |
| LayoutObject* layoutObject = associatedLayoutObjectOf(*anchorNode, offsetInNode); |
| if (!layoutObject) |
| return false; |
| |
| LayoutText* textLayoutObject = toLayoutText(layoutObject); |
| const int textOffset = offsetInNode - textLayoutObject->textStartOffset(); |
| for (InlineTextBox *box = textLayoutObject->firstTextBox(); box; box = box->nextTextBox()) { |
| if (textOffset < static_cast<int>(box->start()) && !textLayoutObject->containsReversedText()) { |
| // The offset we're looking for is before this node |
| // this means the offset must be in content that is |
| // not laid out. Return false. |
| return false; |
| } |
| if (box->containsCaretOffset(textOffset)) { |
| // Return false for offsets inside composed characters. |
| return textOffset == 0 || textOffset == nextGraphemeBoundaryOf(anchorNode, previousGraphemeBoundaryOf(anchorNode, textOffset)); |
| } |
| } |
| |
| return false; |
| } |
| |
| bool rendersInDifferentPosition(const Position& position1, const Position& position2) |
| { |
| if (position1.isNull() || position2.isNull()) |
| return false; |
| LayoutObject* layoutObject1; |
| const LayoutRect& rect1 = localCaretRectOfPosition(PositionWithAffinity(position1), layoutObject1); |
| LayoutObject* layoutObject2; |
| const LayoutRect& rect2 = localCaretRectOfPosition(PositionWithAffinity(position2), layoutObject2); |
| if (!layoutObject1 || !layoutObject2) |
| return layoutObject1 != layoutObject2; |
| return layoutObject1->localToAbsoluteQuad(FloatRect(rect1)) != layoutObject2->localToAbsoluteQuad(FloatRect(rect2)); |
| } |
| |
| static bool isVisuallyEmpty(const LayoutObject* layout) |
| { |
| for (LayoutObject* child = layout->slowFirstChild(); child; child = child->nextSibling()) { |
| // TODO(xiaochengh): Replace type-based conditioning by virtual function. |
| if (child->isBox()) { |
| if (!toLayoutBox(child)->size().isEmpty()) |
| return false; |
| } else if (child->isLayoutInline()) { |
| if (toLayoutInline(child)->firstLineBoxIncludingCulling()) |
| return false; |
| } else if (child->isText()) { |
| if (toLayoutText(child)->hasTextBoxes()) |
| return false; |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // FIXME: Share code with isCandidate, if possible. |
| bool endsOfNodeAreVisuallyDistinctPositions(const Node* node) |
| { |
| if (!node || !node->layoutObject()) |
| return false; |
| |
| if (!node->layoutObject()->isInline()) |
| return true; |
| |
| // Don't include inline tables. |
| if (isHTMLTableElement(*node)) |
| return false; |
| |
| // A Marquee elements are moving so we should assume their ends are always |
| // visibily distinct. |
| if (isHTMLMarqueeElement(*node)) |
| return true; |
| |
| // There is a VisiblePosition inside an empty inline-block container. |
| return node->layoutObject()->isAtomicInlineLevel() && canHaveChildrenForEditing(node) && !toLayoutBox(node->layoutObject())->size().isEmpty() && isVisuallyEmpty(node->layoutObject()); |
| } |
| |
| template <typename Strategy> |
| static Node* enclosingVisualBoundary(Node* node) |
| { |
| while (node && !endsOfNodeAreVisuallyDistinctPositions(node)) |
| node = Strategy::parent(*node); |
| |
| return node; |
| } |
| |
| // upstream() and downstream() want to return positions that are either in a |
| // text node or at just before a non-text node. This method checks for that. |
| template <typename Strategy> |
| static bool isStreamer(const PositionIteratorAlgorithm<Strategy>& pos) |
| { |
| if (!pos.node()) |
| return true; |
| |
| if (isAtomicNode(pos.node())) |
| return true; |
| |
| return pos.atStartOfNode(); |
| } |
| |
| template <typename Strategy> |
| static PositionTemplate<Strategy> mostBackwardCaretPosition(const PositionTemplate<Strategy>& position, EditingBoundaryCrossingRule rule) |
| { |
| TRACE_EVENT0("input", "VisibleUnits::mostBackwardCaretPosition"); |
| |
| Node* startNode = position.anchorNode(); |
| if (!startNode) |
| return PositionTemplate<Strategy>(); |
| |
| // iterate backward from there, looking for a qualified position |
| Node* boundary = enclosingVisualBoundary<Strategy>(startNode); |
| // FIXME: PositionIterator should respect Before and After positions. |
| PositionIteratorAlgorithm<Strategy> lastVisible(position.isAfterAnchor() ? PositionTemplate<Strategy>::editingPositionOf(position.anchorNode(), Strategy::caretMaxOffset(*position.anchorNode())) : position); |
| PositionIteratorAlgorithm<Strategy> currentPos = lastVisible; |
| bool startEditable = hasEditableStyle(*startNode); |
| Node* lastNode = startNode; |
| bool boundaryCrossed = false; |
| for (; !currentPos.atStart(); currentPos.decrement()) { |
| Node* currentNode = currentPos.node(); |
| // Don't check for an editability change if we haven't moved to a different node, |
| // to avoid the expense of computing hasEditableStyle(). |
| if (currentNode != lastNode) { |
| // Don't change editability. |
| bool currentEditable = hasEditableStyle(*currentNode); |
| if (startEditable != currentEditable) { |
| if (rule == CannotCrossEditingBoundary) |
| break; |
| boundaryCrossed = true; |
| } |
| lastNode = currentNode; |
| } |
| |
| // If we've moved to a position that is visually distinct, return the last saved position. There |
| // is code below that terminates early if we're *about* to move to a visually distinct position. |
| if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) |
| return lastVisible.deprecatedComputePosition(); |
| |
| // skip position in non-laid out or invisible node |
| LayoutObject* layoutObject = associatedLayoutObjectOf(*currentNode, currentPos.offsetInLeafNode()); |
| if (!layoutObject || layoutObject->style()->visibility() != EVisibility::Visible) |
| continue; |
| |
| if (rule == CanCrossEditingBoundary && boundaryCrossed) { |
| lastVisible = currentPos; |
| break; |
| } |
| |
| // track last visible streamer position |
| if (isStreamer<Strategy>(currentPos)) |
| lastVisible = currentPos; |
| |
| // Don't move past a position that is visually distinct. We could rely on code above to terminate and |
| // return lastVisible on the next iteration, but we terminate early to avoid doing a nodeIndex() call. |
| if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentPos.atStartOfNode()) |
| return lastVisible.deprecatedComputePosition(); |
| |
| // Return position after tables and nodes which have content that can be ignored. |
| if (Strategy::editingIgnoresContent(currentNode) || isDisplayInsideTable(currentNode)) { |
| if (currentPos.atEndOfNode()) |
| return PositionTemplate<Strategy>::afterNode(currentNode); |
| continue; |
| } |
| |
| // return current position if it is in laid out text |
| if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) { |
| LayoutText* const textLayoutObject = toLayoutText(layoutObject); |
| const unsigned textStartOffset = textLayoutObject->textStartOffset(); |
| if (currentNode != startNode) { |
| // This assertion fires in layout tests in the case-transform.html test because |
| // of a mix-up between offsets in the text in the DOM tree with text in the |
| // layout tree which can have a different length due to case transformation. |
| // Until we resolve that, disable this so we can run the layout tests! |
| // DCHECK_GE(currentOffset, layoutObject->caretMaxOffset()); |
| return PositionTemplate<Strategy>(currentNode, layoutObject->caretMaxOffset() + textStartOffset); |
| } |
| |
| // Map offset in DOM node to offset in InlineBox. |
| DCHECK_GE(currentPos.offsetInLeafNode(), static_cast<int>(textStartOffset)); |
| const unsigned textOffset = currentPos.offsetInLeafNode() - textStartOffset; |
| InlineTextBox* lastTextBox = textLayoutObject->lastTextBox(); |
| for (InlineTextBox* box = textLayoutObject->firstTextBox(); box; box = box->nextTextBox()) { |
| if (textOffset == box->start()) { |
| if (textLayoutObject->isTextFragment() && toLayoutTextFragment(layoutObject)->isRemainingTextLayoutObject()) { |
| // |currentPos| is at start of remaining text of |
| // |Text| node with :first-letter. |
| DCHECK_GE(currentPos.offsetInLeafNode(), 1); |
| LayoutObject* firstLetterLayoutObject = toLayoutTextFragment(layoutObject)->firstLetterPseudoElement()->layoutObject(); |
| if (firstLetterLayoutObject && firstLetterLayoutObject->style()->visibility() == EVisibility::Visible) |
| return currentPos.computePosition(); |
| } |
| continue; |
| } |
| if (textOffset <= box->start() + box->len()) { |
| if (textOffset > box->start()) |
| return currentPos.computePosition(); |
| continue; |
| } |
| |
| if (box == lastTextBox || textOffset != box->start() + box->len() + 1) |
| continue; |
| |
| // The text continues on the next line only if the last text box is not on this line and |
| // none of the boxes on this line have a larger start offset. |
| |
| bool continuesOnNextLine = true; |
| InlineBox* otherBox = box; |
| while (continuesOnNextLine) { |
| otherBox = otherBox->nextLeafChild(); |
| if (!otherBox) |
| break; |
| if (otherBox == lastTextBox || (LineLayoutAPIShim::layoutObjectFrom(otherBox->getLineLayoutItem()) == textLayoutObject && toInlineTextBox(otherBox)->start() > textOffset)) |
| continuesOnNextLine = false; |
| } |
| |
| otherBox = box; |
| while (continuesOnNextLine) { |
| otherBox = otherBox->prevLeafChild(); |
| if (!otherBox) |
| break; |
| if (otherBox == lastTextBox || (LineLayoutAPIShim::layoutObjectFrom(otherBox->getLineLayoutItem()) == textLayoutObject && toInlineTextBox(otherBox)->start() > textOffset)) |
| continuesOnNextLine = false; |
| } |
| |
| if (continuesOnNextLine) |
| return currentPos.computePosition(); |
| } |
| } |
| } |
| return lastVisible.deprecatedComputePosition(); |
| } |
| |
| Position mostBackwardCaretPosition(const Position& position, EditingBoundaryCrossingRule rule) |
| { |
| return mostBackwardCaretPosition<EditingStrategy>(position, rule); |
| } |
| |
| PositionInFlatTree mostBackwardCaretPosition(const PositionInFlatTree& position, EditingBoundaryCrossingRule rule) |
| { |
| return mostBackwardCaretPosition<EditingInFlatTreeStrategy>(position, rule); |
| } |
| |
| template <typename Strategy> |
| PositionTemplate<Strategy> mostForwardCaretPosition(const PositionTemplate<Strategy>& position, EditingBoundaryCrossingRule rule) |
| { |
| TRACE_EVENT0("input", "VisibleUnits::mostForwardCaretPosition"); |
| |
| Node* startNode = position.anchorNode(); |
| if (!startNode) |
| return PositionTemplate<Strategy>(); |
| |
| // iterate forward from there, looking for a qualified position |
| Node* boundary = enclosingVisualBoundary<Strategy>(startNode); |
| // FIXME: PositionIterator should respect Before and After positions. |
| PositionIteratorAlgorithm<Strategy> lastVisible(position.isAfterAnchor() ? PositionTemplate<Strategy>::editingPositionOf(position.anchorNode(), Strategy::caretMaxOffset(*position.anchorNode())) : position); |
| PositionIteratorAlgorithm<Strategy> currentPos = lastVisible; |
| bool startEditable = hasEditableStyle(*startNode); |
| Node* lastNode = startNode; |
| bool boundaryCrossed = false; |
| for (; !currentPos.atEnd(); currentPos.increment()) { |
| Node* currentNode = currentPos.node(); |
| // Don't check for an editability change if we haven't moved to a different node, |
| // to avoid the expense of computing hasEditableStyle(). |
| if (currentNode != lastNode) { |
| // Don't change editability. |
| bool currentEditable = hasEditableStyle(*currentNode); |
| if (startEditable != currentEditable) { |
| if (rule == CannotCrossEditingBoundary) |
| break; |
| boundaryCrossed = true; |
| } |
| |
| lastNode = currentNode; |
| } |
| |
| // stop before going above the body, up into the head |
| // return the last visible streamer position |
| if (isHTMLBodyElement(*currentNode) && currentPos.atEndOfNode()) |
| break; |
| |
| // Do not move to a visually distinct position. |
| if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) |
| return lastVisible.deprecatedComputePosition(); |
| // Do not move past a visually disinct position. |
| // Note: The first position after the last in a node whose ends are visually distinct |
| // positions will be [boundary->parentNode(), originalBlock->nodeIndex() + 1]. |
| if (boundary && Strategy::parent(*boundary) == currentNode) |
| return lastVisible.deprecatedComputePosition(); |
| |
| // skip position in non-laid out or invisible node |
| LayoutObject* layoutObject = associatedLayoutObjectOf(*currentNode, currentPos.offsetInLeafNode()); |
| if (!layoutObject || layoutObject->style()->visibility() != EVisibility::Visible) |
| continue; |
| |
| if (rule == CanCrossEditingBoundary && boundaryCrossed) { |
| lastVisible = currentPos; |
| break; |
| } |
| |
| // track last visible streamer position |
| if (isStreamer<Strategy>(currentPos)) |
| lastVisible = currentPos; |
| |
| // Return position before tables and nodes which have content that can be ignored. |
| if (Strategy::editingIgnoresContent(currentNode) || isDisplayInsideTable(currentNode)) { |
| if (currentPos.offsetInLeafNode() <= layoutObject->caretMinOffset()) |
| return PositionTemplate<Strategy>::editingPositionOf(currentNode, layoutObject->caretMinOffset()); |
| continue; |
| } |
| |
| // return current position if it is in laid out text |
| if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) { |
| LayoutText* const textLayoutObject = toLayoutText(layoutObject); |
| const unsigned textStartOffset = textLayoutObject->textStartOffset(); |
| if (currentNode != startNode) { |
| DCHECK(currentPos.atStartOfNode()); |
| return PositionTemplate<Strategy>(currentNode, layoutObject->caretMinOffset() + textStartOffset); |
| } |
| |
| // Map offset in DOM node to offset in InlineBox. |
| DCHECK_GE(currentPos.offsetInLeafNode(), static_cast<int>(textStartOffset)); |
| const unsigned textOffset = currentPos.offsetInLeafNode() - textStartOffset; |
| InlineTextBox* lastTextBox = textLayoutObject->lastTextBox(); |
| for (InlineTextBox* box = textLayoutObject->firstTextBox(); box; box = box->nextTextBox()) { |
| if (textOffset <= box->end()) { |
| if (textOffset >= box->start()) |
| return currentPos.computePosition(); |
| continue; |
| } |
| |
| if (box == lastTextBox || textOffset != box->start() + box->len()) |
| continue; |
| |
| // The text continues on the next line only if the last text box is not on this line and |
| // none of the boxes on this line have a larger start offset. |
| |
| bool continuesOnNextLine = true; |
| InlineBox* otherBox = box; |
| while (continuesOnNextLine) { |
| otherBox = otherBox->nextLeafChild(); |
| if (!otherBox) |
| break; |
| if (otherBox == lastTextBox || (LineLayoutAPIShim::layoutObjectFrom(otherBox->getLineLayoutItem()) == textLayoutObject && toInlineTextBox(otherBox)->start() >= textOffset)) |
| continuesOnNextLine = false; |
| } |
| |
| otherBox = box; |
| while (continuesOnNextLine) { |
| otherBox = otherBox->prevLeafChild(); |
| if (!otherBox) |
| break; |
| if (otherBox == lastTextBox || (LineLayoutAPIShim::layoutObjectFrom(otherBox->getLineLayoutItem()) == textLayoutObject && toInlineTextBox(otherBox)->start() >= textOffset)) |
| continuesOnNextLine = false; |
| } |
| |
| if (continuesOnNextLine) |
| return currentPos.computePosition(); |
| } |
| } |
| } |
| |
| return lastVisible.deprecatedComputePosition(); |
| } |
| |
| Position mostForwardCaretPosition(const Position& position, EditingBoundaryCrossingRule rule) |
| { |
| return mostForwardCaretPosition<EditingStrategy>(position, rule); |
| } |
| |
| PositionInFlatTree mostForwardCaretPosition(const PositionInFlatTree& position, EditingBoundaryCrossingRule rule) |
| { |
| return mostForwardCaretPosition<EditingInFlatTreeStrategy>(position, rule); |
| } |
| |
| // Returns true if the visually equivalent positions around have different |
| // editability. A position is considered at editing boundary if one of the |
| // following is true: |
| // 1. It is the first position in the node and the next visually equivalent |
| // position is non editable. |
| // 2. It is the last position in the node and the previous visually equivalent |
| // position is non editable. |
| // 3. It is an editable position and both the next and previous visually |
| // equivalent positions are both non editable. |
| template <typename Strategy> |
| static bool atEditingBoundary(const PositionTemplate<Strategy> positions) |
| { |
| PositionTemplate<Strategy> nextPosition = mostForwardCaretPosition(positions, CanCrossEditingBoundary); |
| if (positions.atFirstEditingPositionForNode() && nextPosition.isNotNull() && !hasEditableStyle(*nextPosition.anchorNode())) |
| return true; |
| |
| PositionTemplate<Strategy> prevPosition = mostBackwardCaretPosition(positions, CanCrossEditingBoundary); |
| if (positions.atLastEditingPositionForNode() && prevPosition.isNotNull() && !hasEditableStyle(*prevPosition.anchorNode())) |
| return true; |
| |
| return nextPosition.isNotNull() && !hasEditableStyle(*nextPosition.anchorNode()) |
| && prevPosition.isNotNull() && !hasEditableStyle(*prevPosition.anchorNode()); |
| } |
| |
| template <typename Strategy> |
| static bool isVisuallyEquivalentCandidateAlgorithm(const PositionTemplate<Strategy>& position) |
| { |
| Node* const anchorNode = position.anchorNode(); |
| if (!anchorNode) |
| return false; |
| |
| LayoutObject* layoutObject = anchorNode->layoutObject(); |
| if (!layoutObject) |
| return false; |
| |
| if (layoutObject->style()->visibility() != EVisibility::Visible) |
| return false; |
| |
| if (layoutObject->isBR()) { |
| // TODO(leviw) The condition should be |
| // m_anchorType == PositionAnchorType::BeforeAnchor, but for now we |
| // still need to support legacy positions. |
| if (position.isAfterAnchor()) |
| return false; |
| if (position.computeEditingOffset()) |
| return false; |
| const Node* parent = Strategy::parent(*anchorNode); |
| return parent->layoutObject() && parent->layoutObject()->isSelectable(); |
| } |
| |
| if (layoutObject->isText()) |
| return layoutObject->isSelectable() && inRenderedText(position); |
| |
| if (layoutObject->isSVG()) { |
| // We don't consider SVG elements are contenteditable except for |
| // associated |layoutObject| returns |isText()| true, |
| // e.g. |LayoutSVGInlineText|. |
| return false; |
| } |
| |
| if (isDisplayInsideTable(anchorNode) || Strategy::editingIgnoresContent(anchorNode)) { |
| if (!position.atFirstEditingPositionForNode() && !position.atLastEditingPositionForNode()) |
| return false; |
| const Node* parent = Strategy::parent(*anchorNode); |
| return parent->layoutObject() && parent->layoutObject()->isSelectable(); |
| } |
| |
| if (anchorNode->document().documentElement() == anchorNode || anchorNode->isDocumentNode()) |
| return false; |
| |
| if (!layoutObject->isSelectable()) |
| return false; |
| |
| if (layoutObject->isLayoutBlockFlow() || layoutObject->isFlexibleBox() || layoutObject->isLayoutGrid()) { |
| if (toLayoutBlock(layoutObject)->logicalHeight() || isHTMLBodyElement(*anchorNode)) { |
| if (!hasRenderedNonAnonymousDescendantsWithHeight(layoutObject)) |
| return position.atFirstEditingPositionForNode(); |
| return hasEditableStyle(*anchorNode) && atEditingBoundary(position); |
| } |
| } else { |
| LocalFrame* frame = anchorNode->document().frame(); |
| bool caretBrowsing = frame->settings() && frame->settings()->caretBrowsingEnabled(); |
| return (caretBrowsing || hasEditableStyle(*anchorNode)) && atEditingBoundary(position); |
| } |
| |
| return false; |
| } |
| |
| bool isVisuallyEquivalentCandidate(const Position& position) |
| { |
| return isVisuallyEquivalentCandidateAlgorithm<EditingStrategy>(position); |
| } |
| |
| bool isVisuallyEquivalentCandidate(const PositionInFlatTree& position) |
| { |
| return isVisuallyEquivalentCandidateAlgorithm<EditingInFlatTreeStrategy>(position); |
| } |
| |
| template <typename Strategy> |
| static IntRect absoluteCaretBoundsOfAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| LayoutObject* layoutObject; |
| LayoutRect localRect = localCaretRectOfPosition(visiblePosition.toPositionWithAffinity(), layoutObject); |
| if (localRect.isEmpty() || !layoutObject) |
| return IntRect(); |
| |
| return layoutObject->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); |
| } |
| |
| IntRect absoluteCaretBoundsOf(const VisiblePosition& visiblePosition) |
| { |
| return absoluteCaretBoundsOfAlgorithm<EditingStrategy>(visiblePosition); |
| } |
| |
| IntRect absoluteCaretBoundsOf(const VisiblePositionInFlatTree& visiblePosition) |
| { |
| return absoluteCaretBoundsOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> skipToEndOfEditingBoundary(const VisiblePositionTemplate<Strategy>& pos, const PositionTemplate<Strategy>& anchor) |
| { |
| if (pos.isNull()) |
| return pos; |
| |
| ContainerNode* highestRoot = highestEditableRoot(anchor); |
| ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent()); |
| |
| // Return |pos| itself if the two are from the very same editable region, |
| // or both are non-editable. |
| if (highestRootOfPos == highestRoot) |
| return pos; |
| |
| // If this is not editable but |pos| has an editable root, skip to the end |
| if (!highestRoot && highestRootOfPos) |
| return createVisiblePosition(PositionTemplate<Strategy>(highestRootOfPos, PositionAnchorType::AfterAnchor).parentAnchoredEquivalent()); |
| |
| // That must mean that |pos| is not editable. Return the next position after |
| // |pos| that is in the same editable region as this position |
| DCHECK(highestRoot); |
| return firstEditableVisiblePositionAfterPositionInRoot(pos.deepEquivalent(), *highestRoot); |
| } |
| |
| template <typename Strategy> |
| static UChar32 characterAfterAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| // We canonicalize to the first of two equivalent candidates, but the second |
| // of the two candidates is the one that will be inside the text node |
| // containing the character after this visible position. |
| const PositionTemplate<Strategy> pos = mostForwardCaretPosition(visiblePosition.deepEquivalent()); |
| if (!pos.isOffsetInAnchor()) |
| return 0; |
| Node* containerNode = pos.computeContainerNode(); |
| if (!containerNode || !containerNode->isTextNode()) |
| return 0; |
| unsigned offset = static_cast<unsigned>(pos.offsetInContainerNode()); |
| Text* textNode = toText(containerNode); |
| unsigned length = textNode->length(); |
| if (offset >= length) |
| return 0; |
| |
| return textNode->data().characterStartingAt(offset); |
| } |
| |
| UChar32 characterAfter(const VisiblePosition& visiblePosition) |
| { |
| return characterAfterAlgorithm<EditingStrategy>(visiblePosition); |
| } |
| |
| UChar32 characterAfter(const VisiblePositionInFlatTree& visiblePosition) |
| { |
| return characterAfterAlgorithm<EditingInFlatTreeStrategy>(visiblePosition); |
| } |
| |
| template <typename Strategy> |
| static UChar32 characterBeforeAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| return characterAfter(previousPositionOf(visiblePosition)); |
| } |
| |
| UChar32 characterBefore(const VisiblePosition& visiblePosition) |
| { |
| return characterBeforeAlgorithm<EditingStrategy>(visiblePosition); |
| } |
| |
| UChar32 characterBefore(const VisiblePositionInFlatTree& visiblePosition) |
| { |
| return characterBeforeAlgorithm<EditingInFlatTreeStrategy>(visiblePosition); |
| } |
| |
| template <typename Strategy> |
| static PositionTemplate<Strategy> leftVisuallyDistinctCandidate(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| const PositionTemplate<Strategy> deepPosition = visiblePosition.deepEquivalent(); |
| PositionTemplate<Strategy> p = deepPosition; |
| |
| if (p.isNull()) |
| return PositionTemplate<Strategy>(); |
| |
| const PositionTemplate<Strategy> downstreamStart = mostForwardCaretPosition(p); |
| TextDirection primaryDirection = primaryDirectionOf(*p.anchorNode()); |
| const TextAffinity affinity = visiblePosition.affinity(); |
| |
| while (true) { |
| InlineBoxPosition boxPosition = computeInlineBoxPosition(p, affinity, primaryDirection); |
| InlineBox* box = boxPosition.inlineBox; |
| int offset = boxPosition.offsetInBox; |
| if (!box) |
| return primaryDirection == LTR ? previousVisuallyDistinctCandidate(deepPosition) : nextVisuallyDistinctCandidate(deepPosition); |
| |
| LineLayoutItem lineLayoutItem = box->getLineLayoutItem(); |
| |
| while (true) { |
| if ((lineLayoutItem.isAtomicInlineLevel() || lineLayoutItem.isBR()) && offset == box->caretRightmostOffset()) |
| return box->isLeftToRightDirection() ? previousVisuallyDistinctCandidate(deepPosition) : nextVisuallyDistinctCandidate(deepPosition); |
| |
| if (!lineLayoutItem.node()) { |
| box = box->prevLeafChild(); |
| if (!box) |
| return primaryDirection == LTR ? previousVisuallyDistinctCandidate(deepPosition) : nextVisuallyDistinctCandidate(deepPosition); |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = box->caretRightmostOffset(); |
| continue; |
| } |
| |
| offset = box->isLeftToRightDirection() ? previousGraphemeBoundaryOf(lineLayoutItem.node(), offset) : nextGraphemeBoundaryOf(lineLayoutItem.node(), offset); |
| |
| int caretMinOffset = box->caretMinOffset(); |
| int caretMaxOffset = box->caretMaxOffset(); |
| |
| if (offset > caretMinOffset && offset < caretMaxOffset) |
| break; |
| |
| if (box->isLeftToRightDirection() ? offset < caretMinOffset : offset > caretMaxOffset) { |
| // Overshot to the left. |
| InlineBox* prevBox = box->prevLeafChildIgnoringLineBreak(); |
| if (!prevBox) { |
| PositionTemplate<Strategy> positionOnLeft = primaryDirection == LTR ? previousVisuallyDistinctCandidate(visiblePosition.deepEquivalent()) : nextVisuallyDistinctCandidate(visiblePosition.deepEquivalent()); |
| if (positionOnLeft.isNull()) |
| return PositionTemplate<Strategy>(); |
| |
| InlineBox* boxOnLeft = computeInlineBoxPosition(positionOnLeft, affinity, primaryDirection).inlineBox; |
| if (boxOnLeft && boxOnLeft->root() == box->root()) |
| return PositionTemplate<Strategy>(); |
| return positionOnLeft; |
| } |
| |
| // Reposition at the other logical position corresponding to our |
| // edge's visual position and go for another round. |
| box = prevBox; |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = prevBox->caretRightmostOffset(); |
| continue; |
| } |
| |
| DCHECK_EQ(offset, box->caretLeftmostOffset()); |
| |
| unsigned char level = box->bidiLevel(); |
| InlineBox* prevBox = box->prevLeafChild(); |
| |
| if (box->direction() == primaryDirection) { |
| if (!prevBox) { |
| InlineBox* logicalStart = 0; |
| if (primaryDirection == LTR ? box->root().getLogicalStartBoxWithNode(logicalStart) : box->root().getLogicalEndBoxWithNode(logicalStart)) { |
| box = logicalStart; |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = primaryDirection == LTR ? box->caretMinOffset() : box->caretMaxOffset(); |
| } |
| break; |
| } |
| if (prevBox->bidiLevel() >= level) |
| break; |
| |
| level = prevBox->bidiLevel(); |
| |
| InlineBox* nextBox = box; |
| do { |
| nextBox = nextBox->nextLeafChild(); |
| } while (nextBox && nextBox->bidiLevel() > level); |
| |
| if (nextBox && nextBox->bidiLevel() == level) |
| break; |
| |
| box = prevBox; |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = box->caretRightmostOffset(); |
| if (box->direction() == primaryDirection) |
| break; |
| continue; |
| } |
| |
| while (prevBox && !prevBox->getLineLayoutItem().node()) |
| prevBox = prevBox->prevLeafChild(); |
| |
| if (prevBox) { |
| box = prevBox; |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = box->caretRightmostOffset(); |
| if (box->bidiLevel() > level) { |
| do { |
| prevBox = prevBox->prevLeafChild(); |
| } while (prevBox && prevBox->bidiLevel() > level); |
| |
| if (!prevBox || prevBox->bidiLevel() < level) |
| continue; |
| } |
| } else { |
| // Trailing edge of a secondary run. Set to the leading edge of |
| // the entire run. |
| while (true) { |
| while (InlineBox* nextBox = box->nextLeafChild()) { |
| if (nextBox->bidiLevel() < level) |
| break; |
| box = nextBox; |
| } |
| if (box->bidiLevel() == level) |
| break; |
| level = box->bidiLevel(); |
| while (InlineBox* prevBox = box->prevLeafChild()) { |
| if (prevBox->bidiLevel() < level) |
| break; |
| box = prevBox; |
| } |
| if (box->bidiLevel() == level) |
| break; |
| level = box->bidiLevel(); |
| } |
| lineLayoutItem = box->getLineLayoutItem(); |
| offset = primaryDirection == LTR ? box->caretMinOffset() : box->caretMaxOffset(); |
| } |
| break; |
| } |
| |
| p = PositionTemplate<Strategy>::editingPositionOf(lineLayoutItem.node(), offset); |
| |
| if ((isVisuallyEquivalentCandidate(p) && mostForwardCaretPosition(p) != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) |
| return p; |
| |
| DCHECK_NE(p, deepPosition); |
| } |
| } |
| |
| template <typename Strategy> |
| VisiblePositionTemplate<Strategy> leftPositionOfAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| const PositionTemplate<Strategy> pos = leftVisuallyDistinctCandidate(visiblePosition); |
| // TODO(yosin) Why can't we move left from the last position in a tree? |
| if (pos.atStartOfTree() || pos.atEndOfTree()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| const VisiblePositionTemplate<Strategy> left = createVisiblePosition(pos); |
| DCHECK_NE(left.deepEquivalent(), visiblePosition.deepEquivalent()); |
| |
| return directionOfEnclosingBlock(left.deepEquivalent()) == LTR ? honorEditingBoundaryAtOrBefore(left, visiblePosition.deepEquivalent()) : honorEditingBoundaryAtOrAfter(left, visiblePosition.deepEquivalent()); |
| } |
| |
| VisiblePosition leftPositionOf(const VisiblePosition& visiblePosition) |
| { |
| return leftPositionOfAlgorithm<EditingStrategy>(visiblePosition); |
| } |
| |
| VisiblePositionInFlatTree leftPositionOf(const VisiblePositionInFlatTree& visiblePosition) |
| { |
| return leftPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition); |
| } |
| |
| template <typename Strategy> |
| static PositionTemplate<Strategy> rightVisuallyDistinctCandidate(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| const PositionTemplate<Strategy> deepPosition = visiblePosition.deepEquivalent(); |
| PositionTemplate<Strategy> p = deepPosition; |
| if (p.isNull()) |
| return PositionTemplate<Strategy>(); |
| |
| const PositionTemplate<Strategy> downstreamStart = mostForwardCaretPosition(p); |
| TextDirection primaryDirection = primaryDirectionOf(*p.anchorNode()); |
| const TextAffinity affinity = visiblePosition.affinity(); |
| |
| while (true) { |
| InlineBoxPosition boxPosition = computeInlineBoxPosition(p, affinity, primaryDirection); |
| InlineBox* box = boxPosition.inlineBox; |
| int offset = boxPosition.offsetInBox; |
| if (!box) |
| return primaryDirection == LTR ? nextVisuallyDistinctCandidate(deepPosition) : previousVisuallyDistinctCandidate(deepPosition); |
| |
| LayoutObject* layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| |
| while (true) { |
| if ((layoutObject->isAtomicInlineLevel() || layoutObject->isBR()) && offset == box->caretLeftmostOffset()) |
| return box->isLeftToRightDirection() ? nextVisuallyDistinctCandidate(deepPosition) : previousVisuallyDistinctCandidate(deepPosition); |
| |
| if (!layoutObject->node()) { |
| box = box->nextLeafChild(); |
| if (!box) |
| return primaryDirection == LTR ? nextVisuallyDistinctCandidate(deepPosition) : previousVisuallyDistinctCandidate(deepPosition); |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = box->caretLeftmostOffset(); |
| continue; |
| } |
| |
| offset = box->isLeftToRightDirection() ? nextGraphemeBoundaryOf(layoutObject->node(), offset) : previousGraphemeBoundaryOf(layoutObject->node(), offset); |
| |
| int caretMinOffset = box->caretMinOffset(); |
| int caretMaxOffset = box->caretMaxOffset(); |
| |
| if (offset > caretMinOffset && offset < caretMaxOffset) |
| break; |
| |
| if (box->isLeftToRightDirection() ? offset > caretMaxOffset : offset < caretMinOffset) { |
| // Overshot to the right. |
| InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak(); |
| if (!nextBox) { |
| PositionTemplate<Strategy> positionOnRight = primaryDirection == LTR ? nextVisuallyDistinctCandidate(deepPosition) : previousVisuallyDistinctCandidate(deepPosition); |
| if (positionOnRight.isNull()) |
| return PositionTemplate<Strategy>(); |
| |
| InlineBox* boxOnRight = computeInlineBoxPosition(positionOnRight, affinity, primaryDirection).inlineBox; |
| if (boxOnRight && boxOnRight->root() == box->root()) |
| return PositionTemplate<Strategy>(); |
| return positionOnRight; |
| } |
| |
| // Reposition at the other logical position corresponding to our |
| // edge's visual position and go for another round. |
| box = nextBox; |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = nextBox->caretLeftmostOffset(); |
| continue; |
| } |
| |
| DCHECK_EQ(offset, box->caretRightmostOffset()); |
| |
| unsigned char level = box->bidiLevel(); |
| InlineBox* nextBox = box->nextLeafChild(); |
| |
| if (box->direction() == primaryDirection) { |
| if (!nextBox) { |
| InlineBox* logicalEnd = 0; |
| if (primaryDirection == LTR ? box->root().getLogicalEndBoxWithNode(logicalEnd) : box->root().getLogicalStartBoxWithNode(logicalEnd)) { |
| box = logicalEnd; |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); |
| } |
| break; |
| } |
| |
| if (nextBox->bidiLevel() >= level) |
| break; |
| |
| level = nextBox->bidiLevel(); |
| |
| InlineBox* prevBox = box; |
| do { |
| prevBox = prevBox->prevLeafChild(); |
| } while (prevBox && prevBox->bidiLevel() > level); |
| |
| // For example, abc FED 123 ^ CBA |
| if (prevBox && prevBox->bidiLevel() == level) |
| break; |
| |
| // For example, abc 123 ^ CBA or 123 ^ CBA abc |
| box = nextBox; |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = box->caretLeftmostOffset(); |
| if (box->direction() == primaryDirection) |
| break; |
| continue; |
| } |
| |
| while (nextBox && !nextBox->getLineLayoutItem().node()) |
| nextBox = nextBox->nextLeafChild(); |
| |
| if (nextBox) { |
| box = nextBox; |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = box->caretLeftmostOffset(); |
| |
| if (box->bidiLevel() > level) { |
| do { |
| nextBox = nextBox->nextLeafChild(); |
| } while (nextBox && nextBox->bidiLevel() > level); |
| |
| if (!nextBox || nextBox->bidiLevel() < level) |
| continue; |
| } |
| } else { |
| // Trailing edge of a secondary run. Set to the leading edge of the entire run. |
| while (true) { |
| while (InlineBox* prevBox = box->prevLeafChild()) { |
| if (prevBox->bidiLevel() < level) |
| break; |
| box = prevBox; |
| } |
| if (box->bidiLevel() == level) |
| break; |
| level = box->bidiLevel(); |
| while (InlineBox* nextBox = box->nextLeafChild()) { |
| if (nextBox->bidiLevel() < level) |
| break; |
| box = nextBox; |
| } |
| if (box->bidiLevel() == level) |
| break; |
| level = box->bidiLevel(); |
| } |
| layoutObject = LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem()); |
| offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); |
| } |
| break; |
| } |
| |
| p = PositionTemplate<Strategy>::editingPositionOf(layoutObject->node(), offset); |
| |
| if ((isVisuallyEquivalentCandidate(p) && mostForwardCaretPosition(p) != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) |
| return p; |
| |
| DCHECK_NE(p, deepPosition); |
| } |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> rightPositionOfAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition) |
| { |
| const PositionTemplate<Strategy> pos = rightVisuallyDistinctCandidate(visiblePosition); |
| // FIXME: Why can't we move left from the last position in a tree? |
| if (pos.atStartOfTree() || pos.atEndOfTree()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| const VisiblePositionTemplate<Strategy> right = createVisiblePosition(pos); |
| DCHECK_NE(right.deepEquivalent(), visiblePosition.deepEquivalent()); |
| |
| return directionOfEnclosingBlock(right.deepEquivalent()) == LTR ? honorEditingBoundaryAtOrAfter(right, visiblePosition.deepEquivalent()) : honorEditingBoundaryAtOrBefore(right, visiblePosition.deepEquivalent()); |
| } |
| |
| VisiblePosition rightPositionOf(const VisiblePosition& visiblePosition) |
| { |
| return rightPositionOfAlgorithm<EditingStrategy>(visiblePosition); |
| } |
| |
| VisiblePositionInFlatTree rightPositionOf(const VisiblePositionInFlatTree& visiblePosition) |
| { |
| return rightPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> nextPositionOfAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| const VisiblePositionTemplate<Strategy> next = createVisiblePosition(nextVisuallyDistinctCandidate(visiblePosition.deepEquivalent()), visiblePosition.affinity()); |
| |
| switch (rule) { |
| case CanCrossEditingBoundary: |
| return next; |
| case CannotCrossEditingBoundary: |
| return honorEditingBoundaryAtOrAfter(next, visiblePosition.deepEquivalent()); |
| case CanSkipOverEditingBoundary: |
| return skipToEndOfEditingBoundary(next, visiblePosition.deepEquivalent()); |
| } |
| NOTREACHED(); |
| return honorEditingBoundaryAtOrAfter(next, visiblePosition.deepEquivalent()); |
| } |
| |
| VisiblePosition nextPositionOf(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| return nextPositionOfAlgorithm<EditingStrategy>(visiblePosition, rule); |
| } |
| |
| VisiblePositionInFlatTree nextPositionOf(const VisiblePositionInFlatTree& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| return nextPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition, rule); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> skipToStartOfEditingBoundary(const VisiblePositionTemplate<Strategy>& pos, const PositionTemplate<Strategy>& anchor) |
| { |
| if (pos.isNull()) |
| return pos; |
| |
| ContainerNode* highestRoot = highestEditableRoot(anchor); |
| ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent()); |
| |
| // Return |pos| itself if the two are from the very same editable region, or |
| // both are non-editable. |
| if (highestRootOfPos == highestRoot) |
| return pos; |
| |
| // If this is not editable but |pos| has an editable root, skip to the start |
| if (!highestRoot && highestRootOfPos) |
| return createVisiblePosition(previousVisuallyDistinctCandidate(PositionTemplate<Strategy>(highestRootOfPos, PositionAnchorType::BeforeAnchor).parentAnchoredEquivalent())); |
| |
| // That must mean that |pos| is not editable. Return the last position |
| // before |pos| that is in the same editable region as this position |
| DCHECK(highestRoot); |
| return lastEditableVisiblePositionBeforePositionInRoot(pos.deepEquivalent(), *highestRoot); |
| } |
| |
| template <typename Strategy> |
| static VisiblePositionTemplate<Strategy> previousPositionOfAlgorithm(const VisiblePositionTemplate<Strategy>& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| const PositionTemplate<Strategy> pos = previousVisuallyDistinctCandidate(visiblePosition.deepEquivalent()); |
| |
| // return null visible position if there is no previous visible position |
| if (pos.atStartOfTree()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| // we should always be able to make the affinity |TextAffinity::Downstream|, |
| // because going previous from an |TextAffinity::Upstream| position can |
| // never yield another |TextAffinity::Upstream position| (unless line wrap |
| // length is 0!). |
| const VisiblePositionTemplate<Strategy> prev = createVisiblePosition(pos); |
| if (prev.deepEquivalent() == visiblePosition.deepEquivalent()) |
| return VisiblePositionTemplate<Strategy>(); |
| |
| switch (rule) { |
| case CanCrossEditingBoundary: |
| return prev; |
| case CannotCrossEditingBoundary: |
| return honorEditingBoundaryAtOrBefore(prev, visiblePosition.deepEquivalent()); |
| case CanSkipOverEditingBoundary: |
| return skipToStartOfEditingBoundary(prev, visiblePosition.deepEquivalent()); |
| } |
| |
| NOTREACHED(); |
| return honorEditingBoundaryAtOrBefore(prev, visiblePosition.deepEquivalent()); |
| } |
| |
| VisiblePosition previousPositionOf(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| return previousPositionOfAlgorithm<EditingStrategy>(visiblePosition, rule); |
| } |
| |
| VisiblePositionInFlatTree previousPositionOf(const VisiblePositionInFlatTree& visiblePosition, EditingBoundaryCrossingRule rule) |
| { |
| return previousPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition, rule); |
| } |
| |
| } // namespace blink |