Changeset 96553 in webkit


Ignore:
Timestamp:
Oct 3, 2011 4:05:39 PM (13 years ago)
Author:
rniwa@webkit.org
Message:

Replace m_firstNodeInserted and m_lastLeafInserted in ReplaceSelectionCommand by positions
https://bugs.webkit.org/show_bug.cgi?id=68874

Reviewed by Enrica Casucci.

Replaced m_firstNodeInserted and m_lastLeafInserted by m_startOfInsertedContent and m_endOfInsertedContent
respectively. Also removed removeNodePreservingChildren and removeNodeAndPruneAncestors in ReplaceSelectionCommand
because they were not virtual in CompositeEditCommand and implicitly overriding the functions was confusing.
Since each of these two functions is used at exactly one place, just update positions and insertedNodes explicitly.

  • editing/CompositeEditCommand.cpp:
  • editing/ReplaceSelectionCommand.cpp:

(WebCore::ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds):
(WebCore::ReplaceSelectionCommand::positionAtEndOfInsertedContent):
(WebCore::ReplaceSelectionCommand::positionAtStartOfInsertedContent):
(WebCore::ReplaceSelectionCommand::handleStyleSpans):
(WebCore::ReplaceSelectionCommand::mergeEndIfNeeded): Update m_endOfInsertedContent by endingSelection().visibleEnd()
instead of m_lastLeafInserted with destination.previous() because moveParagraph could have removed leading whitespace in
the text node referenced by destination. This is tested by an existing layout test.
(WebCore::ReplaceSelectionCommand::doApply):
(WebCore::ReplaceSelectionCommand::addSpacesForSmartReplace): Update positions as needed. All changes are tested
by the existing layout tests in editing/pasteboard.
(WebCore::ReplaceSelectionCommand::updateNodesInserted):

  • editing/ReplaceSelectionCommand.h:
  • editing/htmlediting.cpp:

(WebCore::hasARenderedDescendant): Moved from CompositeEditCommand.cpp.
(WebCore::highestNodeToRemoveInPruning): Ditto.

  • editing/htmlediting.h:
