diff options
Diffstat (limited to 'Source/WebCore/dom/Position.cpp')
-rw-r--r-- | Source/WebCore/dom/Position.cpp | 784 |
1 files changed, 489 insertions, 295 deletions
diff --git a/Source/WebCore/dom/Position.cpp b/Source/WebCore/dom/Position.cpp index 287b37b2b..10c4b540d 100644 --- a/Source/WebCore/dom/Position.cpp +++ b/Source/WebCore/dom/Position.cpp @@ -10,10 +10,10 @@ * 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 + * THIS SOFTWARE IS PROVIDED BY APPLE 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 + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE 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 @@ -27,6 +27,9 @@ #include "Position.h" #include "CSSComputedStyleDeclaration.h" +#include "HTMLBRElement.h" +#include "HTMLBodyElement.h" +#include "HTMLHtmlElement.h" #include "HTMLNames.h" #include "HTMLTableElement.h" #include "InlineElementBox.h" @@ -35,30 +38,38 @@ #include "Logging.h" #include "PositionIterator.h" #include "RenderBlock.h" +#include "RenderFlexibleBox.h" +#include "RenderGrid.h" #include "RenderInline.h" +#include "RenderIterator.h" #include "RenderLineBreak.h" #include "RenderText.h" #include "RuntimeEnabledFeatures.h" #include "Text.h" #include "TextIterator.h" +#include "TextStream.h" #include "VisiblePosition.h" #include "VisibleUnits.h" #include "htmlediting.h" #include <stdio.h> #include <wtf/text/CString.h> #include <wtf/unicode/CharacterNames.h> - + +#if ENABLE(TREE_DEBUGGING) +#include <wtf/text/StringBuilder.h> +#endif + namespace WebCore { using namespace HTMLNames; static bool hasInlineBoxWrapper(RenderObject& renderer) { - if (renderer.isBox() && toRenderBox(renderer).inlineBoxWrapper()) + if (is<RenderBox>(renderer) && downcast<RenderBox>(renderer).inlineBoxWrapper()) return true; - if (renderer.isText() && toRenderText(renderer).firstTextBox()) + if (is<RenderText>(renderer) && downcast<RenderText>(renderer).firstTextBox()) return true; - if (renderer.isLineBreak() && toRenderLineBreak(renderer).inlineBoxWrapper()) + if (is<RenderLineBreak>(renderer) && downcast<RenderLineBreak>(renderer).inlineBoxWrapper()) return true; return false; } @@ -72,7 +83,7 @@ static Node* nextRenderedEditable(Node* node) if (hasInlineBoxWrapper(*renderer)) return node; } - return 0; + return nullptr; } static Node* previousRenderedEditable(Node* node) @@ -84,72 +95,55 @@ static Node* previousRenderedEditable(Node* node) if (hasInlineBoxWrapper(*renderer)) return node; } - return 0; + return nullptr; } -Position::Position(PassRefPtr<Node> anchorNode, LegacyEditingOffset offset) +Position::Position(Node* anchorNode, unsigned offset, LegacyEditingPositionFlag) : m_anchorNode(anchorNode) - , m_offset(offset.value()) + , m_offset(offset) , m_anchorType(anchorTypeForLegacyEditingPosition(m_anchorNode.get(), m_offset)) , m_isLegacyEditingPosition(true) { -#if ENABLE(SHADOW_DOM) - ASSERT((m_anchorNode && RuntimeEnabledFeatures::sharedFeatures().shadowDOMEnabled()) || !m_anchorNode || !m_anchorNode->isShadowRoot() || m_anchorNode == containerNode()); -#else ASSERT(!m_anchorNode || !m_anchorNode->isShadowRoot() || m_anchorNode == containerNode()); -#endif ASSERT(!m_anchorNode || !m_anchorNode->isPseudoElement()); } -Position::Position(PassRefPtr<Node> anchorNode, AnchorType anchorType) +Position::Position(Node* anchorNode, AnchorType anchorType) : m_anchorNode(anchorNode) , m_offset(0) , m_anchorType(anchorType) , m_isLegacyEditingPosition(false) { -#if ENABLE(SHADOW_DOM) - ASSERT((m_anchorNode && RuntimeEnabledFeatures::sharedFeatures().shadowDOMEnabled()) || !m_anchorNode || !m_anchorNode->isShadowRoot() || m_anchorNode == containerNode()); -#else ASSERT(!m_anchorNode || !m_anchorNode->isShadowRoot() || m_anchorNode == containerNode()); -#endif - ASSERT(!m_anchorNode || !m_anchorNode->isPseudoElement()); - ASSERT(anchorType != PositionIsOffsetInAnchor); ASSERT(!((anchorType == PositionIsBeforeChildren || anchorType == PositionIsAfterChildren) - && (m_anchorNode->isTextNode() || editingIgnoresContent(m_anchorNode.get())))); + && (is<Text>(*m_anchorNode) || editingIgnoresContent(*m_anchorNode)))); } -Position::Position(PassRefPtr<Node> anchorNode, int offset, AnchorType anchorType) +Position::Position(Node* anchorNode, int offset, AnchorType anchorType) : m_anchorNode(anchorNode) , m_offset(offset) , m_anchorType(anchorType) , m_isLegacyEditingPosition(false) { -#if ENABLE(SHADOW_DOM) - ASSERT((m_anchorNode && RuntimeEnabledFeatures::sharedFeatures().shadowDOMEnabled()) - || !m_anchorNode || !editingIgnoresContent(m_anchorNode.get()) || !m_anchorNode->isShadowRoot()); -#else - ASSERT(!m_anchorNode || !editingIgnoresContent(m_anchorNode.get()) || !m_anchorNode->isShadowRoot()); -#endif - + ASSERT(!m_anchorNode || !editingIgnoresContent(*m_anchorNode)); ASSERT(!m_anchorNode || !m_anchorNode->isPseudoElement()); - ASSERT(anchorType == PositionIsOffsetInAnchor); } -Position::Position(PassRefPtr<Text> textNode, unsigned offset) +Position::Position(Text* textNode, unsigned offset) : m_anchorNode(textNode) - , m_offset(static_cast<int>(offset)) + , m_offset(offset) , m_anchorType(PositionIsOffsetInAnchor) , m_isLegacyEditingPosition(false) { ASSERT(m_anchorNode); } -void Position::moveToPosition(PassRefPtr<Node> node, int offset) +void Position::moveToPosition(Node* node, int offset) { - ASSERT(!editingIgnoresContent(node.get())); + ASSERT(!editingIgnoresContent(*node)); ASSERT(anchorType() == PositionIsOffsetInAnchor || m_isLegacyEditingPosition); m_anchorNode = node; m_offset = offset; @@ -167,7 +161,7 @@ void Position::moveToOffset(int offset) Node* Position::containerNode() const { if (!m_anchorNode) - return 0; + return nullptr; switch (anchorType()) { case PositionIsBeforeChildren: @@ -176,27 +170,27 @@ Node* Position::containerNode() const return m_anchorNode.get(); case PositionIsBeforeAnchor: case PositionIsAfterAnchor: - return findParent(m_anchorNode.get()); + return m_anchorNode->parentNode(); } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } Text* Position::containerText() const { switch (anchorType()) { case PositionIsOffsetInAnchor: - return m_anchorNode && m_anchorNode->isTextNode() ? toText(m_anchorNode.get()) : 0; + return m_anchorNode && is<Text>(*m_anchorNode) ? downcast<Text>(m_anchorNode.get()) : nullptr; case PositionIsBeforeAnchor: case PositionIsAfterAnchor: - return 0; + return nullptr; case PositionIsBeforeChildren: case PositionIsAfterChildren: - ASSERT(!m_anchorNode || !m_anchorNode->isTextNode()); - return 0; + ASSERT(!m_anchorNode || !is<Text>(*m_anchorNode)); + return nullptr; } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } int Position::computeOffsetInContainerNode() const @@ -212,9 +206,9 @@ int Position::computeOffsetInContainerNode() const case PositionIsOffsetInAnchor: return minOffsetForNode(m_anchorNode.get(), m_offset); case PositionIsBeforeAnchor: - return m_anchorNode->nodeIndex(); + return m_anchorNode->computeNodeIndex(); case PositionIsAfterAnchor: - return m_anchorNode->nodeIndex() + 1; + return m_anchorNode->computeNodeIndex() + 1; } ASSERT_NOT_REACHED(); return 0; @@ -224,7 +218,8 @@ int Position::offsetForPositionAfterAnchor() const { ASSERT(m_anchorType == PositionIsAfterAnchor || m_anchorType == PositionIsAfterChildren); ASSERT(!m_isLegacyEditingPosition); - return lastOffsetForEditing(m_anchorNode.get()); + ASSERT(m_anchorNode); + return m_anchorNode ? lastOffsetForEditing(*m_anchorNode) : 0; } // Neighbor-anchored positions are invalid DOM positions, so they need to be @@ -232,69 +227,70 @@ int Position::offsetForPositionAfterAnchor() const Position Position::parentAnchoredEquivalent() const { if (!m_anchorNode) - return Position(); + return { }; // FIXME: This should only be necessary for legacy positions, but is also needed for positions before and after Tables if (m_offset <= 0 && (m_anchorType != PositionIsAfterAnchor && m_anchorType != PositionIsAfterChildren)) { - if (findParent(m_anchorNode.get()) && (editingIgnoresContent(m_anchorNode.get()) || isTableElement(m_anchorNode.get()))) + if (m_anchorNode->parentNode() && (editingIgnoresContent(*m_anchorNode) || isRenderedTable(m_anchorNode.get()))) return positionInParentBeforeNode(m_anchorNode.get()); return Position(m_anchorNode.get(), 0, PositionIsOffsetInAnchor); } + if (!m_anchorNode->offsetInCharacters() - && (m_anchorType == PositionIsAfterAnchor || m_anchorType == PositionIsAfterChildren || static_cast<unsigned>(m_offset) == m_anchorNode->childNodeCount()) - && (editingIgnoresContent(m_anchorNode.get()) || isTableElement(m_anchorNode.get())) + && (m_anchorType == PositionIsAfterAnchor || m_anchorType == PositionIsAfterChildren || static_cast<unsigned>(m_offset) == m_anchorNode->countChildNodes()) + && (editingIgnoresContent(*m_anchorNode) || isRenderedTable(m_anchorNode.get())) && containerNode()) { return positionInParentAfterNode(m_anchorNode.get()); } - return Position(containerNode(), computeOffsetInContainerNode(), PositionIsOffsetInAnchor); + return { containerNode(), computeOffsetInContainerNode(), PositionIsOffsetInAnchor }; } Node* Position::computeNodeBeforePosition() const { if (!m_anchorNode) - return 0; + return nullptr; switch (anchorType()) { case PositionIsBeforeChildren: - return 0; + return nullptr; case PositionIsAfterChildren: return m_anchorNode->lastChild(); case PositionIsOffsetInAnchor: - return m_anchorNode->childNode(m_offset - 1); // -1 converts to childNode((unsigned)-1) and returns null. + return m_offset ? m_anchorNode->traverseToChildAt(m_offset - 1) : nullptr; case PositionIsBeforeAnchor: return m_anchorNode->previousSibling(); case PositionIsAfterAnchor: return m_anchorNode.get(); } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } Node* Position::computeNodeAfterPosition() const { if (!m_anchorNode) - return 0; + return nullptr; switch (anchorType()) { case PositionIsBeforeChildren: return m_anchorNode->firstChild(); case PositionIsAfterChildren: - return 0; + return nullptr; case PositionIsOffsetInAnchor: - return m_anchorNode->childNode(m_offset); + return m_anchorNode->traverseToChildAt(m_offset); case PositionIsBeforeAnchor: return m_anchorNode.get(); case PositionIsAfterAnchor: return m_anchorNode->nextSibling(); } ASSERT_NOT_REACHED(); - return 0; + return nullptr; } Position::AnchorType Position::anchorTypeForLegacyEditingPosition(Node* anchorNode, int offset) { - if (anchorNode && editingIgnoresContent(anchorNode)) { + if (anchorNode && editingIgnoresContent(*anchorNode)) { if (offset == 0) return Position::PositionIsBeforeAnchor; return Position::PositionIsAfterAnchor; @@ -305,25 +301,32 @@ Position::AnchorType Position::anchorTypeForLegacyEditingPosition(Node* anchorNo // FIXME: This method is confusing (does it return anchorNode() or containerNode()?) and should be renamed or removed Element* Position::element() const { - Node* n = anchorNode(); - while (n && !n->isElementNode()) - n = n->parentNode(); - return toElement(n); + Node* node = anchorNode(); + while (node && !is<Element>(*node)) + node = node->parentNode(); + return downcast<Element>(node); } Position Position::previous(PositionMoveType moveType) const { - Node* n = deprecatedNode(); - if (!n) + Node* node = deprecatedNode(); + if (!node) return *this; - int o = deprecatedEditingOffset(); + int offset = deprecatedEditingOffset(); // FIXME: Negative offsets shouldn't be allowed. We should catch this earlier. - ASSERT(o >= 0); + ASSERT(offset >= 0); - if (o > 0) { - Node* child = n->childNode(o - 1); - if (child) + if (anchorType() == PositionIsBeforeAnchor) { + node = containerNode(); + if (!node) + return *this; + + offset = computeOffsetInContainerNode(); + } + + if (offset > 0) { + if (Node* child = node->traverseToChildAt(offset - 1)) return lastPositionInOrAfterNode(child); // There are two reasons child might be 0: @@ -333,35 +336,50 @@ Position Position::previous(PositionMoveType moveType) const // Going from 1 to 0 is correct. switch (moveType) { case CodePoint: - return createLegacyEditingPosition(n, o - 1); + return createLegacyEditingPosition(node, offset - 1); case Character: - return createLegacyEditingPosition(n, uncheckedPreviousOffset(n, o)); + return createLegacyEditingPosition(node, uncheckedPreviousOffset(node, offset)); case BackwardDeletion: - return createLegacyEditingPosition(n, uncheckedPreviousOffsetForBackwardDeletion(n, o)); + return createLegacyEditingPosition(node, uncheckedPreviousOffsetForBackwardDeletion(node, offset)); } } - ContainerNode* parent = findParent(n); + ContainerNode* parent = node->parentNode(); if (!parent) return *this; - return createLegacyEditingPosition(parent, n->nodeIndex()); + if (positionBeforeOrAfterNodeIsCandidate(*node)) + return positionBeforeNode(node); + + Node* previousSibling = node->previousSibling(); + if (previousSibling && positionBeforeOrAfterNodeIsCandidate(*previousSibling)) + return positionAfterNode(previousSibling); + + return createLegacyEditingPosition(parent, node->computeNodeIndex()); } Position Position::next(PositionMoveType moveType) const { ASSERT(moveType != BackwardDeletion); - Node* n = deprecatedNode(); - if (!n) + Node* node = deprecatedNode(); + if (!node) return *this; - int o = deprecatedEditingOffset(); + int offset = deprecatedEditingOffset(); // FIXME: Negative offsets shouldn't be allowed. We should catch this earlier. - ASSERT(o >= 0); + ASSERT(offset >= 0); + + if (anchorType() == PositionIsAfterAnchor) { + node = containerNode(); + if (!node) + return *this; - Node* child = n->childNode(o); - if (child || (!n->hasChildNodes() && o < lastOffsetForEditing(n))) { + offset = computeOffsetInContainerNode(); + } + + Node* child = node->traverseToChildAt(offset); + if (child || (!node->hasChildNodes() && offset < lastOffsetForEditing(*node))) { if (child) return firstPositionInOrBeforeNode(child); @@ -370,14 +388,21 @@ Position Position::next(PositionMoveType moveType) const // Going forward one character at a time is correct. // 2) The new offset is a bogus offset like (<br>, 1), and there is no child. // Going from 0 to 1 is correct. - return createLegacyEditingPosition(n, (moveType == Character) ? uncheckedNextOffset(n, o) : o + 1); + return createLegacyEditingPosition(node, (moveType == Character) ? uncheckedNextOffset(node, offset) : offset + 1); } - ContainerNode* parent = findParent(n); + ContainerNode* parent = node->parentNode(); if (!parent) return *this; - return createLegacyEditingPosition(parent, n->nodeIndex() + 1); + if (isRenderedTable(node) || editingIgnoresContent(*node)) + return positionAfterNode(node); + + Node* nextSibling = node->nextSibling(); + if (nextSibling && positionBeforeOrAfterNodeIsCandidate(*nextSibling)) + return positionBeforeNode(nextSibling); + + return createLegacyEditingPosition(parent, node->computeNodeIndex() + 1); } int Position::uncheckedPreviousOffset(const Node* n, int current) @@ -409,7 +434,7 @@ bool Position::atFirstEditingPositionForNode() const return true; case PositionIsAfterChildren: case PositionIsAfterAnchor: - return !lastOffsetForEditing(deprecatedNode()); + return !lastOffsetForEditing(*deprecatedNode()); } ASSERT_NOT_REACHED(); return false; @@ -421,7 +446,7 @@ bool Position::atLastEditingPositionForNode() const return true; // FIXME: Position after anchor shouldn't be considered as at the first editing position for node // since that position resides outside of the node. - return m_anchorType == PositionIsAfterAnchor || m_anchorType == PositionIsAfterChildren || m_offset >= lastOffsetForEditing(deprecatedNode()); + return m_anchorType == PositionIsAfterAnchor || m_anchorType == PositionIsAfterChildren || m_offset >= lastOffsetForEditing(*deprecatedNode()); } // A position is considered at editing boundary if one of the following is true: @@ -448,11 +473,11 @@ bool Position::atEditingBoundary() const Node* Position::parentEditingBoundary() const { if (!m_anchorNode) - return 0; + return nullptr; Node* documentElement = m_anchorNode->document().documentElement(); if (!documentElement) - return 0; + return nullptr; Node* boundary = m_anchorNode.get(); while (boundary != documentElement && boundary->nonShadowBoundaryParentNode() && m_anchorNode->hasEditableStyle() == boundary->parentNode()->hasEditableStyle()) @@ -466,39 +491,75 @@ bool Position::atStartOfTree() const { if (isNull()) return true; - return !findParent(deprecatedNode()) && m_offset <= 0; + + Node* container = containerNode(); + if (container && container->parentNode()) + return false; + + switch (m_anchorType) { + case PositionIsOffsetInAnchor: + return m_offset <= 0; + case PositionIsBeforeAnchor: + return !m_anchorNode->previousSibling(); + case PositionIsAfterAnchor: + return false; + case PositionIsBeforeChildren: + return true; + case PositionIsAfterChildren: + return !lastOffsetForEditing(*m_anchorNode); + } + ASSERT_NOT_REACHED(); + return false; } bool Position::atEndOfTree() const { if (isNull()) return true; - return !findParent(deprecatedNode()) && m_offset >= lastOffsetForEditing(deprecatedNode()); + + Node* container = containerNode(); + if (container && container->parentNode()) + return false; + + switch (m_anchorType) { + case PositionIsOffsetInAnchor: + return m_offset >= lastOffsetForEditing(*m_anchorNode); + case PositionIsBeforeAnchor: + return false; + case PositionIsAfterAnchor: + return !m_anchorNode->nextSibling(); + case PositionIsBeforeChildren: + return !lastOffsetForEditing(*m_anchorNode); + case PositionIsAfterChildren: + return true; + } + ASSERT_NOT_REACHED(); + return false; } // return first preceding DOM position rendered at a different location, or "this" Position Position::previousCharacterPosition(EAffinity affinity) const { if (isNull()) - return Position(); + return { }; Node* fromRootEditableElement = deprecatedNode()->rootEditableElement(); bool atStartOfLine = isStartOfLine(VisiblePosition(*this, affinity)); bool rendered = isCandidate(); - Position currentPos = *this; - while (!currentPos.atStartOfTree()) { - currentPos = currentPos.previous(); + Position currentPosition = *this; + while (!currentPosition.atStartOfTree()) { + currentPosition = currentPosition.previous(); - if (currentPos.deprecatedNode()->rootEditableElement() != fromRootEditableElement) + if (currentPosition.deprecatedNode()->rootEditableElement() != fromRootEditableElement) return *this; if (atStartOfLine || !rendered) { - if (currentPos.isCandidate()) - return currentPos; - } else if (rendersInDifferentPosition(currentPos)) - return currentPos; + if (currentPosition.isCandidate()) + return currentPosition; + } else if (rendersInDifferentPosition(currentPosition)) + return currentPosition; } return *this; @@ -508,25 +569,25 @@ Position Position::previousCharacterPosition(EAffinity affinity) const Position Position::nextCharacterPosition(EAffinity affinity) const { if (isNull()) - return Position(); + return { }; Node* fromRootEditableElement = deprecatedNode()->rootEditableElement(); - bool atEndOfLine = isEndOfLine(VisiblePosition(*this, affinity)); + bool atEndOfLine = isEndOfLine({ *this, affinity }); bool rendered = isCandidate(); - Position currentPos = *this; - while (!currentPos.atEndOfTree()) { - currentPos = currentPos.next(); + Position currentPosition = *this; + while (!currentPosition.atEndOfTree()) { + currentPosition = currentPosition.next(); - if (currentPos.deprecatedNode()->rootEditableElement() != fromRootEditableElement) + if (currentPosition.deprecatedNode()->rootEditableElement() != fromRootEditableElement) return *this; if (atEndOfLine || !rendered) { - if (currentPos.isCandidate()) - return currentPos; - } else if (rendersInDifferentPosition(currentPos)) - return currentPos; + if (currentPosition.isCandidate()) + return currentPosition; + } else if (rendersInDifferentPosition(currentPosition)) + return currentPosition; } return *this; @@ -545,11 +606,11 @@ static bool endsOfNodeAreVisuallyDistinctPositions(Node* node) return true; // Don't include inline tables. - if (isHTMLTableElement(node)) + if (is<HTMLTableElement>(*node)) return false; // There is a VisiblePosition inside an empty inline-block container. - return node->renderer()->isReplaced() && canHaveChildrenForEditing(node) && toRenderBox(node->renderer())->height() != 0 && !node->firstChild(); + return node->renderer()->isReplaced() && canHaveChildrenForEditing(*node) && downcast<RenderBox>(*node->renderer()).height() && !node->firstChild(); } static Node* enclosingVisualBoundary(Node* node) @@ -583,85 +644,85 @@ Position Position::upstream(EditingBoundaryCrossingRule rule) const { Node* startNode = deprecatedNode(); if (!startNode) - return Position(); + return { }; // iterate backward from there, looking for a qualified position Node* boundary = enclosingVisualBoundary(startNode); // FIXME: PositionIterator should respect Before and After positions. - PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? createLegacyEditingPosition(m_anchorNode.get(), caretMaxOffset(m_anchorNode.get())) : *this; - PositionIterator currentPos = lastVisible; + PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? createLegacyEditingPosition(m_anchorNode.get(), caretMaxOffset(*m_anchorNode)) : *this; + PositionIterator currentPosition = lastVisible; bool startEditable = startNode->hasEditableStyle(); Node* lastNode = startNode; bool boundaryCrossed = false; - for (; !currentPos.atStart(); currentPos.decrement()) { - Node* currentNode = currentPos.node(); + for (; !currentPosition.atStart(); currentPosition.decrement()) { + auto& currentNode = *currentPosition.node(); // Don't check for an editability change if we haven't moved to a different node, // to avoid the expense of computing hasEditableStyle(). - if (currentNode != lastNode) { + if (¤tNode != lastNode) { // Don't change editability. - bool currentEditable = currentNode->hasEditableStyle(); + bool currentEditable = currentNode.hasEditableStyle(); if (startEditable != currentEditable) { if (rule == CannotCrossEditingBoundary) break; boundaryCrossed = true; } - lastNode = currentNode; + lastNode = ¤tNode; } // If we've moved to a position that is visually distinct, return the last saved position. There // is code below that terminates early if we're *about* to move to a visually distinct position. - if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) + if (endsOfNodeAreVisuallyDistinctPositions(¤tNode) && ¤tNode != boundary) return lastVisible; // skip position in unrendered or invisible node - RenderObject* renderer = currentNode->renderer(); + RenderObject* renderer = currentNode.renderer(); if (!renderer || renderer->style().visibility() != VISIBLE) continue; if (rule == CanCrossEditingBoundary && boundaryCrossed) { - lastVisible = currentPos; + lastVisible = currentPosition; break; } // track last visible streamer position - if (isStreamer(currentPos)) - lastVisible = currentPos; + if (isStreamer(currentPosition)) + lastVisible = currentPosition; // Don't move past a position that is visually distinct. We could rely on code above to terminate and - // return lastVisible on the next iteration, but we terminate early to avoid doing a nodeIndex() call. - if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentPos.atStartOfNode()) + // return lastVisible on the next iteration, but we terminate early to avoid doing a computeNodeIndex() call. + if (endsOfNodeAreVisuallyDistinctPositions(¤tNode) && currentPosition.atStartOfNode()) return lastVisible; // Return position after tables and nodes which have content that can be ignored. - if (editingIgnoresContent(currentNode) || isTableElement(currentNode)) { - if (currentPos.atEndOfNode()) - return positionAfterNode(currentNode); + if (editingIgnoresContent(currentNode) || isRenderedTable(¤tNode)) { + if (currentPosition.atEndOfNode()) + return positionAfterNode(¤tNode); continue; } // return current position if it is in rendered text - if (renderer->isText()) { - auto& textRenderer = toRenderText(*renderer); + if (is<RenderText>(*renderer)) { + auto& textRenderer = downcast<RenderText>(*renderer); textRenderer.ensureLineBoxes(); if (!textRenderer.firstTextBox()) continue; - if (currentNode != startNode) { + if (¤tNode != startNode) { // This assertion fires in layout tests in the case-transform.html test because // of a mix-up between offsets in the text in the DOM tree with text in the // render tree which can have a different length due to case transformation. // Until we resolve that, disable this so we can run the layout tests! //ASSERT(currentOffset >= renderer->caretMaxOffset()); - return createLegacyEditingPosition(currentNode, renderer->caretMaxOffset()); + return createLegacyEditingPosition(¤tNode, renderer->caretMaxOffset()); } - unsigned textOffset = currentPos.offsetInLeafNode(); + unsigned textOffset = currentPosition.offsetInLeafNode(); auto lastTextBox = textRenderer.lastTextBox(); - for (auto box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) { + for (auto* box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) { if (textOffset <= box->start() + box->len()) { if (textOffset > box->start()) - return currentPos; + return currentPosition; continue; } @@ -677,7 +738,7 @@ Position Position::upstream(EditingBoundaryCrossingRule rule) const otherBox = otherBox->nextLeafChild(); if (!otherBox) break; - if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && toInlineTextBox(otherBox)->start() > textOffset)) + if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && downcast<InlineTextBox>(*otherBox).start() > textOffset)) continuesOnNextLine = false; } @@ -686,12 +747,12 @@ Position Position::upstream(EditingBoundaryCrossingRule rule) const otherBox = otherBox->prevLeafChild(); if (!otherBox) break; - if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && toInlineTextBox(otherBox)->start() > textOffset)) + if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && downcast<InlineTextBox>(*otherBox).start() > textOffset)) continuesOnNextLine = false; } if (continuesOnNextLine) - return currentPos; + return currentPosition; } } } @@ -710,86 +771,86 @@ Position Position::downstream(EditingBoundaryCrossingRule rule) const { Node* startNode = deprecatedNode(); if (!startNode) - return Position(); + return { }; // iterate forward from there, looking for a qualified position Node* boundary = enclosingVisualBoundary(startNode); // FIXME: PositionIterator should respect Before and After positions. - PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? createLegacyEditingPosition(m_anchorNode.get(), caretMaxOffset(m_anchorNode.get())) : *this; - PositionIterator currentPos = lastVisible; + PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? createLegacyEditingPosition(m_anchorNode.get(), caretMaxOffset(*m_anchorNode)) : *this; + PositionIterator currentPosition = lastVisible; bool startEditable = startNode->hasEditableStyle(); Node* lastNode = startNode; bool boundaryCrossed = false; - for (; !currentPos.atEnd(); currentPos.increment()) { - Node* currentNode = currentPos.node(); + for (; !currentPosition.atEnd(); currentPosition.increment()) { + auto& currentNode = *currentPosition.node(); // Don't check for an editability change if we haven't moved to a different node, // to avoid the expense of computing hasEditableStyle(). - if (currentNode != lastNode) { + if (¤tNode != lastNode) { // Don't change editability. - bool currentEditable = currentNode->hasEditableStyle(); + bool currentEditable = currentNode.hasEditableStyle(); if (startEditable != currentEditable) { if (rule == CannotCrossEditingBoundary) break; boundaryCrossed = true; } - lastNode = currentNode; + lastNode = ¤tNode; } // stop before going above the body, up into the head // return the last visible streamer position - if (currentNode->hasTagName(bodyTag) && currentPos.atEndOfNode()) + if (is<HTMLBodyElement>(currentNode) && currentPosition.atEndOfNode()) break; - + // Do not move to a visually distinct position. - if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) + if (endsOfNodeAreVisuallyDistinctPositions(¤tNode) && ¤tNode != boundary) return lastVisible; // Do not move past a visually disinct position. // Note: The first position after the last in a node whose ends are visually distinct - // positions will be [boundary->parentNode(), originalBlock->nodeIndex() + 1]. - if (boundary && boundary->parentNode() == currentNode) + // positions will be [boundary->parentNode(), originalBlock->computeNodeIndex() + 1]. + if (boundary && boundary->parentNode() == ¤tNode) return lastVisible; // skip position in unrendered or invisible node - RenderObject* renderer = currentNode->renderer(); + auto* renderer = currentNode.renderer(); if (!renderer || renderer->style().visibility() != VISIBLE) continue; if (rule == CanCrossEditingBoundary && boundaryCrossed) { - lastVisible = currentPos; + lastVisible = currentPosition; break; } // track last visible streamer position - if (isStreamer(currentPos)) - lastVisible = currentPos; + if (isStreamer(currentPosition)) + lastVisible = currentPosition; // Return position before tables and nodes which have content that can be ignored. - if (editingIgnoresContent(currentNode) || isTableElement(currentNode)) { - if (currentPos.offsetInLeafNode() <= renderer->caretMinOffset()) - return createLegacyEditingPosition(currentNode, renderer->caretMinOffset()); + if (editingIgnoresContent(currentNode) || isRenderedTable(¤tNode)) { + if (currentPosition.atStartOfNode()) + return positionBeforeNode(¤tNode); continue; } // return current position if it is in rendered text - if (renderer->isText()) { - auto& textRenderer = toRenderText(*renderer); + if (is<RenderText>(*renderer)) { + auto& textRenderer = downcast<RenderText>(*renderer); textRenderer.ensureLineBoxes(); if (!textRenderer.firstTextBox()) continue; - if (currentNode != startNode) { - ASSERT(currentPos.atStartOfNode()); - return createLegacyEditingPosition(currentNode, renderer->caretMinOffset()); + if (¤tNode != startNode) { + ASSERT(currentPosition.atStartOfNode()); + return createLegacyEditingPosition(¤tNode, renderer->caretMinOffset()); } - unsigned textOffset = currentPos.offsetInLeafNode(); + unsigned textOffset = currentPosition.offsetInLeafNode(); auto lastTextBox = textRenderer.lastTextBox(); - for (auto box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) { + for (auto* box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) { if (textOffset <= box->end()) { if (textOffset >= box->start()) - return currentPos; + return currentPosition; continue; } @@ -805,7 +866,7 @@ Position Position::downstream(EditingBoundaryCrossingRule rule) const otherBox = otherBox->nextLeafChild(); if (!otherBox) break; - if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && toInlineTextBox(otherBox)->start() >= textOffset)) + if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && downcast<InlineTextBox>(*otherBox).start() >= textOffset)) continuesOnNextLine = false; } @@ -814,12 +875,12 @@ Position Position::downstream(EditingBoundaryCrossingRule rule) const otherBox = otherBox->prevLeafChild(); if (!otherBox) break; - if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && toInlineTextBox(otherBox)->start() >= textOffset)) + if (otherBox == lastTextBox || (&otherBox->renderer() == &textRenderer && downcast<InlineTextBox>(*otherBox).start() >= textOffset)) continuesOnNextLine = false; } if (continuesOnNextLine) - return currentPos; + return currentPosition; } } } @@ -827,6 +888,30 @@ Position Position::downstream(EditingBoundaryCrossingRule rule) const return lastVisible; } +unsigned Position::positionCountBetweenPositions(const Position& a, const Position& b) +{ + if (a.isNull() || b.isNull()) + return UINT_MAX; + + Position endPos; + Position pos; + if (a > b) { + endPos = a; + pos = b; + } else if (a < b) { + endPos = b; + pos = a; + } else + return 0; + + unsigned posCount = 0; + while (!pos.atEndOfTree() && pos != endPos) { + pos = pos.next(); + ++posCount; + } + return posCount; +} + static int boundingBoxLogicalHeight(RenderObject *o, const IntRect &rect) { return o->style().isHorizontalWritingMode() ? rect.height() : rect.width(); @@ -838,23 +923,23 @@ bool Position::hasRenderedNonAnonymousDescendantsWithHeight(const RenderElement& for (RenderObject* o = renderer.firstChild(); o && o != stop; o = o->nextInPreOrder()) { if (!o->nonPseudoNode()) continue; - if (o->isText()) { - if (boundingBoxLogicalHeight(o, toRenderText(o)->linesBoundingBox())) + if (is<RenderText>(*o)) { + if (boundingBoxLogicalHeight(o, downcast<RenderText>(*o).linesBoundingBox())) return true; continue; } - if (o->isLineBreak()) { - if (boundingBoxLogicalHeight(o, toRenderLineBreak(o)->linesBoundingBox())) + if (is<RenderLineBreak>(*o)) { + if (boundingBoxLogicalHeight(o, downcast<RenderLineBreak>(*o).linesBoundingBox())) return true; continue; } - if (o->isBox()) { - if (toRenderBox(o)->pixelSnappedLogicalHeight()) + if (is<RenderBox>(*o)) { + if (roundToInt(downcast<RenderBox>(*o).logicalHeight())) return true; continue; } - if (o->isRenderInline()) { - const RenderInline& renderInline = toRenderInline(*o); + if (is<RenderInline>(*o)) { + const RenderInline& renderInline = downcast<RenderInline>(*o); if (isEmptyInline(renderInline) && boundingBoxLogicalHeight(o, renderInline.linesBoundingBox())) return true; continue; @@ -868,18 +953,6 @@ bool Position::nodeIsUserSelectNone(Node* node) return node && node->renderer() && node->renderer()->style().userSelect() == SELECT_NONE; } -ContainerNode* Position::findParent(const Node* node) -{ - // FIXME: See http://web.ug/82697 - -#if ENABLE(SHADOW_DOM) - if (RuntimeEnabledFeatures::sharedFeatures().shadowDOMEnabled()) - return node->parentNode(); -#endif - - return node->nonShadowBoundaryParentNode(); -} - #if ENABLE(USERSELECT_ALL) bool Position::nodeIsUserSelectAll(const Node* node) { @@ -889,7 +962,7 @@ bool Position::nodeIsUserSelectAll(const Node* node) Node* Position::rootUserSelectAllForNode(Node* node) { if (!node || !nodeIsUserSelectAll(node)) - return 0; + return nullptr; Node* parent = node->parentNode(); if (!parent) return node; @@ -913,52 +986,53 @@ bool Position::isCandidate() const { if (isNull()) return false; - - RenderObject* renderer = deprecatedNode()->renderer(); + + auto* renderer = deprecatedNode()->renderer(); if (!renderer) return false; - + if (renderer->style().visibility() != VISIBLE) return false; - if (renderer->isBR()) + if (renderer->isBR()) { // FIXME: The condition should be m_anchorType == PositionIsBeforeAnchor, but for now we still need to support legacy positions. return !m_offset && m_anchorType != PositionIsAfterAnchor && !nodeIsUserSelectNone(deprecatedNode()->parentNode()); + } - if (renderer->isText()) - return !nodeIsUserSelectNone(deprecatedNode()) && toRenderText(renderer)->containsCaretOffset(m_offset); + if (is<RenderText>(*renderer)) + return !nodeIsUserSelectNone(deprecatedNode()) && downcast<RenderText>(*renderer).containsCaretOffset(m_offset); - if (isTableElement(deprecatedNode()) || editingIgnoresContent(deprecatedNode())) - return (atFirstEditingPositionForNode() || atLastEditingPositionForNode()) && !nodeIsUserSelectNone(deprecatedNode()->parentNode()); + if (positionBeforeOrAfterNodeIsCandidate(*deprecatedNode())) { + return ((atFirstEditingPositionForNode() && m_anchorType == PositionIsBeforeAnchor) + || (atLastEditingPositionForNode() && m_anchorType == PositionIsAfterAnchor)) + && !nodeIsUserSelectNone(deprecatedNode()->parentNode()); + } - if (m_anchorNode->hasTagName(htmlTag)) + if (is<HTMLHtmlElement>(*m_anchorNode)) return false; - - if (isRendererReplacedElement(renderer)) - return !nodeIsUserSelectNone(deprecatedNode()) && atFirstEditingPositionForNode(); - if (renderer->isRenderBlockFlow()) { - RenderBlock& block = toRenderBlock(*renderer); - if (block.logicalHeight() || m_anchorNode->hasTagName(bodyTag)) { + if (is<RenderBlockFlow>(*renderer) || is<RenderGrid>(*renderer) || is<RenderFlexibleBox>(*renderer)) { + RenderBlock& block = downcast<RenderBlock>(*renderer); + if (block.logicalHeight() || is<HTMLBodyElement>(*m_anchorNode)) { if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(block)) return atFirstEditingPositionForNode() && !Position::nodeIsUserSelectNone(deprecatedNode()); return m_anchorNode->hasEditableStyle() && !Position::nodeIsUserSelectNone(deprecatedNode()) && atEditingBoundary(); } - } else - return m_anchorNode->hasEditableStyle() && !Position::nodeIsUserSelectNone(deprecatedNode()) && atEditingBoundary(); + return false; + } - return false; + return m_anchorNode->hasEditableStyle() && !Position::nodeIsUserSelectNone(deprecatedNode()) && atEditingBoundary(); } bool Position::isRenderedCharacter() const { - if (isNull() || !deprecatedNode()->isTextNode()) + if (!is<Text>(deprecatedNode())) return false; - - RenderText* renderer = toText(deprecatedNode())->renderer(); + + RenderText* renderer = downcast<Text>(*deprecatedNode()).renderer(); if (!renderer) return false; - + return renderer->containsRenderedCharacterOffset(m_offset); } @@ -967,69 +1041,68 @@ static bool inSameEnclosingBlockFlowElement(Node* a, Node* b) return a && b && deprecatedEnclosingBlockFlowElement(a) == deprecatedEnclosingBlockFlowElement(b); } -bool Position::rendersInDifferentPosition(const Position &pos) const +bool Position::rendersInDifferentPosition(const Position& position) const { - if (isNull() || pos.isNull()) + if (isNull() || position.isNull()) return false; - RenderObject* renderer = deprecatedNode()->renderer(); + auto* renderer = deprecatedNode()->renderer(); if (!renderer) return false; - RenderObject* posRenderer = pos.deprecatedNode()->renderer(); - if (!posRenderer) + auto* positionRenderer = position.deprecatedNode()->renderer(); + if (!positionRenderer) return false; - if (renderer->style().visibility() != VISIBLE || - posRenderer->style().visibility() != VISIBLE) + if (renderer->style().visibility() != VISIBLE || positionRenderer->style().visibility() != VISIBLE) return false; - if (deprecatedNode() == pos.deprecatedNode()) { - if (deprecatedNode()->hasTagName(brTag)) + if (deprecatedNode() == position.deprecatedNode()) { + if (is<HTMLBRElement>(*deprecatedNode())) return false; - if (m_offset == pos.deprecatedEditingOffset()) + if (m_offset == position.deprecatedEditingOffset()) return false; - - if (!deprecatedNode()->isTextNode() && !pos.deprecatedNode()->isTextNode()) { - if (m_offset != pos.deprecatedEditingOffset()) + + if (!is<Text>(*deprecatedNode()) && !is<Text>(*position.deprecatedNode())) { + if (m_offset != position.deprecatedEditingOffset()) return true; } } - - if (deprecatedNode()->hasTagName(brTag) && pos.isCandidate()) + + if (is<HTMLBRElement>(*deprecatedNode()) && position.isCandidate()) return true; - - if (pos.deprecatedNode()->hasTagName(brTag) && isCandidate()) + + if (is<HTMLBRElement>(*position.deprecatedNode()) && isCandidate()) return true; - - if (!inSameEnclosingBlockFlowElement(deprecatedNode(), pos.deprecatedNode())) + + if (!inSameEnclosingBlockFlowElement(deprecatedNode(), position.deprecatedNode())) return true; - if (renderer->isText() && !toRenderText(renderer)->containsCaretOffset(m_offset)) + if (is<RenderText>(*renderer) && !downcast<RenderText>(*renderer).containsCaretOffset(m_offset)) return false; - if (posRenderer->isText() && !toRenderText(posRenderer)->containsCaretOffset(pos.m_offset)) + if (is<RenderText>(*positionRenderer) && !downcast<RenderText>(*positionRenderer).containsCaretOffset(position.m_offset)) return false; - int thisRenderedOffset = renderer->isText() ? toRenderText(renderer)->countRenderedCharacterOffsetsUntil(m_offset) : m_offset; - int posRenderedOffset = posRenderer->isText() ? toRenderText(posRenderer)->countRenderedCharacterOffsetsUntil(pos.m_offset) : pos.m_offset; + int thisRenderedOffset = is<RenderText>(*renderer) ? downcast<RenderText>(*renderer).countRenderedCharacterOffsetsUntil(m_offset) : m_offset; + int positionRenderedOffset = is<RenderText>(*positionRenderer) ? downcast<RenderText>(*positionRenderer).countRenderedCharacterOffsetsUntil(position.m_offset) : position.m_offset; - if (renderer == posRenderer && thisRenderedOffset == posRenderedOffset) + if (renderer == positionRenderer && thisRenderedOffset == positionRenderedOffset) return false; int ignoredCaretOffset; InlineBox* b1; getInlineBoxAndOffset(DOWNSTREAM, b1, ignoredCaretOffset); InlineBox* b2; - pos.getInlineBoxAndOffset(DOWNSTREAM, b2, ignoredCaretOffset); + position.getInlineBoxAndOffset(DOWNSTREAM, b2, ignoredCaretOffset); LOG(Editing, "renderer: %p [%p]\n", renderer, b1); LOG(Editing, "thisRenderedOffset: %d\n", thisRenderedOffset); - LOG(Editing, "posRenderer: %p [%p]\n", posRenderer, b2); - LOG(Editing, "posRenderedOffset: %d\n", posRenderedOffset); - LOG(Editing, "node min/max: %d:%d\n", caretMinOffset(deprecatedNode()), caretMaxOffset(deprecatedNode())); - LOG(Editing, "pos node min/max: %d:%d\n", caretMinOffset(pos.deprecatedNode()), caretMaxOffset(pos.deprecatedNode())); + LOG(Editing, "posRenderer: %p [%p]\n", positionRenderer, b2); + LOG(Editing, "posRenderedOffset: %d\n", positionRenderedOffset); + LOG(Editing, "node min/max: %d:%d\n", caretMinOffset(*deprecatedNode()), caretMaxOffset(*deprecatedNode())); + LOG(Editing, "pos node min/max: %d:%d\n", caretMinOffset(*position.deprecatedNode()), caretMaxOffset(*position.deprecatedNode())); LOG(Editing, "----------------------------------------------------------------------\n"); if (!b1 || !b2) { @@ -1040,13 +1113,13 @@ bool Position::rendersInDifferentPosition(const Position &pos) const return true; } - if (nextRenderedEditable(deprecatedNode()) == pos.deprecatedNode() - && thisRenderedOffset == caretMaxOffset(deprecatedNode()) && !posRenderedOffset) { + if (nextRenderedEditable(deprecatedNode()) == position.deprecatedNode() + && thisRenderedOffset == caretMaxOffset(*deprecatedNode()) && !positionRenderedOffset) { return false; } - if (previousRenderedEditable(deprecatedNode()) == pos.deprecatedNode() - && !thisRenderedOffset && posRenderedOffset == caretMaxOffset(pos.deprecatedNode())) { + if (previousRenderedEditable(deprecatedNode()) == position.deprecatedNode() + && !thisRenderedOffset && positionRenderedOffset == caretMaxOffset(*position.deprecatedNode())) { return false; } @@ -1058,21 +1131,21 @@ Position Position::leadingWhitespacePosition(EAffinity affinity, bool considerNo { ASSERT(isEditablePosition(*this)); if (isNull()) - return Position(); + return { }; - if (upstream().deprecatedNode()->hasTagName(brTag)) - return Position(); + if (is<HTMLBRElement>(*upstream().deprecatedNode())) + return { }; Position prev = previousCharacterPosition(affinity); - if (prev != *this && inSameEnclosingBlockFlowElement(deprecatedNode(), prev.deprecatedNode()) && prev.deprecatedNode()->isTextNode()) { - String string = toText(prev.deprecatedNode())->data(); + if (prev != *this && inSameEnclosingBlockFlowElement(deprecatedNode(), prev.deprecatedNode()) && is<Text>(*prev.deprecatedNode())) { + String string = downcast<Text>(*prev.deprecatedNode()).data(); UChar c = string[prev.deprecatedEditingOffset()]; - if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : isCollapsibleWhitespace(c)) + if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : deprecatedIsCollapsibleWhitespace(c)) if (isEditablePosition(prev)) return prev; } - return Position(); + return { }; } // This assumes that it starts in editable content. @@ -1080,16 +1153,16 @@ Position Position::trailingWhitespacePosition(EAffinity, bool considerNonCollaps { ASSERT(isEditablePosition(*this)); if (isNull()) - return Position(); + return { }; VisiblePosition v(*this); UChar c = v.characterAfter(); // The space must not be in another paragraph and it must be editable. if (!isEndOfParagraph(v) && v.next(CannotCrossEditingBoundary).isNotNull()) - if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : isCollapsibleWhitespace(c)) + if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : deprecatedIsCollapsibleWhitespace(c)) return *this; - return Position(); + return { }; } void Position::getInlineBoxAndOffset(EAffinity affinity, InlineBox*& inlineBox, int& caretOffset) const @@ -1097,13 +1170,11 @@ void Position::getInlineBoxAndOffset(EAffinity affinity, InlineBox*& inlineBox, getInlineBoxAndOffset(affinity, primaryDirection(), inlineBox, caretOffset); } -static bool isNonTextLeafChild(RenderObject* object) +static bool isNonTextLeafChild(RenderObject& object) { - if (object->isText()) - return false; - if (toRenderElement(object)->firstChild()) + if (is<RenderText>(object)) return false; - return true; + return !downcast<RenderElement>(object).firstChild(); } static InlineTextBox* searchAheadForBetterMatch(RenderObject* renderer) @@ -1111,16 +1182,16 @@ static InlineTextBox* searchAheadForBetterMatch(RenderObject* renderer) RenderBlock* container = renderer->containingBlock(); RenderObject* next = renderer; while ((next = next->nextInPreOrder(container))) { - if (next->isRenderBlock()) - return 0; + if (is<RenderBlock>(*next)) + return nullptr; if (next->isBR()) - return 0; - if (isNonTextLeafChild(next)) - return 0; - if (next->isText()) { - InlineTextBox* match = 0; + return nullptr; + if (isNonTextLeafChild(*next)) + return nullptr; + if (is<RenderText>(*next)) { + InlineTextBox* match = nullptr; int minOffset = INT_MAX; - for (InlineTextBox* box = toRenderText(next)->firstTextBox(); box; box = box->nextTextBox()) { + for (InlineTextBox* box = downcast<RenderText>(*next).firstTextBox(); box; box = box->nextTextBox()) { int caretMinOffset = box->caretMinOffset(); if (caretMinOffset < minOffset) { match = box; @@ -1131,7 +1202,7 @@ static InlineTextBox* searchAheadForBetterMatch(RenderObject* renderer) return match; } } - return 0; + return nullptr; } static Position downstreamIgnoringEditingBoundaries(Position position) @@ -1160,15 +1231,15 @@ void Position::getInlineBoxAndOffset(EAffinity affinity, TextDirection primaryDi RenderObject* renderer = deprecatedNode()->renderer(); if (renderer->isBR()) - inlineBox = !caretOffset ? toRenderLineBreak(renderer)->inlineBoxWrapper() : nullptr; - else if (renderer->isText()) { - auto textRenderer = toRenderText(renderer); - textRenderer->ensureLineBoxes(); + inlineBox = !caretOffset ? downcast<RenderLineBreak>(*renderer).inlineBoxWrapper() : nullptr; + else if (is<RenderText>(*renderer)) { + auto& textRenderer = downcast<RenderText>(*renderer); + textRenderer.ensureLineBoxes(); InlineTextBox* box; - InlineTextBox* candidate = 0; + InlineTextBox* candidate = nullptr; - for (box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { + for (box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) { int caretMinOffset = box->caretMinOffset(); int caretMaxOffset = box->caretMaxOffset(); @@ -1187,15 +1258,15 @@ void Position::getInlineBoxAndOffset(EAffinity affinity, TextDirection primaryDi candidate = box; } - if (candidate && candidate == textRenderer->lastTextBox() && affinity == DOWNSTREAM) { - box = searchAheadForBetterMatch(textRenderer); + if (candidate && candidate == textRenderer.lastTextBox() && affinity == DOWNSTREAM) { + box = searchAheadForBetterMatch(&textRenderer); if (box) caretOffset = box->caretMinOffset(); } inlineBox = box ? box : candidate; } else { - inlineBox = 0; - if (canHaveChildrenForEditing(deprecatedNode()) && renderer->isRenderBlockFlow() && hasRenderedNonAnonymousDescendantsWithHeight(toRenderBlock(*renderer))) { + inlineBox = nullptr; + if (canHaveChildrenForEditing(*deprecatedNode()) && is<RenderBlockFlow>(*renderer) && hasRenderedNonAnonymousDescendantsWithHeight(downcast<RenderBlockFlow>(*renderer))) { // Try a visually equivalent position with possibly opposite editability. This helps in case |this| is in // an editable block but surrounded by non-editable positions. It acts to negate the logic at the beginning // of RenderObject::createVisiblePosition(). @@ -1209,8 +1280,8 @@ void Position::getInlineBoxAndOffset(EAffinity affinity, TextDirection primaryDi equivalent.getInlineBoxAndOffset(UPSTREAM, primaryDirection, inlineBox, caretOffset); return; } - if (renderer->isBox()) { - inlineBox = toRenderBox(renderer)->inlineBoxWrapper(); + if (is<RenderBox>(*renderer)) { + inlineBox = downcast<RenderBox>(*renderer).inlineBoxWrapper(); if (!inlineBox || (caretOffset > inlineBox->caretMinOffset() && caretOffset < inlineBox->caretMaxOffset())) return; } @@ -1310,17 +1381,14 @@ void Position::getInlineBoxAndOffset(EAffinity affinity, TextDirection primaryDi TextDirection Position::primaryDirection() const { - TextDirection primaryDirection = LTR; - for (const RenderObject* r = m_anchorNode->renderer(); r; r = r->parent()) { - if (r->isRenderBlockFlow()) { - primaryDirection = toRenderBlockFlow(r)->style().direction(); - break; - } - } - - return primaryDirection; + if (!m_anchorNode->renderer()) + return LTR; + if (auto* blockFlow = lineageOfType<RenderBlockFlow>(*m_anchorNode->renderer()).first()) + return blockFlow->style().direction(); + return LTR; } +#if ENABLE(TREE_DEBUGGING) void Position::debugPosition(const char* msg) const { @@ -1330,8 +1398,6 @@ void Position::debugPosition(const char* msg) const fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, deprecatedNode()->nodeName().utf8().data(), deprecatedNode(), m_offset); } -#ifndef NDEBUG - void Position::formatForDebugger(char* buffer, unsigned length) const { StringBuilder result; @@ -1384,11 +1450,139 @@ void Position::showTreeForThis() const #endif +bool Position::equals(const Position& other) const +{ + if (!m_anchorNode) + return !m_anchorNode == !other.m_anchorNode; + if (!other.m_anchorNode) + return false; + + switch (anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*m_anchorNode)); + switch (other.anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode; + case PositionIsAfterChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode && !m_anchorNode->hasChildNodes(); + case PositionIsOffsetInAnchor: + return m_anchorNode == other.m_anchorNode && !other.m_offset; + case PositionIsBeforeAnchor: + return m_anchorNode->firstChild() == other.m_anchorNode; + case PositionIsAfterAnchor: + return false; + } + break; + case PositionIsAfterChildren: + ASSERT(!is<Text>(*m_anchorNode)); + switch (other.anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode && !m_anchorNode->hasChildNodes(); + case PositionIsAfterChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode; + case PositionIsOffsetInAnchor: + return m_anchorNode == other.m_anchorNode && m_anchorNode->countChildNodes() == static_cast<unsigned>(m_offset); + case PositionIsBeforeAnchor: + return false; + case PositionIsAfterAnchor: + return m_anchorNode->lastChild() == other.m_anchorNode; + } + break; + case PositionIsOffsetInAnchor: + switch (other.anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode && !m_offset; + case PositionIsAfterChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode && m_offset == static_cast<int>(other.m_anchorNode->countChildNodes()); + case PositionIsOffsetInAnchor: + return m_anchorNode == other.m_anchorNode && m_offset == other.m_offset; + case PositionIsBeforeAnchor: + return m_anchorNode->traverseToChildAt(m_offset) == other.m_anchorNode; + case PositionIsAfterAnchor: + return m_offset && m_anchorNode->traverseToChildAt(m_offset - 1) == other.m_anchorNode; + } + break; + case PositionIsBeforeAnchor: + switch (other.anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode->firstChild(); + case PositionIsAfterChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return false; + case PositionIsOffsetInAnchor: + return m_anchorNode == other.m_anchorNode->traverseToChildAt(other.m_offset); + case PositionIsBeforeAnchor: + return m_anchorNode == other.m_anchorNode; + case PositionIsAfterAnchor: + return m_anchorNode->previousSibling() == other.m_anchorNode; + } + break; + case PositionIsAfterAnchor: + switch (other.anchorType()) { + case PositionIsBeforeChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return false; + case PositionIsAfterChildren: + ASSERT(!is<Text>(*other.m_anchorNode)); + return m_anchorNode == other.m_anchorNode->lastChild(); + case PositionIsOffsetInAnchor: + return other.m_offset && m_anchorNode == other.m_anchorNode->traverseToChildAt(other.m_offset - 1); + case PositionIsBeforeAnchor: + return m_anchorNode->nextSibling() == other.m_anchorNode; + case PositionIsAfterAnchor: + return m_anchorNode == other.m_anchorNode; + } + break; + } + + ASSERT_NOT_REACHED(); + return false; +} + +static TextStream& operator<<(TextStream& stream, Position::AnchorType anchorType) +{ + switch (anchorType) { + case Position::PositionIsOffsetInAnchor: + stream << "offset in anchor"; + break; + case Position::PositionIsBeforeAnchor: + stream << "before anchor"; + break; + case Position::PositionIsAfterAnchor: + stream << "after anchor"; + break; + case Position::PositionIsBeforeChildren: + stream << "before children"; + break; + case Position::PositionIsAfterChildren: + stream << "after children"; + break; + } + return stream; +} + +TextStream& operator<<(TextStream& stream, const Position& position) +{ + TextStream::GroupScope scope(stream); + stream << "Position " << &position; + stream.dumpProperty("anchor node", position.anchorNode()); + stream.dumpProperty("offset", position.offsetInContainerNode()); + stream.dumpProperty("anchor type", position.anchorType()); + + return stream; +} } // namespace WebCore -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::Position& pos) { |