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 getRGBABackgroundColor(CSSStyleDeclaration* style)
373 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor).get());
376 static inline RGBA32 getRGBABackgroundColor(StylePropertySet* style)
378 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get());
381 static inline RGBA32 rgbaBackgroundColorInEffect(Node* node)
383 return cssValueToRGBA(backgroundColorInEffect(node).get());
386 static int textAlignResolvingStartAndEnd(int textAlign, int direction)
390 case CSSValueWebkitCenter:
391 return CSSValueCenter;
392 case CSSValueJustify:
393 return CSSValueJustify;
395 case CSSValueWebkitLeft:
398 case CSSValueWebkitRight:
399 return CSSValueRight;
401 return direction != CSSValueRtl ? CSSValueLeft : CSSValueRight;
403 return direction == CSSValueRtl ? CSSValueRight : CSSValueLeft;
405 return CSSValueInvalid;
409 static int textAlignResolvingStartAndEnd(T* style)
411 return textAlignResolvingStartAndEnd(getIdentifierValue(style, CSSPropertyTextAlign), getIdentifierValue(style, CSSPropertyDirection));
414 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
416 if (isTabSpanTextNode(node))
417 node = tabSpanNode(node)->parentNode();
418 else if (isTabSpanNode(node))
419 node = node->parentNode();
421 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node);
422 m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
424 if (propertiesToInclude == EditingPropertiesInEffect) {
425 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
426 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
427 if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
428 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
431 if (node && node->computedStyle()) {
432 RenderStyle* renderStyle = node->computedStyle();
433 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
434 replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
437 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
438 extractFontSizeDelta();
441 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
443 // If a node's text fill color is invalid, then its children use
444 // their font-color as their text fill color (they don't
445 // inherit it). Likewise for stroke color.
446 if (!renderStyle->textFillColor().isValid())
447 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
448 if (!renderStyle->textStrokeColor().isValid())
449 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
452 void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important)
455 m_mutableStyle = StylePropertySet::create();
457 m_mutableStyle->setProperty(propertyID, value, important);
460 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
463 if (renderStyle->fontDescription().keywordSize())
464 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
467 void EditingStyle::extractFontSizeDelta()
472 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
473 // Explicit font size overrides any delta.
474 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
478 // Get the adjustment amount out of the style.
479 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
480 if (!value || !value->isPrimitiveValue())
483 CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
485 // Only PX handled now. If we handle more types in the future, perhaps
486 // a switch statement here would be more appropriate.
487 if (!primitiveValue->isPx())
490 m_fontSizeDelta = primitiveValue->getFloatValue();
491 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
494 bool EditingStyle::isEmpty() const
496 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
499 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
504 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
505 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
508 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
509 if (unicodeBidiValue == CSSValueEmbed) {
510 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
511 if (!direction || !direction->isPrimitiveValue())
514 writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
519 if (unicodeBidiValue == CSSValueNormal) {
520 writingDirection = NaturalWritingDirection;
527 void EditingStyle::setStyle(PassRefPtr<StylePropertySet> style)
529 m_mutableStyle = style;
530 // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
531 // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
532 m_shouldUseFixedDefaultFontSize = false;
533 extractFontSizeDelta();
536 void EditingStyle::overrideWithStyle(const StylePropertySet* style)
538 if (!style || style->isEmpty())
541 m_mutableStyle = StylePropertySet::create();
542 m_mutableStyle->mergeAndOverrideOnConflict(style);
543 extractFontSizeDelta();
546 void EditingStyle::clear()
548 m_mutableStyle.clear();
549 m_shouldUseFixedDefaultFontSize = false;
550 m_fontSizeDelta = NoFontDelta;
553 PassRefPtr<EditingStyle> EditingStyle::copy() const
555 RefPtr<EditingStyle> copy = EditingStyle::create();
557 copy->m_mutableStyle = m_mutableStyle->copy();
558 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
559 copy->m_fontSizeDelta = m_fontSizeDelta;
563 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
565 RefPtr<EditingStyle> blockProperties = EditingStyle::create();
567 return blockProperties;
569 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
570 m_mutableStyle->removeBlockProperties();
572 return blockProperties;
575 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
577 RefPtr<EditingStyle> textDirection = EditingStyle::create();
578 textDirection->m_mutableStyle = StylePropertySet::create();
579 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
580 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
581 m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
583 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
584 m_mutableStyle->removeProperty(CSSPropertyDirection);
586 return textDirection;
589 void EditingStyle::removeBlockProperties()
594 m_mutableStyle->removeBlockProperties();
597 void EditingStyle::removeStyleAddedByNode(Node* node)
599 if (!node || !node->parentNode())
601 RefPtr<StylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
602 RefPtr<StylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
603 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
604 m_mutableStyle->removeEquivalentProperties(nodeStyle->ensureCSSStyleDeclaration());
607 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
609 if (!node || !node->parentNode() || !m_mutableStyle)
612 RefPtr<StylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
613 RefPtr<StylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
614 nodeStyle->removeEquivalentProperties(parentStyle->ensureCSSStyleDeclaration());
616 unsigned propertyCount = nodeStyle->propertyCount();
617 for (unsigned i = 0; i < propertyCount; ++i)
618 m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id());
621 #if ENABLE(TIZEN_PRESERVE_PROPERTIES_WHEN_PARAGRAPH_MERGE)
622 void EditingStyle::removeNonEditingProperties(bool removeNonInheritableEditingProperties)
624 void EditingStyle::removeNonEditingProperties()
628 #if ENABLE(TIZEN_PRESERVE_PROPERTIES_WHEN_PARAGRAPH_MERGE)
629 m_mutableStyle = copyEditingProperties(m_mutableStyle.get(), removeNonInheritableEditingProperties ? OnlyInheritableEditingProperties : AllEditingProperties);
631 m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
635 void EditingStyle::collapseTextDecorationProperties()
640 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
641 if (!textDecorationsInEffect)
644 if (textDecorationsInEffect->isValueList())
645 m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->propertyIsImportant(CSSPropertyTextDecoration));
647 m_mutableStyle->removeProperty(CSSPropertyTextDecoration);
648 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
651 // CSS properties that create a visual difference only when applied to text.
652 static const CSSPropertyID textOnlyProperties[] = {
653 CSSPropertyTextDecoration,
654 CSSPropertyWebkitTextDecorationsInEffect,
655 CSSPropertyFontStyle,
656 CSSPropertyFontWeight,
660 TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
662 if (!style || !style->m_mutableStyle)
663 return FalseTriState;
664 return triStateOfStyle(style->m_mutableStyle->ensureCSSStyleDeclaration(), DoNotIgnoreTextOnlyProperties);
667 TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
669 RefPtr<StylePropertySet> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
671 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
672 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
674 if (difference->isEmpty())
676 if (difference->propertyCount() == m_mutableStyle->propertyCount())
677 return FalseTriState;
679 return MixedTriState;
682 TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
684 if (!selection.isCaretOrRange())
685 return FalseTriState;
687 if (selection.isCaret())
688 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
690 TriState state = FalseTriState;
691 for (Node* node = selection.start().deprecatedNode(); node; node = node->traverseNextNode()) {
692 RefPtr<CSSComputedStyleDeclaration> nodeStyle = CSSComputedStyleDeclaration::create(node);
694 TriState nodeState = triStateOfStyle(nodeStyle.get(), node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
695 if (node == selection.start().deprecatedNode())
697 #if ENABLE(TIZEN_EDITING_IGNORE_NONVISIBLE)
698 else if (state != nodeState && node->isTextNode() && node->renderer()) {
700 else if (state != nodeState && node->isTextNode()) {
702 state = MixedTriState;
706 if (node == selection.end().deprecatedNode())
713 bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
716 ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
718 const StylePropertySet* inlineStyle = element->inlineStyle();
719 if (!m_mutableStyle || !inlineStyle)
722 unsigned propertyCount = m_mutableStyle->propertyCount();
723 for (unsigned i = 0; i < propertyCount; ++i) {
724 CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
726 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
727 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
730 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) {
731 if (!conflictingProperties)
733 conflictingProperties->append(CSSPropertyTextDecoration);
735 extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->propertyIsImportant(CSSPropertyTextDecoration));
739 if (!inlineStyle->getPropertyCSSValue(propertyID))
742 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
743 if (!conflictingProperties)
745 conflictingProperties->append(CSSPropertyDirection);
747 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
750 if (!conflictingProperties)
753 conflictingProperties->append(propertyID);
756 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
759 return conflictingProperties && !conflictingProperties->isEmpty();
762 static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
764 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
766 if (!HTMLElementEquivalents.size()) {
767 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
768 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
769 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
770 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
771 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
772 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
774 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag));
775 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag));
776 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag));
779 return HTMLElementEquivalents;
783 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
788 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
789 for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
790 const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
791 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
792 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
794 equivalent->addToStyle(element, extractedStyle);
801 static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
803 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
805 if (!HTMLAttributeEquivalents.size()) {
806 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
807 // of exactly one element except dirAttr.
808 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
809 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
810 HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
812 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
813 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
816 return HTMLAttributeEquivalents;
819 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
825 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
826 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
827 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
828 && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
835 bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
836 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
839 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
840 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
844 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
845 bool removed = false;
846 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
847 const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
849 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
850 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
853 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
854 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
858 equivalent->addToStyle(element, extractedStyle);
859 conflictingAttributes.append(equivalent->attributeName());
866 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
868 return !m_mutableStyle || getPropertiesNotIn(m_mutableStyle.get(), CSSComputedStyleDeclaration::create(node).get())->isEmpty();
871 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
873 bool elementIsSpanOrElementEquivalent = false;
874 if (element->hasTagName(HTMLNames::spanTag))
875 elementIsSpanOrElementEquivalent = true;
877 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
879 for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
880 if (HTMLElementEquivalents[i]->matches(element)) {
881 elementIsSpanOrElementEquivalent = true;
887 if (!element->hasAttributes())
888 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
890 unsigned matchedAttributes = 0;
891 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
892 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
893 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
897 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
898 return false; // element is not a span, a html element equivalent, or font element.
900 if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
903 if (element->hasAttribute(HTMLNames::styleAttr)) {
904 if (const StylePropertySet* style = element->inlineStyle()) {
905 unsigned propertyCount = style->propertyCount();
906 for (unsigned i = 0; i < propertyCount; ++i) {
907 if (!isEditingProperty(style->propertyAt(i).id()))
914 // font with color attribute, span with style attribute, etc...
915 ASSERT(matchedAttributes <= element->attributeCount());
916 return matchedAttributes >= element->attributeCount();
919 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
924 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
925 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
926 // which one of editingStyleAtPosition or computedStyle is called.
927 RefPtr<EditingStyle> editingStyleAtPosition = EditingStyle::create(position, EditingPropertiesInEffect);
928 StylePropertySet* styleAtPosition = editingStyleAtPosition->m_mutableStyle.get();
930 RefPtr<CSSValue> unicodeBidi;
931 RefPtr<CSSValue> direction;
932 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
933 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
934 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
937 m_mutableStyle->removeEquivalentProperties(styleAtPosition);
939 if (textAlignResolvingStartAndEnd(m_mutableStyle.get()) == textAlignResolvingStartAndEnd(styleAtPosition))
940 m_mutableStyle->removeProperty(CSSPropertyTextAlign);
942 if (getRGBAFontColor(m_mutableStyle.get()) == getRGBAFontColor(styleAtPosition))
943 m_mutableStyle->removeProperty(CSSPropertyColor);
945 if (hasTransparentBackgroundColor(m_mutableStyle.get())
946 || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
947 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
949 if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
950 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
951 if (direction && direction->isPrimitiveValue())
952 m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
956 void EditingStyle::mergeTypingStyle(Document* document)
960 RefPtr<EditingStyle> typingStyle = document->frame()->selection()->typingStyle();
961 if (!typingStyle || typingStyle == this)
964 mergeStyle(typingStyle->style(), OverrideValues);
967 void EditingStyle::mergeInlineStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
970 if (!element->inlineStyle())
973 switch (propertiesToInclude) {
975 mergeStyle(element->inlineStyle(), mode);
977 case OnlyEditingInheritableProperties:
978 mergeStyle(copyEditingProperties(element->inlineStyle(), OnlyInheritableEditingProperties).get(), mode);
980 case EditingPropertiesInEffect:
981 mergeStyle(copyEditingProperties(element->inlineStyle(), AllEditingProperties).get(), mode);
986 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const StyledElement* element,
987 EditingStyle::CSSPropertyOverrideMode mode, StylePropertySet* style)
989 return equivalent->matches(element) && !equivalent->propertyExistsInStyle(element->inlineStyle())
990 && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
993 void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
995 RefPtr<EditingStyle> styleFromRules = EditingStyle::create();
996 styleFromRules->mergeStyleFromRulesForSerialization(element);
997 #if ENABLE(TIZEN_PRESERVE_PROPERTIES_WHEN_PARAGRAPH_MERGE)
998 styleFromRules->removeNonEditingProperties(propertiesToInclude == OnlyEditingInheritableProperties);
1000 styleFromRules->removeNonEditingProperties();
1002 mergeStyle(styleFromRules->m_mutableStyle.get(), mode);
1004 mergeInlineStyleOfElement(element, mode, propertiesToInclude);
1006 const Vector<OwnPtr<HTMLElementEquivalent> >& elementEquivalents = htmlElementEquivalents();
1007 for (size_t i = 0; i < elementEquivalents.size(); ++i) {
1008 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1009 elementEquivalents[i]->addToStyle(element, this);
1012 const Vector<OwnPtr<HTMLAttributeEquivalent> >& attributeEquivalents = htmlAttributeEquivalents();
1013 for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
1014 if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
1015 continue; // We don't want to include directionality
1016 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1017 attributeEquivalents[i]->addToStyle(element, this);
1021 PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate)
1023 RefPtr<EditingStyle> wrappingStyle;
1024 if (shouldAnnotate) {
1025 wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
1027 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
1028 // to help us differentiate those styles from ones that the user has applied.
1029 // This helps us get the color of content pasted into blockquotes right.
1030 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(context), isMailBlockquote, CanCrossEditingBoundary));
1032 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
1033 wrappingStyle->collapseTextDecorationProperties();
1035 return wrappingStyle.release();
1038 wrappingStyle = EditingStyle::create();
1040 // When not annotating for interchange, we only preserve inline style declarations.
1041 for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) {
1042 if (node->isStyledElement() && !isMailBlockquote(node)) {
1043 wrappingStyle->mergeInlineAndImplicitStyleOfElement(static_cast<StyledElement*>(node), EditingStyle::DoNotOverrideValues,
1044 EditingStyle::EditingPropertiesInEffect);
1048 return wrappingStyle.release();
1052 static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
1054 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1055 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1057 if (valueToMerge->hasValue(underline.get()) && !mergedValue->hasValue(underline.get()))
1058 mergedValue->append(underline.get());
1060 if (valueToMerge->hasValue(lineThrough.get()) && !mergedValue->hasValue(lineThrough.get()))
1061 mergedValue->append(lineThrough.get());
1064 void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode)
1069 if (!m_mutableStyle) {
1070 m_mutableStyle = style->copy();
1074 unsigned propertyCount = style->propertyCount();
1075 for (unsigned i = 0; i < propertyCount; ++i) {
1076 const CSSProperty& property = style->propertyAt(i);
1077 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
1079 // text decorations never override values
1080 if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
1081 if (value->isValueList()) {
1082 mergeTextDecorationValues(static_cast<CSSValueList*>(value.get()), static_cast<CSSValueList*>(property.value()));
1085 value = 0; // text-decoration: none is equivalent to not having the property
1088 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1089 m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant());
1093 static PassRefPtr<StylePropertySet> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude)
1095 RefPtr<StylePropertySet> style = StylePropertySet::create();
1096 RefPtr<CSSRuleList> matchedRules = element->document()->styleResolver()->styleRulesForElement(element, rulesToInclude);
1098 for (unsigned i = 0; i < matchedRules->length(); i++) {
1099 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE)
1100 style->mergeAndOverrideOnConflict(static_cast<CSSStyleRule*>(matchedRules->item(i))->styleRule()->properties());
1104 return style.release();
1107 void EditingStyle::mergeStyleFromRules(StyledElement* element)
1109 RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1110 StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules);
1111 // Styles from the inline style declaration, held in the variable "style", take precedence
1112 // over those from matched rules.
1114 styleFromMatchedRules->mergeAndOverrideOnConflict(m_mutableStyle.get());
1117 m_mutableStyle = styleFromMatchedRules;
1120 void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement* element)
1122 mergeStyleFromRules(element);
1124 // The property value, if it's a percentage, may not reflect the actual computed value.
1125 // For example: style="height: 1%; overflow: visible;" in quirksmode
1126 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1127 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = CSSComputedStyleDeclaration::create(element);
1128 RefPtr<StylePropertySet> fromComputedStyle = StylePropertySet::create();
1130 unsigned propertyCount = m_mutableStyle->propertyCount();
1131 for (unsigned i = 0; i < propertyCount; ++i) {
1132 const CSSProperty& property = m_mutableStyle->propertyAt(i);
1133 CSSValue* value = property.value();
1134 if (!value->isPrimitiveValue())
1136 if (static_cast<CSSPrimitiveValue*>(value)->isPercentage()) {
1137 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
1138 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
1142 m_mutableStyle->mergeAndOverrideOnConflict(fromComputedStyle.get());
1145 static void removePropertiesInStyle(StylePropertySet* styleToRemovePropertiesFrom, StylePropertySet* style)
1147 unsigned propertyCount = style->propertyCount();
1148 Vector<CSSPropertyID> propertiesToRemove(propertyCount);
1149 for (unsigned i = 0; i < propertyCount; ++i)
1150 propertiesToRemove[i] = style->propertyAt(i).id();
1152 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1155 void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* context)
1158 if (!m_mutableStyle)
1161 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1162 RefPtr<StylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules);
1163 if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
1164 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules->ensureCSSStyleDeclaration());
1166 // 2. Remove style present in context and not overriden by matched rules.
1167 RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1168 if (computedStyle->m_mutableStyle) {
1169 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1170 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1172 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1173 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle->ensureCSSStyleDeclaration());
1176 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules.
1177 // These rules are added by serialization code to wrap text nodes.
1178 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1179 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline)
1180 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1181 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone)
1182 m_mutableStyle->removeProperty(CSSPropertyFloat);
1186 void EditingStyle::removePropertiesInElementDefaultStyle(Element* element)
1188 if (!m_mutableStyle || m_mutableStyle->isEmpty())
1191 RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules);
1193 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1196 void EditingStyle::forceInline()
1198 if (!m_mutableStyle)
1199 m_mutableStyle = StylePropertySet::create();
1200 const bool propertyIsImportant = true;
1201 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1204 int EditingStyle::legacyFontSize(Document* document) const
1206 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1207 if (!cssValue || !cssValue->isPrimitiveValue())
1209 return legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(cssValue.get()),
1210 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1213 PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1215 if (selection.isNone())
1218 Position position = adjustedSelectionStartForStyleComputation(selection);
1220 // If the pos is at the end of a text node, then this node is not fully selected.
1221 // Move it to the next deep equivalent position to avoid removing the style from this node.
1222 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1223 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1224 Node* positionNode = position.containerNode();
1225 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1226 position = nextVisuallyDistinctCandidate(position);
1228 Element* element = position.element();
1232 RefPtr<EditingStyle> style = EditingStyle::create(element, EditingStyle::AllProperties);
1233 style->mergeTypingStyle(element->document());
1235 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1236 // Also, if the selection is a range, ignore the background color at the start of selection,
1237 // and find the background color of the common ancestor.
1238 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1239 RefPtr<Range> range(selection.toNormalizedRange());
1240 ExceptionCode ec = 0;
1241 if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(ec)))
1242 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1248 WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& selection, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings)
1250 hasNestedOrMultipleEmbeddings = true;
1252 if (selection.isNone())
1253 return NaturalWritingDirection;
1255 Position position = selection.start().downstream();
1257 Node* node = position.deprecatedNode();
1259 return NaturalWritingDirection;
1262 if (selection.isRange()) {
1263 end = selection.end().upstream();
1265 Node* pastLast = Range::create(end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode();
1266 for (Node* n = node; n && n != pastLast; n = n->traverseNextNode()) {
1267 if (!n->isStyledElement())
1270 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(n);
1271 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1272 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1275 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
1276 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
1277 return NaturalWritingDirection;
1281 if (selection.isCaret()) {
1282 WritingDirection direction;
1283 if (typingStyle && typingStyle->textDirection(direction)) {
1284 hasNestedOrMultipleEmbeddings = false;
1287 node = selection.visibleStart().deepEquivalent().deprecatedNode();
1290 // 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
1292 Node* block = enclosingBlock(node);
1293 WritingDirection foundDirection = NaturalWritingDirection;
1295 for (; node != block; node = node->parentNode()) {
1296 if (!node->isStyledElement())
1299 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
1300 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1301 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1304 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
1305 if (unicodeBidiValue == CSSValueNormal)
1308 if (unicodeBidiValue == CSSValueBidiOverride)
1309 return NaturalWritingDirection;
1311 ASSERT(unicodeBidiValue == CSSValueEmbed);
1312 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
1313 if (!direction || !direction->isPrimitiveValue())
1316 int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent();
1317 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
1320 if (foundDirection != NaturalWritingDirection)
1321 return NaturalWritingDirection;
1323 // In the range case, make sure that the embedding element persists until the end of the range.
1324 if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(node))
1325 return NaturalWritingDirection;
1327 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
1329 hasNestedOrMultipleEmbeddings = false;
1330 return foundDirection;
1333 static void reconcileTextDecorationProperties(StylePropertySet* style)
1335 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1336 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1337 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1338 ASSERT(!textDecorationsInEffect || !textDecoration);
1339 if (textDecorationsInEffect) {
1340 style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
1341 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1342 textDecoration = textDecorationsInEffect;
1345 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1346 if (textDecoration && !textDecoration->isValueList())
1347 style->removeProperty(CSSPropertyTextDecoration);
1350 StyleChange::StyleChange(EditingStyle* style, const Position& position)
1351 : m_applyBold(false)
1352 , m_applyItalic(false)
1353 , m_applyUnderline(false)
1354 , m_applyLineThrough(false)
1355 , m_applySubscript(false)
1356 , m_applySuperscript(false)
1358 Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
1359 if (!style || !style->style() || !document || !document->frame())
1362 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
1363 // FIXME: take care of background-color in effect
1364 RefPtr<StylePropertySet> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
1366 reconcileTextDecorationProperties(mutableStyle.get());
1367 if (!document->frame()->editor()->shouldStyleWithCSS())
1368 extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
1370 // Changing the whitespace style in a tab span would collapse the tab into a space.
1371 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1372 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1374 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1375 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1376 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1377 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1379 // Save the result for later
1380 m_cssStyle = mutableStyle->asText().stripWhiteSpace();
1383 static void setTextDecorationProperty(StylePropertySet* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
1385 if (newTextDecoration->length())
1386 style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID));
1388 // text-decoration: none is redundant since it does not remove any text decorations.
1389 ASSERT(!style->propertyIsImportant(propertyID));
1390 style->removeProperty(propertyID);
1394 void StyleChange::extractTextStyles(Document* document, StylePropertySet* style, bool shouldUseFixedFontDefaultSize)
1398 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
1399 style->removeProperty(CSSPropertyFontWeight);
1403 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
1404 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1405 style->removeProperty(CSSPropertyFontStyle);
1406 m_applyItalic = true;
1409 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1410 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1411 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1412 if (textDecoration && textDecoration->isValueList()) {
1413 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1414 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1416 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1417 if (newTextDecoration->removeAll(underline.get()))
1418 m_applyUnderline = true;
1419 if (newTextDecoration->removeAll(lineThrough.get()))
1420 m_applyLineThrough = true;
1422 // If trimTextDecorations, delete underline and line-through
1423 setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
1426 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
1427 switch (verticalAlign) {
1429 style->removeProperty(CSSPropertyVerticalAlign);
1430 m_applySubscript = true;
1433 style->removeProperty(CSSPropertyVerticalAlign);
1434 m_applySuperscript = true;
1438 if (style->getPropertyCSSValue(CSSPropertyColor)) {
1439 m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
1440 style->removeProperty(CSSPropertyColor);
1443 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
1444 // Remove single quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448
1445 m_applyFontFace.replaceWithLiteral('\'', "");
1446 style->removeProperty(CSSPropertyFontFamily);
1448 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
1449 if (!fontSize->isPrimitiveValue())
1450 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1451 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
1452 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1453 m_applyFontSize = String::number(legacyFontSize);
1454 style->removeProperty(CSSPropertyFontSize);
1459 static void diffTextDecorations(StylePropertySet* style, CSSPropertyID propertID, CSSValue* refTextDecoration)
1461 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
1462 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
1465 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1466 CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
1468 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
1469 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
1471 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1474 static bool fontWeightIsBold(CSSValue* fontWeight)
1478 if (!fontWeight->isPrimitiveValue())
1481 // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
1482 // Collapse all other values to either one of these two states for editing purposes.
1483 switch (static_cast<CSSPrimitiveValue*>(fontWeight)->getIdent()) {
1489 case CSSValueNormal:
1499 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
1503 static bool fontWeightIsBold(CSSStyleDeclaration* style)
1506 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValueInternal(CSSPropertyFontWeight);
1507 return fontWeightIsBold(fontWeight.get());
1510 static bool fontWeightIsBold(StylePropertySet* style)
1513 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
1514 return fontWeightIsBold(fontWeight.get());
1517 PassRefPtr<StylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
1519 ASSERT(styleWithRedundantProperties);
1521 RefPtr<StylePropertySet> result = styleWithRedundantProperties->copy();
1523 result->removeEquivalentProperties(baseStyle);
1525 RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValueInternal(CSSPropertyWebkitTextDecorationsInEffect);
1526 diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
1527 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1529 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyFontWeight) && fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
1530 result->removeProperty(CSSPropertyFontWeight);
1532 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyColor) && getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1533 result->removeProperty(CSSPropertyColor);
1535 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyTextAlign)
1536 && textAlignResolvingStartAndEnd(result.get()) == textAlignResolvingStartAndEnd(baseStyle))
1537 result->removeProperty(CSSPropertyTextAlign);
1539 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyBackgroundColor) && getRGBABackgroundColor(result.get()) == getRGBABackgroundColor(baseStyle))
1540 result->removeProperty(CSSPropertyBackgroundColor);
1545 int getIdentifierValue(StylePropertySet* style, CSSPropertyID propertyID)
1549 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1550 if (!value || !value->isPrimitiveValue())
1552 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1555 int getIdentifierValue(CSSStyleDeclaration* style, CSSPropertyID propertyID)
1559 RefPtr<CSSValue> value = style->getPropertyCSSValueInternal(propertyID);
1560 if (!value || !value->isPrimitiveValue())
1562 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1565 static bool isCSSValueLength(CSSPrimitiveValue* value)
1567 return value->isFontIndependentLength();
1570 int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1572 if (isCSSValueLength(value)) {
1573 int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1574 int legacyFontSize = StyleResolver::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1575 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1576 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1577 if (mode == AlwaysUseLegacyFontSize || StyleResolver::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1578 return legacyFontSize;
1583 if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
1584 return value->getIdent() - CSSValueXSmall + 1;
1589 bool isTransparentColorValue(CSSValue* cssValue)
1593 if (!cssValue->isPrimitiveValue())
1595 CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(cssValue);
1596 if (value->isRGBColor())
1597 return !alphaChannel(value->getRGBA32Value());
1598 return value->getIdent() == CSSValueTransparent;
1601 bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
1603 RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
1604 return isTransparentColorValue(cssValue.get());
1607 bool hasTransparentBackgroundColor(StylePropertySet* style)
1609 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
1610 return isTransparentColorValue(cssValue.get());
1613 PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
1615 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1616 RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor);
1617 if (!hasTransparentBackgroundColor(ancestorStyle.get()))
1618 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);