Location:
trunk/Source/WebCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r96552 r96553  
     12011-10-03  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Replace m_firstNodeInserted and m_lastLeafInserted in ReplaceSelectionCommand by positions
     4        https://bugs.webkit.org/show_bug.cgi?id=68874
     5
     6        Reviewed by Enrica Casucci.
     7
     8        Replaced m_firstNodeInserted and m_lastLeafInserted by m_startOfInsertedContent and m_endOfInsertedContent
     9        respectively. Also removed removeNodePreservingChildren and removeNodeAndPruneAncestors in ReplaceSelectionCommand
     10        because they were not virtual in CompositeEditCommand and implicitly overriding the functions was confusing.
     11        Since each of these two functions is used at exactly one place, just update positions and insertedNodes explicitly.
     12
     13        * editing/CompositeEditCommand.cpp:
     14        * editing/ReplaceSelectionCommand.cpp:
     15        (WebCore::ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds):
     16        (WebCore::ReplaceSelectionCommand::positionAtEndOfInsertedContent):
     17        (WebCore::ReplaceSelectionCommand::positionAtStartOfInsertedContent):
     18        (WebCore::ReplaceSelectionCommand::handleStyleSpans):
     19        (WebCore::ReplaceSelectionCommand::mergeEndIfNeeded): Update m_endOfInsertedContent by endingSelection().visibleEnd()
     20        instead of m_lastLeafInserted with destination.previous() because moveParagraph could have removed leading whitespace in
     21        the text node referenced by destination. This is tested by an existing layout test.
     22        (WebCore::ReplaceSelectionCommand::doApply):
     23        (WebCore::ReplaceSelectionCommand::addSpacesForSmartReplace): Update positions as needed. All changes are tested
     24        by the existing layout tests in editing/pasteboard.
     25        (WebCore::ReplaceSelectionCommand::updateNodesInserted):
     26        * editing/ReplaceSelectionCommand.h:
     27        * editing/htmlediting.cpp:
     28        (WebCore::hasARenderedDescendant): Moved from CompositeEditCommand.cpp.
     29        (WebCore::highestNodeToRemoveInPruning): Ditto.
     30        * editing/htmlediting.h:
     31
    1322011-10-03  Ryosuke Niwa  <rniwa@webkit.org>
    233
  • trunk/Source/WebCore/editing/CompositeEditCommand.cpp

    r96150 r96553  
    248248}
    249249
    250 static bool hasARenderedDescendant(Node* node, Node* excludedNode)
    251 {
    252     for (Node* n = node->firstChild(); n;) {
    253         if (n == excludedNode) {
    254             n = n->traverseNextSibling(node);
    255             continue;
    256         }
    257         if (n->renderer())
    258             return true;
    259         n = n->traverseNextNode(node);
    260     }
    261     return false;
    262 }
    263 
    264 static Node* highestNodeToRemoveInPruning(Node* node)
    265 {
    266     Node* previousNode = 0;
    267     Node* rootEditableElement = node ? node->rootEditableElement() : 0;
    268     for (; node; node = node->parentNode()) {
    269         if (RenderObject* renderer = node->renderer()) {
    270             if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node)
    271                 return previousNode;
    272         }
    273         previousNode = node;
    274     }
    275     return 0;
    276 }
    277 
    278250void CompositeEditCommand::prune(PassRefPtr<Node> node)
    279251{
  • trunk/Source/WebCore/editing/ReplaceSelectionCommand.cpp

    r96410 r96553  
    443443}
    444444
    445 // Wrap CompositeEditCommand::removeNodePreservingChildren() so we can update the nodes we track
    446 void ReplaceSelectionCommand::removeNodePreservingChildren(Node* node)
    447 {
    448     if (m_firstNodeInserted == node)
    449         m_firstNodeInserted = node->traverseNextNode();
    450     if (m_lastLeafInserted == node)
    451         m_lastLeafInserted = node->lastChild() ? node->lastChild() : node->traverseNextSibling();
    452     CompositeEditCommand::removeNodePreservingChildren(node);
    453 }
    454 
    455 // Wrap CompositeEditCommand::removeNodeAndPruneAncestors() so we can update the nodes we track
    456 void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node)
    457 {
    458     // prepare in case m_firstNodeInserted and/or m_lastLeafInserted get removed
    459     // FIXME: shouldn't m_lastLeafInserted be adjusted using traversePreviousNode()?
    460     Node* afterFirst = m_firstNodeInserted ? m_firstNodeInserted->traverseNextSibling() : 0;
    461     Node* afterLast = m_lastLeafInserted ? m_lastLeafInserted->traverseNextSibling() : 0;
    462    
    463     CompositeEditCommand::removeNodeAndPruneAncestors(node);
    464    
    465     // adjust m_firstNodeInserted and m_lastLeafInserted since either or both may have been removed
    466     if (m_lastLeafInserted && !m_lastLeafInserted->inDocument())
    467         m_lastLeafInserted = afterLast;
    468     if (m_firstNodeInserted && !m_firstNodeInserted->inDocument())
    469         m_firstNodeInserted = m_lastLeafInserted && m_lastLeafInserted->inDocument() ? afterFirst : 0;
    470 }
    471 
    472445static bool isHeaderElement(Node* a)
    473446{
     
    583556    }
    584557
    585     // We don't have to make sure that m_firstNodeInserted isn't inside a select or script element, because
     558    // We don't have to make sure that firstNodeInserted isn't inside a select or script element, because
    586559    // it is a top level node in the fragment and the user can't insert into those elements.
    587560    Node* firstNodeInserted = insertedNodes.firstNodeInserted();
     
    595568VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() const
    596569{
    597     Node* lastNode = m_lastLeafInserted.get();
    598570    // FIXME: Why is this hack here?  What's special about <select> tags?
    599     Node* enclosingSelect = enclosingNodeWithTag(firstPositionInOrBeforeNode(lastNode), selectTag);
    600     if (enclosingSelect)
    601         lastNode = enclosingSelect;
    602     return lastPositionInOrAfterNode(lastNode);
     571    Node* enclosingSelect = enclosingNodeWithTag(m_endOfInsertedContent, selectTag);
     572    return enclosingSelect ? lastPositionInOrAfterNode(enclosingSelect) : m_endOfInsertedContent;
    603573}
    604574
    605575VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() const
    606576{
    607     return firstPositionInOrBeforeNode(m_firstNodeInserted.get());
     577    return m_startOfInsertedContent;
    608578}
    609579
     
    661631// We should remove styles from spans that are overridden by all of their children, either here
    662632// or at copy time.
    663 void ReplaceSelectionCommand::handleStyleSpans(Node* firstNodeInserted)
     633void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes)
    664634{
    665635    HTMLElement* wrappingStyleSpan = 0;
     
    667637    // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
    668638    // so search for the top level style span instead of assuming it's at the top.
    669     for (Node* node = firstNodeInserted; node; node = node->traverseNextNode()) {
     639    for (Node* node = insertedNodes.firstNodeInserted(); node; node = node->traverseNextNode()) {
    670640        if (isLegacyAppleStyleSpan(node)) {
    671641            wrappingStyleSpan = toHTMLElement(node);
     
    698668    style->removeBlockProperties();
    699669
    700     if (style->isEmpty() || !wrappingStyleSpan->firstChild())
     670    if (style->isEmpty() || !wrappingStyleSpan->firstChild()) {
     671        insertedNodes.willRemoveNodePreservingChildren(wrappingStyleSpan);
    701672        removeNodePreservingChildren(wrappingStyleSpan);
    702     else
     673    } else
    703674        setNodeAttribute(wrappingStyleSpan, styleAttr, style->style()->cssText());
    704675}
     
    737708    moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
    738709   
    739     // Merging forward will remove m_lastLeafInserted from the document.
    740     // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes.  The nodes are
    741     // only ever used to create positions where inserted content starts/ends.  Also, we sometimes insert content
    742     // directly into text nodes already in the document, in which case tracking inserted nodes is inadequate.
     710    // Merging forward will remove m_endOfInsertedContent from the document.
    743711    if (mergeForward) {
    744         m_lastLeafInserted = destination.previous().deepEquivalent().deprecatedNode();
    745         if (!m_firstNodeInserted->inDocument())
    746             m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().deprecatedNode();
    747         // If we merged text nodes, m_lastLeafInserted could be null. If this is the case,
    748         // we use m_firstNodeInserted.
    749         if (!m_lastLeafInserted)
    750             m_lastLeafInserted = m_firstNodeInserted;
     712        if (m_startOfInsertedContent.isOrphan())
     713            m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent();
     714         m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent();
     715        // If we merged text nodes, m_endOfInsertedContent could be null. If this is the case, we use m_startOfInsertedContent.
     716        if (m_endOfInsertedContent.isNull())
     717            m_endOfInsertedContent = m_startOfInsertedContent;
    751718    }
    752719}
     
    966933    // 5) Add spaces for smart replace.
    967934    // 6) Select the replacement if requested, and match style if requested.
    968    
    969     VisiblePosition startOfInsertedContent, endOfInsertedContent;
    970935
    971936    InsertedNodes insertedNodes;
     
    1009974
    1010975    removeUnrenderedTextNodesAtEnds(insertedNodes);
    1011    
    1012     m_firstNodeInserted = insertedNodes.firstNodeInserted();
    1013     m_lastLeafInserted = insertedNodes.lastLeafInserted();
    1014976
    1015977    if (!handledStyleSpans)
    1016         handleStyleSpans(m_firstNodeInserted.get());
     978        handleStyleSpans(insertedNodes);
    1017979
    1018980    // Mutation events (bug 20161) may have already removed the inserted content
    1019     if (!m_firstNodeInserted || !m_firstNodeInserted->inDocument())
    1020         return;
    1021    
    1022     endOfInsertedContent = positionAtEndOfInsertedContent();
    1023     startOfInsertedContent = positionAtStartOfInsertedContent();
    1024    
     981    if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted()->inDocument())
     982        return;
     983
     984    VisiblePosition startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted());
     985
    1025986    // We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and
    1026987    // didn't have a br after it, so the inserted content ended up in the same paragraph.
     
    1028989        insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent());
    1029990
    1030     bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd();
    1031 
    1032     if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR)))
    1033         removeNodeAndPruneAncestors(endBR);
    1034    
     991    if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR))) {
     992        RefPtr<Node> parent = endBR->parentNode();
     993        insertedNodes.willRemoveNode(endBR);
     994        removeNode(endBR);
     995        if (Node* nodeToRemove = highestNodeToRemoveInPruning(parent.get())) {
     996            insertedNodes.willRemoveNode(nodeToRemove);
     997            removeNode(nodeToRemove);
     998        }
     999    }
     1000
     1001    // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes.
     1002    m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted());
     1003    m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafInserted());
     1004
    10351005    // Determine whether or not we should merge the end of inserted content with what's after it before we do
    10361006    // the start merge so that the start merge doesn't effect our decision.
     
    10381008   
    10391009    if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), startIsInsideMailBlockquote)) {
    1040         VisiblePosition destination = startOfInsertedContent.previous();
    1041         VisiblePosition startOfParagraphToMove = startOfInsertedContent;
     1010        VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedContent();
     1011        VisiblePosition destination = startOfParagraphToMove.previous();
    10421012        // We need to handle the case where we need to merge the end
    10431013        // but our destination node is inside an inline that is the last in the block.
     
    10651035        // only ever used to create positions where inserted content starts/ends.
    10661036        moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
    1067         m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().downstream().deprecatedNode();
    1068         if (!m_lastLeafInserted->inDocument())
    1069             m_lastLeafInserted = endingSelection().visibleEnd().deepEquivalent().upstream().deprecatedNode();
    1070     }
    1071            
    1072     endOfInsertedContent = positionAtEndOfInsertedContent();
    1073     startOfInsertedContent = positionAtStartOfInsertedContent();
     1037        m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent().downstream();
     1038        if (m_endOfInsertedContent.isOrphan())
     1039            m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent().upstream();
     1040    }
    10741041
    10751042    Position lastPositionToSelect;
    1076     if (interchangeNewlineAtEnd) {
     1043    if (fragment.hasInterchangeNewlineAtEnd()) {
     1044        VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
    10771045        VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary);
    10781046
     
    11631131        if (endNode->isTextNode()) {
    11641132            Text* text = static_cast<Text*>(endNode);
     1133            // FIXME: we shouldn't always be inserting the space at the end
    11651134            insertTextIntoNode(text, text->length(), collapseWhiteSpace ? nonBreakingSpaceString() : " ");
     1135            if (m_endOfInsertedContent.containerNode() == text)
     1136                m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1);
    11661137        } else {
    11671138            RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
     
    11811152        if (startNode->isTextNode()) {
    11821153            Text* text = static_cast<Text*>(startNode);
     1154            // FIXME: we shouldn't always be inserting the space at the beginning
    11831155            insertTextIntoNode(text, 0, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
     1156            if (m_endOfInsertedContent.containerNode() == text && m_endOfInsertedContent.offsetInContainerNode())
     1157                m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1);
    11841158        } else {
    11851159            RefPtr<Node> node = document()->createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
    1186             // Don't updateNodesInserted. Doing so would set m_lastLeafInserted to be the node containing the leading space,
    1187             // but m_lastLeafInserted is supposed to mark the end of pasted content.
     1160            // Don't updateNodesInserted. Doing so would set m_endOfInsertedContent to be the node containing the leading space,
     1161            // but m_endOfInsertedContent is supposed to mark the end of pasted content.
    11881162            insertNodeBefore(node, startNode);
    1189             // FIXME: Use positions to track the start/end of inserted content.
    1190             m_firstNodeInserted = node;
     1163            m_startOfInsertedContent = firstPositionInNode(node.get());
    11911164        }
    11921165    }
     
    12761249        return;
    12771250
    1278     if (!m_firstNodeInserted)
    1279         m_firstNodeInserted = node;
    1280    
    1281     if (node == m_lastLeafInserted)
    1282         return;
    1283    
    1284     m_lastLeafInserted = node->lastDescendant();
     1251    if (m_startOfInsertedContent.isNull())
     1252        m_startOfInsertedContent = firstPositionInOrBeforeNode(node);
     1253
     1254    m_endOfInsertedContent = lastPositionInOrAfterNode(node->lastDescendant());
    12851255}
    12861256
  • trunk/Source/WebCore/editing/ReplaceSelectionCommand.h

    r96410 r96553  
    8989   
    9090    void removeRedundantStylesAndKeepStyleSpanInline(InsertedNodes&);
    91     void handleStyleSpans(Node* firstNodeInserted);
     91    void handleStyleSpans(InsertedNodes&);
    9292    void handlePasteAsQuotationNode();
    93    
    94     virtual void removeNodePreservingChildren(Node*);
    95     virtual void removeNodeAndPruneAncestors(Node*);
    9693   
    9794    VisiblePosition positionAtStartOfInsertedContent() const;
     
    104101    bool performTrivialReplace(const ReplacementFragment&);
    105102
    106     RefPtr<Node> m_firstNodeInserted;
    107     RefPtr<Node> m_lastLeafInserted;
     103    Position m_startOfInsertedContent;
     104    Position m_endOfInsertedContent;
    108105    RefPtr<EditingStyle> m_insertionStyle;
    109106    bool m_selectReplacement;
  • trunk/Source/WebCore/editing/htmlediting.cpp

    r96257 r96553  
    622622   
    623623    return highest;
     624}
     625
     626static bool hasARenderedDescendant(Node* node, Node* excludedNode)
     627{
     628    for (Node* n = node->firstChild(); n;) {
     629        if (n == excludedNode) {
     630            n = n->traverseNextSibling(node);
     631            continue;
     632        }
     633        if (n->renderer())
     634            return true;
     635        n = n->traverseNextNode(node);
     636    }
     637    return false;
     638}
     639
     640Node* highestNodeToRemoveInPruning(Node* node)
     641{
     642    Node* previousNode = 0;
     643    Node* rootEditableElement = node ? node->rootEditableElement() : 0;
     644    for (; node; node = node->parentNode()) {
     645        if (RenderObject* renderer = node->renderer()) {
     646            if (!renderer->canHaveChildren() || hasARenderedDescendant(node, previousNode) || rootEditableElement == node)
     647                return previousNode;
     648        }
     649        previousNode = node;
     650    }
     651    return 0;
    624652}
    625653
  • trunk/Source/WebCore/editing/htmlediting.h

    r96084 r96553  
    5858Node* highestEnclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*),
    5959    EditingBoundaryCrossingRule = CannotCrossEditingBoundary, Node* stayWithin = 0);
    60 Node* lowestEditableAncestor(Node*);   
     60Node* highestNodeToRemoveInPruning(Node*);
     61Node* lowestEditableAncestor(Node*);
    6162
    6263Node* enclosingBlock(Node*, EditingBoundaryCrossingRule = CannotCrossEditingBoundary);
Note: See TracChangeset for help on using the changeset viewer.