2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "htmlediting.h"
28 #include "css_computedstyle.h"
29 #include "css_value.h"
30 #include "css_valueimpl.h"
31 #include "cssparser.h"
32 #include "cssproperties.h"
34 #include "dom_docimpl.h"
35 #include "dom_elementimpl.h"
36 #include "dom_nodeimpl.h"
37 #include "dom_position.h"
38 #include "dom_stringimpl.h"
39 #include "dom_textimpl.h"
40 #include "dom2_range.h"
41 #include "dom2_rangeimpl.h"
42 #include "html_elementimpl.h"
43 #include "html_imageimpl.h"
44 #include "html_interchange.h"
45 #include "htmlattrs.h"
47 #include "khtml_part.h"
48 #include "khtml_part.h"
49 #include "khtmlview.h"
52 #include "render_object.h"
53 #include "render_style.h"
54 #include "render_text.h"
55 #include "visible_position.h"
56 #include "visible_text.h"
57 #include "visible_units.h"
60 using DOM::CSSComputedStyleDeclarationImpl;
61 using DOM::CSSMutableStyleDeclarationImpl;
63 using DOM::CSSPrimitiveValue;
64 using DOM::CSSPrimitiveValueImpl;
65 using DOM::CSSProperty;
66 using DOM::CSSStyleDeclarationImpl;
68 using DOM::CSSValueImpl;
69 using DOM::DocumentFragmentImpl;
70 using DOM::DocumentImpl;
72 using DOM::DOMStringImpl;
73 using DOM::DoNotStayInBlock;
74 using DOM::DoNotUpdateLayout;
75 using DOM::EditingTextImpl;
76 using DOM::ElementImpl;
77 using DOM::EStayInBlock;
78 using DOM::HTMLElementImpl;
79 using DOM::HTMLImageElementImpl;
80 using DOM::NamedAttrMapImpl;
83 using DOM::NodeListImpl;
87 using DOM::StayInBlock;
89 using DOM::TreeWalkerImpl;
92 #include "KWQAssertions.h"
93 #include "KWQLogging.h"
94 #include "KWQKHTMLPart.h"
98 #define ASSERT(assertion) ((void)0)
99 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
100 #define ASSERT_NOT_REACHED() ((void)0)
101 #define LOG(channel, formatAndArgs...) ((void)0)
102 #define ERROR(formatAndArgs...) ((void)0)
103 #define ASSERT(assertion) assert(assertion)
105 #define debugPosition(a,b) ((void)0)
106 #define debugNode(a,b) ((void)0)
110 #define IF_IMPL_NULL_RETURN_ARG(arg) do { \
111 if (isNull()) { return arg; } \
114 #define IF_IMPL_NULL_RETURN do { \
115 if (isNull()) { return; } \
120 static inline bool isNBSP(const QChar &c)
122 return c.unicode() == 0xa0;
125 // FIXME: Can't really determine this without taking white-space mode into account.
126 static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
130 if (!pos.node()->isTextNode())
132 return isCollapsibleWhitespace(static_cast<TextImpl *>(pos.node())->data()[pos.offset()]);
135 static const int spacesPerTab = 4;
137 static bool isTableStructureNode(const NodeImpl *node)
139 RenderObject *r = node->renderer();
140 return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
143 static bool isListStructureNode(const NodeImpl *node)
145 // FIXME: Irritating that we can get away with just going at the render tree for isTableStructureNode,
146 // but here we also have to peek at the type of DOM node?
147 RenderObject *r = node->renderer();
148 NodeImpl::Id nodeID = node->id();
149 return (r && r->isListItem())
150 || (nodeID == ID_OL || nodeID == ID_UL || nodeID == ID_DD || nodeID == ID_DT || nodeID == ID_DIR || nodeID == ID_MENU);
153 static DOMString &nonBreakingSpaceString()
155 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
156 return nonBreakingSpaceString;
159 static DOMString &styleSpanClassString()
161 static DOMString styleSpanClassString = AppleStyleSpanClass;
162 return styleSpanClassString;
165 static bool isEmptyStyleSpan(const NodeImpl *node)
167 if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
170 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
171 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
172 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
175 static bool isStyleSpan(const NodeImpl *node)
177 if (!node || !node->isHTMLElement())
180 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
181 return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
184 static bool isEmptyFontTag(const NodeImpl *node)
186 if (!node || node->id() != ID_FONT)
189 const ElementImpl *elem = static_cast<const ElementImpl *>(node);
190 NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
191 return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
194 static DOMString &blockPlaceholderClassString()
196 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
197 return blockPlaceholderClassString;
200 static DOMString &matchNearestBlockquoteColorString()
202 static DOMString matchNearestBlockquoteColorString = "match";
203 return matchNearestBlockquoteColorString;
206 static void derefNodesInList(QPtrList<NodeImpl> &list)
208 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
209 it.current()->deref();
212 static int maxRangeOffset(NodeImpl *n)
214 if (DOM::offsetInCharacters(n->nodeType()))
215 return n->maxOffset();
217 if (n->isElementNode())
218 return n->childNodeCount();
223 static int maxDeepOffset(NodeImpl *n)
225 if (n->isAtomicNode())
226 return n->caretMaxOffset();
228 if (n->isElementNode())
229 return n->childNodeCount();
234 static void debugPosition(const char *prefix, const Position &pos)
239 LOG(Editing, "%s <null>", prefix);
241 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
244 static void debugNode(const char *prefix, const NodeImpl *node)
249 LOG(Editing, "%s <null>", prefix);
251 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
254 //------------------------------------------------------------------------------------------
257 EditCommandPtr::EditCommandPtr()
261 EditCommandPtr::EditCommandPtr(EditCommand *impl) : SharedPtr<EditCommand>(impl)
265 EditCommandPtr::EditCommandPtr(const EditCommandPtr &o) : SharedPtr<EditCommand>(o)
269 EditCommandPtr::~EditCommandPtr()
273 EditCommandPtr &EditCommandPtr::operator=(const EditCommandPtr &c)
275 static_cast<SharedPtr<EditCommand> &>(*this) = c;
279 bool EditCommandPtr::isCompositeStep() const
281 IF_IMPL_NULL_RETURN_ARG(false);
282 return get()->isCompositeStep();
285 bool EditCommandPtr::isInsertTextCommand() const
287 IF_IMPL_NULL_RETURN_ARG(false);
288 return get()->isInsertTextCommand();
291 bool EditCommandPtr::isTypingCommand() const
293 IF_IMPL_NULL_RETURN_ARG(false);
294 return get()->isTypingCommand();
297 void EditCommandPtr::apply() const
303 void EditCommandPtr::unapply() const
309 void EditCommandPtr::reapply() const
315 EditAction EditCommandPtr::editingAction() const
317 IF_IMPL_NULL_RETURN_ARG(EditActionUnspecified);
318 return get()->editingAction();
321 DocumentImpl * const EditCommandPtr::document() const
323 IF_IMPL_NULL_RETURN_ARG(0);
324 return get()->document();
327 Selection EditCommandPtr::startingSelection() const
329 IF_IMPL_NULL_RETURN_ARG(Selection());
330 return get()->startingSelection();
333 Selection EditCommandPtr::endingSelection() const
335 IF_IMPL_NULL_RETURN_ARG(Selection());
336 return get()->endingSelection();
339 void EditCommandPtr::setStartingSelection(const Selection &s) const
342 get()->setStartingSelection(s);
345 void EditCommandPtr::setStartingSelection(const VisiblePosition &p) const
348 get()->setStartingSelection(p);
351 void EditCommandPtr::setStartingSelection(const Position &p, EAffinity affinity) const
354 Selection s = Selection(p, affinity);
355 get()->setStartingSelection(s);
358 void EditCommandPtr::setEndingSelection(const Selection &s) const
361 get()->setEndingSelection(s);
364 void EditCommandPtr::setEndingSelection(const VisiblePosition &p) const
367 get()->setEndingSelection(p);
370 void EditCommandPtr::setEndingSelection(const Position &p, EAffinity affinity) const
373 Selection s = Selection(p, affinity);
374 get()->setEndingSelection(s);
377 CSSMutableStyleDeclarationImpl *EditCommandPtr::typingStyle() const
379 IF_IMPL_NULL_RETURN_ARG(0);
380 return get()->typingStyle();
383 void EditCommandPtr::setTypingStyle(CSSMutableStyleDeclarationImpl *style) const
386 get()->setTypingStyle(style);
389 EditCommandPtr EditCommandPtr::parent() const
391 IF_IMPL_NULL_RETURN_ARG(0);
392 return get()->parent();
395 void EditCommandPtr::setParent(const EditCommandPtr &cmd) const
398 get()->setParent(cmd.get());
401 EditCommandPtr &EditCommandPtr::emptyCommand()
403 static EditCommandPtr m_emptyCommand;
404 return m_emptyCommand;
407 //------------------------------------------------------------------------------------------
410 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
411 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
413 init(style, Position());
416 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
417 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
419 init(style, position);
422 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
425 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
429 QString styleText("");
431 QValueListConstIterator<CSSProperty> end;
432 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
433 const CSSProperty *property = &*it;
435 // If position is empty or the position passed in already has the
436 // style, just move on.
437 if (position.isNotNull() && currentlyHasStyle(position, property))
440 // If needed, figure out if this change is a legacy HTML style change.
441 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
446 if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
447 // we have to special-case text decorations
448 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
449 styleText += alteredProperty.cssText().string();
451 styleText += property->cssText().string();
455 mutableStyle->deref();
457 // Save the result for later
458 m_cssStyle = styleText.stripWhiteSpace();
461 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
463 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
466 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
468 if (!property || !property->value()) {
472 DOMString valueText(property->value()->cssText());
473 switch (property->id()) {
474 case CSS_PROP_FONT_WEIGHT:
475 if (strcasecmp(valueText, "bold") == 0) {
480 case CSS_PROP_FONT_STYLE:
481 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
482 m_applyItalic = true;
486 case CSS_PROP_COLOR: {
487 QColor color(CSSParser::parseColor(valueText));
488 m_applyFontColor = color.name();
491 case CSS_PROP_FONT_FAMILY:
492 m_applyFontFace = valueText;
494 case CSS_PROP_FONT_SIZE:
495 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
496 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
497 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
499 m_applyFontSize = "1";
500 else if (number <= 10)
501 m_applyFontSize = "2";
502 else if (number <= 13)
503 m_applyFontSize = "3";
504 else if (number <= 16)
505 m_applyFontSize = "4";
506 else if (number <= 18)
507 m_applyFontSize = "5";
508 else if (number <= 24)
509 m_applyFontSize = "6";
511 m_applyFontSize = "7";
512 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
513 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
514 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
518 // Can't make sense of the number. Put no font size.
525 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
527 ASSERT(pos.isNotNull());
528 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
531 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
536 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
541 //------------------------------------------------------------------------------------------
544 EditCommand::EditCommand(DocumentImpl *document)
545 : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
548 ASSERT(m_document->part());
550 m_startingSelection = m_document->part()->selection();
551 m_endingSelection = m_startingSelection;
553 m_document->part()->setSelection(Selection(), false, true);
556 EditCommand::~EditCommand()
561 m_typingStyle->deref();
564 void EditCommand::apply()
567 ASSERT(m_document->part());
568 ASSERT(state() == NotApplied);
570 KHTMLPart *part = m_document->part();
572 ASSERT(part->selection().isNone());
578 // FIXME: Improve typing style.
579 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
580 if (!preservesTypingStyle())
583 if (!isCompositeStep()) {
584 document()->updateLayout();
585 EditCommandPtr cmd(this);
586 part->appliedEditing(cmd);
590 void EditCommand::unapply()
593 ASSERT(m_document->part());
594 ASSERT(state() == Applied);
596 bool topLevel = !isCompositeStep();
598 KHTMLPart *part = m_document->part();
601 part->setSelection(Selection(), false, true);
603 ASSERT(part->selection().isNone());
607 m_state = NotApplied;
610 document()->updateLayout();
611 EditCommandPtr cmd(this);
612 part->unappliedEditing(cmd);
616 void EditCommand::reapply()
619 ASSERT(m_document->part());
620 ASSERT(state() == NotApplied);
622 bool topLevel = !isCompositeStep();
624 KHTMLPart *part = m_document->part();
627 part->setSelection(Selection(), false, true);
629 ASSERT(part->selection().isNone());
636 document()->updateLayout();
637 EditCommandPtr cmd(this);
638 part->reappliedEditing(cmd);
642 void EditCommand::doReapply()
647 EditAction EditCommand::editingAction() const
649 return EditActionUnspecified;
652 void EditCommand::setStartingSelection(const Selection &s)
654 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
655 cmd->m_startingSelection = s;
658 void EditCommand::setStartingSelection(const VisiblePosition &p)
660 Selection s = Selection(p);
661 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
662 cmd->m_startingSelection = s;
665 void EditCommand::setStartingSelection(const Position &p, EAffinity affinity)
667 Selection s = Selection(p, affinity);
668 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
669 cmd->m_startingSelection = s;
672 void EditCommand::setEndingSelection(const Selection &s)
674 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
675 cmd->m_endingSelection = s;
678 void EditCommand::setEndingSelection(const VisiblePosition &p)
680 Selection s = Selection(p);
681 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
682 cmd->m_endingSelection = s;
685 void EditCommand::setEndingSelection(const Position &p, EAffinity affinity)
687 Selection s = Selection(p, affinity);
688 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
689 cmd->m_endingSelection = s;
692 void EditCommand::assignTypingStyle(CSSMutableStyleDeclarationImpl *style)
694 if (m_typingStyle == style)
697 CSSMutableStyleDeclarationImpl *old = m_typingStyle;
698 m_typingStyle = style;
700 m_typingStyle->ref();
705 void EditCommand::setTypingStyle(CSSMutableStyleDeclarationImpl *style)
707 // FIXME: Improve typing style.
708 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
709 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
710 cmd->assignTypingStyle(style);
713 bool EditCommand::preservesTypingStyle() const
718 bool EditCommand::isInsertTextCommand() const
723 bool EditCommand::isTypingCommand() const
728 CSSMutableStyleDeclarationImpl *EditCommand::styleAtPosition(const Position &pos)
730 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
731 computedStyle->ref();
732 CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
733 computedStyle->deref();
735 // FIXME: Improve typing style.
736 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
737 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
739 style->merge(typingStyle);
745 //------------------------------------------------------------------------------------------
746 // CompositeEditCommand
748 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
749 : EditCommand(document)
753 void CompositeEditCommand::doUnapply()
755 if (m_cmds.count() == 0) {
759 for (int i = m_cmds.count() - 1; i >= 0; --i)
760 m_cmds[i]->unapply();
762 setState(NotApplied);
765 void CompositeEditCommand::doReapply()
767 if (m_cmds.count() == 0) {
771 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
778 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
780 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
782 cmd.setStartingSelection(endingSelection());
783 cmd.setEndingSelection(endingSelection());
789 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
791 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
792 applyCommandToComposite(cmd);
795 void CompositeEditCommand::insertParagraphSeparator()
797 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
798 applyCommandToComposite(cmd);
801 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
803 ASSERT(refChild->id() != ID_BODY);
804 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
805 applyCommandToComposite(cmd);
808 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
810 ASSERT(refChild->id() != ID_BODY);
811 if (refChild->parentNode()->lastChild() == refChild) {
812 appendNode(insertChild, refChild->parentNode());
815 ASSERT(refChild->nextSibling());
816 insertNodeBefore(insertChild, refChild->nextSibling());
820 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
822 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
823 NodeImpl *child = refChild->firstChild();
824 for (long i = 0; child && i < offset; i++)
825 child = child->nextSibling();
827 insertNodeBefore(insertChild, child);
829 appendNode(insertChild, refChild);
831 else if (refChild->caretMinOffset() >= offset) {
832 insertNodeBefore(insertChild, refChild);
834 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
835 splitTextNode(static_cast<TextImpl *>(refChild), offset);
836 insertNodeBefore(insertChild, refChild);
839 insertNodeAfter(insertChild, refChild);
843 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
845 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
846 applyCommandToComposite(cmd);
849 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
851 if (isTableStructureNode(node) || node == node->rootEditableElement()) {
852 // Do not remove an element of table structure; remove its contents.
853 // Likewise for the root editable element.
854 NodeImpl *child = node->firstChild();
856 NodeImpl *remove = child;
857 child = child->nextSibling();
858 removeFullySelectedNode(remove);
866 void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
868 NodeImpl *nodeToRemove = node->childNode(from);
869 for (int i = from; i < to; i++) {
870 ASSERT(nodeToRemove);
871 NodeImpl *next = nodeToRemove->nextSibling();
872 removeNode(nodeToRemove);
877 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
879 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
880 applyCommandToComposite(cmd);
883 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
885 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
886 applyCommandToComposite(cmd);
889 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
891 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
892 applyCommandToComposite(cmd);
895 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
897 EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
898 applyCommandToComposite(cmd);
901 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
903 EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
904 applyCommandToComposite(cmd);
907 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
909 EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
910 applyCommandToComposite(cmd);
913 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
915 EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
916 applyCommandToComposite(cmd);
919 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
921 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
922 applyCommandToComposite(cmd);
925 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
927 InsertTextCommand *impl = new InsertTextCommand(document());
928 EditCommandPtr cmd(impl);
929 applyCommandToComposite(cmd);
930 impl->input(text, selectInsertedText);
933 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
935 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
936 applyCommandToComposite(cmd);
939 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
941 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
942 applyCommandToComposite(cmd);
945 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
947 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
948 applyCommandToComposite(deleteCommand);
949 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
950 applyCommandToComposite(insertCommand);
953 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
955 if (endingSelection().isRange()) {
956 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
957 applyCommandToComposite(cmd);
961 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
963 if (selection.isRange()) {
964 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
965 applyCommandToComposite(cmd);
969 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
971 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
972 applyCommandToComposite(cmd);
975 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
977 DOMString value = element->getAttribute(attribute);
980 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
981 applyCommandToComposite(cmd);
984 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
986 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
987 applyCommandToComposite(cmd);
990 void CompositeEditCommand::rebalanceWhitespace()
992 Selection selection = endingSelection();
993 if (selection.isCaretOrRange()) {
994 EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
995 applyCommandToComposite(startCmd);
996 if (selection.isRange()) {
997 EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
998 applyCommandToComposite(endCmd);
1003 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
1005 if (!textNode || !textNode->renderer() || start >= end)
1008 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
1009 InlineTextBox *box = textRenderer->firstTextBox();
1011 // whole text node is empty
1012 removeNode(textNode);
1016 long length = textNode->length();
1017 if (start >= length || end > length)
1021 InlineTextBox *prevBox = 0;
1022 DOMStringImpl *str = 0;
1024 // This loop structure works to process all gaps preceding a box,
1025 // and also will look at the gap after the last box.
1026 while (prevBox || box) {
1027 int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
1029 // No more chance for any intersections
1032 int gapEnd = box ? box->m_start : length;
1033 bool indicesIntersect = start <= gapEnd && end >= gapStart;
1034 int gapLen = gapEnd - gapStart;
1035 if (indicesIntersect && gapLen > 0) {
1036 gapStart = kMax(gapStart, start);
1037 gapEnd = kMin(gapEnd, end);
1039 str = textNode->string()->substring(start, end - start);
1042 // remove text in the gap
1043 str->remove(gapStart - start - removed, gapLen);
1049 box = box->nextTextBox();
1053 // Replace the text between start and end with our pruned version.
1055 replaceTextInNode(textNode, start, end - start, str);
1058 // Assert that we are not going to delete all of the text in the node.
1059 // If we were, that should have been done above with the call to
1060 // removeNode and return.
1061 ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
1062 deleteTextFromNode(textNode, start, end - start);
1068 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
1070 if (start.isNull() || end.isNull())
1073 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
1076 NodeImpl *node = start.node();
1078 NodeImpl *next = node->traverseNextNode();
1080 if (node->isTextNode()) {
1081 TextImpl *textNode = static_cast<TextImpl *>(node);
1082 bool isStartNode = node == start.node();
1083 bool isEndNode = node == end.node();
1084 int startOffset = isStartNode ? start.offset() : 0;
1085 int endOffset = isEndNode ? end.offset() : textNode->length();
1086 deleteInsignificantText(textNode, startOffset, endOffset);
1089 if (node == end.node())
1095 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
1097 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(StayInBlock);
1098 deleteInsignificantText(pos, end);
1101 NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
1106 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
1108 NodeImpl *placeholder = createBlockPlaceholderElement(document());
1109 appendNode(placeholder, node);
1113 NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
1118 ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow());
1120 NodeImpl *placeholder = createBlockPlaceholderElement(document());
1121 insertNodeAt(placeholder, pos.node(), pos.offset());
1125 NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
1130 document()->updateLayout();
1132 RenderObject *renderer = node->renderer();
1133 if (!renderer || !renderer->isBlockFlow())
1136 // append the placeholder to make sure it follows
1137 // any unrendered blocks
1138 if (renderer->height() == 0) {
1139 return appendBlockPlaceholder(node);
1145 bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
1147 NodeImpl *placeholder = findBlockPlaceholder(node);
1149 removeNode(placeholder);
1155 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
1160 document()->updateLayout();
1162 RenderObject *renderer = node->renderer();
1163 if (!renderer || !renderer->isBlockFlow())
1166 for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
1167 if (checkMe->isElementNode()) {
1168 ElementImpl *element = static_cast<ElementImpl *>(checkMe);
1169 if (element->enclosingBlockFlowElement() == node &&
1170 element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
1179 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
1184 document()->updateLayout();
1186 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
1187 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
1188 VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
1189 Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream(StayInBlock);
1190 Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream(StayInBlock);
1191 Position beforeParagraphStart = paragraphStart.upstream(DoNotStayInBlock);
1193 // Perform some checks to see if we need to perform work in this function.
1194 if (paragraphStart.node()->isBlockFlow()) {
1195 if (paragraphEnd.node()->isBlockFlow()) {
1196 if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
1197 // If the paragraph end is a descendant of paragraph start, then we need to run
1198 // the rest of this function. If not, we can bail here.
1202 else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
1203 // The paragraph end is in another block that is an ancestor of the paragraph start.
1204 // We can bail as we have a full block to work with.
1205 ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
1208 else if (isEndOfDocument(visibleParagraphEnd)) {
1209 // At the end of the document. We can bail here as well.
1214 // Create the block to insert. Most times, this will be a shallow clone of the block containing
1215 // the start of the selection (the start block), except for two cases:
1216 // 1) When the start block is a body element.
1217 // 2) When the start block is a mail blockquote and we are not in a position to insert
1218 // the new block as a peer of the start block. This prevents creating an unwanted
1219 // additional level of quoting.
1220 NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
1221 NodeImpl *newBlock = 0;
1222 if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
1223 newBlock = createDefaultParagraphElement(document());
1225 newBlock = startBlock->cloneNode(false);
1227 NodeImpl *moveNode = paragraphStart.node();
1228 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
1229 moveNode = moveNode->traverseNextNode();
1230 NodeImpl *endNode = paragraphEnd.node();
1232 if (paragraphStart.node()->id() == ID_BODY) {
1233 insertNodeAt(newBlock, paragraphStart.node(), 0);
1235 else if (paragraphStart.node()->id() == ID_BR) {
1236 insertNodeAfter(newBlock, paragraphStart.node());
1238 else if (paragraphStart.node()->isBlockFlow()) {
1239 insertNodeBefore(newBlock, paragraphStart.node());
1241 else if (beforeParagraphStart.node()->enclosingBlockFlowElement()->id() != ID_BODY) {
1242 insertNodeAfter(newBlock, beforeParagraphStart.node()->enclosingBlockFlowElement());
1245 insertNodeAfter(newBlock, beforeParagraphStart.node());
1248 while (moveNode && !moveNode->isBlockFlow()) {
1249 NodeImpl *next = moveNode->traverseNextSibling();
1250 removeNode(moveNode);
1251 appendNode(moveNode, newBlock);
1252 if (moveNode == endNode)
1258 static bool isSpecialElement(NodeImpl *n)
1260 if (!n->isHTMLElement())
1263 if (n->id() == ID_A && n->hasAnchor())
1266 if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
1269 RenderObject *renderer = n->renderer();
1271 if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
1274 if (renderer && renderer->style()->isFloating())
1277 if (renderer && renderer->style()->position() != STATIC)
1283 // This version of the function is meant to be called on positions in a document fragment,
1284 // so it does not check for a root editable element, it is assumed these nodes will be put
1285 // somewhere editable in the future
1286 static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
1288 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1290 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1291 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1293 if (isSpecialElement(n))
1300 static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
1302 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1304 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1305 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1307 if (n->rootEditableElement() == NULL)
1309 if (isSpecialElement(n))
1316 static Position positionBeforeNode(NodeImpl *node)
1318 return Position(node->parentNode(), node->nodeIndex());
1321 static Position positionBeforeContainingSpecialElement(const Position& pos)
1323 ASSERT(isFirstVisiblePositionInSpecialElement(pos));
1325 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1327 NodeImpl *outermostSpecialElement = NULL;
1329 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1330 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1332 if (n->rootEditableElement() == NULL)
1334 if (isSpecialElement(n))
1335 outermostSpecialElement = n;
1338 ASSERT(outermostSpecialElement);
1340 Position result = positionBeforeNode(outermostSpecialElement);
1341 if (result.isNull() || !result.node()->rootEditableElement())
1347 static bool isLastVisiblePositionInSpecialElement(const Position& pos)
1349 // make sure to get a range-compliant version of the position
1350 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
1352 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
1354 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
1355 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
1357 if (n->rootEditableElement() == NULL)
1359 if (isSpecialElement(n))
1366 static Position positionAfterNode(NodeImpl *node)
1368 return Position(node->parentNode(), node->nodeIndex() + 1);
1371 static Position positionAfterContainingSpecialElement(const Position& pos)
1373 ASSERT(isLastVisiblePositionInSpecialElement(pos));
1375 // make sure to get a range-compliant version of the position
1376 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
1378 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
1380 NodeImpl *outermostSpecialElement = NULL;
1382 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
1383 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
1385 if (n->rootEditableElement() == NULL)
1387 if (isSpecialElement(n))
1388 outermostSpecialElement = n;
1391 ASSERT(outermostSpecialElement);
1393 Position result = positionAfterNode(outermostSpecialElement);
1394 if (result.isNull() || !result.node()->rootEditableElement())
1400 static Position positionOutsideContainingSpecialElement(const Position &pos)
1402 if (isFirstVisiblePositionInSpecialElement(pos)) {
1403 return positionBeforeContainingSpecialElement(pos);
1404 } else if (isLastVisiblePositionInSpecialElement(pos)) {
1405 return positionAfterContainingSpecialElement(pos);
1411 static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
1413 if (isFirstVisiblePositionInSpecialElement(pos)) {
1414 return positionBeforeContainingSpecialElement(pos);
1420 static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
1422 if (isLastVisiblePositionInSpecialElement(pos)) {
1423 return positionAfterContainingSpecialElement(pos);
1429 //==========================================================================================
1430 // Concrete commands
1431 //------------------------------------------------------------------------------------------
1432 // AppendNodeCommand
1434 AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
1435 : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
1437 ASSERT(m_appendChild);
1438 m_appendChild->ref();
1440 ASSERT(m_parentNode);
1441 m_parentNode->ref();
1444 AppendNodeCommand::~AppendNodeCommand()
1446 ASSERT(m_appendChild);
1447 m_appendChild->deref();
1449 ASSERT(m_parentNode);
1450 m_parentNode->deref();
1453 void AppendNodeCommand::doApply()
1455 ASSERT(m_appendChild);
1456 ASSERT(m_parentNode);
1458 int exceptionCode = 0;
1459 m_parentNode->appendChild(m_appendChild, exceptionCode);
1460 ASSERT(exceptionCode == 0);
1463 void AppendNodeCommand::doUnapply()
1465 ASSERT(m_appendChild);
1466 ASSERT(m_parentNode);
1467 ASSERT(state() == Applied);
1469 int exceptionCode = 0;
1470 m_parentNode->removeChild(m_appendChild, exceptionCode);
1471 ASSERT(exceptionCode == 0);
1474 //------------------------------------------------------------------------------------------
1475 // ApplyStyleCommand
1477 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
1478 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
1484 ApplyStyleCommand::~ApplyStyleCommand()
1490 void ApplyStyleCommand::doApply()
1492 switch (m_propertyLevel) {
1493 case PropertyDefault: {
1494 // apply the block-centric properties of the style
1495 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
1497 applyBlockStyle(blockStyle);
1498 // apply any remaining styles to the inline elements
1499 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
1500 if (blockStyle->length() < m_style->length()) {
1501 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
1503 applyRelativeFontStyleChange(inlineStyle);
1504 blockStyle->diff(inlineStyle);
1505 applyInlineStyle(inlineStyle);
1506 inlineStyle->deref();
1508 blockStyle->deref();
1511 case ForceBlockProperties:
1512 // Force all properties to be applied as block styles.
1513 applyBlockStyle(m_style);
1517 setEndingSelectionNeedsLayout();
1520 EditAction ApplyStyleCommand::editingAction() const
1522 return m_editingAction;
1525 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
1527 // update document layout once before removing styles
1528 // so that we avoid the expense of updating before each and every call
1529 // to check a computed style
1530 document()->updateLayout();
1532 // get positions we want to use for applying style
1533 Position start(endingSelection().start());
1534 Position end(endingSelection().end());
1536 // remove current values, if any, of the specified styles from the blocks
1537 // NOTE: tracks the previous block to avoid repeated processing
1538 // Also, gather up all the nodes we want to process in a QPtrList before
1539 // doing anything. This averts any bugs iterating over these nodes
1540 // once you start removing and applying style.
1541 NodeImpl *beyondEnd = end.node()->traverseNextNode();
1542 QPtrList<NodeImpl> nodes;
1543 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
1546 NodeImpl *prevBlock = 0;
1547 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1548 NodeImpl *block = it.current()->enclosingBlockFlowElement();
1549 if (block != prevBlock && block->isHTMLElement()) {
1550 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
1555 // apply specified styles to the block flow elements in the selected range
1557 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1558 NodeImpl *node = it.current();
1559 if (node->renderer()) {
1560 NodeImpl *block = node->enclosingBlockFlowElement();
1561 if (block != prevBlock) {
1562 addBlockStyleIfNeeded(style, node);
1569 #define NoFontDelta (0.0f)
1570 #define MinimumFontSize (0.1f)
1572 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
1574 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1575 // Explicit font size overrides any delta.
1576 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1580 // Get the adjustment amount out of the style.
1581 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1585 float adjustment = NoFontDelta;
1586 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
1587 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
1588 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
1589 // Only PX handled now. If we handle more types in the future, perhaps
1590 // a switch statement here would be more appropriate.
1591 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
1594 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1596 if (adjustment == NoFontDelta)
1599 // Adjust to the positions we want to use for applying style.
1600 Selection selection = endingSelection();
1601 Position start(selection.start().downstream(StayInBlock));
1602 Position end(selection.end().upstream(StayInBlock));
1603 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1604 Position swap = start;
1609 // Join up any adjacent text nodes.
1610 if (start.node()->isTextNode()) {
1611 joinChildTextNodes(start.node()->parentNode(), start, end);
1612 selection = endingSelection();
1613 start = selection.start();
1614 end = selection.end();
1616 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
1617 joinChildTextNodes(end.node()->parentNode(), start, end);
1618 selection = endingSelection();
1619 start = selection.start();
1620 end = selection.end();
1623 // Split the start text nodes if needed to apply style.
1624 bool splitStart = splitTextAtStartIfNeeded(start, end);
1626 start = endingSelection().start();
1627 end = endingSelection().end();
1629 bool splitEnd = splitTextAtEndIfNeeded(start, end);
1631 start = endingSelection().start();
1632 end = endingSelection().end();
1635 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
1636 start = start.upstream(StayInBlock); // Move upstream to ensure we do not add redundant spans.
1637 NodeImpl *startNode = start.node();
1638 if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
1639 startNode = startNode->traverseNextNode();
1641 // Store away font size before making any changes to the document.
1642 // This ensures that changes to one node won't effect another.
1643 QMap<const NodeImpl *,float> startingFontSizes;
1644 for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
1645 startingFontSizes.insert(node, computedFontSize(node));
1647 // These spans were added by us. If empty after font size changes, they can be removed.
1648 QPtrList<NodeImpl> emptySpans;
1650 NodeImpl *lastStyledNode = 0;
1651 for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
1652 HTMLElementImpl *elem = 0;
1653 if (node->isHTMLElement()) {
1654 // Only work on fully selected nodes.
1655 if (!nodeFullySelected(node, start, end))
1657 elem = static_cast<HTMLElementImpl *>(node);
1659 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
1660 // Last styled node was not parent node of this text node, but we wish to style this
1661 // text node. To make this possible, add a style span to surround this text node.
1662 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1663 insertNodeBefore(elem, node);
1664 surroundNodeRangeWithElement(node, node, elem);
1667 // Only handle HTML elements and text nodes.
1670 lastStyledNode = node;
1672 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
1673 float currentFontSize = computedFontSize(node);
1674 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
1675 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1676 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
1677 currentFontSize = computedFontSize(node);
1679 if (currentFontSize != desiredFontSize) {
1680 QString desiredFontSizeString = QString::number(desiredFontSize);
1681 desiredFontSizeString += "px";
1682 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
1683 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
1685 if (inlineStyleDecl->length() == 0) {
1686 removeNodeAttribute(elem, ATTR_STYLE);
1687 if (isEmptyStyleSpan(elem))
1688 emptySpans.append(elem);
1692 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
1693 removeNodePreservingChildren(it.current());
1697 #undef MinimumFontSize
1699 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
1701 // adjust to the positions we want to use for applying style
1702 Position start(endingSelection().start().downstream(StayInBlock).equivalentRangeCompliantPosition());
1703 Position end(endingSelection().end().upstream(StayInBlock));
1705 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1706 Position swap = start;
1711 // update document layout once before removing styles
1712 // so that we avoid the expense of updating before each and every call
1713 // to check a computed style
1714 document()->updateLayout();
1716 // split the start node and containing element if the selection starts inside of it
1717 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
1719 start = endingSelection().start();
1720 end = endingSelection().end();
1723 // split the end node and containing element if the selection ends inside of it
1724 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
1725 start = endingSelection().start();
1726 end = endingSelection().end();
1728 // Remove style from the selection.
1729 // Use the upstream position of the start for removing style.
1730 // This will ensure we remove all traces of the relevant styles from the selection
1731 // and prevent us from adding redundant ones, as described in:
1732 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
1733 removeInlineStyle(style, start.upstream(StayInBlock), end);
1734 start = endingSelection().start();
1735 end = endingSelection().end();
1738 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
1740 start = endingSelection().start();
1741 end = endingSelection().end();
1746 mergeEndWithNextIfIdentical(start, end);
1747 start = endingSelection().start();
1748 end = endingSelection().end();
1751 // update document layout once before running the rest of the function
1752 // so that we avoid the expense of updating before each and every call
1753 // to check a computed style
1754 document()->updateLayout();
1756 if (start.node() == end.node()) {
1757 // simple case...start and end are the same node
1758 addInlineStyleIfNeeded(style, start.node(), end.node());
1761 NodeImpl *node = start.node();
1762 if (start.offset() >= start.node()->caretMaxOffset())
1763 node = node->traverseNextNode();
1765 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1766 NodeImpl *runStart = node;
1768 NodeImpl *next = node->traverseNextNode();
1769 // Break if node is the end node, or if the next node does not fit in with
1770 // the current group.
1771 if (node == end.node() ||
1772 runStart->parentNode() != next->parentNode() ||
1773 (next->isHTMLElement() && next->id() != ID_BR) ||
1774 (next->renderer() && !next->renderer()->isInline()))
1778 // Now apply style to the run we found.
1779 addInlineStyleIfNeeded(style, runStart, node);
1781 if (node == end.node())
1783 node = node->traverseNextNode();
1787 if (splitStart || splitEnd) {
1788 cleanUpEmptyStyleSpans(start, end);
1792 //------------------------------------------------------------------------------------------
1793 // ApplyStyleCommand: style-removal helpers
1795 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1797 QValueListConstIterator<CSSProperty> end;
1798 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1799 switch ((*it).id()) {
1800 case CSS_PROP_FONT_WEIGHT:
1801 if (elem->id() == ID_B)
1804 case CSS_PROP_FONT_STYLE:
1805 if (elem->id() == ID_I)
1813 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
1815 // This node can be removed.
1816 // EDIT FIXME: This does not handle the case where the node
1817 // has attributes. But how often do people add attributes to <B> tags?
1818 // Not so often I think.
1820 removeNodePreservingChildren(elem);
1823 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1828 if (elem->id() != ID_FONT)
1831 int exceptionCode = 0;
1832 QValueListConstIterator<CSSProperty> end;
1833 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1834 switch ((*it).id()) {
1835 case CSS_PROP_COLOR:
1836 elem->removeAttribute(ATTR_COLOR, exceptionCode);
1837 ASSERT(exceptionCode == 0);
1839 case CSS_PROP_FONT_FAMILY:
1840 elem->removeAttribute(ATTR_FACE, exceptionCode);
1841 ASSERT(exceptionCode == 0);
1843 case CSS_PROP_FONT_SIZE:
1844 elem->removeAttribute(ATTR_SIZE, exceptionCode);
1845 ASSERT(exceptionCode == 0);
1850 if (isEmptyFontTag(elem))
1851 removeNodePreservingChildren(elem);
1854 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1859 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
1863 QValueListConstIterator<CSSProperty> end;
1864 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1865 int propertyID = (*it).id();
1866 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
1869 removeCSSProperty(decl, propertyID);
1874 if (isEmptyStyleSpan(elem))
1875 removeNodePreservingChildren(elem);
1878 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1880 ASSERT(start.isNotNull());
1881 ASSERT(end.isNotNull());
1882 ASSERT(start.node()->inDocument());
1883 ASSERT(end.node()->inDocument());
1884 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1888 static bool hasTextDecorationProperty(NodeImpl *node)
1890 if (!node->isElementNode())
1893 ElementImpl *element = static_cast<ElementImpl *>(node);
1894 CSSComputedStyleDeclarationImpl style(element);
1896 CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
1900 DOMString valueText(value->cssText());
1902 if (strcasecmp(valueText,"none") != 0)
1909 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
1911 NodeImpl *result = NULL;
1913 for (NodeImpl *n = node; n; n = n->parentNode()) {
1914 if (hasTextDecorationProperty(n))
1921 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
1924 ASSERT(node->isElementNode());
1926 // non-html elements not handled yet
1927 if (!node->isHTMLElement())
1930 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1931 CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
1936 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1937 CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
1939 CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1940 if (property && strcasecmp(property->cssText(), "none") != 0) {
1941 removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
1946 return textDecorationStyle;
1949 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
1952 ASSERT(node->isElementNode());
1954 // non-html elements not handled yet
1955 if (!node->isHTMLElement())
1958 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1959 CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
1960 ASSERT(computedStyle);
1962 computedStyle->ref();
1964 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1965 CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
1968 CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1969 if (property && strcasecmp(property->cssText(), "none") != 0) {
1971 CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
1974 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
1975 applyTextDecorationStyle(node, newStyle);
1981 computedStyle->deref();
1983 return textDecorationStyle;
1986 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
1990 if (!style || !style->cssText().length())
1993 if (node->isTextNode()) {
1994 HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1995 insertNodeBefore(styleSpan, node);
1996 surroundNodeRangeWithElement(node, node, styleSpan);
2000 if (!node->isElementNode())
2003 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
2005 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2006 if (styleChange.cssStyle().length() > 0) {
2007 DOMString cssText = styleChange.cssStyle();
2008 CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
2010 cssText += decl->cssText();
2011 setNodeAttribute(element, ATTR_STYLE, cssText);
2015 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
2017 NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
2019 if (highestAncestor) {
2020 NodeImpl *nextCurrent;
2021 NodeImpl *nextChild;
2022 for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
2027 CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
2031 for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
2032 nextChild = child->nextSibling();
2034 if (node == child) {
2035 nextCurrent = child;
2036 } else if (node->isAncestor(child)) {
2037 applyTextDecorationStyle(child, decoration);
2038 nextCurrent = child;
2040 applyTextDecorationStyle(child, decoration);
2045 decoration->deref();
2050 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
2052 // We need to work in two passes. First we push down any inline
2053 // styles that set text decoration. Then we look for any remaining
2054 // styles (caused by stylesheets) and explicitly negate text
2055 // decoration while pushing down.
2057 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
2058 document()->updateLayout();
2059 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
2061 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
2062 document()->updateLayout();
2063 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
2066 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
2068 ASSERT(start.isNotNull());
2069 ASSERT(end.isNotNull());
2070 ASSERT(start.node()->inDocument());
2071 ASSERT(end.node()->inDocument());
2072 ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
2074 CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
2076 if (textDecorationSpecialProperty) {
2077 pushDownTextDecorationStyleAtBoundaries(start.downstream(StayInBlock), end.upstream(StayInBlock));
2078 style = style->copy();
2079 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
2082 // The s and e variables store the positions used to set the ending selection after style removal
2083 // takes place. This will help callers to recognize when either the start node or the end node
2084 // are removed from the document during the work of this function.
2088 NodeImpl *node = start.node();
2090 NodeImpl *next = node->traverseNextNode();
2091 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
2092 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
2093 NodeImpl *prev = elem->traversePreviousNodePostOrder();
2094 NodeImpl *next = elem->traverseNextNode();
2095 if (isHTMLStyleNode(style, elem)) {
2096 removeHTMLStyleNode(elem);
2099 removeHTMLFontStyle(style, elem);
2100 removeCSSStyle(style, elem);
2102 if (!elem->inDocument()) {
2103 if (s.node() == elem) {
2104 // Since elem must have been fully selected, and it is at the start
2105 // of the selection, it is clear we can set the new s offset to 0.
2106 ASSERT(s.offset() <= s.node()->caretMinOffset());
2107 s = Position(next, 0);
2109 if (e.node() == elem) {
2110 // Since elem must have been fully selected, and it is at the end
2111 // of the selection, it is clear we can set the new e offset to
2112 // the max range offset of prev.
2113 ASSERT(e.offset() >= maxRangeOffset(e.node()));
2114 e = Position(prev, maxRangeOffset(prev));
2118 if (node == end.node())
2124 if (textDecorationSpecialProperty) {
2128 ASSERT(s.node()->inDocument());
2129 ASSERT(e.node()->inDocument());
2130 setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
2133 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
2136 ASSERT(node->isElementNode());
2138 Position pos = Position(node, node->childNodeCount()).upstream(DoNotStayInBlock);
2139 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
2140 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
2143 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
2146 ASSERT(node->isElementNode());
2148 Position pos = Position(node, node->childNodeCount()).upstream(DoNotStayInBlock);
2149 bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
2150 bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
2152 return isFullyBeforeStart || isFullyAfterEnd;
2156 //------------------------------------------------------------------------------------------
2157 // ApplyStyleCommand: style-application helpers
2159 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
2161 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
2162 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
2163 TextImpl *text = static_cast<TextImpl *>(start.node());
2164 splitTextNode(text, start.offset());
2165 setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2171 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
2173 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
2174 TextImpl *text = static_cast<TextImpl *>(end.node());
2175 splitTextNode(text, end.offset());
2177 NodeImpl *prevNode = text->previousSibling();
2179 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
2181 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
2187 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
2189 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
2190 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
2191 TextImpl *text = static_cast<TextImpl *>(start.node());
2192 splitTextNodeContainingElement(text, start.offset());
2194 setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2200 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
2202 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
2203 TextImpl *text = static_cast<TextImpl *>(end.node());
2204 splitTextNodeContainingElement(text, end.offset());
2206 NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
2208 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
2210 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
2216 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
2218 // check that tag name and all attribute names and values are identical
2220 if (!first->isElementNode())
2223 if (!second->isElementNode())
2226 ElementImpl *firstElement = static_cast<ElementImpl *>(first);
2227 ElementImpl *secondElement = static_cast<ElementImpl *>(second);
2229 if (firstElement->id() != secondElement->id())
2232 NamedAttrMapImpl *firstMap = firstElement->attributes();
2233 NamedAttrMapImpl *secondMap = secondElement->attributes();
2235 unsigned firstLength = firstMap->length();
2237 if (firstLength != secondMap->length())
2240 for (unsigned i = 0; i < firstLength; i++) {
2241 DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
2242 DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
2244 if (!secondAttribute || attribute->value() != secondAttribute->value())
2251 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
2253 NodeImpl *startNode = start.node();
2254 long startOffset = start.offset();
2256 if (start.node()->isAtomicNode()) {
2257 if (start.offset() != 0)
2260 if (start.node()->previousSibling())
2263 startNode = start.node()->parent();
2267 if (!startNode->isElementNode())
2270 if (startOffset != 0)
2273 NodeImpl *previousSibling = startNode->previousSibling();
2275 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
2276 ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
2277 ElementImpl *element = static_cast<ElementImpl *>(startNode);
2278 NodeImpl *startChild = element->firstChild();
2280 mergeIdenticalElements(previousElement, element);
2282 long startOffsetAdjustment = startChild->nodeIndex();
2283 long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
2285 setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
2286 Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2294 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
2296 NodeImpl *endNode = end.node();
2297 int endOffset = end.offset();
2299 if (endNode->isAtomicNode()) {
2300 if (endOffset < endNode->caretMaxOffset())
2303 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
2304 if (end.node()->nextSibling())
2307 endNode = end.node()->parent();
2308 endOffset = parentLastOffset;
2311 if (!endNode->isElementNode() || endNode->id() == ID_BR)
2314 NodeImpl *nextSibling = endNode->nextSibling();
2316 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
2317 ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
2318 ElementImpl *element = static_cast<ElementImpl *>(endNode);
2319 NodeImpl *nextChild = nextElement->firstChild();
2321 mergeIdenticalElements(element, nextElement);
2323 NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
2326 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
2328 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
2329 Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
2336 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
2339 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
2342 if (node && isEmptyStyleSpan(node->previousSibling())) {
2343 removeNodePreservingChildren(node->previousSibling());
2346 if (start.node() == end.node()) {
2347 if (start.node()->isTextNode()) {
2348 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2349 if (isEmptyStyleSpan(cur)) {
2350 removeNodePreservingChildren(cur);
2357 if (start.node()->isTextNode()) {
2358 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
2359 if (isEmptyStyleSpan(cur)) {
2360 removeNodePreservingChildren(cur);
2366 if (end.node()->isTextNode()) {
2367 for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2368 if (isEmptyStyleSpan(cur)) {
2369 removeNodePreservingChildren(cur);
2376 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
2378 if (node && isEmptyStyleSpan(node->nextSibling())) {
2379 removeNodePreservingChildren(node->nextSibling());
2383 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
2389 NodeImpl *node = startNode;
2391 NodeImpl *next = node->traverseNextNode();
2392 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
2394 appendNode(node, element);
2396 if (node == endNode)
2402 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
2404 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
2409 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2413 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2414 if (styleChange.cssStyle().length() > 0) {
2415 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
2416 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2417 DOMString cssText = styleChange.cssStyle();
2418 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
2420 cssText += decl->cssText();
2421 setNodeAttribute(block, ATTR_STYLE, cssText);
2425 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
2427 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2428 int exceptionCode = 0;
2431 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
2433 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
2434 ElementImpl *fontElement = createFontElement(document());
2435 ASSERT(exceptionCode == 0);
2436 insertNodeBefore(fontElement, startNode);
2437 if (styleChange.applyFontColor())
2438 fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
2439 if (styleChange.applyFontFace())
2440 fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
2441 if (styleChange.applyFontSize())
2442 fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
2443 surroundNodeRangeWithElement(startNode, endNode, fontElement);
2446 if (styleChange.cssStyle().length() > 0) {
2447 ElementImpl *styleElement = createStyleSpanElement(document());
2448 styleElement->ref();
2449 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
2450 insertNodeBefore(styleElement, startNode);
2451 styleElement->deref();
2452 surroundNodeRangeWithElement(startNode, endNode, styleElement);
2455 if (styleChange.applyBold()) {
2456 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
2457 ASSERT(exceptionCode == 0);
2458 insertNodeBefore(boldElement, startNode);
2459 surroundNodeRangeWithElement(startNode, endNode, boldElement);
2462 if (styleChange.applyItalic()) {
2463 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
2464 ASSERT(exceptionCode == 0);
2465 insertNodeBefore(italicElement, startNode);
2466 surroundNodeRangeWithElement(startNode, endNode, italicElement);
2470 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
2477 Position pos(const_cast<NodeImpl *>(node), 0);
2478 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
2481 computedStyle->ref();
2483 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
2486 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
2490 computedStyle->deref();
2494 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
2499 Position newStart = start;
2500 Position newEnd = end;
2502 NodeImpl *child = node->firstChild();
2504 NodeImpl *next = child->nextSibling();
2505 if (child->isTextNode() && next && next->isTextNode()) {
2506 TextImpl *childText = static_cast<TextImpl *>(child);
2507 TextImpl *nextText = static_cast<TextImpl *>(next);
2508 if (next == start.node())
2509 newStart = Position(childText, childText->length() + start.offset());
2510 if (next == end.node())
2511 newEnd = Position(childText, childText->length() + end.offset());
2512 DOMString textToMove = nextText->data();
2513 insertTextIntoNode(childText, childText->length(), textToMove);
2515 // don't move child node pointer. it may want to merge with more text nodes.
2518 child = child->nextSibling();
2522 setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
2525 //------------------------------------------------------------------------------------------
2526 // DeleteFromTextNodeCommand
2528 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
2529 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
2532 ASSERT(m_offset >= 0);
2533 ASSERT(m_offset < (long)m_node->length());
2534 ASSERT(m_count >= 0);
2539 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
2545 void DeleteFromTextNodeCommand::doApply()
2549 int exceptionCode = 0;
2550 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
2551 ASSERT(exceptionCode == 0);
2553 m_node->deleteData(m_offset, m_count, exceptionCode);
2554 ASSERT(exceptionCode == 0);
2557 void DeleteFromTextNodeCommand::doUnapply()
2560 ASSERT(!m_text.isEmpty());
2562 int exceptionCode = 0;
2563 m_node->insertData(m_offset, m_text, exceptionCode);
2564 ASSERT(exceptionCode == 0);
2567 //------------------------------------------------------------------------------------------
2568 // DeleteSelectionCommand
2570 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
2571 : CompositeEditCommand(document),
2572 m_hasSelectionToDelete(false),
2573 m_smartDelete(smartDelete),
2574 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2582 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
2583 : CompositeEditCommand(document),
2584 m_hasSelectionToDelete(true),
2585 m_smartDelete(smartDelete),
2586 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2587 m_selectionToDelete(selection),
2595 void DeleteSelectionCommand::initializePositionData()
2598 // Handle setting some basic positions
2600 Position start = m_selectionToDelete.start();
2601 start = positionOutsideContainingSpecialElement(start);
2602 Position end = m_selectionToDelete.end();
2603 end = positionOutsideContainingSpecialElement(end);
2605 m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream(StayInBlock));
2606 m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream(StayInBlock));
2607 m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream(StayInBlock));
2608 m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream(StayInBlock));
2611 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
2613 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
2614 // NOTE: Workaround for bug <rdar://problem/4103339> is to avoid calculating trailingWhitespacePosition
2615 // if the m_downstreamEnd is at the end of a paragraph.
2616 if (!isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY)))
2617 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2619 if (m_smartDelete) {
2621 // skip smart delete if the selection to delete already starts or ends with whitespace
2622 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
2623 bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2624 if (!skipSmartDelete)
2625 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2627 // extend selection upstream if there is whitespace there
2628 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
2629 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
2630 VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
2631 pos = visiblePos.deepEquivalent();
2632 // Expand out one character upstream for smart delete and recalculate
2633 // positions based on this change.
2634 m_upstreamStart = pos.upstream(StayInBlock);
2635 m_downstreamStart = pos.downstream(StayInBlock);
2636 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
2639 // trailing whitespace is only considered for smart delete if there is no leading
2640 // whitespace, as in the case where you double-click the first word of a paragraph.
2641 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
2642 // Expand out one character downstream for smart delete and recalculate
2643 // positions based on this change.
2644 pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
2645 m_upstreamEnd = pos.upstream(StayInBlock);
2646 m_downstreamEnd = pos.downstream(StayInBlock);
2647 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2651 m_trailingWhitespaceValid = true;
2654 // Handle setting start and end blocks and the start node.
2656 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
2657 m_startBlock->ref();
2658 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
2660 m_startNode = m_upstreamStart.node();
2664 // Handle detecting if the line containing the selection end is itself fully selected.
2665 // This is one of the tests that determines if block merging of content needs to be done.
2667 VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
2668 if (isFirstVisiblePositionInParagraph(visibleEnd) || isLastVisiblePositionInParagraph(visibleEnd)) {
2669 Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
2670 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
2671 m_mergeBlocksAfterDelete = false;
2674 debugPosition("m_upstreamStart ", m_upstreamStart);
2675 debugPosition("m_downstreamStart ", m_downstreamStart);
2676 debugPosition("m_upstreamEnd ", m_upstreamEnd);
2677 debugPosition("m_downstreamEnd ", m_downstreamEnd);
2678 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
2679 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
2680 debugNode( "m_startBlock ", m_startBlock);
2681 debugNode( "m_endBlock ", m_endBlock);
2682 debugNode( "m_startNode ", m_startNode);
2685 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
2687 // This code makes sure a line does not disappear when deleting in this case:
2688 // <p>foo</p>bar<p>baz</p>
2689 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
2690 // It needs to be held open by inserting a placeholder.
2692 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
2694 // The checks below detect the case where the selection contains content in an ancestor block
2695 // surrounded by child blocks.
2697 NodeImpl *upstreamBlock = m_upstreamStart.node()->enclosingBlockFlowElement();
2698 NodeImpl *beforeUpstreamBlock = m_upstreamStart.upstream(DoNotStayInBlock).node()->enclosingBlockFlowElement();
2700 if (upstreamBlock != beforeUpstreamBlock &&
2701 beforeUpstreamBlock->isAncestor(upstreamBlock) &&
2702 upstreamBlock != m_upstreamStart.node()) {
2703 NodeImpl *downstreamBlock = m_downstreamEnd.node()->enclosingBlockFlowElement();
2704 NodeImpl *afterDownstreamBlock = m_downstreamEnd.downstream(DoNotStayInBlock).node()->enclosingBlockFlowElement();
2706 if ((afterDownstreamBlock != downstreamBlock && afterDownstreamBlock != upstreamBlock) ||
2707 (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY)))) {
2708 NodeImpl *block = createDefaultParagraphElement(document());
2709 insertNodeBefore(block, m_upstreamStart.node());
2710 addBlockPlaceholderIfNeeded(block);
2711 m_endingPosition = Position(block, 0);
2716 void DeleteSelectionCommand::saveTypingStyleState()
2718 // Figure out the typing style in effect before the delete is done.
2719 // FIXME: Improve typing style.
2720 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2721 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
2722 computedStyle->ref();
2723 m_typingStyle = computedStyle->copyInheritableProperties();
2724 m_typingStyle->ref();
2725 computedStyle->deref();
2728 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
2730 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
2731 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
2732 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
2733 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
2734 if (isBROnLineByItself) {
2735 removeNode(m_downstreamStart.node());
2736 m_endingPosition = m_upstreamStart;
2737 m_mergeBlocksAfterDelete = false;
2741 // Not a special-case delete per se, but we can detect that the merging of content between blocks
2742 // should not be done.
2743 if (upstreamStartIsBR && downstreamStartIsBR)
2744 m_mergeBlocksAfterDelete = false;
2749 void DeleteSelectionCommand::setStartNode(NodeImpl *node)
2751 NodeImpl *old = m_startNode;
2759 void DeleteSelectionCommand::handleGeneralDelete()
2761 int startOffset = m_upstreamStart.offset();
2762 VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
2763 bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
2765 // Handle some special cases where the selection begins and ends on specific visible units.
2766 // Sometimes a node that is actually selected needs to be retained in order to maintain
2767 // user expectations for the delete operation. Here is an example:
2768 // 1. Open a new Blot or Mail document
2769 // 2. hit Return ten times or so
2770 // 3. Type a letter (do not hit Return after it)
2771 // 4. Type shift-up-arrow to select the line containing the letter and the previous blank line
2773 // You expect the insertion point to wind up at the start of the line where your selection began.
2774 // Because of the nature of HTML, the editing code needs to perform a special check to get
2775 // this behavior. So:
2776 // If the entire start block is selected, and the selection does not extend to the end of the
2777 // end of a block other than the block containing the selection start, then do not delete the
2778 // start block, otherwise delete the start block.
2779 // A similar case is provided to cover selections starting in BR elements.
2780 if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
2781 setStartNode(m_startNode->traverseNextNode());
2784 if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
2785 // Don't delete the BR element
2786 setStartNode(m_startNode->traverseNextNode());
2788 else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
2789 if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
2790 // Delete all the children of the block, but not the block itself.
2791 setStartNode(m_startBlock->firstChild());
2795 else if (startOffset >= m_startNode->caretMaxOffset() &&
2796 (m_startNode->isAtomicNode() || startOffset == 0)) {
2797 // Move the start node to the next node in the tree since the startOffset is equal to
2798 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
2799 // But don't do this if the node is not atomic - we don't want to move into the first child.
2801 // Also, before moving on, delete any insignificant text that may be present in a text node.
2802 if (m_startNode->isTextNode()) {
2803 // Delete any insignificant text from this node.
2804 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2805 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
2806 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
2809 // shift the start node to the next
2810 setStartNode(m_startNode->traverseNextNode());
2814 // Done adjusting the start. See if we're all done.
2818 if (m_startNode == m_downstreamEnd.node()) {
2819 // The selection to delete is all in one node.
2820 if (!m_startNode->renderer() ||
2821 (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
2823 removeFullySelectedNode(m_startNode);
2824 } else if (m_downstreamEnd.offset() - startOffset > 0) {
2825 if (m_startNode->isTextNode()) {
2826 // in a text node that needs to be trimmed
2827 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2828 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
2829 m_trailingWhitespaceValid = false;
2831 removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
2832 m_endingPosition = m_upstreamStart;
2837 // The selection to delete spans more than one node.
2838 NodeImpl *node = m_startNode;
2840 if (startOffset > 0) {
2841 if (m_startNode->isTextNode()) {
2842 // in a text node that needs to be trimmed
2843 TextImpl *text = static_cast<TextImpl *>(node);
2844 deleteTextFromNode(text, startOffset, text->length() - startOffset);
2845 node = node->traverseNextNode();
2847 node = m_startNode->childNode(startOffset);
2851 // handle deleting all nodes that are completely selected
2852 while (node && node != m_downstreamEnd.node()) {
2853 if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
2854 // traverseNextSibling just blew past the end position, so stop deleting
2856 } else if (!m_downstreamEnd.node()->isAncestor(node)) {
2857 NodeImpl *nextNode = node->traverseNextSibling();
2858 // if we just removed a node from the end container, update end position so the
2859 // check above will work
2860 if (node->parentNode() == m_downstreamEnd.node()) {
2861 ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
2862 m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
2864 removeFullySelectedNode(node);
2867 NodeImpl *n = node->lastChild();
2868 while (n && n->lastChild())
2870 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2871 removeFullySelectedNode(node);
2872 m_trailingWhitespaceValid = false;
2876 node = node->traverseNextNode();
2882 if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
2883 if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
2884 // need to delete whole node
2885 // we can get here if this is the last node in the block
2886 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
2887 if (m_upstreamStart.node() == m_downstreamEnd.node() ||
2888 m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2889 m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
2892 removeFullySelectedNode(m_downstreamEnd.node());
2893 m_trailingWhitespaceValid = false;
2895 if (m_downstreamEnd.node()->isTextNode()) {
2896 // in a text node that needs to be trimmed
2897 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
2898 if (m_downstreamEnd.offset() > 0) {
2899 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
2900 m_downstreamEnd = Position(text, 0);
2901 m_trailingWhitespaceValid = false;
2905 if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2906 NodeImpl *n = m_upstreamStart.node();
2907 while (n && n->parentNode() != m_downstreamEnd.node())
2908 n = n->parentNode();
2910 offset = n->nodeIndex() + 1;
2912 removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
2913 m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
2920 void DeleteSelectionCommand::fixupWhitespace()
2922 document()->updateLayout();
2923 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
2924 LOG(Editing, "replace leading");
2925 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
2926 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
2928 else if (m_trailingWhitespace.isNotNull()) {
2929 if (m_trailingWhitespaceValid) {
2930 if (!m_trailingWhitespace.isRenderedCharacter()) {
2931 LOG(Editing, "replace trailing [valid]");
2932 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
2933 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
2937 Position pos = m_endingPosition.downstream(StayInBlock);
2938 pos = Position(pos.node(), pos.offset() - 1);
2939 if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
2940 LOG(Editing, "replace trailing [invalid]");
2941 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2942 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
2943 // need to adjust ending position since the trailing position is not valid.
2944 m_endingPosition = pos;
2950 // This function moves nodes in the block containing startNode to dstBlock, starting
2951 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
2952 // startNode that appear in document order before startNode are not moved.
2953 // This function is an important helper for deleting selections that cross paragraph
2955 void DeleteSelectionCommand::moveNodesAfterNode()
2957 if (!m_mergeBlocksAfterDelete)
2960 if (m_endBlock == m_startBlock)
2963 NodeImpl *startNode = m_downstreamEnd.node();
2964 NodeImpl *dstNode = m_upstreamStart.node();
2966 if (!startNode->inDocument() || !dstNode->inDocument())
2969 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2970 if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
2971 // Do not move content between parts of a table or list.
2974 // Now that we are about to add content, check to see if a placeholder element
2976 removeBlockPlaceholder(startBlock);
2978 // Move the subtree containing node
2979 NodeImpl *node = startNode->enclosingInlineElement();
2981 // Insert after the subtree containing destNode
2982 NodeImpl *refNode = dstNode->enclosingInlineElement();
2984 // Nothing to do if start is already at the beginning of dstBlock
2985 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
2986 if (startBlock == dstBlock->firstChild())
2990 NodeImpl *rootNode = refNode->rootEditableElement();
2991 while (node && node->isAncestor(startBlock)) {
2992 NodeImpl *moveNode = node;
2993 node = node->nextSibling();
2994 removeNode(moveNode);
2995 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
2996 // Just remove this node, and don't put it back.
2997 // If the BR was not rendered (since it was at the end of a block, for instance),
2998 // putting it back in the document might make it appear, and that is not desirable.
3001 if (refNode == rootNode)
3002 insertNodeAt(moveNode, refNode, 0);
3004 insertNodeAfter(moveNode, refNode);
3006 if (moveNode->id() == ID_BR)
3010 // If the startBlock no longer has any kids, we may need to deal with adding a BR
3011 // to make the layout come out right. Consider this document:
3017 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
3018 // move the contents of the div to the block containing 'One' and delete the div.
3019 // This will have the side effect of moving 'Three' on to the same line as 'One'
3020 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
3021 // This may not be ideal, but it is better than nothing.
3022 document()->updateLayout();
3023 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
3024 removeNode(startBlock);
3025 document()->updateLayout();
3026 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
3027 insertNodeAfter(createBreakElement(document()), refNode);
3032 void DeleteSelectionCommand::calculateEndingPosition()
3034 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
3037 m_endingPosition = m_upstreamStart;
3038 if (m_endingPosition.node()->inDocument())
3041 m_endingPosition = m_downstreamEnd;
3042 if (m_endingPosition.node()->inDocument())
3045 m_endingPosition = Position(m_startBlock, 0);
3046 if (m_endingPosition.node()->inDocument())
3049 m_endingPosition = Position(m_endBlock, 0);
3050 if (m_endingPosition.node()->inDocument())
3053 m_endingPosition = Position(document()->documentElement(), 0);
3056 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
3058 // Compute the difference between the style before the delete and the style now
3059 // after the delete has been done. Set this style on the part, so other editing
3060 // commands being composed with this one will work, and also cache it on the command,
3061 // so the KHTMLPart::appliedEditing can set it after the whole composite command
3063 // FIXME: Improve typing style.
3064 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3065 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
3066 endingStyle.diff(m_typingStyle);
3067 if (!m_typingStyle->length()) {
3068 m_typingStyle->deref();
3071 if (insertedPlaceholder && m_typingStyle) {
3072 // Apply style to the placeholder. This makes sure that the single line in the
3073 // paragraph has the right height, and that the paragraph takes on the style
3074 // of the preceding line and retains it even if you click away, click back, and
3075 // then start typing. In this case, the typing style is applied right now, and
3076 // is not retained until the next typing action.
3078 // FIXME: is this even right? I don't think post-deletion typing style is supposed
3079 // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
3081 Position pastPlaceholder(insertedPlaceholder, 1);
3083 setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
3085 applyStyle(m_typingStyle, EditActionUnspecified);
3087 m_typingStyle->deref();
3090 // Set m_typingStyle as the typing style.
3091 // It's perfectly OK for m_typingStyle to be null.
3092 document()->part()->setTypingStyle(m_typingStyle);
3093 setTypingStyle(m_typingStyle);
3096 void DeleteSelectionCommand::clearTransientState()
3098 m_selectionToDelete.clear();
3099 m_upstreamStart.clear();
3100 m_downstreamStart.clear();
3101 m_upstreamEnd.clear();
3102 m_downstreamEnd.clear();
3103 m_endingPosition.clear();
3104 m_leadingWhitespace.clear();
3105 m_trailingWhitespace.clear();
3108 m_startBlock->deref();
3112 m_endBlock->deref();
3116 m_startNode->deref();
3119 if (m_typingStyle) {
3120 m_typingStyle->deref();
3125 void DeleteSelectionCommand::doApply()
3127 // If selection has not been set to a custom selection when the command was created,
3128 // use the current ending selection.
3129 if (!m_hasSelectionToDelete)
3130 m_selectionToDelete = endingSelection();
3132 if (!m_selectionToDelete.isRange())
3135 // save this to later make the selection with
3136 EAffinity affinity = m_selectionToDelete.startAffinity();
3139 initializePositionData();
3141 if (!m_startBlock || !m_endBlock) {
3142 // Can't figure out what blocks we're in. This can happen if
3143 // the document structure is not what we are expecting, like if
3144 // the document has no body element, or if the editable block
3145 // has been changed to display: inline. Some day it might
3146 // be nice to be able to deal with this, but for now, bail.
3147 clearTransientState();
3151 // if all we are deleting is complete paragraph(s), we need to make
3152 // sure a blank paragraph remains when we are done
3153 bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
3154 isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
3156 // Delete any text that may hinder our ability to fixup whitespace after the detele
3157 deleteInsignificantTextDownstream(m_trailingWhitespace);
3159 saveTypingStyleState();
3160 insertPlaceholderForAncestorBlockContent();
3162 if (!handleSpecialCaseBRDelete())
3163 handleGeneralDelete();
3165 // Do block merge if start and end of selection are in different blocks.
3166 moveNodesAfterNode();
3168 calculateEndingPosition();
3171 // if the m_endingPosition is already a blank paragraph, there is
3172 // no need to force a new one
3173 if (forceBlankParagraph &&
3174 isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
3175 isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
3176 forceBlankParagraph = false;
3179 NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
3180 addBlockPlaceholderIfNeeded(m_endingPosition.node());
3182 calculateTypingStyleAfterDelete(addedPlaceholder);
3183 debugPosition("endingPosition ", m_endingPosition);
3184 setEndingSelection(Selection(m_endingPosition, affinity));
3185 clearTransientState();
3186 rebalanceWhitespace();
3189 EditAction DeleteSelectionCommand::editingAction() const
3191 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
3192 // but in that case there's a TypingCommand that supplies the editingAction(), so
3193 // the Undo menu correctly shows "Undo Typing"
3194 return EditActionCut;
3197 bool DeleteSelectionCommand::preservesTypingStyle() const
3202 //------------------------------------------------------------------------------------------
3203 // InsertIntoTextNode
3205 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
3206 : EditCommand(document), m_node(node), m_offset(offset)
3209 ASSERT(m_offset >= 0);
3210 ASSERT(!text.isEmpty());
3213 m_text = text.copy(); // make a copy to ensure that the string never changes
3216 InsertIntoTextNode::~InsertIntoTextNode()
3222 void InsertIntoTextNode::doApply()
3225 ASSERT(m_offset >= 0);
3226 ASSERT(!m_text.isEmpty());
3228 int exceptionCode = 0;
3229 m_node->insertData(m_offset, m_text, exceptionCode);
3230 ASSERT(exceptionCode == 0);
3233 void InsertIntoTextNode::doUnapply()
3236 ASSERT(m_offset >= 0);
3237 ASSERT(!m_text.isEmpty());
3239 int exceptionCode = 0;
3240 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
3241 ASSERT(exceptionCode == 0);
3244 //------------------------------------------------------------------------------------------
3245 // InsertLineBreakCommand
3247 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
3248 : CompositeEditCommand(document)
3252 bool InsertLineBreakCommand::preservesTypingStyle() const
3257 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
3259 // Insert the BR after the caret position. In the case the
3260 // position is a block, do an append. We don't want to insert
3261 // the BR *after* the block.
3262 Position upstream(pos.upstream(StayInBlock));
3263 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
3264 if (cb == pos.node())
3265 appendNode(node, cb);
3267 insertNodeAfter(node, pos.node());
3270 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
3272 // Insert the BR after the caret position. In the case the
3273 // position is a block, do an append. We don't want to insert
3274 // the BR *before* the block.
3275 Position upstream(pos.upstream(StayInBlock));
3276 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
3277 if (cb == pos.node())
3278 appendNode(node, cb);
3280 insertNodeBefore(node, pos.node());
3283 void InsertLineBreakCommand::doApply()
3286 Selection selection = endingSelection();
3288 ElementImpl *breakNode = createBreakElement(document());
3289 NodeImpl *nodeToInsert = breakNode;
3291 Position pos(selection.start().upstream(StayInBlock));
3293 pos = positionOutsideContainingSpecialElement(pos);
3295 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
3296 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
3297 bool atEndOfBlock = isLastVisiblePositionInBlock(VisiblePosition(pos, selection.startAffinity()));
3300 LOG(Editing, "input newline case 1");
3301 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
3302 // This makes the "real" BR we want to insert appear in the rendering without any
3303 // significant side effects (and no real worries either since you can't arrow past
3305 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
3306 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
3307 insertNodeBefore(nodeToInsert, pos.node());
3310 NodeImpl *next = pos.node()->traverseNextNode();
3311 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
3312 insertNodeAfterPosition(nodeToInsert, pos);
3313 if (hasTrailingBR) {
3314 setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
3316 else if (!document()->inStrictMode()) {
3317 // Insert an "extra" BR at the end of the block.
3318 ElementImpl *extraBreakNode = createBreakElement(document());
3319 insertNodeAfter(extraBreakNode, nodeToInsert);
3320 setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
3325 LOG(Editing, "input newline case 2");
3326 // Insert node before downstream position, and place caret there as well.
3327 Position endingPosition = pos.downstream(StayInBlock);
3328 insertNodeBeforePosition(nodeToInsert, endingPosition);
3329 setEndingSelection(endingPosition, DOWNSTREAM);
3332 LOG(Editing, "input newline case 3");
3333 // Insert BR after this node. Place caret in the position that is downstream
3334 // of the current position, reckoned before inserting the BR in between.
3335 Position endingPosition = pos.downstream(StayInBlock);
3336 insertNodeAfterPosition(nodeToInsert, pos);
3337 setEndingSelection(endingPosition, DOWNSTREAM);
3340 // Split a text node
3341 LOG(Editing, "input newline case 4");
3342 ASSERT(pos.node()->isTextNode());
3345 int exceptionCode = 0;
3346 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
3347 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
3348 deleteTextFromNode(textNode, 0, pos.offset());
3349 insertNodeBefore(textBeforeNode, textNode);
3350 insertNodeBefore(nodeToInsert, textNode);
3351 Position endingPosition = Position(textNode, 0);
3353 // Handle whitespace that occurs after the split
3354 document()->updateLayout();
3355 if (!endingPosition.isRenderedCharacter()) {
3356 // Clear out all whitespace and insert one non-breaking space
3357 deleteInsignificantTextDownstream(endingPosition);
3358 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
3361 setEndingSelection(endingPosition, DOWNSTREAM);
3364 // Handle the case where there is a typing style.
3365 // FIXME: Improve typing style.
3366 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3368 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
3370 if (typingStyle && typingStyle->length() > 0) {
3371 Selection selectionBeforeStyle = endingSelection();
3373 DOM::RangeImpl *rangeAroundNode = document()->createRange();
3375 rangeAroundNode->selectNode(nodeToInsert, exception);
3377 // affinity is not really important since this is a temp selection
3378 // just for calling applyStyle
3379 setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
3380 applyStyle(typingStyle);
3382 setEndingSelection(selectionBeforeStyle);
3385 rebalanceWhitespace();
3388 //------------------------------------------------------------------------------------------
3389 // InsertNodeBeforeCommand
3391 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
3392 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
3394 ASSERT(m_insertChild);
3395 m_insertChild->ref();
3401 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
3403 ASSERT(m_insertChild);
3404 m_insertChild->deref();
3407 m_refChild->deref();
3410 void InsertNodeBeforeCommand::doApply()
3412 ASSERT(m_insertChild);
3414 ASSERT(m_refChild->parentNode());
3416 int exceptionCode = 0;
3417 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
3418 ASSERT(exceptionCode == 0);
3421 void InsertNodeBeforeCommand::doUnapply()
3423 ASSERT(m_insertChild);
3425 ASSERT(m_refChild->parentNode());
3427 int exceptionCode = 0;
3428 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
3429 ASSERT(exceptionCode == 0);
3432 //------------------------------------------------------------------------------------------
3433 // InsertParagraphSeparatorCommand
3435 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
3436 : CompositeEditCommand(document), m_style(0)
3440 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
3442 derefNodesInList(clonedNodes);
3447 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
3452 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
3454 ElementImpl *element = createDefaultParagraphElement(document());
3456 clonedNodes.append(element);
3460 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
3462 // It is only important to set a style to apply later if we're at the boundaries of
3463 // a paragraph. Otherwise, content that is moved as part of the work of the command
3464 // will lend their styles to the new paragraph without any extra work needed.
3465 VisiblePosition visiblePos(pos, UPSTREAM);
3466 if (!isFirstVisiblePositionInParagraph(visiblePos) && !isLastVisiblePositionInParagraph(visiblePos))
3471 m_style = styleAtPosition(pos);
3475 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
3477 // FIXME: Improve typing style.
3478 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3482 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
3483 endingStyle.diff(m_style);
3484 if (m_style->length() > 0) {