blob: 5268054e3f9c3b6518930e97e70e5280836505ed [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 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 "core/editing/Position.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/TextAffinity.h"
#include "wtf/text/CString.h"
#include <stdio.h>
namespace blink {
#if ENABLE(ASSERT)
static bool canBeAnchorNode(Node* node)
{
if (!node || node->isFirstLetterPseudoElement())
return true;
return !node->isPseudoElement();
}
#endif
template <typename Strategy>
const TreeScope* PositionTemplate<Strategy>::commonAncestorTreeScope(const PositionTemplate<Strategy>& a, const PositionTemplate<Strategy>& b)
{
if (!a.computeContainerNode() || !b.computeContainerNode())
return nullptr;
return a.computeContainerNode()->treeScope().commonAncestorTreeScope(b.computeContainerNode()->treeScope());
}
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::editingPositionOf(PassRefPtrWillBeRawPtr<Node> anchorNode, int offset)
{
if (!anchorNode || anchorNode->isTextNode())
return PositionTemplate<Strategy>(anchorNode, offset);
if (!Strategy::editingIgnoresContent(anchorNode.get()))
return PositionTemplate<Strategy>(anchorNode, offset);
if (offset == 0)
return PositionTemplate<Strategy>(anchorNode, PositionAnchorType::BeforeAnchor);
// Note: |offset| can be >= 1, if |anchorNode| have child nodes, e.g.
// using Node.appendChild() to add a child node TEXTAREA.
ASSERT(offset >= 1);
return PositionTemplate<Strategy>(anchorNode, PositionAnchorType::AfterAnchor);
}
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(PassRefPtrWillBeRawPtr<Node> anchorNode, PositionAnchorType anchorType)
: m_anchorNode(anchorNode)
, m_offset(0)
, m_anchorType(anchorType)
{
if (!m_anchorNode) {
m_anchorType = PositionAnchorType::OffsetInAnchor;
return;
}
if (m_anchorNode->isTextNode()) {
ASSERT(m_anchorType == PositionAnchorType::BeforeAnchor || m_anchorType == PositionAnchorType::AfterAnchor);
return;
}
ASSERT(canBeAnchorNode(m_anchorNode.get()));
ASSERT(m_anchorType != PositionAnchorType::OffsetInAnchor);
}
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(PassRefPtrWillBeRawPtr<Node> anchorNode, int offset)
: m_anchorNode(anchorNode)
, m_offset(offset)
, m_anchorType(PositionAnchorType::OffsetInAnchor)
{
if (m_anchorNode)
ASSERT(offset >= 0);
else
ASSERT(offset == 0);
ASSERT(canBeAnchorNode(m_anchorNode.get()));
}
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(const PositionTemplate& other)
: m_anchorNode(other.m_anchorNode)
, m_offset(other.m_offset)
, m_anchorType(other.m_anchorType)
{
}
// --
template <typename Strategy>
Node* PositionTemplate<Strategy>::computeContainerNode() const
{
if (!m_anchorNode)
return 0;
switch (anchorType()) {
case PositionAnchorType::BeforeChildren:
case PositionAnchorType::AfterChildren:
case PositionAnchorType::OffsetInAnchor:
return m_anchorNode.get();
case PositionAnchorType::BeforeAnchor:
case PositionAnchorType::AfterAnchor:
return Strategy::parent(*m_anchorNode);
}
ASSERT_NOT_REACHED();
return 0;
}
template <typename Strategy>
int PositionTemplate<Strategy>::computeOffsetInContainerNode() const
{
if (!m_anchorNode)
return 0;
switch (anchorType()) {
case PositionAnchorType::BeforeChildren:
return 0;
case PositionAnchorType::AfterChildren:
return lastOffsetInNode(m_anchorNode.get());
case PositionAnchorType::OffsetInAnchor:
return minOffsetForNode(m_anchorNode.get(), m_offset);
case PositionAnchorType::BeforeAnchor:
return Strategy::index(*m_anchorNode);
case PositionAnchorType::AfterAnchor:
return Strategy::index(*m_anchorNode) + 1;
}
ASSERT_NOT_REACHED();
return 0;
}
// Neighbor-anchored positions are invalid DOM positions, so they need to be
// fixed up before handing them off to the Range object.
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::parentAnchoredEquivalent() const
{
if (!m_anchorNode)
return PositionTemplate<Strategy>();
// FIXME: This should only be necessary for legacy positions, but is also needed for positions before and after Tables
if (m_offset == 0 && !isAfterAnchorOrAfterChildren()) {
if (Strategy::parent(*m_anchorNode) && (Strategy::editingIgnoresContent(m_anchorNode.get()) || isRenderedHTMLTableElement(m_anchorNode.get())))
return inParentBeforeNode(*m_anchorNode);
return PositionTemplate<Strategy>(m_anchorNode.get(), 0);
}
if (!m_anchorNode->offsetInCharacters()
&& (isAfterAnchorOrAfterChildren() || static_cast<unsigned>(m_offset) == m_anchorNode->countChildren())
&& (Strategy::editingIgnoresContent(m_anchorNode.get()) || isRenderedHTMLTableElement(m_anchorNode.get()))
&& computeContainerNode()) {
return inParentAfterNode(*m_anchorNode);
}
return PositionTemplate<Strategy>(computeContainerNode(), computeOffsetInContainerNode());
}
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::toOffsetInAnchor() const
{
if (isNull())
return PositionTemplate<Strategy>();
return PositionTemplate<Strategy>(computeContainerNode(), computeOffsetInContainerNode());
}
template <typename Strategy>
int PositionTemplate<Strategy>::computeEditingOffset() const
{
if (isAfterAnchorOrAfterChildren())
return Strategy::lastOffsetForEditing(m_anchorNode.get());
return m_offset;
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::computeNodeBeforePosition() const
{
if (!m_anchorNode)
return 0;
switch (anchorType()) {
case PositionAnchorType::BeforeChildren:
return 0;
case PositionAnchorType::AfterChildren:
return Strategy::lastChild(*m_anchorNode);
case PositionAnchorType::OffsetInAnchor:
return m_offset ? Strategy::childAt(*m_anchorNode, m_offset - 1) : 0;
case PositionAnchorType::BeforeAnchor:
return Strategy::previousSibling(*m_anchorNode);
case PositionAnchorType::AfterAnchor:
return m_anchorNode.get();
}
ASSERT_NOT_REACHED();
return 0;
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::computeNodeAfterPosition() const
{
if (!m_anchorNode)
return 0;
switch (anchorType()) {
case PositionAnchorType::BeforeChildren:
return Strategy::firstChild(*m_anchorNode);
case PositionAnchorType::AfterChildren:
return 0;
case PositionAnchorType::OffsetInAnchor:
return Strategy::childAt(*m_anchorNode, m_offset);
case PositionAnchorType::BeforeAnchor:
return m_anchorNode.get();
case PositionAnchorType::AfterAnchor:
return Strategy::nextSibling(*m_anchorNode);
}
ASSERT_NOT_REACHED();
return 0;
}
// An implementation of |Range::firstNode()|.
template <typename Strategy>
Node* PositionTemplate<Strategy>::nodeAsRangeFirstNode() const
{
if (!m_anchorNode)
return nullptr;
if (!isOffsetInAnchor())
return toOffsetInAnchor().nodeAsRangeFirstNode();
if (m_anchorNode->offsetInCharacters())
return m_anchorNode.get();
if (Node* child = Strategy::childAt(*m_anchorNode, m_offset))
return child;
if (!m_offset)
return m_anchorNode.get();
return Strategy::nextSkippingChildren(*m_anchorNode);
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::nodeAsRangeLastNode() const
{
if (isNull())
return nullptr;
if (Node* pastLastNode = nodeAsRangePastLastNode())
return Strategy::previous(*pastLastNode);
return &Strategy::lastWithinOrSelf(*computeContainerNode());
}
// An implementation of |Range::pastLastNode()|.
template <typename Strategy>
Node* PositionTemplate<Strategy>::nodeAsRangePastLastNode() const
{
if (!m_anchorNode)
return nullptr;
if (!isOffsetInAnchor())
return toOffsetInAnchor().nodeAsRangePastLastNode();
if (m_anchorNode->offsetInCharacters())
return Strategy::nextSkippingChildren(*m_anchorNode);
if (Node* child = Strategy::childAt(*m_anchorNode, m_offset))
return child;
return Strategy::nextSkippingChildren(*m_anchorNode);
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::commonAncestorContainer(const PositionTemplate<Strategy>& other) const
{
return Strategy::commonAncestor(*computeContainerNode(), *other.computeContainerNode());
}
int comparePositions(const PositionInComposedTree& positionA, const PositionInComposedTree& positionB)
{
ASSERT(positionA.isNotNull());
ASSERT(positionB.isNotNull());
positionA.anchorNode()->updateDistribution();
Node* containerA = positionA.computeContainerNode();
positionB.anchorNode()->updateDistribution();
Node* containerB = positionB.computeContainerNode();
int offsetA = positionA.computeOffsetInContainerNode();
int offsetB = positionB.computeOffsetInContainerNode();
return comparePositionsInComposedTree(containerA, offsetA, containerB, offsetB);
}
template <typename Strategy>
int PositionTemplate<Strategy>::compareTo(const PositionTemplate<Strategy>& other) const
{
return comparePositions(*this, other);
}
template <typename Strategy>
bool PositionTemplate<Strategy>::atFirstEditingPositionForNode() const
{
if (isNull())
return true;
// FIXME: Position before anchor shouldn't be considered as at the first editing position for node
// since that position resides outside of the node.
switch (m_anchorType) {
case PositionAnchorType::OffsetInAnchor:
return m_offset == 0;
case PositionAnchorType::BeforeChildren:
case PositionAnchorType::BeforeAnchor:
return true;
case PositionAnchorType::AfterChildren:
case PositionAnchorType::AfterAnchor:
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead
// of DOM tree version.
return !EditingStrategy::lastOffsetForEditing(anchorNode());
}
ASSERT_NOT_REACHED();
return false;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::atLastEditingPositionForNode() const
{
if (isNull())
return true;
// TODO(yosin): Position after anchor shouldn't be considered as at the
// first editing position for node since that position resides outside of
// the node.
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of
// DOM tree version.
return isAfterAnchorOrAfterChildren() || m_offset >= EditingStrategy::lastOffsetForEditing(anchorNode());
}
template <typename Strategy>
bool PositionTemplate<Strategy>::atStartOfTree() const
{
if (isNull())
return true;
return !Strategy::parent(*anchorNode()) && m_offset == 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::atEndOfTree() const
{
if (isNull())
return true;
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of
// DOM tree version.
return !Strategy::parent(*anchorNode()) && m_offset >= EditingStrategy::lastOffsetForEditing(anchorNode());
}
template <typename Strategy>
void PositionTemplate<Strategy>::debugPosition(const char* msg) const
{
static const char* const anchorTypes[] = {
"OffsetInAnchor",
"BeforeAnchor",
"AfterAnchor",
"BeforeChildren",
"AfterChildren",
"Invalid",
};
if (isNull()) {
fprintf(stderr, "Position [%s]: null\n", msg);
return;
}
const char* anchorType = anchorTypes[std::min(static_cast<size_t>(m_anchorType), WTF_ARRAY_LENGTH(anchorTypes) - 1)];
if (m_anchorNode->isTextNode()) {
fprintf(stderr, "Position [%s]: %s [%p] %s, (%s) at %d\n", msg, anchorNode()->nodeName().utf8().data(), anchorNode(), anchorType, m_anchorNode->nodeValue().utf8().data(), m_offset);
return;
}
fprintf(stderr, "Position [%s]: %s [%p] %s at %d\n", msg, anchorNode()->nodeName().utf8().data(), anchorNode(), anchorType, m_offset);
}
PositionInComposedTree toPositionInComposedTree(const Position& pos)
{
if (pos.isNull())
return PositionInComposedTree();
if (pos.isOffsetInAnchor()) {
Node* anchor = pos.anchorNode();
if (anchor->offsetInCharacters())
return PositionInComposedTree(anchor, pos.computeOffsetInContainerNode());
ASSERT(!anchor->isSlotOrActiveInsertionPoint());
int offset = pos.computeOffsetInContainerNode();
Node* child = NodeTraversal::childAt(*anchor, offset);
if (!child) {
if (anchor->isShadowRoot())
return PositionInComposedTree(anchor->shadowHost(), PositionAnchorType::AfterChildren);
return PositionInComposedTree(anchor, PositionAnchorType::AfterChildren);
}
child->updateDistribution();
if (child->isSlotOrActiveInsertionPoint()) {
if (anchor->isShadowRoot())
return PositionInComposedTree(anchor->shadowHost(), offset);
return PositionInComposedTree(anchor, offset);
}
if (Node* parent = ComposedTreeTraversal::parent(*child))
return PositionInComposedTree(parent, ComposedTreeTraversal::index(*child));
// When |pos| isn't appeared in composed tree, we map |pos| to after
// children of shadow host.
// e.g. "foo",0 in <progress>foo</progress>
if (anchor->isShadowRoot())
return PositionInComposedTree(anchor->shadowHost(), PositionAnchorType::AfterChildren);
return PositionInComposedTree(anchor, PositionAnchorType::AfterChildren);
}
return PositionInComposedTree(pos.anchorNode(), pos.anchorType());
}
Position toPositionInDOMTree(const Position& position)
{
return position;
}
Position toPositionInDOMTree(const PositionInComposedTree& position)
{
if (position.isNull())
return Position();
Node* anchorNode = position.anchorNode();
switch (position.anchorType()) {
case PositionAnchorType::AfterChildren:
// FIXME: When anchorNode is <img>, assertion fails in the constructor.
return Position(anchorNode, PositionAnchorType::AfterChildren);
case PositionAnchorType::AfterAnchor:
return positionAfterNode(anchorNode);
case PositionAnchorType::BeforeChildren:
return Position(anchorNode, PositionAnchorType::BeforeChildren);
case PositionAnchorType::BeforeAnchor:
return positionBeforeNode(anchorNode);
case PositionAnchorType::OffsetInAnchor: {
int offset = position.offsetInContainerNode();
if (anchorNode->offsetInCharacters())
return Position(anchorNode, offset);
Node* child = ComposedTreeTraversal::childAt(*anchorNode, offset);
if (child)
return Position(child->parentNode(), child->nodeIndex());
if (!position.offsetInContainerNode())
return Position(anchorNode, PositionAnchorType::BeforeChildren);
// |child| is null when the position is at the end of the children.
// <div>foo|</div>
return Position(anchorNode, PositionAnchorType::AfterChildren);
}
default:
ASSERT_NOT_REACHED();
return Position();
}
}
#ifndef NDEBUG
template <typename Strategy>
void PositionTemplate<Strategy>::formatForDebugger(char* buffer, unsigned length) const
{
StringBuilder result;
if (isNull()) {
result.appendLiteral("<null>");
} else {
char s[1024];
result.appendLiteral("offset ");
result.appendNumber(m_offset);
result.appendLiteral(" of ");
anchorNode()->formatForDebugger(s, sizeof(s));
result.append(s);
}
strncpy(buffer, result.toString().utf8().data(), length - 1);
}
template <typename Strategy>
void PositionTemplate<Strategy>::showAnchorTypeAndOffset() const
{
switch (anchorType()) {
case PositionAnchorType::OffsetInAnchor:
fputs("offset", stderr);
break;
case PositionAnchorType::BeforeChildren:
fputs("beforeChildren", stderr);
break;
case PositionAnchorType::AfterChildren:
fputs("afterChildren", stderr);
break;
case PositionAnchorType::BeforeAnchor:
fputs("before", stderr);
break;
case PositionAnchorType::AfterAnchor:
fputs("after", stderr);
break;
}
fprintf(stderr, ", offset:%d\n", m_offset);
}
template <typename Strategy>
void PositionTemplate<Strategy>::showTreeForThis() const
{
if (!anchorNode())
return;
anchorNode()->showTreeForThis();
showAnchorTypeAndOffset();
}
template <typename Strategy>
void PositionTemplate<Strategy>::showTreeForThisInComposedTree() const
{
if (!anchorNode())
return;
anchorNode()->showTreeForThisInComposedTree();
showAnchorTypeAndOffset();
}
#endif
template class CORE_TEMPLATE_EXPORT PositionTemplate<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT PositionTemplate<EditingInComposedTreeStrategy>;
} // namespace blink
#ifndef NDEBUG
void showTree(const blink::Position& pos)
{
pos.showTreeForThis();
}
void showTree(const blink::Position* pos)
{
if (pos)
pos->showTreeForThis();
else
fprintf(stderr, "Cannot showTree for (nil)\n");
}
#endif