root/Source/core/editing/ApplyStyleCommand.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. styleSpanClassString
  2. isLegacyAppleStyleSpan
  3. hasNoAttributeOrOnlyStyleAttribute
  4. isStyleSpanOrSpanWithOnlyStyleAttribute
  5. isSpanWithoutAttributesOrUnstyledStyleSpan
  6. isEmptyFontTag
  7. createFontElement
  8. createStyleSpanElement
  9. m_isInlineElementToRemoveFunction
  10. m_isInlineElementToRemoveFunction
  11. m_isInlineElementToRemoveFunction
  12. m_isInlineElementToRemoveFunction
  13. updateStartEnd
  14. startPosition
  15. endPosition
  16. doApply
  17. editingAction
  18. applyBlockStyle
  19. copyStyleOrCreateEmpty
  20. applyRelativeFontStyleChange
  21. dummySpanAncestorForNode
  22. cleanupUnstyledAppleStyleSpans
  23. splitAncestorsWithUnicodeBidi
  24. removeEmbeddingUpToEnclosingBlock
  25. highestEmbeddingAncestor
  26. applyInlineStyle
  27. fixRangeAndApplyInlineStyle
  28. containsNonEditableRegion
  29. applyInlineStyleToNodeRange
  30. isStyledInlineElementToRemove
  31. shouldApplyInlineStyleToRun
  32. removeConflictingInlineStyleFromRun
  33. removeInlineStyleFromElement
  34. replaceWithSpanOrRemoveIfWithoutAttributes
  35. removeImplicitlyStyledElement
  36. removeCSSStyle
  37. highestAncestorWithConflictingInlineStyle
  38. applyInlineStyleToPushDown
  39. pushDownInlineStyleAroundNode
  40. removeInlineStyle
  41. nodeFullySelected
  42. splitTextAtStart
  43. splitTextAtEnd
  44. splitTextElementAtStart
  45. splitTextElementAtEnd
  46. shouldSplitTextElement
  47. isValidCaretPositionInTextNode
  48. mergeStartWithPreviousIfIdentical
  49. mergeEndWithNextIfIdentical
  50. surroundNodeRangeWithElement
  51. addBlockStyle
  52. addInlineStyleIfNeeded
  53. positionToComputeInlineStyleChange
  54. applyInlineStyleChange
  55. computedFontSize
  56. joinChildTextNodes

