| 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
|
|
|