Source/WebCore/ChangeLog

 12011-03-08 Levi Weintraub <leviw@chromium.org>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 InsertUnorderedList over a non-editable region and multiple lines enters an infinite loop
 6 https://bugs.webkit.org/show_bug.cgi?id=53409
 7
 8 Fixing broken handling of mixed-editability content for InsertListCommand.
 9
 10 Test: editing/execCommand/insert-list-with-noneditable-content.html
 11
 12 * editing/CompositeEditCommand.cpp:
 13 (WebCore::CompositeEditCommand::cleanupAfterDeletion): Changed signature to take the destination
 14 position for the active editing command. Without this, there are cases when the destination happens
 15 to be a placeholder, and we remove it.
 16 (WebCore::CompositeEditCommand::moveParagraphs):
 17 * editing/CompositeEditCommand.h:
 18 * editing/InsertListCommand.cpp:
 19 (WebCore::InsertListCommand::doApply): Added logic to the paragraph iteration loop to handle pockets of
 20 non-editable content in an editable context. Previously, this could cause an infinite loop.
 21 (WebCore::InsertListCommand::isStartOfInsignificantRegion): When iterating over paragraphs with mixed
 22 editability content for editing commands, there are special cases (e.g. the boundary between two non-
 23 editable spans) that shouldn't be treated as a paragraph.
 24 * editing/visible_units.cpp:
 25 (WebCore::startOfParagraph): Added a mode of operation where we'll jump across non-editable
 26 content in the same paragraph to reach the actual editable paragraph start.
 27 (WebCore::endOfParagraph): Ditto.
 28 (WebCore::startOfNextParagraph): Now uses the aforementioned non-editable content skipping mode of
 29 endOfParagraph.
 30
1312011-03-08 Brent Fulgham <bfulgham@webkit.org>
232
333 Reviewed by Adam Roben.
80603

Source/WebCore/editing/CompositeEditCommand.cpp

@@void CompositeEditCommand::cloneParagrap
830830// Deleting a paragraph will leave a placeholder. Remove it (and prune
831831// empty or unrendered parents).
832832
833 void CompositeEditCommand::cleanupAfterDeletion()
 833void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination)
834834{
835835 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
836  if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
 836 if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
837837 // Note: We want the rightmost candidate.
838838 Position position = caretAfterDelete.deepEquivalent().downstream();
839839 Node* node = position.deprecatedNode();

@@void CompositeEditCommand::moveParagraph
947947 }
948948 }
949949
950  VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
951  VisiblePosition afterParagraph(endOfParagraphToMove.next());
 950 bool stayInEditableContent = true;
 951 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(stayInEditableContent);
 952 VisiblePosition afterParagraph(endOfParagraphToMove.next(stayInEditableContent));
952953
953954 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
954955 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.

@@void CompositeEditCommand::moveParagraph
985986 deleteSelection(false, false, false, false);
986987
987988 ASSERT(destination.deepEquivalent().anchorNode()->inDocument());
988 
989  cleanupAfterDeletion();
 989 cleanupAfterDeletion(destination);
990990 ASSERT(destination.deepEquivalent().anchorNode()->inDocument());
991991
992992 // Add a br if pruning an empty block level element caused a collapse. For example:
80603

Source/WebCore/editing/CompositeEditCommand.h

@@protected:
109109 void moveParagraphs(const VisiblePosition&, const VisiblePosition&, const VisiblePosition&, bool preserveSelection = false, bool preserveStyle = true);
110110 void moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode);
111111 void cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement);
112  void cleanupAfterDeletion();
 112 void cleanupAfterDeletion(VisiblePosition destination = VisiblePosition());
113113
114114 bool breakOutOfEmptyListItem();
115115 bool breakOutOfEmptyMailBlockquotedParagraph();
80603

Source/WebCore/editing/InsertListCommand.cpp

@@HTMLElement* InsertListCommand::fixOrpha
6262 return listElement.get();
6363}
6464
 65bool InsertListCommand::isStartOfInsignificantRegion(const VisiblePosition& start)
 66{
 67 ASSERT(start.isNotNull());
 68 VisiblePosition paragraphEnd = endOfParagraph(start);
 69 return start == paragraphEnd && (!start.deepEquivalent().upstream(CanCrossEditingBoundary).anchorNode()->isContentEditable()
 70 || endOfParagraph(paragraphEnd, CanCrossEditingBoundary) != startOfParagraph(start, CanCrossEditingBoundary));
 71}
 72
6573PassRefPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtr<HTMLElement> passedList)
6674{
6775 RefPtr<HTMLElement> list = passedList;

@@void InsertListCommand::doApply()
172180 if (startOfCurrentParagraph == startOfSelection)
173181 startOfSelection = endingSelection().visibleStart();
174182
175  startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart());
 183 VisiblePosition nextStart = startOfNextParagraph(endingSelection().visibleStart());
 184 while (nextStart.isNotNull() && nextStart != startOfLastParagraph && isStartOfInsignificantRegion(nextStart))
 185 nextStart = startOfNextParagraph(nextStart);
 186
 187 if (startOfCurrentParagraph == nextStart)
 188 break;
 189 startOfCurrentParagraph = nextStart;
176190 }
177191 setEndingSelection(endOfSelection);
178192 doApplyForSingleParagraph(forceCreateList, listTag, currentSelection.get());

