Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(191)

Unified Diff: third_party/WebKit/Source/core/editing/EditingStyleUtilities.cpp

Issue 2649613002: Add EditingStyleUtilities.{cpp,h} (Closed)
Patch Set: same as the patch set1 Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/core/editing/EditingStyleUtilities.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/Source/core/editing/EditingStyleUtilities.cpp
diff --git a/third_party/WebKit/Source/core/editing/EditingStyleUtilities.cpp b/third_party/WebKit/Source/core/editing/EditingStyleUtilities.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1df94712e37316dc885fad69969c0f7b0aac1eb5
--- /dev/null
+++ b/third_party/WebKit/Source/core/editing/EditingStyleUtilities.cpp
@@ -0,0 +1,2063 @@
+/*
+ * 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/ExceptionState.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,
+ CSSPropertyCaretColor};
+
+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();
+ removeInheritedColorsIfNeeded(computedStyle);
+ replaceFontSizeByKeywordIfPossible(computedStyle, computedStyleAtPosition);
+ }
+
+ m_isMonospaceFont = computedStyleAtPosition->isMonospaceFont();
+ extractFontSizeDelta();
+}
+
+void EditingStyle::removeInheritedColorsIfNeeded(
+ 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.
+ // Similar thing happens for caret-color if it's auto or currentColor.
+ if (computedStyle->textFillColor().isCurrentColor())
+ m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
+ if (computedStyle->textStrokeColor().isCurrentColor())
+ m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
+ if (computedStyle->caretColor().isAutoColor() ||
+ computedStyle->caretColor().isCurrentColor())
+ m_mutableStyle->removeProperty(CSSPropertyCaretColor);
+}
+
+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->push_back(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->push_back(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->push_back(CSSPropertyDirection);
+ if (extractedStyle) {
+ extractedStyle->setProperty(
+ propertyID, inlineStyle->getPropertyValue(propertyID),
+ inlineStyle->propertyIsImportant(propertyID));
+ }
+ }
+
+ if (!conflictingProperties)
+ return true;
+
+ conflictingProperties->push_back(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.push_back(HTMLElementEquivalent::create(
+ CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag));
+ HTMLElementEquivalents.push_back(HTMLElementEquivalent::create(
+ CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag));
+ HTMLElementEquivalents.push_back(HTMLElementEquivalent::create(
+ CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag));
+ HTMLElementEquivalents.push_back(HTMLElementEquivalent::create(
+ CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag));
+ HTMLElementEquivalents.push_back(HTMLElementEquivalent::create(
+ CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag));
+ HTMLElementEquivalents.push_back(HTMLElementEquivalent::create(
+ CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag));
+
+ HTMLElementEquivalents.push_back(HTMLTextDecorationEquivalent::create(
+ CSSValueUnderline, HTMLNames::uTag));
+ HTMLElementEquivalents.push_back(HTMLTextDecorationEquivalent::create(
+ CSSValueLineThrough, HTMLNames::sTag));
+ HTMLElementEquivalents.push_back(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.push_back(HTMLAttributeEquivalent::create(
+ CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr));
+ HTMLAttributeEquivalents.push_back(HTMLAttributeEquivalent::create(
+ CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr));
+ HTMLAttributeEquivalents.push_back(HTMLFontSizeEquivalent::create());
+
+ HTMLAttributeEquivalents.push_back(HTMLAttributeEquivalent::create(
+ CSSPropertyDirection, HTMLNames::dirAttr));
+ HTMLAttributeEquivalents.push_back(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.push_back(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;
+
+ // StyleResolver requires clean style.
+ DCHECK_GE(element->document().lifecycle().state(),
+ DocumentLifecycle::StyleClean);
+ DCHECK(element->document().isActive());
+
+ // 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();
+
+ DCHECK(!document.needsLayoutTreeUpdate());
+ 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());
+ // "LayoutTests/editing/execCommand/insert-list-and-strikethrough.html" makes
+ // both |textDecorationsInEffect| and |textDecoration| non-null.
+ 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
« no previous file with comments | « third_party/WebKit/Source/core/editing/EditingStyleUtilities.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698