| Index: Source/core/editing/VisibleUnits.cpp
|
| diff --git a/Source/core/editing/VisibleUnits.cpp b/Source/core/editing/VisibleUnits.cpp
|
| index fed0969f7d5879e02880fc2c814399da8de13ade..8226d7bdebceb890baf65f5a917c65f5eabcd0ce 100644
|
| --- a/Source/core/editing/VisibleUnits.cpp
|
| +++ b/Source/core/editing/VisibleUnits.cpp
|
| @@ -36,6 +36,7 @@
|
| #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"
|
| @@ -1602,4 +1603,308 @@ bool rendersInDifferentPosition(const Position& position1, const Position& posit
|
| return true;
|
| }
|
|
|
| +// Whether or not [node, 0] and [node, lastOffsetForEditing(node)] are their own VisiblePositions.
|
| +// If true, adjacent candidates are visually distinct.
|
| +// FIXME: Disregard nodes with layoutObjects that have no height, as we do in isCandidate.
|
| +// FIXME: Share code with isCandidate, if possible.
|
| +static bool endsOfNodeAreVisuallyDistinctPositions(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()->isReplaced() && canHaveChildrenForEditing(node) && toLayoutBox(node->layoutObject())->size().height() != 0 && !node->hasChildren();
|
| +}
|
| +
|
| +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 PositionAlgorithm<Strategy> mostForwardCaretPosition(const PositionAlgorithm<Strategy>& position, EditingBoundaryCrossingRule rule)
|
| +{
|
| + TRACE_EVENT0("blink", "Position::upstream");
|
| +
|
| + Node* startNode = position.anchorNode();
|
| + if (!startNode)
|
| + return PositionAlgorithm<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() ? PositionAlgorithm<Strategy>::editingPositionOf(position.anchorNode(), Strategy::caretMaxOffset(*position.anchorNode())) : position);
|
| + PositionIteratorAlgorithm<Strategy> currentPos = lastVisible;
|
| + bool startEditable = startNode->hasEditableStyle();
|
| + 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 = currentNode->hasEditableStyle();
|
| + 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 = currentNode->layoutObject();
|
| + if (!layoutObject || layoutObject->style()->visibility() != 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) || isRenderedHTMLTableElement(currentNode)) {
|
| + if (currentPos.atEndOfNode())
|
| + return PositionAlgorithm<Strategy>::afterNode(currentNode);
|
| + continue;
|
| + }
|
| +
|
| + // return current position if it is in laid out text
|
| + if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) {
|
| + 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!
|
| + // ASSERT(currentOffset >= layoutObject->caretMaxOffset());
|
| + return PositionAlgorithm<Strategy>(currentNode, layoutObject->caretMaxOffset());
|
| + }
|
| +
|
| + unsigned textOffset = currentPos.offsetInLeafNode();
|
| + LayoutText* textLayoutObject = toLayoutText(layoutObject);
|
| + InlineTextBox* lastTextBox = textLayoutObject->lastTextBox();
|
| + for (InlineTextBox* box = textLayoutObject->firstTextBox(); box; box = box->nextTextBox()) {
|
| + 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 || (otherBox->layoutObject() == textLayoutObject && toInlineTextBox(otherBox)->start() > textOffset))
|
| + continuesOnNextLine = false;
|
| + }
|
| +
|
| + otherBox = box;
|
| + while (continuesOnNextLine) {
|
| + otherBox = otherBox->prevLeafChild();
|
| + if (!otherBox)
|
| + break;
|
| + if (otherBox == lastTextBox || (otherBox->layoutObject() == 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);
|
| +}
|
| +
|
| +PositionInComposedTree mostForwardCaretPosition(const PositionInComposedTree& position, EditingBoundaryCrossingRule rule)
|
| +{
|
| + return mostForwardCaretPosition<EditingInComposedTreeStrategy>(position, rule);
|
| +}
|
| +
|
| +template <typename Strategy>
|
| +PositionAlgorithm<Strategy> mostBackwardCaretPosition(const PositionAlgorithm<Strategy>& position, EditingBoundaryCrossingRule rule)
|
| +{
|
| + TRACE_EVENT0("blink", "Position::downstream");
|
| +
|
| + Node* startNode = position.anchorNode();
|
| + if (!startNode)
|
| + return PositionAlgorithm<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() ? PositionAlgorithm<Strategy>::editingPositionOf(position.anchorNode(), Strategy::caretMaxOffset(*position.anchorNode())) : position);
|
| + PositionIteratorAlgorithm<Strategy> currentPos = lastVisible;
|
| + bool startEditable = startNode->hasEditableStyle();
|
| + 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 = currentNode->hasEditableStyle();
|
| + 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 = currentNode->layoutObject();
|
| + if (!layoutObject || layoutObject->style()->visibility() != 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) || isRenderedHTMLTableElement(currentNode)) {
|
| + if (currentPos.offsetInLeafNode() <= layoutObject->caretMinOffset())
|
| + return PositionAlgorithm<Strategy>::editingPositionOf(currentNode, layoutObject->caretMinOffset());
|
| + continue;
|
| + }
|
| +
|
| + // return current position if it is in laid out text
|
| + if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) {
|
| + if (currentNode != startNode) {
|
| + ASSERT(currentPos.atStartOfNode());
|
| + return PositionAlgorithm<Strategy>(currentNode, layoutObject->caretMinOffset());
|
| + }
|
| +
|
| + unsigned textOffset = currentPos.offsetInLeafNode();
|
| + LayoutText* textLayoutObject = toLayoutText(layoutObject);
|
| + 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 || (otherBox->layoutObject() == textLayoutObject && toInlineTextBox(otherBox)->start() >= textOffset))
|
| + continuesOnNextLine = false;
|
| + }
|
| +
|
| + otherBox = box;
|
| + while (continuesOnNextLine) {
|
| + otherBox = otherBox->prevLeafChild();
|
| + if (!otherBox)
|
| + break;
|
| + if (otherBox == lastTextBox || (otherBox->layoutObject() == 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);
|
| +}
|
| +
|
| +PositionInComposedTree mostBackwardCaretPosition(const PositionInComposedTree& position, EditingBoundaryCrossingRule rule)
|
| +{
|
| + return mostBackwardCaretPosition<EditingInComposedTreeStrategy>(position, rule);
|
| +}
|
| +
|
| }
|
|
|