Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2005, 2006, 2008, 2009 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/editing/ApplyStyleCommand.h"
     28 
     29 #include "CSSPropertyNames.h"
     30 #include "CSSValueKeywords.h"
     31 #include "HTMLNames.h"
     32 #include "core/css/CSSComputedStyleDeclaration.h"
     33 #include "core/css/CSSValuePool.h"
     34 #include "core/css/StylePropertySet.h"
     35 #include "core/dom/Document.h"
     36 #include "core/dom/NodeList.h"
     37 #include "core/dom/NodeTraversal.h"
     38 #include "core/dom/Range.h"
     39 #include "core/dom/Text.h"
     40 #include "core/editing/EditingStyle.h"
     41 #include "core/editing/HTMLInterchange.h"
     42 #include "core/editing/TextIterator.h"
     43 #include "core/editing/VisibleUnits.h"
     44 #include "core/editing/htmlediting.h"
     45 #include "core/rendering/RenderObject.h"
     46 #include "core/rendering/RenderText.h"
     47 #include "wtf/StdLibExtras.h"
     48 #include "wtf/text/StringBuilder.h"
     49 
     50 namespace WebCore {
     51 
     52 using namespace HTMLNames;
     53 
     54 static String& styleSpanClassString()
     55 {
     56     DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
     57     return styleSpanClassString;
     58 }
     59 
     60 bool isLegacyAppleStyleSpan(const Node *node)
     61 {
     62     if (!node || !node->isHTMLElement())
     63         return false;
     64 
     65     const HTMLElement* elem = toHTMLElement(node);
     66     return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
     67 }
     68 
     69 static bool hasNoAttributeOrOnlyStyleAttribute(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
     70 {
     71     if (!element->hasAttributes())
     72         return true;
     73 
     74     unsigned matchedAttributes = 0;
     75     if (element->getAttribute(classAttr) == styleSpanClassString())
     76         matchedAttributes++;
     77     if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute
     78         || !element->inlineStyle() || element->inlineStyle()->isEmpty()))
     79         matchedAttributes++;
     80 
     81     ASSERT(matchedAttributes <= element->attributeCount());
     82     return matchedAttributes == element->attributeCount();
     83 }
     84 
     85 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
     86 {
     87     if (!element || !element->hasTagName(spanTag))
     88         return false;
     89     return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute);
     90 }
     91 
     92 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
     93 {
     94     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
     95         return false;
     96     return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty);
     97 }
     98 
     99 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
    100 {
    101     if (!element || !element->hasTagName(fontTag))
    102         return false;
    103 
    104     return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty);
    105 }
    106 
    107 static PassRefPtr<Element> createFontElement(Document* document)
    108 {
    109     RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
    110     return fontNode.release();
    111 }
    112 
    113 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
    114 {
    115     RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
    116     return styleElement.release();
    117 }
    118 
    119 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
    120     : CompositeEditCommand(document)
    121     , m_style(style->copy())
    122     , m_editingAction(editingAction)
    123     , m_propertyLevel(propertyLevel)
    124     , m_start(endingSelection().start().downstream())
    125     , m_end(endingSelection().end().upstream())
    126     , m_useEndingSelection(true)
    127     , m_styledInlineElement(0)
    128     , m_removeOnly(false)
    129     , m_isInlineElementToRemoveFunction(0)
    130 {
    131 }
    132 
    133 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
    134     : CompositeEditCommand(document)
    135     , m_style(style->copy())
    136     , m_editingAction(editingAction)
    137     , m_propertyLevel(propertyLevel)
    138     , m_start(start)
    139     , m_end(end)
    140     , m_useEndingSelection(false)
    141     , m_styledInlineElement(0)
    142     , m_removeOnly(false)
    143     , m_isInlineElementToRemoveFunction(0)
    144 {
    145 }
    146 
    147 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
    148     : CompositeEditCommand(element->document())
    149     , m_style(EditingStyle::create())
    150     , m_editingAction(editingAction)
    151     , m_propertyLevel(PropertyDefault)
    152     , m_start(endingSelection().start().downstream())
    153     , m_end(endingSelection().end().upstream())
    154     , m_useEndingSelection(true)
    155     , m_styledInlineElement(element)
    156     , m_removeOnly(removeOnly)
    157     , m_isInlineElementToRemoveFunction(0)
    158 {
    159 }
    160 
    161 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
    162     : CompositeEditCommand(document)
    163     , m_style(style->copy())
    164     , m_editingAction(editingAction)
    165     , m_propertyLevel(PropertyDefault)
    166     , m_start(endingSelection().start().downstream())
    167     , m_end(endingSelection().end().upstream())
    168     , m_useEndingSelection(true)
    169     , m_styledInlineElement(0)
    170     , m_removeOnly(true)
    171     , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
    172 {
    173 }
    174 
    175 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
    176 {
    177     ASSERT(comparePositions(newEnd, newStart) >= 0);
    178 
    179     if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
    180         m_useEndingSelection = true;
    181 
    182     setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional()));
    183     m_start = newStart;
    184     m_end = newEnd;
    185 }
    186 
    187 Position ApplyStyleCommand::startPosition()
    188 {
    189     if (m_useEndingSelection)
    190         return endingSelection().start();
    191 
    192     return m_start;
    193 }
    194 
    195 Position ApplyStyleCommand::endPosition()
    196 {
    197     if (m_useEndingSelection)
    198         return endingSelection().end();
    199 
    200     return m_end;
    201 }
    202 
    203 void ApplyStyleCommand::doApply()
    204 {
    205     switch (m_propertyLevel) {
    206     case PropertyDefault: {
    207         // Apply the block-centric properties of the style.
    208         RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
    209         if (!blockStyle->isEmpty())
    210             applyBlockStyle(blockStyle.get());
    211         // Apply any remaining styles to the inline elements.
    212         if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
    213             applyRelativeFontStyleChange(m_style.get());
    214             applyInlineStyle(m_style.get());
    215         }
    216         break;
    217     }
    218     case ForceBlockProperties:
    219         // Force all properties to be applied as block styles.
    220         applyBlockStyle(m_style.get());
    221         break;
    222     }
    223 }
    224 
    225 EditAction ApplyStyleCommand::editingAction() const
    226 {
    227     return m_editingAction;
    228 }
    229 
    230 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
    231 {
    232     // update document layout once before removing styles
    233     // so that we avoid the expense of updating before each and every call
    234     // to check a computed style
    235     document()->updateLayoutIgnorePendingStylesheets();
    236 
    237     // get positions we want to use for applying style
    238     Position start = startPosition();
    239     Position end = endPosition();
    240     if (comparePositions(end, start) < 0) {
    241         Position swap = start;
    242         start = end;
    243         end = swap;
    244     }
    245 
    246     VisiblePosition visibleStart(start);
    247     VisiblePosition visibleEnd(end);
    248 
    249     if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
    250         return;
    251 
    252     // Save and restore the selection endpoints using their indices in the document, since
    253     // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
    254     // Calculate start and end indices from the start of the tree that they're in.
    255     Node* scope = highestAncestor(visibleStart.deepEquivalent().deprecatedNode());
    256     RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
    257     RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
    258     int startIndex = TextIterator::rangeLength(startRange.get(), true);
    259     int endIndex = TextIterator::rangeLength(endRange.get(), true);
    260 
    261     VisiblePosition paragraphStart(startOfParagraph(visibleStart));
    262     VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
    263     VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
    264     while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
    265         StyleChange styleChange(style, paragraphStart.deepEquivalent());
    266         if (styleChange.cssStyle().length() || m_removeOnly) {
    267             RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
    268             if (!m_removeOnly) {
    269                 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
    270                 if (newBlock)
    271                     block = newBlock;
    272             }
    273             ASSERT(!block || block->isHTMLElement());
    274             if (block && block->isHTMLElement()) {
    275                 removeCSSStyle(style, toHTMLElement(block.get()));
    276                 if (!m_removeOnly)
    277                     addBlockStyle(styleChange, toHTMLElement(block.get()));
    278             }
    279 
    280             if (nextParagraphStart.isOrphan())
    281                 nextParagraphStart = endOfParagraph(paragraphStart).next();
    282         }
    283 
    284         paragraphStart = nextParagraphStart;
    285         nextParagraphStart = endOfParagraph(paragraphStart).next();
    286     }
    287 
    288     startRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), startIndex, 0, true);
    289     endRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), endIndex, 0, true);
    290     if (startRange && endRange)
    291         updateStartEnd(startRange->startPosition(), endRange->startPosition());
    292 }
    293 
    294 static PassRefPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style)
    295 {
    296     if (!style)
    297         return MutableStylePropertySet::create();
    298     return style->mutableCopy();
    299 }
    300 
    301 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
    302 {
    303     static const float MinimumFontSize = 0.1f;
    304 
    305     if (!style || !style->hasFontSizeDelta())
    306         return;
    307 
    308     Position start = startPosition();
    309     Position end = endPosition();
    310     if (comparePositions(end, start) < 0) {
    311         Position swap = start;
    312         start = end;
    313         end = swap;
    314     }
    315 
    316     // Join up any adjacent text nodes.
    317     if (start.deprecatedNode()->isTextNode()) {
    318         joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
    319         start = startPosition();
    320         end = endPosition();
    321     }
    322 
    323     if (start.isNull() || end.isNull())
    324         return;
    325 
    326     if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
    327         joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
    328         start = startPosition();
    329         end = endPosition();
    330     }
    331 
    332     if (start.isNull() || end.isNull())
    333         return;
    334 
    335     // Split the start text nodes if needed to apply style.
    336     if (isValidCaretPositionInTextNode(start)) {
    337         splitTextAtStart(start, end);
    338         start = startPosition();
    339         end = endPosition();
    340     }
    341 
    342     if (isValidCaretPositionInTextNode(end)) {
    343         splitTextAtEnd(start, end);
    344         start = startPosition();
    345         end = endPosition();
    346     }
    347 
    348     // Calculate loop end point.
    349     // If the end node is before the start node (can only happen if the end node is
    350     // an ancestor of the start node), we gather nodes up to the next sibling of the end node
    351     Node *beyondEnd;
    352     if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
    353         beyondEnd = NodeTraversal::nextSkippingChildren(end.deprecatedNode());
    354     else
    355         beyondEnd = NodeTraversal::next(end.deprecatedNode());
    356 
    357     start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
    358     Node* startNode = start.deprecatedNode();
    359     if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
    360         startNode = NodeTraversal::next(startNode);
    361 
    362     // Store away font size before making any changes to the document.
    363     // This ensures that changes to one node won't effect another.
    364     HashMap<Node*, float> startingFontSizes;
    365     for (Node *node = startNode; node != beyondEnd; node = NodeTraversal::next(node))
    366         startingFontSizes.set(node, computedFontSize(node));
    367 
    368     // These spans were added by us. If empty after font size changes, they can be removed.
    369     Vector<RefPtr<HTMLElement> > unstyledSpans;
    370 
    371     Node* lastStyledNode = 0;
    372     for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(node)) {
    373         RefPtr<HTMLElement> element;
    374         if (node->isHTMLElement()) {
    375             // Only work on fully selected nodes.
    376             if (!nodeFullySelected(node, start, end))
    377                 continue;
    378             element = toHTMLElement(node);
    379         } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
    380             // Last styled node was not parent node of this text node, but we wish to style this
    381             // text node. To make this possible, add a style span to surround this text node.
    382             RefPtr<HTMLElement> span = createStyleSpanElement(document());
    383             surroundNodeRangeWithElement(node, node, span.get());
    384             element = span.release();
    385         }  else {
    386             // Only handle HTML elements and text nodes.
    387             continue;
    388         }
    389         lastStyledNode = node;
    390 
    391         RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
    392         float currentFontSize = computedFontSize(node);
    393         float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
    394         RefPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize);
    395         if (value) {
    396             element->removeInlineStyleProperty(CSSPropertyFontSize);
    397             currentFontSize = computedFontSize(node);
    398         }
    399         if (currentFontSize != desiredFontSize) {
    400             inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false);
    401             setNodeAttribute(element.get(), styleAttr, inlineStyle->asText());
    402         }
    403         if (inlineStyle->isEmpty()) {
    404             removeNodeAttribute(element.get(), styleAttr);
    405             if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
    406                 unstyledSpans.append(element.release());
    407         }
    408     }
    409 
    410     size_t size = unstyledSpans.size();
    411     for (size_t i = 0; i < size; ++i)
    412         removeNodePreservingChildren(unstyledSpans[i].get());
    413 }
    414 
    415 static Node* dummySpanAncestorForNode(const Node* node)
    416 {
    417     while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node))))
    418         node = node->parentNode();
    419 
    420     return node ? node->parentNode() : 0;
    421 }
    422 
    423 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
    424 {
    425     if (!dummySpanAncestor)
    426         return;
    427 
    428     // Dummy spans are created when text node is split, so that style information
    429     // can be propagated, which can result in more splitting. If a dummy span gets
    430     // cloned/split, the new node is always a sibling of it. Therefore, we scan
    431     // all the children of the dummy's parent
    432     Node* next;
    433     for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
    434         next = node->nextSibling();
    435         if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
    436             removeNodePreservingChildren(node);
    437         node = next;
    438     }
    439 }
    440 
    441 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
    442 {
    443     // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
    444     // In that case, we return the unsplit ancestor. Otherwise, we return 0.
    445     Node* block = enclosingBlock(node);
    446     if (!block)
    447         return 0;
    448 
    449     Node* highestAncestorWithUnicodeBidi = 0;
    450     Node* nextHighestAncestorWithUnicodeBidi = 0;
    451     int highestAncestorUnicodeBidi = 0;
    452     for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
    453         int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi);
    454         if (unicodeBidi && unicodeBidi != CSSValueNormal) {
    455             highestAncestorUnicodeBidi = unicodeBidi;
    456             nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
    457             highestAncestorWithUnicodeBidi = n;
    458         }
    459     }
    460 
    461     if (!highestAncestorWithUnicodeBidi)
    462         return 0;
    463 
    464     HTMLElement* unsplitAncestor = 0;
    465 
    466     WritingDirection highestAncestorDirection;
    467     if (allowedDirection != NaturalWritingDirection
    468         && highestAncestorUnicodeBidi != CSSValueBidiOverride
    469         && highestAncestorWithUnicodeBidi->isHTMLElement()
    470         && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
    471         && highestAncestorDirection == allowedDirection) {
    472         if (!nextHighestAncestorWithUnicodeBidi)
    473             return toHTMLElement(highestAncestorWithUnicodeBidi);
    474 
    475         unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
    476         highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
    477     }
    478 
    479     // Split every ancestor through highest ancestor with embedding.
    480     RefPtr<Node> currentNode = node;
    481     while (currentNode) {
    482         RefPtr<Element> parent = toElement(currentNode->parentNode());
    483         if (before ? currentNode->previousSibling() : currentNode->nextSibling())
    484             splitElement(parent, before ? currentNode : currentNode->nextSibling());
    485         if (parent == highestAncestorWithUnicodeBidi)
    486             break;
    487         currentNode = parent;
    488     }
    489     return unsplitAncestor;
    490 }
    491 
    492 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
    493 {
    494     Node* block = enclosingBlock(node);
    495     if (!block)
    496         return;
    497 
    498     Node* parent = 0;
    499     for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) {
    500         parent = n->parentNode();
    501         if (!n->isStyledElement())
    502             continue;
    503 
    504         Element* element = toElement(n);
    505         int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(element).get(), CSSPropertyUnicodeBidi);
    506         if (!unicodeBidi || unicodeBidi == CSSValueNormal)
    507             continue;
    508 
    509         // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
    510         // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
    511         // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
    512         // otherwise it sets the property in the inline style declaration.
    513         if (element->hasAttribute(dirAttr)) {
    514             // FIXME: If this is a BDO element, we should probably just remove it if it has no
    515             // other attributes, like we (should) do with B and I elements.
    516             removeNodeAttribute(element, dirAttr);
    517         } else {
    518             RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
    519             inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
    520             inlineStyle->removeProperty(CSSPropertyDirection);
    521             setNodeAttribute(element, styleAttr, inlineStyle->asText());
    522             if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
    523                 removeNodePreservingChildren(element);
    524         }
    525     }
    526 }
    527 
    528 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
    529 {
    530     for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
    531         if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
    532             return n;
    533     }
    534 
    535     return 0;
    536 }
    537 
    538 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
    539 {
    540     RefPtr<Node> startDummySpanAncestor = 0;
    541     RefPtr<Node> endDummySpanAncestor = 0;
    542 
    543     // update document layout once before removing styles
    544     // so that we avoid the expense of updating before each and every call
    545     // to check a computed style
    546     document()->updateLayoutIgnorePendingStylesheets();
    547 
    548     // adjust to the positions we want to use for applying style
    549     Position start = startPosition();
    550     Position end = endPosition();
    551 
    552     if (start.isNull() || end.isNull())
    553         return;
    554 
    555     if (comparePositions(end, start) < 0) {
    556         Position swap = start;
    557         start = end;
    558         end = swap;
    559     }
    560 
    561     // split the start node and containing element if the selection starts inside of it
    562     bool splitStart = isValidCaretPositionInTextNode(start);
    563     if (splitStart) {
    564         if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
    565             splitTextElementAtStart(start, end);
    566         else
    567             splitTextAtStart(start, end);
    568         start = startPosition();
    569         end = endPosition();
    570         startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
    571     }
    572 
    573     // split the end node and containing element if the selection ends inside of it
    574     bool splitEnd = isValidCaretPositionInTextNode(end);
    575     if (splitEnd) {
    576         if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
    577             splitTextElementAtEnd(start, end);
    578         else
    579             splitTextAtEnd(start, end);
    580         start = startPosition();
    581         end = endPosition();
    582         endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
    583     }
    584 
    585     // Remove style from the selection.
    586     // Use the upstream position of the start for removing style.
    587     // This will ensure we remove all traces of the relevant styles from the selection
    588     // and prevent us from adding redundant ones, as described in:
    589     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
    590     Position removeStart = start.upstream();
    591     WritingDirection textDirection = NaturalWritingDirection;
    592     bool hasTextDirection = style->textDirection(textDirection);
    593     RefPtr<EditingStyle> styleWithoutEmbedding;
    594     RefPtr<EditingStyle> embeddingStyle;
    595     if (hasTextDirection) {
    596         // Leave alone an ancestor that provides the desired single level embedding, if there is one.
    597         HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
    598         HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
    599         removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
    600         removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);
    601 
    602         // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
    603         Position embeddingRemoveStart = removeStart;
    604         if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
    605             embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
    606 
    607         Position embeddingRemoveEnd = end;
    608         if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
    609             embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
    610 
    611         if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
    612             styleWithoutEmbedding = style->copy();
    613             embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
    614 
    615             if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
    616                 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
    617         }
    618     }
    619 
    620     removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
    621     start = startPosition();
    622     end = endPosition();
    623     if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
    624         return;
    625 
    626     if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
    627         start = startPosition();
    628         end = endPosition();
    629     }
    630 
    631     if (splitEnd) {
    632         mergeEndWithNextIfIdentical(start, end);
    633         start = startPosition();
    634         end = endPosition();
    635     }
    636 
    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
    640     document()->updateLayoutIgnorePendingStylesheets();
    641 
    642     RefPtr<EditingStyle> styleToApply = style;
    643     if (hasTextDirection) {
    644         // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
    645         Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
    646         Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));
    647 
    648         if (embeddingStartNode || embeddingEndNode) {
    649             Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
    650             Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
    651             ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
    652 
    653             if (!embeddingStyle) {
    654                 styleWithoutEmbedding = style->copy();
    655                 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
    656             }
    657             fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
    658 
    659             styleToApply = styleWithoutEmbedding;
    660         }
    661     }
    662 
    663     fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
    664 
    665     // Remove dummy style spans created by splitting text elements.
    666     cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get());
    667     if (endDummySpanAncestor != startDummySpanAncestor)
    668         cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get());
    669 }
    670 
    671 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
    672 {
    673     Node* startNode = start.deprecatedNode();
    674 
    675     if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
    676         startNode = NodeTraversal::next(startNode);
    677         if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
    678             return;
    679     }
    680 
    681     Node* pastEndNode = end.deprecatedNode();
    682     if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
    683         pastEndNode = NodeTraversal::nextSkippingChildren(end.deprecatedNode());
    684 
    685     // FIXME: Callers should perform this operation on a Range that includes the br
    686     // if they want style applied to the empty line.
    687     if (start == end && start.deprecatedNode()->hasTagName(brTag))
    688         pastEndNode = NodeTraversal::next(start.deprecatedNode());
    689 
    690     // Start from the highest fully selected ancestor so that we can modify the fully selected node.
    691     // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
    692     // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
    693     RefPtr<Range> range = Range::create(startNode->document(), start, end);
    694     Element* editableRoot = startNode->rootEditableElement();
    695     if (startNode != editableRoot) {
    696         while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get()))
    697             startNode = startNode->parentNode();
    698     }
    699 
    700     applyInlineStyleToNodeRange(style, startNode, pastEndNode);
    701 }
    702 
    703 static bool containsNonEditableRegion(Node* node)
    704 {
    705     if (!node->rendererIsEditable())
    706         return true;
    707 
    708     Node* sibling = NodeTraversal::nextSkippingChildren(node);
    709     for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(descendent)) {
    710         if (!descendent->rendererIsEditable())
    711             return true;
    712     }
    713 
    714     return false;
    715 }
    716 
    717 struct InlineRunToApplyStyle {
    718     InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode)
    719         : start(start)
    720         , end(end)
    721         , pastEndNode(pastEndNode)
    722     {
    723         ASSERT(start->parentNode() == end->parentNode());
    724     }
    725 
    726     bool startAndEndAreStillInDocument()
    727     {
    728         return start && end && start->inDocument() && end->inDocument();
    729     }
    730 
    731     RefPtr<Node> start;
    732     RefPtr<Node> end;
    733     RefPtr<Node> pastEndNode;
    734     Position positionForStyleComputation;
    735     RefPtr<Node> dummyElement;
    736     StyleChange change;
    737 };
    738 
    739 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode)
    740 {
    741     if (m_removeOnly)
    742         return;
    743 
    744     document()->updateLayoutIgnorePendingStylesheets();
    745 
    746     Vector<InlineRunToApplyStyle> runs;
    747     RefPtr<Node> node = startNode;
    748     for (RefPtr<Node> next; node && node != pastEndNode; node = next) {
    749         next = NodeTraversal::next(node.get());
    750 
    751         if (!node->renderer() || !node->rendererIsEditable())
    752             continue;
    753 
    754         if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
    755             // This is a plaintext-only region. Only proceed if it's fully selected.
    756             // pastEndNode is the node after the last fully selected node, so if it's inside node then
    757             // node isn't fully selected.
    758             if (pastEndNode && pastEndNode->isDescendantOf(node.get()))
    759                 break;
    760             // Add to this element's inline style and skip over its contents.
    761             HTMLElement* element = toHTMLElement(node.get());
    762             RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
    763             inlineStyle->mergeAndOverrideOnConflict(style->style());
    764             setNodeAttribute(element, styleAttr, inlineStyle->asText());
    765             next = NodeTraversal::nextSkippingChildren(node.get());
    766             continue;
    767         }
    768 
    769         if (isBlock(node.get()))
    770             continue;
    771 
    772         if (node->childNodeCount()) {
    773             if (node->contains(pastEndNode.get()) || containsNonEditableRegion(node.get()) || !node->parentNode()->rendererIsEditable())
    774                 continue;
    775             if (editingIgnoresContent(node.get())) {
    776                 next = NodeTraversal::nextSkippingChildren(node.get());
    777                 continue;
    778             }
    779         }
    780 
    781         Node* runStart = node.get();
    782         Node* runEnd = node.get();
    783         Node* sibling = node->nextSibling();
    784         while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get())
    785                && (!isBlock(sibling) || sibling->hasTagName(brTag))
    786                && !containsNonEditableRegion(sibling)) {
    787             runEnd = sibling;
    788             sibling = runEnd->nextSibling();
    789         }
    790         next = NodeTraversal::nextSkippingChildren(runEnd);
    791 
    792         Node* pastEndNode = NodeTraversal::nextSkippingChildren(runEnd);
    793         if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode))
    794             continue;
    795 
    796         runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode));
    797     }
    798 
    799     for (size_t i = 0; i < runs.size(); i++) {
    800         removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode);
    801         runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement);
    802     }
    803 
    804     document()->updateLayoutIgnorePendingStylesheets();
    805 
    806     for (size_t i = 0; i < runs.size(); i++)
    807         runs[i].change = StyleChange(style, runs[i].positionForStyleComputation);
    808 
    809     for (size_t i = 0; i < runs.size(); i++) {
    810         InlineRunToApplyStyle& run = runs[i];
    811         if (run.dummyElement)
    812             removeNode(run.dummyElement);
    813         if (run.startAndEndAreStillInDocument())
    814             applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement);
    815     }
    816 }
    817 
    818 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
    819 {
    820     return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
    821         || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
    822 }
    823 
    824 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode)
    825 {
    826     ASSERT(style && runStart);
    827 
    828     for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(node)) {
    829         if (node->childNodeCount())
    830             continue;
    831         // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
    832         if (!style->styleIsPresentInComputedStyleOfNode(node))
    833             return true;
    834         if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))
    835             return true;
    836     }
    837     return false;
    838 }
    839 
    840 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode)
    841 {
    842     ASSERT(runStart && runEnd);
    843     RefPtr<Node> next = runStart;
    844     for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
    845         if (editingIgnoresContent(node.get())) {
    846             ASSERT(!node->contains(pastEndNode.get()));
    847             next = NodeTraversal::nextSkippingChildren(node.get());
    848         } else
    849             next = NodeTraversal::next(node.get());
    850         if (!node->isHTMLElement())
    851             continue;
    852 
    853         RefPtr<Node> previousSibling = node->previousSibling();
    854         RefPtr<Node> nextSibling = node->nextSibling();
    855         RefPtr<ContainerNode> parent = node->parentNode();
    856         removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways);
    857         if (!node->inDocument()) {
    858             // FIXME: We might need to update the start and the end of current selection here but need a test.
    859             if (runStart == node)
    860                 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
    861             if (runEnd == node)
    862                 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
    863         }
    864     }
    865 }
    866 
    867 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
    868 {
    869     ASSERT(element);
    870 
    871     if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
    872         return false;
    873 
    874     if (isStyledInlineElementToRemove(element.get())) {
    875         if (mode == RemoveNone)
    876             return true;
    877         ASSERT(extractedStyle);
    878         extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues);
    879         removeNodePreservingChildren(element);
    880         return true;
    881     }
    882 
    883     bool removed = false;
    884     if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
    885         removed = true;
    886 
    887     if (!element->inDocument())
    888         return removed;
    889 
    890     // If the node was converted to a span, the span may still contain relevant
    891     // styles which must be removed (e.g. <b style='font-weight: bold'>)
    892     if (removeCSSStyle(style, element.get(), mode, extractedStyle))
    893         removed = true;
    894 
    895     return removed;
    896 }
    897 
    898 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
    899 {
    900     if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
    901         removeNodePreservingChildren(elem);
    902     else {
    903         HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem);
    904         ASSERT(newSpanElement && newSpanElement->inDocument());
    905         elem = newSpanElement;
    906     }
    907 }
    908 
    909 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
    910 {
    911     ASSERT(style);
    912     if (mode == RemoveNone) {
    913         ASSERT(!extractedStyle);
    914         return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
    915     }
    916 
    917     ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
    918     if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
    919         replaceWithSpanOrRemoveIfWithoutAttributes(element);
    920         return true;
    921     }
    922 
    923     // unicode-bidi and direction are pushed down separately so don't push down with other styles
    924     Vector<QualifiedName> attributes;
    925     if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
    926         extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
    927         return false;
    928 
    929     for (size_t i = 0; i < attributes.size(); i++)
    930         removeNodeAttribute(element, attributes[i]);
    931 
    932     if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element))
    933         removeNodePreservingChildren(element);
    934 
    935     return true;
    936 }
    937 
    938 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
    939 {
    940     ASSERT(style);
    941     ASSERT(element);
    942 
    943     if (mode == RemoveNone)
    944         return style->conflictsWithInlineStyleOfElement(element);
    945 
    946     Vector<CSSPropertyID> properties;
    947     if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
    948         return false;
    949 
    950     // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
    951     for (size_t i = 0; i < properties.size(); i++)
    952         removeCSSProperty(element, properties[i]);
    953 
    954     // No need to serialize <foo style=""> if we just removed the last css property
    955     if (element->inlineStyle()->isEmpty())
    956         removeNodeAttribute(element, styleAttr);
    957 
    958     if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
    959         removeNodePreservingChildren(element);
    960 
    961     return true;
    962 }
    963 
    964 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
    965 {
    966     if (!node)
    967         return 0;
    968 
    969     HTMLElement* result = 0;
    970     Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));
    971 
    972     for (Node *n = node; n; n = n->parentNode()) {
    973         if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
    974             result = toHTMLElement(n);
    975         // Should stop at the editable root (cannot cross editing boundary) and
    976         // also stop at the unsplittable element to be consistent with other UAs
    977         if (n == unsplittableElement)
    978             break;
    979     }
    980 
    981     return result;
    982 }
    983 
    984 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
    985 {
    986     ASSERT(node);
    987 
    988     node->document()->updateStyleIfNeeded();
    989 
    990     if (!style || style->isEmpty() || !node->renderer())
    991         return;
    992 
    993     RefPtr<EditingStyle> newInlineStyle = style;
    994     if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) {
    995         newInlineStyle = style->copy();
    996         newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues);
    997     }
    998 
    999     // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
   1000     // FIXME: applyInlineStyleToRange should be used here instead.
   1001     if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
   1002         setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->style()->asText());
   1003         return;
   1004     }
   1005 
   1006     if (node->renderer()->isText() && static_cast<RenderText*>(node->renderer())->isAllCollapsibleWhitespace())
   1007         return;
   1008 
   1009     // We can't wrap node with the styled element here because new styled element will never be removed if we did.
   1010     // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
   1011     // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
   1012     addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
   1013 }
   1014 
   1015 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
   1016 {
   1017     HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
   1018     if (!highestAncestor)
   1019         return;
   1020 
   1021     // The outer loop is traversing the tree vertically from highestAncestor to targetNode
   1022     RefPtr<Node> current = highestAncestor;
   1023     // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
   1024     // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
   1025     Vector<RefPtr<Element> > elementsToPushDown;
   1026     while (current && current != targetNode && current->contains(targetNode)) {
   1027         NodeVector currentChildren;
   1028         getChildNodes(current.get(), currentChildren);
   1029         RefPtr<Element> styledElement;
   1030         if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current.get()))) {
   1031             styledElement = toElement(current.get());
   1032             elementsToPushDown.append(styledElement);
   1033         }
   1034 
   1035         RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
   1036         if (current->isHTMLElement())
   1037             removeInlineStyleFromElement(style, toHTMLElement(current.get()), RemoveIfNeeded, styleToPushDown.get());
   1038 
   1039         // The inner loop will go through children on each level
   1040         // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
   1041         for (size_t i = 0; i < currentChildren.size(); ++i) {
   1042             Node* child = currentChildren[i].get();
   1043             if (!child->parentNode())
   1044                 continue;
   1045             if (!child->contains(targetNode) && elementsToPushDown.size()) {
   1046                 for (size_t i = 0; i < elementsToPushDown.size(); i++) {
   1047                     RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
   1048                     wrapper->removeAttribute(styleAttr);
   1049                     surroundNodeRangeWithElement(child, child, wrapper);
   1050                 }
   1051             }
   1052 
   1053             // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
   1054             // But if we've removed styledElement then go ahead and always apply the style.
   1055             if (child != targetNode || styledElement)
   1056                 applyInlineStyleToPushDown(child, styleToPushDown.get());
   1057 
   1058             // We found the next node for the outer loop (contains targetNode)
   1059             // When reached targetNode, stop the outer loop upon the completion of the current inner loop
   1060             if (child == targetNode || child->contains(targetNode))
   1061                 current = child;
   1062         }
   1063     }
   1064 }
   1065 
   1066 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
   1067 {
   1068     ASSERT(start.isNotNull());
   1069     ASSERT(end.isNotNull());
   1070     ASSERT(start.anchorNode()->inDocument());
   1071     ASSERT(end.anchorNode()->inDocument());
   1072     ASSERT(comparePositions(start, end) <= 0);
   1073     // FIXME: We should assert that start/end are not in the middle of a text node.
   1074 
   1075     Position pushDownStart = start.downstream();
   1076     // If the pushDownStart is at the end of a text node, then this node is not fully selected.
   1077     // Move it to the next deep quivalent position to avoid removing the style from this node.
   1078     // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
   1079     Node* pushDownStartContainer = pushDownStart.containerNode();
   1080     if (pushDownStartContainer && pushDownStartContainer->isTextNode()
   1081         && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
   1082         pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
   1083     Position pushDownEnd = end.upstream();
   1084     // If pushDownEnd is at the start of a text node, then this node is not fully selected.
   1085     // Move it to the previous deep equivalent position to avoid removing the style from this node.
   1086     Node* pushDownEndContainer = pushDownEnd.containerNode();
   1087     if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode())
   1088         pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd);
   1089 
   1090     pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
   1091     pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());
   1092 
   1093     // The s and e variables store the positions used to set the ending selection after style removal
   1094     // takes place. This will help callers to recognize when either the start node or the end node
   1095     // are removed from the document during the work of this function.
   1096     // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
   1097     // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
   1098     Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
   1099     Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
   1100 
   1101     RefPtr<Node> node = start.deprecatedNode();
   1102     while (node) {
   1103         RefPtr<Node> next;
   1104         if (editingIgnoresContent(node.get())) {
   1105             ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode()));
   1106             next = NodeTraversal::nextSkippingChildren(node.get());
   1107         } else {
   1108             next = NodeTraversal::next(node.get());
   1109         }
   1110         if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) {
   1111             RefPtr<HTMLElement> elem = toHTMLElement(node.get());
   1112             RefPtr<Node> prev = NodeTraversal::previousPostOrder(elem.get());
   1113             RefPtr<Node> next = NodeTraversal::next(elem.get());
   1114             RefPtr<EditingStyle> styleToPushDown;
   1115             RefPtr<Node> childNode;
   1116             if (isStyledInlineElementToRemove(elem.get())) {
   1117                 styleToPushDown = EditingStyle::create();
   1118                 childNode = elem->firstChild();
   1119             }
   1120 
   1121             removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
   1122             if (!elem->inDocument()) {
   1123                 if (s.deprecatedNode() == elem) {
   1124                     // Since elem must have been fully selected, and it is at the start
   1125                     // of the selection, it is clear we can set the new s offset to 0.
   1126                     ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
   1127                     s = firstPositionInOrBeforeNode(next.get());
   1128                 }
   1129                 if (e.deprecatedNode() == elem) {
   1130                     // Since elem must have been fully selected, and it is at the end
   1131                     // of the selection, it is clear we can set the new e offset to
   1132                     // the max range offset of prev.
   1133                     ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode()));
   1134                     e = lastPositionInOrAfterNode(prev.get());
   1135                 }
   1136             }
   1137 
   1138             if (styleToPushDown) {
   1139                 for (; childNode; childNode = childNode->nextSibling())
   1140                     applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
   1141             }
   1142         }
   1143         if (node == end.deprecatedNode())
   1144             break;
   1145         node = next;
   1146     }
   1147 
   1148     updateStartEnd(s, e);
   1149 }
   1150 
   1151 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
   1152 {
   1153     ASSERT(node);
   1154     ASSERT(node->isElementNode());
   1155 
   1156     // The tree may have changed and Position::upstream() relies on an up-to-date layout.
   1157     node->document()->updateLayoutIgnorePendingStylesheets();
   1158 
   1159     return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
   1160         && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
   1161 }
   1162 
   1163 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
   1164 {
   1165     ASSERT(node);
   1166     ASSERT(node->isElementNode());
   1167 
   1168     bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0;
   1169     bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0;
   1170 
   1171     return isFullyBeforeStart || isFullyAfterEnd;
   1172 }
   1173 
   1174 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
   1175 {
   1176     ASSERT(start.containerNode()->isTextNode());
   1177 
   1178     Position newEnd;
   1179     if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
   1180         newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
   1181     else
   1182         newEnd = end;
   1183 
   1184     RefPtr<Text> text = start.containerText();
   1185     splitTextNode(text, start.offsetInContainerNode());
   1186     updateStartEnd(firstPositionInNode(text.get()), newEnd);
   1187 }
   1188 
   1189 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
   1190 {
   1191     ASSERT(end.containerNode()->isTextNode());
   1192 
   1193     bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
   1194     Text* text = toText(end.deprecatedNode());
   1195     splitTextNode(text, end.offsetInContainerNode());
   1196 
   1197     Node* prevNode = text->previousSibling();
   1198     if (!prevNode || !prevNode->isTextNode())
   1199         return;
   1200 
   1201     Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start;
   1202     updateStartEnd(newStart, lastPositionInNode(prevNode));
   1203 }
   1204 
   1205 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
   1206 {
   1207     ASSERT(start.containerNode()->isTextNode());
   1208 
   1209     Position newEnd;
   1210     if (start.containerNode() == end.containerNode())
   1211         newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
   1212     else
   1213         newEnd = end;
   1214 
   1215     splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode());
   1216     updateStartEnd(positionBeforeNode(start.containerNode()), newEnd);
   1217 }
   1218 
   1219 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
   1220 {
   1221     ASSERT(end.containerNode()->isTextNode());
   1222 
   1223     bool shouldUpdateStart = start.containerNode() == end.containerNode();
   1224     splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode());
   1225 
   1226     Node* parentElement = end.containerNode()->parentNode();
   1227     if (!parentElement || !parentElement->previousSibling())
   1228         return;
   1229     Node* firstTextNode = parentElement->previousSibling()->lastChild();
   1230     if (!firstTextNode || !firstTextNode->isTextNode())
   1231         return;
   1232 
   1233     Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start;
   1234     updateStartEnd(newStart, positionAfterNode(firstTextNode));
   1235 }
   1236 
   1237 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
   1238 {
   1239     if (!element || !element->isHTMLElement())
   1240         return false;
   1241 
   1242     return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
   1243 }
   1244 
   1245 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
   1246 {
   1247     Node* node = position.containerNode();
   1248     if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
   1249         return false;
   1250     int offsetInText = position.offsetInContainerNode();
   1251     return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
   1252 }
   1253 
   1254 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
   1255 {
   1256     Node* startNode = start.containerNode();
   1257     int startOffset = start.computeOffsetInContainerNode();
   1258     if (startOffset)
   1259         return false;
   1260 
   1261     if (isAtomicNode(startNode)) {
   1262         // note: prior siblings could be unrendered elements. it's silly to miss the
   1263         // merge opportunity just for that.
   1264         if (startNode->previousSibling())
   1265             return false;
   1266 
   1267         startNode = startNode->parentNode();
   1268         startOffset = 0;
   1269     }
   1270 
   1271     if (!startNode->isElementNode())
   1272         return false;
   1273 
   1274     Node* previousSibling = startNode->previousSibling();
   1275 
   1276     if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
   1277         Element* previousElement = toElement(previousSibling);
   1278         Element* element = toElement(startNode);
   1279         Node* startChild = element->firstChild();
   1280         ASSERT(startChild);
   1281         mergeIdenticalElements(previousElement, element);
   1282 
   1283         int startOffsetAdjustment = startChild->nodeIndex();
   1284         int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
   1285         updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
   1286                        Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
   1287         return true;
   1288     }
   1289 
   1290     return false;
   1291 }
   1292 
   1293 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
   1294 {
   1295     Node* endNode = end.containerNode();
   1296 
   1297     if (isAtomicNode(endNode)) {
   1298         int endOffset = end.computeOffsetInContainerNode();
   1299         if (offsetIsBeforeLastNodeOffset(endOffset, endNode))
   1300             return false;
   1301 
   1302         if (end.deprecatedNode()->nextSibling())
   1303             return false;
   1304 
   1305         endNode = end.deprecatedNode()->parentNode();
   1306     }
   1307 
   1308     if (!endNode->isElementNode() || endNode->hasTagName(brTag))
   1309         return false;
   1310 
   1311     Node* nextSibling = endNode->nextSibling();
   1312     if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
   1313         Element* nextElement = toElement(nextSibling);
   1314         Element* element = toElement(endNode);
   1315         Node* nextChild = nextElement->firstChild();
   1316 
   1317         mergeIdenticalElements(element, nextElement);
   1318 
   1319         bool shouldUpdateStart = start.containerNode() == endNode;
   1320         int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
   1321         updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
   1322                        Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
   1323         return true;
   1324     }
   1325 
   1326     return false;
   1327 }
   1328 
   1329 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
   1330 {
   1331     ASSERT(passedStartNode);
   1332     ASSERT(endNode);
   1333     ASSERT(elementToInsert);
   1334     RefPtr<Node> startNode = passedStartNode;
   1335     RefPtr<Element> element = elementToInsert;
   1336 
   1337     insertNodeBefore(element, startNode);
   1338 
   1339     RefPtr<Node> node = startNode;
   1340     while (node) {
   1341         RefPtr<Node> next = node->nextSibling();
   1342         if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
   1343             removeNode(node);
   1344             appendNode(node, element);
   1345         }
   1346         if (node == endNode)
   1347             break;
   1348         node = next;
   1349     }
   1350 
   1351     RefPtr<Node> nextSibling = element->nextSibling();
   1352     RefPtr<Node> previousSibling = element->previousSibling();
   1353     if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
   1354         && areIdenticalElements(element.get(), toElement(nextSibling.get())))
   1355         mergeIdenticalElements(element.get(), toElement(nextSibling.get()));
   1356 
   1357     if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
   1358         Node* mergedElement = previousSibling->nextSibling();
   1359         if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
   1360             && areIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement)))
   1361             mergeIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement));
   1362     }
   1363 
   1364     // FIXME: We should probably call updateStartEnd if the start or end was in the node
   1365     // range so that the endingSelection() is canonicalized.  See the comments at the end of
   1366     // VisibleSelection::validate().
   1367 }
   1368 
   1369 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
   1370 {
   1371     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
   1372     // inline content.
   1373     if (!block)
   1374         return;
   1375 
   1376     String cssStyle = styleChange.cssStyle();
   1377     StringBuilder cssText;
   1378     cssText.append(cssStyle);
   1379     if (const StylePropertySet* decl = block->inlineStyle()) {
   1380         if (!cssStyle.isEmpty())
   1381             cssText.append(' ');
   1382         cssText.append(decl->asText());
   1383     }
   1384     setNodeAttribute(block, styleAttr, cssText.toString());
   1385 }
   1386 
   1387 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
   1388 {
   1389     if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
   1390         return;
   1391 
   1392     RefPtr<Node> start = passedStart;
   1393     RefPtr<Node> dummyElement;
   1394     StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dummyElement));
   1395 
   1396     if (dummyElement)
   1397         removeNode(dummyElement);
   1398 
   1399     applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement);
   1400 }
   1401 
   1402 Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement)
   1403 {
   1404     // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
   1405     Position positionForStyleComparison;
   1406     if (!startNode->isElementNode()) {
   1407         dummyElement = createStyleSpanElement(document());
   1408         insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
   1409         return positionBeforeNode(dummyElement.get());
   1410     }
   1411 
   1412     return firstPositionInOrBeforeNode(startNode.get());
   1413 }
   1414 
   1415 void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, StyleChange& styleChange, EAddStyledElement addStyledElement)
   1416 {
   1417     RefPtr<Node> startNode = passedStart;
   1418     RefPtr<Node> endNode = passedEnd;
   1419     ASSERT(startNode->inDocument());
   1420     ASSERT(endNode->inDocument());
   1421 
   1422     // Find appropriate font and span elements top-down.
   1423     HTMLElement* fontContainer = 0;
   1424     HTMLElement* styleContainer = 0;
   1425     for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
   1426         if (container->isHTMLElement() && container->hasTagName(fontTag))
   1427             fontContainer = toHTMLElement(container);
   1428         bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag);
   1429         if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount())))
   1430             styleContainer = toHTMLElement(container);
   1431         if (!container->firstChild())
   1432             break;
   1433         startNode = container->firstChild();
   1434         endNode = container->lastChild();
   1435     }
   1436 
   1437     // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
   1438     if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
   1439         if (fontContainer) {
   1440             if (styleChange.applyFontColor())
   1441                 setNodeAttribute(fontContainer, colorAttr, styleChange.fontColor());
   1442             if (styleChange.applyFontFace())
   1443                 setNodeAttribute(fontContainer, faceAttr, styleChange.fontFace());
   1444             if (styleChange.applyFontSize())
   1445                 setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize());
   1446         } else {
   1447             RefPtr<Element> fontElement = createFontElement(document());
   1448             if (styleChange.applyFontColor())
   1449                 fontElement->setAttribute(colorAttr, styleChange.fontColor());
   1450             if (styleChange.applyFontFace())
   1451                 fontElement->setAttribute(faceAttr, styleChange.fontFace());
   1452             if (styleChange.applyFontSize())
   1453                 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
   1454             surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
   1455         }
   1456     }
   1457 
   1458     if (styleChange.cssStyle().length()) {
   1459         if (styleContainer) {
   1460             if (const StylePropertySet* existingStyle = styleContainer->inlineStyle()) {
   1461                 String existingText = existingStyle->asText();
   1462                 StringBuilder cssText;
   1463                 cssText.append(existingText);
   1464                 if (!existingText.isEmpty())
   1465                     cssText.append(' ');
   1466                 cssText.append(styleChange.cssStyle());
   1467                 setNodeAttribute(styleContainer, styleAttr, cssText.toString());
   1468             } else
   1469                 setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle());
   1470         } else {
   1471             RefPtr<Element> styleElement = createStyleSpanElement(document());
   1472             styleElement->setAttribute(styleAttr, styleChange.cssStyle());
   1473             surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
   1474         }
   1475     }
   1476 
   1477     if (styleChange.applyBold())
   1478         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
   1479 
   1480     if (styleChange.applyItalic())
   1481         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
   1482 
   1483     if (styleChange.applyUnderline())
   1484         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
   1485 
   1486     if (styleChange.applyLineThrough())
   1487         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));
   1488 
   1489     if (styleChange.applySubscript())
   1490         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
   1491     else if (styleChange.applySuperscript())
   1492         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
   1493 
   1494     if (m_styledInlineElement && addStyledElement == AddStyledElement)
   1495         surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
   1496 }
   1497 
   1498 float ApplyStyleCommand::computedFontSize(Node* node)
   1499 {
   1500     if (!node)
   1501         return 0;
   1502 
   1503     RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
   1504     if (!style)
   1505         return 0;
   1506 
   1507     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
   1508     if (!value)
   1509         return 0;
   1510 
   1511     return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
   1512 }
   1513 
   1514 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
   1515 {
   1516     if (!node)
   1517         return;
   1518 
   1519     Position newStart = start;
   1520     Position newEnd = end;
   1521 
   1522     Vector<RefPtr<Text> > textNodes;
   1523     for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) {
   1524         if (!curr->isTextNode())
   1525             continue;
   1526 
   1527         textNodes.append(toText(curr));
   1528     }
   1529 
   1530     for (size_t i = 0; i < textNodes.size(); ++i) {
   1531         Text* childText = textNodes[i].get();
   1532         Node* next = childText->nextSibling();
   1533         if (!next || !next->isTextNode())
   1534             continue;
   1535 
   1536         Text* nextText = toText(next);
   1537         if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
   1538             newStart = Position(childText, childText->length() + start.offsetInContainerNode());
   1539         if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
   1540             newEnd = Position(childText, childText->length() + end.offsetInContainerNode());
   1541         String textToMove = nextText->data();
   1542         insertTextIntoNode(childText, childText->length(), textToMove);
   1543         removeNode(next);
   1544         // don't move child node pointer. it may want to merge with more text nodes.
   1545     }
   1546 
   1547     updateStartEnd(newStart, newEnd);
   1548 }
   1549 
   1550 }
   1551