summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/editing/visible_units.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/editing/visible_units.cpp
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/editing/visible_units.cpp')
-rw-r--r--Source/WebCore/editing/visible_units.cpp1210
1 files changed, 1210 insertions, 0 deletions
diff --git a/Source/WebCore/editing/visible_units.cpp b/Source/WebCore/editing/visible_units.cpp
new file mode 100644
index 0000000..7bb1515
--- /dev/null
+++ b/Source/WebCore/editing/visible_units.cpp
@@ -0,0 +1,1210 @@
+/*
+ * 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 "Position.h"
+#include "RenderBlock.h"
+#include "RenderLayer.h"
+#include "RenderObject.h"
+#include "TextBoundaries.h"
+#include "TextBreakIterator.h"
+#include "TextIterator.h"
+#include "VisiblePosition.h"
+#include "htmlediting.h"
+#include <wtf/unicode/Unicode.h>
+
+namespace WebCore {
+
+using namespace HTMLNames;
+using namespace WTF::Unicode;
+
+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 = rangeCompliantEquivalent(Position(boundary, 0));
+ Position end = rangeCompliantEquivalent(pos);
+ RefPtr<Range> searchRange = Range::create(d);
+
+ Vector<UChar, 1024> string;
+ unsigned suffixLength = 0;
+
+ ExceptionCode ec = 0;
+ if (requiresContextForWordBoundary(c.characterBefore())) {
+ RefPtr<Range> forwardsScanRange(d->createRange());
+ forwardsScanRange->setEndAfter(boundary, ec);
+ forwardsScanRange->setStart(end.node(), 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.node(), start.deprecatedEditingOffset(), ec);
+ searchRange->setEnd(end.node(), end.deprecatedEditingOffset(), ec);
+
+ ASSERT(!ec);
+ if (ec)
+ return VisiblePosition();
+
+ SimplifiedBackwardsTextIterator it(searchRange.get(), TextIteratorEndsAtEditingBoundary);
+ unsigned next = 0;
+ bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->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 = iteratorString.impl()->secure('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 (it.atEnd() && next == 0) {
+ pos = it.range()->startPosition();
+ } else if (next != 0) {
+ Node *node = it.range()->startContainer(ec);
+ if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next))
+ // The next variable contains a usable index into a text node
+ pos = Position(node, next);
+ else {
+ // Use the character iterator to translate the next value into a DOM position.
+ BackwardsCharacterIterator charIt(searchRange.get(), TextIteratorEndsAtEditingBoundary);
+ charIt.advance(string.size() - suffixLength - next);
+ pos = charIt.range()->endPosition();
+ }
+ }
+
+ return VisiblePosition(pos, DOWNSTREAM);
+}
+
+static VisiblePosition nextBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction)
+{
+ Position pos = c.deepEquivalent();
+ Node* boundary = pos.parentEditingBoundary();
+ if (!boundary)
+ return VisiblePosition();
+
+ Document* d = boundary->document();
+ RefPtr<Range> searchRange(d->createRange());
+ Position start(rangeCompliantEquivalent(pos));
+
+ Vector<UChar, 1024> string;
+ unsigned prefixLength = 0;
+
+ ExceptionCode ec = 0;
+ if (requiresContextForWordBoundary(c.characterAfter())) {
+ RefPtr<Range> backwardsScanRange(d->createRange());
+ backwardsScanRange->setEnd(start.node(), 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.node(), start.deprecatedEditingOffset(), ec);
+ TextIterator it(searchRange.get(), TextIteratorEmitsCharactersBetweenAllVisiblePositions);
+ unsigned next = 0;
+ bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->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 = iteratorString.impl()->secure('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<Range> 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 bool canHaveCursor(RenderObject* o)
+{
+ return (o->isText() && toRenderText(o)->linesBoundingBox().height())
+ || (o->isBox() && toRenderBox(o)->borderBoundingBox().height());
+}
+
+// ---------
+
+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;
+ findWordBoundary(characters, length, offset - 1, &start, &end);
+ return start;
+}
+
+VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side)
+{
+ // FIXME: This returns a null VP for c at the start of the document
+ // and side == LeftWordIfOnBoundary
+ VisiblePosition p = c;
+ if (side == RightWordIfOnBoundary) {
+ // at paragraph end, the startofWord is the current position
+ if (isEndOfParagraph(c))
+ return c;
+
+ p = c.next();
+ if (p.isNull())
+ return c;
+ }
+ return previousBoundary(p, startWordBoundary);
+}
+
+static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
+{
+ ASSERT(offset <= length);
+ if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) {
+ needMoreContext = true;
+ return length;
+ }
+ needMoreContext = false;
+ int start, end;
+ findWordBoundary(characters, length, offset, &start, &end);
+ return end;
+}
+
+VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side)
+{
+ VisiblePosition p = c;
+ if (side == LeftWordIfOnBoundary) {
+ if (isStartOfParagraph(c))
+ return c;
+
+ p = c.previous();
+ if (p.isNull())
+ return c;
+ } else if (isEndOfParagraph(c))
+ return c;
+
+ return nextBoundary(p, endWordBoundary);
+}
+
+static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
+{
+ if (mayHaveMoreContext && !startOfLastWordBoundaryContext(characters, offset)) {
+ needMoreContext = true;
+ return 0;
+ }
+ needMoreContext = false;
+ return findNextWordFromIndex(characters, length, offset, false);
+}
+
+VisiblePosition previousWordPosition(const VisiblePosition &c)
+{
+ VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary);
+ return c.honorEditableBoundaryAtOrAfter(prev);
+}
+
+static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset, BoundarySearchContextAvailability mayHaveMoreContext, bool& needMoreContext)
+{
+ if (mayHaveMoreContext && endOfFirstWordBoundaryContext(characters + offset, length - offset) == static_cast<int>(length - offset)) {
+ needMoreContext = true;
+ return length;
+ }
+ needMoreContext = false;
+ return findNextWordFromIndex(characters, length, offset, true);
+}
+
+VisiblePosition nextWordPosition(const VisiblePosition &c)
+{
+ VisiblePosition next = nextBoundary(c, nextWordPositionBoundary);
+ return c.honorEditableBoundaryAtOrBefore(next);
+}
+
+// ---------
+
+static RootInlineBox *rootBoxForLine(const VisiblePosition &c)
+{
+ Position p = c.deepEquivalent();
+ Node *node = p.node();
+ if (!node)
+ return 0;
+
+ RenderObject *renderer = node->renderer();
+ if (!renderer)
+ return 0;
+
+ InlineBox* box;
+ int offset;
+ c.getInlineBoxAndOffset(box, offset);
+
+ return box ? box->root() : 0;
+}
+
+static VisiblePosition positionAvoidingFirstPositionInTable(const VisiblePosition& c)
+{
+ // return table offset 0 instead of the first VisiblePosition inside the table
+ VisiblePosition previous = c.previous();
+ if (isLastPositionBeforeTable(previous) && isEditablePosition(previous.deepEquivalent()))
+ return previous;
+
+ return c;
+}
+
+static VisiblePosition startPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox *rootBox = rootBoxForLine(c);
+ 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.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0)
+ return positionAvoidingFirstPositionInTable(c);
+
+ return VisiblePosition();
+ }
+
+ // 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.
+ InlineBox *startBox = rootBox->firstLeafChild();
+ Node *startNode;
+ while (1) {
+ if (!startBox)
+ return VisiblePosition();
+
+ RenderObject *startRenderer = startBox->renderer();
+ if (!startRenderer)
+ return VisiblePosition();
+
+ startNode = startRenderer->node();
+ if (startNode)
+ break;
+
+ startBox = startBox->nextLeafChild();
+ }
+
+ int startOffset = 0;
+ if (startBox->isInlineTextBox()) {
+ InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox);
+ startOffset = startTextBox->start();
+ }
+
+ VisiblePosition visPos = VisiblePosition(startNode, startOffset, DOWNSTREAM);
+ return positionAvoidingFirstPositionInTable(visPos);
+}
+
+VisiblePosition startOfLine(const VisiblePosition& c)
+{
+ VisiblePosition visPos = startPositionForLine(c);
+
+ return c.honorEditableBoundaryAtOrAfter(visPos);
+}
+
+static VisiblePosition endPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox *rootBox = rootBoxForLine(c);
+ 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.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0)
+ return c;
+ return VisiblePosition();
+ }
+
+ // 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.
+ Node *endNode;
+ InlineBox *endBox = rootBox->lastLeafChild();
+ while (1) {
+ if (!endBox)
+ return VisiblePosition();
+
+ RenderObject *endRenderer = endBox->renderer();
+ if (!endRenderer)
+ return VisiblePosition();
+
+ endNode = endRenderer->node();
+ if (endNode)
+ break;
+
+ endBox = endBox->prevLeafChild();
+ }
+
+ int endOffset = 1;
+ if (endNode->hasTagName(brTag)) {
+ endOffset = 0;
+ } else if (endBox->isInlineTextBox()) {
+ InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox);
+ endOffset = endTextBox->start();
+ if (!endTextBox->isLineBreak())
+ endOffset += endTextBox->len();
+ }
+
+ return VisiblePosition(endNode, endOffset, VP_UPSTREAM_IF_POSSIBLE);
+}
+
+VisiblePosition endOfLine(const VisiblePosition& c)
+{
+ VisiblePosition visPos = endPositionForLine(c);
+
+ // 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);
+ }
+
+ return c.honorEditableBoundaryAtOrBefore(visPos);
+}
+
+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);
+}
+
+// The first leaf before node that has the same editability as node.
+static Node* previousLeafWithSameEditability(Node* node)
+{
+ bool editable = node->isContentEditable();
+ Node* n = node->previousLeafNode();
+ while (n) {
+ if (editable == n->isContentEditable())
+ return n;
+ n = n->previousLeafNode();
+ }
+ return 0;
+}
+
+static Node* enclosingNodeWithNonInlineRenderer(Node* n)
+{
+ for (Node* p = n; p; p = p->parentNode()) {
+ if (p->renderer() && !p->renderer()->isInline())
+ return p;
+ }
+ return 0;
+}
+
+VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int x)
+{
+ Position p = visiblePosition.deepEquivalent();
+ Node *node = p.node();
+ Node* highestRoot = highestEditableRoot(p);
+ if (!node)
+ return VisiblePosition();
+
+ node->document()->updateLayoutIgnorePendingStylesheets();
+
+ RenderObject *renderer = node->renderer();
+ if (!renderer)
+ return VisiblePosition();
+
+ RenderBlock *containingBlock = 0;
+ 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())
+ containingBlock = renderer->containingBlock();
+ else
+ 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);
+ while (n && startBlock == enclosingNodeWithNonInlineRenderer(n))
+ n = previousLeafWithSameEditability(n);
+ while (n) {
+ if (highestEditableRoot(Position(n, 0)) != highestRoot)
+ break;
+ Position pos(n, caretMinOffset(n));
+ if (pos.isCandidate()) {
+ RenderObject* o = n->renderer();
+ ASSERT(o);
+ if (canHaveCursor(o)) {
+ Position maxPos(n, caretMaxOffset(n));
+ maxPos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset);
+ if (box) {
+ // previous root line box found
+ root = box->root();
+ containingBlock = n->renderer()->containingBlock();
+ break;
+ }
+
+ return VisiblePosition(pos, DOWNSTREAM);
+ }
+ }
+ n = previousLeafWithSameEditability(n);
+ }
+ }
+
+ if (root) {
+ // FIXME: Can be wrong for multi-column layout and with transforms.
+ FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint());
+ if (containingBlock->hasOverflowClip())
+ absPos -= containingBlock->layer()->scrolledContentOffset();
+ RenderObject* renderer = root->closestLeafChildForLogicalLeftPosition(x - absPos.x(), isEditablePosition(p))->renderer();
+ Node* node = renderer->node();
+ if (node && editingIgnoresContent(node))
+ return Position(node->parentNode(), node->nodeIndex());
+ return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop()));
+ }
+
+ // 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->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement();
+ return VisiblePosition(rootElement, 0, DOWNSTREAM);
+}
+
+static Node* nextLeafWithSameEditability(Node* node, int offset)
+{
+ bool editable = node->isContentEditable();
+ ASSERT(offset >= 0);
+ Node* child = node->childNode(offset);
+ Node* n = child ? child->nextLeafNode() : node->nextLeafNode();
+ while (n) {
+ if (editable == n->isContentEditable())
+ return n;
+ n = n->nextLeafNode();
+ }
+ return 0;
+}
+
+static Node* nextLeafWithSameEditability(Node* node)
+{
+ if (!node)
+ return 0;
+
+ bool editable = node->isContentEditable();
+ Node* n = node->nextLeafNode();
+ while (n) {
+ if (editable == n->isContentEditable())
+ return n;
+ n = n->nextLeafNode();
+ }
+ return 0;
+}
+
+VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x)
+{
+ Position p = visiblePosition.deepEquivalent();
+ Node *node = p.node();
+ Node* highestRoot = highestEditableRoot(p);
+ if (!node)
+ return VisiblePosition();
+
+ node->document()->updateLayoutIgnorePendingStylesheets();
+
+ RenderObject *renderer = node->renderer();
+ if (!renderer)
+ return VisiblePosition();
+
+ RenderBlock *containingBlock = 0;
+ 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())
+ containingBlock = renderer->containingBlock();
+ else
+ 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);
+ while (n) {
+ if (highestEditableRoot(Position(n, 0)) != highestRoot)
+ break;
+ Position pos(n, caretMinOffset(n));
+ if (pos.isCandidate()) {
+ ASSERT(n->renderer());
+ pos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset);
+ if (box) {
+ // next root line box found
+ root = box->root();
+ containingBlock = n->renderer()->containingBlock();
+ break;
+ }
+
+ return VisiblePosition(pos, DOWNSTREAM);
+ }
+ n = nextLeafWithSameEditability(n);
+ }
+ }
+
+ if (root) {
+ // FIXME: Can be wrong for multi-column layout and with transforms.
+ FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint());
+ if (containingBlock->hasOverflowClip())
+ absPos -= containingBlock->layer()->scrolledContentOffset();
+ RenderObject* renderer = root->closestLeafChildForLogicalLeftPosition(x - absPos.x(), isEditablePosition(p))->renderer();
+ Node* node = renderer->node();
+ if (node && editingIgnoresContent(node))
+ return Position(node->parentNode(), node->nodeIndex());
+ return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop()));
+ }
+
+ // 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->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement();
+ return VisiblePosition(rootElement, rootElement ? rootElement->childNodeCount() : 0, 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.honorEditableBoundaryAtOrAfter(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.honorEditableBoundaryAtOrBefore(next);
+}
+
+VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossingRule boundaryCrossingRule)
+{
+ Position p = c.deepEquivalent();
+ Node *startNode = p.node();
+
+ if (!startNode)
+ return VisiblePosition();
+
+ if (isRenderedAsNonInlineTableImageOrHR(startNode))
+ return firstDeepEditingPositionForNode(startNode);
+
+ Node* startBlock = enclosingBlock(startNode);
+
+ Node *node = startNode;
+ int offset = p.deprecatedEditingOffset();
+
+ Node *n = startNode;
+ while (n) {
+ if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable())
+ 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() && r->caretMaxRenderedOffset() > 0) {
+ 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(n, i + 1, DOWNSTREAM);
+ }
+ node = n;
+ offset = 0;
+ n = n->traversePreviousNodePostOrder(startBlock);
+ } else if (editingIgnoresContent(n) || isTableElement(n)) {
+ node = n;
+ offset = 0;
+ n = n->previousSibling() ? n->previousSibling() : n->traversePreviousNodePostOrder(startBlock);
+ } else
+ n = n->traversePreviousNodePostOrder(startBlock);
+ }
+
+ return VisiblePosition(node, offset, DOWNSTREAM);
+}
+
+VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossingRule boundaryCrossingRule)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ Position p = c.deepEquivalent();
+ Node* startNode = p.node();
+
+ if (isRenderedAsNonInlineTableImageOrHR(startNode))
+ return lastDeepEditingPositionForNode(startNode);
+
+ Node* startBlock = enclosingBlock(startNode);
+ Node *stayInsideBlock = startBlock;
+
+ Node *node = startNode;
+ int offset = p.deprecatedEditingOffset();
+
+ Node *n = startNode;
+ while (n) {
+ if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable())
+ 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() && r->caretMaxRenderedOffset() > 0) {
+ int length = toRenderText(r)->textLength();
+ 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(n, i, DOWNSTREAM);
+ }
+ node = n;
+ offset = r->caretMaxOffset();
+ n = n->traverseNextNode(stayInsideBlock);
+ } else if (editingIgnoresContent(n) || isTableElement(n)) {
+ node = n;
+ offset = lastOffsetForEditing(n);
+ n = n->traverseNextSibling(stayInsideBlock);
+ } else
+ n = n->traverseNextNode(stayInsideBlock);
+ }
+
+ return VisiblePosition(node, offset, DOWNSTREAM);
+}
+
+VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition)
+{
+ VisiblePosition paragraphEnd(endOfParagraph(visiblePosition));
+ VisiblePosition afterParagraphEnd(paragraphEnd.next(true));
+ // 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(true);
+ return afterParagraphEnd;
+}
+
+bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b)
+{
+ return a.isNotNull() && startOfParagraph(a) == startOfParagraph(b);
+}
+
+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 &c)
+{
+ Position p = c.deepEquivalent();
+ Node *startNode = p.node();
+ if (!startNode)
+ return VisiblePosition();
+ return VisiblePosition(Position(startNode->enclosingBlockFlowElement(), 0), DOWNSTREAM);
+}
+
+VisiblePosition endOfBlock(const VisiblePosition &c)
+{
+ Position p = c.deepEquivalent();
+
+ Node *startNode = p.node();
+ if (!startNode)
+ return VisiblePosition();
+
+ Node *startBlock = startNode->enclosingBlockFlowElement();
+
+ return VisiblePosition(startBlock, startBlock->childNodeCount(), VP_DEFAULT_AFFINITY);
+}
+
+bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b)
+{
+ return !a.isNull() && enclosingBlockFlowElement(a) == enclosingBlockFlowElement(b);
+}
+
+bool isStartOfBlock(const VisiblePosition &pos)
+{
+ return pos.isNotNull() && pos == startOfBlock(pos);
+}
+
+bool isEndOfBlock(const VisiblePosition &pos)
+{
+ return pos.isNotNull() && pos == endOfBlock(pos);
+}
+
+// ---------
+
+VisiblePosition startOfDocument(const Node* node)
+{
+ if (!node)
+ return VisiblePosition();
+
+ return VisiblePosition(node->document()->documentElement(), 0, DOWNSTREAM);
+}
+
+VisiblePosition startOfDocument(const VisiblePosition &c)
+{
+ return startOfDocument(c.deepEquivalent().node());
+}
+
+VisiblePosition endOfDocument(const Node* node)
+{
+ if (!node || !node->document() || !node->document()->documentElement())
+ return VisiblePosition();
+
+ Element* doc = node->document()->documentElement();
+ return VisiblePosition(doc, doc->childNodeCount(), DOWNSTREAM);
+}
+
+VisiblePosition endOfDocument(const VisiblePosition &c)
+{
+ return endOfDocument(c.deepEquivalent().node());
+}
+
+bool inSameDocument(const VisiblePosition &a, const VisiblePosition &b)
+{
+ Position ap = a.deepEquivalent();
+ Node *an = ap.node();
+ if (!an)
+ return false;
+ Position bp = b.deepEquivalent();
+ Node *bn = bp.node();
+ 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 firstDeepEditingPositionForNode(highestRoot);
+}
+
+VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition)
+{
+ Node* highestRoot = highestEditableRoot(visiblePosition.deepEquivalent());
+ if (!highestRoot)
+ return VisiblePosition();
+
+ return lastDeepEditingPositionForNode(highestRoot);
+}
+
+static void getLeafBoxesInLogicalOrder(RootInlineBox* rootBox, Vector<InlineBox*>& leafBoxesInLogicalOrder)
+{
+ unsigned char minLevel = 128;
+ unsigned char maxLevel = 0;
+ unsigned count = 0;
+ InlineBox* r = rootBox->firstLeafChild();
+ // First find highest and lowest levels,
+ // and initialize leafBoxesInLogicalOrder with the leaf boxes in visual order.
+ while (r) {
+ if (r->bidiLevel() > maxLevel)
+ maxLevel = r->bidiLevel();
+ if (r->bidiLevel() < minLevel)
+ minLevel = r->bidiLevel();
+ leafBoxesInLogicalOrder.append(r);
+ r = r->nextLeafChild();
+ ++count;
+ }
+
+ if (rootBox->renderer()->style()->visuallyOrdered())
+ return;
+ // Reverse of reordering of the line (L2 according to Bidi spec):
+ // L2. From the highest level found in the text to the lowest odd level on each line,
+ // reverse any contiguous sequence of characters that are at that level or higher.
+
+ // Reversing the reordering of the line is only done up to the lowest odd level.
+ if (!(minLevel % 2))
+ minLevel++;
+
+ InlineBox** end = leafBoxesInLogicalOrder.end();
+ while (minLevel <= maxLevel) {
+ InlineBox** iter = leafBoxesInLogicalOrder.begin();
+ while (iter != end) {
+ while (iter != end) {
+ if ((*iter)->bidiLevel() >= minLevel)
+ break;
+ ++iter;
+ }
+ InlineBox** first = iter;
+ while (iter != end) {
+ if ((*iter)->bidiLevel() < minLevel)
+ break;
+ ++iter;
+ }
+ InlineBox** last = iter;
+ std::reverse(first, last);
+ }
+ ++minLevel;
+ }
+}
+
+static void getLogicalStartBoxAndNode(RootInlineBox* rootBox, InlineBox*& startBox, Node*& startNode)
+{
+ Vector<InlineBox*> leafBoxesInLogicalOrder;
+ getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+ startBox = 0;
+ startNode = 0;
+ for (size_t i = 0; i < leafBoxesInLogicalOrder.size(); ++i) {
+ startBox = leafBoxesInLogicalOrder[i];
+ startNode = startBox->renderer()->node();
+ if (startNode)
+ return;
+ }
+}
+
+static void getLogicalEndBoxAndNode(RootInlineBox* rootBox, InlineBox*& endBox, Node*& endNode)
+{
+ Vector<InlineBox*> leafBoxesInLogicalOrder;
+ getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder);
+ endBox = 0;
+ endNode = 0;
+ // 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.
+ for (size_t i = leafBoxesInLogicalOrder.size(); i > 0; --i) {
+ endBox = leafBoxesInLogicalOrder[i - 1];
+ endNode = endBox->renderer()->node();
+ if (endNode)
+ return;
+ }
+}
+
+static VisiblePosition logicalStartPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox* rootBox = rootBoxForLine(c);
+ 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.node()->renderer() && p.node()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset())
+ return positionAvoidingFirstPositionInTable(c);
+
+ return VisiblePosition();
+ }
+
+ InlineBox* logicalStartBox;
+ Node* logicalStartNode;
+ getLogicalStartBoxAndNode(rootBox, logicalStartBox, logicalStartNode);
+
+ if (!logicalStartNode)
+ return VisiblePosition();
+
+ int startOffset = logicalStartBox->caretMinOffset();
+
+ VisiblePosition visPos = VisiblePosition(logicalStartNode, startOffset, DOWNSTREAM);
+ return positionAvoidingFirstPositionInTable(visPos);
+}
+
+VisiblePosition logicalStartOfLine(const VisiblePosition& 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.
+ VisiblePosition visPos = logicalStartPositionForLine(c);
+
+ return c.honorEditableBoundaryAtOrAfter(visPos);
+}
+
+static VisiblePosition logicalEndPositionForLine(const VisiblePosition& c)
+{
+ if (c.isNull())
+ return VisiblePosition();
+
+ RootInlineBox* rootBox = rootBoxForLine(c);
+ 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.node()->renderer() && p.node()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset())
+ return c;
+ return VisiblePosition();
+ }
+
+ InlineBox* logicalEndBox;
+ Node* logicalEndNode;
+ getLogicalEndBoxAndNode(rootBox, logicalEndBox, logicalEndNode);
+ if (!logicalEndNode)
+ return VisiblePosition();
+
+ int endOffset = 1;
+ if (logicalEndNode->hasTagName(brTag))
+ endOffset = 0;
+ else if (logicalEndBox->isInlineTextBox()) {
+ InlineTextBox* endTextBox = static_cast<InlineTextBox*>(logicalEndBox);
+ endOffset = endTextBox->start();
+ if (!endTextBox->isLineBreak())
+ endOffset += endTextBox->len();
+ }
+
+ return VisiblePosition(logicalEndNode, endOffset, VP_UPSTREAM_IF_POSSIBLE);
+}
+
+bool inSameLogicalLine(const VisiblePosition& a, const VisiblePosition& b)
+{
+ return a.isNotNull() && logicalStartOfLine(a) == logicalStartOfLine(b);
+}
+
+VisiblePosition logicalEndOfLine(const VisiblePosition& 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.
+
+ VisiblePosition visPos = logicalEndPositionForLine(c);
+
+ // Make sure the end of line is at the same line as the given input position. For a wrapping line, the logical end
+ // position for the not-last-2-lines might incorrectly hand back the logical beginning of the next line.
+ // For example, <div contenteditable dir="rtl" style="line-break:before-white-space">abcdefg abcdefg abcdefg
+ // a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg </div>
+ // In this case, use the previous position of the computed logical end position.
+ if (!inSameLogicalLine(c, visPos))
+ visPos = visPos.previous();
+
+ return c.honorEditableBoundaryAtOrBefore(visPos);
+}
+
+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);
+}
+
+}