blob: 5e95838e8cc2ae72983ba91a3002f1caf4dce3a8 [file] [log] [blame]
/*
* Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "VisiblePosition.h"
#include "Document.h"
#include "Element.h"
#include "HTMLNames.h"
#include "InlineTextBox.h"
#include "Logging.h"
#include "Range.h"
#include "Text.h"
#include "htmlediting.h"
#include "visible_units.h"
namespace WebCore {
using namespace HTMLNames;
VisiblePosition::VisiblePosition(const Position &pos, EAffinity affinity)
{
init(pos, affinity);
}
VisiblePosition::VisiblePosition(Node *node, int offset, EAffinity affinity)
{
ASSERT(offset >= 0);
init(Position(node, offset), affinity);
}
void VisiblePosition::init(const Position& position, EAffinity affinity)
{
m_affinity = affinity;
m_deepPosition = canonicalPosition(position);
// When not at a line wrap, make sure to end up with DOWNSTREAM affinity.
if (m_affinity == UPSTREAM && (isNull() || inSameLine(VisiblePosition(position, DOWNSTREAM), *this)))
m_affinity = DOWNSTREAM;
}
VisiblePosition VisiblePosition::next(bool stayInEditableContent) const
{
VisiblePosition next(nextVisuallyDistinctCandidate(m_deepPosition), m_affinity);
if (!stayInEditableContent || next.isNull())
return next;
Node* highestRoot = highestEditableRoot(deepEquivalent());
if (!next.deepEquivalent().node()->isDescendantOf(highestRoot))
return VisiblePosition();
if (highestEditableRoot(next.deepEquivalent()) == highestRoot)
return next;
return firstEditablePositionAfterPositionInRoot(next.deepEquivalent(), highestRoot);
}
VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const
{
// find first previous DOM position that is visible
Position pos = previousVisuallyDistinctCandidate(m_deepPosition);
// return null visible position if there is no previous visible position
if (pos.atStart())
return VisiblePosition();
VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM);
ASSERT(prev != *this);
#ifndef NDEBUG
// we should always be able to make the affinity DOWNSTREAM, because going previous from an
// UPSTREAM position can never yield another UPSTREAM position (unless line wrap length is 0!).
if (prev.isNotNull() && m_affinity == UPSTREAM) {
VisiblePosition temp = prev;
temp.setAffinity(UPSTREAM);
ASSERT(inSameLine(temp, prev));
}
#endif
if (!stayInEditableContent || prev.isNull())
return prev;
Node* highestRoot = highestEditableRoot(deepEquivalent());
if (!prev.deepEquivalent().node()->isDescendantOf(highestRoot))
return VisiblePosition();
if (highestEditableRoot(prev.deepEquivalent()) == highestRoot)
return prev;
return lastEditablePositionBeforePositionInRoot(prev.deepEquivalent(), highestRoot);
}
Position canonicalizeCandidate(const Position& candidate)
{
if (candidate.isNull())
return Position();
ASSERT(candidate.isCandidate());
Position upstream = candidate.upstream();
if (upstream.isCandidate())
return upstream;
return candidate;
}
Position VisiblePosition::canonicalPosition(const Position& position)
{
// FIXME (9535): Canonicalizing to the leftmost candidate means that if we're at a line wrap, we will
// ask renderers to paint downstream carets for other renderers.
// To fix this, we need to either a) add code to all paintCarets to pass the responsibility off to
// the appropriate renderer for VisiblePosition's like these, or b) canonicalize to the rightmost candidate
// unless the affinity is upstream.
Node* node = position.node();
if (!node)
return Position();
node->document()->updateLayoutIgnorePendingStylesheets();
Position candidate = position.upstream();
if (candidate.isCandidate())
return candidate;
candidate = position.downstream();
if (candidate.isCandidate())
return candidate;
// When neither upstream or downstream gets us to a candidate (upstream/downstream won't leave
// blocks or enter new ones), we search forward and backward until we find one.
Position next = canonicalizeCandidate(nextCandidate(position));
Position prev = canonicalizeCandidate(previousCandidate(position));
Node* nextNode = next.node();
Node* prevNode = prev.node();
// The new position must be in the same editable element. Enforce that first.
// Unless the descent is from a non-editable html element to an editable body.
if (node->hasTagName(htmlTag) && !node->isContentEditable())
return next.isNotNull() ? next : prev;
Node* editingRoot = editableRootForPosition(position);
// If the html element is editable, descending into its body will look like a descent
// from non-editable to editable content since rootEditableElement() always stops at the body.
if (editingRoot && editingRoot->hasTagName(htmlTag) || position.node()->isDocumentNode())
return next.isNotNull() ? next : prev;
bool prevIsInSameEditableElement = prevNode && editableRootForPosition(prev) == editingRoot;
bool nextIsInSameEditableElement = nextNode && editableRootForPosition(next) == editingRoot;
if (prevIsInSameEditableElement && !nextIsInSameEditableElement)
return prev;
if (nextIsInSameEditableElement && !prevIsInSameEditableElement)
return next;
if (!nextIsInSameEditableElement && !prevIsInSameEditableElement)
return Position();
// The new position should be in the same block flow element. Favor that.
Node *originalBlock = node->enclosingBlockFlowElement();
bool nextIsOutsideOriginalBlock = !nextNode->isDescendantOf(originalBlock) && nextNode != originalBlock;
bool prevIsOutsideOriginalBlock = !prevNode->isDescendantOf(originalBlock) && prevNode != originalBlock;
if (nextIsOutsideOriginalBlock && !prevIsOutsideOriginalBlock)
return prev;
return next;
}
UChar VisiblePosition::characterAfter() const
{
// We canonicalize to the first of two equivalent candidates, but the second of the two candidates
// is the one that will be inside the text node containing the character after this visible position.
Position pos = m_deepPosition.downstream();
Node* node = pos.node();
if (!node || !node->isTextNode())
return 0;
Text* textNode = static_cast<Text*>(pos.node());
int offset = pos.offset();
if ((unsigned)offset >= textNode->length())
return 0;
return textNode->data()[offset];
}
IntRect VisiblePosition::caretRect() const
{
if (!m_deepPosition.node() || !m_deepPosition.node()->renderer())
return IntRect();
return m_deepPosition.node()->renderer()->caretRect(m_deepPosition.offset(), m_affinity);
}
void VisiblePosition::debugPosition(const char *msg) const
{
if (isNull())
fprintf(stderr, "Position [%s]: null\n", msg);
else
fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, m_deepPosition.node()->nodeName().deprecatedString().latin1(), m_deepPosition.node(), m_deepPosition.offset());
}
#ifndef NDEBUG
void VisiblePosition::formatForDebugger(char* buffer, unsigned length) const
{
m_deepPosition.formatForDebugger(buffer, length);
}
void VisiblePosition::showTreeForThis() const
{
m_deepPosition.showTreeForThis();
}
#endif
PassRefPtr<Range> makeRange(const VisiblePosition &start, const VisiblePosition &end)
{
Position s = rangeCompliantEquivalent(start);
Position e = rangeCompliantEquivalent(end);
return new Range(s.node()->document(), s.node(), s.offset(), e.node(), e.offset());
}
VisiblePosition startVisiblePosition(const Range *r, EAffinity affinity)
{
int exception = 0;
return VisiblePosition(r->startContainer(exception), r->startOffset(exception), affinity);
}
VisiblePosition endVisiblePosition(const Range *r, EAffinity affinity)
{
int exception = 0;
return VisiblePosition(r->endContainer(exception), r->endOffset(exception), affinity);
}
bool setStart(Range *r, const VisiblePosition &visiblePosition)
{
if (!r)
return false;
Position p = rangeCompliantEquivalent(visiblePosition);
int code = 0;
r->setStart(p.node(), p.offset(), code);
return code == 0;
}
bool setEnd(Range *r, const VisiblePosition &visiblePosition)
{
if (!r)
return false;
Position p = rangeCompliantEquivalent(visiblePosition);
int code = 0;
r->setEnd(p.node(), p.offset(), code);
return code == 0;
}
Node *enclosingBlockFlowElement(const VisiblePosition &visiblePosition)
{
if (visiblePosition.isNull())
return NULL;
return visiblePosition.deepEquivalent().node()->enclosingBlockFlowElement();
}
bool isFirstVisiblePositionInNode(const VisiblePosition &visiblePosition, const Node *node)
{
if (visiblePosition.isNull())
return false;
if (!visiblePosition.deepEquivalent().node()->isDescendantOf(node))
return false;
VisiblePosition previous = visiblePosition.previous();
return previous.isNull() || !previous.deepEquivalent().node()->isDescendantOf(node);
}
bool isLastVisiblePositionInNode(const VisiblePosition &visiblePosition, const Node *node)
{
if (visiblePosition.isNull())
return false;
if (!visiblePosition.deepEquivalent().node()->isDescendantOf(node))
return false;
VisiblePosition next = visiblePosition.next();
return next.isNull() || !next.deepEquivalent().node()->isDescendantOf(node);
}
} // namespace WebCore
#ifndef NDEBUG
void showTree(const WebCore::VisiblePosition* vpos)
{
if (vpos)
vpos->showTreeForThis();
}
void showTree(const WebCore::VisiblePosition& vpos)
{
vpos.showTreeForThis();
}
#endif