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 "CSSMutableStyleDeclaration.h"
33 #include "CSSParser.h"
34 #include "CSSStyleRule.h"
35 #include "CSSStyleSelector.h"
36 #include "CSSValueKeywords.h"
37 #include "CSSValueList.h"
39 #include "FrameSelection.h"
40 #include "HTMLFontElement.h"
41 #include "HTMLInterchange.h"
42 #include "HTMLNames.h"
45 #include "QualifiedName.h"
46 #include "RenderStyle.h"
47 #include "StyledElement.h"
48 #include "htmlediting.h"
49 #include "visible_units.h"
50 #include <wtf/HashSet.h>
54 // Editing style properties must be preserved during editing operation.
55 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
56 static const int editingProperties[] = {
57 CSSPropertyBackgroundColor,
58 CSSPropertyTextDecoration,
60 // CSS inheritable properties
62 CSSPropertyFontFamily,
65 CSSPropertyFontVariant,
66 CSSPropertyFontWeight,
67 CSSPropertyLetterSpacing,
68 CSSPropertyLineHeight,
71 CSSPropertyTextIndent,
72 CSSPropertyTextTransform,
73 CSSPropertyWhiteSpace,
75 CSSPropertyWordSpacing,
76 CSSPropertyWebkitTextDecorationsInEffect,
77 CSSPropertyWebkitTextFillColor,
78 CSSPropertyWebkitTextSizeAdjust,
79 CSSPropertyWebkitTextStrokeColor,
80 CSSPropertyWebkitTextStrokeWidth,
83 static PassRefPtr<CSSMutableStyleDeclaration> copyEditingProperties(CSSStyleDeclaration* style, bool includeNonInheritableProperties = false)
85 if (includeNonInheritableProperties)
86 return style->copyPropertiesInSet(editingProperties, WTF_ARRAY_LENGTH(editingProperties));
87 return style->copyPropertiesInSet(editingProperties + 2, WTF_ARRAY_LENGTH(editingProperties) - 2);
90 static inline bool isEditingProperty(int id)
92 for (size_t i = 0; i < WTF_ARRAY_LENGTH(editingProperties); ++i) {
93 if (editingProperties[i] == id)
99 static PassRefPtr<CSSMutableStyleDeclaration> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style)
102 return CSSMutableStyleDeclaration::create();
103 return copyEditingProperties(style.get());
106 static RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
107 enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
108 static int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
109 static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
110 static PassRefPtr<CSSValue> backgroundColorInEffect(Node*);
112 class HTMLElementEquivalent {
114 static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, int primitiveValue, const QualifiedName& tagName)
116 return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
119 virtual ~HTMLElementEquivalent() { }
120 virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
121 virtual bool hasAttribute() const { return false; }
122 virtual bool propertyExistsInStyle(CSSStyleDeclaration* style) const { return style && style->getPropertyCSSValue(m_propertyID); }
123 virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
124 virtual void addToStyle(Element*, EditingStyle*) const;
127 HTMLElementEquivalent(CSSPropertyID);
128 HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
129 HTMLElementEquivalent(CSSPropertyID, int primitiveValue, const QualifiedName& tagName);
130 const int m_propertyID;
131 const RefPtr<CSSPrimitiveValue> m_primitiveValue;
132 const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
135 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
141 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
143 , m_tagName(&tagName)
147 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, int primitiveValue, const QualifiedName& tagName)
149 , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
150 , m_tagName(&tagName)
152 ASSERT(primitiveValue != CSSValueInvalid);
155 bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
157 RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
158 return matches(element) && value && value->isPrimitiveValue() && static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == m_primitiveValue->getIdent();
161 void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
163 style->setProperty(m_propertyID, m_primitiveValue->cssText());
166 class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
168 static PassOwnPtr<HTMLElementEquivalent> create(int primitiveValue, const QualifiedName& tagName)
170 return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
172 virtual bool propertyExistsInStyle(CSSStyleDeclaration*) const;
173 virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
176 HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName);
179 HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(int primitiveValue, const QualifiedName& tagName)
180 : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
181 // m_propertyID is used in HTMLElementEquivalent::addToStyle
185 bool HTMLTextDecorationEquivalent::propertyExistsInStyle(CSSStyleDeclaration* style) const
187 return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) || style->getPropertyCSSValue(CSSPropertyTextDecoration);
190 bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
192 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
194 styleValue = style->getPropertyCSSValue(CSSPropertyTextDecoration);
195 return matches(element) && styleValue && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(m_primitiveValue.get());
198 class HTMLAttributeEquivalent : public HTMLElementEquivalent {
200 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName)
202 return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
204 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
206 return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
209 bool matches(const Element* elem) const { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
210 virtual bool hasAttribute() const { return true; }
211 virtual bool valueIsPresentInStyle(Element*, CSSStyleDeclaration*) const;
212 virtual void addToStyle(Element*, EditingStyle*) const;
213 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
214 inline const QualifiedName& attributeName() const { return m_attrName; }
217 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
218 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
219 const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
222 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
223 : HTMLElementEquivalent(id, tagName)
224 , m_attrName(attrName)
228 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
229 : HTMLElementEquivalent(id)
230 , m_attrName(attrName)
234 bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, CSSStyleDeclaration* style) const
236 RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
237 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
239 // FIXME: This is very inefficient way of comparing values
240 // but we can't string compare attribute value and CSS property value.
241 return value && styleValue && value->cssText() == styleValue->cssText();
244 void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
246 if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
247 style->setProperty(m_propertyID, value->cssText());
250 PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
253 if (!element->hasAttribute(m_attrName))
256 RefPtr<CSSMutableStyleDeclaration> dummyStyle;
257 dummyStyle = CSSMutableStyleDeclaration::create();
258 dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName));
259 return dummyStyle->getPropertyCSSValue(m_propertyID);
262 class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent {
264 static PassOwnPtr<HTMLFontSizeEquivalent> create()
266 return adoptPtr(new HTMLFontSizeEquivalent());
268 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
271 HTMLFontSizeEquivalent();
274 HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
275 : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
279 PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
282 if (!element->hasAttribute(m_attrName))
285 if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size))
287 return CSSPrimitiveValue::createIdentifier(size);
290 float EditingStyle::NoFontDelta = 0.0f;
292 EditingStyle::EditingStyle()
293 : m_shouldUseFixedDefaultFontSize(false)
294 , m_fontSizeDelta(NoFontDelta)
298 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
299 : m_shouldUseFixedDefaultFontSize(false)
300 , m_fontSizeDelta(NoFontDelta)
302 init(node, propertiesToInclude);
305 EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
306 : m_shouldUseFixedDefaultFontSize(false)
307 , m_fontSizeDelta(NoFontDelta)
309 init(position.deprecatedNode(), propertiesToInclude);
312 EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
313 : m_mutableStyle(style ? style->copy() : 0)
314 , m_shouldUseFixedDefaultFontSize(false)
315 , m_fontSizeDelta(NoFontDelta)
317 extractFontSizeDelta();
320 EditingStyle::EditingStyle(int propertyID, const String& value)
322 , m_shouldUseFixedDefaultFontSize(false)
323 , m_fontSizeDelta(NoFontDelta)
325 setProperty(propertyID, value);
328 EditingStyle::~EditingStyle()
332 static RGBA32 cssValueToRGBA(CSSValue* colorValue)
334 if (!colorValue || !colorValue->isPrimitiveValue())
335 return Color::transparent;
337 CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue);
338 if (primitiveColor->primitiveType() == CSSPrimitiveValue::CSS_RGBCOLOR)
339 return primitiveColor->getRGBA32Value();
342 CSSParser::parseColor(rgba, colorValue->cssText());
346 static inline RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
348 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyColor).get());
351 static inline RGBA32 rgbaBackgroundColorInEffect(Node* node)
353 return cssValueToRGBA(backgroundColorInEffect(node).get());
356 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
358 if (isTabSpanTextNode(node))
359 node = tabSpanNode(node)->parentNode();
360 else if (isTabSpanNode(node))
361 node = node->parentNode();
363 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = computedStyle(node);
364 m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copy() : editingStyleFromComputedStyle(computedStyleAtPosition);
366 if (propertiesToInclude == EditingPropertiesInEffect) {
367 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
368 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
369 if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
370 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
373 if (node && node->computedStyle()) {
374 RenderStyle* renderStyle = node->computedStyle();
375 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
376 replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
379 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
380 extractFontSizeDelta();
383 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
385 // If a node's text fill color is invalid, then its children use
386 // their font-color as their text fill color (they don't
387 // inherit it). Likewise for stroke color.
388 ExceptionCode ec = 0;
389 if (!renderStyle->textFillColor().isValid())
390 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor, ec);
391 if (!renderStyle->textStrokeColor().isValid())
392 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor, ec);
396 void EditingStyle::setProperty(int propertyID, const String& value, bool important)
399 m_mutableStyle = CSSMutableStyleDeclaration::create();
402 m_mutableStyle->setProperty(propertyID, value, important, ec);
405 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
408 if (renderStyle->fontDescription().keywordSize())
409 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
412 void EditingStyle::extractFontSizeDelta()
417 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
418 // Explicit font size overrides any delta.
419 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
423 // Get the adjustment amount out of the style.
424 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
425 if (!value || !value->isPrimitiveValue())
428 CSSPrimitiveValue* primitiveValue = static_cast<CSSPrimitiveValue*>(value.get());
430 // Only PX handled now. If we handle more types in the future, perhaps
431 // a switch statement here would be more appropriate.
432 if (primitiveValue->primitiveType() != CSSPrimitiveValue::CSS_PX)
435 m_fontSizeDelta = primitiveValue->getFloatValue();
436 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
439 bool EditingStyle::isEmpty() const
441 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
444 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
449 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
450 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
453 int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent();
454 if (unicodeBidiValue == CSSValueEmbed) {
455 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
456 if (!direction || !direction->isPrimitiveValue())
459 writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
464 if (unicodeBidiValue == CSSValueNormal) {
465 writingDirection = NaturalWritingDirection;
472 void EditingStyle::setStyle(PassRefPtr<CSSMutableStyleDeclaration> style)
474 m_mutableStyle = style;
475 // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
476 // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
477 m_shouldUseFixedDefaultFontSize = false;
478 extractFontSizeDelta();
481 void EditingStyle::overrideWithStyle(const CSSMutableStyleDeclaration* style)
483 if (!style || !style->length())
486 m_mutableStyle = CSSMutableStyleDeclaration::create();
487 m_mutableStyle->merge(style);
488 extractFontSizeDelta();
491 void EditingStyle::clear()
493 m_mutableStyle.clear();
494 m_shouldUseFixedDefaultFontSize = false;
495 m_fontSizeDelta = NoFontDelta;
498 PassRefPtr<EditingStyle> EditingStyle::copy() const
500 RefPtr<EditingStyle> copy = EditingStyle::create();
502 copy->m_mutableStyle = m_mutableStyle->copy();
503 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
504 copy->m_fontSizeDelta = m_fontSizeDelta;
508 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
510 RefPtr<EditingStyle> blockProperties = EditingStyle::create();
512 return blockProperties;
514 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
515 m_mutableStyle->removeBlockProperties();
517 return blockProperties;
520 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
522 RefPtr<EditingStyle> textDirection = EditingStyle::create();
523 textDirection->m_mutableStyle = CSSMutableStyleDeclaration::create();
524 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->getPropertyPriority(CSSPropertyUnicodeBidi));
525 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
526 m_mutableStyle->getPropertyPriority(CSSPropertyDirection));
528 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
529 m_mutableStyle->removeProperty(CSSPropertyDirection);
531 return textDirection;
534 void EditingStyle::removeBlockProperties()
539 m_mutableStyle->removeBlockProperties();
542 void EditingStyle::removeStyleAddedByNode(Node* node)
544 if (!node || !node->parentNode())
546 RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
547 RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
548 parentStyle->diff(nodeStyle.get());
549 nodeStyle->diff(m_mutableStyle.get());
552 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
554 if (!node || !node->parentNode() || !m_mutableStyle)
556 RefPtr<CSSMutableStyleDeclaration> parentStyle = editingStyleFromComputedStyle(computedStyle(node->parentNode()));
557 RefPtr<CSSMutableStyleDeclaration> nodeStyle = editingStyleFromComputedStyle(computedStyle(node));
558 parentStyle->diff(nodeStyle.get());
560 CSSMutableStyleDeclaration::const_iterator end = nodeStyle->end();
561 for (CSSMutableStyleDeclaration::const_iterator it = nodeStyle->begin(); it != end; ++it)
562 m_mutableStyle->removeProperty(it->id());
565 void EditingStyle::removeNonEditingProperties()
568 m_mutableStyle = copyEditingProperties(m_mutableStyle.get());
571 void EditingStyle::collapseTextDecorationProperties()
576 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
577 if (!textDecorationsInEffect)
580 if (textDecorationsInEffect->isValueList())
581 m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->getPropertyPriority(CSSPropertyTextDecoration));
583 m_mutableStyle->removeProperty(CSSPropertyTextDecoration);
584 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
587 // CSS properties that create a visual difference only when applied to text.
588 static const int textOnlyProperties[] = {
589 CSSPropertyTextDecoration,
590 CSSPropertyWebkitTextDecorationsInEffect,
591 CSSPropertyFontStyle,
592 CSSPropertyFontWeight,
596 TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
598 if (!style || !style->m_mutableStyle)
599 return FalseTriState;
600 return triStateOfStyle(style->m_mutableStyle.get(), DoNotIgnoreTextOnlyProperties);
603 TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
605 RefPtr<CSSMutableStyleDeclaration> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
607 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
608 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
610 if (!difference->length())
612 if (difference->length() == m_mutableStyle->length())
613 return FalseTriState;
615 return MixedTriState;
618 TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
620 if (!selection.isCaretOrRange())
621 return FalseTriState;
623 if (selection.isCaret())
624 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
626 TriState state = FalseTriState;
627 for (Node* node = selection.start().deprecatedNode(); node; node = node->traverseNextNode()) {
628 RefPtr<CSSComputedStyleDeclaration> nodeStyle = computedStyle(node);
630 TriState nodeState = triStateOfStyle(nodeStyle.get(), node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
631 if (node == selection.start().deprecatedNode())
633 else if (state != nodeState && node->isTextNode()) {
634 state = MixedTriState;
638 if (node == selection.end().deprecatedNode())
645 bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
648 ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
650 CSSMutableStyleDeclaration* inlineStyle = element->inlineStyleDecl();
651 if (!m_mutableStyle || !inlineStyle)
654 CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
655 for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
656 CSSPropertyID propertyID = static_cast<CSSPropertyID>(it->id());
658 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
659 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
662 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) {
663 if (!conflictingProperties)
665 conflictingProperties->append(CSSPropertyTextDecoration);
667 extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->getPropertyPriority(CSSPropertyTextDecoration));
671 if (!inlineStyle->getPropertyCSSValue(propertyID))
674 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
675 if (!conflictingProperties)
677 conflictingProperties->append(CSSPropertyDirection);
679 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->getPropertyPriority(propertyID));
682 if (!conflictingProperties)
685 conflictingProperties->append(propertyID);
688 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->getPropertyPriority(propertyID));
691 return conflictingProperties && !conflictingProperties->isEmpty();
694 static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
696 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
698 if (!HTMLElementEquivalents.size()) {
699 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
700 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
701 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
702 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
703 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
704 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
706 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag));
707 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag));
708 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag));
711 return HTMLElementEquivalents;
715 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
720 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
721 for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
722 const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
723 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
724 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
726 equivalent->addToStyle(element, extractedStyle);
733 static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
735 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
737 if (!HTMLAttributeEquivalents.size()) {
738 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
739 // of exactly one element except dirAttr.
740 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
741 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
742 HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
744 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
745 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
748 return HTMLAttributeEquivalents;
751 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
757 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
758 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
759 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
760 && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
767 bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
768 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
771 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
772 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
776 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
777 bool removed = false;
778 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
779 const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
781 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
782 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
785 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
786 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
790 equivalent->addToStyle(element, extractedStyle);
791 conflictingAttributes.append(equivalent->attributeName());
798 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
800 return !m_mutableStyle || !getPropertiesNotIn(m_mutableStyle.get(), computedStyle(node).get())->length();
803 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
805 bool elementIsSpanOrElementEquivalent = false;
806 if (element->hasTagName(HTMLNames::spanTag))
807 elementIsSpanOrElementEquivalent = true;
809 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
811 for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
812 if (HTMLElementEquivalents[i]->matches(element)) {
813 elementIsSpanOrElementEquivalent = true;
819 const NamedNodeMap* attributeMap = element->attributeMap();
820 if (!attributeMap || attributeMap->isEmpty())
821 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
823 unsigned matchedAttributes = 0;
824 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
825 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
826 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
830 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
831 return false; // element is not a span, a html element equivalent, or font element.
833 if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
836 if (element->hasAttribute(HTMLNames::styleAttr)) {
837 if (CSSMutableStyleDeclaration* style = element->inlineStyleDecl()) {
838 CSSMutableStyleDeclaration::const_iterator end = style->end();
839 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
840 if (!isEditingProperty(it->id()))
847 // font with color attribute, span with style attribute, etc...
848 ASSERT(matchedAttributes <= attributeMap->length());
849 return matchedAttributes >= attributeMap->length();
852 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
857 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
858 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
859 // which one of editingStyleAtPosition or computedStyle is called.
860 RefPtr<EditingStyle> style = EditingStyle::create(position, EditingPropertiesInEffect);
862 RefPtr<CSSValue> unicodeBidi;
863 RefPtr<CSSValue> direction;
864 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
865 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
866 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
869 style->m_mutableStyle->diff(m_mutableStyle.get());
870 if (getRGBAFontColor(m_mutableStyle.get()) == getRGBAFontColor(style->m_mutableStyle.get()))
871 m_mutableStyle->removeProperty(CSSPropertyColor);
873 if (hasTransparentBackgroundColor(m_mutableStyle.get())
874 || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
875 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
877 if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
878 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent());
879 if (direction && direction->isPrimitiveValue())
880 m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent());
884 void EditingStyle::mergeTypingStyle(Document* document)
888 RefPtr<EditingStyle> typingStyle = document->frame()->selection()->typingStyle();
889 if (!typingStyle || typingStyle == this)
892 mergeStyle(typingStyle->style(), OverrideValues);
895 void EditingStyle::mergeInlineStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
898 if (!element->inlineStyleDecl())
901 switch (propertiesToInclude) {
903 mergeStyle(element->inlineStyleDecl(), mode);
905 case OnlyEditingInheritableProperties:
906 case EditingPropertiesInEffect:
907 mergeStyle(copyEditingProperties(element->inlineStyleDecl(), propertiesToInclude == EditingPropertiesInEffect).get(), mode);
912 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const StyledElement* element,
913 EditingStyle::CSSPropertyOverrideMode mode, CSSMutableStyleDeclaration* style)
915 return equivalent->matches(element) && !equivalent->propertyExistsInStyle(element->inlineStyleDecl())
916 && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
919 void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
921 mergeInlineStyleOfElement(element, mode, propertiesToInclude);
923 const Vector<OwnPtr<HTMLElementEquivalent> >& elementEquivalents = htmlElementEquivalents();
924 for (size_t i = 0; i < elementEquivalents.size(); ++i) {
925 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
926 elementEquivalents[i]->addToStyle(element, this);
929 const Vector<OwnPtr<HTMLAttributeEquivalent> >& attributeEquivalents = htmlAttributeEquivalents();
930 for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
931 if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
932 continue; // We don't want to include directionality
933 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
934 attributeEquivalents[i]->addToStyle(element, this);
938 PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate)
940 RefPtr<EditingStyle> wrappingStyle;
941 if (shouldAnnotate) {
942 wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
944 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
945 // to help us differentiate those styles from ones that the user has applied.
946 // This helps us get the color of content pasted into blockquotes right.
947 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(context), isMailBlockquote, CanCrossEditingBoundary));
949 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
950 wrappingStyle->collapseTextDecorationProperties();
952 return wrappingStyle.release();
955 wrappingStyle = EditingStyle::create();
957 // When not annotating for interchange, we only preserve inline style declarations.
958 for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) {
959 if (node->isStyledElement()) {
960 wrappingStyle->mergeInlineAndImplicitStyleOfElement(static_cast<StyledElement*>(node), EditingStyle::DoNotOverrideValues,
961 EditingStyle::EditingPropertiesInEffect);
965 return wrappingStyle.release();
969 static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
971 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
972 DEFINE_STATIC_LOCAL(const RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
974 if (valueToMerge->hasValue(underline.get()) && !mergedValue->hasValue(underline.get()))
975 mergedValue->append(underline.get());
977 if (valueToMerge->hasValue(lineThrough.get()) && !mergedValue->hasValue(lineThrough.get()))
978 mergedValue->append(lineThrough.get());
981 void EditingStyle::mergeStyle(CSSMutableStyleDeclaration* style, CSSPropertyOverrideMode mode)
986 if (!m_mutableStyle) {
987 m_mutableStyle = style->copy();
991 CSSMutableStyleDeclaration::const_iterator end = style->end();
992 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
993 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(it->id());
996 // text decorations never override values
997 if ((it->id() == CSSPropertyTextDecoration || it->id() == CSSPropertyWebkitTextDecorationsInEffect) && it->value()->isValueList() && value) {
998 if (value->isValueList()) {
999 mergeTextDecorationValues(static_cast<CSSValueList*>(value.get()), static_cast<CSSValueList*>(it->value()));
1002 value = 0; // text-decoration: none is equivalent to not having the property
1005 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1006 m_mutableStyle->setProperty(it->id(), it->value()->cssText(), it->isImportant(), ec);
1010 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude)
1012 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
1013 RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, rulesToInclude);
1015 for (unsigned i = 0; i < matchedRules->length(); i++) {
1016 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
1017 RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
1018 style->merge(s.get(), true);
1023 return style.release();
1026 void EditingStyle::mergeStyleFromRules(StyledElement* element)
1028 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1029 CSSStyleSelector::AuthorCSSRules | CSSStyleSelector::CrossOriginCSSRules);
1030 // Styles from the inline style declaration, held in the variable "style", take precedence
1031 // over those from matched rules.
1033 styleFromMatchedRules->merge(m_mutableStyle.get());
1036 m_mutableStyle = styleFromMatchedRules;
1039 void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement* element)
1041 mergeStyleFromRules(element);
1043 // The property value, if it's a percentage, may not reflect the actual computed value.
1044 // For example: style="height: 1%; overflow: visible;" in quirksmode
1045 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1046 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
1047 RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
1049 CSSMutableStyleDeclaration::const_iterator end = m_mutableStyle->end();
1050 for (CSSMutableStyleDeclaration::const_iterator it = m_mutableStyle->begin(); it != end; ++it) {
1051 const CSSProperty& property = *it;
1052 CSSValue* value = property.value();
1053 if (!value->isPrimitiveValue())
1055 if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) {
1056 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
1057 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
1061 m_mutableStyle->merge(fromComputedStyle.get());
1064 static void removePropertiesInStyle(CSSMutableStyleDeclaration* styleToRemovePropertiesFrom, CSSMutableStyleDeclaration* style)
1066 Vector<int> propertiesToRemove(style->length());
1068 CSSMutableStyleDeclaration::const_iterator end = style->end();
1069 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it, ++i)
1070 propertiesToRemove[i] = it->id();
1072 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1075 void EditingStyle::removeStyleFromRulesAndContext(StyledElement* element, Node* context)
1078 if (!m_mutableStyle)
1081 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1082 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(element, CSSStyleSelector::AllButEmptyCSSRules);
1083 if (styleFromMatchedRules && styleFromMatchedRules->length())
1084 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules.get());
1086 // 2. Remove style present in context and not overriden by matched rules.
1087 RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1088 if (computedStyle->m_mutableStyle) {
1089 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1090 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1092 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1093 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle.get());
1096 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules.
1097 // These rules are added by serialization code to wrap text nodes.
1098 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1099 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline)
1100 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1101 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone)
1102 m_mutableStyle->removeProperty(CSSPropertyFloat);
1106 void EditingStyle::removePropertiesInElementDefaultStyle(Element* element)
1108 if (!m_mutableStyle || !m_mutableStyle->length())
1111 RefPtr<CSSMutableStyleDeclaration> defaultStyle = styleFromMatchedRulesForElement(element, CSSStyleSelector::UAAndUserCSSRules);
1113 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1116 void EditingStyle::forceInline()
1118 if (!m_mutableStyle)
1119 m_mutableStyle = CSSMutableStyleDeclaration::create();
1120 const bool propertyIsImportant = true;
1121 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1124 int EditingStyle::legacyFontSize(Document* document) const
1126 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1127 if (!cssValue || !cssValue->isPrimitiveValue())
1129 return legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(cssValue.get()),
1130 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1133 PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1135 if (selection.isNone())
1138 Position position = adjustedSelectionStartForStyleComputation(selection);
1140 // If the pos is at the end of a text node, then this node is not fully selected.
1141 // Move it to the next deep equivalent position to avoid removing the style from this node.
1142 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1143 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1144 Node* positionNode = position.containerNode();
1145 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1146 position = nextVisuallyDistinctCandidate(position);
1148 Element* element = position.element();
1152 RefPtr<EditingStyle> style = EditingStyle::create(element, EditingStyle::AllProperties);
1153 style->mergeTypingStyle(element->document());
1155 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1156 // Also, if the selection is a range, ignore the background color at the start of selection,
1157 // and find the background color of the common ancestor.
1158 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1159 RefPtr<Range> range(selection.toNormalizedRange());
1160 ExceptionCode ec = 0;
1161 if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(ec)))
1162 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1168 static void reconcileTextDecorationProperties(CSSMutableStyleDeclaration* style)
1170 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1171 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1172 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1173 ASSERT(!textDecorationsInEffect || !textDecoration);
1174 if (textDecorationsInEffect) {
1175 style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
1176 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1177 textDecoration = textDecorationsInEffect;
1180 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1181 if (textDecoration && !textDecoration->isValueList())
1182 style->removeProperty(CSSPropertyTextDecoration);
1185 StyleChange::StyleChange(EditingStyle* style, const Position& position)
1186 : m_applyBold(false)
1187 , m_applyItalic(false)
1188 , m_applyUnderline(false)
1189 , m_applyLineThrough(false)
1190 , m_applySubscript(false)
1191 , m_applySuperscript(false)
1193 Document* document = position.anchorNode() ? position.anchorNode()->document() : 0;
1194 if (!style || !style->style() || !document || !document->frame())
1197 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
1198 // FIXME: take care of background-color in effect
1199 RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
1201 reconcileTextDecorationProperties(mutableStyle.get());
1202 if (!document->frame()->editor()->shouldStyleWithCSS())
1203 extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
1205 // Changing the whitespace style in a tab span would collapse the tab into a space.
1206 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1207 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1209 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1210 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1211 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1212 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1214 // Save the result for later
1215 m_cssStyle = mutableStyle->cssText().stripWhiteSpace();
1218 static void setTextDecorationProperty(CSSMutableStyleDeclaration* style, const CSSValueList* newTextDecoration, int propertyID)
1220 if (newTextDecoration->length())
1221 style->setProperty(propertyID, newTextDecoration->cssText(), style->getPropertyPriority(propertyID));
1223 // text-decoration: none is redundant since it does not remove any text decorations.
1224 ASSERT(!style->getPropertyPriority(propertyID));
1225 style->removeProperty(propertyID);
1229 void StyleChange::extractTextStyles(Document* document, CSSMutableStyleDeclaration* style, bool shouldUseFixedFontDefaultSize)
1233 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
1234 style->removeProperty(CSSPropertyFontWeight);
1238 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
1239 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1240 style->removeProperty(CSSPropertyFontStyle);
1241 m_applyItalic = true;
1244 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1245 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1246 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1247 if (textDecoration && textDecoration->isValueList()) {
1248 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1249 DEFINE_STATIC_LOCAL(RefPtr<CSSPrimitiveValue>, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1251 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1252 if (newTextDecoration->removeAll(underline.get()))
1253 m_applyUnderline = true;
1254 if (newTextDecoration->removeAll(lineThrough.get()))
1255 m_applyLineThrough = true;
1257 // If trimTextDecorations, delete underline and line-through
1258 setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
1261 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
1262 switch (verticalAlign) {
1264 style->removeProperty(CSSPropertyVerticalAlign);
1265 m_applySubscript = true;
1268 style->removeProperty(CSSPropertyVerticalAlign);
1269 m_applySuperscript = true;
1273 if (style->getPropertyCSSValue(CSSPropertyColor)) {
1274 m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
1275 style->removeProperty(CSSPropertyColor);
1278 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
1279 style->removeProperty(CSSPropertyFontFamily);
1281 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
1282 if (!fontSize->isPrimitiveValue())
1283 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1284 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, static_cast<CSSPrimitiveValue*>(fontSize.get()),
1285 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1286 m_applyFontSize = String::number(legacyFontSize);
1287 style->removeProperty(CSSPropertyFontSize);
1292 static void diffTextDecorations(CSSMutableStyleDeclaration* style, int propertID, CSSValue* refTextDecoration)
1294 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
1295 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
1298 RefPtr<CSSValueList> newTextDecoration = static_cast<CSSValueList*>(textDecoration.get())->copy();
1299 CSSValueList* valuesInRefTextDecoration = static_cast<CSSValueList*>(refTextDecoration);
1301 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
1302 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
1304 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1307 static bool fontWeightIsBold(CSSStyleDeclaration* style)
1310 RefPtr<CSSValue> fontWeight = style->getPropertyCSSValue(CSSPropertyFontWeight);
1314 if (!fontWeight->isPrimitiveValue())
1317 // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
1318 // Collapse all other values to either one of these two states for editing purposes.
1319 switch (static_cast<CSSPrimitiveValue*>(fontWeight.get())->getIdent()) {
1325 case CSSValueNormal:
1335 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
1336 return false; // Make compiler happy
1339 static int getTextAlignment(CSSStyleDeclaration* style)
1341 int textAlign = getIdentifierValue(style, CSSPropertyTextAlign);
1342 switch (textAlign) {
1343 case CSSValueCenter:
1344 case CSSValueWebkitCenter:
1345 return CSSValueCenter;
1346 case CSSValueJustify:
1347 return CSSValueJustify;
1349 case CSSValueWebkitLeft:
1350 return CSSValueLeft;
1352 case CSSValueWebkitRight:
1353 return CSSValueRight;
1355 return CSSValueInvalid;
1358 RefPtr<CSSMutableStyleDeclaration> getPropertiesNotIn(CSSStyleDeclaration* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
1360 ASSERT(styleWithRedundantProperties);
1362 RefPtr<CSSMutableStyleDeclaration> result = styleWithRedundantProperties->copy();
1364 baseStyle->diff(result.get());
1366 RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1367 diffTextDecorations(result.get(), CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
1368 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1370 if (baseStyle->getPropertyCSSValue(CSSPropertyFontSize) && fontWeightIsBold(result.get()) == fontWeightIsBold(baseStyle))
1371 result->removeProperty(CSSPropertyFontWeight);
1373 if (baseStyle->getPropertyCSSValue(CSSPropertyColor) && getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1374 result->removeProperty(CSSPropertyColor);
1376 if (baseStyle->getPropertyCSSValue(CSSPropertyTextAlign) && getTextAlignment(result.get()) == getTextAlignment(baseStyle))
1377 result->removeProperty(CSSPropertyTextAlign);
1382 int getIdentifierValue(CSSStyleDeclaration* style, int propertyID)
1387 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1388 if (!value || !value->isPrimitiveValue())
1391 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent();
1394 static bool isCSSValueLength(CSSPrimitiveValue* value)
1396 return value->primitiveType() >= CSSPrimitiveValue::CSS_PX && value->primitiveType() <= CSSPrimitiveValue::CSS_PC;
1399 int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1401 if (isCSSValueLength(value)) {
1402 int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1403 int legacyFontSize = CSSStyleSelector::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1404 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1405 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1406 if (mode == AlwaysUseLegacyFontSize || CSSStyleSelector::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1407 return legacyFontSize;
1412 if (CSSValueXSmall <= value->getIdent() && value->getIdent() <= CSSValueWebkitXxxLarge)
1413 return value->getIdent() - CSSValueXSmall + 1;
1418 bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
1420 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
1424 if (!cssValue->isPrimitiveValue())
1426 CSSPrimitiveValue* value = static_cast<CSSPrimitiveValue*>(cssValue.get());
1428 if (value->primitiveType() == CSSPrimitiveValue::CSS_RGBCOLOR)
1429 return !alphaChannel(value->getRGBA32Value());
1431 return value->getIdent() == CSSValueTransparent;
1434 PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
1436 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1437 RefPtr<CSSComputedStyleDeclaration> ancestorStyle = computedStyle(ancestor);
1438 if (!hasTransparentBackgroundColor(ancestorStyle.get()))
1439 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);