Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(332)

Unified Diff: third_party/WebKit/Source/core/editing/LayoutPosition.cpp

Issue 1756763002: 2016-03-02T18:53:01 LayoutPosition Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: 2016-04-25T19:02:18 Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/core/editing/LayoutPosition.cpp
diff --git a/third_party/WebKit/Source/core/editing/LayoutPosition.cpp b/third_party/WebKit/Source/core/editing/LayoutPosition.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..242477a80d47839b17f10d56e4e3abd4590e09c4
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/LayoutPosition.cpp
@@ -0,0 +1,799 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/editing/LayoutPosition.h"
+
+#include "core/dom/DocumentLifecycle.h"
+#include "core/dom/NodeTraversal.h"
+#include "core/editing/LayoutTreeTraversal.h"
+#include "core/editing/VisibleUnits.h"
+#include "core/frame/FrameView.h"
+#include "core/html/HTMLImageElement.h"
+#include "core/layout/LayoutBR.h"
+#include "core/layout/LayoutBlockFlow.h"
+#include "core/layout/LayoutInline.h"
+#include "core/layout/LayoutObject.h"
+#include "core/layout/LayoutText.h"
+#include "core/layout/LayoutTextFragment.h"
+#include "core/layout/api/LineLayoutAPIShim.h"
+#include "core/layout/api/LineLayoutItem.h"
+#include "core/layout/line/InlineTextBox.h"
+#include "wtf/Assertions.h"
+#include <ostream> // NOLINT
+
+namespace blink {
+
+namespace {
+ InlineBoxPosition findInlineBoxPosition(const Text& textNode, int offsetInNode)
+ {
+ DCHECK_GE(offsetInNode, 0);
+ LayoutObject* const layoutObject = associatedLayoutObjectOf(textNode, offsetInNode);
+ if (!layoutObject)
+ return InlineBoxPosition();
+ LayoutText* const layoutText = toLayoutText(layoutObject);
+ CHECK_GE(static_cast<unsigned>(offsetInNode), layoutText->textStartOffset());
+ InlineTextBox* afterBox = nullptr;
+ InlineTextBox* beforeBox = nullptr;
+ const unsigned offsetInText = static_cast<unsigned>(offsetInNode - layoutText->textStartOffset());
+ for (InlineTextBox* box = layoutText->firstTextBox(); box; box = box->nextTextBox()) {
+ if (offsetInText >= box->start() && offsetInText <= box->end())
+ return InlineBoxPosition(box, offsetInText);
+ if (box->start() > offsetInText) {
+ if (!afterBox || afterBox->start() > box->start())
+ afterBox = box;
+ continue;
+ }
+ if (box->end() < offsetInText) {
+ if (!beforeBox || beforeBox->end() < box->end())
+ beforeBox = box;
+ continue;
+ }
+ }
+ if (beforeBox) {
+ DCHECK_LT(beforeBox->end(), offsetInText);
+ return InlineBoxPosition(beforeBox, beforeBox->end() + 1);
+ }
+ if (afterBox) {
+ DCHECK_GT(afterBox->start(), offsetInText);
+ return InlineBoxPosition(afterBox, afterBox->start());
+ }
+ return InlineBoxPosition();
+ }
+
+ // Note: Since |LayoutBR| is derived from |LayoutText|, we need to
+ // use associated node instead of |LayoutObject::isText()|.
+ // Note: |LayoutObject::node()| returns null for a layout object for
+ // first-letter fragment.
+ //
+ // Following nodes are associated to anonymous LayoutText object:
+ // - ::before/::after pseudo elements
+ // - OPTION elements when SELECT[@size=1]
+ bool shouldUseOffsetInAnchor(const LayoutObject& layoutObject)
+ {
+ if (!layoutObject.isText())
+ return false;
+ const LayoutText& layoutText = toLayoutText(layoutObject);
+ if (!layoutText.firstTextBox())
+ return false;
+ Node* const node = layoutText.node();
+ if (node)
+ return node->isTextNode();
+ Node* parentNode = layoutObject.parent()->node();
+ return parentNode && parentNode->isFirstLetterPseudoElement();
+ }
+
+ bool shouldUseOffsetInAnchor(const InlineBox& inlineBox)
+ {
+ Node* const node = inlineBox.getLineLayoutItem().node();
+ return node && node->isTextNode();
+ }
+
+ InlineBox* inlineBoxOf(const LayoutObject& layoutObject)
+ {
+ if (layoutObject.isBR())
+ return toLayoutBR(layoutObject).firstTextBox();
+ DCHECK(!shouldUseOffsetInAnchor(layoutObject))
+ << "A position for LayoutText should be constructed OffsetInAnchor: " << layoutObject;
+ if (!layoutObject.isBox())
+ return nullptr;
+ return toLayoutBox(layoutObject).inlineBoxWrapper();
+ }
+} // namespace
+
+LayoutPosition::LayoutPosition(PositionAnchorType anchorType, LayoutObject* layoutObject)
+ : m_anchorType(anchorType)
+ , m_layoutObject(layoutObject)
+ , m_inlineBox(inlineBoxOf(*layoutObject))
+{
+ DCHECK(m_anchorType == PositionAnchorType::AfterAnchor || m_anchorType == PositionAnchorType::BeforeAnchor || m_anchorType == PositionAnchorType::BeforeChildren) << m_anchorType;
+ DCHECK(m_layoutObject);
+ DCHECK(!shouldUseOffsetInAnchor(*m_layoutObject)) << m_layoutObject;
+#if ENABLE(ASSERT)
+ m_domTreeVersion = document().domTreeVersion();
+ m_styleVersion = document().styleVersion();
+#endif
+}
+
+LayoutPosition::LayoutPosition(const InlineBoxPosition& boxPosition)
+ : m_anchorType(PositionAnchorType::OffsetInAnchor)
+ , m_inlineBox(boxPosition.inlineBox)
+ , m_layoutObject(layoutObjectOf(*boxPosition.inlineBox))
+ , m_offsetInBox(boxPosition.offsetInBox)
+{
+ DCHECK(m_inlineBox);
+ DCHECK(m_inlineBox->isInlineTextBox());
+ DCHECK(m_layoutObject->isText());
+ DCHECK(shouldUseOffsetInAnchor(*m_layoutObject)) << m_layoutObject;
+ DCHECK_GE(m_offsetInBox, static_cast<int>(toInlineTextBox(m_inlineBox)->start()));
+ DCHECK_LE(m_offsetInBox, static_cast<int>(toInlineTextBox(m_inlineBox)->end() + 1));
+#if ENABLE(ASSERT)
+ m_domTreeVersion = document().domTreeVersion();
+ m_styleVersion = document().styleVersion();
+#endif
+}
+
+LayoutPosition::LayoutPosition(const LayoutPosition& other)
+ : m_anchorType(other.m_anchorType)
+ , m_inlineBox(other.m_inlineBox)
+ , m_layoutObject(other.m_layoutObject)
+ , m_offsetInBox(other.m_offsetInBox)
+{
+#if ENABLE(ASSERT)
+ m_domTreeVersion = other.m_domTreeVersion;
+ m_styleVersion = other.m_styleVersion;
+#endif
+}
+
+LayoutPosition::LayoutPosition()
+ : m_anchorType(PositionAnchorType::BeforeAnchor)
+{
+}
+
+LayoutPosition::~LayoutPosition()
+{
+}
+
+bool LayoutPosition::operator==(const LayoutPosition& other) const
+{
+ DCHECK(isValid()) << *this;
+ DCHECK(other.isValid()) << other;
+ if (this == &other)
+ return true;
+ return m_anchorType == other.m_anchorType && m_layoutObject == other.m_layoutObject && m_inlineBox == other.m_inlineBox && m_offsetInBox == other.m_offsetInBox;
+}
+
+bool LayoutPosition::operator!=(const LayoutPosition& other) const
+{
+ return !operator==(other);
+}
+
+// Returns false when |m_layoutObject| is anonymous or |m_layoutObject| is
+// associated to pseudo-element, e.g. ::before, ::after, ::first-letter, etc.
+bool LayoutPosition::canBePosition() const
+{
+ DCHECK(isValid()) << *this;
+ if (isNull())
+ return true;
+ const Node* const node = m_layoutObject->nonPseudoNode();
+ if (!node)
+ return false;
+ if (node->isDocumentNode()) {
+ // We can't construct a position before/after document.
+ return false;
+ }
+ if (!node->isInShadowTree())
+ return true;
+ Element* shadowHost = node->shadowHost();
+ if (node == shadowHost)
+ return true;
+ if (!isHTMLImageElement(shadowHost))
+ return true;
+ // We can't place caret fallback content of IMG element.
+ return !shadowHost->userAgentShadowRoot();
+}
+
+bool LayoutPosition::canPlaceCaret() const
+{
+ if (!canPlaceCaretInternal())
+ return false;
+ DCHECK(!computeCaretQuad().isEmpty())
+ << "canPlaceCaret() says true, but caret quad is empty for " << *this;
+ return true;
+}
+
+bool LayoutPosition::canPlaceCaretInternal() const
+{
+ if (isNull() || !canBePosition())
+ return false;
+ const Node* node = m_layoutObject->nonPseudoNode();
+ if (!node) {
+ // We can't place a caret in pseudo element and auto created layout
+ // object.
+ return false;
+ }
+ if (node == node->document().documentElement()) {
+ // We can't place a caret before/after document element.
+ return isBeforeChildren();
+ }
+ if (m_layoutObject->style()->visibility() != VISIBLE)
+ return false;
+ if (m_layoutObject->isBR()) {
+ // Since, |LayoutObject::localCaretRect()| doesn't support after BR.
+ // We don't allow to place caret after BR.
+ return isBeforeAnchor() && toLayoutText(m_layoutObject)->hasTextBoxes();
+ }
+ if (m_layoutObject->isText()) {
+ if (!m_inlineBox) {
+ DCHECK(isBeforeAnchor() || isAfterAnchor()) << this;
+ // We can't place caret before/after collapsed whitespace.
+ return false;
+ }
+ DCHECK(isOffsetInAnchor()) << this;
+ return !U16_IS_TRAIL(codeUnitAfter());
+ }
+ if (m_layoutObject->isSVG()) {
+ // We don't consider SVG elements are contenteditable except for
+ // associated |layoutObject| returns |isText()| true,
+ // e.g. |LayoutSVGInlineText|.
+ return false;
+ }
+ // TODO(yosin): We should have |LayoutObject::canPlaceCaretBefore()|.
+ if (!m_layoutObject->canHaveChildren()) {
+ // We can place caret before/after replaced elements, e.g. CANVAS,
+ // IMG, etc.
+ // Note: when IMG element has alt/title and no image, IMG element
+ // yields |LayoutBlockFlow| instead of |LayoutImage|.
+ return isBeforeAnchor() || isAfterAnchor();
+ }
+ if (m_layoutObject->isRuby()) {
+ // We can't place a caret before/after RUBY element. |isRuby| checks
+ // must be done before |isLayoutInline()| since |LayoutRubyInline| is
+ // derived from |LayoutInline|.
+ return isBeforeChildren();
+ }
+ // TODO(yosin): |LayoutProgress::canHaveChildren()| should return false.
+ if (m_layoutObject->isProgress())
+ return isBeforeAnchor() || isAfterAnchor();
+ if (m_layoutObject->isLayoutButton() || m_layoutObject->isTable() || m_layoutObject->isTextControl() || m_layoutObject->isMenuList() || m_layoutObject->isMedia())
+ return isBeforeAnchor() || isAfterAnchor();
+ // TODO(yosin): Should we use |LayoutBox| to cover both |LayoutBlockFLow|
+ // and |LayoutInline|?
+ if (m_layoutObject->isLayoutBlock()) {
+ if (!isBeforeChildren())
+ return false;
+#if 0
+ // We can place a caret in block having no children and the block
+ // is an editing host.
+ if (m_layoutObject->style()->userModify() == READ_ONLY)
+ return false;
+ return m_layoutObject->parent()->style()->userModify() == READ_ONLY;
+#else
+ return !m_inlineBox;
+#endif
+ }
+ if (m_layoutObject->isLayoutInline()) {
+ DCHECK(!m_inlineBox) << "When do we have inline box for LayoutInline?";
+ if (isBeforeAnchor()) {
+ if (m_layoutObject->style()->userModify() == READ_ONLY)
+ return false;
+ LayoutObject* parent = m_layoutObject->parent();
+ if (parent->style()->userModify() == READ_ONLY)
+ return false;
+ if (parent->parent()->style()->userModify() != READ_ONLY)
+ return false;
+ // We can place a caret before first child of editable root.
+ // e.g. <span contenteditable>|<b></b><i></i></span>
+ if (LayoutTreeTraversal::firstChild(*parent) != m_layoutObject)
+ return false;
+ return !computeCaretQuad().isEmpty();
+ }
+ if (isBeforeChildren()) {
+ if (m_layoutObject->style()->userModify() == READ_ONLY)
+ return false;
+ // We can place a caret for root editable empty inline element,
+ // e.g. <span contenteditable></span>
+ if (m_layoutObject->parent()->style()->userModify() != READ_ONLY)
+ return false;
+ return !computeCaretQuad().isEmpty();
+ }
+ return false;
+ }
+ return false;
+}
+
+UChar LayoutPosition::codeUnitAfter() const
+{
+ DCHECK(isOffsetInAnchor()) << *this;
+ return toInlineTextBox(m_inlineBox)->text()[m_offsetInBox];
+}
+
+FloatQuad LayoutPosition::computeCaretQuad() const
+{
+ if (m_hasCaretQuad)
+ return m_caretQuad;
+ m_hasCaretQuad = true;
+ if (isNull())
+ return FloatQuad();
+ const InlineBoxPosition& inlineBoxPosition = computeInlineBoxPosition();
+ // TODO(yosin): We should make |LayoutObject::localCaretRect()| to take
+ // |const InlineBox*|.
+ const LayoutRect& localCaretRect = m_layoutObject->localCaretRect(inlineBoxPosition.inlineBox, inlineBoxPosition.offsetInBox);
+ if (localCaretRect.isEmpty())
+ return FloatQuad();
+ m_caretQuad = m_layoutObject->localToAbsoluteQuad(FloatRect(localCaretRect));
+ return m_caretQuad;
+}
+
+InlineBoxPosition LayoutPosition::computeInlineBoxPosition() const
+{
+ if (m_inlineBox)
+ return InlineBoxPosition(m_inlineBox, m_offsetInBox);
+ DCHECK_EQ(m_offsetInBox, 0);
+ if (m_layoutObject->isBox()) {
+ InlineBox* const inlineBox = toLayoutBox(m_layoutObject)->inlineBoxWrapper();
+ if (isAfterAnchor())
+ return InlineBoxPosition(inlineBox, 1);
+ return InlineBoxPosition(inlineBox, 0);
+ }
+
+ if (m_layoutObject->isText() && toLayoutText(m_layoutObject)->hasTextBoxes()) {
+ if (isAfterAnchor()) {
+ InlineTextBox * const inlineBox = toLayoutText(m_layoutObject)->lastTextBox();
+ return InlineBoxPosition(inlineBox, inlineBox->end());
+ }
+ InlineTextBox * const inlineBox = toLayoutText(m_layoutObject)->firstTextBox();
+ return InlineBoxPosition(inlineBox, inlineBox->start());
+ }
+ return InlineBoxPosition();
+}
+
+const Document& LayoutPosition::document() const
+{
+ DCHECK(isNotNull());
+ return m_layoutObject->document();
+}
+
+bool LayoutPosition::isCaretEquivalent(const LayoutPosition& other) const
+{
+ if (*this == other)
+ return true;
+ if (!canPlaceCaret())
+ return !other.canPlaceCaret();
+ if (!other.canPlaceCaret())
+ return false;
+ if (m_layoutObject == other.m_layoutObject && m_inlineBox == other.m_inlineBox && m_offsetInBox == other.m_offsetInBox) {
+ // TODO(yosin): Is BeforeChildren is different from BeforeAnchor and
+ // AfterAnchor?
+ // Example:
+ // |<span></span> vs <span>|</span>
+ return computeCaretQuad().isEmpty();
+ }
+
+ if (isAfterAnchor() && m_inlineBox) {
+ // In case of a position at end of line and a position at start of
+ // next line.
+ if (m_inlineBox->nextOnLine() != other.m_inlineBox)
+ return false;
+ if (other.isBeforeAnchor())
+ return true;
+ if (other.isOffsetInAnchor())
+ return static_cast<unsigned>(other.m_offsetInBox) == toInlineTextBox(other.m_inlineBox)->start();
+ return false;
+ }
+
+ if (other.isAfterAnchor() && other.m_inlineBox)
+ return !isAfterAnchor() && other.isCaretEquivalent(*this);
+
+ if (isOffsetInAnchor() && other.isOffsetInAnchor()) {
+ // Note: for "<span>A</span>B", carets for position after "A" and
+ // position before "B" are same..
+ if (m_inlineBox->parent() != other.m_inlineBox->parent()) {
+ // |this| and |other| are in different lines.
+ return false;
+ }
+ }
+
+#if 0
+ if (m_layoutObject->isLayoutBlock() || other.m_layoutObject->isLayoutBlock()) {
+ if (LayoutTreeTraversal::parent(*m_layoutObject) == other.m_layoutObject || LayoutTreeTraversal::parent(*other.m_layoutObject) == m_layoutObject) {
+ // We consider before anchor and inside anchor are different.
+ return false;
+ }
+ }
+#endif
+ // TODO(yosin): Once we finish debugging of |isCaretEquivalent()|, we
+ // should not use local variables for caret quad.
+ const auto& thisQuad = computeCaretQuad();
+ const auto& otherQuad = other.computeCaretQuad();
+ return thisQuad == otherQuad;
+}
+
+// See |primaryDictionOf()| in "EditingUtilities.cpp".
+bool LayoutPosition::isLeftToRightDirection() const
+{
+ DCHECK(isNotNull());
+ if (m_inlineBox)
+ return m_inlineBox->isLeftToRightDirection();
+ for (const LayoutObject* runner = m_layoutObject; runner; runner = LayoutTreeTraversal::parent(*runner)) {
+ if (runner->isLayoutBlockFlow())
+ return runner->style()->direction() == LTR;
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool LayoutPosition::isValid() const
+{
+#if ENABLE(ASSERT)
+ if (isNull())
+ return true;
+ if (!document().isActive())
+ return false;
+ if (document().view()->needsLayout())
+ return false;
+ // TODO(yosin): We should introduce const version of |Document::lifecycle()|
+ if (const_cast<Document&>(document()).lifecycle().state() < DocumentLifecycle::LayoutClean)
+ return false;
+ return document().domTreeVersion() == m_domTreeVersion && document().styleVersion() == m_styleVersion;
+#else
+ return true;
+#endif
+}
+
+template <typename Strategy>
+PositionTemplate<Strategy> LayoutPosition::toPositionAlgorithm(const PositionTemplate<Strategy>&) const
+{
+ DCHECK(canBePosition()) << *this;
+ if (isNull())
+ return PositionTemplate<Strategy>();
+ Node* const node = m_layoutObject->nonPseudoNode();
+ if (!node)
+ return PositionTemplate<Strategy>();
+ if (isOffsetInAnchor()) {
+ // TODO(yosin): |LayoputText| contains more or less characters than |Text|
+ // node due by CSS text-transform, e.g. transform German Eszet to upper
+ // case. http://crbug.com/27616
+ const int offsetInNode = toLayoutText(m_layoutObject)->textStartOffset() + m_offsetInBox;
+ return PositionTemplate<Strategy>(node, std::min(offsetInNode, static_cast<int>(toText(node)->length())));
+ }
+ if (isBeforeAnchor())
+ return PositionTemplate<Strategy>::beforeNode(node);
+ if (isBeforeChildren()) {
+ // TODO(yosin): Once we update layout tests, we should use
+ // |firstPositionInNode() to have a BeforeChildren position.
+ // return PositionTemplate<Strategy>(node, 0);
+ return PositionTemplate<Strategy>::firstPositionInNode(node);
+ }
+ DCHECK(isAfterAnchor()) << *this;
+ return PositionTemplate<Strategy>::afterNode(node);
+}
+
+Position LayoutPosition::toPosition(const Position& sample) const
+{
+ return toPositionAlgorithm<EditingStrategy>(sample);
+}
+
+PositionInFlatTree LayoutPosition::toPosition(const PositionInFlatTree& sample) const
+{
+ return toPositionAlgorithm<EditingInFlatTreeStrategy>(sample);
+}
+
+// static
+LayoutPosition LayoutPosition::afterAnchorOf(InlineBox* inlineBox)
+{
+ if (shouldUseOffsetInAnchor(*inlineBox))
+ return LayoutPosition(InlineBoxPosition(inlineBox, toInlineTextBox(inlineBox)->end() + 1));
+ return afterAnchorOf(layoutObjectOf(*inlineBox));
+}
+
+// static
+LayoutPosition LayoutPosition::afterAnchorOf(LayoutObject* layoutObject)
+{
+ if (shouldUseOffsetInAnchor(*layoutObject)) {
+ InlineTextBox* const textBox = toLayoutText(layoutObject)->lastTextBox();
+ return LayoutPosition(InlineBoxPosition(textBox, textBox->end() + 1));
+ }
+ return LayoutPosition(PositionAnchorType::AfterAnchor, layoutObject);
+}
+
+// static
+LayoutPosition LayoutPosition::beforeAnchorOf(InlineBox* inlineBox)
+{
+ if (shouldUseOffsetInAnchor(*inlineBox))
+ return LayoutPosition(InlineBoxPosition(inlineBox, toInlineTextBox(inlineBox)->start()));
+ return LayoutPosition(PositionAnchorType::BeforeAnchor, layoutObjectOf(*inlineBox));
+}
+
+// static
+LayoutPosition LayoutPosition::beforeAnchorOf(LayoutObject* layoutObject)
+{
+ if (shouldUseOffsetInAnchor(*layoutObject)) {
+ InlineTextBox* const textBox = toLayoutText(layoutObject)->firstTextBox();
+ return LayoutPosition(InlineBoxPosition(textBox, textBox->start()));
+ }
+ return LayoutPosition(PositionAnchorType::BeforeAnchor, layoutObject);
+}
+
+// static
+LayoutPosition LayoutPosition::beforeChildrenOf(LayoutObject* layoutObject)
+{
+ DCHECK(layoutObject->canHaveChildren()) << layoutObject;
+ return LayoutPosition(PositionAnchorType::BeforeChildren, layoutObject);
+}
+
+// Returns a |LayoutPosition| for |position| for
+// - |BeforeAnchor|
+// - |BeforeChildren|
+// - |OffsetInContainer| for |Text|
+// - |OffsetInContainer| for offset:0 and anchor node w/o children.
+// Other anchor types of |position| are transformed to equivalent position.
+// Note: We should not normalize |position|, caret position can be different
+// for after node and before node, e.g.
+// "hello" <img>
+// afterNode("hello") and beforeNode(<img>) point to between "hello" and
+// <img>.
+template <typename Strategy>
+LayoutPosition LayoutPosition::createAlgorithm(const PositionTemplate<Strategy>& position)
+{
+ if (position.isNull())
+ return LayoutPosition();
+ DCHECK(position.inShadowIncludingDocument()) << position;
+ DCHECK(position.document()->isActive()) << position;
+ DCHECK_GE(position.document()->lifecycle().state(), DocumentLifecycle::LayoutClean) << position;
+ Node* const anchorNode = position.anchorNode();
+ LayoutObject* const layoutObject = anchorNode->layoutObject();
+ if (!layoutObject)
+ return LayoutPosition();
+ switch (position.anchorType()) {
+ case PositionAnchorType::AfterAnchor:
+ return afterAnchorOf(layoutObject);
+ case PositionAnchorType::AfterChildren:
+ DCHECK(layoutObject->canHaveChildren()) << layoutObject;
+ if (LayoutObject* lastChild = LayoutTreeTraversal::lastChild(*layoutObject))
+ return afterAnchorOf(lastChild);
+ return beforeChildrenOf(layoutObject);
+ case PositionAnchorType::BeforeAnchor:
+ return beforeAnchorOf(layoutObject);
+ case PositionAnchorType::BeforeChildren:
+ DCHECK(layoutObject->canHaveChildren()) << layoutObject;
+ if (LayoutObject* firstChild = LayoutTreeTraversal::firstChild(*layoutObject))
+ return beforeAnchorOf(firstChild);
+ return beforeChildrenOf(layoutObject);
+ case PositionAnchorType::OffsetInAnchor: {
+ const int offsetInNode = position.offsetInContainerNode();
+ if (anchorNode->isTextNode()) {
+ LayoutText* const layoutText = toLayoutText(layoutObject);
+ if (!layoutText->firstTextBox())
+ return offsetInNode == 0 ? beforeAnchorOf(layoutObject) : afterAnchorOf(layoutObject);
+ const InlineBoxPosition& boxPosition = findInlineBoxPosition(*toText(anchorNode), offsetInNode);
+ if (boxPosition.inlineBox)
+ return LayoutPosition(boxPosition);
+ NOTREACHED() << "Why no inlineBox? " << anchorNode << ' ' << offsetInNode;
+ return LayoutPosition();
+ }
+ if (!LayoutTreeTraversal::hasChildren(*layoutObject))
+ return beforeChildrenOf(layoutObject);
+ if (Node* child = Strategy::childAt(*anchorNode, offsetInNode)) {
+ if (child->isTextNode())
+ return create(Position(child, 0));
+ return create(PositionTemplate<Strategy>::beforeNode(child));
+ }
+ if (Node* lastChild = Strategy::lastChild(*anchorNode)) {
+ if (lastChild->isTextNode())
+ return create(PositionTemplate<Strategy>(lastChild, toText(lastChild)->length()));
+ return create(PositionTemplate<Strategy>::afterNode(lastChild));
+ }
+ return beforeChildrenOf(layoutObject);
+ }
+ }
+ NOTREACHED() << "Invalid anchor type " << position;
+ return LayoutPosition();
+}
+
+// static
+LayoutPosition LayoutPosition::create(const Position& position)
+{
+ return createAlgorithm<EditingStrategy>(position);
+}
+
+// static
+LayoutPosition LayoutPosition::create(const PositionInFlatTree& position)
+{
+ return createAlgorithm<EditingInFlatTreeStrategy>(position);
+}
+
+// static
+LayoutObject* LayoutPosition::layoutObjectOf(const InlineBox& inlineBox)
+{
+ return inlineBox.getLineLayoutItem().layoutObject();
+}
+
+LayoutPosition leftPositionOf(const LayoutPosition& position)
+{
+ if (position.isNull())
+ return LayoutPosition();
+ if (position.isLeftToRightDirection())
+ return previousPositionOf(position);
+ return nextPositionOf(position);
+}
+
+// |nextPositionOf()| returns a next position of |position| by Unicode
+// code unit, 16-bit quantity. Callers of this function should handle
+// Unicode surrogate pair.
+LayoutPosition nextPositionOf(const LayoutPosition& position)
+{
+ DCHECK(position.isValid()) << position;
+ LayoutObject* const layoutObject = position.m_layoutObject;
+ switch (position.m_anchorType) {
+ case PositionAnchorType::AfterAnchor:
+ if (LayoutObject* nextSibling = LayoutTreeTraversal::nextSibling(*layoutObject)) {
+ // <a></a>|<b></b> => <a></a>|<b></b>
+ return LayoutPosition::beforeAnchorOf(nextSibling);
+ }
+ if (LayoutObject* parent = LayoutTreeTraversal::parent(*layoutObject)) {
+ // <a><b></b>|</a> => <a><b></b></a>|
+ return LayoutPosition::afterAnchorOf(parent);
+ }
+ return LayoutPosition();
+ case PositionAnchorType::BeforeAnchor:
+ if (LayoutObject* firstChild = LayoutTreeTraversal::firstChild(*layoutObject)) {
+ // |<a>x</a> => <a>|x</a>
+ return LayoutPosition::beforeAnchorOf(firstChild);
+ }
+ if (layoutObject->canHaveChildren()) {
+ // |<a></a> => <a>|</a>
+ return LayoutPosition::beforeChildrenOf(layoutObject);
+ }
+ // |<br> => <br>|
+ return LayoutPosition::afterAnchorOf(layoutObject);
+ case PositionAnchorType::BeforeChildren:
+ // <a>|</a> => <a></a>|
+ return LayoutPosition::afterAnchorOf(layoutObject);
+ case PositionAnchorType::OffsetInAnchor: {
+ DCHECK(position.m_inlineBox);
+ InlineTextBox* const inlineTextBox = toInlineTextBox(position.m_inlineBox);
+ const int newOffset = position.m_offsetInBox + 1;
+ // Note: InlineTextBox::end() == start() + length() - 1
+ if (static_cast<unsigned>(newOffset) <= inlineTextBox->end() + 1)
+ return LayoutPosition(InlineBoxPosition(inlineTextBox, newOffset));
+
+ if (InlineBox* nextLeafChild = position.m_inlineBox->nextLeafChild()) {
+ // Move within current |RootInlineBox| or next |RootInlineBox|
+ return LayoutPosition::beforeAnchorOf(nextLeafChild);
+ }
+
+ if (const RootInlineBox* nextRoot = position.m_inlineBox->root().nextRootBox()) {
+ // Move to first |InlineBox| of next line.
+ return LayoutPosition::beforeAnchorOf(nextRoot->firstChild());
+ }
+
+ // Below logic is as same as |AfterAnchor|.
+ if (LayoutObject* nextSibling = LayoutTreeTraversal::nextSibling(*layoutObject)) {
+ // foo|<a></a> => foo|<a></a>
+ return LayoutPosition::beforeAnchorOf(nextSibling);
+ }
+ if (LayoutObject* parent = LayoutTreeTraversal::parent(*layoutObject)) {
+ // <a>foo|</a> => <a>foo</a>|
+ return LayoutPosition::afterAnchorOf(parent);
+ }
+ return LayoutPosition();
+ }
+ }
+ NOTREACHED() << "Invalid anchor type " << position;
+ return LayoutPosition();
+}
+
+LayoutPosition LayoutPosition::previousPositionOf(LayoutObject* layoutObject)
+{
+ LayoutObject* const previous = LayoutTreeTraversal::previous(*layoutObject);
+ if (!previous)
+ return LayoutPosition();
+ if (previous == LayoutTreeTraversal::parent(*layoutObject)) {
+ // |layoutObject| is a first child of parent. The previous position is
+ // before parent.
+ return LayoutPosition::beforeAnchorOf(previous);
+ }
+ return LayoutPosition::afterAnchorOf(previous);
+}
+
+// |previousPositionOf()| returns a previous position of |position| by Unicode
+// code unit, 16-bit quantity. Callers of this function should handle
+// Unicode surrogate pair.
+LayoutPosition previousPositionOf(const LayoutPosition& position)
+{
+ DCHECK(position.isValid()) << position;
+ LayoutObject* const layoutObject = position.m_layoutObject;
+ switch (position.m_anchorType) {
+ case PositionAnchorType::AfterAnchor:
+ if (LayoutObject* lastChild = LayoutTreeTraversal::lastChild(*layoutObject))
+ return LayoutPosition::afterAnchorOf(lastChild);
+ return LayoutPosition::beforeAnchorOf(layoutObject);
+ case PositionAnchorType::BeforeAnchor:
+ case PositionAnchorType::BeforeChildren:
+ return LayoutPosition::previousPositionOf(layoutObject);
+ case PositionAnchorType::OffsetInAnchor:
+ DCHECK(position.m_inlineBox) << position;
+ if (position.isOffsetInAnchor() && position.m_offsetInBox > 0) {
+ InlineTextBox* const inlineTextBox = toInlineTextBox(position.m_inlineBox);
+ const int newOffset = position.m_offsetInBox - 1;
+ if (static_cast<unsigned>(newOffset) >= inlineTextBox->start())
+ return LayoutPosition(InlineBoxPosition(inlineTextBox, newOffset));
+ }
+ if (InlineBox* previousLeafChild = position.m_inlineBox->prevLeafChild()) {
+#if 0
+ if (shouldUseOffsetInAnchor(*previousLeafChild)) {
+ // In case of adjacent Text node, we move to before end of
+ // character instead of after previous Text node, e.g.
+ // <S>ab</S><U>|cd</U> => <S>a|b</S><U>cd</U>,
+ // instead of <S>ab|</S><U>cd</U>
+ return LayoutPosition(InlineBoxPosition(previousLeafChild, toInlineTextBox(previousLeafChild)->end()));
+ }
+#endif
+ return LayoutPosition::afterAnchorOf(previousLeafChild);
+ }
+
+ if (const RootInlineBox* previousRoot = position.m_inlineBox->root().prevRootBox()) {
+ // Move to last |InlineBox| of previous line.
+ return LayoutPosition::afterAnchorOf(previousRoot->lastChild());
+ }
+
+ return LayoutPosition::previousPositionOf(layoutObject);
+ }
+ NOTREACHED() << "Invalid anchor type " << position;
+ return LayoutPosition();
+}
+
+LayoutPosition rightPositionOf(const LayoutPosition& position)
+{
+ if (position.isNull())
+ return LayoutPosition();
+ if (position.isLeftToRightDirection())
+ return nextPositionOf(position);
+ return previousPositionOf(position);
+}
+
+class LayoutPositionPrinter final {
+ STATIC_ONLY(LayoutPositionPrinter);
+
+public:
+ // |LayoutPosition| member variable getters
+ static PositionAnchorType anchorTypeOf(const LayoutPosition&);
+ static String nameOf(const LayoutPosition&);
+ static int textOffsetOf(const LayoutPosition&);
+};
+
+// static
+PositionAnchorType LayoutPositionPrinter::anchorTypeOf(const LayoutPosition& position)
+{
+ return position.m_anchorType;
+}
+
+// static
+String LayoutPositionPrinter::nameOf(const LayoutPosition& position)
+{
+ if (position.isNull())
+ return emptyString();
+ if (position.m_inlineBox)
+ return position.m_inlineBox->debugName();
+ return position.m_layoutObject->debugName();
+}
+
+// static
+int LayoutPositionPrinter::textOffsetOf(const LayoutPosition& position)
+{
+ ASSERT(position.isOffsetInAnchor());
+ return position.m_offsetInBox;
+}
+
+std::ostream& operator<<(std::ostream& ostream, const LayoutPosition& position)
+{
+ if (position.isNull())
+ return ostream << "null";
+ ostream << LayoutPositionPrinter::nameOf(position) << '@';
+ if (position.isOffsetInAnchor())
+ return ostream << std::dec << LayoutPositionPrinter::textOffsetOf(position);
+ return ostream << LayoutPositionPrinter::anchorTypeOf(position);
+}
+
+} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/core/editing/LayoutPosition.h ('k') | third_party/WebKit/Source/core/editing/LayoutPositionTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698