blob: 3a318172e522a4524ba290b4b029fefd2cd372c7 [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 "third_party/blink/renderer/core/editing/editing_style.h"
#include "base/stl_util.h"
#include "third_party/blink/renderer/core/css/css_color_value.h"
#include "third_party/blink/renderer/core/css/css_computed_style_declaration.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_rule_list.h"
#include "third_party/blink/renderer/core/css/css_style_rule.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/font_size_functions.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_rule.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/qualified_name.h"
#include "third_party/blink/renderer/core/editing/commands/apply_style_command.h"
#include "third_party/blink/renderer/core/editing/editing_style_utilities.h"
#include "third_party/blink/renderer/core/editing/editing_tri_state.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/position.h"
#include "third_party/blink/renderer/core/editing/serializers/html_interchange.h"
#include "third_party/blink/renderer/core/editing/visible_selection.h"
#include "third_party/blink/renderer/core/editing/writing_direction.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_font_element.h"
#include "third_party/blink/renderer/core/html/html_span_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace blink {
using namespace cssvalue;
// 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 kStaticEditingProperties[] = {
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 {
kOnlyInheritableEditingProperties,
kAllEditingProperties
};
static const Vector<const CSSProperty*>& AllEditingProperties() {
DEFINE_STATIC_LOCAL(Vector<const CSSProperty*>, properties, ());
if (properties.IsEmpty()) {
CSSProperty::FilterEnabledCSSPropertiesIntoVector(
kStaticEditingProperties, base::size(kStaticEditingProperties),
properties);
for (wtf_size_t index = 0; index < properties.size(); index++) {
if (properties[index]->IDEquals(CSSPropertyTextDecoration)) {
properties.EraseAt(index);
break;
}
}
}
return properties;
}
static const Vector<const CSSProperty*>& InheritableEditingProperties() {
DEFINE_STATIC_LOCAL(Vector<const CSSProperty*>, properties, ());
if (properties.IsEmpty()) {
CSSProperty::FilterEnabledCSSPropertiesIntoVector(
kStaticEditingProperties, base::size(kStaticEditingProperties),
properties);
for (wtf_size_t index = 0; index < properties.size();) {
if (!properties[index]->IsInherited()) {
properties.EraseAt(index);
continue;
}
++index;
}
}
return properties;
}
template <class StyleDeclarationType>
static MutableCSSPropertyValueSet* CopyEditingProperties(
StyleDeclarationType* style,
EditingPropertiesType type = kOnlyInheritableEditingProperties) {
if (type == kAllEditingProperties)
return style->CopyPropertiesInSet(AllEditingProperties());
return style->CopyPropertiesInSet(InheritableEditingProperties());
}
static inline bool IsEditingProperty(CSSPropertyID id) {
static const Vector<const CSSProperty*>& properties = AllEditingProperties();
for (wtf_size_t index = 0; index < properties.size(); index++) {
if (properties[index]->IDEquals(id))
return true;
}
return false;
}
static CSSComputedStyleDeclaration* EnsureComputedStyle(
const Position& position) {
Element* elem = AssociatedElementOf(position);
if (!elem)
return nullptr;
return CSSComputedStyleDeclaration::Create(elem);
}
static MutableCSSPropertyValueSet* GetPropertiesNotIn(
CSSPropertyValueSet* style_with_redundant_properties,
CSSStyleDeclaration* base_style,
SecureContextMode);
enum LegacyFontSizeMode {
kAlwaysUseLegacyFontSize,
kUseLegacyFontSizeOnlyIfPixelValuesMatch
};
static int LegacyFontSizeFromCSSValue(Document*,
const CSSValue*,
bool,
LegacyFontSizeMode);
class HTMLElementEquivalent : public GarbageCollected<HTMLElementEquivalent> {
public:
static HTMLElementEquivalent* Create(CSSPropertyID property_id,
CSSValueID primitive_value,
const HTMLQualifiedName& tag_name) {
return MakeGarbageCollected<HTMLElementEquivalent>(
property_id, primitive_value, tag_name);
}
HTMLElementEquivalent(CSSPropertyID);
HTMLElementEquivalent(CSSPropertyID, const HTMLQualifiedName& tag_name);
HTMLElementEquivalent(CSSPropertyID,
CSSValueID primitive_value,
const HTMLQualifiedName& tag_name);
virtual bool Matches(const Element* element) const {
return !tag_name_ || element->HasTagName(*tag_name_);
}
virtual bool HasAttribute() const { return false; }
virtual bool PropertyExistsInStyle(const CSSPropertyValueSet* style) const {
return style->GetPropertyCSSValue(property_id_);
}
virtual bool ValueIsPresentInStyle(HTMLElement*, CSSPropertyValueSet*) const;
virtual void AddToStyle(Element*, EditingStyle*) const;
virtual void Trace(blink::Visitor* visitor) {
visitor->Trace(identifier_value_);
}
protected:
const CSSPropertyID property_id_;
const Member<CSSIdentifierValue> identifier_value_;
// We can store a pointer because HTML tag names are const global.
const HTMLQualifiedName* tag_name_;
};
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
: property_id_(id), tag_name_(nullptr) {}
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id,
const HTMLQualifiedName& tag_name)
: property_id_(id), tag_name_(&tag_name) {}
HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id,
CSSValueID value_id,
const HTMLQualifiedName& tag_name)
: property_id_(id),
identifier_value_(CSSIdentifierValue::Create(value_id)),
tag_name_(&tag_name) {
DCHECK_NE(value_id, CSSValueInvalid);
}
bool HTMLElementEquivalent::ValueIsPresentInStyle(
HTMLElement* element,
CSSPropertyValueSet* style) const {
const CSSValue* value = style->GetPropertyCSSValue(property_id_);
// TODO: Does this work on style or computed style? The code here, but we
// might need to do something here to match CSSPrimitiveValues. if
// (property_id_ == CSSPropertyFontWeight &&
// identifier_value_->GetValueID() == CSSValueBold) {
// if (value->IsPrimitiveValue() &&
// ToCSSPrimitiveValue(value)->GetFloatValue() >= BoldThreshold()) {
// LOG(INFO) << "weight match in HTMLElementEquivalent for primitive
// value"; return true;
// } else {
// LOG(INFO) << "weight match in HTMLElementEquivalent for identifier
// value";
// }
// }
return Matches(element) && value && value->IsIdentifierValue() &&
ToCSSIdentifierValue(value)->GetValueID() ==
identifier_value_->GetValueID();
}
void HTMLElementEquivalent::AddToStyle(Element* element,
EditingStyle* style) const {
style->SetProperty(property_id_, identifier_value_->CssText(),
/* important */ false,
element->GetDocument().GetSecureContextMode());
}
class HTMLTextDecorationEquivalent final : public HTMLElementEquivalent {
public:
static HTMLElementEquivalent* Create(CSSValueID primitive_value,
const HTMLQualifiedName& tag_name) {
return MakeGarbageCollected<HTMLTextDecorationEquivalent>(primitive_value,
tag_name);
}
HTMLTextDecorationEquivalent(CSSValueID primitive_value,
const HTMLQualifiedName& tag_name);
bool PropertyExistsInStyle(const CSSPropertyValueSet*) const override;
bool ValueIsPresentInStyle(HTMLElement*, CSSPropertyValueSet*) const override;
void Trace(blink::Visitor* visitor) override {
HTMLElementEquivalent::Trace(visitor);
}
};
HTMLTextDecorationEquivalent::HTMLTextDecorationEquivalent(
CSSValueID primitive_value,
const HTMLQualifiedName& tag_name)
: HTMLElementEquivalent(CSSPropertyTextDecorationLine,
primitive_value,
tag_name)
// CSSPropertyTextDecorationLine is used in HTMLElementEquivalent::AddToStyle
{}
bool HTMLTextDecorationEquivalent::PropertyExistsInStyle(
const CSSPropertyValueSet* style) const {
return style->GetPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect) ||
style->GetPropertyCSSValue(CSSPropertyTextDecorationLine);
}
bool HTMLTextDecorationEquivalent::ValueIsPresentInStyle(
HTMLElement* element,
CSSPropertyValueSet* style) const {
const CSSValue* style_value =
style->GetPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
if (!style_value)
style_value = style->GetPropertyCSSValue(CSSPropertyTextDecorationLine);
return Matches(element) && style_value && style_value->IsValueList() &&
ToCSSValueList(style_value)->HasValue(*identifier_value_);
}
class HTMLAttributeEquivalent : public HTMLElementEquivalent {
public:
static HTMLAttributeEquivalent* Create(CSSPropertyID property_id,
const HTMLQualifiedName& tag_name,
const QualifiedName& attr_name) {
return MakeGarbageCollected<HTMLAttributeEquivalent>(property_id, tag_name,
attr_name);
}
static HTMLAttributeEquivalent* Create(CSSPropertyID property_id,
const QualifiedName& attr_name) {
return MakeGarbageCollected<HTMLAttributeEquivalent>(property_id,
attr_name);
}
HTMLAttributeEquivalent(CSSPropertyID,
const HTMLQualifiedName& tag_name,
const QualifiedName& attr_name);
HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attr_name);
bool Matches(const Element* element) const override {
return HTMLElementEquivalent::Matches(element) &&
element->hasAttribute(attr_name_);
}
bool HasAttribute() const override { return true; }
bool ValueIsPresentInStyle(HTMLElement*, CSSPropertyValueSet*) const override;
void AddToStyle(Element*, EditingStyle*) const override;
virtual const CSSValue* AttributeValueAsCSSValue(Element*) const;
inline const QualifiedName& AttributeName() const { return attr_name_; }
void Trace(blink::Visitor* visitor) override {
HTMLElementEquivalent::Trace(visitor);
}
protected:
// We can store a reference because HTML attribute names are const global.
const QualifiedName& attr_name_;
};
HTMLAttributeEquivalent::HTMLAttributeEquivalent(
CSSPropertyID id,
const HTMLQualifiedName& tag_name,
const QualifiedName& attr_name)
: HTMLElementEquivalent(id, tag_name), attr_name_(attr_name) {}
HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id,
const QualifiedName& attr_name)
: HTMLElementEquivalent(id), attr_name_(attr_name) {}
bool HTMLAttributeEquivalent::ValueIsPresentInStyle(
HTMLElement* element,
CSSPropertyValueSet* style) const {
const CSSValue* value = AttributeValueAsCSSValue(element);
const CSSValue* style_value = style->GetPropertyCSSValue(property_id_);
return DataEquivalent(value, style_value);
}
void HTMLAttributeEquivalent::AddToStyle(Element* element,
EditingStyle* style) const {
if (const CSSValue* value = AttributeValueAsCSSValue(element)) {
style->SetProperty(property_id_, value->CssText(), /* important */ false,
element->GetDocument().GetSecureContextMode());
}
}
const CSSValue* HTMLAttributeEquivalent::AttributeValueAsCSSValue(
Element* element) const {
DCHECK(element);
const AtomicString& value = element->getAttribute(attr_name_);
if (value.IsNull())
return nullptr;
MutableCSSPropertyValueSet* dummy_style = nullptr;
dummy_style = MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
dummy_style->SetProperty(property_id_, value, /* important */ false,
element->GetDocument().GetSecureContextMode());
return dummy_style->GetPropertyCSSValue(property_id_);
}
class HTMLFontSizeEquivalent final : public HTMLAttributeEquivalent {
public:
static HTMLFontSizeEquivalent* Create() {
return MakeGarbageCollected<HTMLFontSizeEquivalent>();
}
HTMLFontSizeEquivalent();
const CSSValue* AttributeValueAsCSSValue(Element*) const override;
void Trace(blink::Visitor* visitor) override {
HTMLAttributeEquivalent::Trace(visitor);
}
};
HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
: HTMLAttributeEquivalent(CSSPropertyFontSize,
html_names::kFontTag,
html_names::kSizeAttr) {}
const CSSValue* HTMLFontSizeEquivalent::AttributeValueAsCSSValue(
Element* element) const {
DCHECK(element);
const AtomicString& value = element->getAttribute(attr_name_);
if (value.IsNull())
return nullptr;
CSSValueID size;
if (!HTMLFontElement::CssValueFromFontSizeNumber(value, size))
return nullptr;
return CSSIdentifierValue::Create(size);
}
float EditingStyle::no_font_delta_ = 0.0f;
EditingStyle::EditingStyle(ContainerNode* node,
PropertiesToInclude properties_to_include) {
Init(node, properties_to_include);
}
EditingStyle::EditingStyle(const Position& position,
PropertiesToInclude properties_to_include) {
Init(position.AnchorNode(), properties_to_include);
}
EditingStyle::EditingStyle(const CSSPropertyValueSet* style)
: mutable_style_(style ? style->MutableCopy() : nullptr) {
ExtractFontSizeDelta();
}
EditingStyle::EditingStyle(CSSPropertyID property_id,
const String& value,
SecureContextMode secure_context_mode)
: mutable_style_(nullptr) {
SetProperty(property_id, value, /* important */ false, secure_context_mode);
is_vertical_align_ = property_id == CSSPropertyVerticalAlign &&
(value == "sub" || value == "super");
}
static Color CssValueToColor(const CSSValue* color_value) {
if (!color_value ||
(!color_value->IsColorValue() && !color_value->IsPrimitiveValue() &&
!color_value->IsIdentifierValue()))
return Color::kTransparent;
if (color_value->IsColorValue())
return ToCSSColorValue(color_value)->Value();
Color color = 0;
// FIXME: Why ignore the return value?
CSSParser::ParseColor(color, color_value->CssText());
return color;
}
static inline Color GetFontColor(CSSStyleDeclaration* style) {
return CssValueToColor(style->GetPropertyCSSValueInternal(CSSPropertyColor));
}
static inline Color GetFontColor(CSSPropertyValueSet* style) {
return CssValueToColor(style->GetPropertyCSSValue(CSSPropertyColor));
}
static inline Color GetBackgroundColor(CSSStyleDeclaration* style) {
return CssValueToColor(
style->GetPropertyCSSValueInternal(CSSPropertyBackgroundColor));
}
static inline Color GetBackgroundColor(CSSPropertyValueSet* style) {
return CssValueToColor(
style->GetPropertyCSSValue(CSSPropertyBackgroundColor));
}
static inline Color BackgroundColorInEffect(Node* node) {
return CssValueToColor(
EditingStyleUtilities::BackgroundColorValueInEffect(node));
}
static int TextAlignResolvingStartAndEnd(int text_align, int direction) {
switch (text_align) {
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 properties_to_include) {
if (IsTabHTMLSpanElementTextNode(node))
node = TabSpanElement(node)->parentNode();
else if (IsTabHTMLSpanElement(node))
node = node->parentNode();
CSSComputedStyleDeclaration* computed_style_at_position =
CSSComputedStyleDeclaration::Create(node);
mutable_style_ =
properties_to_include == kAllProperties && computed_style_at_position
? computed_style_at_position->CopyProperties()
: CopyEditingProperties(computed_style_at_position);
if (properties_to_include == kEditingPropertiesInEffect) {
if (const CSSValue* value =
EditingStyleUtilities::BackgroundColorValueInEffect(node)) {
mutable_style_->SetProperty(CSSPropertyBackgroundColor, value->CssText(),
/* important */ false,
node->GetDocument().GetSecureContextMode());
}
if (const CSSValue* value = computed_style_at_position->GetPropertyCSSValue(
GetCSSPropertyWebkitTextDecorationsInEffect())) {
mutable_style_->SetProperty(CSSPropertyTextDecoration, value->CssText(),
/* important */ false,
node->GetDocument().GetSecureContextMode());
}
}
if (node && node->EnsureComputedStyle()) {
const ComputedStyle* computed_style = node->EnsureComputedStyle();
// Fix for crbug.com/768261: due to text-autosizing, reading the current
// computed font size and re-writing it to an element may actually cause the
// font size to become larger (since the autosizer will run again on the new
// computed size). The fix is to toss out the computed size property here
// and use ComputedStyle::SpecifiedFontSize().
if (computed_style->ComputedFontSize() !=
computed_style->SpecifiedFontSize()) {
// ReplaceSelectionCommandTest_TextAutosizingDoesntInflateText gets here.
mutable_style_->SetProperty(
CSSPropertyFontSize,
CSSPrimitiveValue::Create(computed_style->SpecifiedFontSize(),
CSSPrimitiveValue::UnitType::kPixels)
->CssText(),
/* important */ false, node->GetDocument().GetSecureContextMode());
}
RemoveInheritedColorsIfNeeded(computed_style);
ReplaceFontSizeByKeywordIfPossible(
computed_style, node->GetDocument().GetSecureContextMode(),
computed_style_at_position);
}
is_monospace_font_ = computed_style_at_position->IsMonospaceFont();
ExtractFontSizeDelta();
}
void EditingStyle::RemoveInheritedColorsIfNeeded(
const ComputedStyle* computed_style) {
// 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 (computed_style->TextFillColor().IsCurrentColor())
mutable_style_->RemoveProperty(CSSPropertyWebkitTextFillColor);
if (computed_style->TextStrokeColor().IsCurrentColor())
mutable_style_->RemoveProperty(CSSPropertyWebkitTextStrokeColor);
if (computed_style->CaretColor().IsAutoColor() ||
computed_style->CaretColor().IsCurrentColor())
mutable_style_->RemoveProperty(CSSPropertyCaretColor);
}
void EditingStyle::SetProperty(CSSPropertyID property_id,
const String& value,
bool important,
SecureContextMode secure_context_mode) {
if (!mutable_style_)
mutable_style_ = MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
mutable_style_->SetProperty(property_id, value, important,
secure_context_mode);
}
void EditingStyle::ReplaceFontSizeByKeywordIfPossible(
const ComputedStyle* computed_style,
SecureContextMode secure_context_mode,
CSSComputedStyleDeclaration* css_computed_style) {
DCHECK(computed_style);
if (computed_style->GetFontDescription().KeywordSize()) {
mutable_style_->SetProperty(
CSSPropertyFontSize,
css_computed_style->GetFontSizeCSSValuePreferringKeyword()->CssText(),
/* important */ false, secure_context_mode);
}
}
void EditingStyle::ExtractFontSizeDelta() {
if (!mutable_style_)
return;
if (mutable_style_->GetPropertyCSSValue(CSSPropertyFontSize)) {
// Explicit font size overrides any delta.
mutable_style_->RemoveProperty(CSSPropertyWebkitFontSizeDelta);
return;
}
// Get the adjustment amount out of the style.
const CSSValue* value =
mutable_style_->GetPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
if (!value || !value->IsPrimitiveValue())
return;
const CSSPrimitiveValue* primitive_value = ToCSSPrimitiveValue(value);
// Only PX handled now. If we handle more types in the future, perhaps
// a switch statement here would be more appropriate.
if (!primitive_value->IsPx())
return;
font_size_delta_ = primitive_value->GetFloatValue();
mutable_style_->RemoveProperty(CSSPropertyWebkitFontSizeDelta);
}
bool EditingStyle::IsEmpty() const {
return (!mutable_style_ || mutable_style_->IsEmpty()) &&
font_size_delta_ == no_font_delta_;
}
bool EditingStyle::GetTextDirection(WritingDirection& writing_direction) const {
if (!mutable_style_)
return false;
const CSSValue* unicode_bidi =
mutable_style_->GetPropertyCSSValue(CSSPropertyUnicodeBidi);
if (!unicode_bidi || !unicode_bidi->IsIdentifierValue())
return false;
CSSValueID unicode_bidi_value =
ToCSSIdentifierValue(unicode_bidi)->GetValueID();
if (EditingStyleUtilities::IsEmbedOrIsolate(unicode_bidi_value)) {
const CSSValue* direction =
mutable_style_->GetPropertyCSSValue(CSSPropertyDirection);
if (!direction || !direction->IsIdentifierValue())
return false;
writing_direction =
ToCSSIdentifierValue(direction)->GetValueID() == CSSValueLtr
? WritingDirection::kLeftToRight
: WritingDirection::kRightToLeft;
return true;
}
if (unicode_bidi_value == CSSValueNormal) {
writing_direction = WritingDirection::kNatural;
return true;
}
return false;
}
void EditingStyle::OverrideWithStyle(const CSSPropertyValueSet* style) {
if (!style || style->IsEmpty())
return;
if (!mutable_style_)
mutable_style_ = MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
mutable_style_->MergeAndOverrideOnConflict(style);
ExtractFontSizeDelta();
}
void EditingStyle::Clear() {
mutable_style_.Clear();
is_monospace_font_ = false;
font_size_delta_ = no_font_delta_;
}
EditingStyle* EditingStyle::Copy() const {
EditingStyle* copy = EditingStyle::Create();
if (mutable_style_)
copy->mutable_style_ = mutable_style_->MutableCopy();
copy->is_monospace_font_ = is_monospace_font_;
copy->font_size_delta_ = font_size_delta_;
return copy;
}
// This is the list of CSS properties that apply specially to block-level
// elements.
static const CSSPropertyID kStaticBlockProperties[] = {
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 Vector<const CSSProperty*>& BlockPropertiesVector() {
DEFINE_STATIC_LOCAL(Vector<const CSSProperty*>, properties, ());
if (properties.IsEmpty()) {
CSSProperty::FilterEnabledCSSPropertiesIntoVector(
kStaticBlockProperties, base::size(kStaticBlockProperties), properties);
}
return properties;
}
EditingStyle* EditingStyle::ExtractAndRemoveBlockProperties() {
EditingStyle* block_properties = EditingStyle::Create();
if (!mutable_style_)
return block_properties;
block_properties->mutable_style_ =
mutable_style_->CopyPropertiesInSet(BlockPropertiesVector());
RemoveBlockProperties();
return block_properties;
}
EditingStyle* EditingStyle::ExtractAndRemoveTextDirection(
SecureContextMode secure_context_mode) {
EditingStyle* text_direction = EditingStyle::Create();
text_direction->mutable_style_ =
MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
text_direction->mutable_style_->SetProperty(
CSSPropertyUnicodeBidi, CSSValueIsolate,
mutable_style_->PropertyIsImportant(CSSPropertyUnicodeBidi));
text_direction->mutable_style_->SetProperty(
CSSPropertyDirection,
mutable_style_->GetPropertyValue(CSSPropertyDirection),
mutable_style_->PropertyIsImportant(CSSPropertyDirection),
secure_context_mode);
mutable_style_->RemoveProperty(CSSPropertyUnicodeBidi);
mutable_style_->RemoveProperty(CSSPropertyDirection);
return text_direction;
}
void EditingStyle::RemoveBlockProperties() {
if (!mutable_style_)
return;
mutable_style_->RemovePropertiesInSet(BlockPropertiesVector().data(),
BlockPropertiesVector().size());
}
void EditingStyle::RemoveStyleAddedByElement(Element* element) {
if (!element || !element->parentNode())
return;
MutableCSSPropertyValueSet* parent_style = CopyEditingProperties(
CSSComputedStyleDeclaration::Create(element->parentNode()),
kAllEditingProperties);
MutableCSSPropertyValueSet* node_style = CopyEditingProperties(
CSSComputedStyleDeclaration::Create(element), kAllEditingProperties);
node_style->RemoveEquivalentProperties(parent_style);
mutable_style_->RemoveEquivalentProperties(node_style);
}
void EditingStyle::RemoveStyleConflictingWithStyleOfElement(Element* element) {
if (!element || !element->parentNode() || !mutable_style_)
return;
MutableCSSPropertyValueSet* parent_style = CopyEditingProperties(
CSSComputedStyleDeclaration::Create(element->parentNode()),
kAllEditingProperties);
MutableCSSPropertyValueSet* node_style = CopyEditingProperties(
CSSComputedStyleDeclaration::Create(element), kAllEditingProperties);
node_style->RemoveEquivalentProperties(parent_style);
unsigned property_count = node_style->PropertyCount();
for (unsigned i = 0; i < property_count; ++i)
mutable_style_->RemoveProperty(node_style->PropertyAt(i).Id());
}
void EditingStyle::CollapseTextDecorationProperties(
SecureContextMode secure_context_mode) {
if (!mutable_style_)
return;
const CSSValue* text_decorations_in_effect =
mutable_style_->GetPropertyCSSValue(
CSSPropertyWebkitTextDecorationsInEffect);
if (!text_decorations_in_effect)
return;
if (text_decorations_in_effect->IsValueList()) {
mutable_style_->SetProperty(
CSSPropertyTextDecorationLine, text_decorations_in_effect->CssText(),
mutable_style_->PropertyIsImportant(CSSPropertyTextDecorationLine),
secure_context_mode);
} else {
mutable_style_->RemoveProperty(CSSPropertyTextDecorationLine);
}
mutable_style_->RemoveProperty(CSSPropertyWebkitTextDecorationsInEffect);
}
EditingTriState EditingStyle::TriStateOfStyle(
EditingStyle* style,
SecureContextMode secure_context_mode) const {
if (!style || !style->mutable_style_)
return EditingTriState::kFalse;
return TriStateOfStyle(style->mutable_style_->EnsureCSSStyleDeclaration(),
kDoNotIgnoreTextOnlyProperties, secure_context_mode);
}
EditingTriState EditingStyle::TriStateOfStyle(
CSSStyleDeclaration* style_to_compare,
ShouldIgnoreTextOnlyProperties should_ignore_text_only_properties,
SecureContextMode secure_context_mode) const {
MutableCSSPropertyValueSet* difference = GetPropertiesNotIn(
mutable_style_.Get(), style_to_compare, secure_context_mode);
// CSS properties that create a visual difference only when applied to text.
static const CSSProperty* kTextOnlyProperties[] = {
// FIXME: CSSPropertyTextDecoration needs to be removed when CSS3 Text
// Decoration feature is no longer experimental.
&GetCSSPropertyTextDecoration(),
&GetCSSPropertyTextDecorationLine(),
&GetCSSPropertyWebkitTextDecorationsInEffect(),
&GetCSSPropertyFontStyle(),
&GetCSSPropertyFontWeight(),
&GetCSSPropertyColor(),
};
if (should_ignore_text_only_properties == kIgnoreTextOnlyProperties) {
difference->RemovePropertiesInSet(kTextOnlyProperties,
base::size(kTextOnlyProperties));
}
if (difference->IsEmpty())
return EditingTriState::kTrue;
if (difference->PropertyCount() == mutable_style_->PropertyCount())
return EditingTriState::kFalse;
return EditingTriState::kMixed;
}
EditingTriState EditingStyle::TriStateOfStyle(
const VisibleSelection& selection,
SecureContextMode secure_context_mode) const {
if (selection.IsNone())
return EditingTriState::kFalse;
if (selection.IsCaret()) {
return TriStateOfStyle(
EditingStyleUtilities::CreateStyleAtSelectionStart(selection),
secure_context_mode);
}
EditingTriState state = EditingTriState::kFalse;
bool node_is_start = true;
for (Node& node : NodeTraversal::StartsAt(*selection.Start().AnchorNode())) {
if (node.GetLayoutObject() && HasEditableStyle(node)) {
CSSComputedStyleDeclaration* node_style =
CSSComputedStyleDeclaration::Create(&node);
if (node_style) {
// 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 (is_vertical_align_ &&
GetIdentifierValue(node_style, CSSPropertyVerticalAlign) ==
CSSValueBaseline) {
const CSSIdentifierValue* vertical_align = ToCSSIdentifierValue(
mutable_style_->GetPropertyCSSValue(CSSPropertyVerticalAlign));
if (EditingStyleUtilities::HasAncestorVerticalAlignStyle(
node, vertical_align->GetValueID()))
node.MutableComputedStyle()->SetVerticalAlign(
vertical_align->ConvertTo<EVerticalAlign>());
}
// Pass EditingStyle::DoNotIgnoreTextOnlyProperties without checking if
// node.isTextNode() because the node can be an element node. See bug
// http://crbug.com/584939.
EditingTriState node_state = TriStateOfStyle(
node_style, EditingStyle::kDoNotIgnoreTextOnlyProperties,
secure_context_mode);
if (node_is_start) {
state = node_state;
node_is_start = false;
} else if (state != node_state && node.IsTextNode()) {
state = EditingTriState::kMixed;
break;
}
}
}
if (&node == selection.End().AnchorNode())
break;
}
return state;
}
bool EditingStyle::ConflictsWithInlineStyleOfElement(
HTMLElement* element,
EditingStyle* extracted_style,
Vector<CSSPropertyID>* conflicting_properties) const {
DCHECK(element);
DCHECK(!conflicting_properties || conflicting_properties->IsEmpty());
const CSSPropertyValueSet* inline_style = element->InlineStyle();
if (!mutable_style_ || !inline_style)
return false;
unsigned property_count = mutable_style_->PropertyCount();
for (unsigned i = 0; i < property_count; ++i) {
CSSPropertyID property_id = mutable_style_->PropertyAt(i).Id();
// We don't override whitespace property of a tab span because that would
// collapse the tab into a space.
if (property_id == CSSPropertyWhiteSpace && IsTabHTMLSpanElement(element))
continue;
if (property_id == CSSPropertyWebkitTextDecorationsInEffect &&
inline_style->GetPropertyCSSValue(CSSPropertyTextDecorationLine)) {
if (!conflicting_properties)
return true;
conflicting_properties->push_back(CSSPropertyTextDecoration);
// Because text-decoration expands to text-decoration-line,
// we also state it as conflicting.
conflicting_properties->push_back(CSSPropertyTextDecorationLine);
if (extracted_style) {
extracted_style->SetProperty(
CSSPropertyTextDecorationLine,
inline_style->GetPropertyValue(CSSPropertyTextDecorationLine),
inline_style->PropertyIsImportant(CSSPropertyTextDecorationLine),
element->GetDocument().GetSecureContextMode());
}
continue;
}
if (!inline_style->GetPropertyCSSValue(property_id))
continue;
if (property_id == CSSPropertyUnicodeBidi &&
inline_style->GetPropertyCSSValue(CSSPropertyDirection)) {
if (!conflicting_properties)
return true;
conflicting_properties->push_back(CSSPropertyDirection);
if (extracted_style) {
extracted_style->SetProperty(
property_id, inline_style->GetPropertyValue(property_id),
inline_style->PropertyIsImportant(property_id),
element->GetDocument().GetSecureContextMode());
}
}
if (!conflicting_properties)
return true;
conflicting_properties->push_back(property_id);
if (extracted_style) {
extracted_style->SetProperty(
property_id, inline_style->GetPropertyValue(property_id),
inline_style->PropertyIsImportant(property_id),
element->GetDocument().GetSecureContextMode());
}
}
return conflicting_properties && !conflicting_properties->IsEmpty();
}
static const HeapVector<Member<HTMLElementEquivalent>>&
HtmlElementEquivalents() {
DEFINE_STATIC_LOCAL(
Persistent<HeapVector<Member<HTMLElementEquivalent>>>,
html_element_equivalents,
(MakeGarbageCollected<HeapVector<Member<HTMLElementEquivalent>>>()));
if (!html_element_equivalents->size()) {
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyFontWeight, CSSValueBold, html_names::kBTag));
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyFontWeight, CSSValueBold, html_names::kStrongTag));
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyVerticalAlign, CSSValueSub, html_names::kSubTag));
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyVerticalAlign, CSSValueSuper, html_names::kSupTag));
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyFontStyle, CSSValueItalic, html_names::kITag));
html_element_equivalents->push_back(HTMLElementEquivalent::Create(
CSSPropertyFontStyle, CSSValueItalic, html_names::kEmTag));
html_element_equivalents->push_back(HTMLTextDecorationEquivalent::Create(
CSSValueUnderline, html_names::kUTag));
html_element_equivalents->push_back(HTMLTextDecorationEquivalent::Create(
CSSValueLineThrough, html_names::kSTag));
html_element_equivalents->push_back(HTMLTextDecorationEquivalent::Create(
CSSValueLineThrough, html_names::kStrikeTag));
}
return *html_element_equivalents;
}
bool EditingStyle::ConflictsWithImplicitStyleOfElement(
HTMLElement* element,
EditingStyle* extracted_style,
ShouldExtractMatchingStyle should_extract_matching_style) const {
if (!mutable_style_)
return false;
const HeapVector<Member<HTMLElementEquivalent>>& html_element_equivalents =
HtmlElementEquivalents();
for (wtf_size_t i = 0; i < html_element_equivalents.size(); ++i) {
const HTMLElementEquivalent* equivalent = html_element_equivalents[i].Get();
if (equivalent->Matches(element) &&
equivalent->PropertyExistsInStyle(mutable_style_.Get()) &&
(should_extract_matching_style == kExtractMatchingStyle ||
!equivalent->ValueIsPresentInStyle(element, mutable_style_.Get()))) {
if (extracted_style)
equivalent->AddToStyle(element, extracted_style);
return true;
}
}
return false;
}
static const HeapVector<Member<HTMLAttributeEquivalent>>&
HtmlAttributeEquivalents() {
DEFINE_STATIC_LOCAL(
Persistent<HeapVector<Member<HTMLAttributeEquivalent>>>,
html_attribute_equivalents,
(MakeGarbageCollected<HeapVector<Member<HTMLAttributeEquivalent>>>()));
if (!html_attribute_equivalents->size()) {
// elementIsStyledSpanOrHTMLEquivalent depends on the fact each
// HTMLAttriuteEquivalent matches exactly one attribute of exactly one
// element except dirAttr.
html_attribute_equivalents->push_back(HTMLAttributeEquivalent::Create(
CSSPropertyColor, html_names::kFontTag, html_names::kColorAttr));
html_attribute_equivalents->push_back(HTMLAttributeEquivalent::Create(
CSSPropertyFontFamily, html_names::kFontTag, html_names::kFaceAttr));
html_attribute_equivalents->push_back(HTMLFontSizeEquivalent::Create());
html_attribute_equivalents->push_back(HTMLAttributeEquivalent::Create(
CSSPropertyDirection, html_names::kDirAttr));
html_attribute_equivalents->push_back(HTMLAttributeEquivalent::Create(
CSSPropertyUnicodeBidi, html_names::kDirAttr));
}
return *html_attribute_equivalents;
}
bool EditingStyle::ConflictsWithImplicitStyleOfAttributes(
HTMLElement* element) const {
DCHECK(element);
if (!mutable_style_)
return false;
const HeapVector<Member<HTMLAttributeEquivalent>>&
html_attribute_equivalents = HtmlAttributeEquivalents();
for (const auto& equivalent : html_attribute_equivalents) {
if (equivalent->Matches(element) &&
equivalent->PropertyExistsInStyle(mutable_style_.Get()) &&
!equivalent->ValueIsPresentInStyle(element, mutable_style_.Get()))
return true;
}
return false;
}
bool EditingStyle::ExtractConflictingImplicitStyleOfAttributes(
HTMLElement* element,
ShouldPreserveWritingDirection should_preserve_writing_direction,
EditingStyle* extracted_style,
Vector<QualifiedName>& conflicting_attributes,
ShouldExtractMatchingStyle should_extract_matching_style) const {
DCHECK(element);
// HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and
// direction properties
if (extracted_style)
DCHECK_EQ(should_preserve_writing_direction, kPreserveWritingDirection);
if (!mutable_style_)
return false;
const HeapVector<Member<HTMLAttributeEquivalent>>&
html_attribute_equivalents = HtmlAttributeEquivalents();
bool removed = false;
for (const auto& attribute : html_attribute_equivalents) {
const HTMLAttributeEquivalent* equivalent = attribute.Get();
// unicode-bidi and direction are pushed down separately so don't push down
// with other styles.
if (should_preserve_writing_direction == kPreserveWritingDirection &&
equivalent->AttributeName() == html_names::kDirAttr)
continue;
if (!equivalent->Matches(element) ||
!equivalent->PropertyExistsInStyle(mutable_style_.Get()) ||
(should_extract_matching_style == kDoNotExtractMatchingStyle &&
equivalent->ValueIsPresentInStyle(element, mutable_style_.Get())))
continue;
if (extracted_style)
equivalent->AddToStyle(element, extracted_style);
conflicting_attributes.push_back(equivalent->AttributeName());
removed = true;
}
return removed;
}
bool EditingStyle::StyleIsPresentInComputedStyleOfNode(Node* node) const {
return !mutable_style_ ||
GetPropertiesNotIn(mutable_style_.Get(),
CSSComputedStyleDeclaration::Create(node),
node->GetDocument().GetSecureContextMode())
->IsEmpty();
}
bool EditingStyle::ElementIsStyledSpanOrHTMLEquivalent(
const HTMLElement* element) {
DCHECK(element);
bool element_is_span_or_element_equivalent = false;
if (IsHTMLSpanElement(*element)) {
element_is_span_or_element_equivalent = true;
} else {
const HeapVector<Member<HTMLElementEquivalent>>& html_element_equivalents =
HtmlElementEquivalents();
wtf_size_t i;
for (i = 0; i < html_element_equivalents.size(); ++i) {
if (html_element_equivalents[i]->Matches(element)) {
element_is_span_or_element_equivalent = true;
break;
}
}
}
AttributeCollection attributes = element->Attributes();
if (attributes.IsEmpty()) {
// span, b, etc... without any attributes
return element_is_span_or_element_equivalent;
}
unsigned matched_attributes = 0;
const HeapVector<Member<HTMLAttributeEquivalent>>&
html_attribute_equivalents = HtmlAttributeEquivalents();
for (const auto& equivalent : html_attribute_equivalents) {
if (equivalent->Matches(element) &&
equivalent->AttributeName() != html_names::kDirAttr)
matched_attributes++;
}
if (!element_is_span_or_element_equivalent && !matched_attributes) {
// element is not a span, a html element equivalent, or font element.
return false;
}
if (element->hasAttribute(html_names::kStyleAttr)) {
if (const CSSPropertyValueSet* style = element->InlineStyle()) {
unsigned property_count = style->PropertyCount();
for (unsigned i = 0; i < property_count; ++i) {
if (!IsEditingProperty(style->PropertyAt(i).Id()))
return false;
}
}
matched_attributes++;
}
// font with color attribute, span with style attribute, etc...
DCHECK_LE(matched_attributes, attributes.size());
return matched_attributes >= attributes.size();
}
void EditingStyle::PrepareToApplyAt(
const Position& position,
ShouldPreserveWritingDirection should_preserve_writing_direction) {
if (!mutable_style_)
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* editing_style_at_position =
EditingStyle::Create(position, kEditingPropertiesInEffect);
CSSPropertyValueSet* style_at_position =
editing_style_at_position->mutable_style_.Get();
const CSSValue* unicode_bidi = nullptr;
const CSSValue* direction = nullptr;
if (should_preserve_writing_direction == kPreserveWritingDirection) {
unicode_bidi = mutable_style_->GetPropertyCSSValue(CSSPropertyUnicodeBidi);
direction = mutable_style_->GetPropertyCSSValue(CSSPropertyDirection);
}
mutable_style_->RemoveEquivalentProperties(style_at_position);
if (TextAlignResolvingStartAndEnd(mutable_style_.Get()) ==
TextAlignResolvingStartAndEnd(style_at_position))
mutable_style_->RemoveProperty(CSSPropertyTextAlign);
if (GetFontColor(mutable_style_.Get()) == GetFontColor(style_at_position))
mutable_style_->RemoveProperty(CSSPropertyColor);
if (EditingStyleUtilities::HasTransparentBackgroundColor(
mutable_style_.Get()) ||
CssValueToColor(
mutable_style_->GetPropertyCSSValue(CSSPropertyBackgroundColor)) ==
BackgroundColorInEffect(position.ComputeContainerNode()))
mutable_style_->RemoveProperty(CSSPropertyBackgroundColor);
if (unicode_bidi && unicode_bidi->IsIdentifierValue()) {
mutable_style_->SetProperty(
CSSPropertyUnicodeBidi,
ToCSSIdentifierValue(unicode_bidi)->GetValueID());
if (direction && direction->IsIdentifierValue()) {
mutable_style_->SetProperty(
CSSPropertyDirection, ToCSSIdentifierValue(direction)->GetValueID());
}
}
}
void EditingStyle::MergeTypingStyle(Document* document) {
DCHECK(document);
EditingStyle* typing_style = document->GetFrame()->GetEditor().TypingStyle();
if (!typing_style || typing_style == this)
return;
MergeStyle(typing_style->Style(), kOverrideValues);
}
void EditingStyle::MergeInlineStyleOfElement(
HTMLElement* element,
CSSPropertyOverrideMode mode,
PropertiesToInclude properties_to_include) {
DCHECK(element);
if (!element->InlineStyle())
return;
switch (properties_to_include) {
case kAllProperties:
MergeStyle(element->InlineStyle(), mode);
return;
case kOnlyEditingInheritableProperties:
MergeStyle(CopyEditingProperties(element->InlineStyle(),
kOnlyInheritableEditingProperties),
mode);
return;
case kEditingPropertiesInEffect:
MergeStyle(
CopyEditingProperties(element->InlineStyle(), kAllEditingProperties),
mode);
return;
}
}
static inline bool ElementMatchesAndPropertyIsNotInInlineStyleDecl(
const HTMLElementEquivalent* equivalent,
const Element* element,
EditingStyle::CSSPropertyOverrideMode mode,
CSSPropertyValueSet* style) {
return equivalent->Matches(element) &&
(!element->InlineStyle() ||
!equivalent->PropertyExistsInStyle(element->InlineStyle())) &&
(mode == EditingStyle::kOverrideValues ||
!equivalent->PropertyExistsInStyle(style));
}
static MutableCSSPropertyValueSet* ExtractEditingProperties(
const CSSPropertyValueSet* style,
EditingStyle::PropertiesToInclude properties_to_include) {
if (!style)
return nullptr;
switch (properties_to_include) {
case EditingStyle::kAllProperties:
case EditingStyle::kEditingPropertiesInEffect:
return CopyEditingProperties(style, kAllEditingProperties);
case EditingStyle::kOnlyEditingInheritableProperties:
return CopyEditingProperties(style, kOnlyInheritableEditingProperties);
}
NOTREACHED();
return nullptr;
}
void EditingStyle::MergeInlineAndImplicitStyleOfElement(
Element* element,
CSSPropertyOverrideMode mode,
PropertiesToInclude properties_to_include) {
EditingStyle* style_from_rules = EditingStyle::Create();
style_from_rules->MergeStyleFromRulesForSerialization(element);
if (element->InlineStyle())
style_from_rules->mutable_style_->MergeAndOverrideOnConflict(
element->InlineStyle());
style_from_rules->mutable_style_ = ExtractEditingProperties(
style_from_rules->mutable_style_.Get(), properties_to_include);
MergeStyle(style_from_rules->mutable_style_.Get(), mode);
const HeapVector<Member<HTMLElementEquivalent>>& element_equivalents =
HtmlElementEquivalents();
for (const auto& equivalent : element_equivalents) {
if (ElementMatchesAndPropertyIsNotInInlineStyleDecl(
equivalent.Get(), element, mode, mutable_style_.Get()))
equivalent->AddToStyle(element, this);
}
const HeapVector<Member<HTMLAttributeEquivalent>>& attribute_equivalents =
HtmlAttributeEquivalents();
for (const auto& attribute : attribute_equivalents) {
if (attribute->AttributeName() == html_names::kDirAttr)
continue; // We don't want to include directionality
if (ElementMatchesAndPropertyIsNotInInlineStyleDecl(
attribute.Get(), element, mode, mutable_style_.Get()))
attribute->AddToStyle(element, this);
}
}
static const CSSValueList& MergeTextDecorationValues(
const CSSValueList& merged_value,
const CSSValueList& value_to_merge) {
DEFINE_STATIC_LOCAL(Persistent<CSSIdentifierValue>, underline,
(CSSIdentifierValue::Create(CSSValueUnderline)));
DEFINE_STATIC_LOCAL(Persistent<CSSIdentifierValue>, line_through,
(CSSIdentifierValue::Create(CSSValueLineThrough)));
CSSValueList& result = *merged_value.Copy();
if (value_to_merge.HasValue(*underline) && !merged_value.HasValue(*underline))
result.Append(*underline);
if (value_to_merge.HasValue(*line_through) &&
!merged_value.HasValue(*line_through))
result.Append(*line_through);
return result;
}
void EditingStyle::MergeStyle(const CSSPropertyValueSet* style,
CSSPropertyOverrideMode mode) {
if (!style)
return;
if (!mutable_style_) {
mutable_style_ = style->MutableCopy();
return;
}
unsigned property_count = style->PropertyCount();
for (unsigned i = 0; i < property_count; ++i) {
CSSPropertyValueSet::PropertyReference property = style->PropertyAt(i);
const CSSValue* value = mutable_style_->GetPropertyCSSValue(property.Id());
// text decorations never override values
if ((property.Id() == CSSPropertyTextDecorationLine ||
property.Id() == CSSPropertyWebkitTextDecorationsInEffect) &&
property.Value().IsValueList() && value) {
if (value->IsValueList()) {
const CSSValueList& result = MergeTextDecorationValues(
*ToCSSValueList(value), ToCSSValueList(property.Value()));
mutable_style_->SetProperty(property.Id(), result,
property.IsImportant());
continue;
}
// text-decoration: none is equivalent to not having the property
value = nullptr;
}
if (mode == kOverrideValues || (mode == kDoNotOverrideValues && !value)) {
mutable_style_->SetProperty(
CSSPropertyValue(property.PropertyMetadata(), property.Value()));
}
}
}
static MutableCSSPropertyValueSet* StyleFromMatchedRulesForElement(
Element* element,
unsigned rules_to_include) {
MutableCSSPropertyValueSet* style =
MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
StyleRuleList* matched_rules =
element->GetDocument().EnsureStyleResolver().StyleRulesForElement(
element, rules_to_include);
if (matched_rules) {
for (unsigned i = 0; i < matched_rules->size(); ++i)
style->MergeAndOverrideOnConflict(&matched_rules->at(i)->Properties());
}
return style;
}
void EditingStyle::MergeStyleFromRules(Element* element) {
MutableCSSPropertyValueSet* style_from_matched_rules =
StyleFromMatchedRulesForElement(
element,
StyleResolver::kAuthorCSSRules | StyleResolver::kCrossOriginCSSRules);
// Styles from the inline style declaration, held in the variable "style",
// take precedence over those from matched rules.
if (mutable_style_)
style_from_matched_rules->MergeAndOverrideOnConflict(mutable_style_.Get());
Clear();
mutable_style_ = style_from_matched_rules;
}
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* computed_style_for_element =
CSSComputedStyleDeclaration::Create(element);
MutableCSSPropertyValueSet* from_computed_style =
MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
{
unsigned property_count = mutable_style_->PropertyCount();
for (unsigned i = 0; i < property_count; ++i) {
CSSPropertyValueSet::PropertyReference property =
mutable_style_->PropertyAt(i);
const CSSProperty& css_property = property.Property();
const CSSValue& value = property.Value();
if (!value.IsPrimitiveValue())
continue;
if (ToCSSPrimitiveValue(value).IsPercentage()) {
if (const CSSValue* computed_property_value =
computed_style_for_element->GetPropertyCSSValue(css_property)) {
from_computed_style->AddRespectingCascade(
CSSPropertyValue(css_property, *computed_property_value));
}
}
}
}
mutable_style_->MergeAndOverrideOnConflict(from_computed_style);
}
static void RemovePropertiesInStyle(
MutableCSSPropertyValueSet* style_to_remove_properties_from,
CSSPropertyValueSet* style) {
unsigned property_count = style->PropertyCount();
Vector<const CSSProperty*> properties_to_remove(property_count);
for (unsigned i = 0; i < property_count; ++i)
properties_to_remove[i] = &style->PropertyAt(i).Property();
style_to_remove_properties_from->RemovePropertiesInSet(
properties_to_remove.data(), properties_to_remove.size());
}
void EditingStyle::RemoveStyleFromRulesAndContext(Element* element,
ContainerNode* context) {
DCHECK(element);
if (!mutable_style_)
return;
// StyleResolver requires clean style.
DCHECK_GE(element->GetDocument().Lifecycle().GetState(),
DocumentLifecycle::kStyleClean);
DCHECK(element->GetDocument().IsActive());
SecureContextMode secure_context_mode =
element->GetDocument().GetSecureContextMode();
// 1. Remove style from matched rules because style remain without repeating
// it in inline style declaration
MutableCSSPropertyValueSet* style_from_matched_rules =
StyleFromMatchedRulesForElement(element,
StyleResolver::kAllButEmptyCSSRules);
if (style_from_matched_rules && !style_from_matched_rules->IsEmpty()) {
mutable_style_ = GetPropertiesNotIn(
mutable_style_.Get(),
style_from_matched_rules->EnsureCSSStyleDeclaration(),
secure_context_mode);
}
// 2. Remove style present in context and not overriden by matched rules.
EditingStyle* computed_style =
EditingStyle::Create(context, kEditingPropertiesInEffect);
if (computed_style->mutable_style_) {
if (!computed_style->mutable_style_->GetPropertyCSSValue(
CSSPropertyBackgroundColor))
computed_style->mutable_style_->SetProperty(CSSPropertyBackgroundColor,
CSSValueTransparent);
RemovePropertiesInStyle(computed_style->mutable_style_.Get(),
style_from_matched_rules);
mutable_style_ = GetPropertiesNotIn(
mutable_style_.Get(),
computed_style->mutable_style_->EnsureCSSStyleDeclaration(),
secure_context_mode);
}
// 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 (!style_from_matched_rules->GetPropertyCSSValue(CSSPropertyDisplay) &&
GetIdentifierValue(mutable_style_.Get(), CSSPropertyDisplay) ==
CSSValueInline)
mutable_style_->RemoveProperty(CSSPropertyDisplay);
if (!style_from_matched_rules->GetPropertyCSSValue(CSSPropertyFloat) &&
GetIdentifierValue(mutable_style_.Get(), CSSPropertyFloat) ==
CSSValueNone)
mutable_style_->RemoveProperty(CSSPropertyFloat);
}
}
void EditingStyle::RemovePropertiesInElementDefaultStyle(Element* element) {
if (!mutable_style_ || mutable_style_->IsEmpty())
return;
CSSPropertyValueSet* default_style = StyleFromMatchedRulesForElement(
element, StyleResolver::kUAAndUserCSSRules);
RemovePropertiesInStyle(mutable_style_.Get(), default_style);
}
void EditingStyle::ForceInline() {
if (!mutable_style_)
mutable_style_ = MutableCSSPropertyValueSet::Create(kHTMLQuirksMode);
const bool kPropertyIsImportant = true;
mutable_style_->SetProperty(CSSPropertyDisplay, CSSValueInline,
kPropertyIsImportant);
}
int EditingStyle::LegacyFontSize(Document* document) const {
const CSSValue* css_value =
mutable_style_->GetPropertyCSSValue(CSSPropertyFontSize);
if (!css_value ||
!(css_value->IsPrimitiveValue() || css_value->IsIdentifierValue()))
return 0;
return LegacyFontSizeFromCSSValue(document, css_value, is_monospace_font_,
kAlwaysUseLegacyFontSize);
}
void EditingStyle::Trace(blink::Visitor* visitor) {
visitor->Trace(mutable_style_);
}
static void ReconcileTextDecorationProperties(
MutableCSSPropertyValueSet* style,
SecureContextMode secure_context_mode) {
const CSSValue* text_decorations_in_effect =
style->GetPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
const CSSValue* text_decoration =
style->GetPropertyCSSValue(CSSPropertyTextDecorationLine);
// "web_tests/editing/execCommand/insert-list-and-strikethrough.html" makes
// both |textDecorationsInEffect| and |textDecoration| non-null.
if (text_decorations_in_effect) {
style->SetProperty(CSSPropertyTextDecorationLine,
text_decorations_in_effect->CssText(),
/* important */ false, secure_context_mode);
style->RemoveProperty(CSSPropertyWebkitTextDecorationsInEffect);
text_decoration = text_decorations_in_effect;
}
// If text-decoration is set to "none", remove the property because we don't
// want to add redundant "text-decoration: none".
if (text_decoration && !text_decoration->IsValueList())
style->RemoveProperty(CSSPropertyTextDecorationLine);
}
StyleChange::StyleChange(EditingStyle* style, const Position& position)
: apply_bold_(false),
apply_italic_(false),
apply_underline_(false),
apply_line_through_(false),
apply_subscript_(false),
apply_superscript_(false) {
Document* document = position.GetDocument();
if (!style || !style->Style() || !document || !document->GetFrame() ||
!AssociatedElementOf(position))
return;
CSSComputedStyleDeclaration* computed_style = EnsureComputedStyle(position);
// FIXME: take care of background-color in effect
MutableCSSPropertyValueSet* mutable_style = GetPropertiesNotIn(
style->Style(), computed_style, document->GetSecureContextMode());
DCHECK(mutable_style);
ReconcileTextDecorationProperties(mutable_style,
document->GetSecureContextMode());
if (!document->GetFrame()->GetEditor().ShouldStyleWithCSS())
ExtractTextStyles(document, mutable_style,
computed_style->IsMonospaceFont());
// Changing the whitespace style in a tab span would collapse the tab into a
// space.
if (IsTabHTMLSpanElementTextNode(position.AnchorNode()) ||
IsTabHTMLSpanElement((position.AnchorNode())))
mutable_style->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 (mutable_style->GetPropertyCSSValue(CSSPropertyUnicodeBidi) &&
!style->Style()->GetPropertyCSSValue(CSSPropertyDirection)) {
mutable_style->SetProperty(
CSSPropertyDirection,
style->Style()->GetPropertyValue(CSSPropertyDirection),
/* important */ false, document->GetSecureContextMode());
}
// Save the result for later
css_style_ = mutable_style->AsText().StripWhiteSpace();
}
static void SetTextDecorationProperty(MutableCSSPropertyValueSet* style,
const CSSValueList* new_text_decoration,
CSSPropertyID property_id,
SecureContextMode secure_context_mode) {
if (new_text_decoration->length()) {
style->SetProperty(property_id, new_text_decoration->CssText(),
style->PropertyIsImportant(property_id),
secure_context_mode);
} else {
// text-decoration: none is redundant since it does not remove any text
// decorations.
style->RemoveProperty(property_id);
}
}
static bool GetPrimitiveValueNumber(CSSPropertyValueSet* style,
CSSPropertyID property_id,
float& number) {
if (!style)
return false;
const CSSValue* value = style->GetPropertyCSSValue(property_id);
if (!value || !value->IsPrimitiveValue())
return false;
number = ToCSSPrimitiveValue(value)->GetFloatValue();
return true;
}
void StyleChange::ExtractTextStyles(Document* document,
MutableCSSPropertyValueSet* style,
bool is_monospace_font) {
DCHECK(style);
float weight = 0;
bool is_number =
GetPrimitiveValueNumber(style, CSSPropertyFontWeight, weight);
if (GetIdentifierValue(style, CSSPropertyFontWeight) == CSSValueBold ||
(is_number && weight >= BoldThreshold())) {
style->RemoveProperty(CSSPropertyFontWeight);
apply_bold_ = true;
}
int font_style = GetIdentifierValue(style, CSSPropertyFontStyle);
if (font_style == CSSValueItalic || font_style == CSSValueOblique) {
style->RemoveProperty(CSSPropertyFontStyle);
apply_italic_ = 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* text_decoration =
style->GetPropertyCSSValue(CSSPropertyTextDecorationLine);
if (text_decoration && text_decoration->IsValueList()) {
DEFINE_STATIC_LOCAL(Persistent<CSSIdentifierValue>, underline,
(CSSIdentifierValue::Create(CSSValueUnderline)));
DEFINE_STATIC_LOCAL(Persistent<CSSIdentifierValue>, line_through,
(CSSIdentifierValue::Create(CSSValueLineThrough)));
CSSValueList* new_text_decoration = ToCSSValueList(text_decoration)->Copy();
if (new_text_decoration->RemoveAll(*underline))
apply_underline_ = true;
if (new_text_decoration->RemoveAll(*line_through))
apply_line_through_ = true;
// If trimTextDecorations, delete underline and line-through
SetTextDecorationProperty(style, new_text_decoration,
CSSPropertyTextDecorationLine,
document->GetSecureContextMode());
}
int vertical_align = GetIdentifierValue(style, CSSPropertyVerticalAlign);
switch (vertical_align) {
case CSSValueSub:
style->RemoveProperty(CSSPropertyVerticalAlign);
apply_subscript_ = true;
break;
case CSSValueSuper:
style->RemoveProperty(CSSPropertyVerticalAlign);
apply_superscript_ = true;
break;
}
if (style->GetPropertyCSSValue(CSSPropertyColor)) {
apply_font_color_ = GetFontColor(style).Serialized();
style->RemoveProperty(CSSPropertyColor);
}
apply_font_face_ = style->GetPropertyValue(CSSPropertyFontFamily);
// Remove double quotes for Outlook 2007 compatibility. See
// https://bugs.webkit.org/show_bug.cgi?id=79448
apply_font_face_.Replace('"', "");
style->RemoveProperty(CSSPropertyFontFamily);
if (const CSSValue* font_size =
style->GetPropertyCSSValue(CSSPropertyFontSize)) {
if (!font_size->IsPrimitiveValue() && !font_size->IsIdentifierValue()) {
// Can't make sense of the number. Put no font size.
style->RemoveProperty(CSSPropertyFontSize);
} else if (int legacy_font_size = LegacyFontSizeFromCSSValue(
document, font_size, is_monospace_font,
kUseLegacyFontSizeOnlyIfPixelValuesMatch)) {
apply_font_size_ = String::Number(legacy_font_size);
style->RemoveProperty(CSSPropertyFontSize);
}
}
}
static void DiffTextDecorations(MutableCSSPropertyValueSet* style,
CSSPropertyID property_id,
const CSSValue* ref_text_decoration,
SecureContextMode secure_context_mode) {
const CSSValue* text_decoration = style->GetPropertyCSSValue(property_id);
if (!text_decoration || !text_decoration->IsValueList() ||
!ref_text_decoration || !ref_text_decoration->IsValueList())
return;
CSSValueList* new_text_decoration = ToCSSValueList(text_decoration)->Copy();
const CSSValueList* values_in_ref_text_decoration =
ToCSSValueList(ref_text_decoration);
for (wtf_size_t i = 0; i < values_in_ref_text_decoration->length(); i++)
new_text_decoration->RemoveAll(values_in_ref_text_decoration->Item(i));
SetTextDecorationProperty(style, new_text_decoration, property_id,
secure_context_mode);
}
static bool FontWeightIsBold(const CSSValue* font_weight) {
if (font_weight->IsIdentifierValue()) {
// 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(font_weight)->GetValueID()) {
case CSSValueNormal:
return false;
case CSSValueBold:
return true;
default:
break;
}
}
CHECK(font_weight->IsPrimitiveValue());
CHECK(ToCSSPrimitiveValue(font_weight)->IsNumber());
return ToCSSPrimitiveValue(font_weight)->GetFloatValue() >= BoldThreshold();
}
static bool FontWeightNeedsResolving(const CSSValue* font_weight) {
if (font_weight->IsPrimitiveValue())
return false;
if (!font_weight->IsIdentifierValue())
return true;
const CSSValueID value = ToCSSIdentifierValue(font_weight)->GetValueID();
return value == CSSValueLighter || value == CSSValueBolder;
}
MutableCSSPropertyValueSet* GetPropertiesNotIn(
CSSPropertyValueSet* style_with_redundant_properties,
CSSStyleDeclaration* base_style,
SecureContextMode secure_context_mode) {
DCHECK(style_with_redundant_properties);
DCHECK(base_style);
MutableCSSPropertyValueSet* result =
style_with_redundant_properties->MutableCopy();
result->RemoveEquivalentProperties(base_style);
const CSSValue* base_text_decorations_in_effect =
base_style->GetPropertyCSSValueInternal(
CSSPropertyWebkitTextDecorationsInEffect);
DiffTextDecorations(result, CSSPropertyTextDecorationLine,
base_text_decorations_in_effect, secure_context_mode);
DiffTextDecorations(result, CSSPropertyWebkitTextDecorationsInEffect,
base_text_decorations_in_effect, secure_context_mode);
if (const CSSValue* base_font_weight =
base_style->GetPropertyCSSValueInternal(CSSPropertyFontWeight)) {
if (const CSSValue* font_weight =
result->GetPropertyCSSValue(CSSPropertyFontWeight)) {
if (!FontWeightNeedsResolving(font_weight) &&
!FontWeightNeedsResolving(base_font_weight) &&
(FontWeightIsBold(font_weight) == FontWeightIsBold(base_font_weight)))
result->RemoveProperty(CSSPropertyFontWeight);
}
}
if (base_style->GetPropertyCSSValueInternal(CSSPropertyColor) &&
GetFontColor(result) == GetFontColor(base_style))
result->RemoveProperty(CSSPropertyColor);
if (base_style->GetPropertyCSSValueInternal(CSSPropertyTextAlign) &&
TextAlignResolvingStartAndEnd(result) ==
TextAlignResolvingStartAndEnd(base_style))
result->RemoveProperty(CSSPropertyTextAlign);
if (base_style->GetPropertyCSSValueInternal(CSSPropertyBackgroundColor) &&
GetBackgroundColor(result) == GetBackgroundColor(base_style))
result->RemoveProperty(CSSPropertyBackgroundColor);
return result;
}
CSSValueID GetIdentifierValue(CSSPropertyValueSet* style,
CSSPropertyID property_id) {
if (!style)
return CSSValueInvalid;
const CSSValue* value = style->GetPropertyCSSValue(property_id);
if (!value || !value->IsIdentifierValue())
return CSSValueInvalid;
return ToCSSIdentifierValue(value)->GetValueID();
}
CSSValueID GetIdentifierValue(CSSStyleDeclaration* style,
CSSPropertyID property_id) {
if (!style)
return CSSValueInvalid;
const CSSValue* value = style->GetPropertyCSSValueInternal(property_id);
if (!value || !value->IsIdentifierValue())
return CSSValueInvalid;
return ToCSSIdentifierValue(value)->GetValueID();
}
int LegacyFontSizeFromCSSValue(Document* document,
const CSSValue* value,
bool is_monospace_font,
LegacyFontSizeMode mode) {
if (value->IsPrimitiveValue()) {
const CSSPrimitiveValue& primitive_value = ToCSSPrimitiveValue(*value);
CSSPrimitiveValue::LengthUnitType length_type;
if (CSSPrimitiveValue::UnitTypeToLengthUnitType(
primitive_value.TypeWithCalcResolved(), length_type) &&
length_type == CSSPrimitiveValue::kUnitTypePixels) {
double conversion =
CSSPrimitiveValue::ConversionToCanonicalUnitsScaleFactor(
primitive_value.TypeWithCalcResolved());
int pixel_font_size =
clampTo<int>(primitive_value.GetDoubleValue() * conversion);
int legacy_font_size = FontSizeFunctions::LegacyFontSize(
document, pixel_font_size, is_monospace_font);
// Use legacy font size only if pixel value matches exactly to that of
// legacy font size.
if (mode == kAlwaysUseLegacyFontSize ||
FontSizeFunctions::FontSizeForKeyword(
document, legacy_font_size, is_monospace_font) == pixel_font_size)
return legacy_font_size;
return 0;
}
}
if (value->IsIdentifierValue()) {
const CSSIdentifierValue& identifier_value = ToCSSIdentifierValue(*value);
if (CSSValueXSmall <= identifier_value.GetValueID() &&
identifier_value.GetValueID() <= CSSValueWebkitXxxLarge)
return identifier_value.GetValueID() - CSSValueXSmall + 1;
}
return 0;
}
EditingTriState EditingStyle::SelectionHasStyle(const LocalFrame& frame,
CSSPropertyID property_id,
const String& value) {
const SecureContextMode secure_context_mode =
frame.GetDocument()->GetSecureContextMode();
return Create(property_id, value, secure_context_mode)
->TriStateOfStyle(
frame.Selection().ComputeVisibleSelectionInDOMTreeDeprecated(),
secure_context_mode);
}
} // namespace blink