• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
cssStyle() const58     String cssStyle() const { return m_cssStyle; }
applyBold() const59     bool applyBold() const { return m_applyBold; }
applyItalic() const60     bool applyItalic() const { return m_applyItalic; }
applyUnderline() const61     bool applyUnderline() const { return m_applyUnderline; }
applyLineThrough() const62     bool applyLineThrough() const { return m_applyLineThrough; }
applySubscript() const63     bool applySubscript() const { return m_applySubscript; }
applySuperscript() const64     bool applySuperscript() const { return m_applySuperscript; }
applyFontColor() const65     bool applyFontColor() const { return m_applyFontColor.length() > 0; }
applyFontFace() const66     bool applyFontFace() const { return m_applyFontFace.length() > 0; }
applyFontSize() const67     bool applyFontSize() const { return m_applyFontSize.length() > 0; }
68 
fontColor()69     String fontColor() { return m_applyFontColor; }
fontFace()70     String fontFace() { return m_applyFontFace; }
fontSize()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 
StyleChange(CSSStyleDeclaration * style,const Position & position)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 
init(PassRefPtr<CSSStyleDeclaration> style,const Position & position)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 
reconcileTextDecorationProperties(CSSMutableStyleDeclaration * style)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 
getIdentifierValue(CSSMutableStyleDeclaration * style,int propertyID)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 
setTextDecorationProperty(CSSMutableStyleDeclaration * style,const CSSValueList * newTextDecoration,int propertyID)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 
extractTextStyles(CSSMutableStyleDeclaration * style)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     RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
186     if (textDecoration && 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;
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 
styleSpanClassString()260 static String& styleSpanClassString()
261 {
262     DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
263     return styleSpanClassString;
264 }
265 
isStyleSpan(const Node * node)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 
isUnstyledStyleSpan(const Node * node)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 
isSpanWithoutAttributesOrUnstyleStyleSpan(const Node * node)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 
isEmptyFontTag(const Node * node)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 
createFontElement(Document * document)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 
createStyleSpanElement(Document * document)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 
diffTextDecorations(CSSMutableStyleDeclaration * style,int propertID,CSSValue * refTextDecoration)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 
fontWeightIsBold(CSSStyleDeclaration * style)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 
getPropertiesNotInComputedStyle(CSSStyleDeclaration * style,CSSComputedStyleDeclaration * computedStyle)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 
editingStyleAtPosition(Position pos,ShouldIncludeTypingStyle shouldIncludeTypingStyle)419 PassRefPtr<CSSMutableStyleDeclaration> 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 
prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration * editingStyle,Position pos)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 = 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 
removeStylesAddedByNode(CSSMutableStyleDeclaration * editingStyle,Node * node)471 void removeStylesAddedByNode(CSSMutableStyleDeclaration* editingStyle, Node* node)
472 {
473     ASSERT(node);
474     ASSERT(node->parentNode());
475     RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleAtPosition(Position(node->parentNode(), 0));
476     RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(Position(node, 0));
477     parentStyle->diff(style.get());
478     style->diff(editingStyle);
479 }
480 
ApplyStyleCommand(Document * document,CSSStyleDeclaration * style,EditAction editingAction,EPropertyLevel propertyLevel)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 
ApplyStyleCommand(Document * document,CSSStyleDeclaration * style,const Position & start,const Position & end,EditAction editingAction,EPropertyLevel propertyLevel)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 
ApplyStyleCommand(PassRefPtr<Element> element,bool removeOnly,EditAction editingAction)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 
updateStartEnd(const Position & newStart,const Position & newEnd)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 
startPosition()532 Position ApplyStyleCommand::startPosition()
533 {
534     if (m_useEndingSelection)
535         return endingSelection().start();
536 
537     return m_start;
538 }
539 
endPosition()540 Position ApplyStyleCommand::endPosition()
541 {
542     if (m_useEndingSelection)
543         return endingSelection().end();
544 
545     return m_end;
546 }
547 
doApply()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 
editingAction() const573 EditAction ApplyStyleCommand::editingAction() const
574 {
575     return m_editingAction;
576 }
577 
applyBlockStyle(CSSMutableStyleDeclaration * style)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         paragraphStart = nextParagraphStart;
624         nextParagraphStart = endOfParagraph(paragraphStart).next();
625     }
626 
627     startRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), startIndex, 0, true);
628     endRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), endIndex, 0, true);
629     if (startRange && endRange)
630         updateStartEnd(startRange->startPosition(), endRange->startPosition());
631 }
632 
633 #define NoFontDelta (0.0f)
634 #define MinimumFontSize (0.1f)
635 
applyRelativeFontStyleChange(CSSMutableStyleDeclaration * style)636 void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration *style)
637 {
638     RefPtr<CSSValue> value = style->getPropertyCSSValue(CSSPropertyFontSize);
639     if (value) {
640         // Explicit font size overrides any delta.
641         style->removeProperty(CSSPropertyWebkitFontSizeDelta);
642         return;
643     }
644 
645     // Get the adjustment amount out of the style.
646     value = style->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
647     if (!value)
648         return;
649     float adjustment = NoFontDelta;
650     if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
651         CSSPrimitiveValue *primitiveValue = static_cast<CSSPrimitiveValue *>(value.get());
652         if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) {
653             // Only PX handled now. If we handle more types in the future, perhaps
654             // a switch statement here would be more appropriate.
655             adjustment = primitiveValue->getFloatValue();
656         }
657     }
658     style->removeProperty(CSSPropertyWebkitFontSizeDelta);
659     if (adjustment == NoFontDelta)
660         return;
661 
662     Position start = startPosition();
663     Position end = endPosition();
664     if (comparePositions(end, start) < 0) {
665         Position swap = start;
666         start = end;
667         end = swap;
668     }
669 
670     // Join up any adjacent text nodes.
671     if (start.node()->isTextNode()) {
672         joinChildTextNodes(start.node()->parentNode(), start, end);
673         start = startPosition();
674         end = endPosition();
675     }
676     if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) {
677         joinChildTextNodes(end.node()->parentNode(), start, end);
678         start = startPosition();
679         end = endPosition();
680     }
681 
682     // Split the start text nodes if needed to apply style.
683     bool splitStart = splitTextAtStartIfNeeded(start, end);
684     if (splitStart) {
685         start = startPosition();
686         end = endPosition();
687     }
688     bool splitEnd = splitTextAtEndIfNeeded(start, end);
689     if (splitEnd) {
690         start = startPosition();
691         end = endPosition();
692     }
693 
694     // Calculate loop end point.
695     // If the end node is before the start node (can only happen if the end node is
696     // an ancestor of the start node), we gather nodes up to the next sibling of the end node
697     Node *beyondEnd;
698     if (start.node()->isDescendantOf(end.node()))
699         beyondEnd = end.node()->traverseNextSibling();
700     else
701         beyondEnd = end.node()->traverseNextNode();
702 
703     start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
704     Node *startNode = start.node();
705     if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
706         startNode = startNode->traverseNextNode();
707 
708     // Store away font size before making any changes to the document.
709     // This ensures that changes to one node won't effect another.
710     HashMap<Node*, float> startingFontSizes;
711     for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode())
712         startingFontSizes.set(node, computedFontSize(node));
713 
714     // These spans were added by us. If empty after font size changes, they can be removed.
715     Vector<RefPtr<HTMLElement> > unstyledSpans;
716 
717     Node* lastStyledNode = 0;
718     for (Node* node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
719         RefPtr<HTMLElement> element;
720         if (node->isHTMLElement()) {
721             // Only work on fully selected nodes.
722             if (!nodeFullySelected(node, start, end))
723                 continue;
724             element = static_cast<HTMLElement*>(node);
725         } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
726             // Last styled node was not parent node of this text node, but we wish to style this
727             // text node. To make this possible, add a style span to surround this text node.
728             RefPtr<HTMLElement> span = createStyleSpanElement(document());
729             surroundNodeRangeWithElement(node, node, span.get());
730             element = span.release();
731         }  else {
732             // Only handle HTML elements and text nodes.
733             continue;
734         }
735         lastStyledNode = node;
736 
737         CSSMutableStyleDeclaration* inlineStyleDecl = element->getInlineStyleDecl();
738         float currentFontSize = computedFontSize(node);
739         float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + adjustment);
740         RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSSPropertyFontSize);
741         if (value) {
742             inlineStyleDecl->removeProperty(CSSPropertyFontSize, true);
743             currentFontSize = computedFontSize(node);
744         }
745         if (currentFontSize != desiredFontSize) {
746             inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false);
747             setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText());
748         }
749         if (inlineStyleDecl->isEmpty()) {
750             removeNodeAttribute(element.get(), styleAttr);
751             // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
752             if (isUnstyledStyleSpan(element.get()))
753                 unstyledSpans.append(element.release());
754         }
755     }
756 
757     size_t size = unstyledSpans.size();
758     for (size_t i = 0; i < size; ++i)
759         removeNodePreservingChildren(unstyledSpans[i].get());
760 }
761 
762 #undef NoFontDelta
763 #undef MinimumFontSize
764 
dummySpanAncestorForNode(const Node * node)765 static Node* dummySpanAncestorForNode(const Node* node)
766 {
767     while (node && !isStyleSpan(node))
768         node = node->parent();
769 
770     return node ? node->parent() : 0;
771 }
772 
cleanupUnstyledAppleStyleSpans(Node * dummySpanAncestor)773 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
774 {
775     if (!dummySpanAncestor)
776         return;
777 
778     // Dummy spans are created when text node is split, so that style information
779     // can be propagated, which can result in more splitting. If a dummy span gets
780     // cloned/split, the new node is always a sibling of it. Therefore, we scan
781     // all the children of the dummy's parent
782     Node* next;
783     for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
784         next = node->nextSibling();
785         if (isUnstyledStyleSpan(node))
786             removeNodePreservingChildren(node);
787         node = next;
788     }
789 }
790 
splitAncestorsWithUnicodeBidi(Node * node,bool before,RefPtr<CSSPrimitiveValue> allowedDirection)791 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, RefPtr<CSSPrimitiveValue> allowedDirection)
792 {
793     // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
794     // In that case, we return the unsplit ancestor. Otherwise, we return 0.
795     Node* block = enclosingBlock(node);
796     if (!block)
797         return 0;
798 
799     Node* highestAncestorWithUnicodeBidi = 0;
800     Node* nextHighestAncestorWithUnicodeBidi = 0;
801     RefPtr<CSSPrimitiveValue> highestAncestorUnicodeBidi;
802     for (Node* n = node->parent(); n != block; n = n->parent()) {
803         RefPtr<CSSValue> unicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
804         if (unicodeBidi) {
805             ASSERT(unicodeBidi->isPrimitiveValue());
806             if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
807                 highestAncestorUnicodeBidi = static_cast<CSSPrimitiveValue*>(unicodeBidi.get());
808                 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
809                 highestAncestorWithUnicodeBidi = n;
810             }
811         }
812     }
813 
814     if (!highestAncestorWithUnicodeBidi)
815         return 0;
816 
817     HTMLElement* unsplitAncestor = 0;
818 
819     if (allowedDirection && highestAncestorUnicodeBidi->getIdent() != CSSValueBidiOverride) {
820         RefPtr<CSSValue> highestAncestorDirection = computedStyle(highestAncestorWithUnicodeBidi)->getPropertyCSSValue(CSSPropertyDirection);
821         ASSERT(highestAncestorDirection->isPrimitiveValue());
822         if (static_cast<CSSPrimitiveValue*>(highestAncestorDirection.get())->getIdent() == allowedDirection->getIdent() && highestAncestorWithUnicodeBidi->isHTMLElement()) {
823             if (!nextHighestAncestorWithUnicodeBidi)
824                 return static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
825 
826             unsplitAncestor = static_cast<HTMLElement*>(highestAncestorWithUnicodeBidi);
827             highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
828         }
829     }
830 
831     // Split every ancestor through highest ancestor with embedding.
832     Node* n = node;
833     while (true) {
834         Element* parent = static_cast<Element*>(n->parent());
835         if (before ? n->previousSibling() : n->nextSibling())
836             splitElement(parent, before ? n : n->nextSibling());
837         if (parent == highestAncestorWithUnicodeBidi)
838             break;
839         n = n->parent();
840     }
841     return unsplitAncestor;
842 }
843 
removeEmbeddingUpToEnclosingBlock(Node * node,Node * unsplitAncestor)844 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
845 {
846     Node* block = enclosingBlock(node);
847     if (!block)
848         return;
849 
850     Node* n = node->parent();
851     while (n != block && n != unsplitAncestor) {
852         Node* parent = n->parent();
853         if (!n->isStyledElement()) {
854             n = parent;
855             continue;
856         }
857 
858         StyledElement* element = static_cast<StyledElement*>(n);
859         RefPtr<CSSValue> unicodeBidi = computedStyle(element)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
860         if (unicodeBidi) {
861             ASSERT(unicodeBidi->isPrimitiveValue());
862             if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() != CSSValueNormal) {
863                 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
864                 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
865                 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
866                 // otherwise it sets the property in the inline style declaration.
867                 if (element->hasAttribute(dirAttr)) {
868                     // FIXME: If this is a BDO element, we should probably just remove it if it has no
869                     // other attributes, like we (should) do with B and I elements.
870                     removeNodeAttribute(element, dirAttr);
871                 } else {
872                     RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
873                     inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
874                     inlineStyle->removeProperty(CSSPropertyDirection);
875                     setNodeAttribute(element, styleAttr, inlineStyle->cssText());
876                     // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
877                     if (isUnstyledStyleSpan(element))
878                         removeNodePreservingChildren(element);
879                 }
880             }
881         }
882         n = parent;
883     }
884 }
885 
applyInlineStyle(CSSMutableStyleDeclaration * style)886 void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclaration *style)
887 {
888     Node* startDummySpanAncestor = 0;
889     Node* endDummySpanAncestor = 0;
890 
891     // update document layout once before removing styles
892     // so that we avoid the expense of updating before each and every call
893     // to check a computed style
894     updateLayout();
895 
896     // adjust to the positions we want to use for applying style
897     Position start = startPosition();
898     Position end = endPosition();
899     if (comparePositions(end, start) < 0) {
900         Position swap = start;
901         start = end;
902         end = swap;
903     }
904 
905     // split the start node and containing element if the selection starts inside of it
906     bool splitStart = splitTextElementAtStartIfNeeded(start, end);
907     if (splitStart) {
908         start = startPosition();
909         end = endPosition();
910         startDummySpanAncestor = dummySpanAncestorForNode(start.node());
911     }
912 
913     // split the end node and containing element if the selection ends inside of it
914     bool splitEnd = splitTextElementAtEndIfNeeded(start, end);
915     if (splitEnd) {
916         start = startPosition();
917         end = endPosition();
918         endDummySpanAncestor = dummySpanAncestorForNode(end.node());
919     }
920 
921     RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
922     RefPtr<CSSValue> direction;
923     HTMLElement* startUnsplitAncestor = 0;
924     HTMLElement* endUnsplitAncestor = 0;
925     if (unicodeBidi) {
926         RefPtr<CSSPrimitiveValue> allowedDirection;
927         ASSERT(unicodeBidi->isPrimitiveValue());
928         if (static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent() == CSSValueEmbed) {
929             // Leave alone an ancestor that provides the desired single level embedding, if there is one.
930             direction = style->getPropertyCSSValue(CSSPropertyDirection);
931             ASSERT(direction->isPrimitiveValue());
932             allowedDirection = static_cast<CSSPrimitiveValue*>(direction.get());
933         }
934         startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.node(), true, allowedDirection);
935         endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.node(), false, allowedDirection);
936         removeEmbeddingUpToEnclosingBlock(start.node(), startUnsplitAncestor);
937         removeEmbeddingUpToEnclosingBlock(end.node(), endUnsplitAncestor);
938     }
939 
940     // Remove style from the selection.
941     // Use the upstream position of the start for removing style.
942     // This will ensure we remove all traces of the relevant styles from the selection
943     // and prevent us from adding redundant ones, as described in:
944     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
945     Position removeStart = start.upstream();
946     Position embeddingRemoveStart = removeStart;
947     Position embeddingRemoveEnd = end;
948     if (unicodeBidi) {
949         // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
950         if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
951             embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
952         if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
953             embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
954     }
955 
956     if (embeddingRemoveStart != removeStart || embeddingRemoveEnd != end) {
957         RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
958         embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
959         embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
960         if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
961             removeInlineStyle(embeddingStyle, embeddingRemoveStart, embeddingRemoveEnd);
962 
963         RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
964         styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
965         styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
966         removeInlineStyle(styleWithoutEmbedding, removeStart, end);
967     } else
968         removeInlineStyle(style, removeStart, end);
969 
970     start = startPosition();
971     end = endPosition();
972 
973     if (splitStart) {
974         bool mergedStart = mergeStartWithPreviousIfIdentical(start, end);
975         if (mergedStart) {
976             start = startPosition();
977             end = endPosition();
978         }
979     }
980 
981     if (splitEnd) {
982         mergeEndWithNextIfIdentical(start, end);
983         start = startPosition();
984         end = endPosition();
985     }
986 
987     // update document layout once before running the rest of the function
988     // so that we avoid the expense of updating before each and every call
989     // to check a computed style
990     updateLayout();
991 
992     Position embeddingApplyStart = start;
993     Position embeddingApplyEnd = end;
994     if (unicodeBidi) {
995         // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
996         Node* startEnclosingBlock = enclosingBlock(start.node());
997         for (Node* n = start.node(); n != startEnclosingBlock; n = n->parent()) {
998             if (n->isHTMLElement()) {
999                 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1000                 if (ancestorUnicodeBidi) {
1001                     ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
1002                     if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
1003                         embeddingApplyStart = positionInParentAfterNode(n);
1004                         break;
1005                     }
1006                 }
1007             }
1008         }
1009 
1010         Node* endEnclosingBlock = enclosingBlock(end.node());
1011         for (Node* n = end.node(); n != endEnclosingBlock; n = n->parent()) {
1012             if (n->isHTMLElement()) {
1013                 RefPtr<CSSValue> ancestorUnicodeBidi = computedStyle(n)->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1014                 if (ancestorUnicodeBidi) {
1015                     ASSERT(ancestorUnicodeBidi->isPrimitiveValue());
1016                     if (static_cast<CSSPrimitiveValue*>(ancestorUnicodeBidi.get())->getIdent() == CSSValueEmbed) {
1017                         embeddingApplyEnd = positionInParentBeforeNode(n);
1018                         break;
1019                     }
1020                 }
1021             }
1022         }
1023     }
1024 
1025     if (embeddingApplyStart != start || embeddingApplyEnd != end) {
1026         if (embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull()) {
1027             RefPtr<CSSMutableStyleDeclaration> embeddingStyle = CSSMutableStyleDeclaration::create();
1028             embeddingStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed);
1029             embeddingStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
1030             applyInlineStyleToRange(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
1031         }
1032 
1033         RefPtr<CSSMutableStyleDeclaration> styleWithoutEmbedding = style->copy();
1034         styleWithoutEmbedding->removeProperty(CSSPropertyUnicodeBidi);
1035         styleWithoutEmbedding->removeProperty(CSSPropertyDirection);
1036         applyInlineStyleToRange(styleWithoutEmbedding.get(), start, end);
1037     } else
1038         applyInlineStyleToRange(style, start, end);
1039 
1040     // Remove dummy style spans created by splitting text elements.
1041     cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
1042     if (endDummySpanAncestor != startDummySpanAncestor)
1043         cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
1044 }
1045 
applyInlineStyleToRange(CSSMutableStyleDeclaration * style,const Position & start,const Position & rangeEnd)1046 void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* style, const Position& start, const Position& rangeEnd)
1047 {
1048     Node* node = start.node();
1049     Position end = rangeEnd;
1050 
1051     bool rangeIsEmpty = false;
1052 
1053     if (start.deprecatedEditingOffset() >= caretMaxOffset(start.node())) {
1054         node = node->traverseNextNode();
1055         Position newStart = Position(node, 0);
1056         if (!node || comparePositions(end, newStart) < 0)
1057             rangeIsEmpty = true;
1058     }
1059 
1060     if (!rangeIsEmpty) {
1061         // pastEndNode is the node after the last fully selected node.
1062         Node* pastEndNode = end.node();
1063         if (end.deprecatedEditingOffset() >= caretMaxOffset(end.node()))
1064             pastEndNode = end.node()->traverseNextSibling();
1065         // FIXME: Callers should perform this operation on a Range that includes the br
1066         // if they want style applied to the empty line.
1067         if (start == end && start.node()->hasTagName(brTag))
1068             pastEndNode = start.node()->traverseNextNode();
1069         // Add the style to selected inline runs.
1070         for (Node* next; node && node != pastEndNode; node = next) {
1071 
1072             next = node->traverseNextNode();
1073 
1074             if (!node->renderer() || !node->isContentEditable())
1075                 continue;
1076 
1077             if (!node->isContentRichlyEditable() && node->isHTMLElement()) {
1078                 // This is a plaintext-only region. Only proceed if it's fully selected.
1079                 // pastEndNode is the node after the last fully selected node, so if it's inside node then
1080                 // node isn't fully selected.
1081                 if (pastEndNode && pastEndNode->isDescendantOf(node))
1082                     break;
1083                 // Add to this element's inline style and skip over its contents.
1084                 HTMLElement* element = static_cast<HTMLElement*>(node);
1085                 RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
1086                 inlineStyle->merge(style);
1087                 setNodeAttribute(element, styleAttr, inlineStyle->cssText());
1088                 next = node->traverseNextSibling();
1089                 continue;
1090             }
1091 
1092             if (isBlock(node))
1093                 continue;
1094 
1095             if (node->childNodeCount()) {
1096                 if (editingIgnoresContent(node)) {
1097                     next = node->traverseNextSibling();
1098                     continue;
1099                 }
1100                 continue;
1101             }
1102 
1103             Node* runStart = node;
1104             // Find the end of the run.
1105             Node* sibling = node->nextSibling();
1106             while (sibling && sibling != pastEndNode && (!sibling->isElementNode() || sibling->hasTagName(brTag)) && !isBlock(sibling)) {
1107                 node = sibling;
1108                 sibling = node->nextSibling();
1109             }
1110             // Recompute next, since node has changed.
1111             next = node->traverseNextNode();
1112             // Apply the style to the run.
1113             addInlineStyleIfNeeded(style, runStart, node);
1114         }
1115     }
1116 }
1117 
shouldRemoveTextDecorationTag(CSSStyleDeclaration * styleToApply,int textDecorationAddedByTag) const1118 bool ApplyStyleCommand::shouldRemoveTextDecorationTag(CSSStyleDeclaration* styleToApply, int textDecorationAddedByTag) const
1119 {
1120     // Honor text-decorations-in-effect
1121     RefPtr<CSSValue> textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1122     if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
1123         textDecorationsToApply = styleToApply->getPropertyCSSValue(CSSPropertyTextDecoration);
1124 
1125     // When there is no text decorations to apply, remove any one of u, s, & strike
1126     if (!textDecorationsToApply || !textDecorationsToApply->isValueList())
1127         return true;
1128 
1129     // Remove node if it implicitly adds style not present in styleToApply
1130     CSSValueList* valueList = static_cast<CSSValueList*>(textDecorationsToApply.get());
1131     RefPtr<CSSPrimitiveValue> value = CSSPrimitiveValue::createIdentifier(textDecorationAddedByTag);
1132     return !valueList->hasValue(value.get());
1133 }
1134 
1135 // This function maps from styling tags to CSS styles.  Used for knowing which
1136 // styling tags should be removed when toggling styles.
implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement * elem,CSSMutableStyleDeclaration * style)1137 bool ApplyStyleCommand::implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(HTMLElement* elem, CSSMutableStyleDeclaration* style)
1138 {
1139     CSSMutableStyleDeclaration::const_iterator end = style->end();
1140     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
1141         const CSSProperty& property = *it;
1142         // FIXME: This should probably be re-written to lookup the tagname in a
1143         // hash and match against an expected property/value pair.
1144         switch (property.id()) {
1145         case CSSPropertyFontWeight:
1146             // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational
1147             if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag))
1148                 return !equalIgnoringCase(property.value()->cssText(), "bold") || !elem->hasChildNodes();
1149             break;
1150         case CSSPropertyVerticalAlign:
1151             if (elem->hasLocalName(subTag))
1152                 return !equalIgnoringCase(property.value()->cssText(), "sub") || !elem->hasChildNodes();
1153             if (elem->hasLocalName(supTag))
1154                 return !equalIgnoringCase(property.value()->cssText(), "sup") || !elem->hasChildNodes();
1155             break;
1156         case CSSPropertyFontStyle:
1157             // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational
1158             if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag))
1159                 return !equalIgnoringCase(property.value()->cssText(), "italic") || !elem->hasChildNodes();
1160             break;
1161         case CSSPropertyTextDecoration:
1162         case CSSPropertyWebkitTextDecorationsInEffect:
1163                 if (elem->hasLocalName(uTag))
1164                     return shouldRemoveTextDecorationTag(style, CSSValueUnderline) || !elem->hasChildNodes();
1165                 else if (elem->hasLocalName(sTag) || elem->hasTagName(strikeTag))
1166                     return shouldRemoveTextDecorationTag(style,CSSValueLineThrough) || !elem->hasChildNodes();
1167         }
1168     }
1169     return false;
1170 }
1171 
replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement * & elem)1172 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
1173 {
1174     bool removeNode = false;
1175 
1176     // Similar to isSpanWithoutAttributesOrUnstyleStyleSpan, but does not look for Apple-style-span.
1177     NamedNodeMap* attributes = elem->attributes(true); // readonly
1178     if (!attributes || attributes->isEmpty())
1179         removeNode = true;
1180     else if (attributes->length() == 1 && elem->hasAttribute(styleAttr)) {
1181         // Remove the element even if it has just style='' (this might be redundantly checked later too)
1182         CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
1183         if (!inlineStyleDecl || inlineStyleDecl->isEmpty())
1184             removeNode = true;
1185     }
1186 
1187     if (removeNode)
1188         removeNodePreservingChildren(elem);
1189     else {
1190         HTMLElement* newSpanElement = replaceNodeWithSpanPreservingChildrenAndAttributes(elem);
1191         ASSERT(newSpanElement && newSpanElement->inDocument());
1192         elem = newSpanElement;
1193     }
1194 }
1195 
removeHTMLFontStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)1196 void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
1197 {
1198     ASSERT(style);
1199     ASSERT(elem);
1200 
1201     if (!elem->hasLocalName(fontTag))
1202         return;
1203 
1204     CSSMutableStyleDeclaration::const_iterator end = style->end();
1205     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
1206         switch ((*it).id()) {
1207             case CSSPropertyColor:
1208                 removeNodeAttribute(elem, colorAttr);
1209                 break;
1210             case CSSPropertyFontFamily:
1211                 removeNodeAttribute(elem, faceAttr);
1212                 break;
1213             case CSSPropertyFontSize:
1214                 removeNodeAttribute(elem, sizeAttr);
1215                 break;
1216         }
1217     }
1218 
1219     if (isEmptyFontTag(elem))
1220         removeNodePreservingChildren(elem);
1221 }
1222 
removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)1223 void ApplyStyleCommand::removeHTMLBidiEmbeddingStyle(CSSMutableStyleDeclaration *style, HTMLElement *elem)
1224 {
1225     ASSERT(style);
1226     ASSERT(elem);
1227 
1228     if (!elem->hasAttribute(dirAttr))
1229         return;
1230 
1231     if (!style->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection))
1232         return;
1233 
1234     removeNodeAttribute(elem, dirAttr);
1235 
1236     // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
1237     if (isUnstyledStyleSpan(elem))
1238         removeNodePreservingChildren(elem);
1239 }
1240 
removeCSSStyle(CSSMutableStyleDeclaration * style,HTMLElement * elem)1241 void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclaration* style, HTMLElement* elem)
1242 {
1243     ASSERT(style);
1244     ASSERT(elem);
1245 
1246     CSSMutableStyleDeclaration* decl = elem->inlineStyleDecl();
1247     if (!decl)
1248         return;
1249 
1250     CSSMutableStyleDeclaration::const_iterator end = style->end();
1251     for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
1252         CSSPropertyID propertyID = static_cast<CSSPropertyID>((*it).id());
1253         RefPtr<CSSValue> value = decl->getPropertyCSSValue(propertyID);
1254         if (value && (propertyID != CSSPropertyWhiteSpace || !isTabSpanNode(elem))) {
1255             removeCSSProperty(decl, propertyID);
1256             if (propertyID == CSSPropertyUnicodeBidi && !decl->getPropertyValue(CSSPropertyDirection).isEmpty())
1257                 removeCSSProperty(decl, CSSPropertyDirection);
1258         }
1259     }
1260 
1261     // No need to serialize <foo style=""> if we just removed the last css property
1262     if (decl->isEmpty())
1263         removeNodeAttribute(elem, styleAttr);
1264 
1265     if (isSpanWithoutAttributesOrUnstyleStyleSpan(elem))
1266         removeNodePreservingChildren(elem);
1267 }
1268 
hasTextDecorationProperty(Node * node)1269 static bool hasTextDecorationProperty(Node *node)
1270 {
1271     if (!node->isElementNode())
1272         return false;
1273 
1274     RefPtr<CSSValue> value = computedStyle(node)->getPropertyCSSValue(CSSPropertyTextDecoration, DoNotUpdateLayout);
1275     return value && !equalIgnoringCase(value->cssText(), "none");
1276 }
1277 
highestAncestorWithTextDecoration(Node * node)1278 static Node* highestAncestorWithTextDecoration(Node *node)
1279 {
1280     ASSERT(node);
1281     Node* result = 0;
1282     Node* unsplittableElement = unsplittableElementForPosition(Position(node, 0));
1283 
1284     for (Node *n = node; n; n = n->parentNode()) {
1285         if (hasTextDecorationProperty(n))
1286             result = n;
1287         // Should stop at the editable root (cannot cross editing boundary) and
1288         // also stop at the unsplittable element to be consistent with other UAs
1289         if (n == unsplittableElement)
1290             break;
1291     }
1292 
1293     return result;
1294 }
1295 
extractTextDecorationStyle(Node * node)1296 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractTextDecorationStyle(Node* node)
1297 {
1298     ASSERT(node);
1299     ASSERT(node->isElementNode());
1300 
1301     // non-html elements not handled yet
1302     if (!node->isHTMLElement())
1303         return 0;
1304 
1305     HTMLElement *element = static_cast<HTMLElement *>(node);
1306     RefPtr<CSSMutableStyleDeclaration> style = element->inlineStyleDecl();
1307     if (!style)
1308         return 0;
1309 
1310     int properties[1] = { CSSPropertyTextDecoration };
1311     RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = style->copyPropertiesInSet(properties, 1);
1312 
1313     RefPtr<CSSValue> property = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1314     if (property && !equalIgnoringCase(property->cssText(), "none"))
1315         removeCSSProperty(style.get(), CSSPropertyTextDecoration);
1316 
1317     return textDecorationStyle.release();
1318 }
1319 
extractAndNegateTextDecorationStyle(Node * node)1320 PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractAndNegateTextDecorationStyle(Node* node)
1321 {
1322     ASSERT(node);
1323     ASSERT(node->isElementNode());
1324 
1325     // non-html elements not handled yet
1326     if (!node->isHTMLElement())
1327         return 0;
1328 
1329     RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
1330     ASSERT(nodeStyle);
1331 
1332     int properties[1] = { CSSPropertyTextDecoration };
1333     RefPtr<CSSMutableStyleDeclaration> textDecorationStyle = nodeStyle->copyPropertiesInSet(properties, 1);
1334 
1335     RefPtr<CSSValue> property = nodeStyle->getPropertyCSSValue(CSSPropertyTextDecoration);
1336     if (property && !equalIgnoringCase(property->cssText(), "none")) {
1337         RefPtr<CSSMutableStyleDeclaration> newStyle = textDecorationStyle->copy();
1338         newStyle->setProperty(CSSPropertyTextDecoration, "none");
1339         applyTextDecorationStyle(node, newStyle.get());
1340     }
1341 
1342     return textDecorationStyle.release();
1343 }
1344 
applyTextDecorationStyle(Node * node,CSSMutableStyleDeclaration * style)1345 void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDeclaration *style)
1346 {
1347     ASSERT(node);
1348 
1349     if (!style || style->cssText().isEmpty())
1350         return;
1351 
1352     StyleChange styleChange(style, Position(node, 0));
1353     if (styleChange.cssStyle().length()) {
1354         if (node->isTextNode()) {
1355             RefPtr<HTMLElement> styleSpan = createStyleSpanElement(document());
1356             surroundNodeRangeWithElement(node, node, styleSpan.get());
1357             node = styleSpan.get();
1358         }
1359 
1360         if (!node->isElementNode())
1361             return;
1362 
1363         HTMLElement *element = static_cast<HTMLElement *>(node);
1364         String cssText = styleChange.cssStyle();
1365         CSSMutableStyleDeclaration *decl = element->inlineStyleDecl();
1366         if (decl)
1367             cssText += decl->cssText();
1368         setNodeAttribute(element, styleAttr, cssText);
1369     }
1370 
1371     if (styleChange.applyUnderline())
1372         surroundNodeRangeWithElement(node, node, createHTMLElement(document(), uTag));
1373 
1374     if (styleChange.applyLineThrough())
1375         surroundNodeRangeWithElement(node, node, createHTMLElement(document(), sTag));
1376 }
1377 
pushDownTextDecorationStyleAroundNode(Node * targetNode,bool forceNegate)1378 void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(Node* targetNode, bool forceNegate)
1379 {
1380     ASSERT(targetNode);
1381     Node* highestAncestor = highestAncestorWithTextDecoration(targetNode);
1382     if (!highestAncestor)
1383         return;
1384 
1385     // The outer loop is traversing the tree vertically from highestAncestor to targetNode
1386     Node* current = highestAncestor;
1387     while (current != targetNode) {
1388         ASSERT(current);
1389         ASSERT(current->contains(targetNode));
1390         RefPtr<CSSMutableStyleDeclaration> decoration = forceNegate ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current);
1391 
1392         // The inner loop will go through children on each level
1393         Node* child = current->firstChild();
1394         while (child) {
1395             Node* nextChild = child->nextSibling();
1396 
1397             // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode
1398             if (child != targetNode)
1399                 applyTextDecorationStyle(child, decoration.get());
1400 
1401             // We found the next node for the outer loop (contains targetNode)
1402             // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1403             if (child == targetNode || child->contains(targetNode))
1404                 current = child;
1405 
1406             child = nextChild;
1407         }
1408     }
1409 }
1410 
pushDownTextDecorationStyleAtBoundaries(const Position & start,const Position & end)1411 void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end)
1412 {
1413     // We need to work in two passes. First we push down any inline
1414     // styles that set text decoration. Then we look for any remaining
1415     // styles (caused by stylesheets) and explicitly negate text
1416     // decoration while pushing down.
1417 
1418     pushDownTextDecorationStyleAroundNode(start.node(), false);
1419     updateLayout();
1420     pushDownTextDecorationStyleAroundNode(start.node(), true);
1421 
1422     pushDownTextDecorationStyleAroundNode(end.node(), false);
1423     updateLayout();
1424     pushDownTextDecorationStyleAroundNode(end.node(), true);
1425 }
1426 
1427 // FIXME: Why does this exist?  Callers should either use lastOffsetForEditing or lastOffsetInNode
maxRangeOffset(Node * n)1428 static int maxRangeOffset(Node *n)
1429 {
1430     if (n->offsetInCharacters())
1431         return n->maxCharacterOffset();
1432 
1433     if (n->isElementNode())
1434         return n->childNodeCount();
1435 
1436     return 1;
1437 }
1438 
removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style,const Position & start,const Position & end)1439 void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> style, const Position &start, const Position &end)
1440 {
1441     ASSERT(start.isNotNull());
1442     ASSERT(end.isNotNull());
1443     ASSERT(start.node()->inDocument());
1444     ASSERT(end.node()->inDocument());
1445     ASSERT(comparePositions(start, end) <= 0);
1446 
1447     RefPtr<CSSValue> textDecorationSpecialProperty = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1448 
1449     if (textDecorationSpecialProperty) {
1450         pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream());
1451         style = style->copy();
1452         style->setProperty(CSSPropertyTextDecoration, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSSPropertyWebkitTextDecorationsInEffect));
1453     }
1454 
1455     // The s and e variables store the positions used to set the ending selection after style removal
1456     // takes place. This will help callers to recognize when either the start node or the end node
1457     // are removed from the document during the work of this function.
1458     Position s = start;
1459     Position e = end;
1460 
1461     Node* node = start.node();
1462     while (node) {
1463         Node* next = node->traverseNextNode();
1464         if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1465             HTMLElement* elem = static_cast<HTMLElement*>(node);
1466             Node* prev = elem->traversePreviousNodePostOrder();
1467             Node* next = elem->traverseNextNode();
1468             if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName()))
1469                 removeNodePreservingChildren(elem);
1470 
1471             if (implicitlyStyledElementShouldBeRemovedWhenApplyingStyle(elem, style.get()))
1472                 replaceWithSpanOrRemoveIfWithoutAttributes(elem);
1473 
1474             // If the node was converted to a span, the span may still contain relevant
1475             // styles which must be removed (e.g. <b style='font-weight: bold'>)
1476             if (elem->inDocument()) {
1477                 removeHTMLFontStyle(style.get(), elem);
1478                 removeHTMLBidiEmbeddingStyle(style.get(), elem);
1479                 removeCSSStyle(style.get(), elem);
1480             }
1481             if (!elem->inDocument()) {
1482                 if (s.node() == elem) {
1483                     // Since elem must have been fully selected, and it is at the start
1484                     // of the selection, it is clear we can set the new s offset to 0.
1485                     ASSERT(s.deprecatedEditingOffset() <= caretMinOffset(s.node()));
1486                     s = Position(next, 0);
1487                 }
1488                 if (e.node() == elem) {
1489                     // Since elem must have been fully selected, and it is at the end
1490                     // of the selection, it is clear we can set the new e offset to
1491                     // the max range offset of prev.
1492                     ASSERT(e.deprecatedEditingOffset() >= maxRangeOffset(e.node()));
1493                     e = Position(prev, maxRangeOffset(prev));
1494                 }
1495             }
1496         }
1497         if (node == end.node())
1498             break;
1499         node = next;
1500     }
1501 
1502     ASSERT(s.node()->inDocument());
1503     ASSERT(e.node()->inDocument());
1504     updateStartEnd(s, e);
1505 }
1506 
nodeFullySelected(Node * node,const Position & start,const Position & end) const1507 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
1508 {
1509     ASSERT(node);
1510     ASSERT(node->isElementNode());
1511 
1512     Position pos = Position(node, node->childNodeCount()).upstream();
1513     return comparePositions(Position(node, 0), start) >= 0 && comparePositions(pos, end) <= 0;
1514 }
1515 
nodeFullyUnselected(Node * node,const Position & start,const Position & end) const1516 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
1517 {
1518     ASSERT(node);
1519     ASSERT(node->isElementNode());
1520 
1521     Position pos = Position(node, node->childNodeCount()).upstream();
1522     bool isFullyBeforeStart = comparePositions(pos, start) < 0;
1523     bool isFullyAfterEnd = comparePositions(Position(node, 0), end) > 0;
1524 
1525     return isFullyBeforeStart || isFullyAfterEnd;
1526 }
1527 
1528 
splitTextAtStartIfNeeded(const Position & start,const Position & end)1529 bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
1530 {
1531     if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
1532         int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
1533         Text *text = static_cast<Text *>(start.node());
1534         splitTextNode(text, start.deprecatedEditingOffset());
1535         updateStartEnd(Position(start.node(), 0), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
1536         return true;
1537     }
1538     return false;
1539 }
1540 
splitTextAtEndIfNeeded(const Position & start,const Position & end)1541 bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end)
1542 {
1543     if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
1544         Text *text = static_cast<Text *>(end.node());
1545         splitTextNode(text, end.deprecatedEditingOffset());
1546 
1547         Node *prevNode = text->previousSibling();
1548         ASSERT(prevNode);
1549         Node *startNode = start.node() == end.node() ? prevNode : start.node();
1550         ASSERT(startNode);
1551         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode, caretMaxOffset(prevNode)));
1552         return true;
1553     }
1554     return false;
1555 }
1556 
splitTextElementAtStartIfNeeded(const Position & start,const Position & end)1557 bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end)
1558 {
1559     if (start.node()->isTextNode() && start.deprecatedEditingOffset() > caretMinOffset(start.node()) && start.deprecatedEditingOffset() < caretMaxOffset(start.node())) {
1560         int endOffsetAdjustment = start.node() == end.node() ? start.deprecatedEditingOffset() : 0;
1561         Text *text = static_cast<Text *>(start.node());
1562         splitTextNodeContainingElement(text, start.deprecatedEditingOffset());
1563 
1564         updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.deprecatedEditingOffset() - endOffsetAdjustment));
1565         return true;
1566     }
1567     return false;
1568 }
1569 
splitTextElementAtEndIfNeeded(const Position & start,const Position & end)1570 bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end)
1571 {
1572     if (end.node()->isTextNode() && end.deprecatedEditingOffset() > caretMinOffset(end.node()) && end.deprecatedEditingOffset() < caretMaxOffset(end.node())) {
1573         Text *text = static_cast<Text *>(end.node());
1574         splitTextNodeContainingElement(text, end.deprecatedEditingOffset());
1575 
1576         Node *prevNode = text->parent()->previousSibling()->lastChild();
1577         ASSERT(prevNode);
1578         Node *startNode = start.node() == end.node() ? prevNode : start.node();
1579         ASSERT(startNode);
1580         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1));
1581         return true;
1582     }
1583     return false;
1584 }
1585 
areIdenticalElements(Node * first,Node * second)1586 static bool areIdenticalElements(Node *first, Node *second)
1587 {
1588     // check that tag name and all attribute names and values are identical
1589 
1590     if (!first->isElementNode())
1591         return false;
1592 
1593     if (!second->isElementNode())
1594         return false;
1595 
1596     Element *firstElement = static_cast<Element *>(first);
1597     Element *secondElement = static_cast<Element *>(second);
1598 
1599     if (!firstElement->tagQName().matches(secondElement->tagQName()))
1600         return false;
1601 
1602     NamedNodeMap *firstMap = firstElement->attributes();
1603     NamedNodeMap *secondMap = secondElement->attributes();
1604 
1605     unsigned firstLength = firstMap->length();
1606 
1607     if (firstLength != secondMap->length())
1608         return false;
1609 
1610     for (unsigned i = 0; i < firstLength; i++) {
1611         Attribute *attribute = firstMap->attributeItem(i);
1612         Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name());
1613 
1614         if (!secondAttribute || attribute->value() != secondAttribute->value())
1615             return false;
1616     }
1617 
1618     return true;
1619 }
1620 
mergeStartWithPreviousIfIdentical(const Position & start,const Position & end)1621 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end)
1622 {
1623     Node *startNode = start.node();
1624     int startOffset = start.deprecatedEditingOffset();
1625 
1626     if (isAtomicNode(start.node())) {
1627         if (start.deprecatedEditingOffset() != 0)
1628             return false;
1629 
1630         // note: prior siblings could be unrendered elements. it's silly to miss the
1631         // merge opportunity just for that.
1632         if (start.node()->previousSibling())
1633             return false;
1634 
1635         startNode = start.node()->parent();
1636         startOffset = 0;
1637     }
1638 
1639     if (!startNode->isElementNode())
1640         return false;
1641 
1642     if (startOffset != 0)
1643         return false;
1644 
1645     Node *previousSibling = startNode->previousSibling();
1646 
1647     if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1648         Element *previousElement = static_cast<Element *>(previousSibling);
1649         Element *element = static_cast<Element *>(startNode);
1650         Node *startChild = element->firstChild();
1651         ASSERT(startChild);
1652         mergeIdenticalElements(previousElement, element);
1653 
1654         int startOffsetAdjustment = startChild->nodeIndex();
1655         int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0;
1656         updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.deprecatedEditingOffset() + endOffsetAdjustment));
1657         return true;
1658     }
1659 
1660     return false;
1661 }
1662 
mergeEndWithNextIfIdentical(const Position & start,const Position & end)1663 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end)
1664 {
1665     Node *endNode = end.node();
1666     int endOffset = end.deprecatedEditingOffset();
1667 
1668     if (isAtomicNode(endNode)) {
1669         if (endOffset < caretMaxOffset(endNode))
1670             return false;
1671 
1672         unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1;
1673         if (end.node()->nextSibling())
1674             return false;
1675 
1676         endNode = end.node()->parent();
1677         endOffset = parentLastOffset;
1678     }
1679 
1680     if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1681         return false;
1682 
1683     Node *nextSibling = endNode->nextSibling();
1684 
1685     if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1686         Element *nextElement = static_cast<Element *>(nextSibling);
1687         Element *element = static_cast<Element *>(endNode);
1688         Node *nextChild = nextElement->firstChild();
1689 
1690         mergeIdenticalElements(element, nextElement);
1691 
1692         Node *startNode = start.node() == endNode ? nextElement : start.node();
1693         ASSERT(startNode);
1694 
1695         int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1696         updateStartEnd(Position(startNode, start.deprecatedEditingOffset()), Position(nextElement, endOffset));
1697         return true;
1698     }
1699 
1700     return false;
1701 }
1702 
surroundNodeRangeWithElement(Node * startNode,Node * endNode,PassRefPtr<Element> elementToInsert)1703 void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endNode, PassRefPtr<Element> elementToInsert)
1704 {
1705     ASSERT(startNode);
1706     ASSERT(endNode);
1707     ASSERT(elementToInsert);
1708     RefPtr<Element> element = elementToInsert;
1709 
1710     insertNodeBefore(element, startNode);
1711 
1712     Node* node = startNode;
1713     while (1) {
1714         Node* next = node->traverseNextNode();
1715         if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) {
1716             removeNode(node);
1717             appendNode(node, element);
1718         }
1719         if (node == endNode)
1720             break;
1721         node = next;
1722     }
1723 
1724     Node* nextSibling = element->nextSibling();
1725     Node* previousSibling = element->previousSibling();
1726     if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable()
1727         && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling)))
1728         mergeIdenticalElements(element, static_cast<Element*>(nextSibling));
1729 
1730     if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) {
1731         Node* mergedElement = previousSibling->nextSibling();
1732         if (mergedElement->isElementNode() && mergedElement->isContentEditable()
1733             && areIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement)))
1734             mergeIdenticalElements(static_cast<Element*>(previousSibling), static_cast<Element*>(mergedElement));
1735     }
1736 
1737     // FIXME: We should probably call updateStartEnd if the start or end was in the node
1738     // range so that the endingSelection() is canonicalized.  See the comments at the end of
1739     // VisibleSelection::validate().
1740 }
1741 
addBlockStyle(const StyleChange & styleChange,HTMLElement * block)1742 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
1743 {
1744     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1745     // inline content.
1746     if (!block)
1747         return;
1748 
1749     String cssText = styleChange.cssStyle();
1750     CSSMutableStyleDeclaration* decl = block->inlineStyleDecl();
1751     if (decl)
1752         cssText += decl->cssText();
1753     setNodeAttribute(block, styleAttr, cssText);
1754 }
1755 
fontColorChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)1756 static bool fontColorChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange)
1757 {
1758     if (styleChange.applyFontColor()) {
1759         if (Color(styleChange.fontColor()) != computedStyle->color())
1760             return true;
1761     }
1762     return false;
1763 }
1764 
fontSizeChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)1765 static bool fontSizeChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange)
1766 {
1767     if (styleChange.applyFontSize()) {
1768         if (styleChange.fontSize().toInt() != computedStyle->fontSize())
1769             return true;
1770     }
1771     return false;
1772 }
1773 
fontFaceChangesComputedStyle(RenderStyle * computedStyle,StyleChange styleChange)1774 static bool fontFaceChangesComputedStyle(RenderStyle* computedStyle, StyleChange styleChange)
1775 {
1776     if (styleChange.applyFontFace()) {
1777         if (computedStyle->fontDescription().family().family().string() != styleChange.fontFace())
1778             return true;
1779     }
1780     return false;
1781 }
1782 
addInlineStyleIfNeeded(CSSMutableStyleDeclaration * style,Node * startNode,Node * endNode)1783 void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode)
1784 {
1785     if (m_removeOnly)
1786         return;
1787 
1788     StyleChange styleChange(style, Position(startNode, 0));
1789 
1790     //
1791     // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1792     //
1793     if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1794         RefPtr<Element> fontElement = createFontElement(document());
1795         RenderStyle* computedStyle = startNode->computedStyle();
1796 
1797         // We only want to insert a font element if it will end up changing the style of the
1798         // text somehow. Otherwise it will be a garbage node that will create problems for us
1799         // most notably when we apply a blockquote style for a message reply.
1800         if (fontColorChangesComputedStyle(computedStyle, styleChange)
1801                 || fontFaceChangesComputedStyle(computedStyle, styleChange)
1802                 || fontSizeChangesComputedStyle(computedStyle, styleChange)) {
1803             if (styleChange.applyFontColor())
1804                 fontElement->setAttribute(colorAttr, styleChange.fontColor());
1805             if (styleChange.applyFontFace())
1806                 fontElement->setAttribute(faceAttr, styleChange.fontFace());
1807             if (styleChange.applyFontSize())
1808                 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
1809             surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1810         }
1811     }
1812 
1813     if (styleChange.cssStyle().length()) {
1814         RefPtr<Element> styleElement = createStyleSpanElement(document());
1815         styleElement->setAttribute(styleAttr, styleChange.cssStyle());
1816         surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
1817     }
1818 
1819     if (styleChange.applyBold())
1820         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
1821 
1822     if (styleChange.applyItalic())
1823         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
1824 
1825     if (styleChange.applyUnderline())
1826         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
1827 
1828     if (styleChange.applyLineThrough())
1829         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), sTag));
1830 
1831     if (styleChange.applySubscript())
1832         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
1833     else if (styleChange.applySuperscript())
1834         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
1835 
1836     if (m_styledInlineElement)
1837         surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
1838 }
1839 
computedFontSize(const Node * node)1840 float ApplyStyleCommand::computedFontSize(const Node *node)
1841 {
1842     if (!node)
1843         return 0;
1844 
1845     Position pos(const_cast<Node *>(node), 0);
1846     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
1847     if (!computedStyle)
1848         return 0;
1849 
1850     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(computedStyle->getPropertyCSSValue(CSSPropertyFontSize));
1851     if (!value)
1852         return 0;
1853 
1854     return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1855 }
1856 
joinChildTextNodes(Node * node,const Position & start,const Position & end)1857 void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, const Position &end)
1858 {
1859     if (!node)
1860         return;
1861 
1862     Position newStart = start;
1863     Position newEnd = end;
1864 
1865     Node *child = node->firstChild();
1866     while (child) {
1867         Node *next = child->nextSibling();
1868         if (child->isTextNode() && next && next->isTextNode()) {
1869             Text *childText = static_cast<Text *>(child);
1870             Text *nextText = static_cast<Text *>(next);
1871             if (next == start.node())
1872                 newStart = Position(childText, childText->length() + start.deprecatedEditingOffset());
1873             if (next == end.node())
1874                 newEnd = Position(childText, childText->length() + end.deprecatedEditingOffset());
1875             String textToMove = nextText->data();
1876             insertTextIntoNode(childText, childText->length(), textToMove);
1877             removeNode(next);
1878             // don't move child node pointer. it may want to merge with more text nodes.
1879         }
1880         else {
1881             child = child->nextSibling();
1882         }
1883     }
1884 
1885     updateStartEnd(newStart, newEnd);
1886 }
1887 
1888 }
1889