blob: 70589eceaab327bf6aa9a0be860ad286cc23bf83 [file] [log] [blame]
/*
* 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 "core/editing/EditingStyle.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "core/HTMLNames.h"
#include "core/css/CSSColorValue.h"
#include "core/css/CSSComputedStyleDeclaration.h"
#include "core/css/CSSIdentifierValue.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSPrimitiveValueMappings.h"
#include "core/css/CSSPropertyMetadata.h"
#include "core/css/CSSRuleList.h"
#include "core/css/CSSStyleRule.h"
#include "core/css/CSSValueList.h"
#include "core/css/FontSize.h"
#include "core/css/StylePropertySet.h"
#include "core/css/StyleRule.h"
#include "core/css/parser/CSSParser.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/Node.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/QualifiedName.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Editor.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/Position.h"
#include "core/editing/commands/ApplyStyleCommand.h"
#include "core/editing/serializers/HTMLInterchange.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLFontElement.h"
#include "core/html/HTMLSpanElement.h"
#include "core/layout/LayoutBox.h"
#include "core/layout/LayoutObject.h"
#include "core/style/ComputedStyle.h"
#include "wtf/StdLibExtras.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, CSSPropertyFontVariantLigatures,
CSSPropertyFontVariantCaps, CSSPropertyFontWeight, CSSPropertyLetterSpacing,
CSSPropertyOrphans, CSSPropertyTextAlign,
// FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
// Decoration feature is no longer experimental.
CSSPropertyTextDecoration, CSSPropertyTextDecorationLine,
CSSPropertyTextIndent, CSSPropertyTextTransform, 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 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 MutableStylePropertySet* editingStyleFromComputedStyle(
CSSComputedStyleDeclaration* style,
EditingPropertiesType type = OnlyInheritableEditingProperties) {
if (!style)
return MutableStylePropertySet::create(HTMLQuirksMode);
return copyEditingProperties(style, type);
}
static CSSComputedStyleDeclaration* ensureComputedStyle(
const Position& position) {
Element* elem = associatedElementOf(position);
if (!elem)
return nullptr;
return CSSComputedStyleDeclaration::create(elem);
}
static MutableStylePropertySet* getPropertiesNotIn(
StylePropertySet* styleWithRedundantProperties,
CSSStyleDeclaration* baseStyle);
enum LegacyFontSizeMode {
AlwaysUseLegacyFontSize,
UseLegacyFontSizeOnlyIfPixelValuesMatch
};
static int legacyFontSizeFromCSSValue(Document*,
const CSSValue*,
bool,
LegacyFontSizeMode);
static bool isTransparentColorValue(const CSSValue*);
static bool hasTransparentBackgroundColor(CSSStyleDeclaration*);
static bool hasTransparentBackgroundColor(StylePropertySet*);
static const CSSValue* backgroundColorValueInEffect(Node*);
static bool hasAncestorVerticalAlignStyle(Node&, CSSValueID);
class HTMLElementEquivalent : public GarbageCollected<HTMLElementEquivalent> {
public:
static HTMLElementEquivalent* create(CSSPropertyID propertyID,
CSSValueID primitiveValue,
const HTMLQualifiedName& tagName) {
return 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;
DEFINE_INLINE_VIRTUAL_TRACE() { visitor->trace(m_identifierValue); }
protected:
HTMLElementEquivalent(CSSPropertyID);
HTMLElementEquivalent(CSSPropertyID, const HTMLQualifiedName& tagName);
HTMLElementEquivalent(CSSPropertyID,
CSSValueID primitiveValue,
const HTMLQualifiedName& tagName);
const CSSPropertyID m_propertyID;
const Member<CSSIdentifierValue> m_identifierValue;
// We can store a pointer because HTML tag names are const global.
const HTMLQualifiedName* m_tagName;
};
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 valueID,
const HTMLQualifiedName& tagName)
: m_propertyID(id),
m_identifierValue(CSSIdentifierValue::create(valueID)),
m_tagName(&tagName) {
DCHECK_NE(valueID, CSSValueInvalid);
}
bool HTMLElementEquivalent::valueIsPresentInStyle(
HTMLElement* element,
StylePropertySet* style) const {
const CSSValue* value = style->getPropertyCSSValue(m_propertyID);
return matches(element) && value && value->isIdentifierValue() &&
toCSSIdentifierValue(value)->getValueID() ==
m_identifierValue->getValueID();
}
void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const {
style->setProperty(m_propertyID, m_identifierValue->cssText());
}
class HTMLTextDecorationEquivalent final : public HTMLElementEquivalent {
public:
static HTMLElementEquivalent* create(CSSValueID primitiveValue,
const HTMLQualifiedName& tagName) {
return new HTMLTextDecorationEquivalent(primitiveValue, tagName);
}
bool propertyExistsInStyle(const StylePropertySet*) const override;
bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override;
DEFINE_INLINE_VIRTUAL_TRACE() { HTMLElementEquivalent::trace(visitor); }
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 {
const CSSValue* styleValue =
style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
if (!styleValue)
styleValue = style->getPropertyCSSValue(textDecorationPropertyForEditing());
return matches(element) && styleValue && styleValue->isValueList() &&
toCSSValueList(styleValue)->hasValue(*m_identifierValue);
}
class HTMLAttributeEquivalent : public HTMLElementEquivalent {
public:
static HTMLAttributeEquivalent* create(CSSPropertyID propertyID,
const HTMLQualifiedName& tagName,
const QualifiedName& attrName) {
return new HTMLAttributeEquivalent(propertyID, tagName, attrName);
}
static HTMLAttributeEquivalent* create(CSSPropertyID propertyID,
const QualifiedName& attrName) {
return new HTMLAttributeEquivalent(propertyID, attrName);
}
bool matches(const Element* element) const override {
return HTMLElementEquivalent::matches(element) &&
element->hasAttribute(m_attrName);
}
bool hasAttribute() const override { return true; }
bool valueIsPresentInStyle(HTMLElement*, StylePropertySet*) const override;
void addToStyle(Element*, EditingStyle*) const override;
virtual const CSSValue* attributeValueAsCSSValue(Element*) const;
inline const QualifiedName& attributeName() const { return m_attrName; }
DEFINE_INLINE_VIRTUAL_TRACE() { HTMLElementEquivalent::trace(visitor); }
protected:
HTMLAttributeEquivalent(CSSPropertyID,
const HTMLQualifiedName& tagName,
const QualifiedName& attrName);
HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
// We can store a reference because HTML attribute names are const global.
const QualifiedName& m_attrName;
};
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 {
const CSSValue* value = attributeValueAsCSSValue(element);
const CSSValue* styleValue = style->getPropertyCSSValue(m_propertyID);
return compareCSSValuePtr(value, styleValue);
}
void HTMLAttributeEquivalent::addToStyle(Element* element,
EditingStyle* style) const {
if (const CSSValue* value = attributeValueAsCSSValue(element))
style->setProperty(m_propertyID, value->cssText());
}
const CSSValue* HTMLAttributeEquivalent::attributeValueAsCSSValue(
Element* element) const {
DCHECK(element);
const AtomicString& value = element->getAttribute(m_attrName);
if (value.isNull())
return nullptr;
MutableStylePropertySet* dummyStyle = nullptr;
dummyStyle = MutableStylePropertySet::create(HTMLQuirksMode);
dummyStyle->setProperty(m_propertyID, value);
return dummyStyle->getPropertyCSSValue(m_propertyID);
}
class HTMLFontSizeEquivalent final : public HTMLAttributeEquivalent {
public:
static HTMLFontSizeEquivalent* create() {
return new HTMLFontSizeEquivalent();
}
const CSSValue* attributeValueAsCSSValue(Element*) const override;
DEFINE_INLINE_VIRTUAL_TRACE() { HTMLAttributeEquivalent::trace(visitor); }
private:
HTMLFontSizeEquivalent();
};
HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
: HTMLAttributeEquivalent(CSSPropertyFontSize,
HTMLNames::fontTag,
HTMLNames::sizeAttr) {}
const CSSValue* HTMLFontSizeEquivalent::attributeValueAsCSSValue(
Element* element) const {
DCHECK(element);
const AtomicString& value = element->getAttribute(m_attrName);
if (value.isNull())
return nullptr;
CSSValueID size;
if (!HTMLFontElement::cssValueFromFontSizeNumber(value, size))
return nullptr;
return CSSIdentifierValue::create(size);
}
float EditingStyle::NoFontDelta = 0.0f;
EditingStyle::EditingStyle(ContainerNode* node,
PropertiesToInclude propertiesToInclude) {
init(node, propertiesToInclude);
}
EditingStyle::EditingStyle(const Position& position,
PropertiesToInclude propertiesToInclude) {
init(position.anchorNode(), propertiesToInclude);
}
EditingStyle::EditingStyle(const StylePropertySet* style)
: m_mutableStyle(style ? style->mutableCopy() : nullptr) {
extractFontSizeDelta();
}
EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
: m_mutableStyle(nullptr) {
setProperty(propertyID, value);
m_isVerticalAlign = propertyID == CSSPropertyVerticalAlign &&
(value == "sub" || value == "super");
}
static Color cssValueToColor(const CSSValue* colorValue) {
if (!colorValue ||
(!colorValue->isColorValue() && !colorValue->isPrimitiveValue() &&
!colorValue->isIdentifierValue()))
return Color::transparent;
if (colorValue->isColorValue())
return toCSSColorValue(colorValue)->value();
Color color = 0;
// FIXME: Why ignore the return value?
CSSParser::parseColor(color, colorValue->cssText());
return color;
}
static inline Color getFontColor(CSSStyleDeclaration* style) {
return cssValueToColor(style->getPropertyCSSValueInternal(CSSPropertyColor));
}
static inline Color getFontColor(StylePropertySet* style) {
return cssValueToColor(style->getPropertyCSSValue(CSSPropertyColor));
}
static inline Color getBackgroundColor(CSSStyleDeclaration* style) {
return cssValueToColor(
style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor));
}
static inline Color getBackgroundColor(StylePropertySet* style) {
return cssValueToColor(
style->getPropertyCSSValue(CSSPropertyBackgroundColor));
}
static inline Color backgroundColorInEffect(Node* node) {
return cssValueToColor(backgroundColorValueInEffect(node));
}
static int textAlignResolvingStartAndEnd(int textAlign, int direction) {
switch (textAlign) {
case CSSValueCenter:
case CSSValueWebkitCenter:
return CSSValueCenter;
case CSSValueJustify:
return CSSValueJustify;
case CSSValueLeft:
case CSSValueWebkitLeft:
return CSSValueLeft;
case CSSValueRight:
case CSSValueWebkitRight:
return CSSValueRight;
case CSSValueStart:
return direction != CSSValueRtl ? CSSValueLeft : CSSValueRight;
case CSSValueEnd:
return direction == CSSValueRtl ? CSSValueRight : CSSValueLeft;
}
return CSSValueInvalid;
}
template <typename T>
static int textAlignResolvingStartAndEnd(T* style) {
return textAlignResolvingStartAndEnd(
getIdentifierValue(style, CSSPropertyTextAlign),
getIdentifierValue(style, CSSPropertyDirection));
}
void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude) {
if (isTabHTMLSpanElementTextNode(node))
node = tabSpanElement(node)->parentNode();
else if (isTabHTMLSpanElement(node))
node = node->parentNode();
CSSComputedStyleDeclaration* computedStyleAtPosition =
CSSComputedStyleDeclaration::create(node);
m_mutableStyle =
propertiesToInclude == AllProperties && computedStyleAtPosition
? computedStyleAtPosition->copyProperties()
: editingStyleFromComputedStyle(computedStyleAtPosition);
if (propertiesToInclude == EditingPropertiesInEffect) {
if (const CSSValue* value = backgroundColorValueInEffect(node))
m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
if (const CSSValue* value = computedStyleAtPosition->getPropertyCSSValue(
CSSPropertyWebkitTextDecorationsInEffect))
m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
}
if (node && node->ensureComputedStyle()) {
const ComputedStyle* computedStyle = node->ensureComputedStyle();
removeTextFillAndStrokeColorsIfNeeded(computedStyle);
replaceFontSizeByKeywordIfPossible(computedStyle, computedStyleAtPosition);
}
m_isMonospaceFont = computedStyleAtPosition->isMonospaceFont();
extractFontSizeDelta();
}
void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(
const ComputedStyle* computedStyle) {
// 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 (computedStyle->textFillColor().isCurrentColor())
m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
if (computedStyle->textStrokeColor().isCurrentColor())
m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
}
void EditingStyle::setProperty(CSSPropertyID propertyID,
const String& value,
bool important) {
if (!m_mutableStyle)
m_mutableStyle = MutableStylePropertySet::create(HTMLQuirksMode);
m_mutableStyle->setProperty(propertyID, value, important);
}
void EditingStyle::replaceFontSizeByKeywordIfPossible(
const ComputedStyle* computedStyle,
CSSComputedStyleDeclaration* cssComputedStyle) {
DCHECK(computedStyle);
if (computedStyle->getFontDescription().keywordSize())
m_mutableStyle->setProperty(
CSSPropertyFontSize,
cssComputedStyle->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.
const CSSValue* value =
m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
if (!value || !value->isPrimitiveValue())
return;
const CSSPrimitiveValue* primitiveValue = toCSSPrimitiveValue(value);
// 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;
}
bool EditingStyle::textDirection(WritingDirection& writingDirection) const {
if (!m_mutableStyle)
return false;
const CSSValue* unicodeBidi =
m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicodeBidi || !unicodeBidi->isIdentifierValue())
return false;
CSSValueID unicodeBidiValue = toCSSIdentifierValue(unicodeBidi)->getValueID();
if (isEmbedOrIsolate(unicodeBidiValue)) {
const CSSValue* direction =
m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
if (!direction || !direction->isIdentifierValue())
return false;
writingDirection =
toCSSIdentifierValue(direction)->getValueID() == CSSValueLtr
? LeftToRightWritingDirection
: RightToLeftWritingDirection;
return true;
}
if (unicodeBidiValue == CSSValueNormal) {
writingDirection = NaturalWritingDirection;
return true;
}
return false;
}
void EditingStyle::overrideWithStyle(const StylePropertySet* style) {
if (!style || style->isEmpty())
return;
if (!m_mutableStyle)
m_mutableStyle = MutableStylePropertySet::create(HTMLQuirksMode);
m_mutableStyle->mergeAndOverrideOnConflict(style);
extractFontSizeDelta();
}
void EditingStyle::clear() {
m_mutableStyle.clear();
m_isMonospaceFont = false;
m_fontSizeDelta = NoFontDelta;
}
EditingStyle* EditingStyle::copy() const {
EditingStyle* copy = EditingStyle::create();
if (m_mutableStyle)
copy->m_mutableStyle = m_mutableStyle->mutableCopy();
copy->m_isMonospaceFont = m_isMonospaceFont;
copy->m_fontSizeDelta = m_fontSizeDelta;
return copy;
}
// This is the list of CSS properties that apply specially to block-level
// elements.
static const CSSPropertyID staticBlockProperties[] = {
CSSPropertyBreakAfter,
CSSPropertyBreakBefore,
CSSPropertyBreakInside,
CSSPropertyOrphans,
CSSPropertyOverflow, // This can be also be applied to replaced elements
CSSPropertyColumnCount,
CSSPropertyColumnGap,
CSSPropertyColumnRuleColor,
CSSPropertyColumnRuleStyle,
CSSPropertyColumnRuleWidth,
CSSPropertyWebkitColumnBreakBefore,
CSSPropertyWebkitColumnBreakAfter,
CSSPropertyWebkitColumnBreakInside,
CSSPropertyColumnWidth,
CSSPropertyPageBreakAfter,
CSSPropertyPageBreakBefore,
CSSPropertyPageBreakInside,
CSSPropertyTextAlign,
CSSPropertyTextAlignLast,
CSSPropertyTextIndent,
CSSPropertyTextJustify,
CSSPropertyWidows};
static const Vector<CSSPropertyID>& blockPropertiesVector() {
DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
if (properties.isEmpty())
CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(
staticBlockProperties, WTF_ARRAY_LENGTH(staticBlockProperties),
properties);
return properties;
}
EditingStyle* EditingStyle::extractAndRemoveBlockProperties() {
EditingStyle* blockProperties = EditingStyle::create();
if (!m_mutableStyle)
return blockProperties;
blockProperties->m_mutableStyle =
m_mutableStyle->copyPropertiesInSet(blockPropertiesVector());
removeBlockProperties();
return blockProperties;
}
EditingStyle* EditingStyle::extractAndRemoveTextDirection() {
EditingStyle* textDirection = EditingStyle::create();
textDirection->m_mutableStyle =
MutableStylePropertySet::create(HTMLQuirksMode);
textDirection->m_mutableStyle->setProperty(
CSSPropertyUnicodeBidi, CSSValueIsolate,
m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
textDirection->m_mutableStyle->setProperty(
CSSPropertyDirection,
m_mutableStyle->getPropertyValue(CSSPropertyDirection),
m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
m_mutableStyle->removeProperty(CSSPropertyDirection);
return textDirection;
}
void EditingStyle::removeBlockProperties() {
if (!m_mutableStyle)
return;
m_mutableStyle->removePropertiesInSet(blockPropertiesVector().data(),
blockPropertiesVector().size());
}
void EditingStyle::removeStyleAddedByElement(Element* element) {
if (!element || !element->parentNode())
return;
MutableStylePropertySet* parentStyle = editingStyleFromComputedStyle(
CSSComputedStyleDeclaration::create(element->parentNode()),
AllEditingProperties);
MutableStylePropertySet* nodeStyle = editingStyleFromComputedStyle(
CSSComputedStyleDeclaration::create(element), AllEditingProperties);
nodeStyle->removeEquivalentProperties(parentStyle);
m_mutableStyle->removeEquivalentProperties(nodeStyle);
}
void EditingStyle::removeStyleConflictingWithStyleOfElement(Element* element) {
if (!element || !element->parentNode() || !m_mutableStyle)
return;
MutableStylePropertySet* parentStyle = editingStyleFromComputedStyle(
CSSComputedStyleDeclaration::create(element->parentNode()),
AllEditingProperties);
MutableStylePropertySet* nodeStyle = editingStyleFromComputedStyle(
CSSComputedStyleDeclaration::create(element), AllEditingProperties);
nodeStyle->removeEquivalentProperties(parentStyle);
unsigned propertyCount = nodeStyle->propertyCount();
for (unsigned i = 0; i < propertyCount; ++i)
m_mutableStyle->removeProperty(nodeStyle->propertyAt(i).id());
}
void EditingStyle::collapseTextDecorationProperties() {
if (!m_mutableStyle)
return;
const CSSValue* textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(
CSSPropertyWebkitTextDecorationsInEffect);
if (!textDecorationsInEffect)
return;
if (textDecorationsInEffect->isValueList())
m_mutableStyle->setProperty(textDecorationPropertyForEditing(),
textDecorationsInEffect->cssText(),
m_mutableStyle->propertyIsImportant(
textDecorationPropertyForEditing()));
else
m_mutableStyle->removeProperty(textDecorationPropertyForEditing());
m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
}
// CSS properties that create a visual difference only when applied to text.
static const CSSPropertyID textOnlyProperties[] = {
// FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
// Decoration feature is no longer experimental.
CSSPropertyTextDecoration,
CSSPropertyTextDecorationLine,
CSSPropertyWebkitTextDecorationsInEffect,
CSSPropertyFontStyle,
CSSPropertyFontWeight,
CSSPropertyColor,
};
TriState EditingStyle::triStateOfStyle(EditingStyle* style) const {
if (!style || !style->m_mutableStyle)
return FalseTriState;
return triStateOfStyle(style->m_mutableStyle->ensureCSSStyleDeclaration(),
DoNotIgnoreTextOnlyProperties);
}
TriState EditingStyle::triStateOfStyle(
CSSStyleDeclaration* styleToCompare,
ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const {
MutableStylePropertySet* difference =
getPropertiesNotIn(m_mutableStyle.get(), styleToCompare);
if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
difference->removePropertiesInSet(textOnlyProperties,
WTF_ARRAY_LENGTH(textOnlyProperties));
if (difference->isEmpty())
return TrueTriState;
if (difference->propertyCount() == m_mutableStyle->propertyCount())
return FalseTriState;
return MixedTriState;
}
static bool hasAncestorVerticalAlignStyle(Node& node, CSSValueID value) {
for (Node& runner : NodeTraversal::inclusiveAncestorsOf(node)) {
CSSComputedStyleDeclaration* ancestorStyle =
CSSComputedStyleDeclaration::create(&runner);
if (getIdentifierValue(ancestorStyle, CSSPropertyVerticalAlign) == value)
return true;
}
return false;
}
TriState EditingStyle::triStateOfStyle(
const VisibleSelection& selection) const {
if (selection.isNone())
return FalseTriState;
if (selection.isCaret())
return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection));
TriState state = FalseTriState;
bool nodeIsStart = true;
for (Node& node : NodeTraversal::startsAt(*selection.start().anchorNode())) {
if (node.layoutObject() && hasEditableStyle(node)) {
CSSComputedStyleDeclaration* nodeStyle =
CSSComputedStyleDeclaration::create(&node);
if (nodeStyle) {
// If the selected element has <sub> or <sup> ancestor element, apply
// the corresponding style(vertical-align) to it so that
// document.queryCommandState() works with the style. See bug
// http://crbug.com/582225.
if (m_isVerticalAlign &&
getIdentifierValue(nodeStyle, CSSPropertyVerticalAlign) ==
CSSValueBaseline) {
const CSSIdentifierValue* verticalAlign = toCSSIdentifierValue(
m_mutableStyle->getPropertyCSSValue(CSSPropertyVerticalAlign));
if (hasAncestorVerticalAlignStyle(node, verticalAlign->getValueID()))
node.mutableComputedStyle()->setVerticalAlign(
verticalAlign->convertTo<EVerticalAlign>());
}
// Pass EditingStyle::DoNotIgnoreTextOnlyProperties without checking if
// node.isTextNode() because the node can be an element node. See bug
// http://crbug.com/584939.
TriState nodeState = triStateOfStyle(
nodeStyle, EditingStyle::DoNotIgnoreTextOnlyProperties);
if (nodeIsStart) {
state = nodeState;
nodeIsStart = false;
} else if (state != nodeState && node.isTextNode()) {
state = MixedTriState;
break;
}
}
}
if (&node == selection.end().anchorNode())
break;
}
return state;
}
bool EditingStyle::conflictsWithInlineStyleOfElement(
HTMLElement* element,
EditingStyle* extractedStyle,
Vector<CSSPropertyID>* conflictingProperties) const {
DCHECK(element);
DCHECK(!conflictingProperties || conflictingProperties->isEmpty());
const StylePropertySet* inlineStyle = element->inlineStyle();
if (!m_mutableStyle || !inlineStyle)
return false;
unsigned propertyCount = m_mutableStyle->propertyCount();
for (unsigned i = 0; i < propertyCount; ++i) {
CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
// We don't override whitespace property of a tab span because that would
// collapse the tab into a space.
if (propertyID == CSSPropertyWhiteSpace && isTabHTMLSpanElement(element))
continue;
if (propertyID == CSSPropertyWebkitTextDecorationsInEffect &&
inlineStyle->getPropertyCSSValue(textDecorationPropertyForEditing())) {
if (!conflictingProperties)
return true;
conflictingProperties->append(CSSPropertyTextDecoration);
// Because text-decoration expands to text-decoration-line when CSS3
// Text Decoration is enabled, we also state it as conflicting.
if (RuntimeEnabledFeatures::css3TextDecorationsEnabled())
conflictingProperties->append(CSSPropertyTextDecorationLine);
if (extractedStyle)
extractedStyle->setProperty(
textDecorationPropertyForEditing(),
inlineStyle->getPropertyValue(textDecorationPropertyForEditing()),
inlineStyle->propertyIsImportant(
textDecorationPropertyForEditing()));
continue;
}
if (!inlineStyle->getPropertyCSSValue(propertyID))
continue;
if (propertyID == CSSPropertyUnicodeBidi &&
inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
if (!conflictingProperties)
return true;
conflictingProperties->append(CSSPropertyDirection);
if (extractedStyle)
extractedStyle->setProperty(
propertyID, inlineStyle->getPropertyValue(propertyID),
inlineStyle->propertyIsImportant(propertyID));
}
if (!conflictingProperties)
return true;
conflictingProperties->append(propertyID);
if (extractedStyle)
extractedStyle->setProperty(propertyID,
inlineStyle->getPropertyValue(propertyID),
inlineStyle->propertyIsImportant(propertyID));
}
return conflictingProperties && !conflictingProperties->isEmpty();
}
static const HeapVector<Member<HTMLElementEquivalent>>&
htmlElementEquivalents() {
DEFINE_STATIC_LOCAL(HeapVector<Member<HTMLElementEquivalent>>,
HTMLElementEquivalents,
(new HeapVector<Member<HTMLElementEquivalent>>));
if (!HTMLElementEquivalents.size()) {
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
HTMLElementEquivalents.append(HTMLElementEquivalent::create(
CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(
CSSValueUnderline, HTMLNames::uTag));
HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(
CSSValueLineThrough, HTMLNames::sTag));
HTMLElementEquivalents.append(HTMLTextDecorationEquivalent::create(
CSSValueLineThrough, HTMLNames::strikeTag));
}
return HTMLElementEquivalents;
}
bool EditingStyle::conflictsWithImplicitStyleOfElement(
HTMLElement* element,
EditingStyle* extractedStyle,
ShouldExtractMatchingStyle shouldExtractMatchingStyle) const {
if (!m_mutableStyle)
return false;
const HeapVector<Member<HTMLElementEquivalent>>& HTMLElementEquivalents =
htmlElementEquivalents();
for (size_t i = 0; i < HTMLElementEquivalents.size(); ++i) {
const HTMLElementEquivalent* equivalent = HTMLElementEquivalents[i].get();
if (equivalent->matches(element) &&
equivalent->propertyExistsInStyle(m_mutableStyle.get()) &&
(shouldExtractMatchingStyle == ExtractMatchingStyle ||
!equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))) {
if (extractedStyle)
equivalent->addToStyle(element, extractedStyle);
return true;
}
}
return false;
}
static const HeapVector<Member<HTMLAttributeEquivalent>>&
htmlAttributeEquivalents() {
DEFINE_STATIC_LOCAL(HeapVector<Member<HTMLAttributeEquivalent>>,
HTMLAttributeEquivalents,
(new HeapVector<Member<HTMLAttributeEquivalent>>));
if (!HTMLAttributeEquivalents.size()) {
// elementIsStyledSpanOrHTMLEquivalent depends on the fact each
// HTMLAttriuteEquivalent matches exactly one attribute of exactly one
// element except dirAttr.
HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(
CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(
CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
HTMLAttributeEquivalents.append(HTMLFontSizeEquivalent::create());
HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(
CSSPropertyDirection, HTMLNames::dirAttr));
HTMLAttributeEquivalents.append(HTMLAttributeEquivalent::create(
CSSPropertyUnicodeBidi, HTMLNames::dirAttr));
}
return HTMLAttributeEquivalents;
}
bool EditingStyle::conflictsWithImplicitStyleOfAttributes(
HTMLElement* element) const {
DCHECK(element);
if (!m_mutableStyle)
return false;
const HeapVector<Member<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents =
htmlAttributeEquivalents();
for (const auto& equivalent : HTMLAttributeEquivalents) {
if (equivalent->matches(element) &&
equivalent->propertyExistsInStyle(m_mutableStyle.get()) &&
!equivalent->valueIsPresentInStyle(element, m_mutableStyle.get()))
return true;
}
return false;
}
bool EditingStyle::extractConflictingImplicitStyleOfAttributes(
HTMLElement* element,
ShouldPreserveWritingDirection shouldPreserveWritingDirection,
EditingStyle* extractedStyle,
Vector<QualifiedName>& conflictingAttributes,
ShouldExtractMatchingStyle shouldExtractMatchingStyle) const {
DCHECK(element);
// HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and
// direction properties
if (extractedStyle)
DCHECK_EQ(shouldPreserveWritingDirection, PreserveWritingDirection);
if (!m_mutableStyle)
return false;
const HeapVector<Member<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents =
htmlAttributeEquivalents();
bool removed = false;
for (const auto& attribute : HTMLAttributeEquivalents) {
const HTMLAttributeEquivalent* equivalent = attribute.get();
// unicode-bidi and direction are pushed down separately so don't push down
// with other styles.
if (shouldPreserveWritingDirection == PreserveWritingDirection &&
equivalent->attributeName() == HTMLNames::dirAttr)
continue;
if (!equivalent->matches(element) ||
!equivalent->propertyExistsInStyle(m_mutableStyle.get()) ||
(shouldExtractMatchingStyle == DoNotExtractMatchingStyle &&
equivalent->valueIsPresentInStyle(element, m_mutableStyle.get())))
continue;
if (extractedStyle)
equivalent->addToStyle(element, extractedStyle);
conflictingAttributes.append(equivalent->attributeName());
removed = true;
}
return removed;
}
bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const {
return !m_mutableStyle ||
getPropertiesNotIn(m_mutableStyle.get(),
CSSComputedStyleDeclaration::create(node))
->isEmpty();
}
bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(
const HTMLElement* element) {
DCHECK(element);
bool elementIsSpanOrElementEquivalent = false;
if (isHTMLSpanElement(*element)) {
elementIsSpanOrElementEquivalent = true;
} else {
const HeapVector<Member<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()) {
// span, b, etc... without any attributes
return elementIsSpanOrElementEquivalent;
}
unsigned matchedAttributes = 0;
const HeapVector<Member<HTMLAttributeEquivalent>>& HTMLAttributeEquivalents =
htmlAttributeEquivalents();
for (const auto& equivalent : HTMLAttributeEquivalents) {
if (equivalent->matches(element) &&
equivalent->attributeName() != HTMLNames::dirAttr)
matchedAttributes++;
}
if (!elementIsSpanOrElementEquivalent && !matchedAttributes) {
// element is not a span, a html element equivalent, or font element.
return false;
}
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...
DCHECK_LE(matchedAttributes, attributes.size());
return matchedAttributes >= attributes.size();
}
void EditingStyle::prepareToApplyAt(
const Position& position,
ShouldPreserveWritingDirection shouldPreserveWritingDirection) {
if (!m_mutableStyle)
return;
// ReplaceSelectionCommand::handleStyleSpans() requires that this function
// only removes the editing style. If this function was modified in the future
// to delete all redundant properties, then add a boolean value to indicate
// which one of editingStyleAtPosition or computedStyle is called.
EditingStyle* editingStyleAtPosition =
EditingStyle::create(position, EditingPropertiesInEffect);
StylePropertySet* styleAtPosition =
editingStyleAtPosition->m_mutableStyle.get();
const CSSValue* unicodeBidi = nullptr;
const CSSValue* direction = nullptr;
if (shouldPreserveWritingDirection == PreserveWritingDirection) {
unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
}
m_mutableStyle->removeEquivalentProperties(styleAtPosition);
if (textAlignResolvingStartAndEnd(m_mutableStyle.get()) ==
textAlignResolvingStartAndEnd(styleAtPosition))
m_mutableStyle->removeProperty(CSSPropertyTextAlign);
if (getFontColor(m_mutableStyle.get()) == getFontColor(styleAtPosition))
m_mutableStyle->removeProperty(CSSPropertyColor);
if (hasTransparentBackgroundColor(m_mutableStyle.get()) ||
cssValueToColor(
m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor)) ==
backgroundColorInEffect(position.computeContainerNode()))
m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
if (unicodeBidi && unicodeBidi->isIdentifierValue()) {
m_mutableStyle->setProperty(
CSSPropertyUnicodeBidi,
toCSSIdentifierValue(unicodeBidi)->getValueID());
if (direction && direction->isIdentifierValue()) {
m_mutableStyle->setProperty(
CSSPropertyDirection, toCSSIdentifierValue(direction)->getValueID());
}
}
}
void EditingStyle::mergeTypingStyle(Document* document) {
DCHECK(document);
EditingStyle* typingStyle = document->frame()->selection().typingStyle();
if (!typingStyle || typingStyle == this)
return;
mergeStyle(typingStyle->style(), OverrideValues);
}
void EditingStyle::mergeInlineStyleOfElement(
HTMLElement* element,
CSSPropertyOverrideMode mode,
PropertiesToInclude propertiesToInclude) {
DCHECK(element);
if (!element->inlineStyle())
return;
switch (propertiesToInclude) {
case AllProperties:
mergeStyle(element->inlineStyle(), mode);
return;
case OnlyEditingInheritableProperties:
mergeStyle(copyEditingProperties(element->inlineStyle(),
OnlyInheritableEditingProperties),
mode);
return;
case EditingPropertiesInEffect:
mergeStyle(
copyEditingProperties(element->inlineStyle(), AllEditingProperties),
mode);
return;
}
}
static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(
const HTMLElementEquivalent* equivalent,
const Element* element,
EditingStyle::CSSPropertyOverrideMode mode,
StylePropertySet* style) {
return equivalent->matches(element) &&
(!element->inlineStyle() ||
!equivalent->propertyExistsInStyle(element->inlineStyle())) &&
(mode == EditingStyle::OverrideValues ||
!equivalent->propertyExistsInStyle(style));
}
static MutableStylePropertySet* extractEditingProperties(
const StylePropertySet* style,
EditingStyle::PropertiesToInclude propertiesToInclude) {
if (!style)
return nullptr;
switch (propertiesToInclude) {
case EditingStyle::AllProperties:
case EditingStyle::EditingPropertiesInEffect:
return copyEditingProperties(style, AllEditingProperties);
case EditingStyle::OnlyEditingInheritableProperties:
return copyEditingProperties(style, OnlyInheritableEditingProperties);
}
NOTREACHED();
return nullptr;
}
void EditingStyle::mergeInlineAndImplicitStyleOfElement(
Element* element,
CSSPropertyOverrideMode mode,
PropertiesToInclude propertiesToInclude) {
EditingStyle* styleFromRules = EditingStyle::create();
styleFromRules->mergeStyleFromRulesForSerialization(element);
if (element->inlineStyle())
styleFromRules->m_mutableStyle->mergeAndOverrideOnConflict(
element->inlineStyle());
styleFromRules->m_mutableStyle = extractEditingProperties(
styleFromRules->m_mutableStyle.get(), propertiesToInclude);
mergeStyle(styleFromRules->m_mutableStyle.get(), mode);
const HeapVector<Member<HTMLElementEquivalent>>& elementEquivalents =
htmlElementEquivalents();
for (const auto& equivalent : elementEquivalents) {
if (elementMatchesAndPropertyIsNotInInlineStyleDecl(
equivalent.get(), element, mode, m_mutableStyle.get()))
equivalent->addToStyle(element, this);
}
const HeapVector<Member<HTMLAttributeEquivalent>>& attributeEquivalents =
htmlAttributeEquivalents();
for (const auto& attribute : attributeEquivalents) {
if (attribute->attributeName() == HTMLNames::dirAttr)
continue; // We don't want to include directionality
if (elementMatchesAndPropertyIsNotInInlineStyleDecl(
attribute.get(), element, mode, m_mutableStyle.get()))
attribute->addToStyle(element, this);
}
}
EditingStyle* EditingStyle::wrappingStyleForAnnotatedSerialization(
ContainerNode* context) {
EditingStyle* wrappingStyle =
EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect);
// Styles that Mail blockquotes contribute should only be placed on the Mail
// blockquote, to help us differentiate those styles from ones that the user
// has applied. This helps us get the color of content pasted into
// blockquotes right.
wrappingStyle->removeStyleAddedByElement(toHTMLElement(enclosingNodeOfType(
firstPositionInOrBeforeNode(context), isMailHTMLBlockquoteElement,
CanCrossEditingBoundary)));
// Call collapseTextDecorationProperties first or otherwise it'll copy the
// value over from in-effect to text-decorations.
wrappingStyle->collapseTextDecorationProperties();
return wrappingStyle;
}
EditingStyle* EditingStyle::wrappingStyleForSerialization(
ContainerNode* context) {
DCHECK(context);
EditingStyle* wrappingStyle = EditingStyle::create();
// When not annotating for interchange, we only preserve inline style
// declarations.
for (Node& node : NodeTraversal::inclusiveAncestorsOf(*context)) {
if (node.isDocumentNode())
break;
if (node.isStyledElement() && !isMailHTMLBlockquoteElement(&node)) {
wrappingStyle->mergeInlineAndImplicitStyleOfElement(
toElement(&node), EditingStyle::DoNotOverrideValues,
EditingStyle::EditingPropertiesInEffect);
}
}
return wrappingStyle;
}
static const CSSValueList& mergeTextDecorationValues(
const CSSValueList& mergedValue,
const CSSValueList& valueToMerge) {
DEFINE_STATIC_LOCAL(CSSIdentifierValue, underline,
(CSSIdentifierValue::create(CSSValueUnderline)));
DEFINE_STATIC_LOCAL(CSSIdentifierValue, lineThrough,
(CSSIdentifierValue::create(CSSValueLineThrough)));
CSSValueList& result = *mergedValue.copy();
if (valueToMerge.hasValue(underline) && !mergedValue.hasValue(underline))
result.append(underline);
if (valueToMerge.hasValue(lineThrough) && !mergedValue.hasValue(lineThrough))
result.append(lineThrough);
return result;
}
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);
const 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()) {
const CSSValueList& result = mergeTextDecorationValues(
*toCSSValueList(value), toCSSValueList(property.value()));
m_mutableStyle->setProperty(property.id(), result,
property.isImportant());
continue;
}
// text-decoration: none is equivalent to not having the property
value = nullptr;
}
if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
m_mutableStyle->setProperty(property.toCSSProperty());
}
}
static MutableStylePropertySet* styleFromMatchedRulesForElement(
Element* element,
unsigned rulesToInclude) {
MutableStylePropertySet* style =
MutableStylePropertySet::create(HTMLQuirksMode);
StyleRuleList* matchedRules =
element->document().ensureStyleResolver().styleRulesForElement(
element, rulesToInclude);
if (matchedRules) {
for (unsigned i = 0; i < matchedRules->size(); ++i)
style->mergeAndOverrideOnConflict(&matchedRules->at(i)->properties());
}
return style;
}
void EditingStyle::mergeStyleFromRules(Element* element) {
MutableStylePropertySet* styleFromMatchedRules =
styleFromMatchedRulesForElement(
element,
StyleResolver::AuthorCSSRules | StyleResolver::CrossOriginCSSRules);
// Styles from the inline style declaration, held in the variable "style",
// take precedence over those from matched rules.
if (m_mutableStyle)
styleFromMatchedRules->mergeAndOverrideOnConflict(m_mutableStyle.get());
clear();
m_mutableStyle = styleFromMatchedRules;
}
void EditingStyle::mergeStyleFromRulesForSerialization(Element* element) {
mergeStyleFromRules(element);
// The property value, if it's a percentage, may not reflect the actual
// computed value.
// For example: style="height: 1%; overflow: visible;" in quirksmode
// FIXME: There are others like this, see <rdar://problem/5195123> Slashdot
// copy/paste fidelity problem
CSSComputedStyleDeclaration* computedStyleForElement =
CSSComputedStyleDeclaration::create(element);
MutableStylePropertySet* fromComputedStyle =
MutableStylePropertySet::create(HTMLQuirksMode);
{
unsigned propertyCount = m_mutableStyle->propertyCount();
for (unsigned i = 0; i < propertyCount; ++i) {
StylePropertySet::PropertyReference property =
m_mutableStyle->propertyAt(i);
const CSSValue& value = property.value();
if (!value.isPrimitiveValue())
continue;
if (toCSSPrimitiveValue(value).isPercentage()) {
if (const CSSValue* computedPropertyValue =
computedStyleForElement->getPropertyCSSValue(property.id()))
fromComputedStyle->addRespectingCascade(
CSSProperty(property.id(), *computedPropertyValue));
}
}
}
m_mutableStyle->mergeAndOverrideOnConflict(fromComputedStyle);
}
static void removePropertiesInStyle(
MutableStylePropertySet* styleToRemovePropertiesFrom,
StylePropertySet* style) {
unsigned propertyCount = style->propertyCount();
Vector<CSSPropertyID> propertiesToRemove(propertyCount);
for (unsigned i = 0; i < propertyCount; ++i)
propertiesToRemove[i] = style->propertyAt(i).id();
styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(),
propertiesToRemove.size());
}
void EditingStyle::removeStyleFromRulesAndContext(Element* element,
ContainerNode* context) {
DCHECK(element);
if (!m_mutableStyle)
return;
// TODO(yosin): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. see http://crbug.com/590369 for more details.
element->document().updateStyleAndLayoutIgnorePendingStylesheetsForNode(
element);
// 1. Remove style from matched rules because style remain without repeating
// it in inline style declaration
MutableStylePropertySet* styleFromMatchedRules =
styleFromMatchedRulesForElement(element,
StyleResolver::AllButEmptyCSSRules);
if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
m_mutableStyle =
getPropertiesNotIn(m_mutableStyle.get(),
styleFromMatchedRules->ensureCSSStyleDeclaration());
// 2. Remove style present in context and not overriden by matched rules.
EditingStyle* computedStyle =
EditingStyle::create(context, EditingPropertiesInEffect);
if (computedStyle->m_mutableStyle) {
if (!computedStyle->m_mutableStyle->getPropertyCSSValue(
CSSPropertyBackgroundColor))
computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor,
CSSValueTransparent);
removePropertiesInStyle(computedStyle->m_mutableStyle.get(),
styleFromMatchedRules);
m_mutableStyle = getPropertiesNotIn(
m_mutableStyle.get(),
computedStyle->m_mutableStyle->ensureCSSStyleDeclaration());
}
// 3. If this element is a span and has display: inline or float: none, remove
// them unless they are overriden by rules. These rules are added by
// serialization code to wrap text nodes.
if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) &&
getIdentifierValue(m_mutableStyle.get(), CSSPropertyDisplay) ==
CSSValueInline)
m_mutableStyle->removeProperty(CSSPropertyDisplay);
if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) &&
getIdentifierValue(m_mutableStyle.get(), CSSPropertyFloat) ==
CSSValueNone)
m_mutableStyle->removeProperty(CSSPropertyFloat);
}
}
void EditingStyle::removePropertiesInElementDefaultStyle(Element* element) {
if (!m_mutableStyle || m_mutableStyle->isEmpty())
return;
StylePropertySet* defaultStyle = styleFromMatchedRulesForElement(
element, StyleResolver::UAAndUserCSSRules);
removePropertiesInStyle(m_mutableStyle.get(), defaultStyle);
}
void EditingStyle::addAbsolutePositioningFromElement(const Element& element) {
LayoutRect rect = element.boundingBox();
LayoutObject* layoutObject = element.layoutObject();
LayoutUnit x = rect.x();
LayoutUnit y = rect.y();
LayoutUnit width = rect.width();
LayoutUnit height = rect.height();
if (layoutObject && layoutObject->isBox()) {
LayoutBox* layoutBox = toLayoutBox(layoutObject);
x -= layoutBox->marginLeft();
y -= layoutBox->marginTop();
m_mutableStyle->setProperty(CSSPropertyBoxSizing, CSSValueBorderBox);
}
m_mutableStyle->setProperty(CSSPropertyPosition, CSSValueAbsolute);
m_mutableStyle->setProperty(
CSSPropertyLeft,
*CSSPrimitiveValue::create(x, CSSPrimitiveValue::UnitType::Pixels));
m_mutableStyle->setProperty(
CSSPropertyTop,
*CSSPrimitiveValue::create(y, CSSPrimitiveValue::UnitType::Pixels));
m_mutableStyle->setProperty(
CSSPropertyWidth,
*CSSPrimitiveValue::create(width, CSSPrimitiveValue::UnitType::Pixels));
m_mutableStyle->setProperty(
CSSPropertyHeight,
*CSSPrimitiveValue::create(height, CSSPrimitiveValue::UnitType::Pixels));
}
void EditingStyle::forceInline() {
if (!m_mutableStyle)
m_mutableStyle = MutableStylePropertySet::create(HTMLQuirksMode);
const bool propertyIsImportant = true;
m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline,
propertyIsImportant);
}
int EditingStyle::legacyFontSize(Document* document) const {
const CSSValue* cssValue =
m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
if (!cssValue ||
!(cssValue->isPrimitiveValue() || cssValue->isIdentifierValue()))
return 0;
return legacyFontSizeFromCSSValue(document, cssValue, m_isMonospaceFont,
AlwaysUseLegacyFontSize);
}
EditingStyle* EditingStyle::styleAtSelectionStart(
const VisibleSelection& selection,
bool shouldUseBackgroundColorInEffect,
MutableStylePropertySet* styleToCheck) {
if (selection.isNone())
return nullptr;
Document& document = *selection.start().document();
// TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. see http://crbug.com/590369 for more details.
document.updateStyleAndLayoutIgnorePendingStylesheets();
DocumentLifecycle::DisallowTransitionScope disallowTransition(
document.lifecycle());
Position position = adjustedSelectionStartForStyleComputation(selection);
// If the pos is at the end of a text node, then this node is not fully
// selected. Move it to the next deep equivalent position to avoid removing
// the style from this node.
// e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we
// want Position("world", 0) instead.
// We only do this for range because caret at Position("hello", 5) in
// <b>hello</b>world should give you font-weight: bold.
Node* positionNode = position.computeContainerNode();
if (selection.isRange() && positionNode && positionNode->isTextNode() &&
position.computeOffsetInContainerNode() ==
positionNode->maxCharacterOffset())
position = nextVisuallyDistinctCandidate(position);
Element* element = associatedElementOf(position);
if (!element)
return nullptr;
EditingStyle* style =
EditingStyle::create(element, EditingStyle::AllProperties);
style->mergeTypingStyle(&element->document());
// If |element| has <sub> or <sup> ancestor element, apply the corresponding
// style(vertical-align) to it so that document.queryCommandState() works with
// the style. See bug http://crbug.com/582225.
CSSValueID valueID =
getIdentifierValue(styleToCheck, CSSPropertyVerticalAlign);
if (valueID == CSSValueSub || valueID == CSSValueSuper) {
CSSComputedStyleDeclaration* elementStyle =
CSSComputedStyleDeclaration::create(element);
// Find the ancestor that has CSSValueSub or CSSValueSuper as the value of
// CSS vertical-align property.
if (getIdentifierValue(elementStyle, CSSPropertyVerticalAlign) ==
CSSValueBaseline &&
hasAncestorVerticalAlignStyle(*element, valueID))
style->m_mutableStyle->setProperty(CSSPropertyVerticalAlign, valueID);
}
// If background color is transparent, traverse parent nodes until we hit a
// different value or document root Also, if the selection is a range, ignore
// the background color at the start of selection, and find the background
// color of the common ancestor.
if (shouldUseBackgroundColorInEffect &&
(selection.isRange() ||
hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
const EphemeralRange range(selection.toNormalizedEphemeralRange());
if (const CSSValue* value =
backgroundColorValueInEffect(Range::commonAncestorContainer(
range.startPosition().computeContainerNode(),
range.endPosition().computeContainerNode())))
style->setProperty(CSSPropertyBackgroundColor, value->cssText());
}
return style;
}
static bool isUnicodeBidiNestedOrMultipleEmbeddings(CSSValueID valueID) {
return valueID == CSSValueEmbed || valueID == CSSValueBidiOverride ||
valueID == CSSValueWebkitIsolate ||
valueID == CSSValueWebkitIsolateOverride ||
valueID == CSSValueWebkitPlaintext || valueID == CSSValueIsolate ||
valueID == CSSValueIsolateOverride || valueID == CSSValuePlaintext;
}
WritingDirection EditingStyle::textDirectionForSelection(
const VisibleSelection& selection,
EditingStyle* typingStyle,
bool& hasNestedOrMultipleEmbeddings) {
hasNestedOrMultipleEmbeddings = true;
if (selection.isNone())
return NaturalWritingDirection;
Position position = mostForwardCaretPosition(selection.start());
Node* node = position.anchorNode();
if (!node)
return NaturalWritingDirection;
Position end;
if (selection.isRange()) {
end = mostBackwardCaretPosition(selection.end());
DCHECK(end.document());
const EphemeralRange caretRange(position.parentAnchoredEquivalent(),
end.parentAnchoredEquivalent());
for (Node& n : caretRange.nodes()) {
if (!n.isStyledElement())
continue;
CSSComputedStyleDeclaration* style =
CSSComputedStyleDeclaration::create(&n);
const CSSValue* unicodeBidi =
style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicodeBidi || !unicodeBidi->isIdentifierValue())
continue;
CSSValueID unicodeBidiValue =
toCSSIdentifierValue(unicodeBidi)->getValueID();
if (isUnicodeBidiNestedOrMultipleEmbeddings(unicodeBidiValue))
return NaturalWritingDirection;
}
}
if (selection.isCaret()) {
WritingDirection direction;
if (typingStyle && typingStyle->textDirection(direction)) {
hasNestedOrMultipleEmbeddings = false;
return direction;
}
node = selection.visibleStart().deepEquivalent().anchorNode();
}
DCHECK(node);
// 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 to decide.
Node* block = enclosingBlock(node);
WritingDirection foundDirection = NaturalWritingDirection;
for (Node& runner : NodeTraversal::inclusiveAncestorsOf(*node)) {
if (runner == block)
break;
if (!runner.isStyledElement())
continue;
Element* element = &toElement(runner);
CSSComputedStyleDeclaration* style =
CSSComputedStyleDeclaration::create(element);
const CSSValue* unicodeBidi =
style->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicodeBidi || !unicodeBidi->isIdentifierValue())
continue;
CSSValueID unicodeBidiValue =
toCSSIdentifierValue(unicodeBidi)->getValueID();
if (unicodeBidiValue == CSSValueNormal)
continue;
if (unicodeBidiValue == CSSValueBidiOverride)
return NaturalWritingDirection;
DCHECK(isEmbedOrIsolate(unicodeBidiValue)) << unicodeBidiValue;
const CSSValue* direction =
style->getPropertyCSSValue(CSSPropertyDirection);
if (!direction || !direction->isIdentifierValue())
continue;
int directionValue = toCSSIdentifierValue(direction)->getValueID();
if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
continue;
if (foundDirection != NaturalWritingDirection)
return NaturalWritingDirection;
// In the range case, make sure that the embedding element persists until
// the end of the range.
if (selection.isRange() && !end.anchorNode()->isDescendantOf(element))
return NaturalWritingDirection;
foundDirection = directionValue == CSSValueLtr
? LeftToRightWritingDirection
: RightToLeftWritingDirection;
}
hasNestedOrMultipleEmbeddings = false;
return foundDirection;
}
DEFINE_TRACE(EditingStyle) {
visitor->trace(m_mutableStyle);
}
static void reconcileTextDecorationProperties(MutableStylePropertySet* style) {
const CSSValue* textDecorationsInEffect =
style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
const CSSValue* textDecoration =
style->getPropertyCSSValue(textDecorationPropertyForEditing());
// We shouldn't have both text-decoration and
// -webkit-text-decorations-in-effect because that wouldn't make sense.
DCHECK(!textDecorationsInEffect || !textDecoration);
if (textDecorationsInEffect) {
style->setProperty(textDecorationPropertyForEditing(),
textDecorationsInEffect->cssText());
style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
textDecoration = textDecorationsInEffect;
}
// If text-decoration is set to "none", remove the property because we don't
// want to add redundant "text-decoration: none".
if (textDecoration && !textDecoration->isValueList())
style->removeProperty(textDecorationPropertyForEditing());
}
StyleChange::StyleChange(EditingStyle* style, const Position& position)
: m_applyBold(false),
m_applyItalic(false),
m_applyUnderline(false),
m_applyLineThrough(false),
m_applySubscript(false),
m_applySuperscript(false) {
Document* document = position.document();
if (!style || !style->style() || !document || !document->frame() ||
!associatedElementOf(position))
return;
CSSComputedStyleDeclaration* computedStyle = ensureComputedStyle(position);
// FIXME: take care of background-color in effect
MutableStylePropertySet* mutableStyle =
getPropertiesNotIn(style->style(), computedStyle);
DCHECK(mutableStyle);
reconcileTextDecorationProperties(mutableStyle);
if (!document->frame()->editor().shouldStyleWithCSS())
extractTextStyles(document, mutableStyle, computedStyle->isMonospaceFont());
// Changing the whitespace style in a tab span would collapse the tab into a
// space.
if (isTabHTMLSpanElementTextNode(position.anchorNode()) ||
isTabHTMLSpanElement((position.anchorNode())))
mutableStyle->removeProperty(CSSPropertyWhiteSpace);
// If unicode-bidi is present in mutableStyle and direction is not, then add
// direction to mutableStyle.
// FIXME: Shouldn't this be done in getPropertiesNotIn?
if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) &&
!style->style()->getPropertyCSSValue(CSSPropertyDirection))
mutableStyle->setProperty(
CSSPropertyDirection,
style->style()->getPropertyValue(CSSPropertyDirection));
// Save the result for later
m_cssStyle = mutableStyle->asText().stripWhiteSpace();
}
static void setTextDecorationProperty(MutableStylePropertySet* style,
const CSSValueList* newTextDecoration,
CSSPropertyID propertyID) {
if (newTextDecoration->length()) {
style->setProperty(propertyID, newTextDecoration->cssText(),
style->propertyIsImportant(propertyID));
} else {
// text-decoration: none is redundant since it does not remove any text
// decorations.
style->removeProperty(propertyID);
}
}
void StyleChange::extractTextStyles(Document* document,
MutableStylePropertySet* style,
bool isMonospaceFont) {
DCHECK(style);
if (getIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold) {
style->removeProperty(CSSPropertyFontWeight);
m_applyBold = true;
}
int fontStyle = getIdentifierValue(style, CSSPropertyFontStyle);
if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
style->removeProperty(CSSPropertyFontStyle);
m_applyItalic = true;
}
// Assuming reconcileTextDecorationProperties has been called, there should
// not be -webkit-text-decorations-in-effect
// Furthermore, text-decoration: none has been trimmed so that text-decoration
// property is always a CSSValueList.
const CSSValue* textDecoration =
style->getPropertyCSSValue(textDecorationPropertyForEditing());
if (textDecoration && textDecoration->isValueList()) {
DEFINE_STATIC_LOCAL(CSSIdentifierValue, underline,
(CSSIdentifierValue::create(CSSValueUnderline)));
DEFINE_STATIC_LOCAL(CSSIdentifierValue, lineThrough,
(CSSIdentifierValue::create(CSSValueLineThrough)));
CSSValueList* newTextDecoration = toCSSValueList(textDecoration)->copy();
if (newTextDecoration->removeAll(underline))
m_applyUnderline = true;
if (newTextDecoration->removeAll(lineThrough))
m_applyLineThrough = true;
// If trimTextDecorations, delete underline and line-through
setTextDecorationProperty(style, newTextDecoration,
textDecorationPropertyForEditing());
}
int verticalAlign = getIdentifierValue(style, CSSPropertyVerticalAlign);
switch (verticalAlign) {
case CSSValueSub:
style->removeProperty(CSSPropertyVerticalAlign);
m_applySubscript = true;
break;
case CSSValueSuper:
style->removeProperty(CSSPropertyVerticalAlign);
m_applySuperscript = true;
break;
}
if (style->getPropertyCSSValue(CSSPropertyColor)) {
m_applyFontColor = getFontColor(style).serialized();
style->removeProperty(CSSPropertyColor);
}
m_applyFontFace = style->getPropertyValue(CSSPropertyFontFamily);
// Remove double quotes for Outlook 2007 compatibility. See
// https://bugs.webkit.org/show_bug.cgi?id=79448
m_applyFontFace.replace('"', "");
style->removeProperty(CSSPropertyFontFamily);
if (const CSSValue* fontSize =
style->getPropertyCSSValue(CSSPropertyFontSize)) {
if (!fontSize->isPrimitiveValue() && !fontSize->isIdentifierValue()) {
// Can't make sense of the number. Put no font size.
style->removeProperty(CSSPropertyFontSize);
} else if (int legacyFontSize = legacyFontSizeFromCSSValue(
document, fontSize, isMonospaceFont,
UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
m_applyFontSize = String::number(legacyFontSize);
style->removeProperty(CSSPropertyFontSize);
}
}
}
static void diffTextDecorations(MutableStylePropertySet* style,
CSSPropertyID propertyID,
const CSSValue* refTextDecoration) {
const CSSValue* textDecoration = style->getPropertyCSSValue(propertyID);
if (!textDecoration || !textDecoration->isValueList() || !refTextDecoration ||
!refTextDecoration->isValueList())
return;
CSSValueList* newTextDecoration = toCSSValueList(textDecoration)->copy();
const CSSValueList* valuesInRefTextDecoration =
toCSSValueList(refTextDecoration);
for (size_t i = 0; i < valuesInRefTextDecoration->length(); i++)
newTextDecoration->removeAll(valuesInRefTextDecoration->item(i));
setTextDecorationProperty(style, newTextDecoration, propertyID);
}
static bool fontWeightIsBold(const CSSValue* fontWeight) {
if (!fontWeight->isIdentifierValue())
return false;
// Because b tag can only bold text, there are only two states in plain html:
// bold and not bold. Collapse all other values to either one of these two
// states for editing purposes.
switch (toCSSIdentifierValue(fontWeight)->getValueID()) {
case CSSValue100:
case CSSValue200:
case CSSValue300:
case CSSValue400:
case CSSValue500:
case CSSValueNormal:
return false;
case CSSValueBold:
case CSSValue600:
case CSSValue700:
case CSSValue800:
case CSSValue900:
return true;
default:
break;
}
NOTREACHED(); // For CSSValueBolder and CSSValueLighter
return false;
}
static bool fontWeightNeedsResolving(const CSSValue* fontWeight) {
if (!fontWeight->isIdentifierValue())
return true;
const CSSValueID value = toCSSIdentifierValue(fontWeight)->getValueID();
return value == CSSValueLighter || value == CSSValueBolder;
}
MutableStylePropertySet* getPropertiesNotIn(
StylePropertySet* styleWithRedundantProperties,
CSSStyleDeclaration* baseStyle) {
DCHECK(styleWithRedundantProperties);
DCHECK(baseStyle);
MutableStylePropertySet* result = styleWithRedundantProperties->mutableCopy();
result->removeEquivalentProperties(baseStyle);
const CSSValue* baseTextDecorationsInEffect =
baseStyle->getPropertyCSSValueInternal(
CSSPropertyWebkitTextDecorationsInEffect);
diffTextDecorations(result, textDecorationPropertyForEditing(),
baseTextDecorationsInEffect);
diffTextDecorations(result, CSSPropertyWebkitTextDecorationsInEffect,
baseTextDecorationsInEffect);
if (const CSSValue* baseFontWeight =
baseStyle->getPropertyCSSValueInternal(CSSPropertyFontWeight)) {
if (const CSSValue* fontWeight =
result->getPropertyCSSValue(CSSPropertyFontWeight)) {
if (!fontWeightNeedsResolving(fontWeight) &&
!fontWeightNeedsResolving(baseFontWeight) &&
(fontWeightIsBold(fontWeight) == fontWeightIsBold(baseFontWeight)))
result->removeProperty(CSSPropertyFontWeight);
}
}
if (baseStyle->getPropertyCSSValueInternal(CSSPropertyColor) &&
getFontColor(result) == getFontColor(baseStyle))
result->removeProperty(CSSPropertyColor);
if (baseStyle->getPropertyCSSValueInternal(CSSPropertyTextAlign) &&
textAlignResolvingStartAndEnd(result) ==
textAlignResolvingStartAndEnd(baseStyle))
result->removeProperty(CSSPropertyTextAlign);
if (baseStyle->getPropertyCSSValueInternal(CSSPropertyBackgroundColor) &&
getBackgroundColor(result) == getBackgroundColor(baseStyle))
result->removeProperty(CSSPropertyBackgroundColor);
return result;
}
CSSValueID getIdentifierValue(StylePropertySet* style,
CSSPropertyID propertyID) {
if (!style)
return CSSValueInvalid;
const CSSValue* value = style->getPropertyCSSValue(propertyID);
if (!value || !value->isIdentifierValue())
return CSSValueInvalid;
return toCSSIdentifierValue(value)->getValueID();
}
CSSValueID getIdentifierValue(CSSStyleDeclaration* style,
CSSPropertyID propertyID) {
if (!style)
return CSSValueInvalid;
const CSSValue* value = style->getPropertyCSSValueInternal(propertyID);
if (!value || !value->isIdentifierValue())
return CSSValueInvalid;
return toCSSIdentifierValue(value)->getValueID();
}
int legacyFontSizeFromCSSValue(Document* document,
const CSSValue* value,
bool isMonospaceFont,
LegacyFontSizeMode mode) {
if (value->isPrimitiveValue()) {
const CSSPrimitiveValue& primitiveValue = toCSSPrimitiveValue(*value);
CSSPrimitiveValue::LengthUnitType lengthType;
if (CSSPrimitiveValue::unitTypeToLengthUnitType(
primitiveValue.typeWithCalcResolved(), lengthType) &&
lengthType == CSSPrimitiveValue::UnitTypePixels) {
double conversion =
CSSPrimitiveValue::conversionToCanonicalUnitsScaleFactor(
primitiveValue.typeWithCalcResolved());
int pixelFontSize =
clampTo<int>(primitiveValue.getDoubleValue() * conversion);
int legacyFontSize =
FontSize::legacyFontSize(document, pixelFontSize, isMonospaceFont);
// Use legacy font size only if pixel value matches exactly to that of
// legacy font size.
if (mode == AlwaysUseLegacyFontSize ||
FontSize::fontSizeForKeyword(document, legacyFontSize,
isMonospaceFont) == pixelFontSize)
return legacyFontSize;
return 0;
}
}
if (value->isIdentifierValue()) {
const CSSIdentifierValue& identifierValue = toCSSIdentifierValue(*value);
if (CSSValueXSmall <= identifierValue.getValueID() &&
identifierValue.getValueID() <= CSSValueWebkitXxxLarge)
return identifierValue.getValueID() - CSSValueXSmall + 1;
}
return 0;
}
bool isTransparentColorValue(const CSSValue* cssValue) {
if (!cssValue)
return true;
if (cssValue->isColorValue())
return !toCSSColorValue(cssValue)->value().alpha();
if (!cssValue->isIdentifierValue())
return false;
return toCSSIdentifierValue(cssValue)->getValueID() == CSSValueTransparent;
}
bool hasTransparentBackgroundColor(CSSStyleDeclaration* style) {
const CSSValue* cssValue =
style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor);
return isTransparentColorValue(cssValue);
}
bool hasTransparentBackgroundColor(StylePropertySet* style) {
const CSSValue* cssValue =
style->getPropertyCSSValue(CSSPropertyBackgroundColor);
return isTransparentColorValue(cssValue);
}
const CSSValue* backgroundColorValueInEffect(Node* node) {
for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
CSSComputedStyleDeclaration* ancestorStyle =
CSSComputedStyleDeclaration::create(ancestor);
if (!hasTransparentBackgroundColor(ancestorStyle))
return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
}
return nullptr;
}
} // namespace blink