2006-03-25 Eric Seidel <eseidel@apple.com>
[WebKit-https.git] / WebCore / editing / CompositeEditCommand.cpp
1 /*
2  * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "CompositeEditCommand.h"
28
29 #include "Document.h"
30 #include "InlineTextBox.h"
31 #include "AppendNodeCommand.h"
32 #include "ApplyStyleCommand.h"
33 #include "DeleteFromTextNodeCommand.h"
34 #include "DeleteSelectionCommand.h"
35 #include "Element.h"
36 #include "Range.h"
37 #include "htmlediting.h"
38 #include "HTMLNames.h"
39 #include "InsertIntoTextNodeCommand.h"
40 #include "InsertNodeBeforeCommand.h"
41 #include "InsertParagraphSeparatorCommand.h"
42 #include "InsertTextCommand.h"
43 #include "JoinTextNodesCommand.h"
44 #include "MergeIdenticalElementsCommand.h"
45 #include "RebalanceWhitespaceCommand.h"
46 #include "RemoveCSSPropertyCommand.h"
47 #include "RemoveNodeAttributeCommand.h"
48 #include "RemoveNodeCommand.h"
49 #include "RemoveNodePreservingChildrenCommand.h"
50 #include "SetNodeAttributeCommand.h"
51 #include "SplitElementCommand.h"
52 #include "SplitTextNodeCommand.h"
53 #include "SplitTextNodeContainingElementCommand.h"
54 #include "VisiblePosition.h"
55 #include "visible_units.h"
56 #include "WrapContentsInDummySpanCommand.h"
57 #include <kxmlcore/Assertions.h>
58
59 namespace WebCore {
60
61 using namespace HTMLNames;
62
63 static const String &blockPlaceholderClassString();
64
65 CompositeEditCommand::CompositeEditCommand(Document *document) 
66     : EditCommand(document)
67 {
68 }
69
70 void CompositeEditCommand::doUnapply()
71 {
72     if (m_cmds.count() == 0)
73         return;
74     
75     DeprecatedValueList<EditCommandPtr>::ConstIterator end;
76     for (DeprecatedValueList<EditCommandPtr>::ConstIterator it = m_cmds.fromLast(); it != end; --it)
77         (*it)->unapply();
78
79     setState(NotApplied);
80 }
81
82 void CompositeEditCommand::doReapply()
83 {
84     if (m_cmds.count() == 0)
85         return;
86
87     for (DeprecatedValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
88         (*it)->reapply();
89
90     setState(Applied);
91 }
92
93 //
94 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
95 //
96 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
97 {
98     cmd.setStartingSelection(endingSelection());
99     cmd.setEndingSelection(endingSelection());
100     cmd.setParent(this);
101     cmd.apply();
102     m_cmds.append(cmd);
103 }
104
105 void CompositeEditCommand::applyStyle(CSSStyleDeclaration *style, EditAction editingAction)
106 {
107     EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
108     applyCommandToComposite(cmd);
109 }
110
111 void CompositeEditCommand::applyStyle(CSSStyleDeclaration *style, Position start, Position end, EditAction editingAction)
112 {
113     EditCommandPtr cmd(new ApplyStyleCommand(document(), style, start, end, editingAction));
114     applyCommandToComposite(cmd);
115 }
116
117 void CompositeEditCommand::applyStyledElement(Element* element)
118 {
119     EditCommandPtr cmd(new ApplyStyleCommand(document(), element, false));
120     applyCommandToComposite(cmd);
121 }
122
123 void CompositeEditCommand::removeStyledElement(Element* element)
124 {
125     EditCommandPtr cmd(new ApplyStyleCommand(document(), element, true));
126     applyCommandToComposite(cmd);
127 }
128
129 void CompositeEditCommand::insertParagraphSeparator()
130 {
131     EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
132     applyCommandToComposite(cmd);
133 }
134
135 void CompositeEditCommand::insertNodeBefore(Node *insertChild, Node *refChild)
136 {
137     ASSERT(!refChild->hasTagName(bodyTag));
138     EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
139     applyCommandToComposite(cmd);
140 }
141
142 void CompositeEditCommand::insertNodeAfter(Node *insertChild, Node *refChild)
143 {
144     ASSERT(!refChild->hasTagName(bodyTag));
145     if (refChild->parentNode()->lastChild() == refChild) {
146         appendNode(insertChild, refChild->parentNode());
147     }
148     else {
149         ASSERT(refChild->nextSibling());
150         insertNodeBefore(insertChild, refChild->nextSibling());
151     }
152 }
153
154 void CompositeEditCommand::insertNodeAt(Node *insertChild, Node *refChild, int offset)
155 {
156     if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
157         Node *child = refChild->firstChild();
158         for (int i = 0; child && i < offset; i++)
159             child = child->nextSibling();
160         if (child)
161             insertNodeBefore(insertChild, child);
162         else
163             appendNode(insertChild, refChild);
164     } 
165     else if (refChild->caretMinOffset() >= offset) {
166         insertNodeBefore(insertChild, refChild);
167     } 
168     else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
169         splitTextNode(static_cast<Text *>(refChild), offset);
170         insertNodeBefore(insertChild, refChild);
171     } 
172     else {
173         insertNodeAfter(insertChild, refChild);
174     }
175 }
176
177 void CompositeEditCommand::appendNode(Node *appendChild, Node *parent)
178 {
179     EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
180     applyCommandToComposite(cmd);
181 }
182
183 void CompositeEditCommand::removeFullySelectedNode(Node *node)
184 {
185     if (isTableStructureNode(node) || node == node->rootEditableElement()) {
186         // Do not remove an element of table structure; remove its contents.
187         // Likewise for the root editable element.
188         Node *child = node->firstChild();
189         while (child) {
190             Node *remove = child;
191             child = child->nextSibling();
192             removeFullySelectedNode(remove);
193         }
194         
195         // make sure empty cell has some height
196         updateLayout();
197         RenderObject *r = node->renderer();
198         if (r && r->isTableCell() && r->contentHeight() <= 0)
199             insertBlockPlaceholder(Position(node,0));
200         return;
201     }
202     
203     removeNode(node);
204 }
205
206 void CompositeEditCommand::removeChildrenInRange(Node *node, int from, int to)
207 {
208     Node *nodeToRemove = node->childNode(from);
209     for (int i = from; i < to; i++) {
210         ASSERT(nodeToRemove);
211         Node *next = nodeToRemove->nextSibling();
212         removeNode(nodeToRemove);
213         nodeToRemove = next;
214     }
215 }
216
217 void CompositeEditCommand::removeNode(Node *removeChild)
218 {
219     EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
220     applyCommandToComposite(cmd);
221 }
222
223 void CompositeEditCommand::removeNodePreservingChildren(Node *removeChild)
224 {
225     EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
226     applyCommandToComposite(cmd);
227 }
228
229 void CompositeEditCommand::splitTextNode(Text *text, int offset)
230 {
231     EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
232     applyCommandToComposite(cmd);
233 }
234
235 void CompositeEditCommand::splitElement(Element *element, Node *atChild)
236 {
237     EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
238     applyCommandToComposite(cmd);
239 }
240
241 void CompositeEditCommand::mergeIdenticalElements(WebCore::Element *first, WebCore::Element *second)
242 {
243     EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
244     applyCommandToComposite(cmd);
245 }
246
247 void CompositeEditCommand::wrapContentsInDummySpan(WebCore::Element *element)
248 {
249     EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
250     applyCommandToComposite(cmd);
251 }
252
253 void CompositeEditCommand::splitTextNodeContainingElement(WebCore::Text *text, int offset)
254 {
255     EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
256     applyCommandToComposite(cmd);
257 }
258
259 void CompositeEditCommand::joinTextNodes(Text *text1, Text *text2)
260 {
261     EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
262     applyCommandToComposite(cmd);
263 }
264
265 void CompositeEditCommand::inputText(const String &text, bool selectInsertedText)
266 {
267     InsertTextCommand *impl = new InsertTextCommand(document());
268     EditCommandPtr cmd(impl);
269     applyCommandToComposite(cmd);
270     impl->input(text, selectInsertedText);
271 }
272
273 void CompositeEditCommand::insertTextIntoNode(Text *node, int offset, const String &text)
274 {
275     EditCommandPtr cmd(new InsertIntoTextNodeCommand(document(), node, offset, text));
276     applyCommandToComposite(cmd);
277 }
278
279 void CompositeEditCommand::deleteTextFromNode(Text *node, int offset, int count)
280 {
281     EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
282     applyCommandToComposite(cmd);
283 }
284
285 void CompositeEditCommand::replaceTextInNode(Text *node, int offset, int count, const String &replacementText)
286 {
287     EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
288     applyCommandToComposite(deleteCommand);
289     EditCommandPtr insertCommand(new InsertIntoTextNodeCommand(document(), node, offset, replacementText));
290     applyCommandToComposite(insertCommand);
291 }
292
293 Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
294 {
295     if (!isTabSpanTextNode(pos.node()))
296         return pos;
297     
298     Node *tabSpan = tabSpanNode(pos.node());
299     
300     if (pos.offset() <= pos.node()->caretMinOffset())
301         return positionBeforeNode(tabSpan);
302         
303     if (pos.offset() >= pos.node()->caretMaxOffset())
304         return positionAfterNode(tabSpan);
305
306     splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.offset());
307     return positionBeforeNode(tabSpan);
308 }
309
310 void CompositeEditCommand::insertNodeAtTabSpanPosition(Node *node, const Position& pos)
311 {
312     // insert node before, after, or at split of tab span
313     Position insertPos = positionOutsideTabSpan(pos);
314     insertNodeAt(node, insertPos.node(), insertPos.offset());
315 }
316
317 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
318 {
319     if (endingSelection().isRange()) {
320         EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
321         applyCommandToComposite(cmd);
322     }
323 }
324
325 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
326 {
327     if (selection.isRange()) {
328         EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
329         applyCommandToComposite(cmd);
330     }
331 }
332
333 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclaration *decl, int property)
334 {
335     EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
336     applyCommandToComposite(cmd);
337 }
338
339 void CompositeEditCommand::removeNodeAttribute(Element *element, const QualifiedName& attribute)
340 {
341     String value = element->getAttribute(attribute);
342     if (value.isEmpty())
343         return;
344     EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
345     applyCommandToComposite(cmd);
346 }
347
348 void CompositeEditCommand::setNodeAttribute(Element *element, const QualifiedName& attribute, const String &value)
349 {
350     EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
351     applyCommandToComposite(cmd);
352 }
353
354 void CompositeEditCommand::rebalanceWhitespaceAt(const Position &position)
355 {
356     EditCommandPtr cmd(new RebalanceWhitespaceCommand(document(), position));
357     applyCommandToComposite(cmd);    
358 }
359
360 void CompositeEditCommand::rebalanceWhitespace()
361 {
362     Selection selection = endingSelection();
363     if (selection.isCaretOrRange()) {
364         EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
365         applyCommandToComposite(startCmd);
366         if (selection.isRange()) {
367             EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
368             applyCommandToComposite(endCmd);
369         }
370     }
371 }
372
373 void CompositeEditCommand::deleteInsignificantText(Text *textNode, int start, int end)
374 {
375     if (!textNode || !textNode->renderer() || start >= end)
376         return;
377
378     RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
379     InlineTextBox *box = textRenderer->firstTextBox();
380     if (!box) {
381         // whole text node is empty
382         removeNode(textNode);
383         return;    
384     }
385     
386     int length = textNode->length();
387     if (start >= length || end > length)
388         return;
389
390     int removed = 0;
391     InlineTextBox *prevBox = 0;
392     RefPtr<StringImpl> str;
393
394     // This loop structure works to process all gaps preceding a box,
395     // and also will look at the gap after the last box.
396     while (prevBox || box) {
397         int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
398         if (end < gapStart)
399             // No more chance for any intersections
400             break;
401
402         int gapEnd = box ? box->m_start : length;
403         bool indicesIntersect = start <= gapEnd && end >= gapStart;
404         int gapLen = gapEnd - gapStart;
405         if (indicesIntersect && gapLen > 0) {
406             gapStart = kMax(gapStart, start);
407             gapEnd = kMin(gapEnd, end);
408             if (!str)
409                 str = textNode->string()->substring(start, end - start);
410             // remove text in the gap
411             str->remove(gapStart - start - removed, gapLen);
412             removed += gapLen;
413         }
414         
415         prevBox = box;
416         if (box)
417             box = box->nextTextBox();
418     }
419
420     if (str) {
421         // Replace the text between start and end with our pruned version.
422         if (str->length() > 0) {
423             replaceTextInNode(textNode, start, end - start, str.get());
424         } else {
425             // Assert that we are not going to delete all of the text in the node.
426             // If we were, that should have been done above with the call to 
427             // removeNode and return.
428             ASSERT(start > 0 || (unsigned)end - start < textNode->length());
429             deleteTextFromNode(textNode, start, end - start);
430         }
431     }
432 }
433
434 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
435 {
436     if (start.isNull() || end.isNull())
437         return;
438
439     if (Range::compareBoundaryPoints(start, end) >= 0)
440         return;
441
442     Node *node = start.node();
443     while (node) {
444         Node *next = node->traverseNextNode();
445     
446         if (node->isTextNode()) {
447             Text *textNode = static_cast<Text *>(node);
448             bool isStartNode = node == start.node();
449             bool isEndNode = node == end.node();
450             int startOffset = isStartNode ? start.offset() : 0;
451             int endOffset = isEndNode ? end.offset() : textNode->length();
452             deleteInsignificantText(textNode, startOffset, endOffset);
453         }
454             
455         if (node == end.node())
456             break;
457         node = next;
458     }
459 }
460
461 void CompositeEditCommand::deleteInsignificantTextDownstream(const WebCore::Position &pos)
462 {
463     Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
464     deleteInsignificantText(pos, end);
465 }
466
467 Node *CompositeEditCommand::appendBlockPlaceholder(Node *node)
468 {
469     if (!node)
470         return NULL;
471     
472     // Should assert isBlockFlow || isInlineFlow when deletion improves.  See 4244964.
473     ASSERT(node->renderer());
474
475     RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
476     appendNode(placeholder.get(), node);
477     return placeholder.get();
478 }
479
480 Node *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
481 {
482     if (pos.isNull())
483         return NULL;
484
485     // Should assert isBlockFlow || isInlineFlow when deletion improves.  See 4244964.
486     ASSERT(pos.node()->renderer());
487
488     RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
489     insertNodeAt(placeholder.get(), pos.node(), pos.offset());
490     return placeholder.get();
491 }
492
493 Node *CompositeEditCommand::addBlockPlaceholderIfNeeded(Node *node)
494 {
495     if (!node)
496         return false;
497
498     updateLayout();
499
500     RenderObject *renderer = node->renderer();
501     if (!renderer || !renderer->isBlockFlow())
502         return false;
503     
504     // append the placeholder to make sure it follows
505     // any unrendered blocks
506     if (renderer->height() == 0 || (renderer->isListItem() && renderer->isEmpty()))
507         return appendBlockPlaceholder(node);
508
509     return NULL;
510 }
511
512 bool CompositeEditCommand::removeBlockPlaceholder(Node *node)
513 {
514     Node *placeholder = findBlockPlaceholder(node);
515     if (placeholder) {
516         removeNode(placeholder);
517         return true;
518     }
519     return false;
520 }
521
522 Node *CompositeEditCommand::findBlockPlaceholder(Node *node)
523 {
524     if (!node)
525         return 0;
526
527     updateLayout();
528
529     RenderObject *renderer = node->renderer();
530     if (!renderer || !renderer->isBlockFlow())
531         return 0;
532
533     for (Node *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
534         if (checkMe->isElementNode()) {
535             Element *element = static_cast<Element *>(checkMe);
536             if (element->enclosingBlockFlowElement() == node && 
537                 element->getAttribute(classAttr) == blockPlaceholderClassString()) {
538                 return element;
539             }
540         }
541     }
542     
543     return 0;
544 }
545
546 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
547 {
548     if (pos.isNull())
549         return;
550     
551     updateLayout();
552     
553     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
554     VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
555     VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
556     VisiblePosition next = visibleParagraphEnd.next();
557     VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
558     
559     Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream();
560     Position end = visibleEnd.deepEquivalent().upstream();
561     
562     // Perform some checks to see if we need to perform work in this function.
563     if (paragraphStart.node()->isBlockFlow()) {
564         if (end.node()->isBlockFlow()) {
565             if (!end.node()->isAncestor(paragraphStart.node())) {
566                 // If the paragraph end is a descendant of paragraph start, then we need to run
567                 // the rest of this function. If not, we can bail here.
568                 return;
569             }
570         }
571         else if (end.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
572             // The paragraph end is in another block that is an ancestor of the paragraph start.
573             // We can bail as we have a full block to work with.
574             ASSERT(paragraphStart.node()->isAncestor(end.node()->enclosingBlockFlowElement()));
575             return;
576         }
577         else if (isEndOfDocument(visibleEnd)) {
578             // At the end of the document. We can bail here as well.
579             return;
580         }
581     }
582
583     RefPtr<Node> newBlock = createDefaultParagraphElement(document());
584
585     Node *moveNode = paragraphStart.node();
586     if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
587         moveNode = moveNode->traverseNextNode();
588     Node *endNode = end.node();
589     
590     insertNodeAt(newBlock.get(), paragraphStart.node(), paragraphStart.offset());
591
592     while (moveNode && !moveNode->isBlockFlow()) {
593         Node *next = moveNode->traverseNextSibling();
594         removeNode(moveNode);
595         appendNode(moveNode, newBlock.get());
596         if (moveNode == endNode)
597             break;
598         moveNode = next;
599     }
600 }
601
602 Node* enclosingAnchorElement(Node* node)
603 {
604     while (node && !(node->isElementNode() && node->isLink()))
605         node = node->parentNode();
606     
607     return node;
608 }
609
610 void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode)
611 {
612     if (!anchorNode)
613         return;
614     
615     ASSERT(anchorNode->isLink());
616     
617     setEndingSelection(Selection::selectionFromContentsOfNode(anchorNode));
618     applyStyledElement(static_cast<Element*>(anchorNode));
619 }
620
621 // We must push partially selected anchors down before creating or removing
622 // links from a selection to create fully selected chunks that can be removed.
623 // ApplyStyleCommand doesn't do this for us because styles can be nested.
624 // Anchors cannot be nested.
625 void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown()
626 {
627     Selection originalSelection = endingSelection();
628     VisiblePosition visibleStart(originalSelection.start());
629     VisiblePosition visibleEnd(originalSelection.end());
630     
631     Node* startAnchor = enclosingAnchorElement(originalSelection.start().node());
632     VisiblePosition startOfStartAnchor(Position(startAnchor, 0));
633     if (startAnchor && startOfStartAnchor != visibleStart)
634         pushAnchorElementDown(startAnchor);
635
636     Node* endAnchor = enclosingAnchorElement(originalSelection.end().node());
637     VisiblePosition endOfEndAnchor(Position(endAnchor, 0));
638     if (endAnchor && endOfEndAnchor != visibleEnd)
639         pushAnchorElementDown(endAnchor);
640
641     ASSERT(originalSelection.start().node()->inDocument() && originalSelection.end().node()->inDocument());
642     setEndingSelection(originalSelection);
643 }
644
645 PassRefPtr<Element> createBlockPlaceholderElement(Document* document)
646 {
647     ExceptionCode ec = 0;
648     RefPtr<Element> breakNode = document->createElementNS(xhtmlNamespaceURI, "br", ec);
649     ASSERT(ec == 0);
650     breakNode->setAttribute(classAttr, blockPlaceholderClassString());
651     return breakNode.release();
652 }
653
654 static const String &blockPlaceholderClassString()
655 {
656     static String blockPlaceholderClassString = "khtml-block-placeholder";
657     return blockPlaceholderClassString;
658 }
659
660 } // namespace WebCore