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_positioniterator.h"
39 #include "dom_stringimpl.h"
40 #include "dom_textimpl.h"
41 #include "dom2_range.h"
42 #include "dom2_rangeimpl.h"
43 #include "html_elementimpl.h"
44 #include "html_imageimpl.h"
45 #include "html_interchange.h"
46 #include "htmlattrs.h"
48 #include "khtml_part.h"
49 #include "khtml_part.h"
50 #include "khtmlview.h"
53 #include "render_object.h"
54 #include "render_style.h"
55 #include "render_text.h"
56 #include "visible_position.h"
57 #include "visible_text.h"
58 #include "visible_units.h"
61 using DOM::CSSComputedStyleDeclarationImpl;
62 using DOM::CSSMutableStyleDeclarationImpl;
64 using DOM::CSSPrimitiveValue;
65 using DOM::CSSPrimitiveValueImpl;
66 using DOM::CSSProperty;
67 using DOM::CSSStyleDeclarationImpl;
69 using DOM::CSSValueImpl;
70 using DOM::DocumentFragmentImpl;
71 using DOM::DocumentImpl;
73 using DOM::DOMStringImpl;
74 using DOM::DoNotStayInBlock;
75 using DOM::DoNotUpdateLayout;
76 using DOM::EditingTextImpl;
77 using DOM::ElementImpl;
78 using DOM::EStayInBlock;
79 using DOM::HTMLElementImpl;
80 using DOM::HTMLImageElementImpl;
81 using DOM::NamedAttrMapImpl;
84 using DOM::NodeListImpl;
86 using DOM::PositionIterator;
89 using DOM::StayInBlock;
91 using DOM::TreeWalkerImpl;
94 #include "KWQAssertions.h"
95 #include "KWQLogging.h"
96 #include "KWQKHTMLPart.h"
100 #define ASSERT(assertion) ((void)0)
101 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
102 #define ASSERT_NOT_REACHED() ((void)0)
103 #define LOG(channel, formatAndArgs...) ((void)0)
104 #define ERROR(formatAndArgs...) ((void)0)
105 #define ASSERT(assertion) assert(assertion)
107 #define debugPosition(a,b) ((void)0)
108 #define debugNode(a,b) ((void)0)
112 #define IF_IMPL_NULL_RETURN_ARG(arg) do { \
113 if (isNull()) { return arg; } \
116 #define IF_IMPL_NULL_RETURN do { \
117 if (isNull()) { return; } \
122 static inline bool isNBSP(const QChar &c)
124 return c.unicode() == 0xa0;
127 // FIXME: Can't really determine this without taking white-space mode into account.
128 static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
132 if (!pos.node()->isTextNode())
134 return isCollapsibleWhitespace(static_cast<TextImpl *>(pos.node())->data()[pos.offset()]);
137 static const int spacesPerTab = 4;
139 static bool isTableStructureNode(const NodeImpl *node)
141 RenderObject *r = node->renderer();
142 return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
145 static bool isListStructureNode(const NodeImpl *node)
147 // FIXME: Irritating that we can get away with just going at the render tree for isTableStructureNode,
148 // but here we also have to peek at the type of DOM node?
149 RenderObject *r = node->renderer();
150 NodeImpl::Id nodeID = node->id();
151 return (r && r->isListItem())
152 || (nodeID == ID_OL || nodeID == ID_UL || nodeID == ID_DD || nodeID == ID_DT || nodeID == ID_DIR || nodeID == ID_MENU);
155 static DOMString &nonBreakingSpaceString()
157 static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
158 return nonBreakingSpaceString;
161 static DOMString &styleSpanClassString()
163 static DOMString styleSpanClassString = AppleStyleSpanClass;
164 return styleSpanClassString;
167 static bool isEmptyStyleSpan(const NodeImpl *node)
169 if (!node || !node->isHTMLElement() || node->id() != ID_SPAN)
172 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
173 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
174 return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
177 static bool isStyleSpan(const NodeImpl *node)
179 if (!node || !node->isHTMLElement())
182 const HTMLElementImpl *elem = static_cast<const HTMLElementImpl *>(node);
183 return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
186 static bool isEmptyFontTag(const NodeImpl *node)
188 if (!node || node->id() != ID_FONT)
191 const ElementImpl *elem = static_cast<const ElementImpl *>(node);
192 NamedAttrMapImpl *map = elem->attributes(true); // true for read-only
193 return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString();
196 static DOMString &blockPlaceholderClassString()
198 static DOMString blockPlaceholderClassString = "khtml-block-placeholder";
199 return blockPlaceholderClassString;
202 static DOMString &matchNearestBlockquoteColorString()
204 static DOMString matchNearestBlockquoteColorString = "match";
205 return matchNearestBlockquoteColorString;
208 static void derefNodesInList(QPtrList<NodeImpl> &list)
210 for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
211 it.current()->deref();
214 static int maxRangeOffset(NodeImpl *n)
216 if (DOM::offsetInCharacters(n->nodeType()))
217 return n->maxOffset();
219 if (n->isElementNode())
220 return n->childNodeCount();
225 static int maxDeepOffset(NodeImpl *n)
227 if (n->isAtomicNode())
228 return n->caretMaxOffset();
230 if (n->isElementNode())
231 return n->childNodeCount();
236 static void debugPosition(const char *prefix, const Position &pos)
241 LOG(Editing, "%s <null>", prefix);
243 LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset());
246 static void debugNode(const char *prefix, const NodeImpl *node)
251 LOG(Editing, "%s <null>", prefix);
253 LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node);
256 //------------------------------------------------------------------------------------------
259 EditCommandPtr::EditCommandPtr()
263 EditCommandPtr::EditCommandPtr(EditCommand *impl) : SharedPtr<EditCommand>(impl)
267 EditCommandPtr::EditCommandPtr(const EditCommandPtr &o) : SharedPtr<EditCommand>(o)
271 EditCommandPtr::~EditCommandPtr()
275 EditCommandPtr &EditCommandPtr::operator=(const EditCommandPtr &c)
277 static_cast<SharedPtr<EditCommand> &>(*this) = c;
281 bool EditCommandPtr::isCompositeStep() const
283 IF_IMPL_NULL_RETURN_ARG(false);
284 return get()->isCompositeStep();
287 bool EditCommandPtr::isInsertTextCommand() const
289 IF_IMPL_NULL_RETURN_ARG(false);
290 return get()->isInsertTextCommand();
293 bool EditCommandPtr::isTypingCommand() const
295 IF_IMPL_NULL_RETURN_ARG(false);
296 return get()->isTypingCommand();
299 void EditCommandPtr::apply() const
305 void EditCommandPtr::unapply() const
311 void EditCommandPtr::reapply() const
317 EditAction EditCommandPtr::editingAction() const
319 IF_IMPL_NULL_RETURN_ARG(EditActionUnspecified);
320 return get()->editingAction();
323 DocumentImpl * const EditCommandPtr::document() const
325 IF_IMPL_NULL_RETURN_ARG(0);
326 return get()->document();
329 Selection EditCommandPtr::startingSelection() const
331 IF_IMPL_NULL_RETURN_ARG(Selection());
332 return get()->startingSelection();
335 Selection EditCommandPtr::endingSelection() const
337 IF_IMPL_NULL_RETURN_ARG(Selection());
338 return get()->endingSelection();
341 void EditCommandPtr::setStartingSelection(const Selection &s) const
344 get()->setStartingSelection(s);
347 void EditCommandPtr::setStartingSelection(const VisiblePosition &p) const
350 get()->setStartingSelection(p);
353 void EditCommandPtr::setStartingSelection(const Position &p, EAffinity affinity) const
356 Selection s = Selection(p, affinity);
357 get()->setStartingSelection(s);
360 void EditCommandPtr::setEndingSelection(const Selection &s) const
363 get()->setEndingSelection(s);
366 void EditCommandPtr::setEndingSelection(const VisiblePosition &p) const
369 get()->setEndingSelection(p);
372 void EditCommandPtr::setEndingSelection(const Position &p, EAffinity affinity) const
375 Selection s = Selection(p, affinity);
376 get()->setEndingSelection(s);
379 CSSMutableStyleDeclarationImpl *EditCommandPtr::typingStyle() const
381 IF_IMPL_NULL_RETURN_ARG(0);
382 return get()->typingStyle();
385 void EditCommandPtr::setTypingStyle(CSSMutableStyleDeclarationImpl *style) const
388 get()->setTypingStyle(style);
391 EditCommandPtr EditCommandPtr::parent() const
393 IF_IMPL_NULL_RETURN_ARG(0);
394 return get()->parent();
397 void EditCommandPtr::setParent(const EditCommandPtr &cmd) const
400 get()->setParent(cmd.get());
403 EditCommandPtr &EditCommandPtr::emptyCommand()
405 static EditCommandPtr m_emptyCommand;
406 return m_emptyCommand;
409 //------------------------------------------------------------------------------------------
412 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles)
413 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
415 init(style, Position());
418 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles)
419 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles)
421 init(style, position);
424 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
427 CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable();
431 QString styleText("");
433 QValueListConstIterator<CSSProperty> end;
434 for (QValueListConstIterator<CSSProperty> it = mutableStyle->valuesIterator(); it != end; ++it) {
435 const CSSProperty *property = &*it;
437 // If position is empty or the position passed in already has the
438 // style, just move on.
439 if (position.isNotNull() && currentlyHasStyle(position, property))
442 // If needed, figure out if this change is a legacy HTML style change.
443 if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property))
448 if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) {
449 // we have to special-case text decorations
450 CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant());
451 styleText += alteredProperty.cssText().string();
453 styleText += property->cssText().string();
457 mutableStyle->deref();
459 // Save the result for later
460 m_cssStyle = styleText.stripWhiteSpace();
463 StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode)
465 return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles;
468 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property)
470 if (!property || !property->value()) {
474 DOMString valueText(property->value()->cssText());
475 switch (property->id()) {
476 case CSS_PROP_FONT_WEIGHT:
477 if (strcasecmp(valueText, "bold") == 0) {
482 case CSS_PROP_FONT_STYLE:
483 if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
484 m_applyItalic = true;
488 case CSS_PROP_COLOR: {
489 QColor color(CSSParser::parseColor(valueText));
490 m_applyFontColor = color.name();
493 case CSS_PROP_FONT_FAMILY:
494 m_applyFontFace = valueText;
496 case CSS_PROP_FONT_SIZE:
497 if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
498 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(property->value());
499 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
501 m_applyFontSize = "1";
502 else if (number <= 10)
503 m_applyFontSize = "2";
504 else if (number <= 13)
505 m_applyFontSize = "3";
506 else if (number <= 16)
507 m_applyFontSize = "4";
508 else if (number <= 18)
509 m_applyFontSize = "5";
510 else if (number <= 24)
511 m_applyFontSize = "6";
513 m_applyFontSize = "7";
514 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
515 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
516 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
520 // Can't make sense of the number. Put no font size.
527 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
529 ASSERT(pos.isNotNull());
530 CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
533 CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout);
538 bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0;
543 //------------------------------------------------------------------------------------------
546 EditCommand::EditCommand(DocumentImpl *document)
547 : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
550 ASSERT(m_document->part());
552 m_startingSelection = m_document->part()->selection();
553 m_endingSelection = m_startingSelection;
555 m_document->part()->setSelection(Selection(), false, true);
558 EditCommand::~EditCommand()
563 m_typingStyle->deref();
566 void EditCommand::apply()
569 ASSERT(m_document->part());
570 ASSERT(state() == NotApplied);
572 KHTMLPart *part = m_document->part();
574 ASSERT(part->selection().isNone());
580 // FIXME: Improve typing style.
581 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
582 if (!preservesTypingStyle())
585 if (!isCompositeStep()) {
586 document()->updateLayout();
587 EditCommandPtr cmd(this);
588 part->appliedEditing(cmd);
592 void EditCommand::unapply()
595 ASSERT(m_document->part());
596 ASSERT(state() == Applied);
598 bool topLevel = !isCompositeStep();
600 KHTMLPart *part = m_document->part();
603 part->setSelection(Selection(), false, true);
605 ASSERT(part->selection().isNone());
609 m_state = NotApplied;
612 document()->updateLayout();
613 EditCommandPtr cmd(this);
614 part->unappliedEditing(cmd);
618 void EditCommand::reapply()
621 ASSERT(m_document->part());
622 ASSERT(state() == NotApplied);
624 bool topLevel = !isCompositeStep();
626 KHTMLPart *part = m_document->part();
629 part->setSelection(Selection(), false, true);
631 ASSERT(part->selection().isNone());
638 document()->updateLayout();
639 EditCommandPtr cmd(this);
640 part->reappliedEditing(cmd);
644 void EditCommand::doReapply()
649 EditAction EditCommand::editingAction() const
651 return EditActionUnspecified;
654 void EditCommand::setStartingSelection(const Selection &s)
656 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
657 cmd->m_startingSelection = s;
660 void EditCommand::setStartingSelection(const VisiblePosition &p)
662 Selection s = Selection(p);
663 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
664 cmd->m_startingSelection = s;
667 void EditCommand::setStartingSelection(const Position &p, EAffinity affinity)
669 Selection s = Selection(p, affinity);
670 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
671 cmd->m_startingSelection = s;
674 void EditCommand::setEndingSelection(const Selection &s)
676 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
677 cmd->m_endingSelection = s;
680 void EditCommand::setEndingSelection(const VisiblePosition &p)
682 Selection s = Selection(p);
683 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
684 cmd->m_endingSelection = s;
687 void EditCommand::setEndingSelection(const Position &p, EAffinity affinity)
689 Selection s = Selection(p, affinity);
690 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
691 cmd->m_endingSelection = s;
694 void EditCommand::assignTypingStyle(CSSMutableStyleDeclarationImpl *style)
696 if (m_typingStyle == style)
699 CSSMutableStyleDeclarationImpl *old = m_typingStyle;
700 m_typingStyle = style;
702 m_typingStyle->ref();
707 void EditCommand::setTypingStyle(CSSMutableStyleDeclarationImpl *style)
709 // FIXME: Improve typing style.
710 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
711 for (EditCommand *cmd = this; cmd; cmd = cmd->m_parent)
712 cmd->assignTypingStyle(style);
715 bool EditCommand::preservesTypingStyle() const
720 bool EditCommand::isInsertTextCommand() const
725 bool EditCommand::isTypingCommand() const
730 CSSMutableStyleDeclarationImpl *EditCommand::styleAtPosition(const Position &pos)
732 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
733 computedStyle->ref();
734 CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
735 computedStyle->deref();
737 // FIXME: Improve typing style.
738 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
739 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
741 style->merge(typingStyle);
747 //------------------------------------------------------------------------------------------
748 // CompositeEditCommand
750 CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
751 : EditCommand(document)
755 void CompositeEditCommand::doUnapply()
757 if (m_cmds.count() == 0) {
761 for (int i = m_cmds.count() - 1; i >= 0; --i)
762 m_cmds[i]->unapply();
764 setState(NotApplied);
767 void CompositeEditCommand::doReapply()
769 if (m_cmds.count() == 0) {
773 for (QValueList<EditCommandPtr>::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it)
780 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
782 void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd)
784 cmd.setStartingSelection(endingSelection());
785 cmd.setEndingSelection(endingSelection());
791 void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction)
793 EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction));
794 applyCommandToComposite(cmd);
797 void CompositeEditCommand::insertParagraphSeparator()
799 EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
800 applyCommandToComposite(cmd);
803 void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild)
805 ASSERT(refChild->id() != ID_BODY);
806 EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild));
807 applyCommandToComposite(cmd);
810 void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild)
812 ASSERT(refChild->id() != ID_BODY);
813 if (refChild->parentNode()->lastChild() == refChild) {
814 appendNode(insertChild, refChild->parentNode());
817 ASSERT(refChild->nextSibling());
818 insertNodeBefore(insertChild, refChild->nextSibling());
822 void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset)
824 if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) {
825 NodeImpl *child = refChild->firstChild();
826 for (long i = 0; child && i < offset; i++)
827 child = child->nextSibling();
829 insertNodeBefore(insertChild, child);
831 appendNode(insertChild, refChild);
833 else if (refChild->caretMinOffset() >= offset) {
834 insertNodeBefore(insertChild, refChild);
836 else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) {
837 splitTextNode(static_cast<TextImpl *>(refChild), offset);
838 insertNodeBefore(insertChild, refChild);
841 insertNodeAfter(insertChild, refChild);
845 void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent)
847 EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent));
848 applyCommandToComposite(cmd);
851 void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node)
853 if (isTableStructureNode(node) || node == node->rootEditableElement()) {
854 // Do not remove an element of table structure; remove its contents.
855 // Likewise for the root editable element.
856 NodeImpl *child = node->firstChild();
858 NodeImpl *remove = child;
859 child = child->nextSibling();
860 removeFullySelectedNode(remove);
868 void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to)
870 NodeImpl *nodeToRemove = node->childNode(from);
871 for (int i = from; i < to; i++) {
872 ASSERT(nodeToRemove);
873 NodeImpl *next = nodeToRemove->nextSibling();
874 removeNode(nodeToRemove);
879 void CompositeEditCommand::removeNode(NodeImpl *removeChild)
881 EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild));
882 applyCommandToComposite(cmd);
885 void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild)
887 EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild));
888 applyCommandToComposite(cmd);
891 void CompositeEditCommand::splitTextNode(TextImpl *text, long offset)
893 EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset));
894 applyCommandToComposite(cmd);
897 void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild)
899 EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild));
900 applyCommandToComposite(cmd);
903 void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second)
905 EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second));
906 applyCommandToComposite(cmd);
909 void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element)
911 EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element));
912 applyCommandToComposite(cmd);
915 void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset)
917 EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset));
918 applyCommandToComposite(cmd);
921 void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2)
923 EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2));
924 applyCommandToComposite(cmd);
927 void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText)
929 InsertTextCommand *impl = new InsertTextCommand(document());
930 EditCommandPtr cmd(impl);
931 applyCommandToComposite(cmd);
932 impl->input(text, selectInsertedText);
935 void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text)
937 EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text));
938 applyCommandToComposite(cmd);
941 void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count)
943 EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count));
944 applyCommandToComposite(cmd);
947 void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText)
949 EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count));
950 applyCommandToComposite(deleteCommand);
951 EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText));
952 applyCommandToComposite(insertCommand);
955 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete)
957 if (endingSelection().isRange()) {
958 EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete));
959 applyCommandToComposite(cmd);
963 void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
965 if (selection.isRange()) {
966 EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete));
967 applyCommandToComposite(cmd);
971 void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property)
973 EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property));
974 applyCommandToComposite(cmd);
977 void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute)
979 DOMString value = element->getAttribute(attribute);
982 EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute));
983 applyCommandToComposite(cmd);
986 void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value)
988 EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value));
989 applyCommandToComposite(cmd);
992 void CompositeEditCommand::rebalanceWhitespace()
994 Selection selection = endingSelection();
995 if (selection.isCaretOrRange()) {
996 EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start()));
997 applyCommandToComposite(startCmd);
998 if (selection.isRange()) {
999 EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end()));
1000 applyCommandToComposite(endCmd);
1005 void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end)
1007 if (!textNode || !textNode->renderer() || start >= end)
1010 RenderText *textRenderer = static_cast<RenderText *>(textNode->renderer());
1011 InlineTextBox *box = textRenderer->firstTextBox();
1013 // whole text node is empty
1014 removeNode(textNode);
1018 long length = textNode->length();
1019 if (start >= length || end > length)
1023 InlineTextBox *prevBox = 0;
1024 DOMStringImpl *str = 0;
1026 // This loop structure works to process all gaps preceding a box,
1027 // and also will look at the gap after the last box.
1028 while (prevBox || box) {
1029 int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0;
1031 // No more chance for any intersections
1034 int gapEnd = box ? box->m_start : length;
1035 bool indicesIntersect = start <= gapEnd && end >= gapStart;
1036 int gapLen = gapEnd - gapStart;
1037 if (indicesIntersect && gapLen > 0) {
1038 gapStart = kMax(gapStart, start);
1039 gapEnd = kMin(gapEnd, end);
1041 str = textNode->string()->substring(start, end - start);
1044 // remove text in the gap
1045 str->remove(gapStart - start - removed, gapLen);
1051 box = box->nextTextBox();
1055 // Replace the text between start and end with our pruned version.
1057 replaceTextInNode(textNode, start, end - start, str);
1060 // Assert that we are not going to delete all of the text in the node.
1061 // If we were, that should have been done above with the call to
1062 // removeNode and return.
1063 ASSERT(start > 0 || (unsigned long)end - start < textNode->length());
1064 deleteTextFromNode(textNode, start, end - start);
1070 void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end)
1072 if (start.isNull() || end.isNull())
1075 if (RangeImpl::compareBoundaryPoints(start, end) >= 0)
1078 NodeImpl *node = start.node();
1080 NodeImpl *next = node->traverseNextNode();
1082 if (node->isTextNode()) {
1083 TextImpl *textNode = static_cast<TextImpl *>(node);
1084 bool isStartNode = node == start.node();
1085 bool isEndNode = node == end.node();
1086 int startOffset = isStartNode ? start.offset() : 0;
1087 int endOffset = isEndNode ? end.offset() : textNode->length();
1088 deleteInsignificantText(textNode, startOffset, endOffset);
1091 if (node == end.node())
1097 void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos)
1099 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(StayInBlock);
1100 deleteInsignificantText(pos, end);
1103 NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node)
1108 ASSERT(node->renderer() && node->renderer()->isBlockFlow());
1110 NodeImpl *placeholder = createBlockPlaceholderElement(document());
1111 appendNode(placeholder, node);
1115 NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos)
1120 ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow());
1122 NodeImpl *placeholder = createBlockPlaceholderElement(document());
1123 insertNodeAt(placeholder, pos.node(), pos.offset());
1127 NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node)
1132 document()->updateLayout();
1134 RenderObject *renderer = node->renderer();
1135 if (!renderer || !renderer->isBlockFlow())
1138 // append the placeholder to make sure it follows
1139 // any unrendered blocks
1140 if (renderer->height() == 0) {
1141 return appendBlockPlaceholder(node);
1147 bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node)
1149 NodeImpl *placeholder = findBlockPlaceholder(node);
1151 removeNode(placeholder);
1157 NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node)
1162 document()->updateLayout();
1164 RenderObject *renderer = node->renderer();
1165 if (!renderer || !renderer->isBlockFlow())
1168 for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) {
1169 if (checkMe->isElementNode()) {
1170 ElementImpl *element = static_cast<ElementImpl *>(checkMe);
1171 if (element->enclosingBlockFlowElement() == node &&
1172 element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) {
1181 void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos)
1186 document()->updateLayout();
1188 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
1189 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
1190 VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak));
1191 Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream(StayInBlock);
1192 Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream(StayInBlock);
1193 Position beforeParagraphStart = paragraphStart.upstream(DoNotStayInBlock);
1195 // Perform some checks to see if we need to perform work in this function.
1196 if (paragraphStart.node()->isBlockFlow()) {
1197 if (paragraphEnd.node()->isBlockFlow()) {
1198 if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) {
1199 // If the paragraph end is a descendant of paragraph start, then we need to run
1200 // the rest of this function. If not, we can bail here.
1204 else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) {
1205 // The paragraph end is in another block that is an ancestor of the paragraph start.
1206 // We can bail as we have a full block to work with.
1207 ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement()));
1210 else if (isEndOfDocument(visibleParagraphEnd)) {
1211 // At the end of the document. We can bail here as well.
1216 // Create the block to insert. Most times, this will be a shallow clone of the block containing
1217 // the start of the selection (the start block), except for two cases:
1218 // 1) When the start block is a body element.
1219 // 2) When the start block is a mail blockquote and we are not in a position to insert
1220 // the new block as a peer of the start block. This prevents creating an unwanted
1221 // additional level of quoting.
1222 NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement();
1223 NodeImpl *newBlock = 0;
1224 if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock))
1225 newBlock = createDefaultParagraphElement(document());
1227 newBlock = startBlock->cloneNode(false);
1229 NodeImpl *moveNode = paragraphStart.node();
1230 if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset())
1231 moveNode = moveNode->traverseNextNode();
1232 NodeImpl *endNode = paragraphEnd.node();
1234 if (paragraphStart.node()->id() == ID_BODY) {
1235 insertNodeAt(newBlock, paragraphStart.node(), 0);
1237 else if (paragraphStart.node()->id() == ID_BR) {
1238 insertNodeAfter(newBlock, paragraphStart.node());
1240 else if (paragraphStart.node()->isBlockFlow()) {
1241 insertNodeBefore(newBlock, paragraphStart.node());
1243 else if (beforeParagraphStart.node()->enclosingBlockFlowElement()->id() != ID_BODY) {
1244 insertNodeAfter(newBlock, beforeParagraphStart.node()->enclosingBlockFlowElement());
1247 insertNodeAfter(newBlock, beforeParagraphStart.node());
1250 while (moveNode && !moveNode->isBlockFlow()) {
1251 NodeImpl *next = moveNode->traverseNextSibling();
1252 removeNode(moveNode);
1253 appendNode(moveNode, newBlock);
1254 if (moveNode == endNode)
1260 static bool isSpecialElement(NodeImpl *n)
1262 if (!n->isHTMLElement())
1265 if (n->id() == ID_A && n->hasAnchor())
1268 if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL)
1271 RenderObject *renderer = n->renderer();
1273 if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE))
1276 if (renderer && renderer->style()->isFloating())
1279 if (renderer && renderer->style()->position() != STATIC)
1285 // This version of the function is meant to be called on positions in a document fragment,
1286 // so it does not check for a root editable element, it is assumed these nodes will be put
1287 // somewhere editable in the future
1288 static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
1290 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1292 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1293 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1295 if (isSpecialElement(n))
1302 static bool isFirstVisiblePositionInSpecialElement(const Position& pos)
1304 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1306 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1307 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1309 if (n->rootEditableElement() == NULL)
1311 if (isSpecialElement(n))
1318 static Position positionBeforeNode(NodeImpl *node)
1320 return Position(node->parentNode(), node->nodeIndex());
1323 static Position positionBeforeContainingSpecialElement(const Position& pos)
1325 ASSERT(isFirstVisiblePositionInSpecialElement(pos));
1327 VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
1329 NodeImpl *outermostSpecialElement = NULL;
1331 for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
1332 if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
1334 if (n->rootEditableElement() == NULL)
1336 if (isSpecialElement(n))
1337 outermostSpecialElement = n;
1340 ASSERT(outermostSpecialElement);
1342 Position result = positionBeforeNode(outermostSpecialElement);
1343 if (result.isNull() || !result.node()->rootEditableElement())
1349 static bool isLastVisiblePositionInSpecialElement(const Position& pos)
1351 // make sure to get a range-compliant version of the position
1352 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
1354 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
1356 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
1357 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
1359 if (n->rootEditableElement() == NULL)
1361 if (isSpecialElement(n))
1368 static Position positionAfterNode(NodeImpl *node)
1370 return Position(node->parentNode(), node->nodeIndex() + 1);
1373 static Position positionAfterContainingSpecialElement(const Position& pos)
1375 ASSERT(isLastVisiblePositionInSpecialElement(pos));
1377 // make sure to get a range-compliant version of the position
1378 Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
1380 VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
1382 NodeImpl *outermostSpecialElement = NULL;
1384 for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
1385 if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
1387 if (n->rootEditableElement() == NULL)
1389 if (isSpecialElement(n))
1390 outermostSpecialElement = n;
1393 ASSERT(outermostSpecialElement);
1395 Position result = positionAfterNode(outermostSpecialElement);
1396 if (result.isNull() || !result.node()->rootEditableElement())
1402 static Position positionOutsideContainingSpecialElement(const Position &pos)
1404 if (isFirstVisiblePositionInSpecialElement(pos)) {
1405 return positionBeforeContainingSpecialElement(pos);
1406 } else if (isLastVisiblePositionInSpecialElement(pos)) {
1407 return positionAfterContainingSpecialElement(pos);
1413 static Position positionBeforePossibleContainingSpecialElement(const Position &pos)
1415 if (isFirstVisiblePositionInSpecialElement(pos)) {
1416 return positionBeforeContainingSpecialElement(pos);
1422 static Position positionAfterPossibleContainingSpecialElement(const Position &pos)
1424 if (isLastVisiblePositionInSpecialElement(pos)) {
1425 return positionAfterContainingSpecialElement(pos);
1431 //==========================================================================================
1432 // Concrete commands
1433 //------------------------------------------------------------------------------------------
1434 // AppendNodeCommand
1436 AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode)
1437 : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode)
1439 ASSERT(m_appendChild);
1440 m_appendChild->ref();
1442 ASSERT(m_parentNode);
1443 m_parentNode->ref();
1446 AppendNodeCommand::~AppendNodeCommand()
1448 ASSERT(m_appendChild);
1449 m_appendChild->deref();
1451 ASSERT(m_parentNode);
1452 m_parentNode->deref();
1455 void AppendNodeCommand::doApply()
1457 ASSERT(m_appendChild);
1458 ASSERT(m_parentNode);
1460 int exceptionCode = 0;
1461 m_parentNode->appendChild(m_appendChild, exceptionCode);
1462 ASSERT(exceptionCode == 0);
1465 void AppendNodeCommand::doUnapply()
1467 ASSERT(m_appendChild);
1468 ASSERT(m_parentNode);
1469 ASSERT(state() == Applied);
1471 int exceptionCode = 0;
1472 m_parentNode->removeChild(m_appendChild, exceptionCode);
1473 ASSERT(exceptionCode == 0);
1476 //------------------------------------------------------------------------------------------
1477 // ApplyStyleCommand
1479 ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel)
1480 : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel)
1486 ApplyStyleCommand::~ApplyStyleCommand()
1492 void ApplyStyleCommand::doApply()
1494 switch (m_propertyLevel) {
1495 case PropertyDefault: {
1496 // apply the block-centric properties of the style
1497 CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties();
1499 applyBlockStyle(blockStyle);
1500 // apply any remaining styles to the inline elements
1501 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
1502 if (blockStyle->length() < m_style->length()) {
1503 CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy();
1505 applyRelativeFontStyleChange(inlineStyle);
1506 blockStyle->diff(inlineStyle);
1507 applyInlineStyle(inlineStyle);
1508 inlineStyle->deref();
1510 blockStyle->deref();
1513 case ForceBlockProperties:
1514 // Force all properties to be applied as block styles.
1515 applyBlockStyle(m_style);
1519 setEndingSelectionNeedsLayout();
1522 EditAction ApplyStyleCommand::editingAction() const
1524 return m_editingAction;
1527 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style)
1529 // update document layout once before removing styles
1530 // so that we avoid the expense of updating before each and every call
1531 // to check a computed style
1532 document()->updateLayout();
1534 // get positions we want to use for applying style
1535 Position start(endingSelection().start());
1536 Position end(endingSelection().end());
1538 // remove current values, if any, of the specified styles from the blocks
1539 // NOTE: tracks the previous block to avoid repeated processing
1540 // Also, gather up all the nodes we want to process in a QPtrList before
1541 // doing anything. This averts any bugs iterating over these nodes
1542 // once you start removing and applying style.
1543 NodeImpl *beyondEnd = end.node()->traverseNextNode();
1544 QPtrList<NodeImpl> nodes;
1545 for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode())
1548 NodeImpl *prevBlock = 0;
1549 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1550 NodeImpl *block = it.current()->enclosingBlockFlowElement();
1551 if (block != prevBlock && block->isHTMLElement()) {
1552 removeCSSStyle(style, static_cast<HTMLElementImpl *>(block));
1557 // apply specified styles to the block flow elements in the selected range
1559 for (QPtrListIterator<NodeImpl> it(nodes); it.current(); ++it) {
1560 NodeImpl *node = it.current();
1561 if (node->renderer()) {
1562 NodeImpl *block = node->enclosingBlockFlowElement();
1563 if (block != prevBlock) {
1564 addBlockStyleIfNeeded(style, node);
1571 #define NoFontDelta (0.0f)
1572 #define MinimumFontSize (0.1f)
1574 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style)
1576 if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1577 // Explicit font size overrides any delta.
1578 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1582 // Get the adjustment amount out of the style.
1583 CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1587 float adjustment = NoFontDelta;
1588 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
1589 CSSPrimitiveValueImpl *primitiveValue = static_cast<CSSPrimitiveValueImpl *>(value);
1590 if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
1591 // Only PX handled now. If we handle more types in the future, perhaps
1592 // a switch statement here would be more appropriate.
1593 adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX);
1596 style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA);
1598 if (adjustment == NoFontDelta)
1601 // Adjust to the positions we want to use for applying style.
1602 Selection selection = endingSelection();
1603 Position start(selection.start().downstream(StayInBlock));
1604 Position end(selection.end().upstream(StayInBlock));
1605 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1606 Position swap = start;
1611 // Join up any adjacent text nodes.
1612 if (start.node()->isTextNode()) {
1613 joinChildTextNodes(start.node()->parentNode(), start, end);
1614 selection = endingSelection();
1615 start = selection.start();
1616 end = selection.end();
1618 if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
1619 joinChildTextNodes(end.node()->parentNode(), start, end);
1620 selection = endingSelection();
1621 start = selection.start();
1622 end = selection.end();
1625 // Split the start text nodes if needed to apply style.
1626 bool splitStart = splitTextAtStartIfNeeded(start, end);
1628 start = endingSelection().start();
1629 end = endingSelection().end();
1631 bool splitEnd = splitTextAtEndIfNeeded(start, end);
1633 start = endingSelection().start();
1634 end = endingSelection().end();
1637 NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point.
1638 start = start.upstream(StayInBlock); // Move upstream to ensure we do not add redundant spans.
1639 NodeImpl *startNode = start.node();
1640 if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters.
1641 startNode = startNode->traverseNextNode();
1643 // Store away font size before making any changes to the document.
1644 // This ensures that changes to one node won't effect another.
1645 QMap<const NodeImpl *,float> startingFontSizes;
1646 for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode())
1647 startingFontSizes.insert(node, computedFontSize(node));
1649 // These spans were added by us. If empty after font size changes, they can be removed.
1650 QPtrList<NodeImpl> emptySpans;
1652 NodeImpl *lastStyledNode = 0;
1653 for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
1654 HTMLElementImpl *elem = 0;
1655 if (node->isHTMLElement()) {
1656 // Only work on fully selected nodes.
1657 if (!nodeFullySelected(node, start, end))
1659 elem = static_cast<HTMLElementImpl *>(node);
1661 else if (node->isTextNode() && node->parentNode() != lastStyledNode) {
1662 // Last styled node was not parent node of this text node, but we wish to style this
1663 // text node. To make this possible, add a style span to surround this text node.
1664 elem = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1665 insertNodeBefore(elem, node);
1666 surroundNodeRangeWithElement(node, node, elem);
1669 // Only handle HTML elements and text nodes.
1672 lastStyledNode = node;
1674 CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl();
1675 float currentFontSize = computedFontSize(node);
1676 float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment);
1677 if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) {
1678 inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true);
1679 currentFontSize = computedFontSize(node);
1681 if (currentFontSize != desiredFontSize) {
1682 QString desiredFontSizeString = QString::number(desiredFontSize);
1683 desiredFontSizeString += "px";
1684 inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false);
1685 setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText());
1687 if (inlineStyleDecl->length() == 0) {
1688 removeNodeAttribute(elem, ATTR_STYLE);
1689 if (isEmptyStyleSpan(elem))
1690 emptySpans.append(elem);
1694 for (QPtrListIterator<NodeImpl> it(emptySpans); it.current(); ++it)
1695 removeNodePreservingChildren(it.current());
1699 #undef MinimumFontSize
1701 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style)
1703 // adjust to the positions we want to use for applying style
1704 Position start(endingSelection().start().downstream(StayInBlock).equivalentRangeCompliantPosition());
1705 Position end(endingSelection().end().upstream(StayInBlock));
1707 if (RangeImpl::compareBoundaryPoints(end, start) < 0) {
1708 Position swap = start;
1713 // update document layout once before removing styles
1714 // so that we avoid the expense of updating before each and every call
1715 // to check a computed style
1716 document()->updateLayout();
1718 // split the start node and containing element if the selection starts inside of it
1719 bool splitStart = splitTextElementAtStartIfNeeded(start, end);
1721 start = endingSelection().start();
1722 end = endingSelection().end();
1725 // split the end node and containing element if the selection ends inside of it
1726 bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
1727 start = endingSelection().start();
1728 end = endingSelection().end();
1730 // Remove style from the selection.
1731 // Use the upstream position of the start for removing style.
1732 // This will ensure we remove all traces of the relevant styles from the selection
1733 // and prevent us from adding redundant ones, as described in:
1734 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
1735 removeInlineStyle(style, start.upstream(StayInBlock), end);
1736 start = endingSelection().start();
1737 end = endingSelection().end();
1740 bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
1742 start = endingSelection().start();
1743 end = endingSelection().end();
1748 mergeEndWithNextIfIdentical(start, end);
1749 start = endingSelection().start();
1750 end = endingSelection().end();
1753 // update document layout once before running the rest of the function
1754 // so that we avoid the expense of updating before each and every call
1755 // to check a computed style
1756 document()->updateLayout();
1758 if (start.node() == end.node()) {
1759 // simple case...start and end are the same node
1760 addInlineStyleIfNeeded(style, start.node(), end.node());
1763 NodeImpl *node = start.node();
1764 if (start.offset() >= start.node()->caretMaxOffset())
1765 node = node->traverseNextNode();
1767 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1768 NodeImpl *runStart = node;
1770 NodeImpl *next = node->traverseNextNode();
1771 // Break if node is the end node, or if the next node does not fit in with
1772 // the current group.
1773 if (node == end.node() ||
1774 runStart->parentNode() != next->parentNode() ||
1775 (next->isHTMLElement() && next->id() != ID_BR) ||
1776 (next->renderer() && !next->renderer()->isInline()))
1780 // Now apply style to the run we found.
1781 addInlineStyleIfNeeded(style, runStart, node);
1783 if (node == end.node())
1785 node = node->traverseNextNode();
1789 if (splitStart || splitEnd) {
1790 cleanUpEmptyStyleSpans(start, end);
1794 //------------------------------------------------------------------------------------------
1795 // ApplyStyleCommand: style-removal helpers
1797 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1799 QValueListConstIterator<CSSProperty> end;
1800 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1801 switch ((*it).id()) {
1802 case CSS_PROP_FONT_WEIGHT:
1803 if (elem->id() == ID_B)
1806 case CSS_PROP_FONT_STYLE:
1807 if (elem->id() == ID_I)
1815 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem)
1817 // This node can be removed.
1818 // EDIT FIXME: This does not handle the case where the node
1819 // has attributes. But how often do people add attributes to <B> tags?
1820 // Not so often I think.
1822 removeNodePreservingChildren(elem);
1825 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1830 if (elem->id() != ID_FONT)
1833 int exceptionCode = 0;
1834 QValueListConstIterator<CSSProperty> end;
1835 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1836 switch ((*it).id()) {
1837 case CSS_PROP_COLOR:
1838 elem->removeAttribute(ATTR_COLOR, exceptionCode);
1839 ASSERT(exceptionCode == 0);
1841 case CSS_PROP_FONT_FAMILY:
1842 elem->removeAttribute(ATTR_FACE, exceptionCode);
1843 ASSERT(exceptionCode == 0);
1845 case CSS_PROP_FONT_SIZE:
1846 elem->removeAttribute(ATTR_SIZE, exceptionCode);
1847 ASSERT(exceptionCode == 0);
1852 if (isEmptyFontTag(elem))
1853 removeNodePreservingChildren(elem);
1856 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
1861 CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl();
1865 QValueListConstIterator<CSSProperty> end;
1866 for (QValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
1867 int propertyID = (*it).id();
1868 CSSValueImpl *value = decl->getPropertyCSSValue(propertyID);
1871 removeCSSProperty(decl, propertyID);
1876 if (isEmptyStyleSpan(elem))
1877 removeNodePreservingChildren(elem);
1880 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
1882 ASSERT(start.isNotNull());
1883 ASSERT(end.isNotNull());
1884 ASSERT(start.node()->inDocument());
1885 ASSERT(end.node()->inDocument());
1886 ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0);
1890 static bool hasTextDecorationProperty(NodeImpl *node)
1892 if (!node->isElementNode())
1895 ElementImpl *element = static_cast<ElementImpl *>(node);
1896 CSSComputedStyleDeclarationImpl style(element);
1898 CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout);
1902 DOMString valueText(value->cssText());
1904 if (strcasecmp(valueText,"none") != 0)
1911 static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node)
1913 NodeImpl *result = NULL;
1915 for (NodeImpl *n = node; n; n = n->parentNode()) {
1916 if (hasTextDecorationProperty(n))
1923 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node)
1926 ASSERT(node->isElementNode());
1928 // non-html elements not handled yet
1929 if (!node->isHTMLElement())
1932 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1933 CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl();
1938 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1939 CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1);
1941 CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1942 if (property && strcasecmp(property->cssText(), "none") != 0) {
1943 removeCSSProperty(style, CSS_PROP_TEXT_DECORATION);
1948 return textDecorationStyle;
1951 CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node)
1954 ASSERT(node->isElementNode());
1956 // non-html elements not handled yet
1957 if (!node->isHTMLElement())
1960 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
1961 CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element);
1962 ASSERT(computedStyle);
1964 computedStyle->ref();
1966 int properties[1] = { CSS_PROP_TEXT_DECORATION };
1967 CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1);
1970 CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION);
1971 if (property && strcasecmp(property->cssText(), "none") != 0) {
1973 CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy();
1976 newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none");
1977 applyTextDecorationStyle(node, newStyle);
1983 computedStyle->deref();
1985 return textDecorationStyle;
1988 void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
1992 if (!style || !style->cssText().length())
1995 if (node->isTextNode()) {
1996 HTMLElementImpl *styleSpan = static_cast<HTMLElementImpl *>(createStyleSpanElement(document()));
1997 insertNodeBefore(styleSpan, node);
1998 surroundNodeRangeWithElement(node, node, styleSpan);
2002 if (!node->isElementNode())
2005 HTMLElementImpl *element = static_cast<HTMLElementImpl *>(node);
2007 StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2008 if (styleChange.cssStyle().length() > 0) {
2009 DOMString cssText = styleChange.cssStyle();
2010 CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl();
2012 cssText += decl->cssText();
2013 setNodeAttribute(element, ATTR_STYLE, cssText);
2017 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force)
2019 NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node);
2021 if (highestAncestor) {
2022 NodeImpl *nextCurrent;
2023 NodeImpl *nextChild;
2024 for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) {
2029 CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
2033 for (NodeImpl *child = current->firstChild(); child; child = nextChild) {
2034 nextChild = child->nextSibling();
2036 if (node == child) {
2037 nextCurrent = child;
2038 } else if (node->isAncestor(child)) {
2039 applyTextDecorationStyle(child, decoration);
2040 nextCurrent = child;
2042 applyTextDecorationStyle(child, decoration);
2047 decoration->deref();
2052 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
2054 // We need to work in two passes. First we push down any inline
2055 // styles that set text decoration. Then we look for any remaining
2056 // styles (caused by stylesheets) and explicitly negate text
2057 // decoration while pushing down.
2059 pushDownTextDecorationStyleAroundNode(start.node(), start, end, false);
2060 document()->updateLayout();
2061 pushDownTextDecorationStyleAroundNode(start.node(), start, end, true);
2063 pushDownTextDecorationStyleAroundNode(end.node(), start, end, false);
2064 document()->updateLayout();
2065 pushDownTextDecorationStyleAroundNode(end.node(), start, end, true);
2068 void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end)
2070 ASSERT(start.isNotNull());
2071 ASSERT(end.isNotNull());
2072 ASSERT(start.node()->inDocument());
2073 ASSERT(end.node()->inDocument());
2074 ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0);
2076 CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT);
2078 if (textDecorationSpecialProperty) {
2079 pushDownTextDecorationStyleAtBoundaries(start.downstream(StayInBlock), end.upstream(StayInBlock));
2080 style = style->copy();
2081 style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT));
2084 // The s and e variables store the positions used to set the ending selection after style removal
2085 // takes place. This will help callers to recognize when either the start node or the end node
2086 // are removed from the document during the work of this function.
2090 NodeImpl *node = start.node();
2092 NodeImpl *next = node->traverseNextNode();
2093 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
2094 HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
2095 NodeImpl *prev = elem->traversePreviousNodePostOrder();
2096 NodeImpl *next = elem->traverseNextNode();
2097 if (isHTMLStyleNode(style, elem)) {
2098 removeHTMLStyleNode(elem);
2101 removeHTMLFontStyle(style, elem);
2102 removeCSSStyle(style, elem);
2104 if (!elem->inDocument()) {
2105 if (s.node() == elem) {
2106 // Since elem must have been fully selected, and it is at the start
2107 // of the selection, it is clear we can set the new s offset to 0.
2108 ASSERT(s.offset() <= s.node()->caretMinOffset());
2109 s = Position(next, 0);
2111 if (e.node() == elem) {
2112 // Since elem must have been fully selected, and it is at the end
2113 // of the selection, it is clear we can set the new e offset to
2114 // the max range offset of prev.
2115 ASSERT(e.offset() >= maxRangeOffset(e.node()));
2116 e = Position(prev, maxRangeOffset(prev));
2120 if (node == end.node())
2126 if (textDecorationSpecialProperty) {
2130 ASSERT(s.node()->inDocument());
2131 ASSERT(e.node()->inDocument());
2132 setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY));
2135 bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const
2138 ASSERT(node->isElementNode());
2140 Position pos = Position(node, node->childNodeCount()).upstream();
2141 return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 &&
2142 RangeImpl::compareBoundaryPoints(pos, end) <= 0;
2145 bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const
2148 ASSERT(node->isElementNode());
2150 Position pos = Position(node, node->childNodeCount()).upstream();
2151 bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0;
2152 bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0;
2154 return isFullyBeforeStart || isFullyAfterEnd;
2158 //------------------------------------------------------------------------------------------
2159 // ApplyStyleCommand: style-application helpers
2161 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
2163 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
2164 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
2165 TextImpl *text = static_cast<TextImpl *>(start.node());
2166 splitTextNode(text, start.offset());
2167 setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2173 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
2175 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
2176 TextImpl *text = static_cast<TextImpl *>(end.node());
2177 splitTextNode(text, end.offset());
2179 NodeImpl *prevNode = text->previousSibling();
2181 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
2183 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY));
2189 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
2191 if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
2192 long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0;
2193 TextImpl *text = static_cast<TextImpl *>(start.node());
2194 splitTextNodeContainingElement(text, start.offset());
2196 setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2202 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
2204 if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) {
2205 TextImpl *text = static_cast<TextImpl *>(end.node());
2206 splitTextNodeContainingElement(text, end.offset());
2208 NodeImpl *prevNode = text->parent()->previousSibling()->lastChild();
2210 NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node();
2212 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY));
2218 static bool areIdenticalElements(NodeImpl *first, NodeImpl *second)
2220 // check that tag name and all attribute names and values are identical
2222 if (!first->isElementNode())
2225 if (!second->isElementNode())
2228 ElementImpl *firstElement = static_cast<ElementImpl *>(first);
2229 ElementImpl *secondElement = static_cast<ElementImpl *>(second);
2231 if (firstElement->id() != secondElement->id())
2234 NamedAttrMapImpl *firstMap = firstElement->attributes();
2235 NamedAttrMapImpl *secondMap = secondElement->attributes();
2237 unsigned firstLength = firstMap->length();
2239 if (firstLength != secondMap->length())
2242 for (unsigned i = 0; i < firstLength; i++) {
2243 DOM::AttributeImpl *attribute = firstMap->attributeItem(i);
2244 DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id());
2246 if (!secondAttribute || attribute->value() != secondAttribute->value())
2253 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
2255 NodeImpl *startNode = start.node();
2256 long startOffset = start.offset();
2258 if (start.node()->isAtomicNode()) {
2259 if (start.offset() != 0)
2262 if (start.node()->previousSibling())
2265 startNode = start.node()->parent();
2269 if (!startNode->isElementNode())
2272 if (startOffset != 0)
2275 NodeImpl *previousSibling = startNode->previousSibling();
2277 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
2278 ElementImpl *previousElement = static_cast<ElementImpl *>(previousSibling);
2279 ElementImpl *element = static_cast<ElementImpl *>(startNode);
2280 NodeImpl *startChild = element->firstChild();
2282 mergeIdenticalElements(previousElement, element);
2284 long startOffsetAdjustment = startChild->nodeIndex();
2285 long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
2287 setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY,
2288 Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY));
2296 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
2298 NodeImpl *endNode = end.node();
2299 int endOffset = end.offset();
2301 if (endNode->isAtomicNode()) {
2302 if (endOffset < endNode->caretMaxOffset())
2305 unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
2306 if (end.node()->nextSibling())
2309 endNode = end.node()->parent();
2310 endOffset = parentLastOffset;
2313 if (!endNode->isElementNode() || endNode->id() == ID_BR)
2316 NodeImpl *nextSibling = endNode->nextSibling();
2318 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
2319 ElementImpl *nextElement = static_cast<ElementImpl *>(nextSibling);
2320 ElementImpl *element = static_cast<ElementImpl *>(endNode);
2321 NodeImpl *nextChild = nextElement->firstChild();
2323 mergeIdenticalElements(element, nextElement);
2325 NodeImpl *startNode = start.node() == endNode ? nextElement : start.node();
2328 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
2330 setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY,
2331 Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY));
2338 void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end)
2341 for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) {
2344 if (node && isEmptyStyleSpan(node->previousSibling())) {
2345 removeNodePreservingChildren(node->previousSibling());
2348 if (start.node() == end.node()) {
2349 if (start.node()->isTextNode()) {
2350 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2351 if (isEmptyStyleSpan(cur)) {
2352 removeNodePreservingChildren(cur);
2359 if (start.node()->isTextNode()) {
2360 for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) {
2361 if (isEmptyStyleSpan(cur)) {
2362 removeNodePreservingChildren(cur);
2368 if (end.node()->isTextNode()) {
2369 for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) {
2370 if (isEmptyStyleSpan(cur)) {
2371 removeNodePreservingChildren(cur);
2378 for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) {
2380 if (node && isEmptyStyleSpan(node->nextSibling())) {
2381 removeNodePreservingChildren(node->nextSibling());
2385 void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element)
2391 NodeImpl *node = startNode;
2393 NodeImpl *next = node->traverseNextNode();
2394 if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
2396 appendNode(node, element);
2398 if (node == endNode)
2404 void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node)
2406 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
2411 HTMLElementImpl *block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2415 StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2416 if (styleChange.cssStyle().length() > 0) {
2417 moveParagraphContentsToNewBlockIfNecessary(Position(node, 0));
2418 block = static_cast<HTMLElementImpl *>(node->enclosingBlockFlowElement());
2419 DOMString cssText = styleChange.cssStyle();
2420 CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl();
2422 cssText += decl->cssText();
2423 setNodeAttribute(block, ATTR_STYLE, cssText);
2427 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode)
2429 StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
2430 int exceptionCode = 0;
2433 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
2435 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
2436 ElementImpl *fontElement = createFontElement(document());
2437 ASSERT(exceptionCode == 0);
2438 insertNodeBefore(fontElement, startNode);
2439 if (styleChange.applyFontColor())
2440 fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor());
2441 if (styleChange.applyFontFace())
2442 fontElement->setAttribute(ATTR_FACE, styleChange.fontFace());
2443 if (styleChange.applyFontSize())
2444 fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize());
2445 surroundNodeRangeWithElement(startNode, endNode, fontElement);
2448 if (styleChange.cssStyle().length() > 0) {
2449 ElementImpl *styleElement = createStyleSpanElement(document());
2450 styleElement->ref();
2451 styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle());
2452 insertNodeBefore(styleElement, startNode);
2453 styleElement->deref();
2454 surroundNodeRangeWithElement(startNode, endNode, styleElement);
2457 if (styleChange.applyBold()) {
2458 ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode);
2459 ASSERT(exceptionCode == 0);
2460 insertNodeBefore(boldElement, startNode);
2461 surroundNodeRangeWithElement(startNode, endNode, boldElement);
2464 if (styleChange.applyItalic()) {
2465 ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode);
2466 ASSERT(exceptionCode == 0);
2467 insertNodeBefore(italicElement, startNode);
2468 surroundNodeRangeWithElement(startNode, endNode, italicElement);
2472 float ApplyStyleCommand::computedFontSize(const NodeImpl *node)
2479 Position pos(const_cast<NodeImpl *>(node), 0);
2480 CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
2483 computedStyle->ref();
2485 CSSPrimitiveValueImpl *value = static_cast<CSSPrimitiveValueImpl *>(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE));
2488 size = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
2492 computedStyle->deref();
2496 void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end)
2501 Position newStart = start;
2502 Position newEnd = end;
2504 NodeImpl *child = node->firstChild();
2506 NodeImpl *next = child->nextSibling();
2507 if (child->isTextNode() && next && next->isTextNode()) {
2508 TextImpl *childText = static_cast<TextImpl *>(child);
2509 TextImpl *nextText = static_cast<TextImpl *>(next);
2510 if (next == start.node())
2511 newStart = Position(childText, childText->length() + start.offset());
2512 if (next == end.node())
2513 newEnd = Position(childText, childText->length() + end.offset());
2514 DOMString textToMove = nextText->data();
2515 insertTextIntoNode(childText, childText->length(), textToMove);
2517 // don't move child node pointer. it may want to merge with more text nodes.
2520 child = child->nextSibling();
2524 setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY));
2527 //------------------------------------------------------------------------------------------
2528 // DeleteFromTextNodeCommand
2530 DeleteFromTextNodeCommand::DeleteFromTextNodeCommand(DocumentImpl *document, TextImpl *node, long offset, long count)
2531 : EditCommand(document), m_node(node), m_offset(offset), m_count(count)
2534 ASSERT(m_offset >= 0);
2535 ASSERT(m_offset < (long)m_node->length());
2536 ASSERT(m_count >= 0);
2541 DeleteFromTextNodeCommand::~DeleteFromTextNodeCommand()
2547 void DeleteFromTextNodeCommand::doApply()
2551 int exceptionCode = 0;
2552 m_text = m_node->substringData(m_offset, m_count, exceptionCode);
2553 ASSERT(exceptionCode == 0);
2555 m_node->deleteData(m_offset, m_count, exceptionCode);
2556 ASSERT(exceptionCode == 0);
2559 void DeleteFromTextNodeCommand::doUnapply()
2562 ASSERT(!m_text.isEmpty());
2564 int exceptionCode = 0;
2565 m_node->insertData(m_offset, m_text, exceptionCode);
2566 ASSERT(exceptionCode == 0);
2569 //------------------------------------------------------------------------------------------
2570 // DeleteSelectionCommand
2572 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, bool smartDelete, bool mergeBlocksAfterDelete)
2573 : CompositeEditCommand(document),
2574 m_hasSelectionToDelete(false),
2575 m_smartDelete(smartDelete),
2576 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2584 DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete)
2585 : CompositeEditCommand(document),
2586 m_hasSelectionToDelete(true),
2587 m_smartDelete(smartDelete),
2588 m_mergeBlocksAfterDelete(mergeBlocksAfterDelete),
2589 m_selectionToDelete(selection),
2597 void DeleteSelectionCommand::initializePositionData()
2600 // Handle setting some basic positions
2602 Position start = m_selectionToDelete.start();
2603 start = positionOutsideContainingSpecialElement(start);
2604 Position end = m_selectionToDelete.end();
2605 end = positionOutsideContainingSpecialElement(end);
2607 m_upstreamStart = positionBeforePossibleContainingSpecialElement(start.upstream(StayInBlock));
2608 m_downstreamStart = positionBeforePossibleContainingSpecialElement(start.downstream(StayInBlock));
2609 m_upstreamEnd = positionAfterPossibleContainingSpecialElement(end.upstream(StayInBlock));
2610 m_downstreamEnd = positionAfterPossibleContainingSpecialElement(end.downstream(StayInBlock));
2613 // Handle leading and trailing whitespace, as well as smart delete adjustments to the selection
2615 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity());
2616 // NOTE: Workaround for bug <rdar://problem/4103339> is to avoid calculating trailingWhitespacePosition
2617 // if the m_downstreamEnd is at the end of a paragraph.
2618 if (!isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY)))
2619 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2621 if (m_smartDelete) {
2623 // skip smart delete if the selection to delete already starts or ends with whitespace
2624 Position pos = VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()).deepEquivalent();
2625 bool skipSmartDelete = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2626 if (!skipSmartDelete)
2627 skipSmartDelete = m_downstreamEnd.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull();
2629 // extend selection upstream if there is whitespace there
2630 bool hasLeadingWhitespaceBeforeAdjustment = m_upstreamStart.leadingWhitespacePosition(m_selectionToDelete.startAffinity(), true).isNotNull();
2631 if (!skipSmartDelete && hasLeadingWhitespaceBeforeAdjustment) {
2632 VisiblePosition visiblePos = VisiblePosition(start, m_selectionToDelete.startAffinity()).previous();
2633 pos = visiblePos.deepEquivalent();
2634 // Expand out one character upstream for smart delete and recalculate
2635 // positions based on this change.
2636 m_upstreamStart = pos.upstream(StayInBlock);
2637 m_downstreamStart = pos.downstream(StayInBlock);
2638 m_leadingWhitespace = m_upstreamStart.leadingWhitespacePosition(visiblePos.affinity());
2641 // trailing whitespace is only considered for smart delete if there is no leading
2642 // whitespace, as in the case where you double-click the first word of a paragraph.
2643 if (!skipSmartDelete && !hasLeadingWhitespaceBeforeAdjustment && m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNotNull()) {
2644 // Expand out one character downstream for smart delete and recalculate
2645 // positions based on this change.
2646 pos = VisiblePosition(end, m_selectionToDelete.endAffinity()).next().deepEquivalent();
2647 m_upstreamEnd = pos.upstream(StayInBlock);
2648 m_downstreamEnd = pos.downstream(StayInBlock);
2649 m_trailingWhitespace = m_downstreamEnd.trailingWhitespacePosition(VP_DEFAULT_AFFINITY);
2653 m_trailingWhitespaceValid = true;
2656 // Handle setting start and end blocks and the start node.
2658 m_startBlock = m_downstreamStart.node()->enclosingBlockFlowElement();
2659 m_startBlock->ref();
2660 m_endBlock = m_upstreamEnd.node()->enclosingBlockFlowElement();
2662 m_startNode = m_upstreamStart.node();
2666 // Handle detecting if the line containing the selection end is itself fully selected.
2667 // This is one of the tests that determines if block merging of content needs to be done.
2669 VisiblePosition visibleEnd(end, m_selectionToDelete.endAffinity());
2670 if (isFirstVisiblePositionInParagraph(visibleEnd) || isLastVisiblePositionInParagraph(visibleEnd)) {
2671 Position previousLineStart = previousLinePosition(visibleEnd, 0).deepEquivalent();
2672 if (previousLineStart.isNull() || RangeImpl::compareBoundaryPoints(previousLineStart, m_downstreamStart) >= 0)
2673 m_mergeBlocksAfterDelete = false;
2676 debugPosition("m_upstreamStart ", m_upstreamStart);
2677 debugPosition("m_downstreamStart ", m_downstreamStart);
2678 debugPosition("m_upstreamEnd ", m_upstreamEnd);
2679 debugPosition("m_downstreamEnd ", m_downstreamEnd);
2680 debugPosition("m_leadingWhitespace ", m_leadingWhitespace);
2681 debugPosition("m_trailingWhitespace ", m_trailingWhitespace);
2682 debugNode( "m_startBlock ", m_startBlock);
2683 debugNode( "m_endBlock ", m_endBlock);
2684 debugNode( "m_startNode ", m_startNode);
2687 void DeleteSelectionCommand::insertPlaceholderForAncestorBlockContent()
2689 // This code makes sure a line does not disappear when deleting in this case:
2690 // <p>foo</p>bar<p>baz</p>
2691 // Select "bar" and hit delete. If nothing is done, the line containing bar will disappear.
2692 // It needs to be held open by inserting a placeholder.
2694 // <rdar://problem/3928305> selecting an entire line and typing over causes new inserted text at top of document
2696 // The checks below detect the case where the selection contains content in an ancestor block
2697 // surrounded by child blocks.
2699 NodeImpl *upstreamBlock = m_upstreamStart.node()->enclosingBlockFlowElement();
2700 NodeImpl *beforeUpstreamBlock = m_upstreamStart.upstream().node()->enclosingBlockFlowElement();
2702 if (upstreamBlock != beforeUpstreamBlock &&
2703 beforeUpstreamBlock->isAncestor(upstreamBlock) &&
2704 upstreamBlock != m_upstreamStart.node()) {
2705 NodeImpl *downstreamBlock = m_downstreamEnd.node()->enclosingBlockFlowElement();
2706 NodeImpl *afterDownstreamBlock = m_downstreamEnd.downstream().node()->enclosingBlockFlowElement();
2708 if ((afterDownstreamBlock != downstreamBlock && afterDownstreamBlock != upstreamBlock) ||
2709 (m_downstreamEnd == m_selectionToDelete.end() && isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY)))) {
2710 NodeImpl *block = createDefaultParagraphElement(document());
2711 insertNodeBefore(block, m_upstreamStart.node());
2712 addBlockPlaceholderIfNeeded(block);
2713 m_endingPosition = Position(block, 0);
2718 void DeleteSelectionCommand::saveTypingStyleState()
2720 // Figure out the typing style in effect before the delete is done.
2721 // FIXME: Improve typing style.
2722 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
2723 CSSComputedStyleDeclarationImpl *computedStyle = m_selectionToDelete.start().computedStyle();
2724 computedStyle->ref();
2725 m_typingStyle = computedStyle->copyInheritableProperties();
2726 m_typingStyle->ref();
2727 computedStyle->deref();
2730 bool DeleteSelectionCommand::handleSpecialCaseBRDelete()
2732 // Check for special-case where the selection contains only a BR on a line by itself after another BR.
2733 bool upstreamStartIsBR = m_startNode->id() == ID_BR;
2734 bool downstreamStartIsBR = m_downstreamStart.node()->id() == ID_BR;
2735 bool isBROnLineByItself = upstreamStartIsBR && downstreamStartIsBR && m_downstreamStart.node() == m_upstreamEnd.node();
2736 if (isBROnLineByItself) {
2737 removeNode(m_downstreamStart.node());
2738 m_endingPosition = m_upstreamStart;
2739 m_mergeBlocksAfterDelete = false;
2743 // Not a special-case delete per se, but we can detect that the merging of content between blocks
2744 // should not be done.
2745 if (upstreamStartIsBR && downstreamStartIsBR)
2746 m_mergeBlocksAfterDelete = false;
2751 void DeleteSelectionCommand::setStartNode(NodeImpl *node)
2753 NodeImpl *old = m_startNode;
2761 void DeleteSelectionCommand::handleGeneralDelete()
2763 int startOffset = m_upstreamStart.offset();
2764 VisiblePosition visibleEnd = VisiblePosition(m_downstreamEnd, m_selectionToDelete.endAffinity());
2765 bool endAtEndOfBlock = isEndOfBlock(visibleEnd);
2767 // Handle some special cases where the selection begins and ends on specific visible units.
2768 // Sometimes a node that is actually selected needs to be retained in order to maintain
2769 // user expectations for the delete operation. Here is an example:
2770 // 1. Open a new Blot or Mail document
2771 // 2. hit Return ten times or so
2772 // 3. Type a letter (do not hit Return after it)
2773 // 4. Type shift-up-arrow to select the line containing the letter and the previous blank line
2775 // You expect the insertion point to wind up at the start of the line where your selection began.
2776 // Because of the nature of HTML, the editing code needs to perform a special check to get
2777 // this behavior. So:
2778 // If the entire start block is selected, and the selection does not extend to the end of the
2779 // end of a block other than the block containing the selection start, then do not delete the
2780 // start block, otherwise delete the start block.
2781 // A similar case is provided to cover selections starting in BR elements.
2782 if (startOffset == 1 && m_startNode && m_startNode->id() == ID_BR) {
2783 setStartNode(m_startNode->traverseNextNode());
2786 if (m_startBlock != m_endBlock && startOffset == 0 && m_startNode && m_startNode->id() == ID_BR && endAtEndOfBlock) {
2787 // Don't delete the BR element
2788 setStartNode(m_startNode->traverseNextNode());
2790 else if (m_startBlock != m_endBlock && isStartOfBlock(VisiblePosition(m_upstreamStart, m_selectionToDelete.startAffinity()))) {
2791 if (!m_startBlock->isAncestor(m_endBlock) && !isStartOfBlock(visibleEnd) && endAtEndOfBlock) {
2792 // Delete all the children of the block, but not the block itself.
2793 setStartNode(m_startBlock->firstChild());
2797 else if (startOffset >= m_startNode->caretMaxOffset() &&
2798 (m_startNode->isAtomicNode() || startOffset == 0)) {
2799 // Move the start node to the next node in the tree since the startOffset is equal to
2800 // or beyond the start node's caretMaxOffset This means there is nothing visible to delete.
2801 // But don't do this if the node is not atomic - we don't want to move into the first child.
2803 // Also, before moving on, delete any insignificant text that may be present in a text node.
2804 if (m_startNode->isTextNode()) {
2805 // Delete any insignificant text from this node.
2806 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2807 if (text->length() > (unsigned)m_startNode->caretMaxOffset())
2808 deleteTextFromNode(text, m_startNode->caretMaxOffset(), text->length() - m_startNode->caretMaxOffset());
2811 // shift the start node to the next
2812 setStartNode(m_startNode->traverseNextNode());
2816 // Done adjusting the start. See if we're all done.
2820 if (m_startNode == m_downstreamEnd.node()) {
2821 // The selection to delete is all in one node.
2822 if (!m_startNode->renderer() ||
2823 (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(m_startNode))) {
2825 removeFullySelectedNode(m_startNode);
2826 } else if (m_downstreamEnd.offset() - startOffset > 0) {
2827 if (m_startNode->isTextNode()) {
2828 // in a text node that needs to be trimmed
2829 TextImpl *text = static_cast<TextImpl *>(m_startNode);
2830 deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset);
2831 m_trailingWhitespaceValid = false;
2833 removeChildrenInRange(m_startNode, startOffset, m_downstreamEnd.offset());
2834 m_endingPosition = m_upstreamStart;
2839 // The selection to delete spans more than one node.
2840 NodeImpl *node = m_startNode;
2842 if (startOffset > 0) {
2843 if (m_startNode->isTextNode()) {
2844 // in a text node that needs to be trimmed
2845 TextImpl *text = static_cast<TextImpl *>(node);
2846 deleteTextFromNode(text, startOffset, text->length() - startOffset);
2847 node = node->traverseNextNode();
2849 node = m_startNode->childNode(startOffset);
2853 // handle deleting all nodes that are completely selected
2854 while (node && node != m_downstreamEnd.node()) {
2855 if (RangeImpl::compareBoundaryPoints(Position(node, 0), m_downstreamEnd) >= 0) {
2856 // traverseNextSibling just blew past the end position, so stop deleting
2858 } else if (!m_downstreamEnd.node()->isAncestor(node)) {
2859 NodeImpl *nextNode = node->traverseNextSibling();
2860 // if we just removed a node from the end container, update end position so the
2861 // check above will work
2862 if (node->parentNode() == m_downstreamEnd.node()) {
2863 ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset());
2864 m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1);
2866 removeFullySelectedNode(node);
2869 NodeImpl *n = node->lastChild();
2870 while (n && n->lastChild())
2872 if (n == m_downstreamEnd.node() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMaxOffset()) {
2873 removeFullySelectedNode(node);
2874 m_trailingWhitespaceValid = false;
2878 node = node->traverseNextNode();
2884 if (m_downstreamEnd.node() != m_startNode && !m_upstreamStart.node()->isAncestor(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= m_downstreamEnd.node()->caretMinOffset()) {
2885 if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node())) {
2886 // need to delete whole node
2887 // we can get here if this is the last node in the block
2888 // remove an ancestor of m_downstreamEnd.node(), and thus m_downstreamEnd.node() itself
2889 if (m_upstreamStart.node() == m_downstreamEnd.node() ||
2890 m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2891 m_upstreamStart = Position(m_downstreamEnd.node()->parentNode(), m_downstreamEnd.node()->nodeIndex());
2894 removeFullySelectedNode(m_downstreamEnd.node());
2895 m_trailingWhitespaceValid = false;
2897 if (m_downstreamEnd.node()->isTextNode()) {
2898 // in a text node that needs to be trimmed
2899 TextImpl *text = static_cast<TextImpl *>(m_downstreamEnd.node());
2900 if (m_downstreamEnd.offset() > 0) {
2901 deleteTextFromNode(text, 0, m_downstreamEnd.offset());
2902 m_downstreamEnd = Position(text, 0);
2903 m_trailingWhitespaceValid = false;
2907 if (m_upstreamStart.node()->isAncestor(m_downstreamEnd.node())) {
2908 NodeImpl *n = m_upstreamStart.node();
2909 while (n && n->parentNode() != m_downstreamEnd.node())
2910 n = n->parentNode();
2912 offset = n->nodeIndex() + 1;
2914 removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset());
2915 m_downstreamEnd = Position(m_downstreamEnd.node(), offset);
2922 void DeleteSelectionCommand::fixupWhitespace()
2924 document()->updateLayout();
2925 if (m_leadingWhitespace.isNotNull() && (m_trailingWhitespace.isNotNull() || !m_leadingWhitespace.isRenderedCharacter())) {
2926 LOG(Editing, "replace leading");
2927 TextImpl *textNode = static_cast<TextImpl *>(m_leadingWhitespace.node());
2928 replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString());
2930 else if (m_trailingWhitespace.isNotNull()) {
2931 if (m_trailingWhitespaceValid) {
2932 if (!m_trailingWhitespace.isRenderedCharacter()) {
2933 LOG(Editing, "replace trailing [valid]");
2934 TextImpl *textNode = static_cast<TextImpl *>(m_trailingWhitespace.node());
2935 replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString());
2939 Position pos = m_endingPosition.downstream(StayInBlock);
2940 pos = Position(pos.node(), pos.offset() - 1);
2941 if (nextCharacterIsCollapsibleWhitespace(pos) && !pos.isRenderedCharacter()) {
2942 LOG(Editing, "replace trailing [invalid]");
2943 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
2944 replaceTextInNode(textNode, pos.offset(), 1, nonBreakingSpaceString());
2945 // need to adjust ending position since the trailing position is not valid.
2946 m_endingPosition = pos;
2952 // This function moves nodes in the block containing startNode to dstBlock, starting
2953 // from startNode and proceeding to the end of the paragraph. Nodes in the block containing
2954 // startNode that appear in document order before startNode are not moved.
2955 // This function is an important helper for deleting selections that cross paragraph
2957 void DeleteSelectionCommand::moveNodesAfterNode()
2959 if (!m_mergeBlocksAfterDelete)
2962 if (m_endBlock == m_startBlock)
2965 NodeImpl *startNode = m_downstreamEnd.node();
2966 NodeImpl *dstNode = m_upstreamStart.node();
2968 if (!startNode->inDocument() || !dstNode->inDocument())
2971 NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
2972 if (isTableStructureNode(startBlock) || isListStructureNode(startBlock))
2973 // Do not move content between parts of a table or list.
2976 // Now that we are about to add content, check to see if a placeholder element
2978 removeBlockPlaceholder(startBlock);
2980 // Move the subtree containing node
2981 NodeImpl *node = startNode->enclosingInlineElement();
2983 // Insert after the subtree containing destNode
2984 NodeImpl *refNode = dstNode->enclosingInlineElement();
2986 // Nothing to do if start is already at the beginning of dstBlock
2987 NodeImpl *dstBlock = refNode->enclosingBlockFlowElement();
2988 if (startBlock == dstBlock->firstChild())
2992 NodeImpl *rootNode = refNode->rootEditableElement();
2993 while (node && node->isAncestor(startBlock)) {
2994 NodeImpl *moveNode = node;
2995 node = node->nextSibling();
2996 removeNode(moveNode);
2997 if (moveNode->id() == ID_BR && !moveNode->renderer()) {
2998 // Just remove this node, and don't put it back.
2999 // If the BR was not rendered (since it was at the end of a block, for instance),
3000 // putting it back in the document might make it appear, and that is not desirable.
3003 if (refNode == rootNode)
3004 insertNodeAt(moveNode, refNode, 0);
3006 insertNodeAfter(moveNode, refNode);
3008 if (moveNode->id() == ID_BR)
3012 // If the startBlock no longer has any kids, we may need to deal with adding a BR
3013 // to make the layout come out right. Consider this document:
3019 // Placing the insertion before before the 'T' of 'Two' and hitting delete will
3020 // move the contents of the div to the block containing 'One' and delete the div.
3021 // This will have the side effect of moving 'Three' on to the same line as 'One'
3022 // and 'Two'. This is undesirable. We fix this up by adding a BR before the 'Three'.
3023 // This may not be ideal, but it is better than nothing.
3024 document()->updateLayout();
3025 if (!startBlock->renderer() || !startBlock->renderer()->firstChild()) {
3026 removeNode(startBlock);
3027 document()->updateLayout();
3028 if (refNode->renderer() && refNode->renderer()->inlineBox() && refNode->renderer()->inlineBox()->nextOnLineExists()) {
3029 insertNodeAfter(createBreakElement(document()), refNode);
3034 void DeleteSelectionCommand::calculateEndingPosition()
3036 if (m_endingPosition.isNotNull() && m_endingPosition.node()->inDocument())
3039 m_endingPosition = m_upstreamStart;
3040 if (m_endingPosition.node()->inDocument())
3043 m_endingPosition = m_downstreamEnd;
3044 if (m_endingPosition.node()->inDocument())
3047 m_endingPosition = Position(m_startBlock, 0);
3048 if (m_endingPosition.node()->inDocument())
3051 m_endingPosition = Position(m_endBlock, 0);
3052 if (m_endingPosition.node()->inDocument())
3055 m_endingPosition = Position(document()->documentElement(), 0);
3058 void DeleteSelectionCommand::calculateTypingStyleAfterDelete(NodeImpl *insertedPlaceholder)
3060 // Compute the difference between the style before the delete and the style now
3061 // after the delete has been done. Set this style on the part, so other editing
3062 // commands being composed with this one will work, and also cache it on the command,
3063 // so the KHTMLPart::appliedEditing can set it after the whole composite command
3065 // FIXME: Improve typing style.
3066 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3067 CSSComputedStyleDeclarationImpl endingStyle(m_endingPosition.node());
3068 endingStyle.diff(m_typingStyle);
3069 if (!m_typingStyle->length()) {
3070 m_typingStyle->deref();
3073 if (insertedPlaceholder && m_typingStyle) {
3074 // Apply style to the placeholder. This makes sure that the single line in the
3075 // paragraph has the right height, and that the paragraph takes on the style
3076 // of the preceding line and retains it even if you click away, click back, and
3077 // then start typing. In this case, the typing style is applied right now, and
3078 // is not retained until the next typing action.
3080 // FIXME: is this even right? I don't think post-deletion typing style is supposed
3081 // to be saved across clicking away and clicking back, it certainly isn't in TextEdit
3083 Position pastPlaceholder(insertedPlaceholder, 1);
3085 setEndingSelection(Selection(m_endingPosition, m_selectionToDelete.endAffinity(), pastPlaceholder, DOWNSTREAM));
3087 applyStyle(m_typingStyle, EditActionUnspecified);
3089 m_typingStyle->deref();
3092 // Set m_typingStyle as the typing style.
3093 // It's perfectly OK for m_typingStyle to be null.
3094 document()->part()->setTypingStyle(m_typingStyle);
3095 setTypingStyle(m_typingStyle);
3098 void DeleteSelectionCommand::clearTransientState()
3100 m_selectionToDelete.clear();
3101 m_upstreamStart.clear();
3102 m_downstreamStart.clear();
3103 m_upstreamEnd.clear();
3104 m_downstreamEnd.clear();
3105 m_endingPosition.clear();
3106 m_leadingWhitespace.clear();
3107 m_trailingWhitespace.clear();
3110 m_startBlock->deref();
3114 m_endBlock->deref();
3118 m_startNode->deref();
3121 if (m_typingStyle) {
3122 m_typingStyle->deref();
3127 void DeleteSelectionCommand::doApply()
3129 // If selection has not been set to a custom selection when the command was created,
3130 // use the current ending selection.
3131 if (!m_hasSelectionToDelete)
3132 m_selectionToDelete = endingSelection();
3134 if (!m_selectionToDelete.isRange())
3137 // save this to later make the selection with
3138 EAffinity affinity = m_selectionToDelete.startAffinity();
3141 initializePositionData();
3143 if (!m_startBlock || !m_endBlock) {
3144 // Can't figure out what blocks we're in. This can happen if
3145 // the document structure is not what we are expecting, like if
3146 // the document has no body element, or if the editable block
3147 // has been changed to display: inline. Some day it might
3148 // be nice to be able to deal with this, but for now, bail.
3149 clearTransientState();
3153 // if all we are deleting is complete paragraph(s), we need to make
3154 // sure a blank paragraph remains when we are done
3155 bool forceBlankParagraph = isStartOfParagraph(VisiblePosition(m_upstreamStart, VP_DEFAULT_AFFINITY)) &&
3156 isEndOfParagraph(VisiblePosition(m_downstreamEnd, VP_DEFAULT_AFFINITY));
3158 // Delete any text that may hinder our ability to fixup whitespace after the detele
3159 deleteInsignificantTextDownstream(m_trailingWhitespace);
3161 saveTypingStyleState();
3162 insertPlaceholderForAncestorBlockContent();
3164 if (!handleSpecialCaseBRDelete())
3165 handleGeneralDelete();
3167 // Do block merge if start and end of selection are in different blocks.
3168 moveNodesAfterNode();
3170 calculateEndingPosition();
3173 // if the m_endingPosition is already a blank paragraph, there is
3174 // no need to force a new one
3175 if (forceBlankParagraph &&
3176 isStartOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY)) &&
3177 isEndOfParagraph(VisiblePosition(m_endingPosition, VP_DEFAULT_AFFINITY))) {
3178 forceBlankParagraph = false;
3181 NodeImpl *addedPlaceholder = forceBlankParagraph ? insertBlockPlaceholder(m_endingPosition) :
3182 addBlockPlaceholderIfNeeded(m_endingPosition.node());
3184 calculateTypingStyleAfterDelete(addedPlaceholder);
3185 debugPosition("endingPosition ", m_endingPosition);
3186 setEndingSelection(Selection(m_endingPosition, affinity));
3187 clearTransientState();
3188 rebalanceWhitespace();
3191 EditAction DeleteSelectionCommand::editingAction() const
3193 // Note that DeleteSelectionCommand is also used when the user presses the Delete key,
3194 // but in that case there's a TypingCommand that supplies the editingAction(), so
3195 // the Undo menu correctly shows "Undo Typing"
3196 return EditActionCut;
3199 bool DeleteSelectionCommand::preservesTypingStyle() const
3204 //------------------------------------------------------------------------------------------
3205 // InsertIntoTextNode
3207 InsertIntoTextNode::InsertIntoTextNode(DocumentImpl *document, TextImpl *node, long offset, const DOMString &text)
3208 : EditCommand(document), m_node(node), m_offset(offset)
3211 ASSERT(m_offset >= 0);
3212 ASSERT(!text.isEmpty());
3215 m_text = text.copy(); // make a copy to ensure that the string never changes
3218 InsertIntoTextNode::~InsertIntoTextNode()
3224 void InsertIntoTextNode::doApply()
3227 ASSERT(m_offset >= 0);
3228 ASSERT(!m_text.isEmpty());
3230 int exceptionCode = 0;
3231 m_node->insertData(m_offset, m_text, exceptionCode);
3232 ASSERT(exceptionCode == 0);
3235 void InsertIntoTextNode::doUnapply()
3238 ASSERT(m_offset >= 0);
3239 ASSERT(!m_text.isEmpty());
3241 int exceptionCode = 0;
3242 m_node->deleteData(m_offset, m_text.length(), exceptionCode);
3243 ASSERT(exceptionCode == 0);
3246 //------------------------------------------------------------------------------------------
3247 // InsertLineBreakCommand
3249 InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
3250 : CompositeEditCommand(document)
3254 bool InsertLineBreakCommand::preservesTypingStyle() const
3259 void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
3261 // Insert the BR after the caret position. In the case the
3262 // position is a block, do an append. We don't want to insert
3263 // the BR *after* the block.
3264 Position upstream(pos.upstream(StayInBlock));
3265 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
3266 if (cb == pos.node())
3267 appendNode(node, cb);
3269 insertNodeAfter(node, pos.node());
3272 void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
3274 // Insert the BR after the caret position. In the case the
3275 // position is a block, do an append. We don't want to insert
3276 // the BR *before* the block.
3277 Position upstream(pos.upstream(StayInBlock));
3278 NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
3279 if (cb == pos.node())
3280 appendNode(node, cb);
3282 insertNodeBefore(node, pos.node());
3285 void InsertLineBreakCommand::doApply()
3288 Selection selection = endingSelection();
3290 ElementImpl *breakNode = createBreakElement(document());
3291 NodeImpl *nodeToInsert = breakNode;
3293 Position pos(selection.start().upstream(StayInBlock));
3295 pos = positionOutsideContainingSpecialElement(pos);
3297 bool atStart = pos.offset() <= pos.node()->caretMinOffset();
3298 bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
3299 bool atEndOfBlock = isLastVisiblePositionInBlock(VisiblePosition(pos, selection.startAffinity()));
3302 LOG(Editing, "input newline case 1");
3303 // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
3304 // This makes the "real" BR we want to insert appear in the rendering without any
3305 // significant side effects (and no real worries either since you can't arrow past
3307 if (pos.node()->id() == ID_BR && pos.offset() == 0) {
3308 // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
3309 insertNodeBefore(nodeToInsert, pos.node());
3312 NodeImpl *next = pos.node()->traverseNextNode();
3313 bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
3314 insertNodeAfterPosition(nodeToInsert, pos);
3315 if (hasTrailingBR) {
3316 setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
3318 else if (!document()->inStrictMode()) {
3319 // Insert an "extra" BR at the end of the block.
3320 ElementImpl *extraBreakNode = createBreakElement(document());
3321 insertNodeAfter(extraBreakNode, nodeToInsert);
3322 setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
3327 LOG(Editing, "input newline case 2");
3328 // Insert node before downstream position, and place caret there as well.
3329 Position endingPosition = pos.downstream(StayInBlock);
3330 insertNodeBeforePosition(nodeToInsert, endingPosition);
3331 setEndingSelection(endingPosition, DOWNSTREAM);
3334 LOG(Editing, "input newline case 3");
3335 // Insert BR after this node. Place caret in the position that is downstream
3336 // of the current position, reckoned before inserting the BR in between.
3337 Position endingPosition = pos.downstream(StayInBlock);
3338 insertNodeAfterPosition(nodeToInsert, pos);
3339 setEndingSelection(endingPosition, DOWNSTREAM);
3342 // Split a text node
3343 LOG(Editing, "input newline case 4");
3344 ASSERT(pos.node()->isTextNode());
3347 int exceptionCode = 0;
3348 TextImpl *textNode = static_cast<TextImpl *>(pos.node());
3349 TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
3350 deleteTextFromNode(textNode, 0, pos.offset());
3351 insertNodeBefore(textBeforeNode, textNode);
3352 insertNodeBefore(nodeToInsert, textNode);
3353 Position endingPosition = Position(textNode, 0);
3355 // Handle whitespace that occurs after the split
3356 document()->updateLayout();
3357 if (!endingPosition.isRenderedCharacter()) {
3358 // Clear out all whitespace and insert one non-breaking space
3359 deleteInsignificantTextDownstream(endingPosition);
3360 insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
3363 setEndingSelection(endingPosition, DOWNSTREAM);
3366 // Handle the case where there is a typing style.
3367 // FIXME: Improve typing style.
3368 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3370 CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
3372 if (typingStyle && typingStyle->length() > 0) {
3373 Selection selectionBeforeStyle = endingSelection();
3375 DOM::RangeImpl *rangeAroundNode = document()->createRange();
3377 rangeAroundNode->selectNode(nodeToInsert, exception);
3379 // affinity is not really important since this is a temp selection
3380 // just for calling applyStyle
3381 setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
3382 applyStyle(typingStyle);
3384 setEndingSelection(selectionBeforeStyle);
3387 rebalanceWhitespace();
3390 //------------------------------------------------------------------------------------------
3391 // InsertNodeBeforeCommand
3393 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
3394 : EditCommand(document), m_insertChild(insertChild), m_refChild(refChild)
3396 ASSERT(m_insertChild);
3397 m_insertChild->ref();
3403 InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
3405 ASSERT(m_insertChild);
3406 m_insertChild->deref();
3409 m_refChild->deref();
3412 void InsertNodeBeforeCommand::doApply()
3414 ASSERT(m_insertChild);
3416 ASSERT(m_refChild->parentNode());
3418 int exceptionCode = 0;
3419 m_refChild->parentNode()->insertBefore(m_insertChild, m_refChild, exceptionCode);
3420 ASSERT(exceptionCode == 0);
3423 void InsertNodeBeforeCommand::doUnapply()
3425 ASSERT(m_insertChild);
3427 ASSERT(m_refChild->parentNode());
3429 int exceptionCode = 0;
3430 m_refChild->parentNode()->removeChild(m_insertChild, exceptionCode);
3431 ASSERT(exceptionCode == 0);
3434 //------------------------------------------------------------------------------------------
3435 // InsertParagraphSeparatorCommand
3437 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
3438 : CompositeEditCommand(document), m_style(0)
3442 InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
3444 derefNodesInList(clonedNodes);
3449 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
3454 ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
3456 ElementImpl *element = createDefaultParagraphElement(document());
3458 clonedNodes.append(element);
3462 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
3464 // It is only important to set a style to apply later if we're at the boundaries of
3465 // a paragraph. Otherwise, content that is moved as part of the work of the command
3466 // will lend their styles to the new paragraph without any extra work needed.
3467 VisiblePosition visiblePos(pos, UPSTREAM);
3468 if (!isFirstVisiblePositionInParagraph(visiblePos) && !isLastVisiblePositionInParagraph(visiblePos))
3473 m_style = styleAtPosition(pos);
3477 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
3479 // FIXME: Improve typing style.
3480 // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
3484 CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());