1/*
2 * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "EditingStyle.h"
29
30#include "ApplyStyleCommand.h"
31#include "CSSComputedStyleDeclaration.h"
32#include "CSSMutableStyleDeclaration.h"
33#include "CSSParser.h"
34#include "CSSStyleSelector.h"
35#include "CSSValueKeywords.h"
36#include "CSSValueList.h"
37#include "Frame.h"
38#include "HTMLFontElement.h"
39#include "HTMLNames.h"
40#include "Node.h"
41#include "Position.h"
42#include "RenderStyle.h"
43#include "SelectionController.h"
44#include "StyledElement.h"
45#include "htmlediting.h"
46
47namespace WebCore {
48
49// Editing style properties must be preserved during editing operation.
50// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
51// FIXME: The current editingStyleProperties contains all inheritableProperties but we may not need to preserve all inheritable properties
52static const int editingStyleProperties[] = {
53    // CSS inheritable properties
54    CSSPropertyBorderCollapse,
55    CSSPropertyColor,
56    CSSPropertyFontFamily,
57    CSSPropertyFontSize,
58    CSSPropertyFontStyle,
59    CSSPropertyFontVariant,
60    CSSPropertyFontWeight,
61    CSSPropertyLetterSpacing,
62    CSSPropertyLineHeight,
63    CSSPropertyOrphans,
64    CSSPropertyTextAlign,
65    CSSPropertyTextIndent,
66    CSSPropertyTextTransform,
67    CSSPropertyWhiteSpace,
68    CSSPropertyWidows,
69    CSSPropertyWordSpacing,
70    CSSPropertyWebkitBorderHorizontalSpacing,
71    CSSPropertyWebkitBorderVerticalSpacing,
72    CSSPropertyWebkitTextDecorationsInEffect,
73    CSSPropertyWebkitTextFillColor,
74    CSSPropertyWebkitTextSizeAdjust,
75    CSSPropertyWebkitTextStrokeColor,
76    CSSPropertyWebkitTextStrokeWidth,
77};
78size_t numEditingStyleProperties = WTF_ARRAY_LENGTH(editingStyleProperties);
79
80static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style)
81{
82    return style->copyPropertiesInSet(editingStyleProperties, numEditingStyleProperties);
83}
84
85static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style)
86{
87    if (!style)
88        return CSSMutableStyleDeclaration::create();
89    return copyEditingProperties(style.get());
90}
91
92static RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
93
94class HTMLElementEquivalent {
95public:
96    static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, int primitiveValue, const QualifiedName& tagName)
97    {
98        return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
99    }
100
101    virtual ~HTMLElementEquivalent() { }
102    virtual bool matches(Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
103    virtual bool hasAttribute() const { return false; }
104    bool propertyExistsInStyle(CSSStyleDeclaration* style) const { return style->getPropertyCSSValue(m_propertyID); }
105    virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
106    virtual void addToStyle(Element*, EditingStyle*) const;
107
108protected:
109    HTMLElementEquivalent(CSSPropertyID);
110    HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
111    HTMLElementEquivalent(CSSPropertyID, int primitiveValue, const QualifiedName& tagName);
112    const int m_propertyID;
113    const RefPtr<CSSPrimitiveValue> m_primitiveValue;
114    const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
115};
116
117HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
118    : m_propertyID(id)
119    , m_tagName(0)
120{
121}
122
123HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
124    : m_propertyID(id)
125    , m_tagName(&tagName)
126{
127}
128
129HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, int primitiveValue, const QualifiedName& tagName)
130    : m_propertyID(id)
131    , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
132    , m_tagName(&tagName)
133{
134    ASSERT(primitiveValue != CSSValueInvalid);
135}
136
137bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
138{
139    RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
140    return matches(element) && value && value->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == m_primitiveValue->getIdent();
141}
142
143void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
144{
145    style->setProperty(m_propertyID, m_primitiveValue->cssText());
146}
147
148class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
149public:
150    static PassOwnPtr<HTMLElementEquivalent> create(int primitiveValue, const QualifiedName& tagName)
151    {
152        return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
153    }
154    virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
155
156private:
157    HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName);
158};
159
160HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName)
161    : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
162{
163}
164
165bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
166{
167    RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
168    return matches(element) && styleValue && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(m_primitiveValue.get());
169}
170
171class HTMLAttributeEquivalent : public HTMLElementEquivalent {
172public:
173    static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName)
174    {
175        return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
176    }
177    static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
178    {
179        return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
180    }
181
182    bool matches(Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
183    virtual bool hasAttribute() const { return true; }
184    virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
185    virtual void addToStyle(Element*, EditingStyle*) const;
186    virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
187    inline const QualifiedName& attributeName() const { return m_attrName; }
188
189protected:
190    HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
191    HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
192    const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
193};
194
195HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
196    : HTMLElementEquivalent(id, tagName)
197    , m_attrName(attrName)
198{
199}
200
201HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
202    : HTMLElementEquivalent(id)
203    , m_attrName(attrName)
204{
205}
206
207bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
208{
209    RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
210    RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
211
212    // FIXME: This is very inefficient way of comparing values
213    // but we can't string compare attribute value and CSS property value.
214    return value && styleValue && value->cssText() == styleValue->cssText();
215}
216
217void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
218{
219    if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
220        style->setProperty(m_propertyID, value->cssText());
221}
222
223PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
224{
225    ASSERT(element);
226    if (!element->hasAttribute(m_attrName))
227        return 0;
228
229    RefPtr<CSSMutableStyleDeclaration> dummyStyle;
230    dummyStyle = CSSMutableStyleDeclaration::create();
231    dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName));
232    return dummyStyle->getPropertyCSSValue(m_propertyID);
233}
234
235class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent {
236public:
237    static PassOwnPtr<HTMLFontSizeEquivalent> create()
238    {
239        return adoptPtr(new HTMLFontSizeEquivalent());
240    }
241    virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
242
243private:
244    HTMLFontSizeEquivalent();
245};
246
247HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
248    : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
249{
250}
251
252PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
253{
254    ASSERT(element);
255    if (!element->hasAttribute(m_attrName))
256        return 0;
257    int size;
258    if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size))
259        return 0;
260    return CSSPrimitiveValue::createIdentifier(size);
261}
262
263float EditingStyle::NoFontDelta = 0.0f;
264
265EditingStyle::EditingStyle()
266    : m_shouldUseFixedDefaultFontSize(false)
267    , m_fontSizeDelta(NoFontDelta)
268{
269}
270
271EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
272    : m_shouldUseFixedDefaultFontSize(false)
273    , m_fontSizeDelta(NoFontDelta)
274{
275    init(node, propertiesToInclude);
276}
277
278EditingStyle::EditingStyle(const Position& position)
279    : m_shouldUseFixedDefaultFontSize(false)
280    , m_fontSizeDelta(NoFontDelta)
281{
282    init(position.deprecatedNode(), OnlyInheritableProperties);
283}
284
285EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
286    : m_mutableStyle(style->copy())
287    , m_shouldUseFixedDefaultFontSize(false)
288    , m_fontSizeDelta(NoFontDelta)
289{
290    extractFontSizeDelta();
291}
292
293EditingStyle::EditingStyle(int propertyID, const String& value)
294    : m_mutableStyle(0)
295    , m_shouldUseFixedDefaultFontSize(false)
296    , m_fontSizeDelta(NoFontDelta)
297{
298    setProperty(propertyID, value);
299}
300
301EditingStyle::~EditingStyle()
302{
303}
304
305void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
306{
307    if (isTabSpanTextNode(node))
308        node = tabSpanNode(node)->parentNode();
309    else if (isTabSpanNode(node))
310        node = node->parentNode();
311
312    RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node);
313    m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
314
315    if (node && node->computedStyle()) {
316        RenderStyle* renderStyle = node->computedStyle();
317        removeTextFillAndStrokeColorsIfNeeded(renderStyle);
318        replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
319    }
320
321    m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
322    extractFontSizeDelta();
323}
324
325void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
326{
327    // If a node's text fill color is invalid, then its children use
328    // their font-color as their text fill color (they don't
329    // inherit it).  Likewise for stroke color.
330    ExceptionCode ec = 0;
331    if (!renderStyle->textFillColor().isValid())
332        m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor, ec);
333    if (!renderStyle->textStrokeColor().isValid())
334        m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
335    ASSERT(!ec);
336}
337
338void EditingStyle::setProperty(int propertyID, const String& value, bool important)
339{
340    if (!m_mutableStyle)
341        m_mutableStyle = CSSMutableStyleDeclaration::create();
342
343    ExceptionCode ec;
344    m_mutableStyle->setProperty(propertyID, value, important, ec);
345}
346
347void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
348{
349    ASSERT(renderStyle);
350    if (renderStyle->fontDescription().keywordSize())
351        m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
352}
353
354void EditingStyle::extractFontSizeDelta()
355{
356    if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
357        // Explicit font size overrides any delta.
358        m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
359        return;
360    }
361
362    // Get the adjustment amount out of the style.
363    RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
364    if (!value || value->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE)
365        return;
366
367    CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
368
369    // Only PX handled now. If we handle more types in the future, perhaps
370    // a switch statement here would be more appropriate.
371    if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_PX)
372        return;
373
374    m_fontSizeDelta = primitiveValue->getFloatValue();
375    m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
376}
377
378bool EditingStyle::isEmpty() const
379{
380    return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
381}
382
383bool EditingStyle::textDirection(WritingDirection& writingDirection) const
384{
385    if (!m_mutableStyle)
386        return false;
387
388    RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
389    if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
390        return false;
391
392    int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
393    if (unicodeBidiValue == CSSValueEmbed) {
394        RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
395        if (!direction || !direction->isPrimitiveValue())
396            return false;
397
398        writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
399
400        return true;
401    }
402
403    if (unicodeBidiValue == CSSValueNormal) {
404        writingDirection = NaturalWritingDirection;
405        return true;
406    }
407
408    return false;
409}
410
411void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style)
412{
413    m_mutableStyle = style;
414    // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
415    // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
416    m_shouldUseFixedDefaultFontSize = false;
417    extractFontSizeDelta();
418}
419
420void EditingStyle::overrideWithStyle(const CSSMutableStyleDeclaration* style)
421{
422    if (!style || !style->length())
423        return;
424    if (!m_mutableStyle)
425        m_mutableStyle = CSSMutableStyleDeclaration::create();
426    m_mutableStyle->merge(style);
427    extractFontSizeDelta();
428}
429
430void EditingStyle::clear()
431{
432    m_mutableStyle.clear();
433    m_shouldUseFixedDefaultFontSize = false;
434    m_fontSizeDelta = NoFontDelta;
435}
436
437PassRefPtr<EditingStyle> EditingStyle::copy() const
438{
439    RefPtr<EditingStyle> copy = EditingStyle::create();
440    if (m_mutableStyle)
441        copy->m_mutableStyle = m_mutableStyle->copy();
442    copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
443    copy->m_fontSizeDelta = m_fontSizeDelta;
444    return copy;
445}
446
447PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
448{
449    RefPtr<EditingStyle> blockProperties = EditingStyle::create();
450    if (!m_mutableStyle)
451        return blockProperties;
452
453    blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
454    m_mutableStyle->removeBlockProperties();
455
456    return blockProperties;
457}
458
459PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
460{
461    RefPtr<EditingStyle> textDirection = EditingStyle::create();
462    textDirection->m_mutableStyle = CSSMutableStyleDeclaration::create();
463    textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->getPropertyPriority(CSSPropertyUnicodeBidi));
464    textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
465        m_mutableStyle->getPropertyPriority(CSSPropertyDirection));
466
467    m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
468    m_mutableStyle->removeProperty(CSSPropertyDirection);
469
470    return textDirection;
471}
472
473void EditingStyle::removeBlockProperties()
474{
475    if (!m_mutableStyle)
476        return;
477
478    m_mutableStyle->removeBlockProperties();
479}
480
481void EditingStyle::removeStyleAddedByNode(Node* node)
482{
483    if (!node || !node->parentNode())
484        return;
485    RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
486    RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
487    parentStyle->diff(nodeStyle.get());
488    nodeStyle->diff(m_mutableStyle.get());
489}
490
491void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
492{
493    if (!node || !node->parentNode() || !m_mutableStyle)
494        return;
495    RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
496    RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
497    parentStyle->diff(nodeStyle.get());
498
499    CSSMutableStyleDeclaration::const_iterator end = nodeStyle->end();
500    for (CSSMutableStyleDeclaration::const_iterator it = nodeStyle->begin(); it != end; ++it)
501        m_mutableStyle->removeProperty(it->id());
502}
503
504void EditingStyle::removeNonEditingProperties()
505{
506    if (m_mutableStyle)
507        m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
508}
509
510void EditingStyle::collapseTextDecorationProperties()
511{
512    if (!m_mutableStyle)
513        return;
514
515    RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
516    if (!textDecorationsInEffect)
517        return;
518
519    m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->getPropertyPriority(CSSPropertyTextDecoration));
520    m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
521}
522
523// CSS properties that create a visual difference only when applied to text.
524static const int textOnlyProperties[] = {
525    CSSPropertyTextDecoration,
526    CSSPropertyWebkitTextDecorationsInEffect,
527    CSSPropertyFontStyle,
528    CSSPropertyFontWeight,
529    CSSPropertyColor,
530};
531
532TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
533{
534    RefPtr<CSSMutableStyleDeclaration> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
535
536    if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
537        difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
538
539    if (!difference->length())
540        return TrueTriState;
541    if (difference->length() == m_mutableStyle->length())
542        return FalseTriState;
543
544    return MixedTriState;
545}
546
547bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
548{
549    ASSERT(element);
550    ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
551
552    CSSMutableStyleDeclaration* inlineStyle = element->inlineStyleDecl();
553    if (!m_mutableStyle || !inlineStyle)
554        return false;
555
556    if (!conflictingProperties) {
557        CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
558        for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
559            CSSPropertyID propertyID = static_cast<CSSPropertyID>(it->id());
560
561            // We don't override whitespace property of a tab span because that would collapse the tab into a space.
562            if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
563                continue;
564
565            if (inlineStyle->getPropertyCSSValue(propertyID))
566                return true;
567        }
568
569        return false;
570    }
571
572    CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
573    for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
574        CSSPropertyID propertyID = static_cast<CSSPropertyID>(it->id());
575        if ((propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element)) || !inlineStyle->getPropertyCSSValue(propertyID))
576            continue;
577
578        if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
579            if (extractedStyle)
580                extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->getPropertyPriority(propertyID));
581            conflictingProperties->append(CSSPropertyDirection);
582        }
583
584        conflictingProperties->append(propertyID);
585        if (extractedStyle)
586            extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->getPropertyPriority(propertyID));
587    }
588
589    return !conflictingProperties->isEmpty();
590}
591
592bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
593{
594    if (!m_mutableStyle)
595        return false;
596
597    static const HTMLElementEquivalent* HTMLEquivalents[] = {
598        HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag).leakPtr(),
599        HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag).leakPtr(),
600        HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag).leakPtr(),
601        HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag).leakPtr(),
602        HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag).leakPtr(),
603        HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag).leakPtr(),
604
605        HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag).leakPtr(),
606        HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag).leakPtr(),
607        HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag).leakPtr(),
608    };
609
610    for (size_t i = 0; i < WTF_ARRAY_LENGTH(HTMLEquivalents); ++i) {
611        const HTMLElementEquivalent* equivalent = HTMLEquivalents[i];
612        if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
613            && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
614            if (extractedStyle)
615                equivalent->addToStyle(element, extractedStyle);
616            return true;
617        }
618    }
619    return false;
620}
621
622static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
623{
624    DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
625
626    if (!HTMLAttributeEquivalents.size()) {
627        HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
628        HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
629        HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
630
631        HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
632        HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
633    }
634
635    return HTMLAttributeEquivalents;
636}
637
638bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
639{
640    ASSERT(element);
641    if (!m_mutableStyle)
642        return false;
643
644    const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
645    for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
646        if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
647            && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
648            return true;
649    }
650
651    return false;
652}
653
654bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
655    EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
656{
657    ASSERT(element);
658    // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
659    ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
660    if (!m_mutableStyle)
661        return false;
662
663    const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
664    bool removed = false;
665    for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
666        const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
667
668        // unicode-bidi and direction are pushed down separately so don't push down with other styles.
669        if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
670            continue;
671
672        if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
673            || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
674            continue;
675
676        if (extractedStyle)
677            equivalent->addToStyle(element, extractedStyle);
678        conflictingAttributes.append(equivalent->attributeName());
679        removed = true;
680    }
681
682    return removed;
683}
684
685bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
686{
687    return !m_mutableStyle || !getPropertiesNotIn(m_mutableStyle.get(), computedStyle(node).get())->length();
688}
689
690void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
691{
692    if (!m_mutableStyle)
693        return;
694
695    // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
696    // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
697    // which one of editingStyleAtPosition or computedStyle is called.
698    RefPtr<EditingStyle> style = EditingStyle::create(position);
699
700    RefPtr<CSSValue> unicodeBidi;
701    RefPtr<CSSValue> direction;
702    if (shouldPreserveWritingDirection == PreserveWritingDirection) {
703        unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
704        direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
705    }
706
707    style->m_mutableStyle->diff(m_mutableStyle.get());
708
709    // if alpha value is zero, we don't add the background color.
710    RefPtr<CSSValue> backgroundColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
711    if (backgroundColor && backgroundColor->isPrimitiveValue()
712        && !alphaChannel(static_cast<CSSPrimitiveValue*>(backgroundColor.get())->getRGBA32Value())) {
713        ExceptionCode ec;
714        m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec);
715    }
716
717    if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
718        m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
719        if (direction && direction->isPrimitiveValue())
720            m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
721    }
722}
723
724void EditingStyle::mergeTypingStyle(Document* document)
725{
726    ASSERT(document);
727
728    RefPtr<EditingStyle> typingStyle = document->frame()->selection()->typingStyle();
729    if (!typingStyle || typingStyle == this)
730        return;
731
732    mergeStyle(typingStyle->style());
733}
734
735void EditingStyle::mergeInlineStyleOfElement(StyledElement* element)
736{
737    ASSERT(element);
738    mergeStyle(element->inlineStyleDecl());
739}
740
741void EditingStyle::mergeStyle(CSSMutableStyleDeclaration* style)
742{
743    if (!style)
744        return;
745
746    if (!m_mutableStyle) {
747        m_mutableStyle = style->copy();
748        return;
749    }
750
751    CSSMutableStyleDeclaration::const_iterator end = style->end();
752    for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
753        RefPtr<CSSValue> value;
754        if ((it->id() == CSSPropertyTextDecoration || it->id() == CSSPropertyWebkitTextDecorationsInEffect) && it->value()->isValueList()) {
755            value = m_mutableStyle->getPropertyCSSValue(it->id());
756            if (value && !value->isValueList())
757                value = 0;
758        }
759
760        if (!value) {
761            ExceptionCode ec;
762            m_mutableStyle->setProperty(it->id(), it->value()->cssText(), it->isImportant(), ec);
763            continue;
764        }
765
766        CSSValueList* newTextDecorations = static_cast<CSSValueList*>(it->value());
767        CSSValueList* textDecorations = static_cast<CSSValueList*>(value.get());
768
769        DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
770        DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
771
772        if (newTextDecorations->hasValue(underline.get()) && !textDecorations->hasValue(underline.get()))
773            textDecorations->append(underline.get());
774
775        if (newTextDecorations->hasValue(lineThrough.get()) && !textDecorations->hasValue(lineThrough.get()))
776            textDecorations->append(lineThrough.get());
777    }
778}
779
780static void reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style)
781{
782    RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
783    RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
784    // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
785    ASSERT(!textDecorationsInEffect || !textDecoration);
786    if (textDecorationsInEffect) {
787        style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
788        style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
789        textDecoration = textDecorationsInEffect;
790    }
791
792    // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
793    if (textDecoration && !textDecoration->isValueList())
794        style->removeProperty(CSSPropertyTextDecoration);
795}
796
797StyleChange::StyleChange(EditingStyle* style, const Position& position)
798    : m_applyBold(false)
799    , m_applyItalic(false)
800    , m_applyUnderline(false)
801    , m_applyLineThrough(false)
802    , m_applySubscript(false)
803    , m_applySuperscript(false)
804{
805    Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
806    if (!style || !style->style() || !document || !document->frame())
807        return;
808
809    RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
810    RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
811
812    reconcileTextDecorationProperties(mutableStyle.get());
813    if (!document->frame()->editor()->shouldStyleWithCSS())
814        extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
815
816    // Changing the whitespace style in a tab span would collapse the tab into a space.
817    if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
818        mutableStyle->removeProperty(CSSPropertyWhiteSpace);
819
820    // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
821    // FIXME: Shouldn't this be done in getPropertiesNotIn?
822    if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
823        mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
824
825    // Save the result for later
826    m_cssStyle = mutableStyle->cssText().stripWhiteSpace();
827}
828
829static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID)
830{
831    if (newTextDecoration->length())
832        style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID));
833    else {
834        // text-decoration: none is redundant since it does not remove any text decorations.
835        ASSERT(!style->getPropertyPriority(propertyID));
836        style->removeProperty(propertyID);
837    }
838}
839
840static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
841{
842    RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor);
843    if (!colorValue || !colorValue->isPrimitiveValue())
844        return Color::transparent;
845
846    CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get());
847    if (primitiveColor->primitiveType() == CSSPrimitiveValue::CSS_RGBCOLOR)
848        return primitiveColor->getRGBA32Value();
849
850    // Need to take care of named color such as green and black
851    // This code should be removed after https://bugs.webkit.org/show_bug.cgi?id=28282 is fixed.
852    RGBA32 rgba = 0;
853    CSSParser::parseColor(rgba, colorValue->cssText());
854    return rgba;
855}
856
857void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefaultSize)
858{
859    ASSERT(style);
860
861    if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
862        style->removeProperty(CSSPropertyFontWeight);
863        m_applyBold = true;
864    }
865
866    int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
867    if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
868        style->removeProperty(CSSPropertyFontStyle);
869        m_applyItalic = true;
870    }
871
872    // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
873    // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
874    RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
875    if (textDecoration && textDecoration->isValueList()) {
876        DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
877        DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
878
879        RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
880        if (newTextDecoration->removeAll(underline.get()))
881            m_applyUnderline = true;
882        if (newTextDecoration->removeAll(lineThrough.get()))
883            m_applyLineThrough = true;
884
885        // If trimTextDecorations, delete underline and line-through
886        setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
887    }
888
889    int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
890    switch (verticalAlign) {
891    case CSSValueSub:
892        style->removeProperty(CSSPropertyVerticalAlign);
893        m_applySubscript = true;
894        break;
895    case CSSValueSuper:
896        style->removeProperty(CSSPropertyVerticalAlign);
897        m_applySuperscript = true;
898        break;
899    }
900
901    if (style->getPropertyCSSValue(CSSPropertyColor)) {
902        m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
903        style->removeProperty(CSSPropertyColor);
904    }
905
906    m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
907    style->removeProperty(CSSPropertyFontFamily);
908
909    if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
910        if (!fontSize->isPrimitiveValue())
911            style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
912        else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
913                shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
914            m_applyFontSize = String::number(legacyFontSize);
915            style->removeProperty(CSSPropertyFontSize);
916        }
917    }
918}
919
920static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration)
921{
922    RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
923    if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
924        return;
925
926    RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
927    CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
928
929    for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
930        newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
931
932    setTextDecorationProperty(style, newTextDecoration.get(), propertID);
933}
934
935static bool fontWeightIsBold(CSSStyleDeclaration* style)
936{
937    ASSERT(style);
938    RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
939
940    if (!fontWeight)
941        return false;
942    if (!fontWeight->isPrimitiveValue())
943        return false;
944
945    // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
946    // Collapse all other values to either one of these two states for editing purposes.
947    switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) {
948        case CSSValue100:
949        case CSSValue200:
950        case CSSValue300:
951        case CSSValue400:
952        case CSSValue500:
953        case CSSValueNormal:
954            return false;
955        case CSSValueBold:
956        case CSSValue600:
957        case CSSValue700:
958        case CSSValue800:
959        case CSSValue900:
960            return true;
961    }
962
963    ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
964    return false; // Make compiler happy
965}
966
967static int getTextAlignment(CSSStyleDeclaration* style)
968{
969    int textAlign = getIdentifierValue(style, CSSPropertyTextAlign);
970    switch (textAlign) {
971    case CSSValueCenter:
972    case CSSValueWebkitCenter:
973        return CSSValueCenter;
974    case CSSValueJustify:
975        return CSSValueJustify;
976    case CSSValueLeft:
977    case CSSValueWebkitLeft:
978        return CSSValueLeft;
979    case CSSValueRight:
980    case CSSValueWebkitRight:
981        return CSSValueRight;
982    }
983    return CSSValueInvalid;
984}
985
986RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
987{
988    ASSERT(styleWithRedundantProperties);
989    ASSERT(baseStyle);
990    RefPtr<CSSMutableStyleDeclaration> result = styleWithRedundantProperties->copy();
991    baseStyle->diff(result.get());
992
993    RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
994    diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
995    diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
996
997    if (fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
998        result->removeProperty(CSSPropertyFontWeight);
999
1000    if (getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1001        result->removeProperty(CSSPropertyColor);
1002
1003    if (getTextAlignment(result.get()) == getTextAlignment(baseStyle))
1004        result->removeProperty(CSSPropertyTextAlign);
1005
1006    return result;
1007}
1008
1009
1010int getIdentifierValue(CSSStyleDeclaration* style, int propertyID)
1011{
1012    if (!style)
1013        return 0;
1014
1015    RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1016    if (!value || !value->isPrimitiveValue())
1017        return 0;
1018
1019    return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1020}
1021
1022static bool isCSSValueLength(CSSPrimitiveValue* value)
1023{
1024    return value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC;
1025}
1026
1027int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1028{
1029    if (isCSSValueLength(value)) {
1030        int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1031        int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1032        // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1033        int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1034        if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1035            return legacyFontSize;
1036
1037        return 0;
1038    }
1039
1040    if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
1041        return value->getIdent() - CSSValueXSmall + 1;
1042
1043    return 0;
1044}
1045
1046}
1047