2 * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc.
3 * Copyright (C) 2010, 2011 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "EditingStyle.h"
30 #include "ApplyStyleCommand.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "CSSRuleList.h"
34 #include "CSSStyleRule.h"
35 #include "CSSValueKeywords.h"
36 #include "CSSValueList.h"
38 #include "FrameSelection.h"
39 #include "HTMLFontElement.h"
40 #include "HTMLInterchange.h"
41 #include "HTMLNames.h"
44 #include "QualifiedName.h"
45 #include "RenderStyle.h"
46 #include "StylePropertySet.h"
47 #include "StyleResolver.h"
48 #include "StyleRule.h"
49 #include "StyledElement.h"
50 #include "htmlediting.h"
51 #include "visible_units.h"
52 #include <wtf/HashSet.h>
56 // Editing style properties must be preserved during editing operation.
57 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
58 static const CSSPropertyID editingProperties[] = {
59 CSSPropertyBackgroundColor,
60 CSSPropertyTextDecoration,
62 // CSS inheritable properties
64 CSSPropertyFontFamily,
67 CSSPropertyFontVariant,
68 CSSPropertyFontWeight,
69 CSSPropertyLetterSpacing,
70 CSSPropertyLineHeight,
73 CSSPropertyTextIndent,
74 CSSPropertyTextTransform,
75 CSSPropertyWhiteSpace,
77 CSSPropertyWordSpacing,
78 CSSPropertyWebkitTextDecorationsInEffect,
79 CSSPropertyWebkitTextFillColor,
80 CSSPropertyWebkitTextSizeAdjust,
81 CSSPropertyWebkitTextStrokeColor,
82 CSSPropertyWebkitTextStrokeWidth,
85 enum EditingPropertiesType { OnlyInheritableEditingProperties, AllEditingProperties };
87 template <class StyleDeclarationType>
88 static PassRefPtr<StylePropertySet> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesType type = OnlyInheritableEditingProperties)
90 if (type == AllEditingProperties)
91 return style->copyPropertiesInSet(editingProperties, WTF_ARRAY_LENGTH(editingProperties));
92 return style->copyPropertiesInSet(editingProperties + 2, WTF_ARRAY_LENGTH(editingProperties) - 2);
95 static inline bool isEditingProperty(int id)
97 for (size_t i = 0; i < WTF_ARRAY_LENGTH(editingProperties); ++i) {
98 if (editingProperties[i] == id)
104 static PassRefPtr<StylePropertySet> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style, EditingPropertiesType type = OnlyInheritableEditingProperties)
107 return StylePropertySet::create();
108 return copyEditingProperties(style.get(), type);
111 static PassRefPtr<StylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
112 enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
113 static int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
114 static bool isTransparentColorValue(CSSValue*);
115 static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
116 static bool hasTransparentBackgroundColor(StylePropertySet*);
117 static PassRefPtr<CSSValue> backgroundColorInEffect(Node*);
119 class HTMLElementEquivalent {
121 static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, int primitiveValue, const QualifiedName& tagName)
123 return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
126 virtual ~HTMLElementEquivalent() { }
127 virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
128 virtual bool hasAttribute() const { return false; }
129 virtual bool propertyExistsInStyle(const StylePropertySet* style) const { return style && style->getPropertyCSSValue(m_propertyID); }
130 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
131 virtual void addToStyle(Element*, EditingStyle*) const;
134 HTMLElementEquivalent(CSSPropertyID);
135 HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
136 HTMLElementEquivalent(CSSPropertyID, int primitiveValue, const QualifiedName& tagName);
137 const CSSPropertyID m_propertyID;
138 const RefPtr<CSSPrimitiveValue> m_primitiveValue;
139 const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
142 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
148 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
150 , m_tagName(&tagName)
154 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, int primitiveValue, const QualifiedName& tagName)
156 , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
157 , m_tagName(&tagName)
159 ASSERT(primitiveValue != CSSValueInvalid);
162 bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
164 RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
165 return matches(element) && value && value->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == m_primitiveValue->getIdent();
168 void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
170 style->setProperty(m_propertyID, m_primitiveValue->cssText());
173 class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
175 static PassOwnPtr<HTMLElementEquivalent> create(int primitiveValue, const QualifiedName& tagName)
177 return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
179 virtual bool propertyExistsInStyle(const StylePropertySet*) const;
180 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
183 HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName);
186 HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName)
187 : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
188 // m_propertyID is used in HTMLElementEquivalent::addToStyle
192 bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StylePropertySet* style) const
194 return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) || style->getPropertyCSSValue(CSSPropertyTextDecoration);
197 bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
199 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
201 styleValue = style->getPropertyCSSValue(CSSPropertyTextDecoration);
202 return matches(element) && styleValue && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(m_primitiveValue.get());
205 class HTMLAttributeEquivalent : public HTMLElementEquivalent {
207 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName)
209 return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
211 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
213 return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
216 bool matches(const Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
217 virtual bool hasAttribute() const { return true; }
218 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
219 virtual void addToStyle(Element*, EditingStyle*) const;
220 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
221 inline const QualifiedName& attributeName() const { return m_attrName; }
224 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
225 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
226 const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
229 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
230 : HTMLElementEquivalent(id, tagName)
231 , m_attrName(attrName)
235 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
236 : HTMLElementEquivalent(id)
237 , m_attrName(attrName)
241 bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
243 RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
244 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
246 // FIXME: This is very inefficient way of comparing values
247 // but we can't string compare attribute value and CSS property value.
248 return value && styleValue && value->cssText() == styleValue->cssText();
251 void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
253 if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
254 style->setProperty(m_propertyID, value->cssText());
257 PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
260 if (!element->hasAttribute(m_attrName))
263 RefPtr<StylePropertySet> dummyStyle;
264 dummyStyle = StylePropertySet::create();
265 dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName));
266 return dummyStyle->getPropertyCSSValue(m_propertyID);
269 class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent {
271 static PassOwnPtr<HTMLFontSizeEquivalent> create()
273 return adoptPtr(new HTMLFontSizeEquivalent());
275 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
278 HTMLFontSizeEquivalent();
281 HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
282 : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
286 PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
289 if (!element->hasAttribute(m_attrName))
292 if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size))
294 return CSSPrimitiveValue::createIdentifier(size);
297 float EditingStyle::NoFontDelta = 0.0f;
299 EditingStyle::EditingStyle()
300 : m_shouldUseFixedDefaultFontSize(false)
301 , m_fontSizeDelta(NoFontDelta)
305 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
306 : m_shouldUseFixedDefaultFontSize(false)
307 , m_fontSizeDelta(NoFontDelta)
309 init(node, propertiesToInclude);
312 EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
313 : m_shouldUseFixedDefaultFontSize(false)
314 , m_fontSizeDelta(NoFontDelta)
316 init(position.deprecatedNode(), propertiesToInclude);
319 EditingStyle::EditingStyle(const StylePropertySet* style)
320 : m_mutableStyle(style ? style->copy() : 0)
321 , m_shouldUseFixedDefaultFontSize(false)
322 , m_fontSizeDelta(NoFontDelta)
324 extractFontSizeDelta();
327 EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
328 : m_mutableStyle(style ? style->copy() : 0)
329 , m_shouldUseFixedDefaultFontSize(false)
330 , m_fontSizeDelta(NoFontDelta)
332 extractFontSizeDelta();
335 EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
337 , m_shouldUseFixedDefaultFontSize(false)
338 , m_fontSizeDelta(NoFontDelta)
340 setProperty(propertyID, value);
343 EditingStyle::~EditingStyle()
347 static RGBA32 cssValueToRGBA(CSSValue* colorValue)
349 if (!colorValue || !colorValue->isPrimitiveValue())
350 return Color::transparent;
352 CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue);
353 if (primitiveColor->isRGBColor())
354 return primitiveColor->getRGBA32Value();
357 CSSParser::parseColor(rgba, colorValue->cssText());
361 static inline RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
363 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyColor).get());
366 static inline RGBA32 getRGBAFontColor(StylePropertySet* style)
368 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyColor).get());
371 static inline RGBA32 rgbaBackgroundColorInEffect(Node* node)
373 return cssValueToRGBA(backgroundColorInEffect(node).get());
376 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
378 if (isTabSpanTextNode(node))
379 node = tabSpanNode(node)->parentNode();
380 else if (isTabSpanNode(node))
381 node = node->parentNode();
383 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node);
384 m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
386 if (propertiesToInclude == EditingPropertiesInEffect) {
387 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
388 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
389 if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
390 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
393 if (node && node->computedStyle()) {
394 RenderStyle* renderStyle = node->computedStyle();
395 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
396 replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
399 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
400 extractFontSizeDelta();
403 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
405 // If a node's text fill color is invalid, then its children use
406 // their font-color as their text fill color (they don't
407 // inherit it). Likewise for stroke color.
408 if (!renderStyle->textFillColor().isValid())
409 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
410 if (!renderStyle->textStrokeColor().isValid())
411 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
414 void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important)
417 m_mutableStyle = StylePropertySet::create();
419 m_mutableStyle->setProperty(propertyID, value, important);
422 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
425 if (renderStyle->fontDescription().keywordSize())
426 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
429 void EditingStyle::extractFontSizeDelta()
434 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
435 // Explicit font size overrides any delta.
436 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
440 // Get the adjustment amount out of the style.
441 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
442 if (!value || !value->isPrimitiveValue())
445 CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
447 // Only PX handled now. If we handle more types in the future, perhaps
448 // a switch statement here would be more appropriate.
449 if (!primitiveValue->isPx())
452 m_fontSizeDelta = primitiveValue->getFloatValue();
453 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
456 bool EditingStyle::isEmpty() const
458 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
461 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
466 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
467 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
470 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
471 if (unicodeBidiValue == CSSValueEmbed) {
472 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
473 if (!direction || !direction->isPrimitiveValue())
476 writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
481 if (unicodeBidiValue == CSSValueNormal) {
482 writingDirection = NaturalWritingDirection;
489 void EditingStyle::setStyle(PassRefPtr<StylePropertySet> style)
491 m_mutableStyle = style;
492 // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
493 // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
494 m_shouldUseFixedDefaultFontSize = false;
495 extractFontSizeDelta();
498 void EditingStyle::overrideWithStyle(const StylePropertySet* style)
500 if (!style || style->isEmpty())
503 m_mutableStyle = StylePropertySet::create();
504 m_mutableStyle->merge(style);
505 extractFontSizeDelta();
508 void EditingStyle::clear()
510 m_mutableStyle.clear();
511 m_shouldUseFixedDefaultFontSize = false;
512 m_fontSizeDelta = NoFontDelta;
515 PassRefPtr<EditingStyle> EditingStyle::copy() const
517 RefPtr<EditingStyle> copy = EditingStyle::create();
519 copy->m_mutableStyle = m_mutableStyle->copy();
520 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
521 copy->m_fontSizeDelta = m_fontSizeDelta;
525 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
527 RefPtr<EditingStyle> blockProperties = EditingStyle::create();
529 return blockProperties;
531 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
532 m_mutableStyle->removeBlockProperties();
534 return blockProperties;
537 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
539 RefPtr<EditingStyle> textDirection = EditingStyle::create();
540 textDirection->m_mutableStyle = StylePropertySet::create();
541 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
542 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
543 m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
545 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
546 m_mutableStyle->removeProperty(CSSPropertyDirection);
548 return textDirection;
551 void EditingStyle::removeBlockProperties()
556 m_mutableStyle->removeBlockProperties();
559 void EditingStyle::removeStyleAddedByNode(Node* node)
561 if (!node || !node->parentNode())
563 RefPtr<StylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
564 RefPtr<StylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
565 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
566 m_mutableStyle->removeEquivalentProperties(nodeStyle->ensureCSSStyleDeclaration());
569 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
571 if (!node || !node->parentNode() || !m_mutableStyle)
574 RefPtr<StylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
575 RefPtr<StylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
576 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
578 unsigned propertyCount = nodeStyle->propertyCount();
579 for (unsigned i = 0; i < propertyCount; ++i)
580 m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id());
583 void EditingStyle::removeNonEditingProperties()
586 m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
589 void EditingStyle::collapseTextDecorationProperties()
594 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
595 if (!textDecorationsInEffect)
598 if (textDecorationsInEffect->isValueList())
599 m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->propertyIsImportant(CSSPropertyTextDecoration));
601 m_mutableStyle->removeProperty(CSSPropertyTextDecoration);
602 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
605 // CSS properties that create a visual difference only when applied to text.
606 static const CSSPropertyID textOnlyProperties[] = {
607 CSSPropertyTextDecoration,
608 CSSPropertyWebkitTextDecorationsInEffect,
609 CSSPropertyFontStyle,
610 CSSPropertyFontWeight,
614 TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
616 if (!style || !style->m_mutableStyle)
617 return FalseTriState;
618 return triStateOfStyle(style->m_mutableStyle->ensureCSSStyleDeclaration(), DoNotIgnoreTextOnlyProperties);
621 TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
623 RefPtr<StylePropertySet> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
625 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
626 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
628 if (difference->isEmpty())
630 if (difference->propertyCount() == m_mutableStyle->propertyCount())
631 return FalseTriState;
633 return MixedTriState;
636 TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
638 if (!selection.isCaretOrRange())
639 return FalseTriState;
641 if (selection.isCaret())
642 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
644 TriState state = FalseTriState;
645 for (Node* node = selection.start().deprecatedNode(); node; node = node->traverseNextNode()) {
646 RefPtr<CSSComputedStyleDeclaration> nodeStyle = CSSComputedStyleDeclaration::create(node);
648 TriState nodeState = triStateOfStyle(nodeStyle.get(), node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
649 if (node == selection.start().deprecatedNode())
651 else if (state != nodeState && node->isTextNode()) {
652 state = MixedTriState;
656 if (node == selection.end().deprecatedNode())
663 bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
666 ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
668 const StylePropertySet* inlineStyle = element->inlineStyle();
669 if (!m_mutableStyle || !inlineStyle)
672 unsigned propertyCount = m_mutableStyle->propertyCount();
673 for (unsigned i = 0; i < propertyCount; ++i) {
674 CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
676 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
677 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
680 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) {
681 if (!conflictingProperties)
683 conflictingProperties->append(CSSPropertyTextDecoration);
685 extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->propertyIsImportant(CSSPropertyTextDecoration));
689 if (!inlineStyle->getPropertyCSSValue(propertyID))
692 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
693 if (!conflictingProperties)
695 conflictingProperties->append(CSSPropertyDirection);
697 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
700 if (!conflictingProperties)
703 conflictingProperties->append(propertyID);
706 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
709 return conflictingProperties && !conflictingProperties->isEmpty();
712 static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
714 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
716 if (!HTMLElementEquivalents.size()) {
717 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
718 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
719 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
720 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
721 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
722 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
724 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag));
725 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag));
726 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag));
729 return HTMLElementEquivalents;
733 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
738 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
739 for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
740 const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
741 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
742 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
744 equivalent->addToStyle(element, extractedStyle);
751 static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
753 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
755 if (!HTMLAttributeEquivalents.size()) {
756 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
757 // of exactly one element except dirAttr.
758 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
759 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
760 HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
762 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
763 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
766 return HTMLAttributeEquivalents;
769 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
775 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
776 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
777 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
778 && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
785 bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
786 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
789 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
790 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
794 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
795 bool removed = false;
796 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
797 const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
799 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
800 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
803 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
804 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
808 equivalent->addToStyle(element, extractedStyle);
809 conflictingAttributes.append(equivalent->attributeName());
816 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
818 return !m_mutableStyle || getPropertiesNotIn(m_mutableStyle.get(), CSSComputedStyleDeclaration::create(node).get())->isEmpty();
821 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
823 bool elementIsSpanOrElementEquivalent = false;
824 if (element->hasTagName(HTMLNames::spanTag))
825 elementIsSpanOrElementEquivalent = true;
827 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
829 for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
830 if (HTMLElementEquivalents[i]->matches(element)) {
831 elementIsSpanOrElementEquivalent = true;
837 if (!element->hasAttributes())
838 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
840 unsigned matchedAttributes = 0;
841 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
842 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
843 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
847 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
848 return false; // element is not a span, a html element equivalent, or font element.
850 if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
853 if (element->hasAttribute(HTMLNames::styleAttr)) {
854 if (const StylePropertySet* style = element->inlineStyle()) {
855 unsigned propertyCount = style->propertyCount();
856 for (unsigned i = 0; i < propertyCount; ++i) {
857 if (!isEditingProperty(style->propertyAt(i).id()))
864 // font with color attribute, span with style attribute, etc...
865 ASSERT(matchedAttributes <= element->attributeCount());
866 return matchedAttributes >= element->attributeCount();
869 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
874 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
875 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
876 // which one of editingStyleAtPosition or computedStyle is called.
877 RefPtr<EditingStyle> style = EditingStyle::create(position, EditingPropertiesInEffect);
879 RefPtr<CSSValue> unicodeBidi;
880 RefPtr<CSSValue> direction;
881 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
882 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
883 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
886 m_mutableStyle->removeEquivalentProperties(style->m_mutableStyle.get());
888 if (getRGBAFontColor(m_mutableStyle.get()) == getRGBAFontColor(style->m_mutableStyle.get()))
889 m_mutableStyle->removeProperty(CSSPropertyColor);
891 if (hasTransparentBackgroundColor(m_mutableStyle.get())
892 || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
893 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
895 if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
896 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
897 if (direction && direction->isPrimitiveValue())
898 m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
902 void EditingStyle::mergeTypingStyle(Document* document)
906 RefPtr<EditingStyle> typingStyle = document->frame()->selection()->typingStyle();
907 if (!typingStyle || typingStyle == this)
910 mergeStyle(typingStyle->style(), OverrideValues);
913 void EditingStyle::mergeInlineStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
916 if (!element->inlineStyle())
919 switch (propertiesToInclude) {
921 mergeStyle(element->inlineStyle(), mode);
923 case OnlyEditingInheritableProperties:
924 mergeStyle(copyEditingProperties(element->inlineStyle(), OnlyInheritableEditingProperties).get(), mode);
926 case EditingPropertiesInEffect:
927 mergeStyle(copyEditingProperties(element->inlineStyle(), AllEditingProperties).get(), mode);
932 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const StyledElement* element,
933 EditingStyle::CSSPropertyOverrideMode mode, StylePropertySet* style)
935 return equivalent->matches(element) && !equivalent->propertyExistsInStyle(element->inlineStyle())
936 && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
939 void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
941 mergeInlineStyleOfElement(element, mode, propertiesToInclude);
943 const Vector<OwnPtr<HTMLElementEquivalent> >& elementEquivalents = htmlElementEquivalents();
944 for (size_t i = 0; i < elementEquivalents.size(); ++i) {
945 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
946 elementEquivalents[i]->addToStyle(element, this);
949 const Vector<OwnPtr<HTMLAttributeEquivalent> >& attributeEquivalents = htmlAttributeEquivalents();
950 for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
951 if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
952 continue; // We don't want to include directionality
953 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
954 attributeEquivalents[i]->addToStyle(element, this);
958 PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate)
960 RefPtr<EditingStyle> wrappingStyle;
961 if (shouldAnnotate) {
962 wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
964 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
965 // to help us differentiate those styles from ones that the user has applied.
966 // This helps us get the color of content pasted into blockquotes right.
967 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(context), isMailBlockquote, CanCrossEditingBoundary));
969 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
970 wrappingStyle->collapseTextDecorationProperties();
972 return wrappingStyle.release();
975 wrappingStyle = EditingStyle::create();
977 // When not annotating for interchange, we only preserve inline style declarations.
978 for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) {
979 if (node->isStyledElement()) {
980 wrappingStyle->mergeInlineAndImplicitStyleOfElement(static_cast<StyledElement*>(node), EditingStyle::DoNotOverrideValues,
981 EditingStyle::EditingPropertiesInEffect);
985 return wrappingStyle.release();
989 static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
991 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
992 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
994 if (valueToMerge->hasValue(underline.get()) && !mergedValue->hasValue(underline.get()))
995 mergedValue->append(underline.get());
997 if (valueToMerge->hasValue(lineThrough.get()) && !mergedValue->hasValue(lineThrough.get()))
998 mergedValue->append(lineThrough.get());
1001 void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode)
1006 if (!m_mutableStyle) {
1007 m_mutableStyle = style->copy();
1011 unsigned propertyCount = style->propertyCount();
1012 for (unsigned i = 0; i < propertyCount; ++i) {
1013 const CSSProperty& property = style->propertyAt(i);
1014 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
1016 // text decorations never override values
1017 if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
1018 if (value->isValueList()) {
1019 mergeTextDecorationValues(static_cast<CSSValueList*>(value.get()), static_cast<CSSValueList*>(property.value()));
1022 value = 0; // text-decoration: none is equivalent to not having the property
1025 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1026 m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant());
1030 static PassRefPtr<StylePropertySet> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude)
1032 RefPtr<StylePropertySet> style = StylePropertySet::create();
1033 RefPtr<CSSRuleList> matchedRules = element->document()->styleResolver()->styleRulesForElement(element, rulesToInclude);
1035 for (unsigned i = 0; i < matchedRules->length(); i++) {
1036 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE)
1037 style->merge(static_cast<CSSStyleRule*>(matchedRules->item(i))->styleRule()->properties(), true);
1041 return style.release();
1044 void EditingStyle::mergeStyleFromRules(StyledElement* element)
1046 RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1047 StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules);
1048 // Styles from the inline style declaration, held in the variable "style", take precedence
1049 // over those from matched rules.
1051 styleFromMatchedRules->merge(m_mutableStyle.get());
1054 m_mutableStyle = styleFromMatchedRules;
1057 void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement* element)
1059 mergeStyleFromRules(element);
1061 // The property value, if it's a percentage, may not reflect the actual computed value.
1062 // For example: style="height: 1%; overflow: visible;" in quirksmode
1063 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1064 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = CSSComputedStyleDeclaration::create(element);
1065 RefPtr<StylePropertySet> fromComputedStyle = StylePropertySet::create();
1067 unsigned propertyCount = m_mutableStyle->propertyCount();
1068 for (unsigned i = 0; i < propertyCount; ++i) {
1069 const CSSProperty& property = m_mutableStyle->propertyAt(i);
1070 CSSValue* value = property.value();
1071 if (!value->isPrimitiveValue())
1073 if (static_cast<CSSPrimitiveValue*>(value)->isPercentage()) {
1074 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
1075 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
1079 m_mutableStyle->merge(fromComputedStyle.get());
1082 static void removePropertiesInStyle(StylePropertySet* styleToRemovePropertiesFrom, StylePropertySet* style)
1084 unsigned propertyCount = style->propertyCount();
1085 Vector<CSSPropertyID> propertiesToRemove(propertyCount);
1086 for (unsigned i = 0; i < propertyCount; ++i)
1087 propertiesToRemove[i] = style->propertyAt(i).id();
1089 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1092 void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* context)
1095 if (!m_mutableStyle)
1098 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1099 RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules);
1100 if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
1101 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules->ensureCSSStyleDeclaration());
1103 // 2. Remove style present in context and not overriden by matched rules.
1104 RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1105 if (computedStyle->m_mutableStyle) {
1106 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1107 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1109 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1110 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle->ensureCSSStyleDeclaration());
1113 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules.
1114 // These rules are added by serialization code to wrap text nodes.
1115 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1116 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline)
1117 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1118 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone)
1119 m_mutableStyle->removeProperty(CSSPropertyFloat);
1123 void EditingStyle::removePropertiesInElementDefaultStyle(Element* element)
1125 if (!m_mutableStyle || m_mutableStyle->isEmpty())
1128 RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules);
1130 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1133 void EditingStyle::forceInline()
1135 if (!m_mutableStyle)
1136 m_mutableStyle = StylePropertySet::create();
1137 const bool propertyIsImportant = true;
1138 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1141 int EditingStyle::legacyFontSize(Document* document) const
1143 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1144 if (!cssValue || !cssValue->isPrimitiveValue())
1146 return legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(cssValue.get()),
1147 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1150 PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1152 if (selection.isNone())
1155 Position position = adjustedSelectionStartForStyleComputation(selection);
1157 // If the pos is at the end of a text node, then this node is not fully selected.
1158 // Move it to the next deep equivalent position to avoid removing the style from this node.
1159 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1160 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1161 Node* positionNode = position.containerNode();
1162 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1163 position = nextVisuallyDistinctCandidate(position);
1165 Element* element = position.element();
1169 RefPtr<EditingStyle> style = EditingStyle::create(element, EditingStyle::AllProperties);
1170 style->mergeTypingStyle(element->document());
1172 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1173 // Also, if the selection is a range, ignore the background color at the start of selection,
1174 // and find the background color of the common ancestor.
1175 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1176 RefPtr<Range> range(selection.toNormalizedRange());
1177 ExceptionCode ec = 0;
1178 if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(ec)))
1179 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1185 WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& selection, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings)
1187 hasNestedOrMultipleEmbeddings = true;
1189 if (selection.isNone())
1190 return NaturalWritingDirection;
1192 Position position = selection.start().downstream();
1194 Node* node = position.deprecatedNode();
1196 return NaturalWritingDirection;
1199 if (selection.isRange()) {
1200 end = selection.end().upstream();
1202 Node* pastLast = Range::create(end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode();
1203 for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) {
1204 if (!n->isStyledElement())
1207 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(n);
1208 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1209 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1212 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
1213 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
1214 return NaturalWritingDirection;
1218 if (selection.isCaret()) {
1219 WritingDirection direction;
1220 if (typingStyle && typingStyle->textDirection(direction)) {
1221 hasNestedOrMultipleEmbeddings = false;
1224 node = selection.visibleStart().deepEquivalent().deprecatedNode();
1227 // The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position
1229 Node* block = enclosingBlock(node);
1230 WritingDirection foundDirection = NaturalWritingDirection;
1232 for (; node != block; node = node->parentNode()) {
1233 if (!node->isStyledElement())
1236 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
1237 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1238 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1241 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
1242 if (unicodeBidiValue == CSSValueNormal)
1245 if (unicodeBidiValue == CSSValueBidiOverride)
1246 return NaturalWritingDirection;
1248 ASSERT(unicodeBidiValue == CSSValueEmbed);
1249 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
1250 if (!direction || !direction->isPrimitiveValue())
1253 int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent();
1254 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
1257 if (foundDirection != NaturalWritingDirection)
1258 return NaturalWritingDirection;
1260 // In the range case, make sure that the embedding element persists until the end of the range.
1261 if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(node))
1262 return NaturalWritingDirection;
1264 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
1266 hasNestedOrMultipleEmbeddings = false;
1267 return foundDirection;
1270 static void reconcileTextDecorationProperties(StylePropertySet* style)
1272 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1273 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1274 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1275 ASSERT(!textDecorationsInEffect || !textDecoration);
1276 if (textDecorationsInEffect) {
1277 style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
1278 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1279 textDecoration = textDecorationsInEffect;
1282 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1283 if (textDecoration && !textDecoration->isValueList())
1284 style->removeProperty(CSSPropertyTextDecoration);
1287 StyleChange::StyleChange(EditingStyle* style, const Position& position)
1288 : m_applyBold(false)
1289 , m_applyItalic(false)
1290 , m_applyUnderline(false)
1291 , m_applyLineThrough(false)
1292 , m_applySubscript(false)
1293 , m_applySuperscript(false)
1295 Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
1296 if (!style || !style->style() || !document || !document->frame())
1299 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
1300 // FIXME: take care of background-color in effect
1301 RefPtr<StylePropertySet> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
1303 reconcileTextDecorationProperties(mutableStyle.get());
1304 if (!document->frame()->editor()->shouldStyleWithCSS())
1305 extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
1307 // Changing the whitespace style in a tab span would collapse the tab into a space.
1308 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1309 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1311 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1312 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1313 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1314 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1316 // Save the result for later
1317 m_cssStyle = mutableStyle->asText().stripWhiteSpace();
1320 static void setTextDecorationProperty(StylePropertySet* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
1322 if (newTextDecoration->length())
1323 style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID));
1325 // text-decoration: none is redundant since it does not remove any text decorations.
1326 ASSERT(!style->propertyIsImportant(propertyID));
1327 style->removeProperty(propertyID);
1331 void StyleChange::extractTextStyles(Document* document, StylePropertySet* style, bool shouldUseFixedFontDefaultSize)
1335 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
1336 style->removeProperty(CSSPropertyFontWeight);
1340 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
1341 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1342 style->removeProperty(CSSPropertyFontStyle);
1343 m_applyItalic = true;
1346 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1347 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1348 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1349 if (textDecoration && textDecoration->isValueList()) {
1350 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1351 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1353 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1354 if (newTextDecoration->removeAll(underline.get()))
1355 m_applyUnderline = true;
1356 if (newTextDecoration->removeAll(lineThrough.get()))
1357 m_applyLineThrough = true;
1359 // If trimTextDecorations, delete underline and line-through
1360 setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
1363 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
1364 switch (verticalAlign) {
1366 style->removeProperty(CSSPropertyVerticalAlign);
1367 m_applySubscript = true;
1370 style->removeProperty(CSSPropertyVerticalAlign);
1371 m_applySuperscript = true;
1375 if (style->getPropertyCSSValue(CSSPropertyColor)) {
1376 m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
1377 style->removeProperty(CSSPropertyColor);
1380 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
1381 // Remove single quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448
1382 m_applyFontFace.replace('\'', "");
1383 style->removeProperty(CSSPropertyFontFamily);
1385 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
1386 if (!fontSize->isPrimitiveValue())
1387 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1388 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
1389 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1390 m_applyFontSize = String::number(legacyFontSize);
1391 style->removeProperty(CSSPropertyFontSize);
1396 static void diffTextDecorations(StylePropertySet* style, CSSPropertyID propertID, CSSValue* refTextDecoration)
1398 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
1399 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
1402 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1403 CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
1405 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
1406 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
1408 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1411 static bool fontWeightIsBold(CSSValue* fontWeight)
1415 if (!fontWeight->isPrimitiveValue())
1418 // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
1419 // Collapse all other values to either one of these two states for editing purposes.
1420 switch (static_cast<CSSPrimitiveValue*>(fontWeight)->getIdent()) {
1426 case CSSValueNormal:
1436 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
1440 static bool fontWeightIsBold(CSSStyleDeclaration* style)
1443 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValueInternal(CSSPropertyFontWeight);
1444 return fontWeightIsBold(fontWeight.get());
1447 static bool fontWeightIsBold(StylePropertySet* style)
1450 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
1451 return fontWeightIsBold(fontWeight.get());
1454 static int getTextAlignment(int textAlignIdentifierValue)
1456 switch (textAlignIdentifierValue) {
1457 case CSSValueCenter:
1458 case CSSValueWebkitCenter:
1459 return CSSValueCenter;
1460 case CSSValueJustify:
1461 return CSSValueJustify;
1463 case CSSValueWebkitLeft:
1464 return CSSValueLeft;
1466 case CSSValueWebkitRight:
1467 return CSSValueRight;
1469 return CSSValueInvalid;
1472 static int getTextAlignment(CSSStyleDeclaration* style)
1474 return getTextAlignment(getIdentifierValue(style, CSSPropertyTextAlign));
1477 static int getTextAlignment(StylePropertySet* style)
1479 return getTextAlignment(getIdentifierValue(style, CSSPropertyTextAlign));
1482 PassRefPtr<StylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
1484 ASSERT(styleWithRedundantProperties);
1486 RefPtr<StylePropertySet> result = styleWithRedundantProperties->copy();
1488 result->removeEquivalentProperties(baseStyle);
1490 RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValueInternal(CSSPropertyWebkitTextDecorationsInEffect);
1491 diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
1492 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1494 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyFontWeight) && fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
1495 result->removeProperty(CSSPropertyFontWeight);
1497 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyColor) && getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1498 result->removeProperty(CSSPropertyColor);
1500 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyTextAlign) && getTextAlignment(result.get()) == getTextAlignment(baseStyle))
1501 result->removeProperty(CSSPropertyTextAlign);
1506 int getIdentifierValue(StylePropertySet* style, CSSPropertyID propertyID)
1510 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1511 if (!value || !value->isPrimitiveValue())
1513 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1516 int getIdentifierValue(CSSStyleDeclaration* style, CSSPropertyID propertyID)
1520 RefPtr<CSSValue> value = style->getPropertyCSSValueInternal(propertyID);
1521 if (!value || !value->isPrimitiveValue())
1523 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1526 static bool isCSSValueLength(CSSPrimitiveValue* value)
1528 return value->isFontIndependentLength();
1531 int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1533 if (isCSSValueLength(value)) {
1534 int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1535 int legacyFontSize = StyleResolver::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1536 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1537 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1538 if (mode == AlwaysUseLegacyFontSize || StyleResolver::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1539 return legacyFontSize;
1544 if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
1545 return value->getIdent() - CSSValueXSmall + 1;
1550 bool isTransparentColorValue(CSSValue* cssValue)
1554 if (!cssValue->isPrimitiveValue())
1556 CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(cssValue);
1557 if (value->isRGBColor())
1558 return !alphaChannel(value->getRGBA32Value());
1559 return value->getIdent() == CSSValueTransparent;
1562 bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
1564 RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
1565 return isTransparentColorValue(cssValue.get());
1568 bool hasTransparentBackgroundColor(StylePropertySet* style)
1570 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
1571 return isTransparentColorValue(cssValue.get());
1574 PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
1576 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1577 RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor);
1578 if (!hasTransparentBackgroundColor(ancestorStyle.get()))
1579 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);