WebCore/editing/ApplyStyleCommand.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 "ApplyStyleCommand.h"
       
    28 
       
    29 #include "CSSComputedStyleDeclaration.h"
       
    30 #include "CSSMutableStyleDeclaration.h"
       
    31 #include "CSSParser.h"
       
    32 #include "CSSProperty.h"
       
    33 #include "CSSPropertyNames.h"
       
    34 #include "CSSValueKeywords.h"
       
    35 #include "Document.h"
       
    36 #include "Editor.h"
       
    37 #include "Frame.h"
       
    38 #include "HTMLElement.h"
       
    39 #include "HTMLInterchange.h"
       
    40 #include "HTMLNames.h"
       
    41 #include "NodeList.h"
       
    42 #include "Range.h"
       
    43 #include "RenderObject.h"
       
    44 #include "Text.h"
       
    45 #include "TextIterator.h"
       
    46 #include "htmlediting.h"
       
    47 #include "visible_units.h"
       
    48 #include <wtf/StdLibExtras.h>
       
    49 
       
    50 namespace WebCore {
       
    51 
       
    52 using namespace HTMLNames;
       
    53 
       
    54 class StyleChange {
       
    55 public:
       
    56     explicit StyleChange(CSSStyleDeclaration*, const Position&);
       
    57 
       
    58     String cssStyle() const { return m_cssStyle; }
       
    59     bool applyBold() const { return m_applyBold; }
       
    60     bool applyItalic() const { return m_applyItalic; }
       
    61     bool applyUnderline() const { return m_applyUnderline; }
       
    62     bool applyLineThrough() const { return m_applyLineThrough; }
       
    63     bool applySubscript() const { return m_applySubscript; }
       
    64     bool applySuperscript() const { return m_applySuperscript; }
       
    65     bool applyFontColor() const { return m_applyFontColor.length() > 0; }
       
    66     bool applyFontFace() const { return m_applyFontFace.length() > 0; }
       
    67     bool applyFontSize() const { return m_applyFontSize.length() > 0; }
       
    68 
       
    69     String fontColor() { return m_applyFontColor; }
       
    70     String fontFace() { return m_applyFontFace; }
       
    71     String fontSize() { return m_applyFontSize; }
       
    72 
       
    73 private:
       
    74     void init(PassRefPtr<CSSStyleDeclaration>, const Position&);
       
    75     void reconcileTextDecorationProperties(CSSMutableStyleDeclaration*);
       
    76     void extractTextStyles(CSSMutableStyleDeclaration*);
       
    77 
       
    78     String m_cssStyle;
       
    79     bool m_applyBold;
       
    80     bool m_applyItalic;
       
    81     bool m_applyUnderline;
       
    82     bool m_applyLineThrough;
       
    83     bool m_applySubscript;
       
    84     bool m_applySuperscript;
       
    85     String m_applyFontColor;
       
    86     String m_applyFontFace;
       
    87     String m_applyFontSize;
       
    88 };
       
    89 
       
    90 
       
    91 StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position)
       
    92     : m_applyBold(false)
       
    93     , m_applyItalic(false)
       
    94     , m_applyUnderline(false)
       
    95     , m_applyLineThrough(false)
       
    96     , m_applySubscript(false)
       
    97     , m_applySuperscript(false)
       
    98 {
       
    99     init(style, position);
       
   100 }
       
   101 
       
   102 void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& position)
       
   103 {
       
   104     Document* document = position.node() ? position.node()->document() : 0;
       
   105     if (!document || !document->frame())
       
   106         return;
       
   107 
       
   108     RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
       
   109     RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotInComputedStyle(style.get(), computedStyle.get());
       
   110 
       
   111     reconcileTextDecorationProperties(mutableStyle.get());
       
   112     if (!document->frame()->editor()->shouldStyleWithCSS())
       
   113         extractTextStyles(mutableStyle.get());
       
   114 
       
   115     // Changing the whitespace style in a tab span would collapse the tab into a space.
       
   116     if (isTabSpanTextNode(position.node()) || isTabSpanNode((position.node())))
       
   117         mutableStyle->removeProperty(CSSPropertyWhiteSpace);
       
   118 
       
   119     // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
       
   120     // FIXME: Shouldn't this be done in getPropertiesNotInComputedStyle?
       
   121     if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection))
       
   122         mutableStyle->setProperty(CSSPropertyDirection, style->getPropertyValue(CSSPropertyDirection));
       
   123 
       
   124     // Save the result for later
       
   125     m_cssStyle = mutableStyle->cssText().stripWhiteSpace();
       
   126 }
       
   127 
       
   128 void StyleChange::reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style)
       
   129 {    
       
   130     RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
       
   131     RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
       
   132     // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
       
   133     ASSERT(!textDecorationsInEffect || !textDecoration);
       
   134     if (textDecorationsInEffect) {
       
   135         style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
       
   136         style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
       
   137         textDecoration = textDecorationsInEffect;
       
   138     }
       
   139 
       
   140     // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
       
   141     if (textDecoration && !textDecoration->isValueList())
       
   142         style->removeProperty(CSSPropertyTextDecoration);
       
   143 }
       
   144 
       
   145 static int getIdentifierValue(CSSMutableStyleDeclaration* style, int propertyID)
       
   146 {
       
   147     if (!style)
       
   148         return 0;
       
   149 
       
   150     RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
       
   151     if (!value || !value->isPrimitiveValue())
       
   152         return 0;
       
   153 
       
   154     return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
       
   155 }
       
   156 
       
   157 static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID)
       
   158 {
       
   159     if (newTextDecoration->length())
       
   160         style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID));
       
   161     else {
       
   162         // text-decoration: none is redundant since it does not remove any text decorations.
       
   163         ASSERT(!style->getPropertyPriority(propertyID));
       
   164         style->removeProperty(propertyID);
       
   165     }
       
   166 }
       
   167 
       
   168 void StyleChange::extractTextStyles(CSSMutableStyleDeclaration* style)
       
   169 {
       
   170     ASSERT(style);
       
   171 
       
   172     if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
       
   173         style->removeProperty(CSSPropertyFontWeight);
       
   174         m_applyBold = true;
       
   175     }
       
   176 
       
   177     int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
       
   178     if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
       
   179         style->removeProperty(CSSPropertyFontStyle);
       
   180         m_applyItalic = true;
       
   181     }
       
   182 
       
   183     // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
       
   184     // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
       
   185     if (RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration)) {
       
   186         ASSERT(textDecoration->isValueList());
       
   187         DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
       
   188         DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
       
   189 
       
   190         RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
       
   191         if (newTextDecoration->removeAll(underline.get()))
       
   192             m_applyUnderline = true;
       
   193         if (newTextDecoration->removeAll(lineThrough.get()))
       
   194             m_applyLineThrough = true;
       
   195 
       
   196         // If trimTextDecorations, delete underline and line-through
       
   197         setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
       
   198     }
       
   199 
       
   200     int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
       
   201     switch (verticalAlign) {
       
   202     case CSSValueSub:
       
   203         style->removeProperty(CSSPropertyVerticalAlign);
       
   204         m_applySubscript = true;
       
   205         break;
       
   206     case CSSValueSuper:
       
   207         style->removeProperty(CSSPropertyVerticalAlign);
       
   208         m_applySuperscript = true;
       
   209         break;
       
   210     }
       
   211 
       
   212     if (RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor)) {
       
   213         ASSERT(colorValue->isPrimitiveValue());
       
   214         CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get());
       
   215         RGBA32 rgba = 0;
       
   216         if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) {
       
   217             CSSParser::parseColor(rgba, colorValue->cssText());
       
   218             // Need to take care of named color such as green and black
       
   219             // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed.
       
   220         } else
       
   221             rgba = primitiveColor->getRGBA32Value();
       
   222         m_applyFontColor = Color(rgba).name();
       
   223         style->removeProperty(CSSPropertyColor);
       
   224     }
       
   225 
       
   226     m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
       
   227     style->removeProperty(CSSPropertyFontFamily);
       
   228 
       
   229     if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
       
   230         if (!fontSize->isPrimitiveValue())
       
   231             style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
       
   232         else {
       
   233             CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(fontSize.get());
       
   234 
       
   235             // Only accept absolute scale
       
   236             if (value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC) {
       
   237                 float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX);
       
   238                 if (number <= 9)
       
   239                     m_applyFontSize = "1";
       
   240                 else if (number <= 10)
       
   241                     m_applyFontSize = "2";
       
   242                 else if (number <= 13)
       
   243                     m_applyFontSize = "3";
       
   244                 else if (number <= 16)
       
   245                     m_applyFontSize = "4";
       
   246                 else if (number <= 18)
       
   247                     m_applyFontSize = "5";
       
   248                 else if (number <= 24)
       
   249                     m_applyFontSize = "6";
       
   250                 else
       
   251                     m_applyFontSize = "7";
       
   252             }
       
   253             // Huge quirk in Microsoft Entourage is that they understand CSS font-size, but also write 
       
   254             // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all, 
       
   255             // like Eudora). Yes, they write out *both*. We need to write out both as well.
       
   256         }
       
   257     }
       
   258 }
       
   259 
       
   260 static String& styleSpanClassString()
       
   261 {
       
   262     DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
       
   263     return styleSpanClassString;
       
   264 }
       
   265 
       
   266 bool isStyleSpan(const Node *node)
       
   267 {
       
   268     if (!node || !node->isHTMLElement())
       
   269         return false;
       
   270 
       
   271     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
       
   272     return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
       
   273 }
       
   274 
       
   275 static bool isUnstyledStyleSpan(const Node* node)
       
   276 {
       
   277     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
       
   278         return false;
       
   279 
       
   280     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
       
   281     CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
       
   282     return (!inlineStyleDecl || inlineStyleDecl->isEmpty()) && elem->getAttribute(classAttr) == styleSpanClassString();
       
   283 }
       
   284 
       
   285 static bool isSpanWithoutAttributesOrUnstyleStyleSpan(const Node* node)
       
   286 {
       
   287     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
       
   288         return false;
       
   289 
       
   290     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
       
   291     NamedNodeMap* attributes = elem->attributes(true); // readonly
       
   292     if (attributes->isEmpty())
       
   293         return true;
       
   294 
       
   295     return isUnstyledStyleSpan(node);
       
   296 }
       
   297 
       
   298 static bool isEmptyFontTag(const Node *node)
       
   299 {
       
   300     if (!node || !node->hasTagName(fontTag))
       
   301         return false;
       
   302 
       
   303     const Element *elem = static_cast<const Element *>(node);
       
   304     NamedNodeMap *map = elem->attributes(true); // true for read-only
       
   305     if (!map)
       
   306         return true;
       
   307     return map->isEmpty() || (map->length() == 1 && elem->getAttribute(classAttr) == styleSpanClassString());
       
   308 }
       
   309 
       
   310 static PassRefPtr<Element> createFontElement(Document* document)
       
   311 {
       
   312     RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
       
   313     fontNode->setAttribute(classAttr, styleSpanClassString());
       
   314     return fontNode.release();
       
   315 }
       
   316 
       
   317 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
       
   318 {
       
   319     RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
       
   320     styleElement->setAttribute(classAttr, styleSpanClassString());
       
   321     return styleElement.release();
       
   322 }
       
   323 
       
   324 static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration)
       
   325 {
       
   326     RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
       
   327     if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
       
   328         return;
       
   329 
       
   330     RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
       
   331     CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
       
   332 
       
   333     for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
       
   334         newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
       
   335 
       
   336     setTextDecorationProperty(style, newTextDecoration.get(), propertID);
       
   337 }
       
   338 
       
   339 static bool fontWeightIsBold(CSSStyleDeclaration* style)
       
   340 {
       
   341     ASSERT(style);
       
   342     RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
       
   343 
       
   344     if (!fontWeight)
       
   345         return false;
       
   346     if (!fontWeight->isPrimitiveValue())
       
   347         return false;
       
   348 
       
   349     // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
       
   350     // Collapse all other values to either one of these two states for editing purposes.
       
   351     switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) {
       
   352         case CSSValue100:
       
   353         case CSSValue200:
       
   354         case CSSValue300:
       
   355         case CSSValue400:
       
   356         case CSSValue500:
       
   357         case CSSValueNormal:
       
   358             return false;
       
   359         case CSSValueBold:
       
   360         case CSSValue600:
       
   361         case CSSValue700:
       
   362         case CSSValue800:
       
   363         case CSSValue900:
       
   364             return true;
       
   365     }
       
   366 
       
   367     ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
       
   368     return false; // Make compiler happy
       
   369 }
       
   370 
       
   371 RefPtr<CSSMutableStyleDeclaration> getPropertiesNotInComputedStyle(CSSStyleDeclaration* style, CSSComputedStyleDeclaration* computedStyle)
       
   372 {
       
   373     ASSERT(style);
       
   374     ASSERT(computedStyle);
       
   375     RefPtr<CSSMutableStyleDeclaration> result = style->copy();
       
   376     computedStyle->diff(result.get());
       
   377 
       
   378     RefPtr<CSSValue> computedTextDecorationsInEffect = computedStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
       
   379     diffTextDecorations(result.get(), CSSPropertyTextDecoration, computedTextDecorationsInEffect.get());
       
   380     diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, computedTextDecorationsInEffect.get());
       
   381 
       
   382     if (fontWeightIsBold(result.get()) == fontWeightIsBold(computedStyle))
       
   383         result->removeProperty(CSSPropertyFontWeight);
       
   384 
       
   385     return result;
       
   386 }
       
   387 
       
   388 // Editing style properties must be preserved during editing operation.
       
   389 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
       
   390 // FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties
       
   391 static const int editingStyleProperties[] = {
       
   392     // CSS inheritable properties
       
   393     CSSPropertyBorderCollapse,
       
   394     CSSPropertyColor,
       
   395     CSSPropertyFontFamily,
       
   396     CSSPropertyFontSize,
       
   397     CSSPropertyFontStyle,
       
   398     CSSPropertyFontVariant,
       
   399     CSSPropertyFontWeight,
       
   400     CSSPropertyLetterSpacing,
       
   401     CSSPropertyLineHeight,
       
   402     CSSPropertyOrphans,
       
   403     CSSPropertyTextAlign,
       
   404     CSSPropertyTextIndent,
       
   405     CSSPropertyTextTransform,
       
   406     CSSPropertyWhiteSpace,
       
   407     CSSPropertyWidows,
       
   408     CSSPropertyWordSpacing,
       
   409     CSSPropertyWebkitBorderHorizontalSpacing,
       
   410     CSSPropertyWebkitBorderVerticalSpacing,
       
   411     CSSPropertyWebkitTextDecorationsInEffect,
       
   412     CSSPropertyWebkitTextFillColor,
       
   413     CSSPropertyWebkitTextSizeAdjust,
       
   414     CSSPropertyWebkitTextStrokeColor,
       
   415     CSSPropertyWebkitTextStrokeWidth,
       
   416 };
       
   417 size_t numEditingStyleProperties = sizeof(editingStyleProperties)/sizeof(editingStyleProperties[0]);
       
   418 
       
   419 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::editingStyleAtPosition(Position pos, ShouldIncludeTypingStyle shouldIncludeTypingStyle)
       
   420 {
       
   421     RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = pos.computedStyle();
       
   422     RefPtr<CSSMutableStyleDeclaration> style;
       
   423     if (!computedStyleAtPosition)
       
   424         style = CSSMutableStyleDeclaration::create();
       
   425     else
       
   426         style = computedStyleAtPosition->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties);
       
   427 
       
   428     if (style && pos.node() && pos.node()->computedStyle()) {
       
   429         RenderStyle* renderStyle = pos.node()->computedStyle();
       
   430         // If a node's text fill color is invalid, then its children use 
       
   431         // their font-color as their text fill color (they don't
       
   432         // inherit it).  Likewise for stroke color.
       
   433         ExceptionCode ec = 0;
       
   434         if (!renderStyle->textFillColor().isValid())
       
   435             style->removeProperty(CSSPropertyWebkitTextFillColor, ec);
       
   436         if (!renderStyle->textStrokeColor().isValid())
       
   437             style->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
       
   438         ASSERT(ec == 0);
       
   439         if (renderStyle->fontDescription().keywordSize())
       
   440             style->setProperty(CSSPropertyFontSize, computedStyleAtPosition->getFontSizeCSSValuePreferringKeyword()->cssText());
       
   441     }
       
   442 
       
   443     if (shouldIncludeTypingStyle == IncludeTypingStyle) {
       
   444         CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->typingStyle();
       
   445         if (typingStyle)
       
   446             style->merge(typingStyle);
       
   447     }
       
   448 
       
   449     return style.release();
       
   450 }
       
   451 
       
   452 void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration* editingStyle, Position pos)
       
   453 {
       
   454     // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
       
   455     // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
       
   456     // which one of editingStyleAtPosition or computedStyle is called.
       
   457     RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(pos);
       
   458     style->diff(editingStyle);
       
   459 
       
   460     // if alpha value is zero, we don't add the background color.
       
   461     RefPtr<CSSValue> backgroundColor = editingStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
       
   462     if (backgroundColor && backgroundColor->isPrimitiveValue()) {
       
   463         CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(backgroundColor.get());
       
   464         Color color = Color(primitiveValue->getRGBA32Value());
       
   465         ExceptionCode ec;
       
   466         if (color.alpha() == 0)
       
   467             editingStyle->removeProperty(CSSPropertyBackgroundColor, ec);
       
   468     }
       
   469 }
       
   470 
       
   471 void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* node)
       
   472 {
       
   473     ASSERT(node);
       
   474     ASSERT(node->parentNode());
       
   475     RefPtr<CSSMutableStyleDeclaration> parentStyle = ApplyStyleCommand::editingStyleAtPosition(Position(node->parentNode(), 0));
       
   476     RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(Position(node, 0));
       
   477     parentStyle->diff(style.get());
       
   478     style->diff(editingStyle);
       
   479 }
       
   480     
       
   481 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, EditAction editingAction, EPropertyLevel propertyLevel)
       
   482     : CompositeEditCommand(document)
       
   483     , m_style(style->makeMutable())
       
   484     , m_editingAction(editingAction)
       
   485     , m_propertyLevel(propertyLevel)
       
   486     , m_start(endingSelection().start().downstream())
       
   487     , m_end(endingSelection().end().upstream())
       
   488     , m_useEndingSelection(true)
       
   489     , m_styledInlineElement(0)
       
   490     , m_removeOnly(false)
       
   491 {
       
   492 }
       
   493 
       
   494 ApplyStyleCommand::ApplyStyleCommand(Document* document, CSSStyleDeclaration* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
       
   495     : CompositeEditCommand(document)
       
   496     , m_style(style->makeMutable())
       
   497     , m_editingAction(editingAction)
       
   498     , m_propertyLevel(propertyLevel)
       
   499     , m_start(start)
       
   500     , m_end(end)
       
   501     , m_useEndingSelection(false)
       
   502     , m_styledInlineElement(0)
       
   503     , m_removeOnly(false)
       
   504 {
       
   505 }
       
   506 
       
   507 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
       
   508     : CompositeEditCommand(element->document())
       
   509     , m_style(CSSMutableStyleDeclaration::create())
       
   510     , m_editingAction(editingAction)
       
   511     , m_propertyLevel(PropertyDefault)
       
   512     , m_start(endingSelection().start().downstream())
       
   513     , m_end(endingSelection().end().upstream())
       
   514     , m_useEndingSelection(true)
       
   515     , m_styledInlineElement(element)
       
   516     , m_removeOnly(removeOnly)
       
   517 {
       
   518 }
       
   519 
       
   520 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
       
   521 {
       
   522     ASSERT(comparePositions(newEnd, newStart) >= 0);
       
   523 
       
   524     if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
       
   525         m_useEndingSelection = true;
       
   526 
       
   527     setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY));
       
   528     m_start = newStart;
       
   529     m_end = newEnd;
       
   530 }
       
   531 
       
   532 Position ApplyStyleCommand::startPosition()
       
   533 {
       
   534     if (m_useEndingSelection)
       
   535         return endingSelection().start();
       
   536     
       
   537     return m_start;
       
   538 }
       
   539 
       
   540 Position ApplyStyleCommand::endPosition()
       
   541 {
       
   542     if (m_useEndingSelection)
       
   543         return endingSelection().end();
       
   544     
       
   545     return m_end;
       
   546 }
       
   547 
       
   548 void ApplyStyleCommand::doApply()
       
   549 {
       
   550     switch (m_propertyLevel) {
       
   551         case PropertyDefault: {
       
   552             // apply the block-centric properties of the style
       
   553             RefPtr<CSSMutableStyleDeclaration> blockStyle = m_style->copyBlockProperties();
       
   554             if (blockStyle->length())
       
   555                 applyBlockStyle(blockStyle.get());
       
   556             // apply any remaining styles to the inline elements
       
   557             // NOTE: hopefully, this string comparison is the same as checking for a non-null diff
       
   558             if (blockStyle->length() < m_style->length() || m_styledInlineElement) {
       
   559                 RefPtr<CSSMutableStyleDeclaration> inlineStyle = m_style->copy();
       
   560                 applyRelativeFontStyleChange(inlineStyle.get());
       
   561                 blockStyle->diff(inlineStyle.get());
       
   562                 applyInlineStyle(inlineStyle.get());
       
   563             }
       
   564             break;
       
   565         }
       
   566         case ForceBlockProperties:
       
   567             // Force all properties to be applied as block styles.
       
   568             applyBlockStyle(m_style.get());
       
   569             break;
       
   570     }
       
   571 }
       
   572 
       
   573 EditAction ApplyStyleCommand::editingAction() const
       
   574 {
       
   575     return m_editingAction;
       
   576 }
       
   577 
       
   578 void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style)
       
   579 {
       
   580     // update document layout once before removing styles
       
   581     // so that we avoid the expense of updating before each and every call
       
   582     // to check a computed style
       
   583     updateLayout();
       
   584 
       
   585     // get positions we want to use for applying style
       
   586     Position start = startPosition();
       
   587     Position end = endPosition();
       
   588     if (comparePositions(end, start) < 0) {
       
   589         Position swap = start;
       
   590         start = end;
       
   591         end = swap;
       
   592     }
       
   593         
       
   594     VisiblePosition visibleStart(start);
       
   595     VisiblePosition visibleEnd(end);
       
   596     // Save and restore the selection endpoints using their indices in the document, since
       
   597     // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
       
   598     // Calculate start and end indices from the start of the tree that they're in.
       
   599     Node* scope = highestAncestor(visibleStart.deepEquivalent().node());
       
   600     Position rangeStart(scope, 0);
       
   601     RefPtr<Range> startRange = Range::create(document(), rangeStart, rangeCompliantEquivalent(visibleStart.deepEquivalent()));
       
   602     RefPtr<Range> endRange = Range::create(document(), rangeStart, rangeCompliantEquivalent(visibleEnd.deepEquivalent()));
       
   603     int startIndex = TextIterator::rangeLength(startRange.get(), true);
       
   604     int endIndex = TextIterator::rangeLength(endRange.get(), true);
       
   605     
       
   606     VisiblePosition paragraphStart(startOfParagraph(visibleStart));
       
   607     VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
       
   608     VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
       
   609     while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
       
   610         StyleChange styleChange(style, paragraphStart.deepEquivalent());
       
   611         if (styleChange.cssStyle().length() || m_removeOnly) {
       
   612             RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node());
       
   613             RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
       
   614             if (newBlock)
       
   615                 block = newBlock;
       
   616             ASSERT(block->isHTMLElement());
       
   617             if (block->isHTMLElement()) {
       
   618                 removeCSSStyle(style, static_cast<HTMLElement*>(block.get()));
       
   619                 if (!m_removeOnly)
       
   620                     addBlockStyle(styleChange, static_cast<HTMLElement*>(block.get()));
       
   621             }
       
   622 
       
   623             if (nextParagraphStart.isOrphan())
       
   624                 nextParagraphStart = endOfParagraph(paragraphStart).next();
       
   625         }
       
   626 
       
   627         paragraphStart = nextParagraphStart;
       
   628         nextParagraphStart = endOfParagraph(paragraphStart).next();
       
   629     }
       
   630     
       
   631     startRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), startIndex, 0, true);
       
   632     endRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), endIndex, 0, true);
       
   633     if (startRange && endRange)
       
   634         updateStartEnd(startRange->startPosition(), endRange->startPosition());
       
   635 }
       
   636 
       
   637 #define NoFontDelta (0.0f)
       
   638 #define MinimumFontSize (0.1f)
       
   639 
       
   640 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration *style)
       
   641 {
       
   642     RefPtr<CSSValue> value = style->getPropertyCSSValue(CSSPropertyFontSize);
       
   643     if (value) {
       
   644         // Explicit font size overrides any delta.
       
   645         style->removeProperty(CSSPropertyWebkitFontSizeDelta);
       
   646         return;
       
   647     }
       
   648 
       
   649     // Get the adjustment amount out of the style.
       
   650     value = style->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
       
   651     if (!value)
       
   652         return;
       
   653     float adjustment = NoFontDelta;
       
   654     if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
       
   655         CSSPrimitiveValue *primitiveValue = static_cast<CSSPrimitiveValue *>(value.get());
       
   656         if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
       
   657             // Only PX handled now. If we handle more types in the future, perhaps
       
   658             // a switch statement here would be more appropriate.
       
   659             adjustment = primitiveValue->getFloatValue();
       
   660         }
       
   661     }
       
   662     style->removeProperty(CSSPropertyWebkitFontSizeDelta);
       
   663     if (adjustment == NoFontDelta)
       
   664         return;
       
   665     
       
   666     Position start = startPosition();
       
   667     Position end = endPosition();
       
   668     if (comparePositions(end, start) < 0) {
       
   669         Position swap = start;
       
   670         start = end;
       
   671         end = swap;
       
   672     }
       
   673     
       
   674     // Join up any adjacent text nodes.
       
   675     if (start.node()->isTextNode()) {
       
   676         joinChildTextNodes(start.node()->parentNode(), start, end);
       
   677         start = startPosition();
       
   678         end = endPosition();
       
   679     }
       
   680     if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
       
   681         joinChildTextNodes(end.node()->parentNode(), start, end);
       
   682         start = startPosition();
       
   683         end = endPosition();
       
   684     }
       
   685 
       
   686     // Split the start text nodes if needed to apply style.
       
   687     bool splitStart = splitTextAtStartIfNeeded(start, end); 
       
   688     if (splitStart) {
       
   689         start = startPosition();
       
   690         end = endPosition();
       
   691     }
       
   692     bool splitEnd = splitTextAtEndIfNeeded(start, end);
       
   693     if (splitEnd) {
       
   694         start = startPosition();
       
   695         end = endPosition();
       
   696     }
       
   697 
       
   698     // Calculate loop end point.
       
   699     // If the end node is before the start node (can only happen if the end node is
       
   700     // an ancestor of the start node), we gather nodes up to the next sibling of the end node
       
   701     Node *beyondEnd;
       
   702     if (start.node()->isDescendantOf(end.node()))
       
   703         beyondEnd = end.node()->traverseNextSibling();
       
   704     else
       
   705         beyondEnd = end.node()->traverseNextNode();
       
   706     
       
   707     start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
       
   708     Node *startNode = start.node();
       
   709     if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
       
   710         startNode = startNode->traverseNextNode();
       
   711 
       
   712     // Store away font size before making any changes to the document.
       
   713     // This ensures that changes to one node won't effect another.
       
   714     HashMap<Node*, float> startingFontSizes;
       
   715     for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode())
       
   716         startingFontSizes.set(node, computedFontSize(node));
       
   717 
       
   718     // These spans were added by us. If empty after font size changes, they can be removed.
       
   719     Vector<RefPtr<HTMLElement> > unstyledSpans;
       
   720     
       
   721     Node* lastStyledNode = 0;
       
   722     for (Node* node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
       
   723         RefPtr<HTMLElement> element;
       
   724         if (node->isHTMLElement()) {
       
   725             // Only work on fully selected nodes.
       
   726             if (!nodeFullySelected(node, start, end))
       
   727                 continue;
       
   728             element = static_cast<HTMLElement*>(node);
       
   729         } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
       
   730             // Last styled node was not parent node of this text node, but we wish to style this
       
   731             // text node. To make this possible, add a style span to surround this text node.
       
   732             RefPtr<HTMLElement> span = createStyleSpanElement(document());
       
   733             surroundNodeRangeWithElement(node, node, span.get());
       
   734             element = span.release();
       
   735         }  else {
       
   736             // Only handle HTML elements and text nodes.
       
   737             continue;
       
   738         }
       
   739         lastStyledNode = node;
       
   740 
       
   741         CSSMutableStyleDeclaration* inlineStyleDecl = element->getInlineStyleDecl();
       
   742         float currentFontSize = computedFontSize(node);
       
   743         float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + adjustment);
       
   744         RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSSPropertyFontSize);
       
   745         if (value) {
       
   746             inlineStyleDecl->removeProperty(CSSPropertyFontSize, true);
       
   747             currentFontSize = computedFontSize(node);
       
   748         }
       
   749         if (currentFontSize != desiredFontSize) {
       
   750             inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false);
       
   751             setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText());
       
   752         }
       
   753         if (inlineStyleDecl->isEmpty()) {
       
   754             removeNodeAttribute(element.get(), styleAttr);
       
   755             // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
       
   756             if (isUnstyledStyleSpan(element.get()))
       
   757                 unstyledSpans.append(element.release());
       
   758         }
       
   759     }
       
   760 
       
   761     size_t size = unstyledSpans.size();
       
   762     for (size_t i = 0; i < size; ++i)
       
   763         removeNodePreservingChildren(unstyledSpans[i].get());
       
   764 }
       
   765 
       
   766 #undef NoFontDelta
       
   767 #undef MinimumFontSize
       
   768 
       
   769 static Node* dummySpanAncestorForNode(const Node* node)
       
   770 {
       
   771     while (node && !isStyleSpan(node))
       
   772         node = node->parent();
       
   773     
       
   774     return node ? node->parent() : 0;
       
   775 }
       
   776 
       
   777 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
       
   778 {
       
   779     if (!dummySpanAncestor)
       
   780         return;
       
   781 
       
   782     // Dummy spans are created when text node is split, so that style information
       
   783     // can be propagated, which can result in more splitting. If a dummy span gets
       
   784     // cloned/split, the new node is always a sibling of it. Therefore, we scan
       
   785     // all the children of the dummy's parent
       
   786     Node* next;
       
   787     for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
       
   788         next = node->nextSibling();
       
   789         if (isUnstyledStyleSpan(node))
       
   790             removeNodePreservingChildren(node);
       
   791         node = next;
       
   792     }
       
   793 }
       
   794 
       
   795 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, RefPtr<CSSPrimitiveValue> allowedDirection)
       
   796 {
       
   797     // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
       
   798     // In that case, we return the unsplit ancestor. Otherwise, we return 0.
       
   799     Node* block = enclosingBlock(node);
       
   800     if (!block)
       
   801         return 0;
       
   802 
       
   803     Node* highestAncestorWithUnicodeBidi = 0;
       
   804     Node* nextHighestAncestorWithUnicodeBidi = 0;
       
   805     RefPtr<CSSPrimitiveValue> highestAncestorUnicodeBidi;
       
   806     for (Node* n = node->parent(); n != block; n = n->parent()) {
       
   807         RefPtr<CSSValue> unicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
       
   808         if (unicodeBidi) {
       
   809             ASSERT(unicodeBidi->isPrimitiveValue());
       
   810             if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
       
   811                 highestAncestorUnicodeBidi = static_cast<CSSPrimitiveValue*>(unicodeBidi.get());
       
   812                 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
       
   813                 highestAncestorWithUnicodeBidi = n;
       
   814             }
       
   815         }
       
   816     }
       
   817 
       
   818     if (!highestAncestorWithUnicodeBidi)
       
   819         return 0;
       
   820 
       
   821     HTMLElement* unsplitAncestor = 0;
       
   822 
       
   823     if (allowedDirection && highestAncestorUnicodeBidi->getIdent() != CSSValueBidiOverride) {
       
   824         RefPtr<CSSValue> highestAncestorDirection = computedStyle(highestAncestorWithUnicodeBidi)->getPropertyCSSValue(CSSPropertyDirection);
       
   825         ASSERT(highestAncestorDirection->isPrimitiveValue());
       
   826         if (static_cast<CSSPrimitiveValue*>(highestAncestorDirection.get())->getIdent() == allowedDirection->getIdent() && highestAncestorWithUnicodeBidi->isHTMLElement()) {
       
   827             if (!nextHighestAncestorWithUnicodeBidi)
       
   828                 return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
       
   829 
       
   830             unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
       
   831             highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
       
   832         }
       
   833     }
       
   834 
       
   835     // Split every ancestor through highest ancestor with embedding.
       
   836     Node* n = node;
       
   837     while (true) {
       
   838         Element* parent = static_cast<Element*>(n->parent());
       
   839         if (before ? n->previousSibling() : n->nextSibling())
       
   840             splitElement(parent, before ? n : n->nextSibling());
       
   841         if (parent == highestAncestorWithUnicodeBidi)
       
   842             break;
       
   843         n = n->parent();
       
   844     }
       
   845     return unsplitAncestor;
       
   846 }
       
   847 
       
   848 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
       
   849 {
       
   850     Node* block = enclosingBlock(node);
       
   851     if (!block)
       
   852         return;
       
   853 
       
   854     Node* n = node->parent();
       
   855     while (n != block && n != unsplitAncestor) {
       
   856         Node* parent = n->parent();
       
   857         if (!n->isStyledElement()) {
       
   858             n = parent;
       
   859             continue;
       
   860         }
       
   861 
       
   862         StyledElement* element = static_cast<StyledElement*>(n);
       
   863         RefPtr<CSSValue> unicodeBidi = computedStyle(element)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
       
   864         if (unicodeBidi) {
       
   865             ASSERT(unicodeBidi->isPrimitiveValue());
       
   866             if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
       
   867                 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
       
   868                 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
       
   869                 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
       
   870                 // otherwise it sets the property in the inline style declaration.
       
   871                 if (element->hasAttribute(dirAttr)) {
       
   872                     // FIXME: If this is a BDO element, we should probably just remove it if it has no
       
   873                     // other attributes, like we (should) do with B and I elements.
       
   874                     removeNodeAttribute(element, dirAttr);
       
   875                 } else {
       
   876                     RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
       
   877                     inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
       
   878                     inlineStyle->removeProperty(CSSPropertyDirection);
       
   879                     setNodeAttribute(element, styleAttr, inlineStyle->cssText());
       
   880                     // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
       
   881                     if (isUnstyledStyleSpan(element))
       
   882                         removeNodePreservingChildren(element);
       
   883                 }
       
   884             }
       
   885         }
       
   886         n = parent;
       
   887     }
       
   888 }
       
   889 
       
   890 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
       
   891 {
       
   892     Node* startDummySpanAncestor = 0;
       
   893     Node* endDummySpanAncestor = 0;
       
   894     
       
   895     // update document layout once before removing styles
       
   896     // so that we avoid the expense of updating before each and every call
       
   897     // to check a computed style
       
   898     updateLayout();
       
   899 
       
   900     // adjust to the positions we want to use for applying style
       
   901     Position start = startPosition();
       
   902     Position end = endPosition();
       
   903     if (comparePositions(end, start) < 0) {
       
   904         Position swap = start;
       
   905         start = end;
       
   906         end = swap;
       
   907     }
       
   908 
       
   909     // split the start node and containing element if the selection starts inside of it
       
   910     bool splitStart = splitTextElementAtStartIfNeeded(start, end); 
       
   911     if (splitStart) {
       
   912         start = startPosition();
       
   913         end = endPosition();
       
   914         startDummySpanAncestor = dummySpanAncestorForNode(start.node());
       
   915     }
       
   916 
       
   917     // split the end node and containing element if the selection ends inside of it
       
   918     bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
       
   919     if (splitEnd) {
       
   920         start = startPosition();
       
   921         end = endPosition();
       
   922         endDummySpanAncestor = dummySpanAncestorForNode(end.node());
       
   923     }
       
   924 
       
   925     RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
       
   926     RefPtr<CSSValue> direction;
       
   927     HTMLElement* startUnsplitAncestor = 0;
       
   928     HTMLElement* endUnsplitAncestor = 0;
       
   929     if (unicodeBidi) {
       
   930         RefPtr<CSSPrimitiveValue> allowedDirection;
       
   931         ASSERT(unicodeBidi->isPrimitiveValue());
       
   932         if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() == CSSValueEmbed) {
       
   933             // Leave alone an ancestor that provides the desired single level embedding, if there is one.
       
   934             direction = style->getPropertyCSSValue(CSSPropertyDirection);
       
   935             ASSERT(direction->isPrimitiveValue());
       
   936             allowedDirection = static_cast<CSSPrimitiveValue*>(direction.get());
       
   937         }
       
   938         startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, allowedDirection);
       
   939         endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, allowedDirection);
       
   940         removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor);
       
   941         removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor);
       
   942     }
       
   943 
       
   944     // Remove style from the selection.
       
   945     // Use the upstream position of the start for removing style.
       
   946     // This will ensure we remove all traces of the relevant styles from the selection
       
   947     // and prevent us from adding redundant ones, as described in:
       
   948     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
       
   949     Position removeStart = start.upstream();
       
   950     Position embeddingRemoveStart = removeStart;
       
   951     Position embeddingRemoveEnd = end;
       
   952     if (unicodeBidi) {
       
   953         // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
       
   954         if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
       
   955             embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
       
   956         if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
       
   957             embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
       
   958     }
       
   959 
       
   960     if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) {
       
   961         RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
       
   962         embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
       
   963         embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
       
   964         if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
       
   965             removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
       
   966 
       
   967         RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
       
   968         styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
       
   969         styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
       
   970         removeInlineStyle(styleWithoutEmbedding, removeStart, end);
       
   971     } else
       
   972         removeInlineStyle(style, removeStart, end);
       
   973 
       
   974     start = startPosition();
       
   975     end = endPosition();
       
   976 
       
   977     if (splitStart) {
       
   978         bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
       
   979         if (mergedStart) {
       
   980             start = startPosition();
       
   981             end = endPosition();
       
   982         }
       
   983     }
       
   984 
       
   985     if (splitEnd) {
       
   986         mergeEndWithNextIfIdentical(start, end);
       
   987         start = startPosition();
       
   988         end = endPosition();
       
   989     }
       
   990 
       
   991     // update document layout once before running the rest of the function
       
   992     // so that we avoid the expense of updating before each and every call
       
   993     // to check a computed style
       
   994     updateLayout();
       
   995 
       
   996     Position embeddingApplyStart = start;
       
   997     Position embeddingApplyEnd = end;
       
   998     if (unicodeBidi) {
       
   999         // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
       
  1000         Node* startEnclosingBlock = enclosingBlock(start.node());
       
  1001         for (Node* n = start.node(); n != startEnclosingBlock; n = n->parent()) {
       
  1002             if (n->isHTMLElement()) {
       
  1003                 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
       
  1004                 if (ancestorUnicodeBidi) {
       
  1005                     ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
       
  1006                     if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
       
  1007                         embeddingApplyStart = positionInParentAfterNode(n);
       
  1008                         break;
       
  1009                     }
       
  1010                 }
       
  1011             }
       
  1012         }
       
  1013 
       
  1014         Node* endEnclosingBlock = enclosingBlock(end.node());
       
  1015         for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) {
       
  1016             if (n->isHTMLElement()) {
       
  1017                 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
       
  1018                 if (ancestorUnicodeBidi) {
       
  1019                     ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
       
  1020                     if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
       
  1021                         embeddingApplyEnd = positionInParentBeforeNode(n);
       
  1022                         break;
       
  1023                     }
       
  1024                 }
       
  1025             }
       
  1026         }
       
  1027     }
       
  1028 
       
  1029     if (embeddingApplyStart != start || embeddingApplyEnd != end) {
       
  1030         if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) {
       
  1031             RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
       
  1032             embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
       
  1033             embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
       
  1034             applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
       
  1035         }
       
  1036 
       
  1037         RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
       
  1038         styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
       
  1039         styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
       
  1040         applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end);
       
  1041     } else
       
  1042         applyInlineStyleToRange(style, start, end);
       
  1043 
       
  1044     // Remove dummy style spans created by splitting text elements.
       
  1045     cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
       
  1046     if (endDummySpanAncestor != startDummySpanAncestor)
       
  1047         cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
       
  1048 }
       
  1049 
       
  1050 void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* style, const Position& start, const Position& rangeEnd)
       
  1051 {
       
  1052     Node* node = start.node();
       
  1053     Position end = rangeEnd;
       
  1054 
       
  1055     bool rangeIsEmpty = false;
       
  1056 
       
  1057     if (start.deprecatedEditingOffset() >= caretMaxOffset(start.node())) {
       
  1058         node = node->traverseNextNode();
       
  1059         Position newStart = Position(node, 0);
       
  1060         if (!node || comparePositions(end, newStart) < 0)
       
  1061             rangeIsEmpty = true;
       
  1062     }
       
  1063 
       
  1064     if (!rangeIsEmpty) {
       
  1065         // pastEndNode is the node after the last fully selected node.
       
  1066         Node* pastEndNode = end.node();
       
  1067         if (end.deprecatedEditingOffset() >= caretMaxOffset(end.node()))
       
  1068             pastEndNode = end.node()->traverseNextSibling();
       
  1069         // FIXME: Callers should perform this operation on a Range that includes the br
       
  1070         // if they want style applied to the empty line.
       
  1071         if (start == end && start.node()->hasTagName(brTag))
       
  1072             pastEndNode = start.node()->traverseNextNode();
       
  1073         // Add the style to selected inline runs.
       
  1074         for (Node* next; node && node != pastEndNode; node = next) {
       
  1075             
       
  1076             next = node->traverseNextNode();
       
  1077             
       
  1078             if (!node->renderer() || !node->isContentEditable())
       
  1079                 continue;
       
  1080             
       
  1081             if (!node->isContentRichlyEditable() && node->isHTMLElement()) {
       
  1082                 // This is a plaintext-only region. Only proceed if it's fully selected.
       
  1083                 // pastEndNode is the node after the last fully selected node, so if it's inside node then
       
  1084                 // node isn't fully selected.
       
  1085                 if (pastEndNode && pastEndNode->isDescendantOf(node))
       
  1086                     break;
       
  1087                 // Add to this element's inline style and skip over its contents.
       
  1088                 HTMLElement* element = static_cast<HTMLElement*>(node);
       
  1089                 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
       
  1090                 inlineStyle->merge(style);
       
  1091                 setNodeAttribute(element, styleAttr, inlineStyle->cssText());
       
  1092                 next = node->traverseNextSibling();
       
  1093                 continue;
       
  1094             }
       
  1095         
       
  1096             if (isBlock(node))
       
  1097                 continue;
       
  1098                 
       
  1099             if (node->childNodeCount()) {
       
  1100                 if (editingIgnoresContent(node)) {
       
  1101                     next = node->traverseNextSibling();
       
  1102                     continue;
       
  1103                 }
       
  1104                 continue;
       
  1105             }
       
  1106             
       
  1107             Node* runStart = node;
       
  1108             // Find the end of the run.
       
  1109             Node* sibling = node->nextSibling();
       
  1110             while (sibling && sibling != pastEndNode && (!sibling->isElementNode() || sibling->hasTagName(brTag)) && !isBlock(sibling)) {
       
  1111                 node = sibling;
       
  1112                 sibling = node->nextSibling();
       
  1113             }
       
  1114             // Recompute next, since node has changed.
       
  1115             next = node->traverseNextNode();
       
  1116             // Apply the style to the run.
       
  1117             addInlineStyleIfNeeded(style, runStart, node);
       
  1118         }
       
  1119     }
       
  1120 }
       
  1121 
       
  1122 bool ApplyStyleCommand::shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const
       
  1123 {
       
  1124     // Honor text-decorations-in-effect
       
  1125     RefPtr<CSSValue> textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
       
  1126     if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
       
  1127         textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyTextDecoration);
       
  1128 
       
  1129     // When there is no text decorations to apply, remove any one of u, s, & strike
       
  1130     if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
       
  1131         return true;
       
  1132 
       
  1133     // Remove node if it implicitly adds style not present in styleToApply
       
  1134     CSSValueList* valueList = static_cast<CSSValueList*>(textDecorationsToApply.get());
       
  1135     RefPtr<CSSPrimitiveValue> value = CSSPrimitiveValue::createIdentifier(textDecorationAddedByTag);
       
  1136     return !valueList->hasValue(value.get());
       
  1137 }
       
  1138 
       
  1139 // This function maps from styling tags to CSS styles.  Used for knowing which
       
  1140 // styling tags should be removed when toggling styles.
       
  1141 bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style)
       
  1142 {
       
  1143     CSSMutableStyleDeclaration::const_iterator end = style->end();
       
  1144     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
       
  1145         const CSSProperty& property = *it;
       
  1146         // FIXME: This should probably be re-written to lookup the tagname in a
       
  1147         // hash and match against an expected property/value pair.
       
  1148         switch (property.id()) {
       
  1149         case CSSPropertyFontWeight:
       
  1150             // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational
       
  1151             if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag))
       
  1152                 return !equalIgnoringCase(property.value()->cssText(), "bold") || !elem->hasChildNodes();
       
  1153             break;
       
  1154         case CSSPropertyVerticalAlign:
       
  1155             if (elem->hasLocalName(subTag))
       
  1156                 return !equalIgnoringCase(property.value()->cssText(), "sub") || !elem->hasChildNodes();
       
  1157             if (elem->hasLocalName(supTag))
       
  1158                 return !equalIgnoringCase(property.value()->cssText(), "sup") || !elem->hasChildNodes();
       
  1159             break;
       
  1160         case CSSPropertyFontStyle:
       
  1161             // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational
       
  1162             if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag))
       
  1163                 return !equalIgnoringCase(property.value()->cssText(), "italic") || !elem->hasChildNodes();
       
  1164             break;
       
  1165         case CSSPropertyTextDecoration:
       
  1166         case CSSPropertyWebkitTextDecorationsInEffect:
       
  1167                 if (elem->hasLocalName(uTag))
       
  1168                     return shouldRemoveTextDecorationTag(style, CSSValueUnderline) || !elem->hasChildNodes();
       
  1169                 else if (elem->hasLocalName(sTag) || elem->hasTagName(strikeTag))
       
  1170                     return shouldRemoveTextDecorationTag(style,CSSValueLineThrough) || !elem->hasChildNodes();
       
  1171         }
       
  1172     }
       
  1173     return false;
       
  1174 }
       
  1175 
       
  1176 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
       
  1177 {
       
  1178     bool removeNode = false;
       
  1179 
       
  1180     // Similar to isSpanWithoutAttributesOrUnstyleStyleSpan, but does not look for Apple-style-span.
       
  1181     NamedNodeMap* attributes = elem->attributes(true); // readonly
       
  1182     if (!attributes || attributes->isEmpty())
       
  1183         removeNode = true;
       
  1184     else if (attributes->length() == 1 && elem->hasAttribute(styleAttr)) {
       
  1185         // Remove the element even if it has just style='' (this might be redundantly checked later too)
       
  1186         CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
       
  1187         if (!inlineStyleDecl || inlineStyleDecl->isEmpty())
       
  1188             removeNode = true;
       
  1189     }
       
  1190 
       
  1191     if (removeNode)
       
  1192         removeNodePreservingChildren(elem);
       
  1193     else {
       
  1194         HTMLElement* newSpanElement = replaceNodeWithSpanPreservingChildrenAndAttributes(elem);
       
  1195         ASSERT(newSpanElement && newSpanElement->inDocument());
       
  1196         elem = newSpanElement;
       
  1197     }
       
  1198 }
       
  1199 
       
  1200 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
       
  1201 {
       
  1202     ASSERT(style);
       
  1203     ASSERT(elem);
       
  1204 
       
  1205     if (!elem->hasLocalName(fontTag))
       
  1206         return;
       
  1207         
       
  1208     CSSMutableStyleDeclaration::const_iterator end = style->end();
       
  1209     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
       
  1210         switch ((*it).id()) {
       
  1211             case CSSPropertyColor:
       
  1212                 removeNodeAttribute(elem, colorAttr);
       
  1213                 break;
       
  1214             case CSSPropertyFontFamily:
       
  1215                 removeNodeAttribute(elem, faceAttr);
       
  1216                 break;
       
  1217             case CSSPropertyFontSize:
       
  1218                 removeNodeAttribute(elem, sizeAttr);
       
  1219                 break;
       
  1220         }
       
  1221     }
       
  1222 
       
  1223     if (isEmptyFontTag(elem))
       
  1224         removeNodePreservingChildren(elem);
       
  1225 }
       
  1226 
       
  1227 void ApplyStyleCommand::removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
       
  1228 {
       
  1229     ASSERT(style);
       
  1230     ASSERT(elem);
       
  1231 
       
  1232     if (!elem->hasAttribute(dirAttr))
       
  1233         return;
       
  1234 
       
  1235     if (!style->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection))
       
  1236         return;
       
  1237 
       
  1238     removeNodeAttribute(elem, dirAttr);
       
  1239 
       
  1240     // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
       
  1241     if (isUnstyledStyleSpan(elem))
       
  1242         removeNodePreservingChildren(elem);
       
  1243 }
       
  1244 
       
  1245 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem)
       
  1246 {
       
  1247     ASSERT(style);
       
  1248     ASSERT(elem);
       
  1249 
       
  1250     CSSMutableStyleDeclaration* decl = elem->inlineStyleDecl();
       
  1251     if (!decl)
       
  1252         return;
       
  1253 
       
  1254     CSSMutableStyleDeclaration::const_iterator end = style->end();
       
  1255     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
       
  1256         CSSPropertyID propertyID = static_cast<CSSPropertyID>((*it).id());
       
  1257         RefPtr<CSSValue> value = decl->getPropertyCSSValue(propertyID);
       
  1258         if (value && (propertyID != CSSPropertyWhiteSpace || !isTabSpanNode(elem))) {
       
  1259             removeCSSProperty(decl, propertyID);
       
  1260             if (propertyID == CSSPropertyUnicodeBidi && !decl->getPropertyValue(CSSPropertyDirection).isEmpty())
       
  1261                 removeCSSProperty(decl, CSSPropertyDirection);
       
  1262         }
       
  1263     }
       
  1264 
       
  1265     // No need to serialize <foo style=""> if we just removed the last css property
       
  1266     if (decl->isEmpty())
       
  1267         removeNodeAttribute(elem, styleAttr);
       
  1268 
       
  1269     if (isSpanWithoutAttributesOrUnstyleStyleSpan(elem))
       
  1270         removeNodePreservingChildren(elem);
       
  1271 }
       
  1272 
       
  1273 static bool hasTextDecorationProperty(Node *node)
       
  1274 {
       
  1275     if (!node->isElementNode())
       
  1276         return false;
       
  1277 
       
  1278     RefPtr<CSSValue> value = computedStyle(node)->getPropertyCSSValue(CSSPropertyTextDecoration, DoNotUpdateLayout);
       
  1279     return value && !equalIgnoringCase(value->cssText(), "none");
       
  1280 }
       
  1281 
       
  1282 static Node* highestAncestorWithTextDecoration(Node *node)
       
  1283 {
       
  1284     ASSERT(node);
       
  1285     Node* result = 0;
       
  1286     Node* unsplittableElement = unsplittableElementForPosition(Position(node, 0));
       
  1287 
       
  1288     for (Node *n = node; n; n = n->parentNode()) {
       
  1289         if (hasTextDecorationProperty(n))
       
  1290             result = n;
       
  1291         // Should stop at the editable root (cannot cross editing boundary) and
       
  1292         // also stop at the unsplittable element to be consistent with other UAs
       
  1293         if (n == unsplittableElement)
       
  1294             break;
       
  1295     }
       
  1296 
       
  1297     return result;
       
  1298 }
       
  1299 
       
  1300 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractTextDecorationStyle(Node* node)
       
  1301 {
       
  1302     ASSERT(node);
       
  1303     ASSERT(node->isElementNode());
       
  1304     
       
  1305     // non-html elements not handled yet
       
  1306     if (!node->isHTMLElement())
       
  1307         return 0;
       
  1308 
       
  1309     HTMLElement *element = static_cast<HTMLElement *>(node);
       
  1310     RefPtr<CSSMutableStyleDeclaration> style = element->inlineStyleDecl();
       
  1311     if (!style)
       
  1312         return 0;
       
  1313 
       
  1314     int properties[1] = { CSSPropertyTextDecoration };
       
  1315     RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = style->copyPropertiesInSet(properties, 1);
       
  1316 
       
  1317     RefPtr<CSSValue> property = style->getPropertyCSSValue(CSSPropertyTextDecoration);
       
  1318     if (property && !equalIgnoringCase(property->cssText(), "none"))
       
  1319         removeCSSProperty(style.get(), CSSPropertyTextDecoration);
       
  1320 
       
  1321     return textDecorationStyle.release();
       
  1322 }
       
  1323 
       
  1324 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node* node)
       
  1325 {
       
  1326     ASSERT(node);
       
  1327     ASSERT(node->isElementNode());
       
  1328     
       
  1329     // non-html elements not handled yet
       
  1330     if (!node->isHTMLElement())
       
  1331         return 0;
       
  1332 
       
  1333     RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
       
  1334     ASSERT(nodeStyle);
       
  1335 
       
  1336     int properties[1] = { CSSPropertyTextDecoration };
       
  1337     RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = nodeStyle->copyPropertiesInSet(properties, 1);
       
  1338 
       
  1339     RefPtr<CSSValue> property = nodeStyle->getPropertyCSSValue(CSSPropertyTextDecoration);
       
  1340     if (property && !equalIgnoringCase(property->cssText(), "none")) {
       
  1341         RefPtr<CSSMutableStyleDeclaration> newStyle = textDecorationStyle->copy();
       
  1342         newStyle->setProperty(CSSPropertyTextDecoration, "none");
       
  1343         applyTextDecorationStyle(node, newStyle.get());
       
  1344     }
       
  1345 
       
  1346     return textDecorationStyle.release();
       
  1347 }
       
  1348 
       
  1349 void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDeclaration *style)
       
  1350 {
       
  1351     ASSERT(node);
       
  1352 
       
  1353     if (!style || style->cssText().isEmpty())
       
  1354         return;
       
  1355 
       
  1356     StyleChange styleChange(style, Position(node, 0));
       
  1357     if (styleChange.cssStyle().length()) {
       
  1358         if (node->isTextNode()) {
       
  1359             RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document());
       
  1360             surroundNodeRangeWithElement(node, node, styleSpan.get());
       
  1361             node = styleSpan.get();
       
  1362         }
       
  1363 
       
  1364         if (!node->isElementNode())
       
  1365             return;
       
  1366 
       
  1367         HTMLElement *element = static_cast<HTMLElement *>(node);
       
  1368         String cssText = styleChange.cssStyle();
       
  1369         CSSMutableStyleDeclaration *decl = element->inlineStyleDecl();
       
  1370         if (decl)
       
  1371             cssText += decl->cssText();
       
  1372         setNodeAttribute(element, styleAttr, cssText);
       
  1373     }
       
  1374 
       
  1375     if (styleChange.applyUnderline())
       
  1376         surroundNodeRangeWithElement(node, node, createHTMLElement(document(), uTag));
       
  1377 
       
  1378     if (styleChange.applyLineThrough())
       
  1379         surroundNodeRangeWithElement(node, node, createHTMLElement(document(), sTag));    
       
  1380 }
       
  1381 
       
  1382 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, bool forceNegate)
       
  1383 {
       
  1384     ASSERT(targetNode);
       
  1385     Node* highestAncestor = highestAncestorWithTextDecoration(targetNode);
       
  1386     if (!highestAncestor)
       
  1387         return;
       
  1388 
       
  1389     // The outer loop is traversing the tree vertically from highestAncestor to targetNode
       
  1390     Node* current = highestAncestor;
       
  1391     while (current != targetNode) {
       
  1392         ASSERT(current);
       
  1393         ASSERT(current->contains(targetNode));
       
  1394         RefPtr<CSSMutableStyleDeclaration> decoration = forceNegate ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
       
  1395 
       
  1396         // The inner loop will go through children on each level
       
  1397         Node* child = current->firstChild();
       
  1398         while (child) {
       
  1399             Node* nextChild = child->nextSibling();
       
  1400 
       
  1401             // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode
       
  1402             if (child != targetNode)
       
  1403                 applyTextDecorationStyle(child, decoration.get());
       
  1404             
       
  1405             // We found the next node for the outer loop (contains targetNode)
       
  1406             // When reached targetNode, stop the outer loop upon the completion of the current inner loop
       
  1407             if (child == targetNode || child->contains(targetNode))
       
  1408                 current = child;
       
  1409 
       
  1410             child = nextChild;
       
  1411         }
       
  1412     }
       
  1413 }
       
  1414 
       
  1415 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
       
  1416 {
       
  1417     // We need to work in two passes. First we push down any inline
       
  1418     // styles that set text decoration. Then we look for any remaining
       
  1419     // styles (caused by stylesheets) and explicitly negate text
       
  1420     // decoration while pushing down.
       
  1421 
       
  1422     pushDownTextDecorationStyleAroundNode(start.node(), false);
       
  1423     updateLayout();
       
  1424     pushDownTextDecorationStyleAroundNode(start.node(), true);
       
  1425 
       
  1426     pushDownTextDecorationStyleAroundNode(end.node(), false);
       
  1427     updateLayout();
       
  1428     pushDownTextDecorationStyleAroundNode(end.node(), true);
       
  1429 }
       
  1430 
       
  1431 // FIXME: Why does this exist?  Callers should either use lastOffsetForEditing or lastOffsetInNode
       
  1432 static int maxRangeOffset(Node *n)
       
  1433 {
       
  1434     if (n->offsetInCharacters())
       
  1435         return n->maxCharacterOffset();
       
  1436 
       
  1437     if (n->isElementNode())
       
  1438         return n->childNodeCount();
       
  1439 
       
  1440     return 1;
       
  1441 }
       
  1442 
       
  1443 void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style, const Position &start, const Position &end)
       
  1444 {
       
  1445     ASSERT(start.isNotNull());
       
  1446     ASSERT(end.isNotNull());
       
  1447     ASSERT(start.node()->inDocument());
       
  1448     ASSERT(end.node()->inDocument());
       
  1449     ASSERT(comparePositions(start, end) <= 0);
       
  1450     
       
  1451     RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
       
  1452 
       
  1453     if (textDecorationSpecialProperty) {
       
  1454         pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
       
  1455         style = style->copy();
       
  1456         style->setProperty(CSSPropertyTextDecoration, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect));
       
  1457     }
       
  1458 
       
  1459     // The s and e variables store the positions used to set the ending selection after style removal
       
  1460     // takes place. This will help callers to recognize when either the start node or the end node
       
  1461     // are removed from the document during the work of this function.
       
  1462     Position s = start;
       
  1463     Position e = end;
       
  1464 
       
  1465     Node* node = start.node();
       
  1466     while (node) {
       
  1467         Node* next = node->traverseNextNode();
       
  1468         if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
       
  1469             HTMLElement* elem = static_cast<HTMLElement*>(node);
       
  1470             Node* prev = elem->traversePreviousNodePostOrder();
       
  1471             Node* next = elem->traverseNextNode();
       
  1472             if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName()))
       
  1473                 removeNodePreservingChildren(elem);
       
  1474 
       
  1475             if (implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(elem, style.get()))
       
  1476                 replaceWithSpanOrRemoveIfWithoutAttributes(elem);
       
  1477 
       
  1478             // If the node was converted to a span, the span may still contain relevant
       
  1479             // styles which must be removed (e.g. <b style='font-weight: bold'>)
       
  1480             if (elem->inDocument()) {
       
  1481                 removeHTMLFontStyle(style.get(), elem);
       
  1482                 removeHTMLBidiEmbeddingStyle(style.get(), elem);
       
  1483                 removeCSSStyle(style.get(), elem);
       
  1484             }
       
  1485             if (!elem->inDocument()) {
       
  1486                 if (s.node() == elem) {
       
  1487                     // Since elem must have been fully selected, and it is at the start
       
  1488                     // of the selection, it is clear we can set the new s offset to 0.
       
  1489                     ASSERT(s.deprecatedEditingOffset() <= caretMinOffset(s.node()));
       
  1490                     s = Position(next, 0);
       
  1491                 }
       
  1492                 if (e.node() == elem) {
       
  1493                     // Since elem must have been fully selected, and it is at the end
       
  1494                     // of the selection, it is clear we can set the new e offset to
       
  1495                     // the max range offset of prev.
       
  1496                     ASSERT(e.deprecatedEditingOffset() >= maxRangeOffset(e.node()));
       
  1497                     e = Position(prev, maxRangeOffset(prev));
       
  1498                 }
       
  1499             }
       
  1500         }
       
  1501         if (node == end.node())
       
  1502             break;
       
  1503         node = next;
       
  1504     }
       
  1505     
       
  1506     ASSERT(s.node()->inDocument());
       
  1507     ASSERT(e.node()->inDocument());
       
  1508     updateStartEnd(s, e);
       
  1509 }
       
  1510 
       
  1511 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
       
  1512 {
       
  1513     ASSERT(node);
       
  1514     ASSERT(node->isElementNode());
       
  1515 
       
  1516     Position pos = Position(node, node->childNodeCount()).upstream();
       
  1517     return comparePositions(Position(node, 0), start) >= 0 && comparePositions(pos, end) <= 0;
       
  1518 }
       
  1519 
       
  1520 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
       
  1521 {
       
  1522     ASSERT(node);
       
  1523     ASSERT(node->isElementNode());
       
  1524 
       
  1525     Position pos = Position(node, node->childNodeCount()).upstream();
       
  1526     bool isFullyBeforeStart = comparePositions(pos, start) < 0;
       
  1527     bool isFullyAfterEnd = comparePositions(Position(node, 0), end) > 0;
       
  1528 
       
  1529     return isFullyBeforeStart || isFullyAfterEnd;
       
  1530 }
       
  1531 
       
  1532 
       
  1533 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
       
  1534 {
       
  1535     if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
       
  1536         int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
       
  1537         Text *text = static_cast<Text *>(start.node());
       
  1538         splitTextNode(text, start.deprecatedEditingOffset());
       
  1539         updateStartEnd(Position(start.node(), 0), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
       
  1540         return true;
       
  1541     }
       
  1542     return false;
       
  1543 }
       
  1544 
       
  1545 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
       
  1546 {
       
  1547     if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
       
  1548         Text *text = static_cast<Text *>(end.node());
       
  1549         splitTextNode(text, end.deprecatedEditingOffset());
       
  1550         
       
  1551         Node *prevNode = text->previousSibling();
       
  1552         ASSERT(prevNode);
       
  1553         Node *startNode = start.node() == end.node() ? prevNode : start.node();
       
  1554         ASSERT(startNode);
       
  1555         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode, caretMaxOffset(prevNode)));
       
  1556         return true;
       
  1557     }
       
  1558     return false;
       
  1559 }
       
  1560 
       
  1561 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
       
  1562 {
       
  1563     if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
       
  1564         int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
       
  1565         Text *text = static_cast<Text *>(start.node());
       
  1566         splitTextNodeContainingElement(text, start.deprecatedEditingOffset());
       
  1567 
       
  1568         updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
       
  1569         return true;
       
  1570     }
       
  1571     return false;
       
  1572 }
       
  1573 
       
  1574 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
       
  1575 {
       
  1576     if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
       
  1577         Text *text = static_cast<Text *>(end.node());
       
  1578         splitTextNodeContainingElement(text, end.deprecatedEditingOffset());
       
  1579 
       
  1580         Node *prevNode = text->parent()->previousSibling()->lastChild();
       
  1581         ASSERT(prevNode);
       
  1582         Node *startNode = start.node() == end.node() ? prevNode : start.node();
       
  1583         ASSERT(startNode);
       
  1584         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1));
       
  1585         return true;
       
  1586     }
       
  1587     return false;
       
  1588 }
       
  1589 
       
  1590 static bool areIdenticalElements(Node *first, Node *second)
       
  1591 {
       
  1592     // check that tag name and all attribute names and values are identical
       
  1593 
       
  1594     if (!first->isElementNode())
       
  1595         return false;
       
  1596     
       
  1597     if (!second->isElementNode())
       
  1598         return false;
       
  1599 
       
  1600     Element *firstElement = static_cast<Element *>(first);
       
  1601     Element *secondElement = static_cast<Element *>(second);
       
  1602     
       
  1603     if (!firstElement->tagQName().matches(secondElement->tagQName()))
       
  1604         return false;
       
  1605 
       
  1606     NamedNodeMap *firstMap = firstElement->attributes();
       
  1607     NamedNodeMap *secondMap = secondElement->attributes();
       
  1608 
       
  1609     unsigned firstLength = firstMap->length();
       
  1610 
       
  1611     if (firstLength != secondMap->length())
       
  1612         return false;
       
  1613 
       
  1614     for (unsigned i = 0; i < firstLength; i++) {
       
  1615         Attribute *attribute = firstMap->attributeItem(i);
       
  1616         Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name());
       
  1617 
       
  1618         if (!secondAttribute || attribute->value() != secondAttribute->value())
       
  1619             return false;
       
  1620     }
       
  1621     
       
  1622     return true;
       
  1623 }
       
  1624 
       
  1625 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
       
  1626 {
       
  1627     Node *startNode = start.node();
       
  1628     int startOffset = start.deprecatedEditingOffset();
       
  1629 
       
  1630     if (isAtomicNode(start.node())) {
       
  1631         if (start.deprecatedEditingOffset() != 0)
       
  1632             return false;
       
  1633 
       
  1634         // note: prior siblings could be unrendered elements. it's silly to miss the
       
  1635         // merge opportunity just for that.
       
  1636         if (start.node()->previousSibling())
       
  1637             return false;
       
  1638 
       
  1639         startNode = start.node()->parent();
       
  1640         startOffset = 0;
       
  1641     }
       
  1642 
       
  1643     if (!startNode->isElementNode())
       
  1644         return false;
       
  1645 
       
  1646     if (startOffset != 0)
       
  1647         return false;
       
  1648 
       
  1649     Node *previousSibling = startNode->previousSibling();
       
  1650 
       
  1651     if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
       
  1652         Element *previousElement = static_cast<Element *>(previousSibling);
       
  1653         Element *element = static_cast<Element *>(startNode);
       
  1654         Node *startChild = element->firstChild();
       
  1655         ASSERT(startChild);
       
  1656         mergeIdenticalElements(previousElement, element);
       
  1657 
       
  1658         int startOffsetAdjustment = startChild->nodeIndex();
       
  1659         int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
       
  1660         updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.deprecatedEditingOffset() + endOffsetAdjustment)); 
       
  1661         return true;
       
  1662     }
       
  1663 
       
  1664     return false;
       
  1665 }
       
  1666 
       
  1667 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
       
  1668 {
       
  1669     Node *endNode = end.node();
       
  1670     int endOffset = end.deprecatedEditingOffset();
       
  1671 
       
  1672     if (isAtomicNode(endNode)) {
       
  1673         if (endOffset < caretMaxOffset(endNode))
       
  1674             return false;
       
  1675 
       
  1676         unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
       
  1677         if (end.node()->nextSibling())
       
  1678             return false;
       
  1679 
       
  1680         endNode = end.node()->parent();
       
  1681         endOffset = parentLastOffset;
       
  1682     }
       
  1683 
       
  1684     if (!endNode->isElementNode() || endNode->hasTagName(brTag))
       
  1685         return false;
       
  1686 
       
  1687     Node *nextSibling = endNode->nextSibling();
       
  1688 
       
  1689     if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
       
  1690         Element *nextElement = static_cast<Element *>(nextSibling);
       
  1691         Element *element = static_cast<Element *>(endNode);
       
  1692         Node *nextChild = nextElement->firstChild();
       
  1693 
       
  1694         mergeIdenticalElements(element, nextElement);
       
  1695 
       
  1696         Node *startNode = start.node() == endNode ? nextElement : start.node();
       
  1697         ASSERT(startNode);
       
  1698 
       
  1699         int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
       
  1700         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(nextElement, endOffset));
       
  1701         return true;
       
  1702     }
       
  1703 
       
  1704     return false;
       
  1705 }
       
  1706 
       
  1707 void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endNode, PassRefPtr<Element> elementToInsert)
       
  1708 {
       
  1709     ASSERT(startNode);
       
  1710     ASSERT(endNode);
       
  1711     ASSERT(elementToInsert);
       
  1712     RefPtr<Element> element = elementToInsert;
       
  1713 
       
  1714     insertNodeBefore(element, startNode);
       
  1715     
       
  1716     Node* node = startNode;
       
  1717     while (1) {
       
  1718         Node* next = node->traverseNextNode();
       
  1719         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
       
  1720             removeNode(node);
       
  1721             appendNode(node, element);
       
  1722         }
       
  1723         if (node == endNode)
       
  1724             break;
       
  1725         node = next;
       
  1726     }
       
  1727 
       
  1728     Node* nextSibling = element->nextSibling();
       
  1729     Node* previousSibling = element->previousSibling();
       
  1730     if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable()
       
  1731         && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling)))
       
  1732         mergeIdenticalElements(element, static_cast<Element*>(nextSibling));
       
  1733 
       
  1734     if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) {
       
  1735         Node* mergedElement = previousSibling->nextSibling();
       
  1736         if (mergedElement->isElementNode() && mergedElement->isContentEditable()
       
  1737             && areIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement)))
       
  1738             mergeIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement));
       
  1739     }
       
  1740 
       
  1741     // FIXME: We should probably call updateStartEnd if the start or end was in the node
       
  1742     // range so that the endingSelection() is canonicalized.  See the comments at the end of
       
  1743     // VisibleSelection::validate().
       
  1744 }
       
  1745 
       
  1746 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
       
  1747 {
       
  1748     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
       
  1749     // inline content.
       
  1750     if (!block)
       
  1751         return;
       
  1752         
       
  1753     String cssText = styleChange.cssStyle();
       
  1754     CSSMutableStyleDeclaration* decl = block->inlineStyleDecl();
       
  1755     if (decl)
       
  1756         cssText += decl->cssText();
       
  1757     setNodeAttribute(block, styleAttr, cssText);
       
  1758 }
       
  1759 
       
  1760 static bool fontColorChangesComputedStyle(const Color& computedStyleColor, StyleChange styleChange)
       
  1761 {
       
  1762     if (styleChange.applyFontColor()) {
       
  1763         if (Color(styleChange.fontColor()) != computedStyleColor)
       
  1764             return true;
       
  1765     }
       
  1766     return false;
       
  1767 }
       
  1768 
       
  1769 static bool fontSizeChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange)
       
  1770 {
       
  1771     if (styleChange.applyFontSize()) {
       
  1772         if (styleChange.fontSize().toInt() != computedStyle->fontSize())
       
  1773             return true;
       
  1774     }
       
  1775     return false;
       
  1776 }
       
  1777 
       
  1778 static bool fontFaceChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange)
       
  1779 {
       
  1780     if (styleChange.applyFontFace()) {
       
  1781         if (computedStyle->fontDescription().family().family().string() != styleChange.fontFace())
       
  1782             return true;
       
  1783     }
       
  1784     return false;
       
  1785 }
       
  1786 
       
  1787 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode)
       
  1788 {
       
  1789     if (m_removeOnly)
       
  1790         return;
       
  1791 
       
  1792     StyleChange styleChange(style, Position(startNode, 0));
       
  1793 
       
  1794     //
       
  1795     // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
       
  1796     //
       
  1797     if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
       
  1798         RefPtr<Element> fontElement = createFontElement(document());
       
  1799         RenderStyle* computedStyle = startNode->computedStyle();
       
  1800 
       
  1801         // We only want to insert a font element if it will end up changing the style of the
       
  1802         // text somehow. Otherwise it will be a garbage node that will create problems for us
       
  1803         // most notably when we apply a blockquote style for a message reply.
       
  1804         if (fontColorChangesComputedStyle(computedStyle->color(), styleChange)
       
  1805                 || fontFaceChangesComputedStyle(computedStyle, styleChange)
       
  1806                 || fontSizeChangesComputedStyle(computedStyle, styleChange)) {
       
  1807             if (styleChange.applyFontColor())
       
  1808                 fontElement->setAttribute(colorAttr, styleChange.fontColor());
       
  1809             if (styleChange.applyFontFace())
       
  1810                 fontElement->setAttribute(faceAttr, styleChange.fontFace());
       
  1811             if (styleChange.applyFontSize())
       
  1812                 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
       
  1813             surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
       
  1814         }
       
  1815     }
       
  1816 
       
  1817     if (styleChange.cssStyle().length()) {
       
  1818         RefPtr<Element> styleElement = createStyleSpanElement(document());
       
  1819         styleElement->setAttribute(styleAttr, styleChange.cssStyle());
       
  1820         surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
       
  1821     }
       
  1822 
       
  1823     if (styleChange.applyBold())
       
  1824         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
       
  1825 
       
  1826     if (styleChange.applyItalic())
       
  1827         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
       
  1828 
       
  1829     if (styleChange.applyUnderline())
       
  1830         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
       
  1831 
       
  1832     if (styleChange.applyLineThrough())
       
  1833         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), sTag));
       
  1834 
       
  1835     if (styleChange.applySubscript())
       
  1836         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
       
  1837     else if (styleChange.applySuperscript())
       
  1838         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
       
  1839 
       
  1840     if (m_styledInlineElement)
       
  1841         surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
       
  1842 }
       
  1843 
       
  1844 float ApplyStyleCommand::computedFontSize(const Node *node)
       
  1845 {
       
  1846     if (!node)
       
  1847         return 0;
       
  1848     
       
  1849     Position pos(const_cast<Node *>(node), 0);
       
  1850     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
       
  1851     if (!computedStyle)
       
  1852         return 0;
       
  1853 
       
  1854     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(computedStyle->getPropertyCSSValue(CSSPropertyFontSize));
       
  1855     if (!value)
       
  1856         return 0;
       
  1857 
       
  1858     return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
       
  1859 }
       
  1860 
       
  1861 void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, const Position &end)
       
  1862 {
       
  1863     if (!node)
       
  1864         return;
       
  1865 
       
  1866     Position newStart = start;
       
  1867     Position newEnd = end;
       
  1868     
       
  1869     Node *child = node->firstChild();
       
  1870     while (child) {
       
  1871         Node *next = child->nextSibling();
       
  1872         if (child->isTextNode() && next && next->isTextNode()) {
       
  1873             Text *childText = static_cast<Text *>(child);
       
  1874             Text *nextText = static_cast<Text *>(next);
       
  1875             if (next == start.node())
       
  1876                 newStart = Position(childText, childText->length() + start.deprecatedEditingOffset());
       
  1877             if (next == end.node())
       
  1878                 newEnd = Position(childText, childText->length() + end.deprecatedEditingOffset());
       
  1879             String textToMove = nextText->data();
       
  1880             insertTextIntoNode(childText, childText->length(), textToMove);
       
  1881             removeNode(next);
       
  1882             // don't move child node pointer. it may want to merge with more text nodes.
       
  1883         }
       
  1884         else {
       
  1885             child = child->nextSibling();
       
  1886         }
       
  1887     }
       
  1888 
       
  1889     updateStartEnd(newStart, newEnd);
       
  1890 }
       
  1891 
       
  1892 }