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