2 * Copyright (C) 2005, 2006 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.
27 #include "ApplyStyleCommand.h"
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSParser.h"
31 #include "CSSProperty.h"
32 #include "CSSPropertyNames.h"
34 #include "HTMLElement.h"
35 #include "HTMLInterchange.h"
36 #include "HTMLNames.h"
39 #include "RenderObject.h"
41 #include "TextIterator.h"
42 #include "htmlediting.h"
43 #include "visible_units.h"
47 using namespace HTMLNames
;
51 enum ELegacyHTMLStyles
{ DoNotUseLegacyHTMLStyles
, UseLegacyHTMLStyles
};
53 explicit StyleChange(CSSStyleDeclaration
*, ELegacyHTMLStyles usesLegacyStyles
=UseLegacyHTMLStyles
);
54 StyleChange(CSSStyleDeclaration
*, const Position
&, ELegacyHTMLStyles usesLegacyStyles
=UseLegacyHTMLStyles
);
56 static ELegacyHTMLStyles
styleModeForParseMode(bool);
58 String
cssStyle() const { return m_cssStyle
; }
59 bool applyBold() const { return m_applyBold
; }
60 bool applyItalic() const { return m_applyItalic
; }
61 bool applyFontColor() const { return m_applyFontColor
.length() > 0; }
62 bool applyFontFace() const { return m_applyFontFace
.length() > 0; }
63 bool applyFontSize() const { return m_applyFontSize
.length() > 0; }
65 String
fontColor() { return m_applyFontColor
; }
66 String
fontFace() { return m_applyFontFace
; }
67 String
fontSize() { return m_applyFontSize
; }
69 bool usesLegacyStyles() const { return m_usesLegacyStyles
; }
72 void init(PassRefPtr
<CSSStyleDeclaration
>, const Position
&);
73 bool checkForLegacyHTMLStyleChange(const CSSProperty
*);
74 static bool currentlyHasStyle(const Position
&, const CSSProperty
*);
79 String m_applyFontColor
;
80 String m_applyFontFace
;
81 String m_applyFontSize
;
82 bool m_usesLegacyStyles
;
87 StyleChange::StyleChange(CSSStyleDeclaration
*style
, ELegacyHTMLStyles usesLegacyStyles
)
88 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles
)
90 init(style
, Position());
93 StyleChange::StyleChange(CSSStyleDeclaration
*style
, const Position
&position
, ELegacyHTMLStyles usesLegacyStyles
)
94 : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles
)
96 init(style
, position
);
99 void StyleChange::init(PassRefPtr
<CSSStyleDeclaration
> style
, const Position
&position
)
101 RefPtr
<CSSMutableStyleDeclaration
> mutableStyle
= style
->makeMutable();
103 String
styleText("");
105 DeprecatedValueListConstIterator
<CSSProperty
> end
;
106 for (DeprecatedValueListConstIterator
<CSSProperty
> it
= mutableStyle
->valuesIterator(); it
!= end
; ++it
) {
107 const CSSProperty
*property
= &*it
;
109 // If position is empty or the position passed in already has the
110 // style, just move on.
111 if (position
.isNotNull() && currentlyHasStyle(position
, property
))
114 // Changing the whitespace style in a tab span would collapse the tab into a space.
115 if (property
->id() == CSSPropertyWhiteSpace
&& (isTabSpanTextNode(position
.node()) || isTabSpanNode((position
.node()))))
118 // If needed, figure out if this change is a legacy HTML style change.
119 if (m_usesLegacyStyles
&& checkForLegacyHTMLStyleChange(property
))
124 if (property
->id() == CSSPropertyWebkitTextDecorationsInEffect
) {
125 // we have to special-case text decorations
126 CSSProperty alteredProperty
= CSSProperty(CSSPropertyTextDecoration
, property
->value(), property
->isImportant());
127 styleText
+= alteredProperty
.cssText();
129 styleText
+= property
->cssText();
132 // Save the result for later
133 m_cssStyle
= styleText
.stripWhiteSpace();
136 StyleChange::ELegacyHTMLStyles
StyleChange::styleModeForParseMode(bool isQuirksMode
)
138 return isQuirksMode
? UseLegacyHTMLStyles
: DoNotUseLegacyHTMLStyles
;
141 bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty
*property
)
143 if (!property
|| !property
->value()) {
147 String
valueText(property
->value()->cssText());
148 switch (property
->id()) {
149 case CSSPropertyFontWeight
:
150 if (equalIgnoringCase(valueText
, "bold")) {
155 case CSSPropertyFontStyle
:
156 if (equalIgnoringCase(valueText
, "italic") || equalIgnoringCase(valueText
, "oblique")) {
157 m_applyItalic
= true;
161 case CSSPropertyColor
: {
163 CSSParser::parseColor(rgba
, valueText
);
165 m_applyFontColor
= color
.name();
168 case CSSPropertyFontFamily
:
169 m_applyFontFace
= valueText
;
171 case CSSPropertyFontSize
:
172 if (property
->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE
) {
173 CSSPrimitiveValue
*value
= static_cast<CSSPrimitiveValue
*>(property
->value());
175 if (value
->primitiveType() < CSSPrimitiveValue::CSS_PX
|| value
->primitiveType() > CSSPrimitiveValue::CSS_PC
)
176 // Size keyword or relative unit.
179 float number
= value
->getFloatValue(CSSPrimitiveValue::CSS_PX
);
181 m_applyFontSize
= "1";
182 else if (number
<= 10)
183 m_applyFontSize
= "2";
184 else if (number
<= 13)
185 m_applyFontSize
= "3";
186 else if (number
<= 16)
187 m_applyFontSize
= "4";
188 else if (number
<= 18)
189 m_applyFontSize
= "5";
190 else if (number
<= 24)
191 m_applyFontSize
= "6";
193 m_applyFontSize
= "7";
194 // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write
195 // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all,
196 // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false.
200 // Can't make sense of the number. Put no font size.
207 bool StyleChange::currentlyHasStyle(const Position
&pos
, const CSSProperty
*property
)
209 ASSERT(pos
.isNotNull());
210 RefPtr
<CSSComputedStyleDeclaration
> style
= pos
.computedStyle();
211 RefPtr
<CSSValue
> value
= style
->getPropertyCSSValue(property
->id(), DoNotUpdateLayout
);
214 return equalIgnoringCase(value
->cssText(), property
->value()->cssText());
217 static String
&styleSpanClassString()
219 static String styleSpanClassString
= AppleStyleSpanClass
;
220 return styleSpanClassString
;
223 bool isStyleSpan(const Node
*node
)
225 if (!node
|| !node
->isHTMLElement())
228 const HTMLElement
*elem
= static_cast<const HTMLElement
*>(node
);
229 return elem
->hasLocalName(spanAttr
) && elem
->getAttribute(classAttr
) == styleSpanClassString();
232 static bool isUnstyledStyleSpan(const Node
*node
)
234 if (!node
|| !node
->isHTMLElement() || !node
->hasTagName(spanTag
))
237 const HTMLElement
*elem
= static_cast<const HTMLElement
*>(node
);
238 CSSMutableStyleDeclaration
*inlineStyleDecl
= elem
->inlineStyleDecl();
239 return (!inlineStyleDecl
|| inlineStyleDecl
->length() == 0) && elem
->getAttribute(classAttr
) == styleSpanClassString();
242 static bool isEmptyFontTag(const Node
*node
)
244 if (!node
|| !node
->hasTagName(fontTag
))
247 const Element
*elem
= static_cast<const Element
*>(node
);
248 NamedAttrMap
*map
= elem
->attributes(true); // true for read-only
249 return (!map
|| map
->length() == 1) && elem
->getAttribute(classAttr
) == styleSpanClassString();
252 static PassRefPtr
<Element
> createFontElement(Document
* document
)
254 ExceptionCode ec
= 0;
255 RefPtr
<Element
> fontNode
= document
->createElementNS(xhtmlNamespaceURI
, "font", ec
);
257 fontNode
->setAttribute(classAttr
, styleSpanClassString());
258 return fontNode
.release();
261 PassRefPtr
<HTMLElement
> createStyleSpanElement(Document
* document
)
263 ExceptionCode ec
= 0;
264 RefPtr
<Element
> styleElement
= document
->createElementNS(xhtmlNamespaceURI
, "span", ec
);
266 styleElement
->setAttribute(classAttr
, styleSpanClassString());
267 return static_pointer_cast
<HTMLElement
>(styleElement
.release());
270 ApplyStyleCommand::ApplyStyleCommand(Document
* document
, CSSStyleDeclaration
* style
, EditAction editingAction
, EPropertyLevel propertyLevel
)
271 : CompositeEditCommand(document
)
272 , m_style(style
->makeMutable())
273 , m_editingAction(editingAction
)
274 , m_propertyLevel(propertyLevel
)
275 , m_start(endingSelection().start().downstream())
276 , m_end(endingSelection().end().upstream())
277 , m_useEndingSelection(true)
278 , m_styledInlineElement(0)
279 , m_removeOnly(false)
283 ApplyStyleCommand::ApplyStyleCommand(Document
* document
, CSSStyleDeclaration
* style
, const Position
& start
, const Position
& end
, EditAction editingAction
, EPropertyLevel propertyLevel
)
284 : CompositeEditCommand(document
)
285 , m_style(style
->makeMutable())
286 , m_editingAction(editingAction
)
287 , m_propertyLevel(propertyLevel
)
290 , m_useEndingSelection(false)
291 , m_styledInlineElement(0)
292 , m_removeOnly(false)
296 ApplyStyleCommand::ApplyStyleCommand(Element
* element
, bool removeOnly
, EditAction editingAction
)
297 : CompositeEditCommand(element
->document())
298 , m_style(CSSMutableStyleDeclaration::create())
299 , m_editingAction(editingAction
)
300 , m_propertyLevel(PropertyDefault
)
301 , m_start(endingSelection().start().downstream())
302 , m_end(endingSelection().end().upstream())
303 , m_useEndingSelection(true)
304 , m_styledInlineElement(element
)
305 , m_removeOnly(removeOnly
)
309 void ApplyStyleCommand::updateStartEnd(const Position
& newStart
, const Position
& newEnd
)
311 ASSERT(Range::compareBoundaryPoints(newEnd
, newStart
) >= 0);
313 if (!m_useEndingSelection
&& (newStart
!= m_start
|| newEnd
!= m_end
))
314 m_useEndingSelection
= true;
316 setEndingSelection(Selection(newStart
, newEnd
, VP_DEFAULT_AFFINITY
));
321 Position
ApplyStyleCommand::startPosition()
323 if (m_useEndingSelection
)
324 return endingSelection().start();
329 Position
ApplyStyleCommand::endPosition()
331 if (m_useEndingSelection
)
332 return endingSelection().end();
337 void ApplyStyleCommand::doApply()
339 switch (m_propertyLevel
) {
340 case PropertyDefault
: {
341 // apply the block-centric properties of the style
342 RefPtr
<CSSMutableStyleDeclaration
> blockStyle
= m_style
->copyBlockProperties();
343 if (blockStyle
->length())
344 applyBlockStyle(blockStyle
.get());
345 // apply any remaining styles to the inline elements
346 // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
347 if (blockStyle
->length() < m_style
->length() || m_styledInlineElement
) {
348 RefPtr
<CSSMutableStyleDeclaration
> inlineStyle
= m_style
->copy();
349 applyRelativeFontStyleChange(inlineStyle
.get());
350 blockStyle
->diff(inlineStyle
.get());
351 applyInlineStyle(inlineStyle
.get());
355 case ForceBlockProperties
:
356 // Force all properties to be applied as block styles.
357 applyBlockStyle(m_style
.get());
362 EditAction
ApplyStyleCommand::editingAction() const
364 return m_editingAction
;
367 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration
*style
)
369 // update document layout once before removing styles
370 // so that we avoid the expense of updating before each and every call
371 // to check a computed style
374 // get positions we want to use for applying style
375 Position start
= startPosition();
376 Position end
= endPosition();
377 if (Range::compareBoundaryPoints(end
, start
) < 0) {
378 Position swap
= start
;
383 VisiblePosition
visibleStart(start
);
384 VisiblePosition
visibleEnd(end
);
385 // Save and restore the selection endpoints using their indices in the document, since
386 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
387 // Calculate start and end indices from the start of the tree that they're in.
388 Node
* scope
= highestAncestor(visibleStart
.deepEquivalent().node());
389 Position
rangeStart(scope
, 0);
390 RefPtr
<Range
> startRange
= Range::create(document(), rangeStart
, rangeCompliantEquivalent(visibleStart
.deepEquivalent()));
391 RefPtr
<Range
> endRange
= Range::create(document(), rangeStart
, rangeCompliantEquivalent(visibleEnd
.deepEquivalent()));
392 int startIndex
= TextIterator::rangeLength(startRange
.get(), true);
393 int endIndex
= TextIterator::rangeLength(endRange
.get(), true);
395 VisiblePosition
paragraphStart(startOfParagraph(visibleStart
));
396 VisiblePosition
nextParagraphStart(endOfParagraph(paragraphStart
).next());
397 VisiblePosition
beyondEnd(endOfParagraph(visibleEnd
).next());
398 while (paragraphStart
.isNotNull() && paragraphStart
!= beyondEnd
) {
399 StyleChange
styleChange(style
, paragraphStart
.deepEquivalent(), StyleChange::styleModeForParseMode(document()->inCompatMode()));
400 if (styleChange
.cssStyle().length() > 0 || m_removeOnly
) {
401 RefPtr
<Node
> block
= enclosingBlock(paragraphStart
.deepEquivalent().node());
402 RefPtr
<Node
> newBlock
= moveParagraphContentsToNewBlockIfNecessary(paragraphStart
.deepEquivalent());
405 ASSERT(block
->isHTMLElement());
406 if (block
->isHTMLElement()) {
407 removeCSSStyle(style
, static_cast<HTMLElement
*>(block
.get()));
409 addBlockStyle(styleChange
, static_cast<HTMLElement
*>(block
.get()));
412 paragraphStart
= nextParagraphStart
;
413 nextParagraphStart
= endOfParagraph(paragraphStart
).next();
416 startRange
= TextIterator::rangeFromLocationAndLength(static_cast<Element
*>(scope
), startIndex
, 0, true);
417 endRange
= TextIterator::rangeFromLocationAndLength(static_cast<Element
*>(scope
), endIndex
, 0, true);
418 if (startRange
&& endRange
)
419 updateStartEnd(startRange
->startPosition(), endRange
->startPosition());
422 #define NoFontDelta (0.0f)
423 #define MinimumFontSize (0.1f)
425 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration
*style
)
427 RefPtr
<CSSValue
> value
= style
->getPropertyCSSValue(CSSPropertyFontSize
);
429 // Explicit font size overrides any delta.
430 style
->removeProperty(CSSPropertyWebkitFontSizeDelta
);
434 // Get the adjustment amount out of the style.
435 value
= style
->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta
);
438 float adjustment
= NoFontDelta
;
439 if (value
->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE
) {
440 CSSPrimitiveValue
*primitiveValue
= static_cast<CSSPrimitiveValue
*>(value
.get());
441 if (primitiveValue
->primitiveType() == CSSPrimitiveValue::CSS_PX
) {
442 // Only PX handled now. If we handle more types in the future, perhaps
443 // a switch statement here would be more appropriate.
444 adjustment
= primitiveValue
->getFloatValue();
447 style
->removeProperty(CSSPropertyWebkitFontSizeDelta
);
448 if (adjustment
== NoFontDelta
)
451 Position start
= startPosition();
452 Position end
= endPosition();
453 if (Range::compareBoundaryPoints(end
, start
) < 0) {
454 Position swap
= start
;
459 // Join up any adjacent text nodes.
460 if (start
.node()->isTextNode()) {
461 joinChildTextNodes(start
.node()->parentNode(), start
, end
);
462 start
= startPosition();
465 if (end
.node()->isTextNode() && start
.node()->parentNode() != end
.node()->parentNode()) {
466 joinChildTextNodes(end
.node()->parentNode(), start
, end
);
467 start
= startPosition();
471 // Split the start text nodes if needed to apply style.
472 bool splitStart
= splitTextAtStartIfNeeded(start
, end
);
474 start
= startPosition();
477 bool splitEnd
= splitTextAtEndIfNeeded(start
, end
);
479 start
= startPosition();
483 // Calculate loop end point.
484 // If the end node is before the start node (can only happen if the end node is
485 // an ancestor of the start node), we gather nodes up to the next sibling of the end node
487 if (start
.node()->isDescendantOf(end
.node()))
488 beyondEnd
= end
.node()->traverseNextSibling();
490 beyondEnd
= end
.node()->traverseNextNode();
492 start
= start
.upstream(); // Move upstream to ensure we do not add redundant spans.
493 Node
*startNode
= start
.node();
494 if (startNode
->isTextNode() && start
.offset() >= caretMaxOffset(startNode
)) // Move out of text node if range does not include its characters.
495 startNode
= startNode
->traverseNextNode();
497 // Store away font size before making any changes to the document.
498 // This ensures that changes to one node won't effect another.
499 HashMap
<Node
*, float> startingFontSizes
;
500 for (Node
*node
= startNode
; node
!= beyondEnd
; node
= node
->traverseNextNode())
501 startingFontSizes
.set(node
, computedFontSize(node
));
503 // These spans were added by us. If empty after font size changes, they can be removed.
504 DeprecatedPtrList
<Node
> unstyledSpans
;
506 Node
*lastStyledNode
= 0;
507 for (Node
*node
= startNode
; node
!= beyondEnd
; node
= node
->traverseNextNode()) {
508 HTMLElement
*elem
= 0;
509 if (node
->isHTMLElement()) {
510 // Only work on fully selected nodes.
511 if (!nodeFullySelected(node
, start
, end
))
513 elem
= static_cast<HTMLElement
*>(node
);
514 } else if (node
->isTextNode() && node
->renderer() && node
->parentNode() != lastStyledNode
) {
515 // Last styled node was not parent node of this text node, but we wish to style this
516 // text node. To make this possible, add a style span to surround this text node.
517 RefPtr
<HTMLElement
> span
= createStyleSpanElement(document());
518 insertNodeBefore(span
.get(), node
);
519 surroundNodeRangeWithElement(node
, node
, span
.get());
522 // Only handle HTML elements and text nodes.
525 lastStyledNode
= node
;
527 CSSMutableStyleDeclaration
* inlineStyleDecl
= elem
->getInlineStyleDecl();
528 float currentFontSize
= computedFontSize(node
);
529 float desiredFontSize
= max(MinimumFontSize
, startingFontSizes
.get(node
) + adjustment
);
530 RefPtr
<CSSValue
> value
= inlineStyleDecl
->getPropertyCSSValue(CSSPropertyFontSize
);
532 inlineStyleDecl
->removeProperty(CSSPropertyFontSize
, true);
533 currentFontSize
= computedFontSize(node
);
535 if (currentFontSize
!= desiredFontSize
) {
536 inlineStyleDecl
->setProperty(CSSPropertyFontSize
, String::number(desiredFontSize
) + "px", false, false);
537 setNodeAttribute(elem
, styleAttr
, inlineStyleDecl
->cssText());
539 if (inlineStyleDecl
->length() == 0) {
540 removeNodeAttribute(elem
, styleAttr
);
541 if (isUnstyledStyleSpan(elem
))
542 unstyledSpans
.append(elem
);
546 for (DeprecatedPtrListIterator
<Node
> it(unstyledSpans
); it
.current(); ++it
)
547 removeNodePreservingChildren(it
.current());
551 #undef MinimumFontSize
553 static Node
* dummySpanAncestorForNode(const Node
* node
)
555 while (node
&& !isStyleSpan(node
))
556 node
= node
->parent();
558 return node
? node
->parent() : 0;
561 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node
* dummySpanAncestor
)
563 if (!dummySpanAncestor
)
566 // Dummy spans are created when text node is split, so that style information
567 // can be propagated, which can result in more splitting. If a dummy span gets
568 // cloned/split, the new node is always a sibling of it. Therefore, we scan
569 // all the children of the dummy's parent
571 for (Node
* node
= dummySpanAncestor
->firstChild(); node
; node
= next
) {
572 next
= node
->nextSibling();
573 if (isUnstyledStyleSpan(node
))
574 removeNodePreservingChildren(node
);
579 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration
*style
)
581 Node
* startDummySpanAncestor
= 0;
582 Node
* endDummySpanAncestor
= 0;
584 // update document layout once before removing styles
585 // so that we avoid the expense of updating before each and every call
586 // to check a computed style
589 // adjust to the positions we want to use for applying style
590 Position start
= startPosition();
591 Position end
= endPosition();
592 if (Range::compareBoundaryPoints(end
, start
) < 0) {
593 Position swap
= start
;
598 // split the start node and containing element if the selection starts inside of it
599 bool splitStart
= splitTextElementAtStartIfNeeded(start
, end
);
601 start
= startPosition();
603 startDummySpanAncestor
= dummySpanAncestorForNode(start
.node());
606 // split the end node and containing element if the selection ends inside of it
607 bool splitEnd
= splitTextElementAtEndIfNeeded(start
, end
);
609 start
= startPosition();
611 endDummySpanAncestor
= dummySpanAncestorForNode(end
.node());
614 // Remove style from the selection.
615 // Use the upstream position of the start for removing style.
616 // This will ensure we remove all traces of the relevant styles from the selection
617 // and prevent us from adding redundant ones, as described in:
618 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
619 removeInlineStyle(style
, start
.upstream(), end
);
620 start
= startPosition();
624 bool mergedStart
= mergeStartWithPreviousIfIdentical(start
, end
);
626 start
= startPosition();
632 mergeEndWithNextIfIdentical(start
, end
);
633 start
= startPosition();
637 // update document layout once before running the rest of the function
638 // so that we avoid the expense of updating before each and every call
639 // to check a computed style
642 Node
* node
= start
.node();
644 bool rangeIsEmpty
= false;
646 if (start
.offset() >= caretMaxOffset(start
.node())) {
647 node
= node
->traverseNextNode();
648 Position newStart
= Position(node
, 0);
649 if (Range::compareBoundaryPoints(end
, newStart
) < 0)
654 // FIXME: Callers should perform this operation on a Range that includes the br
655 // if they want style applied to the empty line.
656 if (start
== end
&& start
.node()->hasTagName(brTag
))
657 end
= positionAfterNode(start
.node());
658 // Add the style to selected inline runs.
659 Node
* pastLast
= Range::create(document(), rangeCompliantEquivalent(start
), rangeCompliantEquivalent(end
))->pastLastNode();
660 for (Node
* next
; node
&& node
!= pastLast
; node
= next
) {
662 next
= node
->traverseNextNode();
664 if (!node
->renderer() || !node
->isContentEditable())
667 if (!node
->isContentRichlyEditable() && node
->isHTMLElement()) {
668 // This is a plaintext-only region. Only proceed if it's fully selected.
669 if (end
.node()->isDescendantOf(node
))
671 // Add to this element's inline style and skip over its contents.
672 HTMLElement
* element
= static_cast<HTMLElement
*>(node
);
673 RefPtr
<CSSMutableStyleDeclaration
> inlineStyle
= element
->getInlineStyleDecl()->copy();
674 inlineStyle
->merge(style
);
675 setNodeAttribute(element
, styleAttr
, inlineStyle
->cssText());
676 next
= node
->traverseNextSibling();
683 if (node
->childNodeCount()) {
684 if (editingIgnoresContent(node
)) {
685 next
= node
->traverseNextSibling();
691 Node
* runStart
= node
;
692 // Find the end of the run.
693 Node
* sibling
= node
->nextSibling();
694 while (sibling
&& sibling
!= pastLast
&& (!sibling
->isElementNode() || sibling
->hasTagName(brTag
)) && !isBlock(sibling
)) {
696 sibling
= node
->nextSibling();
698 // Recompute next, since node has changed.
699 next
= node
->traverseNextNode();
700 // Apply the style to the run.
701 addInlineStyleIfNeeded(style
, runStart
, node
);
705 // Remove dummy style spans created by splitting text elements.
706 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor
);
707 if (endDummySpanAncestor
!= startDummySpanAncestor
)
708 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor
);
711 bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration
*style
, HTMLElement
*elem
)
713 DeprecatedValueListConstIterator
<CSSProperty
> end
;
714 for (DeprecatedValueListConstIterator
<CSSProperty
> it
= style
->valuesIterator(); it
!= end
; ++it
) {
715 switch ((*it
).id()) {
716 case CSSPropertyFontWeight
:
717 if (elem
->hasLocalName(bTag
))
720 case CSSPropertyFontStyle
:
721 if (elem
->hasLocalName(iTag
))
729 void ApplyStyleCommand::removeHTMLStyleNode(HTMLElement
*elem
)
731 // This node can be removed.
732 // EDIT FIXME: This does not handle the case where the node
733 // has attributes. But how often do people add attributes to <B> tags?
734 // Not so often I think.
736 removeNodePreservingChildren(elem
);
739 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration
*style
, HTMLElement
*elem
)
744 if (!elem
->hasLocalName(fontTag
))
747 DeprecatedValueListConstIterator
<CSSProperty
> end
;
748 for (DeprecatedValueListConstIterator
<CSSProperty
> it
= style
->valuesIterator(); it
!= end
; ++it
) {
749 switch ((*it
).id()) {
750 case CSSPropertyColor
:
751 removeNodeAttribute(elem
, colorAttr
);
753 case CSSPropertyFontFamily
:
754 removeNodeAttribute(elem
, faceAttr
);
756 case CSSPropertyFontSize
:
757 removeNodeAttribute(elem
, sizeAttr
);
762 if (isEmptyFontTag(elem
))
763 removeNodePreservingChildren(elem
);
766 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration
*style
, HTMLElement
*elem
)
771 CSSMutableStyleDeclaration
*decl
= elem
->inlineStyleDecl();
775 DeprecatedValueListConstIterator
<CSSProperty
> end
;
776 for (DeprecatedValueListConstIterator
<CSSProperty
> it
= style
->valuesIterator(); it
!= end
; ++it
) {
777 int propertyID
= (*it
).id();
778 RefPtr
<CSSValue
> value
= decl
->getPropertyCSSValue(propertyID
);
779 if (value
&& (propertyID
!= CSSPropertyWhiteSpace
|| !isTabSpanNode(elem
)))
780 removeCSSProperty(decl
, propertyID
);
783 if (isUnstyledStyleSpan(elem
))
784 removeNodePreservingChildren(elem
);
787 void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclaration
*style
, const Position
&start
, const Position
&end
)
789 ASSERT(start
.isNotNull());
790 ASSERT(end
.isNotNull());
791 ASSERT(start
.node()->inDocument());
792 ASSERT(end
.node()->inDocument());
793 ASSERT(Range::compareBoundaryPoints(start
, end
) <= 0);
797 static bool hasTextDecorationProperty(Node
*node
)
799 if (!node
->isElementNode())
802 RefPtr
<CSSValue
> value
= computedStyle(node
)->getPropertyCSSValue(CSSPropertyTextDecoration
, DoNotUpdateLayout
);
803 return value
&& !equalIgnoringCase(value
->cssText(), "none");
806 static Node
* highestAncestorWithTextDecoration(Node
*node
)
810 for (Node
*n
= node
; n
; n
= n
->parentNode()) {
811 if (hasTextDecorationProperty(n
))
818 PassRefPtr
<CSSMutableStyleDeclaration
> ApplyStyleCommand::extractTextDecorationStyle(Node
* node
)
821 ASSERT(node
->isElementNode());
823 // non-html elements not handled yet
824 if (!node
->isHTMLElement())
827 HTMLElement
*element
= static_cast<HTMLElement
*>(node
);
828 RefPtr
<CSSMutableStyleDeclaration
> style
= element
->inlineStyleDecl();
832 int properties
[1] = { CSSPropertyTextDecoration
};
833 RefPtr
<CSSMutableStyleDeclaration
> textDecorationStyle
= style
->copyPropertiesInSet(properties
, 1);
835 RefPtr
<CSSValue
> property
= style
->getPropertyCSSValue(CSSPropertyTextDecoration
);
836 if (property
&& !equalIgnoringCase(property
->cssText(), "none"))
837 removeCSSProperty(style
.get(), CSSPropertyTextDecoration
);
839 return textDecorationStyle
.release();
842 PassRefPtr
<CSSMutableStyleDeclaration
> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node
* node
)
845 ASSERT(node
->isElementNode());
847 // non-html elements not handled yet
848 if (!node
->isHTMLElement())
851 RefPtr
<CSSComputedStyleDeclaration
> nodeStyle
= computedStyle(node
);
854 int properties
[1] = { CSSPropertyTextDecoration
};
855 RefPtr
<CSSMutableStyleDeclaration
> textDecorationStyle
= nodeStyle
->copyPropertiesInSet(properties
, 1);
857 RefPtr
<CSSValue
> property
= nodeStyle
->getPropertyCSSValue(CSSPropertyTextDecoration
);
858 if (property
&& !equalIgnoringCase(property
->cssText(), "none")) {
859 RefPtr
<CSSMutableStyleDeclaration
> newStyle
= textDecorationStyle
->copy();
860 newStyle
->setProperty(CSSPropertyTextDecoration
, "none");
861 applyTextDecorationStyle(node
, newStyle
.get());
864 return textDecorationStyle
.release();
867 void ApplyStyleCommand::applyTextDecorationStyle(Node
*node
, CSSMutableStyleDeclaration
*style
)
871 if (!style
|| !style
->cssText().length())
874 if (node
->isTextNode()) {
875 RefPtr
<HTMLElement
> styleSpan
= createStyleSpanElement(document());
876 insertNodeBefore(styleSpan
.get(), node
);
877 surroundNodeRangeWithElement(node
, node
, styleSpan
.get());
878 node
= styleSpan
.get();
881 if (!node
->isElementNode())
884 HTMLElement
*element
= static_cast<HTMLElement
*>(node
);
886 StyleChange
styleChange(style
, Position(element
, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
887 if (styleChange
.cssStyle().length() > 0) {
888 String cssText
= styleChange
.cssStyle();
889 CSSMutableStyleDeclaration
*decl
= element
->inlineStyleDecl();
891 cssText
+= decl
->cssText();
892 setNodeAttribute(element
, styleAttr
, cssText
);
896 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node
*node
, const Position
&start
, const Position
&end
, bool force
)
898 Node
*highestAncestor
= highestAncestorWithTextDecoration(node
);
900 if (highestAncestor
) {
903 for (Node
*current
= highestAncestor
; current
!= node
; current
= nextCurrent
) {
908 RefPtr
<CSSMutableStyleDeclaration
> decoration
= force
? extractAndNegateTextDecorationStyle(current
) : extractTextDecorationStyle(current
);
910 for (Node
*child
= current
->firstChild(); child
; child
= nextChild
) {
911 nextChild
= child
->nextSibling();
915 } else if (node
->isDescendantOf(child
)) {
916 applyTextDecorationStyle(child
, decoration
.get());
919 applyTextDecorationStyle(child
, decoration
.get());
926 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position
&start
, const Position
&end
)
928 // We need to work in two passes. First we push down any inline
929 // styles that set text decoration. Then we look for any remaining
930 // styles (caused by stylesheets) and explicitly negate text
931 // decoration while pushing down.
933 pushDownTextDecorationStyleAroundNode(start
.node(), start
, end
, false);
935 pushDownTextDecorationStyleAroundNode(start
.node(), start
, end
, true);
937 pushDownTextDecorationStyleAroundNode(end
.node(), start
, end
, false);
939 pushDownTextDecorationStyleAroundNode(end
.node(), start
, end
, true);
942 static int maxRangeOffset(Node
*n
)
944 if (n
->offsetInCharacters())
945 return n
->maxCharacterOffset();
947 if (n
->isElementNode())
948 return n
->childNodeCount();
953 void ApplyStyleCommand::removeInlineStyle(PassRefPtr
<CSSMutableStyleDeclaration
> style
, const Position
&start
, const Position
&end
)
955 ASSERT(start
.isNotNull());
956 ASSERT(end
.isNotNull());
957 ASSERT(start
.node()->inDocument());
958 ASSERT(end
.node()->inDocument());
959 ASSERT(Range::compareBoundaryPoints(start
, end
) <= 0);
961 RefPtr
<CSSValue
> textDecorationSpecialProperty
= style
->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect
);
963 if (textDecorationSpecialProperty
) {
964 pushDownTextDecorationStyleAtBoundaries(start
.downstream(), end
.upstream());
965 style
= style
->copy();
966 style
->setProperty(CSSPropertyTextDecoration
, textDecorationSpecialProperty
->cssText(), style
->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect
));
969 // The s and e variables store the positions used to set the ending selection after style removal
970 // takes place. This will help callers to recognize when either the start node or the end node
971 // are removed from the document during the work of this function.
975 Node
*node
= start
.node();
977 Node
*next
= node
->traverseNextNode();
978 if (node
->isHTMLElement() && nodeFullySelected(node
, start
, end
)) {
979 HTMLElement
*elem
= static_cast<HTMLElement
*>(node
);
980 Node
*prev
= elem
->traversePreviousNodePostOrder();
981 Node
*next
= elem
->traverseNextNode();
982 if (m_styledInlineElement
&& elem
->hasTagName(m_styledInlineElement
->tagQName()))
983 removeNodePreservingChildren(elem
);
984 if (isHTMLStyleNode(style
.get(), elem
))
985 removeHTMLStyleNode(elem
);
987 removeHTMLFontStyle(style
.get(), elem
);
988 removeCSSStyle(style
.get(), elem
);
990 if (!elem
->inDocument()) {
991 if (s
.node() == elem
) {
992 // Since elem must have been fully selected, and it is at the start
993 // of the selection, it is clear we can set the new s offset to 0.
994 ASSERT(s
.offset() <= caretMinOffset(s
.node()));
995 s
= Position(next
, 0);
997 if (e
.node() == elem
) {
998 // Since elem must have been fully selected, and it is at the end
999 // of the selection, it is clear we can set the new e offset to
1000 // the max range offset of prev.
1001 ASSERT(e
.offset() >= maxRangeOffset(e
.node()));
1002 e
= Position(prev
, maxRangeOffset(prev
));
1006 if (node
== end
.node())
1011 ASSERT(s
.node()->inDocument());
1012 ASSERT(e
.node()->inDocument());
1013 updateStartEnd(s
, e
);
1016 bool ApplyStyleCommand::nodeFullySelected(Node
*node
, const Position
&start
, const Position
&end
) const
1019 ASSERT(node
->isElementNode());
1021 Position pos
= Position(node
, node
->childNodeCount()).upstream();
1022 return Range::compareBoundaryPoints(node
, 0, start
.node(), start
.offset()) >= 0 &&
1023 Range::compareBoundaryPoints(pos
, end
) <= 0;
1026 bool ApplyStyleCommand::nodeFullyUnselected(Node
*node
, const Position
&start
, const Position
&end
) const
1029 ASSERT(node
->isElementNode());
1031 Position pos
= Position(node
, node
->childNodeCount()).upstream();
1032 bool isFullyBeforeStart
= Range::compareBoundaryPoints(pos
, start
) < 0;
1033 bool isFullyAfterEnd
= Range::compareBoundaryPoints(node
, 0, end
.node(), end
.offset()) > 0;
1035 return isFullyBeforeStart
|| isFullyAfterEnd
;
1039 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position
&start
, const Position
&end
)
1041 if (start
.node()->isTextNode() && start
.offset() > caretMinOffset(start
.node()) && start
.offset() < caretMaxOffset(start
.node())) {
1042 int endOffsetAdjustment
= start
.node() == end
.node() ? start
.offset() : 0;
1043 Text
*text
= static_cast<Text
*>(start
.node());
1044 splitTextNode(text
, start
.offset());
1045 updateStartEnd(Position(start
.node(), 0), Position(end
.node(), end
.offset() - endOffsetAdjustment
));
1051 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position
&start
, const Position
&end
)
1053 if (end
.node()->isTextNode() && end
.offset() > caretMinOffset(end
.node()) && end
.offset() < caretMaxOffset(end
.node())) {
1054 Text
*text
= static_cast<Text
*>(end
.node());
1055 splitTextNode(text
, end
.offset());
1057 Node
*prevNode
= text
->previousSibling();
1059 Node
*startNode
= start
.node() == end
.node() ? prevNode
: start
.node();
1061 updateStartEnd(Position(startNode
, start
.offset()), Position(prevNode
, caretMaxOffset(prevNode
)));
1067 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position
&start
, const Position
&end
)
1069 if (start
.node()->isTextNode() && start
.offset() > caretMinOffset(start
.node()) && start
.offset() < caretMaxOffset(start
.node())) {
1070 int endOffsetAdjustment
= start
.node() == end
.node() ? start
.offset() : 0;
1071 Text
*text
= static_cast<Text
*>(start
.node());
1072 splitTextNodeContainingElement(text
, start
.offset());
1074 updateStartEnd(Position(start
.node()->parentNode(), start
.node()->nodeIndex()), Position(end
.node(), end
.offset() - endOffsetAdjustment
));
1080 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position
&start
, const Position
&end
)
1082 if (end
.node()->isTextNode() && end
.offset() > caretMinOffset(end
.node()) && end
.offset() < caretMaxOffset(end
.node())) {
1083 Text
*text
= static_cast<Text
*>(end
.node());
1084 splitTextNodeContainingElement(text
, end
.offset());
1086 Node
*prevNode
= text
->parent()->previousSibling()->lastChild();
1088 Node
*startNode
= start
.node() == end
.node() ? prevNode
: start
.node();
1090 updateStartEnd(Position(startNode
, start
.offset()), Position(prevNode
->parent(), prevNode
->nodeIndex() + 1));
1096 static bool areIdenticalElements(Node
*first
, Node
*second
)
1098 // check that tag name and all attribute names and values are identical
1100 if (!first
->isElementNode())
1103 if (!second
->isElementNode())
1106 Element
*firstElement
= static_cast<Element
*>(first
);
1107 Element
*secondElement
= static_cast<Element
*>(second
);
1109 if (!firstElement
->tagQName().matches(secondElement
->tagQName()))
1112 NamedAttrMap
*firstMap
= firstElement
->attributes();
1113 NamedAttrMap
*secondMap
= secondElement
->attributes();
1115 unsigned firstLength
= firstMap
->length();
1117 if (firstLength
!= secondMap
->length())
1120 for (unsigned i
= 0; i
< firstLength
; i
++) {
1121 Attribute
*attribute
= firstMap
->attributeItem(i
);
1122 Attribute
*secondAttribute
= secondMap
->getAttributeItem(attribute
->name());
1124 if (!secondAttribute
|| attribute
->value() != secondAttribute
->value())
1131 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position
&start
, const Position
&end
)
1133 Node
*startNode
= start
.node();
1134 int startOffset
= start
.offset();
1136 if (isAtomicNode(start
.node())) {
1137 if (start
.offset() != 0)
1140 // note: prior siblings could be unrendered elements. it's silly to miss the
1141 // merge opportunity just for that.
1142 if (start
.node()->previousSibling())
1145 startNode
= start
.node()->parent();
1149 if (!startNode
->isElementNode())
1152 if (startOffset
!= 0)
1155 Node
*previousSibling
= startNode
->previousSibling();
1157 if (previousSibling
&& areIdenticalElements(startNode
, previousSibling
)) {
1158 Element
*previousElement
= static_cast<Element
*>(previousSibling
);
1159 Element
*element
= static_cast<Element
*>(startNode
);
1160 Node
*startChild
= element
->firstChild();
1162 mergeIdenticalElements(previousElement
, element
);
1164 int startOffsetAdjustment
= startChild
->nodeIndex();
1165 int endOffsetAdjustment
= startNode
== end
.node() ? startOffsetAdjustment
: 0;
1166 updateStartEnd(Position(startNode
, startOffsetAdjustment
), Position(end
.node(), end
.offset() + endOffsetAdjustment
));
1173 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position
&start
, const Position
&end
)
1175 Node
*endNode
= end
.node();
1176 int endOffset
= end
.offset();
1178 if (isAtomicNode(endNode
)) {
1179 if (endOffset
< caretMaxOffset(endNode
))
1182 unsigned parentLastOffset
= end
.node()->parent()->childNodes()->length() - 1;
1183 if (end
.node()->nextSibling())
1186 endNode
= end
.node()->parent();
1187 endOffset
= parentLastOffset
;
1190 if (!endNode
->isElementNode() || endNode
->hasTagName(brTag
))
1193 Node
*nextSibling
= endNode
->nextSibling();
1195 if (nextSibling
&& areIdenticalElements(endNode
, nextSibling
)) {
1196 Element
*nextElement
= static_cast<Element
*>(nextSibling
);
1197 Element
*element
= static_cast<Element
*>(endNode
);
1198 Node
*nextChild
= nextElement
->firstChild();
1200 mergeIdenticalElements(element
, nextElement
);
1202 Node
*startNode
= start
.node() == endNode
? nextElement
: start
.node();
1205 int endOffset
= nextChild
? nextChild
->nodeIndex() : nextElement
->childNodes()->length();
1206 updateStartEnd(Position(startNode
, start
.offset()), Position(nextElement
, endOffset
));
1213 void ApplyStyleCommand::surroundNodeRangeWithElement(Node
*startNode
, Node
*endNode
, Element
*element
)
1219 Node
*node
= startNode
;
1221 Node
*next
= node
->traverseNextNode();
1222 if (node
->childNodeCount() == 0 && node
->renderer() && node
->renderer()->isInline()) {
1224 appendNode(node
, element
);
1226 if (node
== endNode
)
1230 // FIXME: We should probably call updateStartEnd if the start or end was in the node
1231 // range so that the endingSelection() is canonicalized. See the comments at the end of
1232 // Selection::validate().
1235 void ApplyStyleCommand::addBlockStyle(const StyleChange
& styleChange
, HTMLElement
* block
)
1237 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1242 String cssText
= styleChange
.cssStyle();
1243 CSSMutableStyleDeclaration
* decl
= block
->inlineStyleDecl();
1245 cssText
+= decl
->cssText();
1246 setNodeAttribute(block
, styleAttr
, cssText
);
1249 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration
*style
, Node
*startNode
, Node
*endNode
)
1254 StyleChange
styleChange(style
, Position(startNode
, 0), StyleChange::styleModeForParseMode(document()->inCompatMode()));
1255 ExceptionCode ec
= 0;
1258 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1260 if (styleChange
.applyFontColor() || styleChange
.applyFontFace() || styleChange
.applyFontSize()) {
1261 RefPtr
<Element
> fontElement
= createFontElement(document());
1263 insertNodeBefore(fontElement
.get(), startNode
);
1264 if (styleChange
.applyFontColor())
1265 fontElement
->setAttribute(colorAttr
, styleChange
.fontColor());
1266 if (styleChange
.applyFontFace())
1267 fontElement
->setAttribute(faceAttr
, styleChange
.fontFace());
1268 if (styleChange
.applyFontSize())
1269 fontElement
->setAttribute(sizeAttr
, styleChange
.fontSize());
1270 surroundNodeRangeWithElement(startNode
, endNode
, fontElement
.get());
1273 if (styleChange
.cssStyle().length() > 0) {
1274 RefPtr
<Element
> styleElement
= createStyleSpanElement(document());
1275 styleElement
->setAttribute(styleAttr
, styleChange
.cssStyle());
1276 insertNodeBefore(styleElement
.get(), startNode
);
1277 surroundNodeRangeWithElement(startNode
, endNode
, styleElement
.get());
1280 if (styleChange
.applyBold()) {
1281 RefPtr
<Element
> boldElement
= document()->createElementNS(xhtmlNamespaceURI
, "b", ec
);
1283 insertNodeBefore(boldElement
.get(), startNode
);
1284 surroundNodeRangeWithElement(startNode
, endNode
, boldElement
.get());
1287 if (styleChange
.applyItalic()) {
1288 RefPtr
<Element
> italicElement
= document()->createElementNS(xhtmlNamespaceURI
, "i", ec
);
1290 insertNodeBefore(italicElement
.get(), startNode
);
1291 surroundNodeRangeWithElement(startNode
, endNode
, italicElement
.get());
1294 if (m_styledInlineElement
) {
1295 RefPtr
<Element
> clonedElement
= static_pointer_cast
<Element
>(m_styledInlineElement
->cloneNode(false));
1296 insertNodeBefore(clonedElement
.get(), startNode
);
1297 surroundNodeRangeWithElement(startNode
, endNode
, clonedElement
.get());
1301 float ApplyStyleCommand::computedFontSize(const Node
*node
)
1306 Position
pos(const_cast<Node
*>(node
), 0);
1307 RefPtr
<CSSComputedStyleDeclaration
> computedStyle
= pos
.computedStyle();
1311 RefPtr
<CSSPrimitiveValue
> value
= static_pointer_cast
<CSSPrimitiveValue
>(computedStyle
->getPropertyCSSValue(CSSPropertyFontSize
));
1315 return value
->getFloatValue(CSSPrimitiveValue::CSS_PX
);
1318 void ApplyStyleCommand::joinChildTextNodes(Node
*node
, const Position
&start
, const Position
&end
)
1323 Position newStart
= start
;
1324 Position newEnd
= end
;
1326 Node
*child
= node
->firstChild();
1328 Node
*next
= child
->nextSibling();
1329 if (child
->isTextNode() && next
&& next
->isTextNode()) {
1330 Text
*childText
= static_cast<Text
*>(child
);
1331 Text
*nextText
= static_cast<Text
*>(next
);
1332 if (next
== start
.node())
1333 newStart
= Position(childText
, childText
->length() + start
.offset());
1334 if (next
== end
.node())
1335 newEnd
= Position(childText
, childText
->length() + end
.offset());
1336 String textToMove
= nextText
->data();
1337 insertTextIntoNode(childText
, childText
->length(), textToMove
);
1339 // don't move child node pointer. it may want to merge with more text nodes.
1342 child
= child
->nextSibling();
1346 updateStartEnd(newStart
, newEnd
);