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