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 "core/editing/EditingStyle.h"
30 #include "HTMLNames.h"
31 #include "bindings/v8/ExceptionStatePlaceholder.h"
32 #include "core/css/CSSComputedStyleDeclaration.h"
33 #include "core/css/parser/BisonCSSParser.h"
34 #include "core/css/CSSRuleList.h"
35 #include "core/css/CSSStyleRule.h"
36 #include "core/css/CSSValueList.h"
37 #include "core/css/FontSize.h"
38 #include "core/css/RuntimeCSSEnabled.h"
39 #include "core/css/StylePropertySet.h"
40 #include "core/css/StyleRule.h"
41 #include "core/css/resolver/StyleResolver.h"
42 #include "core/dom/Element.h"
43 #include "core/dom/Node.h"
44 #include "core/dom/NodeTraversal.h"
45 #include "core/dom/Position.h"
46 #include "core/dom/QualifiedName.h"
47 #include "core/editing/ApplyStyleCommand.h"
48 #include "core/editing/Editor.h"
49 #include "core/editing/FrameSelection.h"
50 #include "core/editing/HTMLInterchange.h"
51 #include "core/editing/htmlediting.h"
52 #include "core/frame/Frame.h"
53 #include "core/html/HTMLFontElement.h"
54 #include "core/rendering/style/RenderStyle.h"
58 static const CSSPropertyID& textDecorationPropertyForEditing()
60 static const CSSPropertyID property = RuntimeEnabledFeatures::css3TextDecorationsEnabled() ? CSSPropertyTextDecorationLine : CSSPropertyTextDecoration;
64 // Editing style properties must be preserved during editing operation.
65 // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
66 // NOTE: Use either allEditingProperties() or inheritableEditingProperties() to
67 // respect runtime enabling of properties.
68 static const CSSPropertyID staticEditingProperties[] = {
69 CSSPropertyBackgroundColor,
71 CSSPropertyFontFamily,
74 CSSPropertyFontVariant,
75 CSSPropertyFontWeight,
76 CSSPropertyLetterSpacing,
77 CSSPropertyLineHeight,
80 // FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
81 // Decoration feature is no longer experimental.
82 CSSPropertyTextDecoration,
83 CSSPropertyTextDecorationLine,
84 CSSPropertyTextIndent,
85 CSSPropertyTextTransform,
86 CSSPropertyWhiteSpace,
88 CSSPropertyWordSpacing,
89 CSSPropertyWebkitTextDecorationsInEffect,
90 CSSPropertyWebkitTextFillColor,
91 CSSPropertyWebkitTextStrokeColor,
92 CSSPropertyWebkitTextStrokeWidth,
95 enum EditingPropertiesType { OnlyInheritableEditingProperties, AllEditingProperties };
97 static const Vector<CSSPropertyID>& allEditingProperties()
99 DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
100 if (properties.isEmpty()) {
101 RuntimeCSSEnabled::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
102 if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
103 properties.remove(properties.find(CSSPropertyTextDecoration));
108 static const Vector<CSSPropertyID>& inheritableEditingProperties()
110 DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
111 if (properties.isEmpty()) {
112 RuntimeCSSEnabled::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties);
113 for (size_t index = 0; index < properties.size();) {
114 if (!CSSProperty::isInheritedProperty(properties[index])) {
115 properties.remove(index);
124 template <class StyleDeclarationType>
125 static PassRefPtr<MutableStylePropertySet> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesType type = OnlyInheritableEditingProperties)
127 if (type == AllEditingProperties)
128 return style->copyPropertiesInSet(allEditingProperties());
129 return style->copyPropertiesInSet(inheritableEditingProperties());
132 static inline bool isEditingProperty(int id)
134 return allEditingProperties().contains(static_cast<CSSPropertyID>(id));
137 static PassRefPtr<MutableStylePropertySet> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style, EditingPropertiesType type = OnlyInheritableEditingProperties)
140 return MutableStylePropertySet::create();
141 return copyEditingProperties(style.get(), type);
144 static PassRefPtr<MutableStylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle);
145 enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
146 static int legacyFontSizeFromCSSValue(Document*, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
147 static bool isTransparentColorValue(CSSValue*);
148 static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
149 static bool hasTransparentBackgroundColor(StylePropertySet*);
150 static PassRefPtr<CSSValue> backgroundColorInEffect(Node*);
152 class HTMLElementEquivalent {
153 WTF_MAKE_FAST_ALLOCATED;
155 static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, CSSValueID primitiveValue, const QualifiedName& tagName)
157 return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName));
160 virtual ~HTMLElementEquivalent() { }
161 virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); }
162 virtual bool hasAttribute() const { return false; }
163 virtual bool propertyExistsInStyle(const StylePropertySet* style) const { return style->getPropertyCSSValue(m_propertyID); }
164 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const;
165 virtual void addToStyle(Element*, EditingStyle*) const;
168 HTMLElementEquivalent(CSSPropertyID);
169 HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
170 HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName);
171 const CSSPropertyID m_propertyID;
172 const RefPtrWillBePersistent<CSSPrimitiveValue> m_primitiveValue;
173 const QualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global.
176 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
182 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
184 , m_tagName(&tagName)
188 HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primitiveValue, const QualifiedName& tagName)
190 , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
191 , m_tagName(&tagName)
193 ASSERT(primitiveValue != CSSValueInvalid);
196 bool HTMLElementEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
198 RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID);
199 return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID();
202 void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
204 style->setProperty(m_propertyID, m_primitiveValue->cssText());
207 class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
209 static PassOwnPtr<HTMLElementEquivalent> create(CSSValueID primitiveValue, const QualifiedName& tagName)
211 return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName));
213 virtual bool propertyExistsInStyle(const StylePropertySet*) const OVERRIDE;
214 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const OVERRIDE;
217 HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName);
220 HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName)
221 : HTMLElementEquivalent(textDecorationPropertyForEditing(), primitiveValue, tagName)
222 // m_propertyID is used in HTMLElementEquivalent::addToStyle
226 bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StylePropertySet* style) const
228 return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)
229 || style->getPropertyCSSValue(textDecorationPropertyForEditing());
232 bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
234 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
236 styleValue = style->getPropertyCSSValue(textDecorationPropertyForEditing());
237 return matches(element) && styleValue && styleValue->isValueList() && toCSSValueList(styleValue.get())->hasValue(m_primitiveValue.get());
240 class HTMLAttributeEquivalent : public HTMLElementEquivalent {
242 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& tagName, const QualifiedName& attrName)
244 return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName));
246 static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName)
248 return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName));
251 virtual bool matches(const Element* elem) const OVERRIDE { return HTMLElementEquivalent::matches(elem) && elem->hasAttribute(m_attrName); }
252 virtual bool hasAttribute() const OVERRIDE { return true; }
253 virtual bool valueIsPresentInStyle(Element*, StylePropertySet*) const OVERRIDE;
254 virtual void addToStyle(Element*, EditingStyle*) const OVERRIDE;
255 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
256 inline const QualifiedName& attributeName() const { return m_attrName; }
259 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
260 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
261 const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
264 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
265 : HTMLElementEquivalent(id, tagName)
266 , m_attrName(attrName)
270 HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
271 : HTMLElementEquivalent(id)
272 , m_attrName(attrName)
276 bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element* element, StylePropertySet* style) const
278 RefPtr<CSSValue> value = attributeValueAsCSSValue(element);
279 RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID);
281 return compareCSSValuePtr(value, styleValue);
284 void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
286 if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
287 style->setProperty(m_propertyID, value->cssText());
290 PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
293 if (!element->hasAttribute(m_attrName))
296 RefPtr<MutableStylePropertySet> dummyStyle;
297 dummyStyle = MutableStylePropertySet::create();
298 dummyStyle->setProperty(m_propertyID, element->getAttribute(m_attrName));
299 return dummyStyle->getPropertyCSSValue(m_propertyID);
302 class HTMLFontSizeEquivalent FINAL : public HTMLAttributeEquivalent {
304 static PassOwnPtr<HTMLFontSizeEquivalent> create()
306 return adoptPtr(new HTMLFontSizeEquivalent());
308 virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const OVERRIDE;
311 HTMLFontSizeEquivalent();
314 HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
315 : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
319 PassRefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
322 if (!element->hasAttribute(m_attrName))
325 if (!HTMLFontElement::cssValueFromFontSizeNumber(element->getAttribute(m_attrName), size))
327 return CSSPrimitiveValue::createIdentifier(size);
330 float EditingStyle::NoFontDelta = 0.0f;
332 EditingStyle::EditingStyle()
333 : m_shouldUseFixedDefaultFontSize(false)
334 , m_fontSizeDelta(NoFontDelta)
338 EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
339 : m_shouldUseFixedDefaultFontSize(false)
340 , m_fontSizeDelta(NoFontDelta)
342 init(node, propertiesToInclude);
345 EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
346 : m_shouldUseFixedDefaultFontSize(false)
347 , m_fontSizeDelta(NoFontDelta)
349 init(position.deprecatedNode(), propertiesToInclude);
352 EditingStyle::EditingStyle(const StylePropertySet* style)
353 : m_mutableStyle(style ? style->mutableCopy() : 0)
354 , m_shouldUseFixedDefaultFontSize(false)
355 , m_fontSizeDelta(NoFontDelta)
357 extractFontSizeDelta();
360 EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
362 , m_shouldUseFixedDefaultFontSize(false)
363 , m_fontSizeDelta(NoFontDelta)
365 setProperty(propertyID, value);
368 EditingStyle::~EditingStyle()
372 static RGBA32 cssValueToRGBA(CSSValue* colorValue)
374 if (!colorValue || !colorValue->isPrimitiveValue())
375 return Color::transparent;
377 CSSPrimitiveValue* primitiveColor = toCSSPrimitiveValue(colorValue);
378 if (primitiveColor->isRGBColor())
379 return primitiveColor->getRGBA32Value();
382 BisonCSSParser::parseColor(rgba, colorValue->cssText());
386 static inline RGBA32 getRGBAFontColor(CSSStyleDeclaration* style)
388 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyColor).get());
391 static inline RGBA32 getRGBAFontColor(StylePropertySet* style)
393 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyColor).get());
396 static inline RGBA32 getRGBABackgroundColor(CSSStyleDeclaration* style)
398 return cssValueToRGBA(style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor).get());
401 static inline RGBA32 getRGBABackgroundColor(StylePropertySet* style)
403 return cssValueToRGBA(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get());
406 static inline RGBA32 rgbaBackgroundColorInEffect(Node* node)
408 return cssValueToRGBA(backgroundColorInEffect(node).get());
411 static int textAlignResolvingStartAndEnd(int textAlign, int direction)
415 case CSSValueWebkitCenter:
416 return CSSValueCenter;
417 case CSSValueJustify:
418 return CSSValueJustify;
420 case CSSValueWebkitLeft:
423 case CSSValueWebkitRight:
424 return CSSValueRight;
426 return direction != CSSValueRtl ? CSSValueLeft : CSSValueRight;
428 return direction == CSSValueRtl ? CSSValueRight : CSSValueLeft;
430 return CSSValueInvalid;
434 static int textAlignResolvingStartAndEnd(T* style)
436 return textAlignResolvingStartAndEnd(getIdentifierValue(style, CSSPropertyTextAlign), getIdentifierValue(style, CSSPropertyDirection));
439 void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
441 if (isTabSpanTextNode(node))
442 node = tabSpanNode(node)->parentNode();
443 else if (isTabSpanNode(node))
444 node = node->parentNode();
446 RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node);
447 m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copyProperties() : editingStyleFromComputedStyle(computedStyleAtPosition);
449 if (propertiesToInclude == EditingPropertiesInEffect) {
450 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
451 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
452 if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect))
453 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
456 if (node && node->computedStyle()) {
457 RenderStyle* renderStyle = node->computedStyle();
458 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
459 replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get());
462 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition->useFixedFontDefaultSize();
463 extractFontSizeDelta();
466 void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle)
468 // If a node's text fill color is currentColor, then its children use
469 // their font-color as their text fill color (they don't
470 // inherit it). Likewise for stroke color.
471 if (renderStyle->textFillColor().isCurrentColor())
472 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
473 if (renderStyle->textStrokeColor().isCurrentColor())
474 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
477 void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important)
480 m_mutableStyle = MutableStylePropertySet::create();
482 m_mutableStyle->setProperty(propertyID, value, important);
485 void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle)
488 if (renderStyle->fontDescription().keywordSize())
489 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText());
492 void EditingStyle::extractFontSizeDelta()
497 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
498 // Explicit font size overrides any delta.
499 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
503 // Get the adjustment amount out of the style.
504 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
505 if (!value || !value->isPrimitiveValue())
508 CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get());
510 // Only PX handled now. If we handle more types in the future, perhaps
511 // a switch statement here would be more appropriate.
512 if (!primitiveValue->isPx())
515 m_fontSizeDelta = primitiveValue->getFloatValue();
516 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
519 bool EditingStyle::isEmpty() const
521 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta;
524 bool EditingStyle::textDirection(WritingDirection& writingDirection) const
529 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
530 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
533 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
534 if (unicodeBidiValue == CSSValueEmbed) {
535 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
536 if (!direction || !direction->isPrimitiveValue())
539 writingDirection = toCSSPrimitiveValue(direction.get())->getValueID() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
544 if (unicodeBidiValue == CSSValueNormal) {
545 writingDirection = NaturalWritingDirection;
552 void EditingStyle::overrideWithStyle(const StylePropertySet* style)
554 if (!style || style->isEmpty())
557 m_mutableStyle = MutableStylePropertySet::create();
558 m_mutableStyle->mergeAndOverrideOnConflict(style);
559 extractFontSizeDelta();
562 void EditingStyle::clear()
564 m_mutableStyle.clear();
565 m_shouldUseFixedDefaultFontSize = false;
566 m_fontSizeDelta = NoFontDelta;
569 PassRefPtr<EditingStyle> EditingStyle::copy() const
571 RefPtr<EditingStyle> copy = EditingStyle::create();
573 copy->m_mutableStyle = m_mutableStyle->mutableCopy();
574 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
575 copy->m_fontSizeDelta = m_fontSizeDelta;
579 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
581 RefPtr<EditingStyle> blockProperties = EditingStyle::create();
583 return blockProperties;
585 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
586 m_mutableStyle->removeBlockProperties();
588 return blockProperties;
591 PassRefPtr<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
593 RefPtr<EditingStyle> textDirection = EditingStyle::create();
594 textDirection->m_mutableStyle = MutableStylePropertySet::create();
595 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
596 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
597 m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
599 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
600 m_mutableStyle->removeProperty(CSSPropertyDirection);
602 return textDirection;
605 void EditingStyle::removeBlockProperties()
610 m_mutableStyle->removeBlockProperties();
613 void EditingStyle::removeStyleAddedByNode(Node* node)
615 if (!node || !node->parentNode())
617 RefPtr<MutableStylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
618 RefPtr<MutableStylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
619 nodeStyle->removeEquivalentProperties(parentStyle.get());
620 m_mutableStyle->removeEquivalentProperties(nodeStyle.get());
623 void EditingStyle::removeStyleConflictingWithStyleOfNode(Node* node)
625 if (!node || !node->parentNode() || !m_mutableStyle)
628 RefPtr<MutableStylePropertySet> parentStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node->parentNode()), AllEditingProperties);
629 RefPtr<MutableStylePropertySet> nodeStyle = editingStyleFromComputedStyle(CSSComputedStyleDeclaration::create(node), AllEditingProperties);
630 nodeStyle->removeEquivalentProperties(parentStyle.get());
632 unsigned propertyCount = nodeStyle->propertyCount();
633 for (unsigned i = 0; i < propertyCount; ++i)
634 m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id());
637 void EditingStyle::collapseTextDecorationProperties()
642 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
643 if (!textDecorationsInEffect)
646 if (textDecorationsInEffect->isValueList())
647 m_mutableStyle->setProperty(textDecorationPropertyForEditing(), textDecorationsInEffect->cssText(), m_mutableStyle->propertyIsImportant(textDecorationPropertyForEditing()));
649 m_mutableStyle->removeProperty(textDecorationPropertyForEditing());
650 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
653 // CSS properties that create a visual difference only when applied to text.
654 static const CSSPropertyID textOnlyProperties[] = {
655 // FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
656 // Decoration feature is no longer experimental.
657 CSSPropertyTextDecoration,
658 CSSPropertyTextDecorationLine,
659 CSSPropertyWebkitTextDecorationsInEffect,
660 CSSPropertyFontStyle,
661 CSSPropertyFontWeight,
665 TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
667 if (!style || !style->m_mutableStyle)
668 return FalseTriState;
669 return triStateOfStyle(style->m_mutableStyle->ensureCSSStyleDeclaration(), DoNotIgnoreTextOnlyProperties);
672 TriState EditingStyle::triStateOfStyle(CSSStyleDeclaration* styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
674 RefPtr<MutableStylePropertySet> difference = getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
676 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
677 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
679 if (difference->isEmpty())
681 if (difference->propertyCount() == m_mutableStyle->propertyCount())
682 return FalseTriState;
684 return MixedTriState;
687 TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
689 if (!selection.isCaretOrRange())
690 return FalseTriState;
692 if (selection.isCaret())
693 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
695 TriState state = FalseTriState;
696 bool nodeIsStart = true;
697 for (Node* node = selection.start().deprecatedNode(); node; node = NodeTraversal::next(*node)) {
698 if (node->renderer() && node->rendererIsEditable()) {
699 RefPtr<CSSComputedStyleDeclaration> nodeStyle = CSSComputedStyleDeclaration::create(node);
701 TriState nodeState = triStateOfStyle(nodeStyle.get(), node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
705 } else if (state != nodeState && node->isTextNode()) {
706 state = MixedTriState;
711 if (node == selection.end().deprecatedNode())
718 bool EditingStyle::conflictsWithInlineStyleOfElement(Element* element, EditingStyle* extractedStyle, Vector<CSSPropertyID>* conflictingProperties) const
721 ASSERT(!conflictingProperties || conflictingProperties->isEmpty());
723 const StylePropertySet* inlineStyle = element->inlineStyle();
724 if (!m_mutableStyle || !inlineStyle)
727 unsigned propertyCount = m_mutableStyle->propertyCount();
728 for (unsigned i = 0; i < propertyCount; ++i) {
729 CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
731 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
732 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(element))
735 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(textDecorationPropertyForEditing())) {
736 if (!conflictingProperties)
738 conflictingProperties->append(CSSPropertyTextDecoration);
739 // Because text-decoration expands to text-decoration-line when CSS3
740 // Text Decoration is enabled, we also state it as conflicting.
741 if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
742 conflictingProperties->append(CSSPropertyTextDecorationLine);
744 extractedStyle->setProperty(textDecorationPropertyForEditing(), inlineStyle->getPropertyValue(textDecorationPropertyForEditing()), inlineStyle->propertyIsImportant(textDecorationPropertyForEditing()));
748 if (!inlineStyle->getPropertyCSSValue(propertyID))
751 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
752 if (!conflictingProperties)
754 conflictingProperties->append(CSSPropertyDirection);
756 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
759 if (!conflictingProperties)
762 conflictingProperties->append(propertyID);
765 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
768 return conflictingProperties && !conflictingProperties->isEmpty();
771 static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents()
773 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ());
775 if (!HTMLElementEquivalents.size()) {
776 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
777 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
778 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
779 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
780 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
781 HTMLElementEquivalents.append(HTMLElementEquivalent::create(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
783 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueUnderline, HTMLNames::uTag));
784 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::sTag));
785 HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(CSSValueLineThrough, HTMLNames::strikeTag));
788 return HTMLElementEquivalents;
792 bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement* element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
797 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
798 for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
799 const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
800 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(m_mutableStyle.get())
801 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
803 equivalent->addToStyle(element, extractedStyle);
810 static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents()
812 DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ());
814 if (!HTMLAttributeEquivalents.size()) {
815 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
816 // of exactly one element except dirAttr.
817 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
818 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
819 HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
821 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyDirection, HTMLNames::dirAttr));
822 HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
825 return HTMLAttributeEquivalents;
828 bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement* element) const
834 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
835 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
836 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->propertyExistsInStyle(m_mutableStyle.get())
837 && !HTMLAttributeEquivalents[i]->valueIsPresentInStyle(element, m_mutableStyle.get()))
844 bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
845 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
848 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
849 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
853 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
854 bool removed = false;
855 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
856 const HTMLAttributeEquivalent* equivalent = HTMLAttributeEquivalents[i].get();
858 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
859 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
862 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(m_mutableStyle.get())
863 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
867 equivalent->addToStyle(element, extractedStyle);
868 conflictingAttributes.append(equivalent->attributeName());
875 bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const
877 return !m_mutableStyle || getPropertiesNotIn(m_mutableStyle.get(), CSSComputedStyleDeclaration::create(node).get())->isEmpty();
880 bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element)
882 bool elementIsSpanOrElementEquivalent = false;
883 if (element->hasTagName(HTMLNames::spanTag))
884 elementIsSpanOrElementEquivalent = true;
886 const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents();
888 for (i = 0; i < HTMLElementEquivalents.size(); ++i) {
889 if (HTMLElementEquivalents[i]->matches(element)) {
890 elementIsSpanOrElementEquivalent = true;
896 if (!element->hasAttributes())
897 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
899 unsigned matchedAttributes = 0;
900 const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents();
901 for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) {
902 if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr)
906 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
907 return false; // element is not a span, a html element equivalent, or font element.
909 if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass)
912 if (element->hasAttribute(HTMLNames::styleAttr)) {
913 if (const StylePropertySet* style = element->inlineStyle()) {
914 unsigned propertyCount = style->propertyCount();
915 for (unsigned i = 0; i < propertyCount; ++i) {
916 if (!isEditingProperty(style->propertyAt(i).id()))
923 // font with color attribute, span with style attribute, etc...
924 ASSERT(matchedAttributes <= element->attributeCount());
925 return matchedAttributes >= element->attributeCount();
928 void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
933 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
934 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
935 // which one of editingStyleAtPosition or computedStyle is called.
936 RefPtr<EditingStyle> editingStyleAtPosition = EditingStyle::create(position, EditingPropertiesInEffect);
937 StylePropertySet* styleAtPosition = editingStyleAtPosition->m_mutableStyle.get();
939 RefPtr<CSSValue> unicodeBidi;
940 RefPtr<CSSValue> direction;
941 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
942 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
943 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
946 m_mutableStyle->removeEquivalentProperties(styleAtPosition);
948 if (textAlignResolvingStartAndEnd(m_mutableStyle.get()) == textAlignResolvingStartAndEnd(styleAtPosition))
949 m_mutableStyle->removeProperty(CSSPropertyTextAlign);
951 if (getRGBAFontColor(m_mutableStyle.get()) == getRGBAFontColor(styleAtPosition))
952 m_mutableStyle->removeProperty(CSSPropertyColor);
954 if (hasTransparentBackgroundColor(m_mutableStyle.get())
955 || cssValueToRGBA(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
956 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
958 if (unicodeBidi && unicodeBidi->isPrimitiveValue()) {
959 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, toCSSPrimitiveValue(unicodeBidi.get())->getValueID());
960 if (direction && direction->isPrimitiveValue())
961 m_mutableStyle->setProperty(CSSPropertyDirection, toCSSPrimitiveValue(direction.get())->getValueID());
965 void EditingStyle::mergeTypingStyle(Document* document)
969 RefPtr<EditingStyle> typingStyle = document->frame()->selection().typingStyle();
970 if (!typingStyle || typingStyle == this)
973 mergeStyle(typingStyle->style(), OverrideValues);
976 void EditingStyle::mergeInlineStyleOfElement(Element* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
979 if (!element->inlineStyle())
982 switch (propertiesToInclude) {
984 mergeStyle(element->inlineStyle(), mode);
986 case OnlyEditingInheritableProperties:
987 mergeStyle(copyEditingProperties(element->inlineStyle(), OnlyInheritableEditingProperties).get(), mode);
989 case EditingPropertiesInEffect:
990 mergeStyle(copyEditingProperties(element->inlineStyle(), AllEditingProperties).get(), mode);
995 static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent* equivalent, const Element* element,
996 EditingStyle::CSSPropertyOverrideMode mode, StylePropertySet* style)
998 return equivalent->matches(element) && (!element->inlineStyle() || !equivalent->propertyExistsInStyle(element->inlineStyle()))
999 && (mode == EditingStyle::OverrideValues || !equivalent->propertyExistsInStyle(style));
1002 static PassRefPtr<MutableStylePropertySet> extractEditingProperties(const StylePropertySet* style, EditingStyle::PropertiesToInclude propertiesToInclude)
1007 switch (propertiesToInclude) {
1008 case EditingStyle::AllProperties:
1009 case EditingStyle::EditingPropertiesInEffect:
1010 return copyEditingProperties(style, AllEditingProperties);
1011 case EditingStyle::OnlyEditingInheritableProperties:
1012 return copyEditingProperties(style, OnlyInheritableEditingProperties);
1015 ASSERT_NOT_REACHED();
1019 void EditingStyle::mergeInlineAndImplicitStyleOfElement(Element* element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
1021 RefPtr<EditingStyle> styleFromRules = EditingStyle::create();
1022 styleFromRules->mergeStyleFromRulesForSerialization(element);
1023 styleFromRules->m_mutableStyle = extractEditingProperties(styleFromRules->m_mutableStyle.get(), propertiesToInclude);
1024 mergeStyle(styleFromRules->m_mutableStyle.get(), mode);
1026 mergeInlineStyleOfElement(element, mode, propertiesToInclude);
1028 const Vector<OwnPtr<HTMLElementEquivalent> >& elementEquivalents = htmlElementEquivalents();
1029 for (size_t i = 0; i < elementEquivalents.size(); ++i) {
1030 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(elementEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1031 elementEquivalents[i]->addToStyle(element, this);
1034 const Vector<OwnPtr<HTMLAttributeEquivalent> >& attributeEquivalents = htmlAttributeEquivalents();
1035 for (size_t i = 0; i < attributeEquivalents.size(); ++i) {
1036 if (attributeEquivalents[i]->attributeName() == HTMLNames::dirAttr)
1037 continue; // We don't want to include directionality
1038 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(attributeEquivalents[i].get(), element, mode, m_mutableStyle.get()))
1039 attributeEquivalents[i]->addToStyle(element, this);
1043 PassRefPtr<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node* context, bool shouldAnnotate)
1045 RefPtr<EditingStyle> wrappingStyle;
1046 if (shouldAnnotate) {
1047 wrappingStyle = EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
1049 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
1050 // to help us differentiate those styles from ones that the user has applied.
1051 // This helps us get the color of content pasted into blockquotes right.
1052 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(context), isMailBlockquote, CanCrossEditingBoundary));
1054 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
1055 wrappingStyle->collapseTextDecorationProperties();
1057 return wrappingStyle.release();
1060 wrappingStyle = EditingStyle::create();
1062 // When not annotating for interchange, we only preserve inline style declarations.
1063 for (Node* node = context; node && !node->isDocumentNode(); node = node->parentNode()) {
1064 if (node->isStyledElement() && !isMailBlockquote(node)) {
1065 wrappingStyle->mergeInlineAndImplicitStyleOfElement(toElement(node), EditingStyle::DoNotOverrideValues,
1066 EditingStyle::EditingPropertiesInEffect);
1070 return wrappingStyle.release();
1074 static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge)
1076 DEFINE_STATIC_REF(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1077 DEFINE_STATIC_REF(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1079 if (valueToMerge->hasValue(underline) && !mergedValue->hasValue(underline))
1080 mergedValue->append(underline);
1082 if (valueToMerge->hasValue(lineThrough) && !mergedValue->hasValue(lineThrough))
1083 mergedValue->append(lineThrough);
1086 void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode)
1091 if (!m_mutableStyle) {
1092 m_mutableStyle = style->mutableCopy();
1096 unsigned propertyCount = style->propertyCount();
1097 for (unsigned i = 0; i < propertyCount; ++i) {
1098 StylePropertySet::PropertyReference property = style->propertyAt(i);
1099 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
1101 // text decorations never override values
1102 if ((property.id() == textDecorationPropertyForEditing() || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) {
1103 if (value->isValueList()) {
1104 mergeTextDecorationValues(toCSSValueList(value.get()), toCSSValueList(property.value()));
1107 value = 0; // text-decoration: none is equivalent to not having the property
1110 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1111 m_mutableStyle->setProperty(property.id(), property.value()->cssText(), property.isImportant());
1115 static PassRefPtr<MutableStylePropertySet> styleFromMatchedRulesForElement(Element* element, unsigned rulesToInclude)
1117 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create();
1118 RefPtr<StyleRuleList> matchedRules = element->document().ensureStyleResolver().styleRulesForElement(element, rulesToInclude);
1120 for (unsigned i = 0; i < matchedRules->m_list.size(); ++i)
1121 style->mergeAndOverrideOnConflict(matchedRules->m_list[i]->properties());
1123 return style.release();
1126 void EditingStyle::mergeStyleFromRules(Element* element)
1128 RefPtr<MutableStylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1129 StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules);
1130 // Styles from the inline style declaration, held in the variable "style", take precedence
1131 // over those from matched rules.
1133 styleFromMatchedRules->mergeAndOverrideOnConflict(m_mutableStyle.get());
1136 m_mutableStyle = styleFromMatchedRules;
1139 void EditingStyle::mergeStyleFromRulesForSerialization(Element* element)
1141 mergeStyleFromRules(element);
1143 // The property value, if it's a percentage, may not reflect the actual computed value.
1144 // For example: style="height: 1%; overflow: visible;" in quirksmode
1145 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1146 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = CSSComputedStyleDeclaration::create(element);
1147 RefPtr<MutableStylePropertySet> fromComputedStyle = MutableStylePropertySet::create();
1149 unsigned propertyCount = m_mutableStyle->propertyCount();
1150 for (unsigned i = 0; i < propertyCount; ++i) {
1151 StylePropertySet::PropertyReference property = m_mutableStyle->propertyAt(i);
1152 CSSValue* value = property.value();
1153 if (!value->isPrimitiveValue())
1155 if (toCSSPrimitiveValue(value)->isPercentage()) {
1156 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
1157 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
1161 m_mutableStyle->mergeAndOverrideOnConflict(fromComputedStyle.get());
1164 static void removePropertiesInStyle(MutableStylePropertySet* styleToRemovePropertiesFrom, StylePropertySet* style)
1166 unsigned propertyCount = style->propertyCount();
1167 Vector<CSSPropertyID> propertiesToRemove(propertyCount);
1168 for (unsigned i = 0; i < propertyCount; ++i)
1169 propertiesToRemove[i] = style->propertyAt(i).id();
1171 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1174 void EditingStyle::removeStyleFromRulesAndContext(Element* element, Node* context)
1177 if (!m_mutableStyle)
1180 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1181 RefPtr<MutableStylePropertySet> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules);
1182 if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
1183 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), styleFromMatchedRules->ensureCSSStyleDeclaration());
1185 // 2. Remove style present in context and not overriden by matched rules.
1186 RefPtr<EditingStyle> computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1187 if (computedStyle->m_mutableStyle) {
1188 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1189 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1191 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1192 m_mutableStyle = getPropertiesNotIn(m_mutableStyle.get(), computedStyle->m_mutableStyle->ensureCSSStyleDeclaration());
1195 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overriden by rules.
1196 // These rules are added by serialization code to wrap text nodes.
1197 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1198 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) == CSSValueInline)
1199 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1200 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) == CSSValueNone)
1201 m_mutableStyle->removeProperty(CSSPropertyFloat);
1205 void EditingStyle::removePropertiesInElementDefaultStyle(Element* element)
1207 if (!m_mutableStyle || m_mutableStyle->isEmpty())
1210 RefPtr<StylePropertySet> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules);
1212 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1215 void EditingStyle::forceInline()
1217 if (!m_mutableStyle)
1218 m_mutableStyle = MutableStylePropertySet::create();
1219 const bool propertyIsImportant = true;
1220 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1223 int EditingStyle::legacyFontSize(Document* document) const
1225 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1226 if (!cssValue || !cssValue->isPrimitiveValue())
1228 return legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(cssValue.get()),
1229 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1232 PassRefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1234 if (selection.isNone())
1237 Position position = adjustedSelectionStartForStyleComputation(selection);
1239 // If the pos is at the end of a text node, then this node is not fully selected.
1240 // Move it to the next deep equivalent position to avoid removing the style from this node.
1241 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1242 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1243 Node* positionNode = position.containerNode();
1244 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1245 position = nextVisuallyDistinctCandidate(position);
1247 Element* element = position.element();
1251 RefPtr<EditingStyle> style = EditingStyle::create(element, EditingStyle::AllProperties);
1252 style->mergeTypingStyle(&element->document());
1254 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1255 // Also, if the selection is a range, ignore the background color at the start of selection,
1256 // and find the background color of the common ancestor.
1257 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1258 RefPtr<Range> range(selection.toNormalizedRange());
1259 if (PassRefPtr<CSSValue> value = backgroundColorInEffect(range->commonAncestorContainer(IGNORE_EXCEPTION)))
1260 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1266 WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& selection, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings)
1268 hasNestedOrMultipleEmbeddings = true;
1270 if (selection.isNone())
1271 return NaturalWritingDirection;
1273 Position position = selection.start().downstream();
1275 Node* node = position.deprecatedNode();
1277 return NaturalWritingDirection;
1280 if (selection.isRange()) {
1281 end = selection.end().upstream();
1283 ASSERT(end.document());
1284 Node* pastLast = Range::create(*end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode();
1285 for (Node* n = node; n && n != pastLast; n = NodeTraversal::next(*n)) {
1286 if (!n->isStyledElement())
1289 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(n);
1290 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1291 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1294 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
1295 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
1296 return NaturalWritingDirection;
1300 if (selection.isCaret()) {
1301 WritingDirection direction;
1302 if (typingStyle && typingStyle->textDirection(direction)) {
1303 hasNestedOrMultipleEmbeddings = false;
1306 node = selection.visibleStart().deepEquivalent().deprecatedNode();
1309 // 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
1311 Node* block = enclosingBlock(node);
1312 WritingDirection foundDirection = NaturalWritingDirection;
1314 for (; node != block; node = node->parentNode()) {
1315 if (!node->isStyledElement())
1318 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
1319 RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1320 if (!unicodeBidi || !unicodeBidi->isPrimitiveValue())
1323 CSSValueID unicodeBidiValue = toCSSPrimitiveValue(unicodeBidi.get())->getValueID();
1324 if (unicodeBidiValue == CSSValueNormal)
1327 if (unicodeBidiValue == CSSValueBidiOverride)
1328 return NaturalWritingDirection;
1330 ASSERT(unicodeBidiValue == CSSValueEmbed);
1331 RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection);
1332 if (!direction || !direction->isPrimitiveValue())
1335 int directionValue = toCSSPrimitiveValue(direction.get())->getValueID();
1336 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
1339 if (foundDirection != NaturalWritingDirection)
1340 return NaturalWritingDirection;
1342 // In the range case, make sure that the embedding element persists until the end of the range.
1343 if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(node))
1344 return NaturalWritingDirection;
1346 foundDirection = directionValue == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection;
1348 hasNestedOrMultipleEmbeddings = false;
1349 return foundDirection;
1352 static void reconcileTextDecorationProperties(MutableStylePropertySet* style)
1354 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1355 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(textDecorationPropertyForEditing());
1356 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1357 ASSERT(!textDecorationsInEffect || !textDecoration);
1358 if (textDecorationsInEffect) {
1359 style->setProperty(textDecorationPropertyForEditing(), textDecorationsInEffect->cssText());
1360 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1361 textDecoration = textDecorationsInEffect;
1364 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1365 if (textDecoration && !textDecoration->isValueList())
1366 style->removeProperty(textDecorationPropertyForEditing());
1369 StyleChange::StyleChange(EditingStyle* style, const Position& position)
1370 : m_applyBold(false)
1371 , m_applyItalic(false)
1372 , m_applyUnderline(false)
1373 , m_applyLineThrough(false)
1374 , m_applySubscript(false)
1375 , m_applySuperscript(false)
1377 Document* document = position.document();
1378 if (!style || !style->style() || !document || !document->frame())
1381 RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle();
1382 // FIXME: take care of background-color in effect
1383 RefPtr<MutableStylePropertySet> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get());
1385 reconcileTextDecorationProperties(mutableStyle.get());
1386 if (!document->frame()->editor().shouldStyleWithCSS())
1387 extractTextStyles(document, mutableStyle.get(), computedStyle->useFixedFontDefaultSize());
1389 // Changing the whitespace style in a tab span would collapse the tab into a space.
1390 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1391 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1393 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1394 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1395 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1396 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1398 // Save the result for later
1399 m_cssStyle = mutableStyle->asText().stripWhiteSpace();
1402 static void setTextDecorationProperty(MutableStylePropertySet* style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
1404 if (newTextDecoration->length())
1405 style->setProperty(propertyID, newTextDecoration->cssText(), style->propertyIsImportant(propertyID));
1407 // text-decoration: none is redundant since it does not remove any text decorations.
1408 ASSERT(!style->propertyIsImportant(propertyID));
1409 style->removeProperty(propertyID);
1413 void StyleChange::extractTextStyles(Document* document, MutableStylePropertySet* style, bool shouldUseFixedFontDefaultSize)
1417 if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
1418 style->removeProperty(CSSPropertyFontWeight);
1422 int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
1423 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1424 style->removeProperty(CSSPropertyFontStyle);
1425 m_applyItalic = true;
1428 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1429 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1430 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(textDecorationPropertyForEditing());
1431 if (textDecoration && textDecoration->isValueList()) {
1432 DEFINE_STATIC_REF(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline)));
1433 DEFINE_STATIC_REF(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough)));
1435 RefPtrWillBeRawPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy();
1436 if (newTextDecoration->removeAll(underline))
1437 m_applyUnderline = true;
1438 if (newTextDecoration->removeAll(lineThrough))
1439 m_applyLineThrough = true;
1441 // If trimTextDecorations, delete underline and line-through
1442 setTextDecorationProperty(style, newTextDecoration.get(), textDecorationPropertyForEditing());
1445 int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
1446 switch (verticalAlign) {
1448 style->removeProperty(CSSPropertyVerticalAlign);
1449 m_applySubscript = true;
1452 style->removeProperty(CSSPropertyVerticalAlign);
1453 m_applySuperscript = true;
1457 if (style->getPropertyCSSValue(CSSPropertyColor)) {
1458 m_applyFontColor = Color(getRGBAFontColor(style)).serialized();
1459 style->removeProperty(CSSPropertyColor);
1462 m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
1463 // Remove single quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448
1464 m_applyFontFace.replaceWithLiteral('\'', "");
1465 style->removeProperty(CSSPropertyFontFamily);
1467 if (RefPtr<CSSValue> fontSize = style->getPropertyCSSValue(CSSPropertyFontSize)) {
1468 if (!fontSize->isPrimitiveValue())
1469 style->removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1470 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, toCSSPrimitiveValue(fontSize.get()),
1471 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1472 m_applyFontSize = String::number(legacyFontSize);
1473 style->removeProperty(CSSPropertyFontSize);
1478 static void diffTextDecorations(MutableStylePropertySet* style, CSSPropertyID propertID, CSSValue* refTextDecoration)
1480 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(propertID);
1481 if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration || !refTextDecoration->isValueList())
1484 RefPtrWillBeRawPtr<CSSValueList> newTextDecoration = toCSSValueList(textDecoration.get())->copy();
1485 CSSValueList* valuesInRefTextDecoration = toCSSValueList(refTextDecoration);
1487 for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
1488 newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
1490 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1493 static bool fontWeightIsBold(CSSValue* fontWeight)
1495 if (!fontWeight->isPrimitiveValue())
1498 // Because b tag can only bold text, there are only two states in plain html: bold and not bold.
1499 // Collapse all other values to either one of these two states for editing purposes.
1500 switch (toCSSPrimitiveValue(fontWeight)->getValueID()) {
1506 case CSSValueNormal:
1518 ASSERT_NOT_REACHED(); // For CSSValueBolder and CSSValueLighter
1522 static bool fontWeightNeedsResolving(CSSValue* fontWeight)
1524 if (!fontWeight->isPrimitiveValue())
1527 CSSValueID value = toCSSPrimitiveValue(fontWeight)->getValueID();
1528 return value == CSSValueLighter || value == CSSValueBolder;
1531 PassRefPtr<MutableStylePropertySet> getPropertiesNotIn(StylePropertySet* styleWithRedundantProperties, CSSStyleDeclaration* baseStyle)
1533 ASSERT(styleWithRedundantProperties);
1535 RefPtr<MutableStylePropertySet> result = styleWithRedundantProperties->mutableCopy();
1537 result->removeEquivalentProperties(baseStyle);
1539 RefPtr<CSSValue> baseTextDecorationsInEffect = baseStyle->getPropertyCSSValueInternal(CSSPropertyWebkitTextDecorationsInEffect);
1540 diffTextDecorations(result.get(), textDecorationPropertyForEditing(), baseTextDecorationsInEffect.get());
1541 diffTextDecorations(result.get(), CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1543 if (RefPtr<CSSValue> baseFontWeight = baseStyle->getPropertyCSSValueInternal(CSSPropertyFontWeight)) {
1544 if (RefPtr<CSSValue> fontWeight = result->getPropertyCSSValue(CSSPropertyFontWeight)) {
1545 if (!fontWeightNeedsResolving(fontWeight.get()) && (fontWeightIsBold(fontWeight.get()) == fontWeightIsBold(baseFontWeight.get())))
1546 result->removeProperty(CSSPropertyFontWeight);
1550 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyColor) && getRGBAFontColor(result.get()) == getRGBAFontColor(baseStyle))
1551 result->removeProperty(CSSPropertyColor);
1553 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyTextAlign)
1554 && textAlignResolvingStartAndEnd(result.get()) == textAlignResolvingStartAndEnd(baseStyle))
1555 result->removeProperty(CSSPropertyTextAlign);
1557 if (baseStyle->getPropertyCSSValueInternal(CSSPropertyBackgroundColor) && getRGBABackgroundColor(result.get()) == getRGBABackgroundColor(baseStyle))
1558 result->removeProperty(CSSPropertyBackgroundColor);
1560 return result.release();
1563 CSSValueID getIdentifierValue(StylePropertySet* style, CSSPropertyID propertyID)
1566 return CSSValueInvalid;
1567 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
1568 if (!value || !value->isPrimitiveValue())
1569 return CSSValueInvalid;
1570 return toCSSPrimitiveValue(value.get())->getValueID();
1573 CSSValueID getIdentifierValue(CSSStyleDeclaration* style, CSSPropertyID propertyID)
1576 return CSSValueInvalid;
1577 RefPtr<CSSValue> value = style->getPropertyCSSValueInternal(propertyID);
1578 if (!value || !value->isPrimitiveValue())
1579 return CSSValueInvalid;
1580 return toCSSPrimitiveValue(value.get())->getValueID();
1583 static bool isCSSValueLength(CSSPrimitiveValue* value)
1585 return value->isFontIndependentLength();
1588 int legacyFontSizeFromCSSValue(Document* document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1590 if (isCSSValueLength(value)) {
1591 int pixelFontSize = value->getIntValue(CSSPrimitiveValue::CSS_PX);
1592 int legacyFontSize = FontSize::legacyFontSize(document, pixelFontSize, shouldUseFixedFontDefaultSize);
1593 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1594 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1595 if (mode == AlwaysUseLegacyFontSize || FontSize::fontSizeForKeyword(document, cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize) == pixelFontSize)
1596 return legacyFontSize;
1601 if (CSSValueXSmall <= value->getValueID() && value->getValueID() <= CSSValueWebkitXxxLarge)
1602 return value->getValueID() - CSSValueXSmall + 1;
1607 bool isTransparentColorValue(CSSValue* cssValue)
1611 if (!cssValue->isPrimitiveValue())
1613 CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue);
1614 if (value->isRGBColor())
1615 return !alphaChannel(value->getRGBA32Value());
1616 return value->getValueID() == CSSValueTransparent;
1619 bool hasTransparentBackgroundColor(CSSStyleDeclaration* style)
1621 RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
1622 return isTransparentColorValue(cssValue.get());
1625 bool hasTransparentBackgroundColor(StylePropertySet* style)
1627 RefPtr<CSSValue> cssValue = style->getPropertyCSSValue(CSSPropertyBackgroundColor);
1628 return isTransparentColorValue(cssValue.get());
1631 PassRefPtr<CSSValue> backgroundColorInEffect(Node* node)
1633 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1634 RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor);
1635 if (!hasTransparentBackgroundColor(ancestorStyle.get()))
1636 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);