@@PassRefPtr<HTMLElement> InsertListComman
375389
376390 // We inserted the list at the start of the content we're about to move
377391 // Update the start of content, so we don't try to move the list into itself. bug 19066
378  if (insertionPos == start.deepEquivalent())
 392 // Layout is necessary since start's node's inline renderers may have been destroyed by
 393 // the insertion.
 394 if (insertionPos == start.deepEquivalent()) {
 395 listElement->document()->updateLayoutIgnorePendingStylesheets();
379396 start = startOfParagraph(originalStart);
 397 }
380398 }
381399
382400 moveParagraph(start, end, positionBeforeNode(placeholder.get()), true);
80603

Source/WebCore/editing/InsertListCommand.h

@@private:
5252 virtual EditAction editingAction() const { return EditActionInsertList; }
5353
5454 HTMLElement* fixOrphanedListChild(Node*);
 55
5556 bool selectionHasListOfType(const VisibleSelection& selection, const QualifiedName&);
 57 bool isStartOfInsignificantRegion(const VisiblePosition&);
 58
5659 PassRefPtr<HTMLElement> mergeWithNeighboringLists(PassRefPtr<HTMLElement>);
5760 void doApplyForSingleParagraph(bool forceCreateList, const QualifiedName&, Range* currentSelection);
5861 void unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listNode, Node* listChildNode);
 62
5963 PassRefPtr<HTMLElement> listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag);
6064 RefPtr<HTMLElement> m_listElement;
6165 Type m_type;
80603

Source/WebCore/editing/visible_units.cpp

@@VisiblePosition startOfParagraph(const V
747747
748748 Node* startBlock = enclosingBlock(startNode);
749749
750  Node *node = startNode;
 750 Node* node = startNode;
 751 Node* highestRoot = highestEditableRoot(p);
751752 int offset = p.deprecatedEditingOffset();
752753 Position::AnchorType type = p.anchorType();
753754
754  Node *n = startNode;
 755 Node* n = startNode;
755756 while (n) {
756757 if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable())
757758 break;
 759 if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
 760 while (n && n->isContentEditable() != startNode->isContentEditable())
 761 n = n->traversePreviousNodePostOrder(startBlock);
 762 if (!n || !n->isDescendantOf(highestRoot))
 763 break;
 764 }
758765 RenderObject *r = n->renderer();
759766 if (!r) {
760767 n = n->traversePreviousNodePostOrder(startBlock);

@@VisiblePosition endOfParagraph(const Vis
813820 Node *stayInsideBlock = startBlock;
814821
815822 Node *node = startNode;
 823 Node* highestRoot = highestEditableRoot(p);
816824 int offset = p.deprecatedEditingOffset();
817825 Position::AnchorType type = p.anchorType();
818826

@@VisiblePosition endOfParagraph(const Vis
820828 while (n) {
821829 if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable())
822830 break;
 831 if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
 832 while (n && n->isContentEditable() != startNode->isContentEditable())
 833 n = n->traverseNextNode(stayInsideBlock);
 834 if (!n || !n->isDescendantOf(highestRoot))
 835 break;
 836 }
 837
823838 RenderObject *r = n->renderer();
824839 if (!r) {
825840 n = n->traverseNextNode(stayInsideBlock);

@@VisiblePosition endOfParagraph(const Vis
862877 return VisiblePosition(Position(node, type), DOWNSTREAM);
863878}
864879
 880// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true
865881VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition)
866882{
867  VisiblePosition paragraphEnd(endOfParagraph(visiblePosition));
 883 VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary));
868884 VisiblePosition afterParagraphEnd(paragraphEnd.next(true));
869885 // The position after the last position in the last cell of a table
870886 // is not the start of the next paragraph.
80603

LayoutTests/ChangeLog

 12011-03-08 Levi Weintraub <leviw@chromium.org>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 InsertUnorderedList over a non-editable region and multiple lines enters an infinite loop
 6 https://bugs.webkit.org/show_bug.cgi?id=53409
 7
 8 Avoiding crashes and infinite loops when listifying content with mixed-editability
 9
 10 * editing/execCommand/insert-list-with-noneditable-content-expected.txt: Added.
 11 * editing/execCommand/insert-list-with-noneditable-content.html: Added.
 12
1132011-03-08 Martin Robinson <mrobinson@igalia.com>
214
315 Rebaseline a GTK+ result after r80582.
80603

LayoutTests/editing/execCommand/insert-list-with-noneditable-content-expected.txt

 1This tests list creation in an editable context but across non-editable content. Editable content should be pulled into list items while non-editable nodes should be left alone, ending up after the created list.
 2Editable paragraph containing a 
 3 span in the middle.
 4   Another editable paragraph.
 5non-editable
 6Insert List
0

LayoutTests/editing/execCommand/insert-list-with-noneditable-content.html

 1<!DOCTYPE html>
 2<div id="description">This tests list creation in an editable context but across non-editable content. Editable content should be pulled into list items while non-editable nodes should be left alone, ending up after the created list.</div>
 3<div contenteditable="true" id="test" style="padding: 1em;">
 4 Editable paragraph containing a <span contenteditable="false" style="background-color: LightGray;">non-editable</span> span in the middle.<br>
 5 Another editable paragraph.
 6</div>
 7<button onclick="insertList();">Insert List</button>
 8<script>
 9function insertList() {
 10 document.execCommand('insertunorderedlist', false, '');
 11}
 12
 13var s = window.getSelection();
 14var div = document.getElementById("test");
 15s.setPosition(div.childNodes[0], 10);
 16s.modify("extend", "forward", "line");
 17
 18if (window.layoutTestController) {
 19 insertList();
 20 window.layoutTestController.dumpAsText();
 21}
 22</script>
0