/*
 * Copyright (C) 2005, 2006, 2008, 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "core/editing/ApplyStyleCommand.h"

#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "HTMLNames.h"
#include "core/css/CSSComputedStyleDeclaration.h"
#include "core/css/CSSValuePool.h"
#include "core/css/StylePropertySet.h"
#include "core/dom/Document.h"
#include "core/dom/NodeList.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/Range.h"
#include "core/dom/Text.h"
#include "core/editing/EditingStyle.h"
#include "core/editing/HTMLInterchange.h"
#include "core/editing/PlainTextRange.h"
#include "core/editing/TextIterator.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/htmlediting.h"
#include "core/rendering/RenderObject.h"
#include "core/rendering/RenderText.h"
#include "platform/heap/Handle.h"
#include "wtf/StdLibExtras.h"
#include "wtf/text/StringBuilder.h"

namespace WebCore {

using namespace HTMLNames;

static String& styleSpanClassString()
{
    DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
    return styleSpanClassString;
}

bool isLegacyAppleStyleSpan(const Node *node)
{
    if (!node || !node->isHTMLElement())
        return false;

    const HTMLElement* elem = toHTMLElement(node);
    return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
}

static bool hasNoAttributeOrOnlyStyleAttribute(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
{
    if (!element->hasAttributes())
        return true;

    unsigned matchedAttributes = 0;
    if (element->getAttribute(classAttr) == styleSpanClassString())
        matchedAttributes++;
    if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute
        || !element->inlineStyle() || element->inlineStyle()->isEmpty()))
        matchedAttributes++;

    ASSERT(matchedAttributes <= element->attributeCount());
    return matchedAttributes == element->attributeCount();
}

bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
{
    if (!isHTMLSpanElement(element))
        return false;
    return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute);
}

static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
{
    if (!isHTMLSpanElement(node))
        return false;
    return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty);
}

bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
{
    if (!isHTMLFontElement(element))
        return false;

    return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty);
}

static PassRefPtr<Element> createFontElement(Document& document)
{
    RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
    return fontNode.release();
}

PassRefPtr<HTMLElement> createStyleSpanElement(Document& document)
{
    RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
    return styleElement.release();
}

ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
    : CompositeEditCommand(document)
    , m_style(style->copy())
    , m_editingAction(editingAction)
    , m_propertyLevel(propertyLevel)
    , m_start(endingSelection().start().downstream())
    , m_end(endingSelection().end().upstream())
    , m_useEndingSelection(true)
    , m_styledInlineElement(nullptr)
    , m_removeOnly(false)
    , m_isInlineElementToRemoveFunction(0)
{
}

ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
    : CompositeEditCommand(document)
    , m_style(style->copy())
    , m_editingAction(editingAction)
    , m_propertyLevel(propertyLevel)
    , m_start(start)
    , m_end(end)
    , m_useEndingSelection(false)
    , m_styledInlineElement(nullptr)
    , m_removeOnly(false)
    , m_isInlineElementToRemoveFunction(0)
{
}

ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
    : CompositeEditCommand(element->document())
    , m_style(EditingStyle::create())
    , m_editingAction(editingAction)
    , m_propertyLevel(PropertyDefault)
    , m_start(endingSelection().start().downstream())
    , m_end(endingSelection().end().upstream())
    , m_useEndingSelection(true)
    , m_styledInlineElement(element)
    , m_removeOnly(removeOnly)
    , m_isInlineElementToRemoveFunction(0)
{
}

ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
    : CompositeEditCommand(document)
    , m_style(style->copy())
    , m_editingAction(editingAction)
    , m_propertyLevel(PropertyDefault)
    , m_start(endingSelection().start().downstream())
    , m_end(endingSelection().end().upstream())
    , m_useEndingSelection(true)
    , m_styledInlineElement(nullptr)
    , m_removeOnly(true)
    , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
{
}

void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
{
    ASSERT(comparePositions(newEnd, newStart) >= 0);

    if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
        m_useEndingSelection = true;

    setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional()));
    m_start = newStart;
    m_end = newEnd;
}

Position ApplyStyleCommand::startPosition()
{
    if (m_useEndingSelection)
        return endingSelection().start();

    return m_start;
}

Position ApplyStyleCommand::endPosition()
{
    if (m_useEndingSelection)
        return endingSelection().end();

    return m_end;
}

void ApplyStyleCommand::doApply()
{
    switch (m_propertyLevel) {
    case PropertyDefault: {
        // Apply the block-centric properties of the style.
        RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
        if (!blockStyle->isEmpty())
            applyBlockStyle(blockStyle.get());
        // Apply any remaining styles to the inline elements.
        if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
            applyRelativeFontStyleChange(m_style.get());
            applyInlineStyle(m_style.get());
        }
        break;
    }
    case ForceBlockProperties:
        // Force all properties to be applied as block styles.
        applyBlockStyle(m_style.get());
        break;
    }
}

EditAction ApplyStyleCommand::editingAction() const
{
    return m_editingAction;
}

void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
{
    // update document layout once before removing styles
    // so that we avoid the expense of updating before each and every call
    // to check a computed style
    document().updateLayoutIgnorePendingStylesheets();

    // get positions we want to use for applying style
    Position start = startPosition();
    Position end = endPosition();
    if (comparePositions(end, start) < 0) {
        Position swap = start;
        start = end;
        end = swap;
    }

    VisiblePosition visibleStart(start);
    VisiblePosition visibleEnd(end);

    if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
        return;

    // Save and restore the selection endpoints using their indices in the document, since
    // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
    // Calculate start and end indices from the start of the tree that they're in.
    Node& scope = visibleStart.deepEquivalent().deprecatedNode()->highestAncestor();
    RefPtrWillBeRawPtr<Range> startRange = Range::create(document(), firstPositionInNode(&scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
    RefPtrWillBeRawPtr<Range> endRange = Range::create(document(), firstPositionInNode(&scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
    int startIndex = TextIterator::rangeLength(startRange.get(), true);
    int endIndex = TextIterator::rangeLength(endRange.get(), true);

    VisiblePosition paragraphStart(startOfParagraph(visibleStart));
    VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
    VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
    while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
        StyleChange styleChange(style, paragraphStart.deepEquivalent());
        if (styleChange.cssStyle().length() || m_removeOnly) {
            RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
            if (!m_removeOnly) {
                RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
                if (newBlock)
                    block = newBlock;
            }
            ASSERT(!block || block->isHTMLElement());
            if (block && block->isHTMLElement()) {
                removeCSSStyle(style, toHTMLElement(block));
                if (!m_removeOnly)
                    addBlockStyle(styleChange, toHTMLElement(block));
            }

            if (nextParagraphStart.isOrphan())
                nextParagraphStart = endOfParagraph(paragraphStart).next();
        }

        paragraphStart = nextParagraphStart;
        nextParagraphStart = endOfParagraph(paragraphStart).next();
    }

    startRange = PlainTextRange(startIndex).createRangeForSelection(toContainerNode(scope));
    endRange = PlainTextRange(endIndex).createRangeForSelection(toContainerNode(scope));
    if (startRange && endRange)
        updateStartEnd(startRange->startPosition(), endRange->startPosition());
}

static PassRefPtrWillBeRawPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style)
{
    if (!style)
        return MutableStylePropertySet::create();
    return style->mutableCopy();
}

void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
{
    static const float MinimumFontSize = 0.1f;

    if (!style || !style->hasFontSizeDelta())
        return;

    Position start = startPosition();
    Position end = endPosition();
    if (comparePositions(end, start) < 0) {
        Position swap = start;
        start = end;
        end = swap;
    }

    // Join up any adjacent text nodes.
    if (start.deprecatedNode()->isTextNode()) {
        joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
        start = startPosition();
        end = endPosition();
    }

    if (start.isNull() || end.isNull())
        return;

    if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
        joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
        start = startPosition();
        end = endPosition();
    }

    if (start.isNull() || end.isNull())
        return;

    // Split the start text nodes if needed to apply style.
    if (isValidCaretPositionInTextNode(start)) {
        splitTextAtStart(start, end);
        start = startPosition();
        end = endPosition();
    }

    if (isValidCaretPositionInTextNode(end)) {
        splitTextAtEnd(start, end);
        start = startPosition();
        end = endPosition();
    }

    // Calculate loop end point.
    // If the end node is before the start node (can only happen if the end node is
    // an ancestor of the start node), we gather nodes up to the next sibling of the end node
    Node* beyondEnd;
    ASSERT(start.deprecatedNode());
    ASSERT(end.deprecatedNode());
    if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
        beyondEnd = NodeTraversal::nextSkippingChildren(*end.deprecatedNode());
    else
        beyondEnd = NodeTraversal::next(*end.deprecatedNode());

    start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
    Node* startNode = start.deprecatedNode();
    ASSERT(startNode);

    // Make sure we're not already at the end or the next NodeTraversal::next() will traverse
    // past it.
    if (startNode == beyondEnd)
        return;

    if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
        startNode = NodeTraversal::next(*startNode);

    // Store away font size before making any changes to the document.
    // This ensures that changes to one node won't effect another.
    HashMap<Node*, float> startingFontSizes;
    for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) {
        ASSERT(node);
        startingFontSizes.set(node, computedFontSize(node));
    }

    // These spans were added by us. If empty after font size changes, they can be removed.
    Vector<RefPtr<HTMLElement> > unstyledSpans;

    Node* lastStyledNode = 0;
    for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) {
        ASSERT(node);
        RefPtr<HTMLElement> element;
        if (node->isHTMLElement()) {
            // Only work on fully selected nodes.
            if (!nodeFullySelected(node, start, end))
                continue;
            element = toHTMLElement(node);
        } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
            // Last styled node was not parent node of this text node, but we wish to style this
            // text node. To make this possible, add a style span to surround this text node.
            RefPtr<HTMLElement> span = createStyleSpanElement(document());
            surroundNodeRangeWithElement(node, node, span.get());
            element = span.release();
        }  else {
            // Only handle HTML elements and text nodes.
            continue;
        }
        lastStyledNode = node;

        RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
        float currentFontSize = computedFontSize(node);
        float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
        RefPtrWillBeRawPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize);
        if (value) {
            element->removeInlineStyleProperty(CSSPropertyFontSize);
            currentFontSize = computedFontSize(node);
        }
        if (currentFontSize != desiredFontSize) {
            inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false);
            setNodeAttribute(element.get(), styleAttr, AtomicString(inlineStyle->asText()));
        }
        if (inlineStyle->isEmpty()) {
            removeNodeAttribute(element.get(), styleAttr);
            if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
                unstyledSpans.append(element.release());
        }
    }

    size_t size = unstyledSpans.size();
    for (size_t i = 0; i < size; ++i)
        removeNodePreservingChildren(unstyledSpans[i].get());
}

static Node* dummySpanAncestorForNode(const Node* node)
{
    while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node))))
        node = node->parentNode();

    return node ? node->parentNode() : 0;
}

void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
{
    if (!dummySpanAncestor)
        return;

    // Dummy spans are created when text node is split, so that style information
    // can be propagated, which can result in more splitting. If a dummy span gets
    // cloned/split, the new node is always a sibling of it. Therefore, we scan
    // all the children of the dummy's parent
    Node* next;
    for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
        next = node->nextSibling();
        if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
            removeNodePreservingChildren(node);
    }
}

HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
{
    // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
    // In that case, we return the unsplit ancestor. Otherwise, we return 0.
    Node* block = enclosingBlock(node);
    if (!block)
        return 0;

    Node* highestAncestorWithUnicodeBidi = 0;
    Node* nextHighestAncestorWithUnicodeBidi = 0;
    int highestAncestorUnicodeBidi = 0;
    for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
        int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi);
        if (unicodeBidi && unicodeBidi != CSSValueNormal) {
            highestAncestorUnicodeBidi = unicodeBidi;
            nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
            highestAncestorWithUnicodeBidi = n;
        }
    }

    if (!highestAncestorWithUnicodeBidi)
        return 0;

    HTMLElement* unsplitAncestor = 0;

    WritingDirection highestAncestorDirection;
    if (allowedDirection != NaturalWritingDirection
        && highestAncestorUnicodeBidi != CSSValueBidiOverride
        && highestAncestorWithUnicodeBidi->isHTMLElement()
        && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
        && highestAncestorDirection == allowedDirection) {
        if (!nextHighestAncestorWithUnicodeBidi)
            return toHTMLElement(highestAncestorWithUnicodeBidi);

        unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
        highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
    }

    // Split every ancestor through highest ancestor with embedding.
    RefPtr<Node> currentNode = node;
    while (currentNode) {
        RefPtr<Element> parent = toElement(currentNode->parentNode());
        if (before ? currentNode->previousSibling() : currentNode->nextSibling())
            splitElement(parent, before ? currentNode : currentNode->nextSibling());
        if (parent == highestAncestorWithUnicodeBidi)
            break;
        currentNode = parent;
    }
    return unsplitAncestor;
}

void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
{
    Node* block = enclosingBlock(node);
    if (!block)
        return;

    for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = n->parentNode()) {
        if (!n->isStyledElement())
            continue;

        Element* element = toElement(n);
        int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(element).get(), CSSPropertyUnicodeBidi);
        if (!unicodeBidi || unicodeBidi == CSSValueNormal)
            continue;

        // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
        // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
        // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
        // otherwise it sets the property in the inline style declaration.
        if (element->hasAttribute(dirAttr)) {
            // FIXME: If this is a BDO element, we should probably just remove it if it has no
            // other attributes, like we (should) do with B and I elements.
            removeNodeAttribute(element, dirAttr);
        } else {
            RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
            inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
            inlineStyle->removeProperty(CSSPropertyDirection);
            setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText()));
            if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
                removeNodePreservingChildren(element);
        }
    }
}

static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
{
    for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
        if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
            return n;
    }

    return 0;
}

void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
{
    RefPtr<Node> startDummySpanAncestor = nullptr;
    RefPtr<Node> endDummySpanAncestor = nullptr;

    // update document layout once before removing styles
    // so that we avoid the expense of updating before each and every call
    // to check a computed style
    document().updateLayoutIgnorePendingStylesheets();

    // adjust to the positions we want to use for applying style
    Position start = startPosition();
    Position end = endPosition();

    if (start.isNull() || end.isNull())
        return;

    if (comparePositions(end, start) < 0) {
        Position swap = start;
        start = end;
        end = swap;
    }

    // split the start node and containing element if the selection starts inside of it
    bool splitStart = isValidCaretPositionInTextNode(start);
    if (splitStart) {
        if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
            splitTextElementAtStart(start, end);
        else
            splitTextAtStart(start, end);
        start = startPosition();
        end = endPosition();
        startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
    }

    // split the end node and containing element if the selection ends inside of it
    bool splitEnd = isValidCaretPositionInTextNode(end);
    if (splitEnd) {
        if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
            splitTextElementAtEnd(start, end);
        else
            splitTextAtEnd(start, end);
        start = startPosition();
        end = endPosition();
        endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
    }

    // Remove style from the selection.
    // Use the upstream position of the start for removing style.
    // This will ensure we remove all traces of the relevant styles from the selection
    // and prevent us from adding redundant ones, as described in:
    // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
    Position removeStart = start.upstream();
    WritingDirection textDirection = NaturalWritingDirection;
    bool hasTextDirection = style->textDirection(textDirection);
    RefPtr<EditingStyle> styleWithoutEmbedding;
    RefPtr<EditingStyle> embeddingStyle;
    if (hasTextDirection) {
        // Leave alone an ancestor that provides the desired single level embedding, if there is one.
        HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
        HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
        removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
        removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);

        // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
        Position embeddingRemoveStart = removeStart;
        if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
            embeddingRemoveStart = positionInParentAfterNode(*startUnsplitAncestor);

        Position embeddingRemoveEnd = end;
        if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
            embeddingRemoveEnd = positionInParentBeforeNode(*endUnsplitAncestor).downstream();

        if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
            styleWithoutEmbedding = style->copy();
            embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();

            if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
                removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
        }
    }

    removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
    start = startPosition();
    end = endPosition();
    if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
        return;

    if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
        start = startPosition();
        end = endPosition();
    }

    if (splitEnd) {
        mergeEndWithNextIfIdentical(start, end);
        start = startPosition();
        end = endPosition();
    }

    // update document layout once before running the rest of the function
    // so that we avoid the expense of updating before each and every call
    // to check a computed style
    document().updateLayoutIgnorePendingStylesheets();

    RefPtr<EditingStyle> styleToApply = style;
    if (hasTextDirection) {
        // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
        Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
        Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));

        if (embeddingStartNode || embeddingEndNode) {
            Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(*embeddingStartNode) : start;
            Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(*embeddingEndNode) : end;
            ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());

            if (!embeddingStyle) {
                styleWithoutEmbedding = style->copy();
                embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
            }
            fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);

            styleToApply = styleWithoutEmbedding;
        }
    }

    fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);

    // Remove dummy style spans created by splitting text elements.
    cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get());
    if (endDummySpanAncestor != startDummySpanAncestor)
        cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get());
}

void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
{
    Node* startNode = start.deprecatedNode();
    ASSERT(startNode);

    if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
        startNode = NodeTraversal::next(*startNode);
        if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
            return;
    }

    Node* pastEndNode = end.deprecatedNode();
    if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
        pastEndNode = NodeTraversal::nextSkippingChildren(*end.deprecatedNode());

    // FIXME: Callers should perform this operation on a Range that includes the br
    // if they want style applied to the empty line.
    if (start == end && isHTMLBRElement(*start.deprecatedNode()))
        pastEndNode = NodeTraversal::next(*start.deprecatedNode());

    // Start from the highest fully selected ancestor so that we can modify the fully selected node.
    // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
    // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
    RefPtrWillBeRawPtr<Range> range = Range::create(startNode->document(), start, end);
    Element* editableRoot = startNode->rootEditableElement();
    if (startNode != editableRoot) {
        while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(*startNode->parentNode(), *range))
            startNode = startNode->parentNode();
    }

    applyInlineStyleToNodeRange(style, startNode, pastEndNode);
}

static bool containsNonEditableRegion(Node& node)
{
    if (!node.rendererIsEditable())
        return true;

    Node* sibling = NodeTraversal::nextSkippingChildren(node);
    for (Node* descendent = node.firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(*descendent)) {
        if (!descendent->rendererIsEditable())
            return true;
    }

    return false;
}

struct InlineRunToApplyStyle {
    InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode)
        : start(start)
        , end(end)
        , pastEndNode(pastEndNode)
    {
        ASSERT(start->parentNode() == end->parentNode());
    }

    bool startAndEndAreStillInDocument()
    {
        return start && end && start->inDocument() && end->inDocument();
    }

    RefPtr<Node> start;
    RefPtr<Node> end;
    RefPtr<Node> pastEndNode;
    Position positionForStyleComputation;
    RefPtr<Node> dummyElement;
    StyleChange change;
};

void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode)
{
    if (m_removeOnly)
        return;

    document().updateLayoutIgnorePendingStylesheets();

    Vector<InlineRunToApplyStyle> runs;
    RefPtr<Node> node = startNode;
    for (RefPtr<Node> next; node && node != pastEndNode; node = next) {
        next = NodeTraversal::next(*node);

        if (!node->renderer() || !node->rendererIsEditable())
            continue;

        if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
            // This is a plaintext-only region. Only proceed if it's fully selected.
            // pastEndNode is the node after the last fully selected node, so if it's inside node then
            // node isn't fully selected.
            if (pastEndNode && pastEndNode->isDescendantOf(node.get()))
                break;
            // Add to this element's inline style and skip over its contents.
            HTMLElement* element = toHTMLElement(node);
            next = NodeTraversal::nextSkippingChildren(*node);
            if (!style->style())
                continue;
            RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
            inlineStyle->mergeAndOverrideOnConflict(style->style());
            setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText()));
            continue;
        }

        if (isBlock(node.get()))
            continue;

        if (node->hasChildren()) {
            if (node->contains(pastEndNode.get()) || containsNonEditableRegion(*node) || !node->parentNode()->rendererIsEditable())
                continue;
            if (editingIgnoresContent(node.get())) {
                next = NodeTraversal::nextSkippingChildren(*node);
                continue;
            }
        }

        Node* runStart = node.get();
        Node* runEnd = node.get();
        Node* sibling = node->nextSibling();
        while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get())
            && (!isBlock(sibling) || isHTMLBRElement(*sibling))
            && !containsNonEditableRegion(*sibling)) {
            runEnd = sibling;
            sibling = runEnd->nextSibling();
        }
        ASSERT(runEnd);
        next = NodeTraversal::nextSkippingChildren(*runEnd);

        Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd);
        if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode))
            continue;

        runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode));
    }

    for (size_t i = 0; i < runs.size(); i++) {
        removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode);
        if (runs[i].startAndEndAreStillInDocument())
            runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement);
    }

    document().updateLayoutIgnorePendingStylesheets();

    for (size_t i = 0; i < runs.size(); i++) {
        if (runs[i].positionForStyleComputation.isNotNull())
            runs[i].change = StyleChange(style, runs[i].positionForStyleComputation);
    }

    for (size_t i = 0; i < runs.size(); i++) {
        InlineRunToApplyStyle& run = runs[i];
        if (run.dummyElement)
            removeNode(run.dummyElement);
        if (run.startAndEndAreStillInDocument())
            applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement);
    }
}

bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
{
    return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
        || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
}

bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode)
{
    ASSERT(style && runStart);

    for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(*node)) {
        if (node->hasChildren())
            continue;
        // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
        if (!style->styleIsPresentInComputedStyleOfNode(node))
            return true;
        if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))
            return true;
    }
    return false;
}

void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode)
{
    ASSERT(runStart && runEnd);
    RefPtr<Node> next = runStart;
    for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
        if (editingIgnoresContent(node.get())) {
            ASSERT(!node->contains(pastEndNode.get()));
            next = NodeTraversal::nextSkippingChildren(*node);
        } else {
            next = NodeTraversal::next(*node);
        }
        if (!node->isHTMLElement())
            continue;

        RefPtr<Node> previousSibling = node->previousSibling();
        RefPtr<Node> nextSibling = node->nextSibling();
        RefPtr<ContainerNode> parent = node->parentNode();
        removeInlineStyleFromElement(style, toHTMLElement(node), RemoveAlways);
        if (!node->inDocument()) {
            // FIXME: We might need to update the start and the end of current selection here but need a test.
            if (runStart == node)
                runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
            if (runEnd == node)
                runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
        }
    }
}

bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
{
    ASSERT(element);

    if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
        return false;

    if (isStyledInlineElementToRemove(element.get())) {
        if (mode == RemoveNone)
            return true;
        if (extractedStyle)
            extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues);
        removeNodePreservingChildren(element);
        return true;
    }

    bool removed = false;
    if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
        removed = true;

    if (!element->inDocument())
        return removed;

    // If the node was converted to a span, the span may still contain relevant
    // styles which must be removed (e.g. <b style='font-weight: bold'>)
    if (removeCSSStyle(style, element.get(), mode, extractedStyle))
        removed = true;

    return removed;
}

void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement* elem)
{
    if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
        removeNodePreservingChildren(elem);
    else
        replaceElementWithSpanPreservingChildrenAndAttributes(elem);
}

bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
{
    ASSERT(style);
    if (mode == RemoveNone) {
        ASSERT(!extractedStyle);
        return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
    }

    ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
    if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
        replaceWithSpanOrRemoveIfWithoutAttributes(element);
        return true;
    }

    // unicode-bidi and direction are pushed down separately so don't push down with other styles
    Vector<QualifiedName> attributes;
    if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
        extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
        return false;

    for (size_t i = 0; i < attributes.size(); i++)
        removeNodeAttribute(element, attributes[i]);

    if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element))
        removeNodePreservingChildren(element);

    return true;
}

bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
{
    ASSERT(style);
    ASSERT(element);

    if (mode == RemoveNone)
        return style->conflictsWithInlineStyleOfElement(element);

    Vector<CSSPropertyID> properties;
    if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
        return false;

    // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
    for (size_t i = 0; i < properties.size(); i++)
        removeCSSProperty(element, properties[i]);

    if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
        removeNodePreservingChildren(element);

    return true;
}

HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
{
    if (!node)
        return 0;

    HTMLElement* result = 0;
    Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));

    for (Node *n = node; n; n = n->parentNode()) {
        if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
            result = toHTMLElement(n);
        // Should stop at the editable root (cannot cross editing boundary) and
        // also stop at the unsplittable element to be consistent with other UAs
        if (n == unsplittableElement)
            break;
    }

    return result;
}

void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
{
    ASSERT(node);

    node->document().updateRenderTreeIfNeeded();

    if (!style || style->isEmpty() || !node->renderer() || isHTMLIFrameElement(*node))
        return;

    RefPtr<EditingStyle> newInlineStyle = style;
    if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) {
        newInlineStyle = style->copy();
        newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues);
    }

    // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
    // FIXME: applyInlineStyleToRange should be used here instead.
    if ((node->renderer()->isRenderBlockFlow() || node->hasChildren()) && node->isHTMLElement()) {
        setNodeAttribute(toHTMLElement(node), styleAttr, AtomicString(newInlineStyle->style()->asText()));
        return;
    }

    if (node->renderer()->isText() && toRenderText(node->renderer())->isAllCollapsibleWhitespace())
        return;

    // We can't wrap node with the styled element here because new styled element will never be removed if we did.
    // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
    // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
    addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
}

void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
{
    HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
    if (!highestAncestor)
        return;

    // The outer loop is traversing the tree vertically from highestAncestor to targetNode
    RefPtr<Node> current = highestAncestor;
    // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
    // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
    Vector<RefPtr<Element> > elementsToPushDown;
    while (current && current != targetNode && current->contains(targetNode)) {
        NodeVector currentChildren;
        getChildNodes(*current, currentChildren);
        RefPtr<Element> styledElement;
        if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current))) {
            styledElement = toElement(current);
            elementsToPushDown.append(styledElement);
        }

        RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
        if (current->isHTMLElement())
            removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get());

        // The inner loop will go through children on each level
        // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
        for (size_t i = 0; i < currentChildren.size(); ++i) {
            Node* child = currentChildren[i].get();
            if (!child->parentNode())
                continue;
            if (!child->contains(targetNode) && elementsToPushDown.size()) {
                for (size_t i = 0; i < elementsToPushDown.size(); i++) {
                    RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
                    wrapper->removeAttribute(styleAttr);
                    surroundNodeRangeWithElement(child, child, wrapper);
                }
            }

            // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
            // But if we've removed styledElement then go ahead and always apply the style.
            if (child != targetNode || styledElement)
                applyInlineStyleToPushDown(child, styleToPushDown.get());

            // We found the next node for the outer loop (contains targetNode)
            // When reached targetNode, stop the outer loop upon the completion of the current inner loop
            if (child == targetNode || child->contains(targetNode))
                current = child;
        }
    }
}

void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
{
    ASSERT(start.isNotNull());
    ASSERT(end.isNotNull());
    ASSERT(start.inDocument());
    ASSERT(end.inDocument());
    ASSERT(comparePositions(start, end) <= 0);
    // FIXME: We should assert that start/end are not in the middle of a text node.

    Position pushDownStart = start.downstream();
    // If the pushDownStart is at the end of a text node, then this node is not fully selected.
    // Move it to the next deep quivalent position to avoid removing the style from this node.
    // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
    Node* pushDownStartContainer = pushDownStart.containerNode();
    if (pushDownStartContainer && pushDownStartContainer->isTextNode()
        && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
        pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
    Position pushDownEnd = end.upstream();
    // If pushDownEnd is at the start of a text node, then this node is not fully selected.
    // Move it to the previous deep equivalent position to avoid removing the style from this node.
    Node* pushDownEndContainer = pushDownEnd.containerNode();
    if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode())
        pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd);

    pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
    pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());

    // The s and e variables store the positions used to set the ending selection after style removal
    // takes place. This will help callers to recognize when either the start node or the end node
    // are removed from the document during the work of this function.
    // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
    // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
    Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
    Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;

    RefPtr<Node> node = start.deprecatedNode();
    while (node) {
        RefPtr<Node> next;
        if (editingIgnoresContent(node.get())) {
            ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode()));
            next = NodeTraversal::nextSkippingChildren(*node);
        } else {
            next = NodeTraversal::next(*node);
        }
        if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) {
            RefPtr<HTMLElement> elem = toHTMLElement(node);
            RefPtr<Node> prev = NodeTraversal::previousPostOrder(*elem);
            RefPtr<Node> next = NodeTraversal::next(*elem);
            RefPtr<EditingStyle> styleToPushDown;
            RefPtr<Node> childNode;
            if (isStyledInlineElementToRemove(elem.get())) {
                styleToPushDown = EditingStyle::create();
                childNode = elem->firstChild();
            }

            removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
            if (!elem->inDocument()) {
                if (s.deprecatedNode() == elem) {
                    // Since elem must have been fully selected, and it is at the start
                    // of the selection, it is clear we can set the new s offset to 0.
                    ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
                    s = firstPositionInOrBeforeNode(next.get());
                }
                if (e.deprecatedNode() == elem) {
                    // Since elem must have been fully selected, and it is at the end
                    // of the selection, it is clear we can set the new e offset to
                    // the max range offset of prev.
                    ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode()));
                    e = lastPositionInOrAfterNode(prev.get());
                }
            }

            if (styleToPushDown) {
                for (; childNode; childNode = childNode->nextSibling())
                    applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
            }
        }
        if (node == end.deprecatedNode())
            break;
        node = next;
    }

    updateStartEnd(s, e);
}

bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
{
    ASSERT(node);
    ASSERT(node->isElementNode());

    // The tree may have changed and Position::upstream() relies on an up-to-date layout.
    node->document().updateLayoutIgnorePendingStylesheets();

    return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
        && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
}

void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
{
    ASSERT(start.containerNode()->isTextNode());

    Position newEnd;
    if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
        newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
    else
        newEnd = end;

    RefPtr<Text> text = start.containerText();
    splitTextNode(text, start.offsetInContainerNode());
    updateStartEnd(firstPositionInNode(text.get()), newEnd);
}

void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
{
    ASSERT(end.containerNode()->isTextNode());

    bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
    Text* text = toText(end.deprecatedNode());
    splitTextNode(text, end.offsetInContainerNode());

    Node* prevNode = text->previousSibling();
    if (!prevNode || !prevNode->isTextNode())
        return;

    Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start;
    updateStartEnd(newStart, lastPositionInNode(prevNode));
}

void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
{
    ASSERT(start.containerNode()->isTextNode());

    Position newEnd;
    if (start.containerNode() == end.containerNode())
        newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
    else
        newEnd = end;

    splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode());
    updateStartEnd(positionBeforeNode(start.containerNode()), newEnd);
}

void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
{
    ASSERT(end.containerNode()->isTextNode());

    bool shouldUpdateStart = start.containerNode() == end.containerNode();
    splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode());

    Node* parentElement = end.containerNode()->parentNode();
    if (!parentElement || !parentElement->previousSibling())
        return;
    Node* firstTextNode = parentElement->previousSibling()->lastChild();
    if (!firstTextNode || !firstTextNode->isTextNode())
        return;

    Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start;
    updateStartEnd(newStart, positionAfterNode(firstTextNode));
}

bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
{
    if (!element || !element->isHTMLElement())
        return false;

    return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
}

bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
{
    Node* node = position.containerNode();
    if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
        return false;
    int offsetInText = position.offsetInContainerNode();
    return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
}

bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
{
    Node* startNode = start.containerNode();
    int startOffset = start.computeOffsetInContainerNode();
    if (startOffset)
        return false;

    if (isAtomicNode(startNode)) {
        // note: prior siblings could be unrendered elements. it's silly to miss the
        // merge opportunity just for that.
        if (startNode->previousSibling())
            return false;

        startNode = startNode->parentNode();
    }

    if (!startNode->isElementNode())
        return false;

    Node* previousSibling = startNode->previousSibling();

    if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
        Element* previousElement = toElement(previousSibling);
        Element* element = toElement(startNode);
        Node* startChild = element->firstChild();
        ASSERT(startChild);
        mergeIdenticalElements(previousElement, element);

        int startOffsetAdjustment = startChild->nodeIndex();
        int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
        updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
                       Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
        return true;
    }

    return false;
}

bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
{
    Node* endNode = end.containerNode();

    if (isAtomicNode(endNode)) {
        int endOffset = end.computeOffsetInContainerNode();
        if (offsetIsBeforeLastNodeOffset(endOffset, endNode))
            return false;

        if (end.deprecatedNode()->nextSibling())
            return false;

        endNode = end.deprecatedNode()->parentNode();
    }

    if (!endNode->isElementNode() || isHTMLBRElement(*endNode))
        return false;

    Node* nextSibling = endNode->nextSibling();
    if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
        Element* nextElement = toElement(nextSibling);
        Element* element = toElement(endNode);
        Node* nextChild = nextElement->firstChild();

        mergeIdenticalElements(element, nextElement);

        bool shouldUpdateStart = start.containerNode() == endNode;
        int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
        updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
                       Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
        return true;
    }

    return false;
}

void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
{
    ASSERT(passedStartNode);
    ASSERT(endNode);
    ASSERT(elementToInsert);
    RefPtr<Node> node = passedStartNode;
    RefPtr<Element> element = elementToInsert;

    insertNodeBefore(element, node);

    while (node) {
        RefPtr<Node> next = node->nextSibling();
        if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
            removeNode(node);
            appendNode(node, element);
        }
        if (node == endNode)
            break;
        node = next;
    }

    RefPtr<Node> nextSibling = element->nextSibling();
    RefPtr<Node> previousSibling = element->previousSibling();
    if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
        && areIdenticalElements(element.get(), toElement(nextSibling)))
        mergeIdenticalElements(element.get(), toElement(nextSibling));

    if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
        Node* mergedElement = previousSibling->nextSibling();
        if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
            && areIdenticalElements(toElement(previousSibling), toElement(mergedElement)))
            mergeIdenticalElements(toElement(previousSibling), toElement(mergedElement));
    }

    // FIXME: We should probably call updateStartEnd if the start or end was in the node
    // range so that the endingSelection() is canonicalized.  See the comments at the end of
    // VisibleSelection::validate().
}

void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
{
    // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
    // inline content.
    if (!block)
        return;

    String cssStyle = styleChange.cssStyle();
    StringBuilder cssText;
    cssText.append(cssStyle);
    if (const StylePropertySet* decl = block->inlineStyle()) {
        if (!cssStyle.isEmpty())
            cssText.append(' ');
        cssText.append(decl->asText());
    }
    setNodeAttribute(block, styleAttr, cssText.toAtomicString());
}

void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
{
    if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
        return;

    RefPtr<Node> start = passedStart;
    RefPtr<Node> dummyElement;
    StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dummyElement));

    if (dummyElement)
        removeNode(dummyElement);

    applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement);
}

Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement)
{
    // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
    if (!startNode->isElementNode()) {
        dummyElement = createStyleSpanElement(document());
        insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
        return positionBeforeNode(dummyElement.get());
    }

    return firstPositionInOrBeforeNode(startNode.get());
}

void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, StyleChange& styleChange, EAddStyledElement addStyledElement)
{
    RefPtr<Node> startNode = passedStart;
    RefPtr<Node> endNode = passedEnd;
    ASSERT(startNode->inDocument());
    ASSERT(endNode->inDocument());

    // Find appropriate font and span elements top-down.
    HTMLElement* fontContainer = 0;
    HTMLElement* styleContainer = 0;
    for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
        if (isHTMLFontElement(*container))
            fontContainer = toHTMLElement(container);
        bool styleContainerIsNotSpan = !isHTMLSpanElement(styleContainer);
        if (container->isHTMLElement()) {
            HTMLElement* containerElement = toHTMLElement(container);
            if (isHTMLSpanElement(*containerElement) || (styleContainerIsNotSpan && containerElement->hasChildren()))
                styleContainer = toHTMLElement(container);
        }
        if (!container->firstChild())
            break;
        startNode = container->firstChild();
        endNode = container->lastChild();
    }

    // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
    if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
        if (fontContainer) {
            if (styleChange.applyFontColor())
                setNodeAttribute(fontContainer, colorAttr, AtomicString(styleChange.fontColor()));
            if (styleChange.applyFontFace())
                setNodeAttribute(fontContainer, faceAttr, AtomicString(styleChange.fontFace()));
            if (styleChange.applyFontSize())
                setNodeAttribute(fontContainer, sizeAttr, AtomicString(styleChange.fontSize()));
        } else {
            RefPtr<Element> fontElement = createFontElement(document());
            if (styleChange.applyFontColor())
                fontElement->setAttribute(colorAttr, AtomicString(styleChange.fontColor()));
            if (styleChange.applyFontFace())
                fontElement->setAttribute(faceAttr, AtomicString(styleChange.fontFace()));
            if (styleChange.applyFontSize())
                fontElement->setAttribute(sizeAttr, AtomicString(styleChange.fontSize()));
            surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
        }
    }

    if (styleChange.cssStyle().length()) {
        if (styleContainer) {
            if (const StylePropertySet* existingStyle = styleContainer->inlineStyle()) {
                String existingText = existingStyle->asText();
                StringBuilder cssText;
                cssText.append(existingText);
                if (!existingText.isEmpty())
                    cssText.append(' ');
                cssText.append(styleChange.cssStyle());
                setNodeAttribute(styleContainer, styleAttr, cssText.toAtomicString());
            } else {
                setNodeAttribute(styleContainer, styleAttr, AtomicString(styleChange.cssStyle()));
            }
        } else {
            RefPtr<Element> styleElement = createStyleSpanElement(document());
            styleElement->setAttribute(styleAttr, AtomicString(styleChange.cssStyle()));
            surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
        }
    }

    if (styleChange.applyBold())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));

    if (styleChange.applyItalic())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));

    if (styleChange.applyUnderline())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));

    if (styleChange.applyLineThrough())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));

    if (styleChange.applySubscript())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
    else if (styleChange.applySuperscript())
        surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));

    if (m_styledInlineElement && addStyledElement == AddStyledElement)
        surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
}

float ApplyStyleCommand::computedFontSize(Node* node)
{
    if (!node)
        return 0;

    RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
    if (!style)
        return 0;

    RefPtrWillBeRawPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
    if (!value)
        return 0;

    return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
}

void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
{
    if (!node)
        return;

    Position newStart = start;
    Position newEnd = end;

    Vector<RefPtr<Text> > textNodes;
    for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) {
        if (!curr->isTextNode())
            continue;

        textNodes.append(toText(curr));
    }

    for (size_t i = 0; i < textNodes.size(); ++i) {
        Text* childText = textNodes[i].get();
        Node* next = childText->nextSibling();
        if (!next || !next->isTextNode())
            continue;

        Text* nextText = toText(next);
        if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
            newStart = Position(childText, childText->length() + start.offsetInContainerNode());
        if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
            newEnd = Position(childText, childText->length() + end.offsetInContainerNode());
        String textToMove = nextText->data();
        insertTextIntoNode(childText, childText->length(), textToMove);
        removeNode(next);
        // don't move child node pointer. it may want to merge with more text nodes.
    }

    updateStartEnd(newStart, newEnd);
}

}

/* [<][>][^][v][top][bottom][index][help] */