blob: eac82d5aadb678ceb04d4fc62e65138dbad85a1d [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007 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 "sky/engine/core/editing/htmlediting.h"
#include "sky/engine/bindings/exception_state.h"
#include "sky/engine/bindings/exception_state_placeholder.h"
#include "sky/engine/core/dom/Document.h"
#include "sky/engine/core/dom/ElementTraversal.h"
#include "sky/engine/core/dom/NodeTraversal.h"
#include "sky/engine/core/dom/PositionIterator.h"
#include "sky/engine/core/dom/Range.h"
#include "sky/engine/core/dom/Text.h"
#include "sky/engine/core/editing/PlainTextRange.h"
#include "sky/engine/core/editing/TextIterator.h"
#include "sky/engine/core/editing/VisiblePosition.h"
#include "sky/engine/core/editing/VisibleUnits.h"
#include "sky/engine/core/frame/LocalFrame.h"
#include "sky/engine/core/rendering/RenderObject.h"
#include "sky/engine/wtf/Assertions.h"
#include "sky/engine/wtf/StdLibExtras.h"
#include "sky/engine/wtf/text/StringBuilder.h"
namespace blink {
// Atomic means that the node has no children, or has children which are ignored for the
// purposes of editing.
bool isAtomicNode(const Node *node)
{
return node && (!node->hasChildren() || editingIgnoresContent(node));
}
// Compare two positions. Only works for non-null values.
int comparePositions(const Position& a, const Position& b)
{
ASSERT(a.isNotNull());
ASSERT(b.isNotNull());
TreeScope* commonScope = commonTreeScope(a.containerNode(), b.containerNode());
ASSERT(commonScope);
if (!commonScope)
return 0;
Node* nodeA = commonScope->ancestorInThisScope(a.containerNode());
ASSERT(nodeA);
bool hasDescendentA = nodeA != a.containerNode();
int offsetA = hasDescendentA ? 0 : a.computeOffsetInContainerNode();
Node* nodeB = commonScope->ancestorInThisScope(b.containerNode());
ASSERT(nodeB);
bool hasDescendentB = nodeB != b.containerNode();
int offsetB = hasDescendentB ? 0 : b.computeOffsetInContainerNode();
int bias = 0;
if (nodeA == nodeB) {
if (hasDescendentA)
bias = -1;
else if (hasDescendentB)
bias = 1;
}
int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB, IGNORE_EXCEPTION);
return result ? result : bias;
}
int comparePositions(const PositionWithAffinity& a, const PositionWithAffinity& b)
{
return comparePositions(a.position(), b.position());
}
int comparePositions(const VisiblePosition& a, const VisiblePosition& b)
{
return comparePositions(a.deepEquivalent(), b.deepEquivalent());
}
ContainerNode* highestEditableRoot(const Position& position, EditableType editableType)
{
if (position.isNull())
return 0;
ContainerNode* highestRoot = editableRootForPosition(position, editableType);
if (!highestRoot)
return 0;
ContainerNode* node = highestRoot->parentNode();
while (node) {
if (node->hasEditableStyle(editableType))
highestRoot = node;
node = node->parentNode();
}
return highestRoot;
}
Element* lowestEditableAncestor(Node* node)
{
while (node) {
if (node->hasEditableStyle())
return node->rootEditableElement();
node = node->parentNode();
}
return 0;
}
bool isEditablePosition(const Position& p, EditableType editableType, EUpdateStyle updateStyle)
{
Node* node = p.parentAnchoredEquivalent().anchorNode();
if (!node)
return false;
if (updateStyle == UpdateStyle)
node->document().updateLayout();
else
ASSERT(updateStyle == DoNotUpdateStyle);
return node->hasEditableStyle(editableType);
}
bool isAtUnsplittableElement(const Position& pos)
{
Node* node = pos.deprecatedNode();
return node == editableRootForPosition(pos);
}
bool isRichlyEditablePosition(const Position& p, EditableType editableType)
{
Node* node = p.deprecatedNode();
if (!node)
return false;
return node->rendererIsRichlyEditable(editableType);
}
Element* editableRootForPosition(const Position& p, EditableType editableType)
{
Node* node = p.containerNode();
if (!node)
return 0;
return node->rootEditableElement(editableType);
}
// Finds the enclosing element until which the tree can be split.
// When a user hits ENTER, he/she won't expect this element to be split into two.
// You may pass it as the second argument of splitTreeToNode.
Element* unsplittableElementForPosition(const Position& p)
{
return editableRootForPosition(p);
}
Position nextCandidate(const Position& position)
{
PositionIterator p = position;
while (!p.atEnd()) {
p.increment();
if (p.isCandidate())
return p;
}
return Position();
}
Position nextVisuallyDistinctCandidate(const Position& position)
{
Position p = position;
Position downstreamStart = p.downstream();
while (!p.atEndOfTree()) {
p = p.next(Character);
if (p.isCandidate() && p.downstream() != downstreamStart)
return p;
}
return Position();
}
Position previousCandidate(const Position& position)
{
PositionIterator p = position;
while (!p.atStart()) {
p.decrement();
if (p.isCandidate())
return p;
}
return Position();
}
Position previousVisuallyDistinctCandidate(const Position& position)
{
Position p = position;
Position downstreamStart = p.downstream();
while (!p.atStartOfTree()) {
p = p.previous(Character);
if (p.isCandidate() && p.downstream() != downstreamStart)
return p;
}
return Position();
}
VisiblePosition firstEditableVisiblePositionAfterPositionInRoot(const Position& position, ContainerNode* highestRoot)
{
// position falls before highestRoot.
if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->hasEditableStyle())
return VisiblePosition(firstPositionInNode(highestRoot));
Position editablePosition = position;
// TODO(ianh): not really sure what's going on here
if (position.deprecatedNode()->treeScope() != highestRoot->treeScope())
editablePosition = positionAfterNode(editablePosition.deprecatedNode());
while (editablePosition.deprecatedNode() && !isEditablePosition(editablePosition) && editablePosition.deprecatedNode()->isDescendantOf(highestRoot))
editablePosition = isAtomicNode(editablePosition.deprecatedNode()) ? positionInParentAfterNode(*editablePosition.deprecatedNode()) : nextVisuallyDistinctCandidate(editablePosition);
if (editablePosition.deprecatedNode() && editablePosition.deprecatedNode() != highestRoot && !editablePosition.deprecatedNode()->isDescendantOf(highestRoot))
return VisiblePosition();
return VisiblePosition(editablePosition);
}
VisiblePosition lastEditableVisiblePositionBeforePositionInRoot(const Position& position, ContainerNode* highestRoot)
{
return VisiblePosition(lastEditablePositionBeforePositionInRoot(position, highestRoot));
}
Position lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot)
{
// When position falls after highestRoot, the result is easy to compute.
if (comparePositions(position, lastPositionInNode(highestRoot)) == 1)
return lastPositionInNode(highestRoot);
Position editablePosition = position;
// TODO(ianh): not really sure what's going on here
if (position.deprecatedNode()->treeScope() != highestRoot->treeScope())
editablePosition = firstPositionInOrBeforeNode(editablePosition.deprecatedNode());
while (editablePosition.deprecatedNode() && !isEditablePosition(editablePosition) && editablePosition.deprecatedNode()->isDescendantOf(highestRoot))
editablePosition = isAtomicNode(editablePosition.deprecatedNode()) ? positionInParentBeforeNode(*editablePosition.deprecatedNode()) : previousVisuallyDistinctCandidate(editablePosition);
if (editablePosition.deprecatedNode() && editablePosition.deprecatedNode() != highestRoot && !editablePosition.deprecatedNode()->isDescendantOf(highestRoot))
return Position();
return editablePosition;
}
// FIXME: The method name, comment, and code say three different things here!
// Whether or not content before and after this node will collapse onto the same line as it.
bool isBlock(const Node* node)
{
return node && node->renderer() && !node->renderer()->isInline();
}
bool isInline(const Node* node)
{
return node && node->renderer() && node->renderer()->isInline();
}
// FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used.
// FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the
// block that contains the table and not the table, and this function should be the only one responsible for
// knowing about these kinds of special cases.
Element* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule)
{
Node* enclosingNode = enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule);
return enclosingNode && enclosingNode->isElementNode() ? toElement(enclosingNode) : 0;
}
Element* enclosingBlockFlowElement(Node& node)
{
if (isBlockFlowElement(node))
return &toElement(node);
for (Node* n = node.parentNode(); n; n = n->parentNode()) {
if (isBlockFlowElement(*n))
return toElement(n);
}
return 0;
}
bool inSameContainingBlockFlowElement(Node* a, Node* b)
{
return a && b && enclosingBlockFlowElement(*a) == enclosingBlockFlowElement(*b);
}
TextDirection directionOfEnclosingBlock(const Position& position)
{
Element* enclosingBlockElement = enclosingBlock(position.containerNode());
if (!enclosingBlockElement)
return LTR;
RenderObject* renderer = enclosingBlockElement->renderer();
return renderer ? renderer->style()->direction() : LTR;
}
// This method is used to create positions in the DOM. It returns the maximum valid offset
// in a node. It returns 1 for some elements even though they do not have children, which
// creates technically invalid DOM Positions. Be sure to call parentAnchoredEquivalent
// on a Position before using it to create a DOM Range, or an exception will be thrown.
int lastOffsetForEditing(const Node* node)
{
ASSERT(node);
if (!node)
return 0;
if (node->offsetInCharacters())
return node->maxCharacterOffset();
if (node->hasChildren())
return node->countChildren();
// NOTE: This should preempt the childNodeCount for, e.g., select nodes
if (editingIgnoresContent(node))
return 1;
return 0;
}
String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph)
{
unsigned length = string.length();
StringBuilder rebalancedString;
rebalancedString.reserveCapacity(length);
bool previousCharacterWasSpace = false;
for (size_t i = 0; i < length; i++) {
UChar c = string[i];
if (!isWhitespace(c)) {
rebalancedString.append(c);
previousCharacterWasSpace = false;
continue;
}
if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i + 1 == length && endIsEndOfParagraph)) {
rebalancedString.append(noBreakSpace);
previousCharacterWasSpace = false;
} else {
rebalancedString.append(' ');
previousCharacterWasSpace = true;
}
}
ASSERT(rebalancedString.length() == length);
return rebalancedString.toString();
}
const String& nonBreakingSpaceString()
{
DEFINE_STATIC_LOCAL(String, nonBreakingSpaceString, (&noBreakSpace, 1));
return nonBreakingSpaceString;
}
// FIXME: need to dump this
bool isSpecialHTMLElement(const Node* n)
{
if (!n)
return false;
if (!n->isElementNode())
return false;
if (n->isLink())
return true;
return false;
}
static HTMLElement* firstInSpecialElement(const Position& pos)
{
Element* rootEditableElement = pos.containerNode()->rootEditableElement();
for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) {
if (isSpecialHTMLElement(n)) {
HTMLElement* specialElement = toHTMLElement(n);
VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
VisiblePosition firstInElement = VisiblePosition(firstPositionInOrBeforeNode(specialElement), DOWNSTREAM);
if (isRenderedTableElement(specialElement) && vPos == firstInElement.next())
return specialElement;
if (vPos == firstInElement)
return specialElement;
}
}
return 0;
}
static HTMLElement* lastInSpecialElement(const Position& pos)
{
Element* rootEditableElement = pos.containerNode()->rootEditableElement();
for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode()) {
if (isSpecialHTMLElement(n)) {
HTMLElement* specialElement = toHTMLElement(n);
VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
VisiblePosition lastInElement = VisiblePosition(lastPositionInOrAfterNode(specialElement), DOWNSTREAM);
if (isRenderedTableElement(specialElement) && vPos == lastInElement.previous())
return specialElement;
if (vPos == lastInElement)
return specialElement;
}
}
return 0;
}
Position positionBeforeContainingSpecialElement(const Position& pos, HTMLElement** containingSpecialElement)
{
HTMLElement* n = firstInSpecialElement(pos);
if (!n)
return pos;
Position result = positionInParentBeforeNode(*n);
if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement())
return pos;
if (containingSpecialElement)
*containingSpecialElement = n;
return result;
}
Position positionAfterContainingSpecialElement(const Position& pos, HTMLElement** containingSpecialElement)
{
HTMLElement* n = lastInSpecialElement(pos);
if (!n)
return pos;
Position result = positionInParentAfterNode(*n);
if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement())
return pos;
if (containingSpecialElement)
*containingSpecialElement = n;
return result;
}
Element* isFirstPositionAfterTable(const VisiblePosition& visiblePosition)
{
Position upstream(visiblePosition.deepEquivalent().upstream());
if (isRenderedTableElement(upstream.deprecatedNode()) && upstream.atLastEditingPositionForNode())
return toElement(upstream.deprecatedNode());
return 0;
}
Element* isLastPositionBeforeTable(const VisiblePosition& visiblePosition)
{
Position downstream(visiblePosition.deepEquivalent().downstream());
if (isRenderedTableElement(downstream.deprecatedNode()) && downstream.atFirstEditingPositionForNode())
return toElement(downstream.deprecatedNode());
return 0;
}
// Returns the visible position at the beginning of a node
VisiblePosition visiblePositionBeforeNode(Node& node)
{
if (node.hasChildren())
return VisiblePosition(firstPositionInOrBeforeNode(&node), DOWNSTREAM);
ASSERT(node.parentNode());
return VisiblePosition(positionInParentBeforeNode(node));
}
// Returns the visible position at the ending of a node
VisiblePosition visiblePositionAfterNode(Node& node)
{
if (node.hasChildren())
return VisiblePosition(lastPositionInOrAfterNode(&node), DOWNSTREAM);
ASSERT(node.parentNode());
return VisiblePosition(positionInParentAfterNode(node));
}
// Create a range object with two visible positions, start and end.
// create(Document*, const Position&, const Position&); will use deprecatedEditingOffset
// Use this function instead of create a regular range object (avoiding editing offset).
PassRefPtr<Range> createRange(Document& document, const VisiblePosition& start, const VisiblePosition& end, ExceptionState& exceptionState)
{
RefPtr<Range> selectedRange = Range::create(document);
selectedRange->setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), exceptionState);
if (!exceptionState.had_exception())
selectedRange->setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), exceptionState);
return selectedRange.release();
}
Element* enclosingElementWithTag(const Position& p, const QualifiedName& tagName)
{
if (p.isNull())
return 0;
ContainerNode* root = highestEditableRoot(p);
Element* ancestor = p.deprecatedNode()->isElementNode() ? toElement(p.deprecatedNode()) : p.deprecatedNode()->parentElement();
for (; ancestor; ancestor = ancestor->parentElement()) {
if (root && !ancestor->hasEditableStyle())
continue;
if (ancestor->hasTagName(tagName))
return ancestor;
if (ancestor == root)
return 0;
}
return 0;
}
Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule)
{
// FIXME: support CanSkipCrossEditingBoundary
ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary);
if (p.isNull())
return 0;
ContainerNode* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0;
for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) {
// Don't return a non-editable node if the input position was editable, since
// the callers from editing will no doubt want to perform editing inside the returned node.
if (root && !n->hasEditableStyle())
continue;
if (nodeIsOfType(n))
return n;
if (n == root)
return 0;
}
return 0;
}
Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule, Node* stayWithin)
{
Node* highest = 0;
ContainerNode* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0;
for (Node* n = p.containerNode(); n && n != stayWithin; n = n->parentNode()) {
if (root && !n->hasEditableStyle())
continue;
if (nodeIsOfType(n))
highest = n;
if (n == root)
break;
}
return highest;
}
static bool hasARenderedDescendant(Node* node, Node* excludedNode)
{
for (Node* n = node->firstChild(); n;) {
if (n == excludedNode) {
n = NodeTraversal::nextSkippingChildren(*n, node);
continue;
}
if (n->renderer())
return true;
n = NodeTraversal::next(*n, node);
}
return false;
}
Node* highestNodeToRemoveInPruning(Node* node, Node* excludeNode)
{
Node* previousNode = 0;
Element* rootEditableElement = node ? node->rootEditableElement() : 0;
for (; node; node = node->parentNode()) {
if (RenderObject* renderer = node->renderer()) {
if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node || excludeNode == node)
return previousNode;
}
previousNode = node;
}
return 0;
}
Element* enclosingAnchorElement(const Position& p)
{
if (p.isNull())
return 0;
for (Element* ancestor = ElementTraversal::firstAncestorOrSelf(*p.deprecatedNode()); ancestor; ancestor = ElementTraversal::firstAncestor(*ancestor)) {
if (ancestor->isLink())
return ancestor;
}
return 0;
}
bool canMergeLists(Element* firstList, Element* secondList)
{
if (!firstList || !secondList)
return false;
return firstList->hasTagName(secondList->tagQName()) // make sure the list types match (ol vs. ul)
&& firstList->hasEditableStyle() && secondList->hasEditableStyle() // both lists are editable
&& firstList->rootEditableElement() == secondList->rootEditableElement() // don't cross editing boundaries
&& isVisiblyAdjacent(positionInParentAfterNode(*firstList), positionInParentBeforeNode(*secondList));
// Make sure there is no visible content between this li and the previous list
}
bool isRenderedTableElement(const Node* node)
{
return false;
}
bool isEmptyTableCell(const Node* node)
{
return false;
}
PassRefPtr<HTMLElement> createDefaultParagraphElement(Document& document)
{
return nullptr;
}
bool isNodeRendered(const Node *node)
{
return node && node->renderer();
}
// return first preceding DOM position rendered at a different location, or "this"
static Position previousCharacterPosition(const Position& position, EAffinity affinity)
{
if (position.isNull())
return Position();
Element* fromRootEditableElement = position.anchorNode()->rootEditableElement();
bool atStartOfLine = isStartOfLine(VisiblePosition(position, affinity));
bool rendered = position.isCandidate();
Position currentPos = position;
while (!currentPos.atStartOfTree()) {
currentPos = currentPos.previous();
if (currentPos.anchorNode()->rootEditableElement() != fromRootEditableElement)
return position;
if (atStartOfLine || !rendered) {
if (currentPos.isCandidate())
return currentPos;
} else if (position.rendersInDifferentPosition(currentPos)) {
return currentPos;
}
}
return position;
}
// This assumes that it starts in editable content.
Position leadingWhitespacePosition(const Position& position, EAffinity affinity, WhitespacePositionOption option)
{
ASSERT(isEditablePosition(position, ContentIsEditable, DoNotUpdateStyle));
if (position.isNull())
return Position();
Position prev = previousCharacterPosition(position, affinity);
if (prev != position && inSameContainingBlockFlowElement(prev.anchorNode(), position.anchorNode()) && prev.anchorNode()->isTextNode()) {
String string = toText(prev.anchorNode())->data();
UChar previousCharacter = string[prev.deprecatedEditingOffset()];
bool isSpace = option == ConsiderNonCollapsibleWhitespace ? (isSpaceOrNewline(previousCharacter) || previousCharacter == noBreakSpace) : isCollapsibleWhitespace(previousCharacter);
if (isSpace && isEditablePosition(prev))
return prev;
}
return Position();
}
// This assumes that it starts in editable content.
Position trailingWhitespacePosition(const Position& position, EAffinity, WhitespacePositionOption option)
{
ASSERT(isEditablePosition(position, ContentIsEditable, DoNotUpdateStyle));
if (position.isNull())
return Position();
VisiblePosition visiblePosition(position);
UChar characterAfterVisiblePosition = visiblePosition.characterAfter();
bool isSpace = option == ConsiderNonCollapsibleWhitespace ? (isSpaceOrNewline(characterAfterVisiblePosition) || characterAfterVisiblePosition == noBreakSpace) : isCollapsibleWhitespace(characterAfterVisiblePosition);
// The space must not be in another paragraph and it must be editable.
if (isSpace && !isEndOfParagraph(visiblePosition) && visiblePosition.next(CannotCrossEditingBoundary).isNotNull())
return position;
return Position();
}
unsigned numEnclosingMailBlockquotes(const Position& p)
{
return 0;
}
void updatePositionForNodeRemoval(Position& position, Node& node)
{
if (position.isNull())
return;
switch (position.anchorType()) {
case Position::PositionIsBeforeChildren:
if (position.containerNode() == node)
position = positionInParentBeforeNode(node);
break;
case Position::PositionIsAfterChildren:
if (position.containerNode() == node)
position = positionInParentAfterNode(node);
break;
case Position::PositionIsOffsetInAnchor:
if (position.containerNode() == node.parentNode() && static_cast<unsigned>(position.offsetInContainerNode()) > node.nodeIndex())
position.moveToOffset(position.offsetInContainerNode() - 1);
else if (node.contains(position.containerNode()))
position = positionInParentBeforeNode(node);
break;
case Position::PositionIsAfterAnchor:
if (node.contains(position.anchorNode()))
position = positionInParentAfterNode(node);
break;
case Position::PositionIsBeforeAnchor:
if (node.contains(position.anchorNode()))
position = positionInParentBeforeNode(node);
break;
}
}
bool isMailHTMLBlockquoteElement(const Node* node)
{
return false;
}
int caretMinOffset(const Node* n)
{
RenderObject* r = n->renderer();
ASSERT(!n->isTextNode() || !r || r->isText()); // FIXME: This was a runtime check that seemingly couldn't fail; changed it to an assertion for now.
return r ? r->caretMinOffset() : 0;
}
// If a node can contain candidates for VisiblePositions, return the offset of the last candidate, otherwise
// return the number of children for container nodes and the length for unrendered text nodes.
int caretMaxOffset(const Node* n)
{
// For rendered text nodes, return the last position that a caret could occupy.
if (n->isTextNode() && n->renderer())
return n->renderer()->caretMaxOffset();
// For containers return the number of children. For others do the same as above.
return lastOffsetForEditing(n);
}
bool lineBreakExistsAtVisiblePosition(const VisiblePosition& visiblePosition)
{
return lineBreakExistsAtPosition(visiblePosition.deepEquivalent().downstream());
}
bool lineBreakExistsAtPosition(const Position& position)
{
if (position.isNull())
return false;
if (!position.anchorNode()->renderer())
return false;
if (!position.anchorNode()->isTextNode() || !position.anchorNode()->renderer()->style()->preserveNewline())
return false;
Text* textNode = toText(position.anchorNode());
unsigned offset = position.offsetInContainerNode();
return offset < textNode->length() && textNode->data()[offset] == '\n';
}
// FIXME: indexForVisiblePosition and visiblePositionForIndex use TextIterators to convert between
// VisiblePositions and indices. But TextIterator iteration using TextIteratorEmitsCharactersBetweenAllVisiblePositions
// does not exactly match VisiblePosition iteration, so using them to preserve a selection during an editing
// opertion is unreliable. TextIterator's TextIteratorEmitsCharactersBetweenAllVisiblePositions mode needs to be fixed,
// or these functions need to be changed to iterate using actual VisiblePositions.
// FIXME: Deploy these functions everywhere that TextIterators are used to convert between VisiblePositions and indices.
int indexForVisiblePosition(const VisiblePosition& visiblePosition, RefPtr<ContainerNode>& scope)
{
if (visiblePosition.isNull())
return 0;
Position p(visiblePosition.deepEquivalent());
Document& document = *p.document();
scope = &document;
RefPtr<Range> range = Range::create(document, firstPositionInNode(scope.get()), p.parentAnchoredEquivalent());
return TextIterator::rangeLength(range.get(), true);
}
VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope)
{
if (!scope)
return VisiblePosition();
RefPtr<Range> range = PlainTextRange(index).createRangeForSelection(*scope);
// Check for an invalid index. Certain editing operations invalidate indices because
// of problems with TextIteratorEmitsCharactersBetweenAllVisiblePositions.
if (!range)
return VisiblePosition();
return VisiblePosition(range->startPosition());
}
// Determines whether two positions are visibly next to each other (first then second)
// while ignoring whitespaces and unrendered nodes
bool isVisiblyAdjacent(const Position& first, const Position& second)
{
return VisiblePosition(first) == VisiblePosition(second.upstream());
}
// Determines whether a node is inside a range or visibly starts and ends at the boundaries of the range.
// Call this function to determine whether a node is visibly fit inside selectedRange
bool isNodeVisiblyContainedWithin(Node& node, const Range& selectedRange)
{
// If the node is inside the range, then it surely is contained within
if (selectedRange.compareNode(&node, IGNORE_EXCEPTION) == Range::NODE_INSIDE)
return true;
bool startIsVisuallySame = visiblePositionBeforeNode(node) == VisiblePosition(selectedRange.startPosition());
if (startIsVisuallySame && comparePositions(positionInParentAfterNode(node), selectedRange.endPosition()) < 0)
return true;
bool endIsVisuallySame = visiblePositionAfterNode(node) == VisiblePosition(selectedRange.endPosition());
if (endIsVisuallySame && comparePositions(selectedRange.startPosition(), positionInParentBeforeNode(node)) < 0)
return true;
return startIsVisuallySame && endIsVisuallySame;
}
bool isRenderedAsNonInlineTableImageOrHR(const Node* node)
{
return false;
}
bool areIdenticalElements(const Node* first, const Node* second)
{
if (!first->isElementNode() || !second->isElementNode())
return false;
const Element* firstElement = toElement(first);
const Element* secondElement = toElement(second);
if (!firstElement->hasTagName(secondElement->tagQName()))
return false;
return firstElement->hasEquivalentAttributes(secondElement);
}
bool isBlockFlowElement(const Node& node)
{
RenderObject* renderer = node.renderer();
return node.isElementNode() && renderer && renderer->isRenderParagraph();
}
} // namespace blink