| /* | 
 |  * Copyright (C) 2007, 2008, 2009 Apple Computer, Inc. | 
 |  * Copyright (C) 2010, 2011 Google Inc. All rights reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer in the | 
 |  *    documentation and/or other materials provided with the distribution. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 |  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 |  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 |  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 |  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 |  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 |  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 |  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 |  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | #include "sky/engine/config.h" | 
 | #include "sky/engine/core/editing/EditingStyle.h" | 
 |  | 
 | #include "gen/sky/core/HTMLNames.h" | 
 | #include "sky/engine/bindings/core/v8/ExceptionStatePlaceholder.h" | 
 | #include "sky/engine/core/css/CSSComputedStyleDeclaration.h" | 
 | #include "sky/engine/core/css/CSSPropertyMetadata.h" | 
 | #include "sky/engine/core/css/CSSValueList.h" | 
 | #include "sky/engine/core/css/CSSValuePool.h" | 
 | #include "sky/engine/core/css/FontSize.h" | 
 | #include "sky/engine/core/css/StylePropertySet.h" | 
 | #include "sky/engine/core/css/StyleRule.h" | 
 | #include "sky/engine/core/css/parser/BisonCSSParser.h" | 
 | #include "sky/engine/core/css/resolver/StyleResolver.h" | 
 | #include "sky/engine/core/dom/Document.h" | 
 | #include "sky/engine/core/dom/Element.h" | 
 | #include "sky/engine/core/dom/Node.h" | 
 | #include "sky/engine/core/dom/NodeTraversal.h" | 
 | #include "sky/engine/core/dom/Position.h" | 
 | #include "sky/engine/core/dom/QualifiedName.h" | 
 | #include "sky/engine/core/editing/Editor.h" | 
 | #include "sky/engine/core/editing/FrameSelection.h" | 
 | #include "sky/engine/core/editing/HTMLInterchange.h" | 
 | #include "sky/engine/core/editing/htmlediting.h" | 
 | #include "sky/engine/core/frame/LocalFrame.h" | 
 | #include "sky/engine/core/rendering/RenderBox.h" | 
 | #include "sky/engine/core/rendering/RenderObject.h" | 
 | #include "sky/engine/core/rendering/style/RenderStyle.h" | 
 |  | 
 | namespace blink { | 
 |  | 
 | static const CSSPropertyID& textDecorationPropertyForEditing() | 
 | { | 
 |     static const CSSPropertyID property = RuntimeEnabledFeatures::css3TextDecorationsEnabled() ? CSSPropertyTextDecorationLine : CSSPropertyTextDecoration; | 
 |     return property; | 
 | } | 
 |  | 
 | // Editing style properties must be preserved during editing operation. | 
 | // e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph. | 
 | // NOTE: Use either allEditingProperties() or inheritableEditingProperties() to | 
 | // respect runtime enabling of properties. | 
 | static const CSSPropertyID staticEditingProperties[] = { | 
 |     CSSPropertyBackgroundColor, | 
 |     CSSPropertyColor, | 
 |     CSSPropertyFontFamily, | 
 |     CSSPropertyFontSize, | 
 |     CSSPropertyFontStyle, | 
 |     CSSPropertyFontVariant, | 
 |     CSSPropertyFontWeight, | 
 |     CSSPropertyLetterSpacing, | 
 |     CSSPropertyLineHeight, | 
 |     CSSPropertyOrphans, | 
 |     CSSPropertyTextAlign, | 
 |     // FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text | 
 |     // Decoration feature is no longer experimental. | 
 |     CSSPropertyTextDecoration, | 
 |     CSSPropertyTextDecorationLine, | 
 |     CSSPropertyTextIndent, | 
 |     CSSPropertyWhiteSpace, | 
 |     CSSPropertyWidows, | 
 |     CSSPropertyWordSpacing, | 
 |     CSSPropertyWebkitTextDecorationsInEffect, | 
 |     CSSPropertyWebkitTextFillColor, | 
 |     CSSPropertyWebkitTextStrokeColor, | 
 |     CSSPropertyWebkitTextStrokeWidth, | 
 | }; | 
 |  | 
 | enum EditingPropertiesType { OnlyInheritableEditingProperties, AllEditingProperties }; | 
 |  | 
 | static const Vector<CSSPropertyID>& allEditingProperties() | 
 | { | 
 |     DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ()); | 
 |     if (properties.isEmpty()) { | 
 |         CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties); | 
 |         if (RuntimeEnabledFeatures::css3TextDecorationsEnabled()) | 
 |             properties.remove(properties.find(CSSPropertyTextDecoration)); | 
 |     } | 
 |     return properties; | 
 | } | 
 |  | 
 | static const Vector<CSSPropertyID>& inheritableEditingProperties() | 
 | { | 
 |     DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ()); | 
 |     if (properties.isEmpty()) { | 
 |         CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(staticEditingProperties, WTF_ARRAY_LENGTH(staticEditingProperties), properties); | 
 |         for (size_t index = 0; index < properties.size();) { | 
 |             if (!CSSPropertyMetadata::isInheritedProperty(properties[index])) { | 
 |                 properties.remove(index); | 
 |                 continue; | 
 |             } | 
 |             ++index; | 
 |         } | 
 |     } | 
 |     return properties; | 
 | } | 
 |  | 
 | template <class StyleDeclarationType> | 
 | static PassRefPtr<MutableStylePropertySet> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesType type = OnlyInheritableEditingProperties) | 
 | { | 
 |     if (type == AllEditingProperties) | 
 |         return style->copyPropertiesInSet(allEditingProperties()); | 
 |     return style->copyPropertiesInSet(inheritableEditingProperties()); | 
 | } | 
 |  | 
 | static inline bool isEditingProperty(int id) | 
 | { | 
 |     return allEditingProperties().contains(static_cast<CSSPropertyID>(id)); | 
 | } | 
 |  | 
 | static PassRefPtr<MutableStylePropertySet> editingStyleFromComputedStyle(PassRefPtr<CSSComputedStyleDeclaration> style, EditingPropertiesType type = OnlyInheritableEditingProperties) | 
 | { | 
 |     if (!style) | 
 |         return MutableStylePropertySet::create(); | 
 |     return copyEditingProperties(style.get(), type); | 
 | } | 
 |  | 
 | enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch }; | 
 |  | 
 | static bool isTransparentColorValue(CSSValue*); | 
 | static bool hasTransparentBackgroundColor(CSSStyleDeclaration*); | 
 |  | 
 | static PassRefPtr<CSSValue> backgroundColorInEffect(Node*); | 
 |  | 
 | class HTMLElementEquivalent { | 
 |     WTF_MAKE_FAST_ALLOCATED; | 
 |     DECLARE_EMPTY_VIRTUAL_DESTRUCTOR_WILL_BE_REMOVED(HTMLElementEquivalent); | 
 | public: | 
 |     static PassOwnPtr<HTMLElementEquivalent> create(CSSPropertyID propertyID, CSSValueID primitiveValue, const HTMLQualifiedName& tagName) | 
 |     { | 
 |         return adoptPtr(new HTMLElementEquivalent(propertyID, primitiveValue, tagName)); | 
 |     } | 
 |  | 
 |     virtual bool matches(const Element* element) const { return !m_tagName || element->hasTagName(*m_tagName); } | 
 |     virtual bool hasAttribute() const { return false; } | 
 |     virtual bool propertyExistsInStyle(const StylePropertySet* style) const { return style->getPropertyCSSValue(m_propertyID); } | 
 |     virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const; | 
 |     virtual void addToStyle(Element*, EditingStyle*) const; | 
 |  | 
 | protected: | 
 |     HTMLElementEquivalent(CSSPropertyID); | 
 |     HTMLElementEquivalent(CSSPropertyID, const HTMLQualifiedName& tagName); | 
 |     HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const HTMLQualifiedName& tagName); | 
 |     const CSSPropertyID m_propertyID; | 
 |     const RefPtr<CSSPrimitiveValue> m_primitiveValue; | 
 |     const HTMLQualifiedName* m_tagName; // We can store a pointer because HTML tag names are const global. | 
 | }; | 
 |  | 
 | DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(HTMLElementEquivalent); | 
 |  | 
 | HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id) | 
 |     : m_propertyID(id) | 
 |     , m_tagName(0) | 
 | { | 
 | } | 
 |  | 
 | HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const HTMLQualifiedName& tagName) | 
 |     : m_propertyID(id) | 
 |     , m_tagName(&tagName) | 
 | { | 
 | } | 
 |  | 
 | HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primitiveValue, const HTMLQualifiedName& tagName) | 
 |     : m_propertyID(id) | 
 |     , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue)) | 
 |     , m_tagName(&tagName) | 
 | { | 
 |     ASSERT(primitiveValue != CSSValueInvalid); | 
 | } | 
 |  | 
 | bool HTMLElementEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const | 
 | { | 
 |     RefPtr<CSSValue> value = style->getPropertyCSSValue(m_propertyID); | 
 |     return matches(element) && value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->getValueID() == m_primitiveValue->getValueID(); | 
 | } | 
 |  | 
 | void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const | 
 | { | 
 |     style->setProperty(m_propertyID, m_primitiveValue->cssText()); | 
 | } | 
 |  | 
 | class HTMLTextDecorationEquivalent final : public HTMLElementEquivalent { | 
 | public: | 
 |     static PassOwnPtr<HTMLElementEquivalent> create(CSSValueID primitiveValue, const HTMLQualifiedName& tagName) | 
 |     { | 
 |         return adoptPtr(new HTMLTextDecorationEquivalent(primitiveValue, tagName)); | 
 |     } | 
 |     virtual bool propertyExistsInStyle(const StylePropertySet*) const override; | 
 |     virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override; | 
 |  | 
 | private: | 
 |     HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const HTMLQualifiedName& tagName); | 
 | }; | 
 |  | 
 | HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const HTMLQualifiedName& tagName) | 
 |     : HTMLElementEquivalent(textDecorationPropertyForEditing(), primitiveValue, tagName) | 
 |     // m_propertyID is used in HTMLElementEquivalent::addToStyle | 
 | { | 
 | } | 
 |  | 
 | bool HTMLTextDecorationEquivalent::propertyExistsInStyle(const StylePropertySet* style) const | 
 | { | 
 |     return style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) | 
 |         || style->getPropertyCSSValue(textDecorationPropertyForEditing()); | 
 | } | 
 |  | 
 | bool HTMLTextDecorationEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const | 
 | { | 
 |     RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); | 
 |     if (!styleValue) | 
 |         styleValue = style->getPropertyCSSValue(textDecorationPropertyForEditing()); | 
 |     return matches(element) && styleValue && styleValue->isValueList() && toCSSValueList(styleValue.get())->hasValue(m_primitiveValue.get()); | 
 | } | 
 |  | 
 | class HTMLAttributeEquivalent : public HTMLElementEquivalent { | 
 | public: | 
 |     static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const HTMLQualifiedName& tagName, const QualifiedName& attrName) | 
 |     { | 
 |         return adoptPtr(new HTMLAttributeEquivalent(propertyID, tagName, attrName)); | 
 |     } | 
 |     static PassOwnPtr<HTMLAttributeEquivalent> create(CSSPropertyID propertyID, const QualifiedName& attrName) | 
 |     { | 
 |         return adoptPtr(new HTMLAttributeEquivalent(propertyID, attrName)); | 
 |     } | 
 |  | 
 |     virtual bool matches(const Element* element) const override { return HTMLElementEquivalent::matches(element) && element->hasAttribute(m_attrName); } | 
 |     virtual bool hasAttribute() const override { return true; } | 
 |     virtual bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override; | 
 |     virtual void addToStyle(Element*, EditingStyle*) const override; | 
 |     virtual PassRefPtr<CSSValue> attributeValueAsCSSValue(Element*) const; | 
 |     inline const QualifiedName& attributeName() const { return m_attrName; } | 
 |  | 
 | protected: | 
 |     HTMLAttributeEquivalent(CSSPropertyID, const HTMLQualifiedName& tagName, const QualifiedName& attrName); | 
 |     HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName); | 
 |     const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global. | 
 | }; | 
 |  | 
 | HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const HTMLQualifiedName& tagName, const QualifiedName& attrName) | 
 |     : HTMLElementEquivalent(id, tagName) | 
 |     , m_attrName(attrName) | 
 | { | 
 | } | 
 |  | 
 | HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName) | 
 |     : HTMLElementEquivalent(id) | 
 |     , m_attrName(attrName) | 
 | { | 
 | } | 
 |  | 
 | bool HTMLAttributeEquivalent::valueIsPresentInStyle(HTMLElement* element, StylePropertySet* style) const | 
 | { | 
 |     RefPtr<CSSValue> value = attributeValueAsCSSValue(element); | 
 |     RefPtr<CSSValue> styleValue = style->getPropertyCSSValue(m_propertyID); | 
 |  | 
 |     return compareCSSValuePtr(value, styleValue); | 
 | } | 
 |  | 
 | void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const | 
 | { | 
 |     if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element)) | 
 |         style->setProperty(m_propertyID, value->cssText()); | 
 | } | 
 |  | 
 | PassRefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const | 
 | { | 
 |     ASSERT(element); | 
 |     const AtomicString& value = element->getAttribute(m_attrName); | 
 |     if (value.isNull()) | 
 |         return nullptr; | 
 |  | 
 |     RefPtr<MutableStylePropertySet> dummyStyle = nullptr; | 
 |     dummyStyle = MutableStylePropertySet::create(); | 
 |     dummyStyle->setProperty(m_propertyID, value); | 
 |     return dummyStyle->getPropertyCSSValue(m_propertyID); | 
 | } | 
 |  | 
 | float EditingStyle::NoFontDelta = 0.0f; | 
 |  | 
 | EditingStyle::EditingStyle() | 
 |     : m_fixedPitchFontType(NonFixedPitchFont) | 
 |     , m_fontSizeDelta(NoFontDelta) | 
 | { | 
 | } | 
 |  | 
 | EditingStyle::EditingStyle(ContainerNode* node, PropertiesToInclude propertiesToInclude) | 
 |     : m_fixedPitchFontType(NonFixedPitchFont) | 
 |     , m_fontSizeDelta(NoFontDelta) | 
 | { | 
 |     init(node, propertiesToInclude); | 
 | } | 
 |  | 
 | EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude) | 
 |     : m_fixedPitchFontType(NonFixedPitchFont) | 
 |     , m_fontSizeDelta(NoFontDelta) | 
 | { | 
 |     init(position.deprecatedNode(), propertiesToInclude); | 
 | } | 
 |  | 
 | EditingStyle::EditingStyle(const StylePropertySet* style) | 
 |     : m_mutableStyle(style ? style->mutableCopy() : nullptr) | 
 |     , m_fixedPitchFontType(NonFixedPitchFont) | 
 |     , m_fontSizeDelta(NoFontDelta) | 
 | { | 
 |     extractFontSizeDelta(); | 
 | } | 
 |  | 
 | EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value) | 
 |     : m_mutableStyle(nullptr) | 
 |     , m_fixedPitchFontType(NonFixedPitchFont) | 
 |     , m_fontSizeDelta(NoFontDelta) | 
 | { | 
 |     setProperty(propertyID, value); | 
 | } | 
 |  | 
 | EditingStyle::~EditingStyle() | 
 | { | 
 | } | 
 |  | 
 | void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude) | 
 | { | 
 |     RefPtr<CSSComputedStyleDeclaration> computedStyleAtPosition = CSSComputedStyleDeclaration::create(node); | 
 |     m_mutableStyle = propertiesToInclude == AllProperties && computedStyleAtPosition ? computedStyleAtPosition->copyProperties() : editingStyleFromComputedStyle(computedStyleAtPosition); | 
 |  | 
 |     if (propertiesToInclude == EditingPropertiesInEffect) { | 
 |         if (RefPtr<CSSValue> value = backgroundColorInEffect(node)) | 
 |             m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText()); | 
 |         if (RefPtr<CSSValue> value = computedStyleAtPosition->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)) | 
 |             m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText()); | 
 |     } | 
 |  | 
 |     if (node && node->computedStyle()) { | 
 |         RenderStyle* renderStyle = node->computedStyle(); | 
 |         removeTextFillAndStrokeColorsIfNeeded(renderStyle); | 
 |         replaceFontSizeByKeywordIfPossible(renderStyle, computedStyleAtPosition.get()); | 
 |     } | 
 |  | 
 |     m_fixedPitchFontType = computedStyleAtPosition->fixedPitchFontType(); | 
 |     extractFontSizeDelta(); | 
 | } | 
 |  | 
 | void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(RenderStyle* renderStyle) | 
 | { | 
 |     // If a node's text fill color is currentColor, then its children use | 
 |     // their font-color as their text fill color (they don't | 
 |     // inherit it).  Likewise for stroke color. | 
 |     if (renderStyle->textFillColor().isCurrentColor()) | 
 |         m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor); | 
 |     if (renderStyle->textStrokeColor().isCurrentColor()) | 
 |         m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor); | 
 | } | 
 |  | 
 | void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value) | 
 | { | 
 |     if (!m_mutableStyle) | 
 |         m_mutableStyle = MutableStylePropertySet::create(); | 
 |  | 
 |     m_mutableStyle->setProperty(propertyID, value); | 
 | } | 
 |  | 
 | void EditingStyle::replaceFontSizeByKeywordIfPossible(RenderStyle* renderStyle, CSSComputedStyleDeclaration* computedStyle) | 
 | { | 
 |     ASSERT(renderStyle); | 
 |     if (renderStyle->fontDescription().keywordSize()) | 
 |         m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyle->getFontSizeCSSValuePreferringKeyword()->cssText()); | 
 | } | 
 |  | 
 | void EditingStyle::extractFontSizeDelta() | 
 | { | 
 |     if (!m_mutableStyle) | 
 |         return; | 
 |  | 
 |     if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) { | 
 |         // Explicit font size overrides any delta. | 
 |         m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta); | 
 |         return; | 
 |     } | 
 |  | 
 |     // Get the adjustment amount out of the style. | 
 |     RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta); | 
 |     if (!value || !value->isPrimitiveValue()) | 
 |         return; | 
 |  | 
 |     CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value.get()); | 
 |  | 
 |     // Only PX handled now. If we handle more types in the future, perhaps | 
 |     // a switch statement here would be more appropriate. | 
 |     if (!primitiveValue->isPx()) | 
 |         return; | 
 |  | 
 |     m_fontSizeDelta = primitiveValue->getFloatValue(); | 
 |     m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta); | 
 | } | 
 |  | 
 | bool EditingStyle::isEmpty() const | 
 | { | 
 |     return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta; | 
 | } | 
 |  | 
 | void EditingStyle::clear() | 
 | { | 
 |     m_mutableStyle.clear(); | 
 |     m_fixedPitchFontType = NonFixedPitchFont; | 
 |     m_fontSizeDelta = NoFontDelta; | 
 | } | 
 |  | 
 | PassRefPtr<EditingStyle> EditingStyle::copy() const | 
 | { | 
 |     RefPtr<EditingStyle> copy = EditingStyle::create(); | 
 |     if (m_mutableStyle) | 
 |         copy->m_mutableStyle = m_mutableStyle->mutableCopy(); | 
 |     copy->m_fixedPitchFontType = m_fixedPitchFontType; | 
 |     copy->m_fontSizeDelta = m_fontSizeDelta; | 
 |     return copy; | 
 | } | 
 |  | 
 | void EditingStyle::removeBlockProperties() | 
 | { | 
 |     if (!m_mutableStyle) | 
 |         return; | 
 |  | 
 |     m_mutableStyle->removeBlockProperties(); | 
 | } | 
 |  | 
 | static const Vector<OwnPtr<HTMLElementEquivalent> >& htmlElementEquivalents() | 
 | { | 
 |     DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLElementEquivalent> >, HTMLElementEquivalents, ()); | 
 |     return HTMLElementEquivalents; | 
 | } | 
 |  | 
 | static const Vector<OwnPtr<HTMLAttributeEquivalent> >& htmlAttributeEquivalents() | 
 | { | 
 |     DEFINE_STATIC_LOCAL(Vector<OwnPtr<HTMLAttributeEquivalent> >, HTMLAttributeEquivalents, ()); | 
 |     return HTMLAttributeEquivalents; | 
 | } | 
 |  | 
 | bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement* element) | 
 | { | 
 |     ASSERT(element); | 
 |     bool elementIsSpanOrElementEquivalent = false; | 
 |     const Vector<OwnPtr<HTMLElementEquivalent> >& HTMLElementEquivalents = htmlElementEquivalents(); | 
 |     size_t i; | 
 |     for (i = 0; i < HTMLElementEquivalents.size(); ++i) { | 
 |         if (HTMLElementEquivalents[i]->matches(element)) { | 
 |             elementIsSpanOrElementEquivalent = true; | 
 |             break; | 
 |         } | 
 |     } | 
 |  | 
 |     AttributeCollection attributes = element->attributes(); | 
 |     if (attributes.isEmpty()) | 
 |         return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes | 
 |  | 
 |     unsigned matchedAttributes = 0; | 
 |     const Vector<OwnPtr<HTMLAttributeEquivalent> >& HTMLAttributeEquivalents = htmlAttributeEquivalents(); | 
 |     for (size_t i = 0; i < HTMLAttributeEquivalents.size(); ++i) { | 
 |         if (HTMLAttributeEquivalents[i]->matches(element) && HTMLAttributeEquivalents[i]->attributeName() != HTMLNames::dirAttr) | 
 |             matchedAttributes++; | 
 |     } | 
 |  | 
 |     if (!elementIsSpanOrElementEquivalent && !matchedAttributes) | 
 |         return false; // element is not a span, a html element equivalent, or font element. | 
 |  | 
 |     if (element->getAttribute(HTMLNames::classAttr) == AppleStyleSpanClass) | 
 |         matchedAttributes++; | 
 |  | 
 |     if (element->hasAttribute(HTMLNames::styleAttr)) { | 
 |         if (const StylePropertySet* style = element->inlineStyle()) { | 
 |             unsigned propertyCount = style->propertyCount(); | 
 |             for (unsigned i = 0; i < propertyCount; ++i) { | 
 |                 if (!isEditingProperty(style->propertyAt(i).id())) | 
 |                     return false; | 
 |             } | 
 |         } | 
 |         matchedAttributes++; | 
 |     } | 
 |  | 
 |     // font with color attribute, span with style attribute, etc... | 
 |     ASSERT(matchedAttributes <= attributes.size()); | 
 |     return matchedAttributes >= attributes.size(); | 
 | } | 
 |  | 
 | void EditingStyle::mergeTypingStyle(Document* document) | 
 | { | 
 |     ASSERT(document); | 
 |  | 
 |     RefPtr<EditingStyle> typingStyle = document->frame()->selection().typingStyle(); | 
 |     if (!typingStyle || typingStyle == this) | 
 |         return; | 
 |  | 
 |     mergeStyle(typingStyle->style(), OverrideValues); | 
 | } | 
 |  | 
 | static void mergeTextDecorationValues(CSSValueList* mergedValue, const CSSValueList* valueToMerge) | 
 | { | 
 |     DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, underline, (CSSPrimitiveValue::createIdentifier(CSSValueUnderline))); | 
 |     DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, lineThrough, (CSSPrimitiveValue::createIdentifier(CSSValueLineThrough))); | 
 |     if (valueToMerge->hasValue(underline) && !mergedValue->hasValue(underline)) | 
 |         mergedValue->append(underline); | 
 |  | 
 |     if (valueToMerge->hasValue(lineThrough) && !mergedValue->hasValue(lineThrough)) | 
 |         mergedValue->append(lineThrough); | 
 | } | 
 |  | 
 | void EditingStyle::mergeStyle(const StylePropertySet* style, CSSPropertyOverrideMode mode) | 
 | { | 
 |     if (!style) | 
 |         return; | 
 |  | 
 |     if (!m_mutableStyle) { | 
 |         m_mutableStyle = style->mutableCopy(); | 
 |         return; | 
 |     } | 
 |  | 
 |     unsigned propertyCount = style->propertyCount(); | 
 |     for (unsigned i = 0; i < propertyCount; ++i) { | 
 |         StylePropertySet::PropertyReference property = style->propertyAt(i); | 
 |         RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id()); | 
 |  | 
 |         // text decorations never override values | 
 |         if ((property.id() == textDecorationPropertyForEditing() || property.id() == CSSPropertyWebkitTextDecorationsInEffect) && property.value()->isValueList() && value) { | 
 |             if (value->isValueList()) { | 
 |                 mergeTextDecorationValues(toCSSValueList(value.get()), toCSSValueList(property.value())); | 
 |                 continue; | 
 |             } | 
 |             value = nullptr; // text-decoration: none is equivalent to not having the property | 
 |         } | 
 |  | 
 |         if (mode == OverrideValues || (mode == DoNotOverrideValues && !value)) | 
 |             m_mutableStyle->setProperty(property.id(), property.value()->cssText()); | 
 |     } | 
 | } | 
 |  | 
 | bool isTransparentColorValue(CSSValue* cssValue) | 
 | { | 
 |     if (!cssValue) | 
 |         return true; | 
 |     if (!cssValue->isPrimitiveValue()) | 
 |         return false; | 
 |     CSSPrimitiveValue* value = toCSSPrimitiveValue(cssValue); | 
 |     if (value->isRGBColor()) | 
 |         return !alphaChannel(value->getRGBA32Value()); | 
 |     return false; | 
 | } | 
 |  | 
 | bool hasTransparentBackgroundColor(CSSStyleDeclaration* style) | 
 | { | 
 |     RefPtr<CSSValue> cssValue = style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor); | 
 |     return isTransparentColorValue(cssValue.get()); | 
 | } | 
 |  | 
 | PassRefPtr<CSSValue> backgroundColorInEffect(Node* node) | 
 | { | 
 |     for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { | 
 |         RefPtr<CSSComputedStyleDeclaration> ancestorStyle = CSSComputedStyleDeclaration::create(ancestor); | 
 |         if (!hasTransparentBackgroundColor(ancestorStyle.get())) | 
 |             return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor); | 
 |     } | 
 |     return nullptr; | 
 | } | 
 |  | 
 | } |