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