/* * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "visible_units.h" #include "Document.h" #include "Element.h" #include "HTMLNames.h" #include "InlineTextBox.h" #include "Position.h" #include "RenderBlock.h" #include "RenderObject.h" #include "RenderedPosition.h" #include "Text.h" #include "TextBoundaries.h" #include "TextBreakIterator.h" #include "TextIterator.h" #include "VisiblePosition.h" #include "VisibleSelection.h" #include "htmlediting.h" #include namespace WebCore { using namespace HTMLNames; using namespace WTF::Unicode; static Node* previousLeafWithSameEditability(Node* node, EditableType editableType) { bool editable = node->rendererIsEditable(editableType); node = node->previousLeafNode(); while (node) { if (editable == node->rendererIsEditable(editableType)) return node; node = node->previousLeafNode(); } return 0; } static Node* enclosingNodeWithNonInlineRenderer(Node* node) { for (; node; node = node->parentNode()) { if (node->renderer() && !node->renderer()->isInline()) return node; } return 0; } static Node* nextLeafWithSameEditability(Node* node, int offset) { bool editable = node->rendererIsEditable(); ASSERT(offset >= 0); Node* child = node->childNode(offset); node = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode(); while (node) { if (editable == node->rendererIsEditable()) return node; node = node->nextLeafNode(); } return 0; } static Node* nextLeafWithSameEditability(Node* node, EditableType editableType = ContentIsEditable) { if (!node) return 0; bool editable = node->rendererIsEditable(editableType); node = node->nextLeafNode(); while (node) { if (editable == node->rendererIsEditable(editableType)) return node; node = node->nextLeafNode(); } return 0; } // FIXME: consolidate with code in previousLinePosition. static const RootInlineBox* previousRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition) { Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable); if (!box->renderer() || !box->renderer()->node()) return 0; Node* node = box->renderer()->node(); Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); Node* previousNode = previousLeafWithSameEditability(node, ContentIsEditable); while (previousNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(previousNode)) previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable); while (previousNode && !previousNode->isShadowRoot()) { if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode), ContentIsEditable) != highestRoot) break; Position pos = previousNode->hasTagName(brTag) ? positionBeforeNode(previousNode) : createLegacyEditingPosition(previousNode, caretMaxOffset(previousNode)); if (pos.isCandidate()) { RenderedPosition renderedPos(pos, DOWNSTREAM); RootInlineBox* root = renderedPos.rootBox(); if (root) return root; } previousNode = previousLeafWithSameEditability(previousNode, ContentIsEditable); } return 0; } static const RootInlineBox* nextRootInlineBox(const InlineBox* box, const VisiblePosition& visiblePosition) { Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent(), ContentIsEditable); if (!box->renderer() || !box->renderer()->node()) return 0; Node* node = box->renderer()->node(); Node* enclosingBlockNode = enclosingNodeWithNonInlineRenderer(node); Node* nextNode = nextLeafWithSameEditability(node, ContentIsEditable); while (nextNode && enclosingBlockNode == enclosingNodeWithNonInlineRenderer(nextNode)) nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); while (nextNode && !nextNode->isShadowRoot()) { if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode), ContentIsEditable) != highestRoot) break; Position pos; pos = createLegacyEditingPosition(nextNode, caretMinOffset(nextNode)); if (pos.isCandidate()) { RenderedPosition renderedPos(pos, DOWNSTREAM); RootInlineBox* root = renderedPos.rootBox(); if (root) return root; } nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable); } return 0; } 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& collectBoxes(const RootInlineBox*); int boxIndexInLeaves(const InlineTextBox*); const RootInlineBox* m_rootInlineBox; Vector 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& 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) { 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) { const RootInlineBox* previousRoot = previousRootInlineBox(startBox, visiblePosition); 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) { const RootInlineBox* nextRoot = nextRootInlineBox(startBox, visiblePosition); 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& 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(); string.append(previousBox->textRenderer()->text()->characters() + previousBox->start(), previousBoxLength); len += previousBoxLength; } string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); len += textBox->len(); return wordBreakIterator(string.data(), len); } static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(const VisiblePosition& visiblePosition, const InlineTextBox* textBox, bool& nextBoxInDifferentBlock, Vector& 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(); string.append(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); len += textBox->len(); if (nextBox) { string.append(nextBox->textRenderer()->text()->characters() + 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 : isTextBreak(iter, position); if (!boundary) return false; textBreakFollowing(iter, 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 = isTextBreak(iter, 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 string; while (1) { VisiblePosition adjacentCharacterPosition = direction == MoveRight ? current.right(true) : current.left(true); if (adjacentCharacterPosition == current || adjacentCharacterPosition.isNull()) return VisiblePosition(); InlineBox* box; int offsetInBox; adjacentCharacterPosition.deepEquivalent().getInlineBoxAndOffset(UPSTREAM, box, offsetInBox); if (!box) break; if (!box->isInlineTextBox()) { current = adjacentCharacterPosition; continue; } InlineTextBox* textBox = toInlineTextBox(box); int previousBoxLength = 0; bool previousBoxInDifferentBlock = false; bool nextBoxInDifferentBlock = false; bool movingIntoNewBox = previouslyVisitedBox != box; if (offsetInBox == box->caretMinOffset()) iter = wordBreakIteratorForMinOffsetBoundary(visiblePosition, textBox, previousBoxLength, previousBoxInDifferentBlock, string, leafBoxes); else if (offsetInBox == box->caretMaxOffset()) iter = wordBreakIteratorForMaxOffsetBoundary(visiblePosition, textBox, nextBoxInDifferentBlock, string, leafBoxes); else if (movingIntoNewBox) { iter = wordBreakIterator(textBox->textRenderer()->text()->characters() + textBox->start(), textBox->len()); previouslyVisitedBox = box; } if (!iter) break; textBreakFirst(iter); int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength; bool isWordBreak; bool boxHasSameDirectionalityAsBlock = box->direction() == blockDirection; bool movingBackward = (direction == MoveLeft && box->direction() == LTR) || (direction == MoveRight && box->direction() == RTL); if ((skipsSpaceWhenMovingRight && boxHasSameDirectionalityAsBlock) || (!skipsSpaceWhenMovingRight && movingBackward)) { bool logicalStartInRenderer = offsetInBox == static_cast(textBox->start()) && previousBoxInDifferentBlock; isWordBreak = isLogicalStartOfWord(iter, offsetInIterator, logicalStartInRenderer); } else { bool logicalEndInRenderer = offsetInBox == static_cast(textBox->start() + textBox->len()) && nextBoxInDifferentBlock; isWordBreak = islogicalEndOfWord(iter, offsetInIterator, logicalEndInRenderer); } if (isWordBreak) return adjacentCharacterPosition; current = adjacentCharacterPosition; } return VisiblePosition(); } VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) { VisiblePosition leftWordBreak = visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight); leftWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(leftWordBreak); // FIXME: How should we handle a non-editable position? if (leftWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); leftWordBreak = blockDirection == LTR ? startOfEditableContent(visiblePosition) : endOfEditableContent(visiblePosition); } return leftWordBreak; } VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition, bool skipsSpaceWhenMovingRight) { VisiblePosition rightWordBreak = visualWordPosition(visiblePosition, MoveRight, skipsSpaceWhenMovingRight); rightWordBreak = visiblePosition.honorEditingBoundaryAtOrBefore(rightWordBreak); // FIXME: How should we handle a non-editable position? if (rightWordBreak.isNull() && isEditablePosition(visiblePosition.deepEquivalent())) { TextDirection blockDirection = directionOfEnclosingBlock(visiblePosition.deepEquivalent()); rightWordBreak = blockDirection == LTR ? endOfEditableContent(visiblePosition) : startOfEditableContent(visiblePosition); } return rightWordBreak; } enum BoundarySearchContextAvailability { DontHaveMoreContext, MayHaveMoreContext }; typedef unsigned (*BoundarySearchFunction)(const UChar*, unsigned length, unsigned offset, BoundarySearchContextAvailability, bool& needMoreContext); static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); Node* boundary = pos.parentEditingBoundary(); if (!boundary) return VisiblePosition(); Document* d = boundary->document(); Position start = createLegacyEditingPosition(boundary, 0).parentAnchoredEquivalent(); Position end = pos.parentAnchoredEquivalent(); RefPtr searchRange = Range::create(d); Vector string; unsigned suffixLength = 0; ExceptionCode ec = 0; if (requiresContextForWordBoundary(c.characterBefore())) { RefPtr forwardsScanRange(d->createRange()); forwardsScanRange->setEndAfter(boundary, ec); forwardsScanRange->setStart(end.deprecatedNode(), end.deprecatedEditingOffset(), ec); TextIterator forwardsIterator(forwardsScanRange.get()); while (!forwardsIterator.atEnd()) { const UChar* characters = forwardsIterator.characters(); int length = forwardsIterator.length(); int i = endOfFirstWordBoundaryContext(characters, length); string.append(characters, i); suffixLength += i; if (i < length) break; forwardsIterator.advance(); } } searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), ec); searchRange->setEnd(end.deprecatedNode(), end.deprecatedEditingOffset(), ec); ASSERT(!ec); if (ec) return VisiblePosition(); SimplifiedBackwardsTextIterator it(searchRange.get()); unsigned next = 0; bool inTextSecurityMode = start.deprecatedNode() && start.deprecatedNode()->renderer() && start.deprecatedNode()->renderer()->style()->textSecurity() != TSNONE; bool needMoreContext = false; while (!it.atEnd()) { // iterate to get chunks until the searchFunction returns a non-zero value. if (!inTextSecurityMode) string.prepend(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString.fill('x'); string.prepend(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); if (next != 0) break; it.advance(); } if (needMoreContext) { // The last search returned the beginning of the buffer and asked for more context, // but there is no earlier text. Force a search with what's available. next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } if (!next) return VisiblePosition(it.atEnd() ? it.range()->startPosition() : pos, DOWNSTREAM); Node* node = it.range()->startContainer(ec); if ((node->isTextNode() && static_cast(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next)) // The next variable contains a usable index into a text node return VisiblePosition(createLegacyEditingPosition(node, next), DOWNSTREAM); // Use the character iterator to translate the next value into a DOM position. BackwardsCharacterIterator charIt(searchRange.get()); charIt.advance(string.size() - suffixLength - next); // FIXME: charIt can get out of shadow host. return VisiblePosition(charIt.range()->endPosition(), DOWNSTREAM); } static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); Node* boundary = pos.parentEditingBoundary(); if (!boundary) return VisiblePosition(); Document* d = boundary->document(); RefPtr searchRange(d->createRange()); Position start(pos.parentAnchoredEquivalent()); Vector string; unsigned prefixLength = 0; ExceptionCode ec = 0; if (requiresContextForWordBoundary(c.characterAfter())) { RefPtr backwardsScanRange(d->createRange()); backwardsScanRange->setEnd(start.deprecatedNode(), start.deprecatedEditingOffset(), ec); SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); while (!backwardsIterator.atEnd()) { const UChar* characters = backwardsIterator.characters(); int length = backwardsIterator.length(); int i = startOfLastWordBoundaryContext(characters, length); string.prepend(characters + i, length - i); prefixLength += length - i; if (i > 0) break; backwardsIterator.advance(); } } searchRange->selectNodeContents(boundary, ec); searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), ec); TextIterator it(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); unsigned next = 0; bool inTextSecurityMode = start.deprecatedNode() && start.deprecatedNode()->renderer() && start.deprecatedNode()->renderer()->style()->textSecurity() != TSNONE; 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. if (!inTextSecurityMode) string.append(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString.fill('x'); string.append(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size(), prefixLength, MayHaveMoreContext, needMoreContext); if (next != string.size()) break; it.advance(); } if (needMoreContext) { // The last search returned the end of the buffer and asked for more context, // but there is no further text. Force a search with what's available. next = searchFunction(string.data(), string.size(), prefixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } if (it.atEnd() && next == string.size()) { pos = it.range()->startPosition(); } else if (next != prefixLength) { // Use the character iterator to translate the next value into a DOM position. CharacterIterator charIt(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions); charIt.advance(next - prefixLength - 1); RefPtr characterRange = charIt.range(); pos = characterRange->endPosition(); if (*charIt.characters() == '\n') { // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) VisiblePosition visPos = VisiblePosition(pos); if (visPos == VisiblePosition(characterRange->startPosition())) { charIt.advance(1); pos = charIt.range()->startPosition(); } } } // generate VisiblePosition, use UPSTREAM affinity if possible return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); } // --------- static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { ASSERT(offset); if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { needMoreContext = true; return 0; } needMoreContext = false; int start, end; U16_BACK_1(characters, 0, offset); findWordBoundary(characters, length, offset, &start, &end); return start; } VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) { // FIXME: This returns a null VP for c at the start of the document // and side == LeftWordIfOnBoundary VisiblePosition p = c; if (side == RightWordIfOnBoundary) { // at paragraph end, the startofWord is the current position if (isEndOfParagraph(c)) return c; p = c.next(); if (p.isNull()) return c; } return previousBoundary(p, startWordBoundary); } static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { ASSERT(offset <= length); if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast(length - offset)) { needMoreContext = true; return length; } needMoreContext = false; int start, end; findWordBoundary(characters, length, offset, &start, &end); return end; } VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) { VisiblePosition p = c; if (side == LeftWordIfOnBoundary) { if (isStartOfParagraph(c)) return c; p = c.previous(); if (p.isNull()) return c; } else if (isEndOfParagraph(c)) return c; return nextBoundary(p, endWordBoundary); } static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) { needMoreContext = true; return 0; } needMoreContext = false; return findNextWordFromIndex(characters, length, offset, false); } VisiblePosition previousWordPosition(const VisiblePosition &c) { VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary); return c.honorEditingBoundaryAtOrBefore(prev); } static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext) { if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast(length - offset)) { needMoreContext = true; return length; } needMoreContext = false; return findNextWordFromIndex(characters, length, offset, true); } VisiblePosition nextWordPosition(const VisiblePosition &c) { VisiblePosition next = nextBoundary(c, nextWordPositionBoundary); return c.honorEditingBoundaryAtOrAfter(next); } bool isStartOfWord(const VisiblePosition& p) { return p.isNotNull() && p == startOfWord(p, RightWordIfOnBoundary); } // --------- enum LineEndpointComputationMode { UseLogicalOrdering, UseInlineBoxOrdering }; static VisiblePosition startPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) { if (c.isNull()) return VisiblePosition(); RootInlineBox* rootBox = RenderedPosition(c).rootBox(); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) return c; return VisiblePosition(); } Node* startNode; InlineBox* startBox; if (mode == UseLogicalOrdering) { startNode = rootBox->getLogicalStartBoxWithNode(startBox); if (!startNode) return VisiblePosition(); } else { // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, // and so cannot be represented by a VisiblePosition. Use whatever follows instead. startBox = rootBox->firstLeafChild(); while (true) { if (!startBox) return VisiblePosition(); RenderObject* startRenderer = startBox->renderer(); if (!startRenderer) return VisiblePosition(); startNode = startRenderer->node(); if (startNode) break; startBox = startBox->nextLeafChild(); } } return startNode->isTextNode() ? Position(toText(startNode), toInlineTextBox(startBox)->start()) : positionBeforeNode(startNode); } static VisiblePosition startOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) { // TODO: this is the current behavior that might need to be fixed. // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. VisiblePosition visPos = startPositionForLine(c, mode); if (mode == UseLogicalOrdering) { if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) return firstPositionInNode(editableRoot); } } return c.honorEditingBoundaryAtOrBefore(visPos); } // FIXME: Rename this function to reflect the fact it ignores bidi levels. VisiblePosition startOfLine(const VisiblePosition& currentPosition) { return startOfLine(currentPosition, UseInlineBoxOrdering); } VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition) { return startOfLine(currentPosition, UseLogicalOrdering); } static VisiblePosition endPositionForLine(const VisiblePosition& c, LineEndpointComputationMode mode) { if (c.isNull()) return VisiblePosition(); RootInlineBox* rootBox = RenderedPosition(c).rootBox(); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.deprecatedNode()->renderer() && p.deprecatedNode()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) return c; return VisiblePosition(); } Node* endNode; InlineBox* endBox; if (mode == UseLogicalOrdering) { endNode = rootBox->getLogicalEndBoxWithNode(endBox); if (!endNode) return VisiblePosition(); } else { // Generated content (e.g. list markers and CSS :before and :after pseudoelements) have no corresponding DOM element, // and so cannot be represented by a VisiblePosition. Use whatever precedes instead. endBox = rootBox->lastLeafChild(); while (true) { if (!endBox) return VisiblePosition(); RenderObject* endRenderer = endBox->renderer(); if (!endRenderer) return VisiblePosition(); endNode = endRenderer->node(); if (endNode) break; endBox = endBox->prevLeafChild(); } } Position pos; if (endNode->hasTagName(brTag)) pos = positionBeforeNode(endNode); else if (endBox->isInlineTextBox() && endNode->isTextNode()) { InlineTextBox* endTextBox = toInlineTextBox(endBox); int endOffset = endTextBox->start(); if (!endTextBox->isLineBreak()) endOffset += endTextBox->len(); pos = Position(toText(endNode), endOffset); } else pos = positionAfterNode(endNode); return VisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE); } static bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b) { return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b); } static VisiblePosition endOfLine(const VisiblePosition& c, LineEndpointComputationMode mode) { // TODO: this is the current behavior that might need to be fixed. // Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail. VisiblePosition visPos = endPositionForLine(c, mode); if (mode == UseLogicalOrdering) { // Make sure the end of line is at the same line as the given input position. For a wrapping line, the logical end // position for the not-last-2-lines might incorrectly hand back the logical beginning of the next line. // For example,
abcdefg abcdefg abcdefg // a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg
// In this case, use the previous position of the computed logical end position. if (!inSameLogicalLine(c, visPos)) visPos = visPos.previous(); if (Node* editableRoot = highestEditableRoot(c.deepEquivalent())) { if (!editableRoot->contains(visPos.deepEquivalent().containerNode())) return lastPositionInNode(editableRoot); } return c.honorEditingBoundaryAtOrAfter(visPos); } // Make sure the end of line is at the same line as the given input position. Else use the previous position to // obtain end of line. This condition happens when the input position is before the space character at the end // of a soft-wrapped non-editable line. In this scenario, endPositionForLine would incorrectly hand back a position // in the next line instead. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space style // versus lines without that style, which would break before a space by default. if (!inSameLine(c, visPos)) { visPos = c.previous(); if (visPos.isNull()) return VisiblePosition(); visPos = endPositionForLine(visPos, UseInlineBoxOrdering); } return c.honorEditingBoundaryAtOrAfter(visPos); } // FIXME: Rename this function to reflect the fact it ignores bidi levels. VisiblePosition endOfLine(const VisiblePosition& currentPosition) { return endOfLine(currentPosition, UseInlineBoxOrdering); } VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition) { return endOfLine(currentPosition, UseLogicalOrdering); } bool inSameLine(const VisiblePosition &a, const VisiblePosition &b) { return a.isNotNull() && startOfLine(a) == startOfLine(b); } bool isStartOfLine(const VisiblePosition &p) { return p.isNotNull() && p == startOfLine(p); } bool isEndOfLine(const VisiblePosition &p) { return p.isNotNull() && p == endOfLine(p); } static inline IntPoint absoluteLineDirectionPointToLocalPointInBlock(RootInlineBox* root, int lineDirectionPoint) { ASSERT(root); RenderBlock* containingBlock = root->block(); FloatPoint absoluteBlockPoint = containingBlock->localToAbsolute(FloatPoint()); if (containingBlock->hasOverflowClip()) absoluteBlockPoint -= containingBlock->scrolledContentOffset(); if (root->block()->isHorizontalWritingMode()) return IntPoint(lineDirectionPoint - absoluteBlockPoint.x(), root->blockDirectionPointInLine()); return IntPoint(root->selectionTop(), lineDirectionPoint - absoluteBlockPoint.y()); } VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) { Position p = visiblePosition.deepEquivalent(); Node* node = p.deprecatedNode(); Node* highestRoot = highestEditableRoot(p, editableType); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->prevRootBox(); // We want to skip zero height boxes. // This could happen in case it is a TrailingFloatsRootInlineBox. if (!root || !root->logicalHeight() || !root->firstLeafChild()) root = 0; } if (!root) { // This containing editable block does not have a previous line. // Need to move back to previous containing editable block in this root editable // block and find the last root line box in that block. Node* startBlock = enclosingNodeWithNonInlineRenderer(node); Node* n = previousLeafWithSameEditability(node, editableType); while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = previousLeafWithSameEditability(n, editableType); while (n) { if (highestEditableRoot(firstPositionInOrBeforeNode(n), editableType) != highestRoot) break; Position pos = n->hasTagName(brTag) ? positionBeforeNode(n) : createLegacyEditingPosition(n, caretMaxOffset(n)); if (pos.isCandidate()) { pos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // previous root line box found root = box->root(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = previousLeafWithSameEditability(n, editableType); } } if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); RenderObject* renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) return positionInParentBeforeNode(node); return renderer->positionForPoint(pointInLine); } // Could not find a previous line. This means we must already be on the first line. // Move to the start of the content in this block, which effectively moves us // to the start of the line we're on. Element* rootElement = node->rendererIsEditable(editableType) ? node->rootEditableElement(editableType) : node->document()->documentElement(); if (!rootElement) return VisiblePosition(); return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); } VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int lineDirectionPoint, EditableType editableType) { Position p = visiblePosition.deepEquivalent(); Node* node = p.deprecatedNode(); Node* highestRoot = highestEditableRoot(p, editableType); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->nextRootBox(); // We want to skip zero height boxes. // This could happen in case it is a TrailingFloatsRootInlineBox. if (!root || !root->logicalHeight() || !root->firstLeafChild()) root = 0; } if (!root) { // This containing editable block does not have a next line. // Need to move forward to next containing editable block in this root editable // block and find the first root line box in that block. Node* startBlock = enclosingNodeWithNonInlineRenderer(node); Node* n = nextLeafWithSameEditability(node, p.deprecatedEditingOffset()); while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = nextLeafWithSameEditability(n, editableType); while (n) { if (highestEditableRoot(firstPositionInOrBeforeNode(n), editableType) != highestRoot) break; Position pos = createLegacyEditingPosition(n, caretMinOffset(n)); if (pos.isCandidate()) { ASSERT(n->renderer()); pos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // next root line box found root = box->root(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = nextLeafWithSameEditability(n, editableType); } } if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. IntPoint pointInLine = absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint); RenderObject* renderer = root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) return positionInParentBeforeNode(node); return renderer->positionForPoint(pointInLine); } // Could not find a next line. This means we must already be on the last line. // Move to the end of the content in this block, which effectively moves us // to the end of the line we're on. Element* rootElement = node->rendererIsEditable(editableType) ? node->rootEditableElement(editableType) : node->document()->documentElement(); if (!rootElement) return VisiblePosition(); return VisiblePosition(lastPositionInNode(rootElement), DOWNSTREAM); } // --------- static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) { TextBreakIterator* iterator = sentenceBreakIterator(characters, length); // FIXME: The following function can return -1; we don't handle that. return textBreakPreceding(iterator, length); } VisiblePosition startOfSentence(const VisiblePosition &c) { return previousBoundary(c, startSentenceBoundary); } static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) { TextBreakIterator* iterator = sentenceBreakIterator(characters, length); return textBreakNext(iterator); } // FIXME: This includes the space after the punctuation that marks the end of the sentence. VisiblePosition endOfSentence(const VisiblePosition &c) { return nextBoundary(c, endSentenceBoundary); } static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) { // FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right. TextBreakIterator* iterator = sentenceBreakIterator(characters, length); // FIXME: The following function can return -1; we don't handle that. return textBreakPreceding(iterator, length); } VisiblePosition previousSentencePosition(const VisiblePosition &c) { VisiblePosition prev = previousBoundary(c, previousSentencePositionBoundary); return c.honorEditingBoundaryAtOrBefore(prev); } static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned, BoundarySearchContextAvailability, bool&) { // FIXME: This is identical to endSentenceBoundary. This isn't right, it needs to // move to the equivlant position in the following sentence. TextBreakIterator* iterator = sentenceBreakIterator(characters, length); return textBreakFollowing(iterator, 0); } VisiblePosition nextSentencePosition(const VisiblePosition &c) { VisiblePosition next = nextBoundary(c, nextSentencePositionBoundary); return c.honorEditingBoundaryAtOrAfter(next); } VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule) { Position p = c.deepEquivalent(); Node* startNode = p.deprecatedNode(); if (!startNode) return VisiblePosition(); if (isRenderedAsNonInlineTableImageOrHR(startNode)) return positionBeforeNode(startNode); Node* startBlock = enclosingBlock(startNode); Node* node = startNode; Node* highestRoot = highestEditableRoot(p); int offset = p.deprecatedEditingOffset(); Position::AnchorType type = p.anchorType(); Node* n = startNode; while (n) { if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable()) break; if (boundaryCrossingRule == CanSkipOverEditingBoundary) { while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) n = n->traversePreviousNodePostOrder(startBlock); if (!n || !n->isDescendantOf(highestRoot)) break; } RenderObject *r = n->renderer(); if (!r) { n = n->traversePreviousNodePostOrder(startBlock); continue; } RenderStyle *style = r->style(); if (style->visibility() != VISIBLE) { n = n->traversePreviousNodePostOrder(startBlock); continue; } if (r->isBR() || isBlock(n)) break; if (r->isText() && toRenderText(r)->renderedTextLength()) { ASSERT(n->isTextNode()); type = Position::PositionIsOffsetInAnchor; if (style->preserveNewline()) { const UChar* chars = toRenderText(r)->characters(); int i = toRenderText(r)->textLength(); int o = offset; if (n == startNode && o < i) i = max(0, o); while (--i >= 0) { if (chars[i] == '\n') return VisiblePosition(Position(toText(n), i + 1), DOWNSTREAM); } } node = n; offset = 0; n = n->traversePreviousNodePostOrder(startBlock); } else if (editingIgnoresContent(n) || isTableElement(n)) { node = n; type = Position::PositionIsBeforeAnchor; n = n->previousSibling() ? n->previousSibling() : n->traversePreviousNodePostOrder(startBlock); } else n = n->traversePreviousNodePostOrder(startBlock); } if (type == Position::PositionIsOffsetInAnchor) { ASSERT(type == Position::PositionIsOffsetInAnchor || !offset); return VisiblePosition(Position(node, offset, type), DOWNSTREAM); } return VisiblePosition(Position(node, type), DOWNSTREAM); } VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossingRule boundaryCrossingRule) { if (c.isNull()) return VisiblePosition(); Position p = c.deepEquivalent(); Node* startNode = p.deprecatedNode(); if (isRenderedAsNonInlineTableImageOrHR(startNode)) return positionAfterNode(startNode); Node* startBlock = enclosingBlock(startNode); Node* stayInsideBlock = startBlock; Node* node = startNode; Node* highestRoot = highestEditableRoot(p); int offset = p.deprecatedEditingOffset(); Position::AnchorType type = p.anchorType(); Node* n = startNode; while (n) { if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable()) break; if (boundaryCrossingRule == CanSkipOverEditingBoundary) { while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) n = n->traverseNextNode(stayInsideBlock); if (!n || !n->isDescendantOf(highestRoot)) break; } RenderObject *r = n->renderer(); if (!r) { n = n->traverseNextNode(stayInsideBlock); continue; } RenderStyle *style = r->style(); if (style->visibility() != VISIBLE) { n = n->traverseNextNode(stayInsideBlock); continue; } if (r->isBR() || isBlock(n)) break; // FIXME: We avoid returning a position where the renderer can't accept the caret. if (r->isText() && toRenderText(r)->renderedTextLength()) { ASSERT(n->isTextNode()); int length = toRenderText(r)->textLength(); type = Position::PositionIsOffsetInAnchor; if (style->preserveNewline()) { const UChar* chars = toRenderText(r)->characters(); int o = n == startNode ? offset : 0; for (int i = o; i < length; ++i) { if (chars[i] == '\n') return VisiblePosition(Position(toText(n), i), DOWNSTREAM); } } node = n; offset = r->caretMaxOffset(); n = n->traverseNextNode(stayInsideBlock); } else if (editingIgnoresContent(n) || isTableElement(n)) { node = n; type = Position::PositionIsAfterAnchor; n = n->traverseNextSibling(stayInsideBlock); } else n = n->traverseNextNode(stayInsideBlock); } if (type == Position::PositionIsOffsetInAnchor) return VisiblePosition(Position(node, offset, type), DOWNSTREAM); return VisiblePosition(Position(node, type), DOWNSTREAM); } // FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) { VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary)); VisiblePosition afterParagraphEnd(paragraphEnd.next(CannotCrossEditingBoundary)); // The position after the last position in the last cell of a table // is not the start of the next paragraph. if (isFirstPositionAfterTable(afterParagraphEnd)) return afterParagraphEnd.next(CannotCrossEditingBoundary); return afterParagraphEnd; } bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b, EditingBoundaryCrossingRule boundaryCrossingRule) { return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule) == startOfParagraph(b, boundaryCrossingRule); } bool isStartOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == startOfParagraph(pos, boundaryCrossingRule); } bool isEndOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) { return pos.isNotNull() && pos == endOfParagraph(pos, boundaryCrossingRule); } VisiblePosition previousParagraphPosition(const VisiblePosition& p, int x) { VisiblePosition pos = p; do { VisiblePosition n = previousLinePosition(pos, x); if (n.isNull() || n == pos) break; pos = n; } while (inSameParagraph(p, pos)); return pos; } VisiblePosition nextParagraphPosition(const VisiblePosition& p, int x) { VisiblePosition pos = p; do { VisiblePosition n = nextLinePosition(pos, x); if (n.isNull() || n == pos) break; pos = n; } while (inSameParagraph(p, pos)); return pos; } // --------- VisiblePosition startOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) { Position position = visiblePosition.deepEquivalent(); Node* startBlock; if (!position.containerNode() || !(startBlock = enclosingBlock(position.containerNode(), rule))) return VisiblePosition(); return firstPositionInNode(startBlock); } VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) { Position position = visiblePosition.deepEquivalent(); Node* endBlock; if (!position.containerNode() || !(endBlock = enclosingBlock(position.containerNode(), rule))) return VisiblePosition(); return lastPositionInNode(endBlock); } bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b) { return !a.isNull() && enclosingBlock(a.deepEquivalent().containerNode()) == enclosingBlock(b.deepEquivalent().containerNode()); } bool isStartOfBlock(const VisiblePosition &pos) { return pos.isNotNull() && pos == startOfBlock(pos, CanCrossEditingBoundary); } bool isEndOfBlock(const VisiblePosition &pos) { return pos.isNotNull() && pos == endOfBlock(pos, CanCrossEditingBoundary); } // --------- VisiblePosition startOfDocument(const Node* node) { if (!node || !node->document() || !node->document()->documentElement()) return VisiblePosition(); return VisiblePosition(firstPositionInNode(node->document()->documentElement()), DOWNSTREAM); } VisiblePosition startOfDocument(const VisiblePosition &c) { return startOfDocument(c.deepEquivalent().deprecatedNode()); } VisiblePosition endOfDocument(const Node* node) { if (!node || !node->document() || !node->document()->documentElement()) return VisiblePosition(); Element* doc = node->document()->documentElement(); return VisiblePosition(lastPositionInNode(doc), DOWNSTREAM); } VisiblePosition endOfDocument(const VisiblePosition &c) { return endOfDocument(c.deepEquivalent().deprecatedNode()); } bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b) { Position ap = a.deepEquivalent(); Node* an = ap.deprecatedNode(); if (!an) return false; Position bp = b.deepEquivalent(); Node* bn = bp.deprecatedNode(); if (an == bn) return true; return an->document() == bn->document(); } bool isStartOfDocument(const VisiblePosition &p) { return p.isNotNull() && p.previous().isNull(); } bool isEndOfDocument(const VisiblePosition &p) { return p.isNotNull() && p.next().isNull(); } // --------- VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) { Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); if (!highestRoot) return VisiblePosition(); return firstPositionInNode(highestRoot); } VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) { Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent()); if (!highestRoot) return VisiblePosition(); return lastPositionInNode(highestRoot); } 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); } }