| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/cssom/css_computed_style_data.h" |
| |
| #include <limits> |
| |
| #include "base/lazy_instance.h" |
| #include "base/string_util.h" |
| #include "cobalt/cssom/css_computed_style_declaration.h" |
| #include "cobalt/cssom/keyword_value.h" |
| #include "cobalt/cssom/length_value.h" |
| |
| namespace cobalt { |
| namespace cssom { |
| |
| CSSComputedStyleData::PropertySetMatcher::PropertySetMatcher( |
| const PropertyKeyVector& properties) |
| : properties_(properties) { |
| for (size_t i = 0; i < properties_.size(); ++i) { |
| PropertyKey property_key = properties_[i]; |
| DCHECK_GT(property_key, kNoneProperty); |
| DCHECK_LE(property_key, kMaxLonghandPropertyKey); |
| properties_bitset_.set(property_key); |
| } |
| } |
| |
| bool CSSComputedStyleData::PropertySetMatcher::DoDeclaredPropertiesMatch( |
| const scoped_refptr<const CSSComputedStyleData>& lhs, |
| const scoped_refptr<const CSSComputedStyleData>& rhs) const { |
| LonghandPropertiesBitset lhs_properties_bitset = |
| lhs->declared_properties_ & properties_bitset_; |
| LonghandPropertiesBitset rhs_properties_bitset = |
| rhs->declared_properties_ & properties_bitset_; |
| if (lhs_properties_bitset != rhs_properties_bitset) { |
| return false; |
| } else if (lhs_properties_bitset.none()) { |
| return true; |
| } |
| for (size_t i = 0; i < properties_.size(); ++i) { |
| PropertyKey property_key = properties_[i]; |
| if (lhs_properties_bitset[property_key] && |
| !lhs->declared_property_values_.find(property_key) |
| ->second->Equals( |
| *rhs->declared_property_values_.find(property_key)->second)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| CSSComputedStyleData::CSSComputedStyleData() |
| : has_declared_inherited_properties_(false) {} |
| |
| CSSComputedStyleData::~CSSComputedStyleData() {} |
| |
| const scoped_refptr<PropertyValue>& |
| CSSComputedStyleData::GetPropertyValueReference(PropertyKey key) const { |
| DCHECK_GT(key, kNoneProperty); |
| DCHECK_LE(key, kMaxLonghandPropertyKey); |
| |
| // If the property's value is explicitly declared, then simply return it. |
| if (declared_properties_[key]) { |
| return declared_property_values_.find(key)->second; |
| } |
| |
| // Otherwise, if the property is inherited and the parent has inherited |
| // properties, then retrieve the parent's value for the property. |
| if (parent_computed_style_declaration_ && |
| GetPropertyInheritance(key) == kInheritedYes && |
| parent_computed_style_declaration_->HasInheritedProperties()) { |
| return parent_computed_style_declaration_ |
| ->GetInheritedPropertyValueReference(key); |
| } |
| |
| // For the root element, which has no parent element, the inherited value is |
| // the initial value of the property. |
| // https://www.w3.org/TR/css-cascade-3/#inheriting |
| return GetComputedInitialValue(key); |
| } |
| |
| scoped_refptr<PropertyValue>& |
| CSSComputedStyleData::GetDeclaredPropertyValueReference(PropertyKey key) { |
| DCHECK_GT(key, kNoneProperty); |
| DCHECK_LE(key, kMaxLonghandPropertyKey); |
| DCHECK(declared_properties_[key]); |
| return declared_property_values_.find(key)->second; |
| } |
| |
| namespace { |
| struct NonTrivialStaticFields { |
| NonTrivialStaticFields() |
| : block_keyword_value(KeywordValue::GetBlock()), |
| zero_length_value(new LengthValue(0.0f, kPixelsUnit)) {} |
| |
| const scoped_refptr<PropertyValue> block_keyword_value; |
| const scoped_refptr<PropertyValue> zero_length_value; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(NonTrivialStaticFields); |
| }; |
| |
| base::LazyInstance<NonTrivialStaticFields> non_trivial_static_fields = |
| LAZY_INSTANCE_INITIALIZER; |
| } // namespace |
| |
| const scoped_refptr<PropertyValue>& |
| CSSComputedStyleData::GetComputedInitialValue(PropertyKey key) const { |
| switch (key) { |
| case kBorderTopColorProperty: |
| case kBorderRightColorProperty: |
| case kBorderBottomColorProperty: |
| case kBorderLeftColorProperty: |
| case kOutlineColorProperty: |
| case kTextDecorationColorProperty: |
| // Note that border color and text decoration color are not inherited. |
| // The initial value of border color is 'currentColor' which means the |
| // border color is the same as the value of the 'color' property. |
| // https://www.w3.org/TR/css3-color/#currentcolor |
| return color(); |
| |
| case kBorderTopWidthProperty: |
| case kBorderRightWidthProperty: |
| case kBorderBottomWidthProperty: |
| case kBorderLeftWidthProperty: |
| case kOutlineWidthProperty: |
| // If the border style is 'none' or 'hidden', border width would be 0. |
| // https://www.w3.org/TR/css3-background/#border-width |
| if (IsBorderStyleNoneOrHiddenForAnEdge(key)) { |
| return non_trivial_static_fields.Get().zero_length_value; |
| } |
| break; |
| |
| case kDisplayProperty: |
| // The initial value of "display" (inline) become "block" if "position" is |
| // "absolute" or "fixed". |
| // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo |
| if (position() == KeywordValue::GetAbsolute() || |
| position() == KeywordValue::GetFixed()) { |
| return non_trivial_static_fields.Get().block_keyword_value; |
| } |
| break; |
| |
| case kAllProperty: |
| case kAnimationDelayProperty: |
| case kAnimationDirectionProperty: |
| case kAnimationDurationProperty: |
| case kAnimationFillModeProperty: |
| case kAnimationIterationCountProperty: |
| case kAnimationNameProperty: |
| case kAnimationProperty: |
| case kAnimationTimingFunctionProperty: |
| case kBackgroundColorProperty: |
| case kBackgroundImageProperty: |
| case kBackgroundPositionProperty: |
| case kBackgroundProperty: |
| case kBackgroundRepeatProperty: |
| case kBackgroundSizeProperty: |
| case kBorderBottomLeftRadiusProperty: |
| case kBorderBottomProperty: |
| case kBorderBottomRightRadiusProperty: |
| case kBorderBottomStyleProperty: |
| case kBorderColorProperty: |
| case kBorderLeftProperty: |
| case kBorderLeftStyleProperty: |
| case kBorderProperty: |
| case kBorderRadiusProperty: |
| case kBorderRightProperty: |
| case kBorderRightStyleProperty: |
| case kBorderStyleProperty: |
| case kBorderTopLeftRadiusProperty: |
| case kBorderTopProperty: |
| case kBorderTopRightRadiusProperty: |
| case kBorderTopStyleProperty: |
| case kBorderWidthProperty: |
| case kBottomProperty: |
| case kBoxShadowProperty: |
| case kColorProperty: |
| case kContentProperty: |
| case kFontFamilyProperty: |
| case kFilterProperty: |
| case kFontProperty: |
| case kFontStyleProperty: |
| case kFontWeightProperty: |
| case kFontSizeProperty: |
| case kHeightProperty: |
| case kLeftProperty: |
| case kLineHeightProperty: |
| case kMarginBottomProperty: |
| case kMarginLeftProperty: |
| case kMarginProperty: |
| case kMarginRightProperty: |
| case kMarginTopProperty: |
| case kMaxHeightProperty: |
| case kMaxWidthProperty: |
| case kMinHeightProperty: |
| case kMinWidthProperty: |
| case kNoneProperty: |
| case kOpacityProperty: |
| case kOutlineProperty: |
| case kOutlineStyleProperty: |
| case kOverflowProperty: |
| case kOverflowWrapProperty: |
| case kPaddingBottomProperty: |
| case kPaddingLeftProperty: |
| case kPaddingProperty: |
| case kPaddingRightProperty: |
| case kPaddingTopProperty: |
| case kPointerEventsProperty: |
| case kPositionProperty: |
| case kRightProperty: |
| case kSrcProperty: |
| case kTextAlignProperty: |
| case kTextDecorationLineProperty: |
| case kTextDecorationProperty: |
| case kTextIndentProperty: |
| case kTextOverflowProperty: |
| case kTextShadowProperty: |
| case kTextTransformProperty: |
| case kTopProperty: |
| case kTransformOriginProperty: |
| case kTransformProperty: |
| case kTransitionDelayProperty: |
| case kTransitionDurationProperty: |
| case kTransitionProperty: |
| case kTransitionPropertyProperty: |
| case kTransitionTimingFunctionProperty: |
| case kUnicodeRangeProperty: |
| case kVerticalAlignProperty: |
| case kVisibilityProperty: |
| case kWhiteSpaceProperty: |
| case kWidthProperty: |
| case kWordWrapProperty: |
| case kZIndexProperty: |
| break; |
| } |
| |
| return GetPropertyInitialValue(key); |
| } |
| |
| bool CSSComputedStyleData::IsBorderStyleNoneOrHiddenForAnEdge( |
| PropertyKey key) const { |
| scoped_refptr<PropertyValue> border_style; |
| switch (key) { |
| case kBorderTopWidthProperty: |
| border_style = border_top_style(); |
| break; |
| case kBorderRightWidthProperty: |
| border_style = border_right_style(); |
| break; |
| case kBorderBottomWidthProperty: |
| border_style = border_bottom_style(); |
| break; |
| case kBorderLeftWidthProperty: |
| border_style = border_left_style(); |
| break; |
| default: |
| DCHECK_EQ(key, kOutlineWidthProperty); |
| border_style = outline_style(); |
| break; |
| } |
| |
| if (border_style == KeywordValue::GetNone() || |
| border_style == KeywordValue::GetHidden()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void CSSComputedStyleData::SetPropertyValue( |
| const PropertyKey key, const scoped_refptr<PropertyValue>& value) { |
| DCHECK_GT(key, kNoneProperty); |
| DCHECK_LE(key, kMaxLonghandPropertyKey); |
| |
| if (value) { |
| declared_properties_.set(key, true); |
| declared_property_values_[key] = value; |
| // Only set |has_declared_inherited_properties_| if the property is |
| // inherited and the the value isn't explicitly set to "inherit". If it is |
| // set to "inherit", then the value is simply a copy of the parent's value, |
| // which doesn't necessitate the node being included in the inheritance |
| // tree, as it doesn't provide new information. |
| // NOTE: Declaring a value of "inherit" on an inherited property is used for |
| // transitions, which need to know the original value of the property (which |
| // would otherwise be lost when the parent changed). |
| has_declared_inherited_properties_ = |
| has_declared_inherited_properties_ || |
| (GetPropertyInheritance(key) == kInheritedYes && |
| value != KeywordValue::GetInherit()); |
| } else if (declared_properties_[key]) { |
| declared_properties_.set(key, false); |
| declared_property_values_.erase(key); |
| } |
| } |
| |
| void CSSComputedStyleData::AssignFrom(const CSSComputedStyleData& rhs) { |
| declared_properties_ = rhs.declared_properties_; |
| declared_property_values_ = rhs.declared_property_values_; |
| has_declared_inherited_properties_ = rhs.has_declared_inherited_properties_; |
| declared_properties_inherited_from_parent_ = |
| rhs.declared_properties_inherited_from_parent_; |
| parent_computed_style_declaration_ = rhs.parent_computed_style_declaration_; |
| } |
| |
| std::string CSSComputedStyleData::SerializeCSSDeclarationBlock() const { |
| // All longhand properties that are supported CSS properties, in |
| // lexicographical order, with the value being the resolved value. |
| // https://www.w3.org/TR/2013/WD-cssom-20131205/#dom-window-getcomputedstyle |
| |
| // TODO: Return the resolved value instead of the computed value. See |
| // https://www.w3.org/TR/2013/WD-cssom-20131205/#resolved-value. |
| std::string serialized_text; |
| for (size_t index = 0; index <= kMaxLonghandPropertyKey; ++index) { |
| PropertyKey key = GetLexicographicalLonghandPropertyKey(index); |
| if (!serialized_text.empty()) { |
| serialized_text.push_back(' '); |
| } |
| serialized_text.append(GetPropertyName(key)); |
| serialized_text.append(": "); |
| serialized_text.append(GetPropertyValue(key)->ToString()); |
| serialized_text.push_back(';'); |
| } |
| return serialized_text; |
| } |
| |
| void CSSComputedStyleData::AddDeclaredPropertyInheritedFromParent( |
| PropertyKey key) { |
| DCHECK_GT(key, kNoneProperty); |
| DCHECK_LE(key, kMaxLonghandPropertyKey); |
| declared_properties_inherited_from_parent_.push_back(key); |
| } |
| |
| bool CSSComputedStyleData::AreDeclaredPropertiesInheritedFromParentValid() |
| const { |
| // If there are no declared properties inherited from the parent, then it's |
| // impossible for them to be invalid. |
| if (declared_properties_inherited_from_parent_.size() == 0) { |
| return true; |
| } |
| |
| if (!parent_computed_style_declaration_) { |
| return false; |
| } |
| |
| const scoped_refptr<const CSSComputedStyleData>& parent_computed_style_data = |
| parent_computed_style_declaration_->data(); |
| if (!parent_computed_style_data) { |
| return false; |
| } |
| |
| // Verify that the parent's data is valid. |
| DCHECK(parent_computed_style_data |
| ->AreDeclaredPropertiesInheritedFromParentValid()); |
| |
| // Walk the declared properties inherited from the parent. They're invalid if |
| // any no longer match the parent's value. |
| for (PropertyKeyVector::const_iterator iter = |
| declared_properties_inherited_from_parent_.begin(); |
| iter != declared_properties_inherited_from_parent_.end(); ++iter) { |
| if (!GetPropertyValueReference(*iter)->Equals( |
| *parent_computed_style_data->GetPropertyValueReference(*iter))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool CSSComputedStyleData::DoDeclaredPropertiesMatch( |
| const scoped_refptr<const CSSComputedStyleData>& other) const { |
| // If the bitsets don't match, then there's no need to check the values; |
| // the declared properties are guaranteed to not match. |
| if (declared_properties_ != other->declared_properties_) { |
| return false; |
| } |
| |
| // Verify that the same number of declared property values exist within the |
| // two CSSComputedStyleData objects. This should be guaranteed by the |
| // bitsets matching. |
| DCHECK_EQ(declared_property_values_.size(), |
| other->declared_property_values_.size()); |
| |
| // Walk the two lists of declared property values looking for any keys or |
| // values that don't match. |
| PropertyValues::const_iterator iter1 = declared_property_values_.begin(); |
| PropertyValues::const_iterator iter2 = |
| other->declared_property_values_.begin(); |
| for (; iter1 != declared_property_values_.end(); ++iter1, ++iter2) { |
| if (iter1->first != iter2->first || |
| !iter1->second->Equals(*iter2->second)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void CSSComputedStyleData::SetParentComputedStyleDeclaration( |
| const scoped_refptr<CSSComputedStyleDeclaration>& |
| parent_computed_style_declaration) { |
| parent_computed_style_declaration_ = parent_computed_style_declaration; |
| } |
| |
| const scoped_refptr<CSSComputedStyleDeclaration>& |
| CSSComputedStyleData::GetParentComputedStyleDeclaration() const { |
| return parent_computed_style_declaration_; |
| } |
| |
| } // namespace cssom |
| } // namespace cobalt |