| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/css/resolver/style_cascade.h" |
| |
| #include "third_party/blink/renderer/core/animation/css/css_animations.h" |
| #include "third_party/blink/renderer/core/animation/css_interpolation_environment.h" |
| #include "third_party/blink/renderer/core/animation/css_interpolation_types_map.h" |
| #include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h" |
| #include "third_party/blink/renderer/core/animation/property_handle.h" |
| #include "third_party/blink/renderer/core/animation/transition_interpolation.h" |
| #include "third_party/blink/renderer/core/css/css_custom_property_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_font_selector.h" |
| #include "third_party/blink/renderer/core/css/css_invalid_variable_value.h" |
| #include "third_party/blink/renderer/core/css/css_pending_substitution_value.h" |
| #include "third_party/blink/renderer/core/css/css_unset_value.h" |
| #include "third_party/blink/renderer/core/css/css_variable_data.h" |
| #include "third_party/blink/renderer/core/css/css_variable_reference_value.h" |
| #include "third_party/blink/renderer/core/css/document_style_environment_variables.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h" |
| #include "third_party/blink/renderer/core/css/parser/css_property_parser.h" |
| #include "third_party/blink/renderer/core/css/properties/css_property.h" |
| #include "third_party/blink/renderer/core/css/properties/css_property_ref.h" |
| #include "third_party/blink/renderer/core/css/property_registry.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_expansion.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_interpolations.h" |
| #include "third_party/blink/renderer/core/css/resolver/cascade_resolver.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_builder.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h" |
| #include "third_party/blink/renderer/core/css/scoped_css_value.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/html/html_element.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/style_property_shorthand.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| AtomicString ConsumeVariableName(CSSParserTokenRange& range) { |
| range.ConsumeWhitespace(); |
| CSSParserToken ident_token = range.ConsumeIncludingWhitespace(); |
| DCHECK_EQ(ident_token.GetType(), kIdentToken); |
| return ident_token.Value().ToAtomicString(); |
| } |
| |
| bool ConsumeComma(CSSParserTokenRange& range) { |
| if (range.Peek().GetType() == kCommaToken) { |
| range.Consume(); |
| return true; |
| } |
| return false; |
| } |
| |
| // TODO(crbug.com/1105782): It is currently unclear how to handle 'revert' |
| // at computed-value-time. For now we treat it as 'unset'. |
| const CSSValue* TreatRevertAsUnset(const CSSValue* value) { |
| if (value && value->IsRevertValue()) |
| return cssvalue::CSSUnsetValue::Create(); |
| return value; |
| } |
| |
| const CSSValue* Parse(const CSSProperty& property, |
| CSSParserTokenRange range, |
| const CSSParserContext* context) { |
| return CSSPropertyParser::ParseSingleValue(property.PropertyID(), range, |
| context); |
| } |
| |
| const CSSValue* ValueAt(const MatchResult& result, uint32_t position) { |
| size_t matched_properties_index = DecodeMatchedPropertiesIndex(position); |
| size_t declaration_index = DecodeDeclarationIndex(position); |
| const MatchedPropertiesVector& vector = result.GetMatchedProperties(); |
| const CSSPropertyValueSet* set = vector[matched_properties_index].properties; |
| return &set->PropertyAt(declaration_index).Value(); |
| } |
| |
| const TreeScope& TreeScopeAt(const MatchResult& result, uint32_t position) { |
| size_t matched_properties_index = DecodeMatchedPropertiesIndex(position); |
| const MatchedProperties& properties = |
| result.GetMatchedProperties()[matched_properties_index]; |
| DCHECK_EQ(properties.types_.origin, CascadeOrigin::kAuthor); |
| return result.ScopeFromTreeOrder(properties.types_.tree_order); |
| } |
| |
| PropertyHandle ToPropertyHandle(const CSSProperty& property, |
| CascadePriority priority) { |
| uint32_t position = priority.GetPosition(); |
| CSSPropertyID id = DecodeInterpolationPropertyID(position); |
| if (id == CSSPropertyID::kVariable) { |
| DCHECK(IsA<CustomProperty>(property)); |
| return PropertyHandle(property.GetPropertyNameAtomicString()); |
| } |
| return PropertyHandle(CSSProperty::Get(id), |
| DecodeIsPresentationAttribute(position)); |
| } |
| |
| // https://drafts.csswg.org/css-cascade-4/#default |
| CascadeOrigin TargetOriginForRevert(CascadeOrigin origin) { |
| switch (origin) { |
| case CascadeOrigin::kNone: |
| case CascadeOrigin::kTransition: |
| NOTREACHED(); |
| return CascadeOrigin::kNone; |
| case CascadeOrigin::kUserAgent: |
| return CascadeOrigin::kNone; |
| case CascadeOrigin::kUser: |
| return CascadeOrigin::kUserAgent; |
| case CascadeOrigin::kAuthor: |
| case CascadeOrigin::kAnimation: |
| return CascadeOrigin::kUser; |
| } |
| } |
| |
| CSSPropertyID UnvisitedID(CSSPropertyID id) { |
| if (id == CSSPropertyID::kVariable) |
| return id; |
| const CSSProperty& property = CSSProperty::Get(id); |
| if (!property.IsVisited()) |
| return id; |
| return property.GetUnvisitedProperty()->PropertyID(); |
| } |
| |
| bool IsRevert(const CSSValue& value) { |
| // TODO(andruud): Don't transport CSS-wide keywords in |
| // CustomPropertyDeclaration. |
| return value.IsRevertValue() || |
| (value.IsCustomPropertyDeclaration() && |
| To<CSSCustomPropertyDeclaration>(value).IsRevert()); |
| } |
| |
| bool IsInterpolation(CascadePriority priority) { |
| switch (priority.GetOrigin()) { |
| case CascadeOrigin::kAnimation: |
| case CascadeOrigin::kTransition: |
| return true; |
| case CascadeOrigin::kNone: |
| case CascadeOrigin::kUserAgent: |
| case CascadeOrigin::kUser: |
| case CascadeOrigin::kAuthor: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| MatchResult& StyleCascade::MutableMatchResult() { |
| DCHECK(!generation_) << "Apply has already been called"; |
| needs_match_result_analyze_ = true; |
| return match_result_; |
| } |
| |
| void StyleCascade::AddInterpolations(const ActiveInterpolationsMap* map, |
| CascadeOrigin origin) { |
| DCHECK(map); |
| needs_interpolations_analyze_ = true; |
| interpolations_.Add(map, origin); |
| } |
| |
| void StyleCascade::Apply(CascadeFilter filter) { |
| AnalyzeIfNeeded(); |
| |
| CascadeResolver resolver(filter, ++generation_); |
| |
| ApplyCascadeAffecting(resolver); |
| |
| // Affects the computed value of 'color', hence needs to happen before |
| // high-priority properties. |
| LookupAndApply(GetCSSPropertyColorScheme(), resolver); |
| |
| // Affects the computed value of 'font-size', hence needs to happen before |
| // high-priority properties. |
| LookupAndApply(GetCSSPropertyMathDepth(), resolver); |
| |
| ApplyWebkitBorderImage(resolver); |
| |
| // -webkit-mask-image needs to be applied before -webkit-mask-composite, |
| // otherwise -webkit-mask-composite has no effect. |
| LookupAndApply(GetCSSPropertyWebkitMaskImage(), resolver); |
| |
| ApplyHighPriority(resolver); |
| |
| ApplyMatchResult(resolver); |
| ApplyInterpolations(resolver); |
| |
| if (state_.Style()->HasAppearance()) { |
| if (resolver.AuthorFlags() & CSSProperty::kBackground) |
| state_.Style()->SetHasAuthorBackground(); |
| if (resolver.AuthorFlags() & CSSProperty::kBorder) |
| state_.Style()->SetHasAuthorBorder(); |
| } |
| } |
| |
| std::unique_ptr<CSSBitset> StyleCascade::GetImportantSet() { |
| AnalyzeIfNeeded(); |
| if (!map_.HasImportant()) |
| return nullptr; |
| auto set = std::make_unique<CSSBitset>(); |
| for (CSSPropertyID id : map_.NativeBitset()) { |
| // We use the unvisited ID because visited/unvisited colors are currently |
| // interpolated together. |
| // TODO(crbug.com/1062217): Interpolate visited colors separately |
| set->Or(UnvisitedID(id), map_.At(CSSPropertyName(id)).IsImportant()); |
| } |
| return set; |
| } |
| |
| void StyleCascade::Reset() { |
| map_.Reset(); |
| match_result_.Reset(); |
| interpolations_.Reset(); |
| generation_ = 0; |
| depends_on_cascade_affecting_property_ = false; |
| } |
| |
| const CSSValue* StyleCascade::Resolve(const CSSPropertyName& name, |
| const CSSValue& value, |
| CascadeOrigin origin, |
| CascadeResolver& resolver) { |
| CSSPropertyRef ref(name, state_.GetDocument()); |
| |
| const CSSValue* resolved = |
| Resolve(ResolveSurrogate(ref.GetProperty()), value, origin, resolver); |
| |
| DCHECK(resolved); |
| |
| if (resolved->IsInvalidVariableValue()) |
| return nullptr; |
| |
| return resolved; |
| } |
| |
| HeapHashMap<CSSPropertyName, Member<const CSSValue>> |
| StyleCascade::GetCascadedValues() const { |
| DCHECK(!needs_match_result_analyze_); |
| DCHECK(!needs_interpolations_analyze_); |
| DCHECK_GE(generation_, 0); |
| |
| HeapHashMap<CSSPropertyName, Member<const CSSValue>> result; |
| |
| for (CSSPropertyID id : map_.NativeBitset()) { |
| CSSPropertyName name(id); |
| CascadePriority priority = map_.At(name); |
| DCHECK(priority.HasOrigin()); |
| if (IsInterpolation(priority)) |
| continue; |
| const CSSValue* cascaded = ValueAt(match_result_, priority.GetPosition()); |
| DCHECK(cascaded); |
| result.Set(name, cascaded); |
| } |
| |
| for (const auto& entry : map_.GetCustomMap()) { |
| CascadePriority priority = entry.value; |
| DCHECK(priority.HasOrigin()); |
| if (IsInterpolation(priority)) |
| continue; |
| const CSSValue* cascaded = ValueAt(match_result_, priority.GetPosition()); |
| DCHECK(cascaded); |
| result.Set(entry.key, cascaded); |
| } |
| |
| return result; |
| } |
| |
| void StyleCascade::AnalyzeIfNeeded() { |
| if (needs_match_result_analyze_) { |
| AnalyzeMatchResult(); |
| needs_match_result_analyze_ = false; |
| } |
| if (needs_interpolations_analyze_) { |
| AnalyzeInterpolations(); |
| needs_interpolations_analyze_ = false; |
| } |
| } |
| |
| void StyleCascade::AnalyzeMatchResult() { |
| for (auto e : match_result_.Expansions(GetDocument(), CascadeFilter())) { |
| for (; !e.AtEnd(); e.Next()) { |
| const CSSProperty& property = ResolveSurrogate(e.Property()); |
| map_.Add(property.GetCSSPropertyName(), e.Priority()); |
| } |
| } |
| } |
| |
| void StyleCascade::AnalyzeInterpolations() { |
| const auto& entries = interpolations_.GetEntries(); |
| for (size_t i = 0; i < entries.size(); ++i) { |
| for (const auto& active_interpolation : *entries[i].map) { |
| auto name = active_interpolation.key.GetCSSPropertyName(); |
| uint32_t position = EncodeInterpolationPosition( |
| name.Id(), i, active_interpolation.key.IsPresentationAttribute()); |
| CascadePriority priority(entries[i].origin, false, 0, position); |
| |
| CSSPropertyRef ref(name, GetDocument()); |
| DCHECK(ref.IsValid()); |
| const CSSProperty& property = ResolveSurrogate(ref.GetProperty()); |
| |
| map_.Add(property.GetCSSPropertyName(), priority); |
| |
| // Since an interpolation for an unvisited property also causes an |
| // interpolation of the visited property, add the visited property to |
| // the map as well. |
| // TODO(crbug.com/1062217): Interpolate visited colors separately |
| if (const CSSProperty* visited = property.GetVisitedProperty()) |
| map_.Add(visited->GetCSSPropertyName(), priority); |
| } |
| } |
| } |
| |
| void StyleCascade::Reanalyze() { |
| map_.Reset(); |
| generation_ = 0; |
| depends_on_cascade_affecting_property_ = false; |
| |
| needs_match_result_analyze_ = true; |
| needs_interpolations_analyze_ = true; |
| AnalyzeIfNeeded(); |
| } |
| |
| void StyleCascade::ApplyCascadeAffecting(CascadeResolver& resolver) { |
| // During the initial call to Analyze, we speculatively assume that the |
| // direction/writing-mode inherited from the parent will be the final |
| // direction/writing-mode. If either property ends up with another value, |
| // our assumption was incorrect, and we have to Reanalyze with the correct |
| // values on ComputedStyle. |
| auto direction = state_.Style()->Direction(); |
| auto writing_mode = state_.Style()->GetWritingMode(); |
| |
| LookupAndApply(GetCSSPropertyDirection(), resolver); |
| LookupAndApply(GetCSSPropertyWritingMode(), resolver); |
| |
| if (depends_on_cascade_affecting_property_) { |
| if (direction != state_.Style()->Direction() || |
| writing_mode != state_.Style()->GetWritingMode()) { |
| Reanalyze(); |
| } |
| } |
| } |
| |
| void StyleCascade::ApplyHighPriority(CascadeResolver& resolver) { |
| uint64_t bits = map_.HighPriorityBits(); |
| |
| if (bits) { |
| int first = static_cast<int>(kFirstHighPriorityCSSProperty); |
| int last = static_cast<int>(kLastHighPriorityCSSProperty); |
| for (int i = first; i <= last; ++i) { |
| if (bits & (static_cast<uint64_t>(1) << i)) |
| LookupAndApply(CSSProperty::Get(ConvertToCSSPropertyID(i)), resolver); |
| } |
| } |
| |
| state_.GetFontBuilder().CreateFont(state_.StyleRef(), state_.ParentStyle()); |
| state_.SetConversionFontSizes(CSSToLengthConversionData::FontSizes( |
| state_.Style(), state_.RootElementStyle())); |
| state_.SetConversionZoom(state_.Style()->EffectiveZoom()); |
| } |
| |
| void StyleCascade::ApplyWebkitBorderImage(CascadeResolver& resolver) { |
| const CascadePriority* priority = |
| map_.Find(CSSPropertyName(CSSPropertyID::kWebkitBorderImage)); |
| if (!priority) |
| return; |
| |
| // -webkit-border-image is a surrogate for the border-image (shorthand). |
| // By applying -webkit-border-image first, we avoid having to "partially" |
| // apply -webkit-border-image depending on the border-image-* longhands that |
| // have already been applied. |
| // See also crbug.com/1056600 |
| LookupAndApply(GetCSSPropertyWebkitBorderImage(), resolver); |
| |
| const auto& shorthand = borderImageShorthand(); |
| const CSSProperty** longhands = shorthand.properties(); |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSProperty& longhand = *longhands[i]; |
| if (CascadePriority* p = map_.Find(longhand.GetCSSPropertyName())) { |
| // If -webkit-border-image has higher priority than a border-image |
| // longhand, we skip applying that longhand. |
| if (*p < *priority) |
| *p = CascadePriority(*p, resolver.generation_); |
| } |
| } |
| } |
| |
| void StyleCascade::ApplyMatchResult(CascadeResolver& resolver) { |
| for (auto e : match_result_.Expansions(GetDocument(), resolver.filter_)) { |
| for (; !e.AtEnd(); e.Next()) { |
| auto priority = CascadePriority(e.Priority(), resolver.generation_); |
| const CSSProperty& property = ResolveSurrogate(e.Property()); |
| CascadePriority* p = map_.Find(property.GetCSSPropertyName()); |
| if (!p || *p >= priority) |
| continue; |
| *p = priority; |
| CascadeOrigin origin = priority.GetOrigin(); |
| const CSSValue* value = Resolve(property, e.Value(), origin, resolver); |
| // TODO(futhark): Use a user scope TreeScope to support tree-scoped names |
| // for animations in user stylesheets. |
| const TreeScope* tree_scope = |
| origin == CascadeOrigin::kAuthor |
| ? &match_result_.ScopeFromTreeOrder(e.TreeOrder()) |
| : nullptr; |
| StyleBuilder::ApplyProperty(property, state_, |
| ScopedCSSValue(*value, tree_scope)); |
| } |
| } |
| } |
| |
| void StyleCascade::ApplyInterpolations(CascadeResolver& resolver) { |
| const auto& entries = interpolations_.GetEntries(); |
| for (size_t i = 0; i < entries.size(); ++i) { |
| const auto& entry = entries[i]; |
| ApplyInterpolationMap(*entry.map, entry.origin, i, resolver); |
| } |
| } |
| |
| void StyleCascade::ApplyInterpolationMap(const ActiveInterpolationsMap& map, |
| CascadeOrigin origin, |
| size_t index, |
| CascadeResolver& resolver) { |
| for (const auto& entry : map) { |
| auto name = entry.key.GetCSSPropertyName(); |
| uint32_t position = EncodeInterpolationPosition( |
| name.Id(), index, entry.key.IsPresentationAttribute()); |
| CascadePriority priority(origin, false, 0, position); |
| priority = CascadePriority(priority, resolver.generation_); |
| |
| CSSPropertyRef ref(name, GetDocument()); |
| if (resolver.filter_.Rejects(ref.GetProperty())) |
| continue; |
| |
| const CSSProperty& property = ResolveSurrogate(ref.GetProperty()); |
| |
| CascadePriority* p = map_.Find(property.GetCSSPropertyName()); |
| if (!p || *p >= priority) { |
| continue; |
| } |
| *p = priority; |
| |
| ApplyInterpolation(property, priority, *entry.value, resolver); |
| } |
| } |
| |
| void StyleCascade::ApplyInterpolation( |
| const CSSProperty& property, |
| CascadePriority priority, |
| const ActiveInterpolations& interpolations, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| |
| const Interpolation& interpolation = *interpolations.front(); |
| if (IsA<InvalidatableInterpolation>(interpolation)) { |
| CSSInterpolationTypesMap map(state_.GetDocument().GetPropertyRegistry(), |
| state_.GetDocument()); |
| CSSInterpolationEnvironment environment(map, state_, this, &resolver); |
| InvalidatableInterpolation::ApplyStack(interpolations, environment); |
| } else { |
| To<TransitionInterpolation>(interpolation).Apply(state_); |
| } |
| |
| // Applying a color property interpolation will also unconditionally apply |
| // the -internal-visited- counterpart (see CSSColorInterpolationType:: |
| // ApplyStandardPropertyValue). To make sure !important rules in :visited |
| // selectors win over animations, we re-apply the -internal-visited property |
| // if its priority is higher. |
| // |
| // TODO(crbug.com/1062217): Interpolate visited colors separately |
| if (const CSSProperty* visited = property.GetVisitedProperty()) { |
| CascadePriority* visited_priority = |
| map_.Find(visited->GetCSSPropertyName()); |
| if (visited_priority && priority < *visited_priority) { |
| DCHECK(visited_priority->IsImportant()); |
| // Resetting generation to zero makes it possible to apply the |
| // visited property again. |
| *visited_priority = CascadePriority(*visited_priority, 0); |
| LookupAndApply(*visited, resolver); |
| } |
| } |
| } |
| |
| void StyleCascade::LookupAndApply(const CSSPropertyName& name, |
| CascadeResolver& resolver) { |
| CSSPropertyRef ref(name, state_.GetDocument()); |
| DCHECK(ref.IsValid()); |
| LookupAndApply(ref.GetProperty(), resolver); |
| } |
| |
| void StyleCascade::LookupAndApply(const CSSProperty& property, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| |
| CSSPropertyName name = property.GetCSSPropertyName(); |
| DCHECK(!resolver.IsLocked(property)); |
| |
| CascadePriority* p = map_.Find(name); |
| if (!p) |
| return; |
| CascadePriority priority(*p, resolver.generation_); |
| if (*p >= priority) |
| return; |
| *p = priority; |
| |
| if (resolver.filter_.Rejects(property)) |
| return; |
| |
| LookupAndApplyValue(property, priority, resolver); |
| } |
| |
| void StyleCascade::LookupAndApplyValue(const CSSProperty& property, |
| CascadePriority priority, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| |
| if (priority.GetOrigin() < CascadeOrigin::kAnimation) |
| LookupAndApplyDeclaration(property, priority, resolver); |
| else if (priority.GetOrigin() >= CascadeOrigin::kAnimation) |
| LookupAndApplyInterpolation(property, priority, resolver); |
| } |
| |
| void StyleCascade::LookupAndApplyDeclaration(const CSSProperty& property, |
| CascadePriority priority, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| DCHECK(priority.GetOrigin() < CascadeOrigin::kAnimation); |
| const CSSValue* value = ValueAt(match_result_, priority.GetPosition()); |
| DCHECK(value); |
| CascadeOrigin origin = priority.GetOrigin(); |
| value = Resolve(property, *value, origin, resolver); |
| DCHECK(!value->IsVariableReferenceValue()); |
| DCHECK(!value->IsPendingSubstitutionValue()); |
| const TreeScope* tree_scope{nullptr}; |
| if (origin == CascadeOrigin::kAuthor) |
| tree_scope = &TreeScopeAt(match_result_, priority.GetPosition()); |
| StyleBuilder::ApplyProperty(property, state_, |
| ScopedCSSValue(*value, tree_scope)); |
| } |
| |
| void StyleCascade::LookupAndApplyInterpolation(const CSSProperty& property, |
| CascadePriority priority, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| |
| // Interpolations for -internal-visited properties are applied via the |
| // interpolation for the main (unvisited) property, so we don't need to |
| // apply it twice. |
| // TODO(crbug.com/1062217): Interpolate visited colors separately |
| if (property.IsVisited()) |
| return; |
| DCHECK(priority.GetOrigin() >= CascadeOrigin::kAnimation); |
| size_t index = DecodeInterpolationIndex(priority.GetPosition()); |
| DCHECK_LE(index, interpolations_.GetEntries().size()); |
| const ActiveInterpolationsMap& map = *interpolations_.GetEntries()[index].map; |
| PropertyHandle handle = ToPropertyHandle(property, priority); |
| const auto& entry = map.find(handle); |
| DCHECK_NE(entry, map.end()); |
| ApplyInterpolation(property, priority, *entry->value, resolver); |
| } |
| |
| bool StyleCascade::IsRootElement() const { |
| return &state_.GetElement() == state_.GetDocument().documentElement(); |
| } |
| |
| StyleCascade::TokenSequence::TokenSequence(const CSSVariableData* data) |
| : backing_strings_(data->BackingStrings()), |
| is_animation_tainted_(data->IsAnimationTainted()), |
| has_font_units_(data->HasFontUnits()), |
| has_root_font_units_(data->HasRootFontUnits()), |
| base_url_(data->BaseURL()), |
| charset_(data->Charset()) {} |
| |
| void StyleCascade::TokenSequence::Append(const TokenSequence& sequence) { |
| tokens_.AppendVector(sequence.tokens_); |
| backing_strings_.AppendVector(sequence.backing_strings_); |
| is_animation_tainted_ |= sequence.is_animation_tainted_; |
| has_font_units_ |= sequence.has_font_units_; |
| has_root_font_units_ |= sequence.has_root_font_units_; |
| } |
| |
| void StyleCascade::TokenSequence::Append(const CSSVariableData* data) { |
| tokens_.AppendVector(data->Tokens()); |
| backing_strings_.AppendVector(data->BackingStrings()); |
| is_animation_tainted_ |= data->IsAnimationTainted(); |
| has_font_units_ |= data->HasFontUnits(); |
| has_root_font_units_ |= data->HasRootFontUnits(); |
| } |
| |
| void StyleCascade::TokenSequence::Append(const CSSParserToken& token) { |
| tokens_.push_back(token); |
| } |
| |
| scoped_refptr<CSSVariableData> |
| StyleCascade::TokenSequence::BuildVariableData() { |
| return CSSVariableData::CreateResolved( |
| std::move(tokens_), std::move(backing_strings_), is_animation_tainted_, |
| has_font_units_, has_root_font_units_, base_url_, charset_); |
| } |
| |
| const CSSValue* StyleCascade::Resolve(const CSSProperty& property, |
| const CSSValue& value, |
| CascadeOrigin& origin, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| if (IsRevert(value)) |
| return ResolveRevert(property, value, origin, resolver); |
| resolver.CollectAuthorFlags(property, origin); |
| if (const auto* v = DynamicTo<CSSCustomPropertyDeclaration>(value)) |
| return ResolveCustomProperty(property, *v, resolver); |
| if (const auto* v = DynamicTo<CSSVariableReferenceValue>(value)) |
| return ResolveVariableReference(property, *v, resolver); |
| if (const auto* v = DynamicTo<cssvalue::CSSPendingSubstitutionValue>(value)) |
| return ResolvePendingSubstitution(property, *v, resolver); |
| return &value; |
| } |
| |
| const CSSValue* StyleCascade::ResolveCustomProperty( |
| const CSSProperty& property, |
| const CSSCustomPropertyDeclaration& decl, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| |
| // TODO(andruud): Don't transport css-wide keywords in this value. |
| if (!decl.Value()) |
| return &decl; |
| |
| DCHECK(!resolver.IsLocked(property)); |
| CascadeResolver::AutoLock lock(property, resolver); |
| |
| scoped_refptr<CSSVariableData> data = decl.Value(); |
| |
| if (data->NeedsVariableResolution()) |
| data = ResolveVariableData(data.get(), resolver); |
| |
| if (HasFontSizeDependency(To<CustomProperty>(property), data.get())) |
| resolver.DetectCycle(GetCSSPropertyFontSize()); |
| |
| state_.Style()->SetHasVariableDeclaration(); |
| |
| if (resolver.InCycle()) |
| return CSSInvalidVariableValue::Create(); |
| |
| if (!data) { |
| MaybeUseCountInvalidVariableUnset(To<CustomProperty>(property)); |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| |
| if (data == decl.Value()) |
| return &decl; |
| |
| return MakeGarbageCollected<CSSCustomPropertyDeclaration>(decl.GetName(), |
| data); |
| } |
| |
| const CSSValue* StyleCascade::ResolveVariableReference( |
| const CSSProperty& property, |
| const CSSVariableReferenceValue& value, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| DCHECK(!resolver.IsLocked(property)); |
| CascadeResolver::AutoLock lock(property, resolver); |
| |
| const CSSVariableData* data = value.VariableDataValue(); |
| const CSSParserContext* context = GetParserContext(value); |
| |
| MarkHasVariableReference(property); |
| |
| DCHECK(data); |
| DCHECK(context); |
| |
| TokenSequence sequence; |
| |
| if (ResolveTokensInto(data->Tokens(), resolver, sequence)) { |
| if (const auto* parsed = Parse(property, sequence.TokenRange(), context)) |
| return TreatRevertAsUnset(parsed); |
| } |
| |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| |
| const CSSValue* StyleCascade::ResolvePendingSubstitution( |
| const CSSProperty& property, |
| const cssvalue::CSSPendingSubstitutionValue& value, |
| CascadeResolver& resolver) { |
| DCHECK(!property.IsSurrogate()); |
| DCHECK(!resolver.IsLocked(property)); |
| CascadeResolver::AutoLock lock(property, resolver); |
| |
| CascadePriority priority = map_.At(property.GetCSSPropertyName()); |
| DCHECK_NE(property.PropertyID(), CSSPropertyID::kVariable); |
| DCHECK_NE(priority.GetOrigin(), CascadeOrigin::kNone); |
| |
| MarkHasVariableReference(property); |
| |
| // If the previous call to ResolvePendingSubstitution parsed 'value', then |
| // we don't need to do it again. |
| bool is_cached = resolver.shorthand_cache_.value == &value; |
| |
| if (!is_cached) { |
| CSSVariableReferenceValue* shorthand_value = value.ShorthandValue(); |
| const auto* shorthand_data = shorthand_value->VariableDataValue(); |
| CSSPropertyID shorthand_property_id = value.ShorthandPropertyId(); |
| |
| TokenSequence sequence; |
| |
| if (!ResolveTokensInto(shorthand_data->Tokens(), resolver, sequence)) |
| return cssvalue::CSSUnsetValue::Create(); |
| |
| HeapVector<CSSPropertyValue, 256> parsed_properties; |
| const bool important = false; |
| |
| if (!CSSPropertyParser::ParseValue( |
| shorthand_property_id, important, sequence.TokenRange(), |
| shorthand_value->ParserContext(), parsed_properties, |
| StyleRule::RuleType::kStyle)) { |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| |
| resolver.shorthand_cache_.value = &value; |
| resolver.shorthand_cache_.parsed_properties = std::move(parsed_properties); |
| } |
| |
| const auto& parsed_properties = resolver.shorthand_cache_.parsed_properties; |
| |
| // For -internal-visited-properties with CSSPendingSubstitutionValues, |
| // the inner 'shorthand_property_id' will expand to a set of longhands |
| // containing the unvisited equivalent. Hence, when parsing the |
| // CSSPendingSubstitutionValue, we look for the unvisited property in |
| // parsed_properties. |
| const CSSProperty* unvisited_property = |
| property.IsVisited() ? property.GetUnvisitedProperty() : &property; |
| |
| unsigned parsed_properties_count = parsed_properties.size(); |
| for (unsigned i = 0; i < parsed_properties_count; ++i) { |
| const CSSProperty& longhand = CSSProperty::Get(parsed_properties[i].Id()); |
| const CSSValue* parsed = parsed_properties[i].Value(); |
| |
| // When using var() in a css-logical shorthand (e.g. margin-inline), |
| // the longhands here will also be logical. |
| if (unvisited_property == &ResolveSurrogate(longhand)) |
| return TreatRevertAsUnset(parsed); |
| } |
| |
| NOTREACHED(); |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| |
| const CSSValue* StyleCascade::ResolveRevert(const CSSProperty& property, |
| const CSSValue& value, |
| CascadeOrigin& origin, |
| CascadeResolver& resolver) { |
| MaybeUseCountRevert(value); |
| |
| CascadeOrigin target_origin = TargetOriginForRevert(origin); |
| |
| switch (target_origin) { |
| case CascadeOrigin::kTransition: |
| case CascadeOrigin::kNone: |
| return cssvalue::CSSUnsetValue::Create(); |
| case CascadeOrigin::kUserAgent: |
| case CascadeOrigin::kUser: |
| case CascadeOrigin::kAuthor: |
| case CascadeOrigin::kAnimation: { |
| CascadePriority* p = |
| map_.Find(property.GetCSSPropertyName(), target_origin); |
| if (!p) { |
| origin = CascadeOrigin::kNone; |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| origin = p->GetOrigin(); |
| return Resolve(property, *ValueAt(match_result_, p->GetPosition()), |
| origin, resolver); |
| } |
| } |
| } |
| |
| scoped_refptr<CSSVariableData> StyleCascade::ResolveVariableData( |
| CSSVariableData* data, |
| CascadeResolver& resolver) { |
| DCHECK(data && data->NeedsVariableResolution()); |
| |
| TokenSequence sequence(data); |
| |
| if (!ResolveTokensInto(data->Tokens(), resolver, sequence)) |
| return nullptr; |
| |
| return sequence.BuildVariableData(); |
| } |
| |
| bool StyleCascade::ResolveTokensInto(CSSParserTokenRange range, |
| CascadeResolver& resolver, |
| TokenSequence& out) { |
| bool success = true; |
| while (!range.AtEnd()) { |
| const CSSParserToken& token = range.Peek(); |
| if (token.FunctionId() == CSSValueID::kVar) |
| success &= ResolveVarInto(range.ConsumeBlock(), resolver, out); |
| else if (token.FunctionId() == CSSValueID::kEnv) |
| success &= ResolveEnvInto(range.ConsumeBlock(), resolver, out); |
| else |
| out.Append(range.Consume()); |
| } |
| return success; |
| } |
| |
| bool StyleCascade::ResolveVarInto(CSSParserTokenRange range, |
| CascadeResolver& resolver, |
| TokenSequence& out) { |
| AtomicString variable_name = ConsumeVariableName(range); |
| DCHECK(range.AtEnd() || (range.Peek().GetType() == kCommaToken)); |
| |
| CustomProperty property(variable_name, state_.GetDocument()); |
| |
| // Any custom property referenced (by anything, even just once) in the |
| // document can currently not be animated on the compositor. Hence we mark |
| // properties that have been referenced. |
| DCHECK(resolver.CurrentProperty()); |
| MarkIsReferenced(*resolver.CurrentProperty(), property); |
| |
| if (!resolver.DetectCycle(property)) { |
| // We are about to substitute var(property). In order to do that, we must |
| // know the computed value of 'property', hence we Apply it. |
| // |
| // We can however not do this if we're in a cycle. If a cycle is detected |
| // here, it means we are already resolving 'property', and have discovered |
| // a reference to 'property' during that resolution. |
| LookupAndApply(property, resolver); |
| } |
| |
| // Note that even if we are in a cycle, we must proceed in order to discover |
| // secondary cycles via the var() fallback. |
| |
| scoped_refptr<CSSVariableData> data = GetVariableData(property); |
| |
| // If substitution is not allowed, treat the value as |
| // invalid-at-computed-value-time. |
| // |
| // https://drafts.csswg.org/css-variables/#animation-tainted |
| if (!resolver.AllowSubstitution(data.get())) |
| data = nullptr; |
| |
| // If we have a fallback, we must process it to look for cycles, |
| // even if we aren't going to use the fallback. |
| // |
| // https://drafts.csswg.org/css-variables/#cycles |
| if (ConsumeComma(range)) { |
| TokenSequence fallback; |
| bool success = ResolveTokensInto(range, resolver, fallback); |
| // The fallback must match the syntax of the referenced custom property. |
| // https://drafts.css-houdini.org/css-properties-values-api-1/#fallbacks-in-var-references |
| if (!ValidateFallback(property, fallback.TokenRange())) |
| return false; |
| if (!data && success) |
| data = fallback.BuildVariableData(); |
| } |
| |
| if (!data || resolver.InCycle()) |
| return false; |
| |
| // https://drafts.csswg.org/css-variables/#long-variables |
| if (data->Tokens().size() > kMaxSubstitutionTokens) |
| return false; |
| |
| out.Append(data.get()); |
| |
| return true; |
| } |
| |
| bool StyleCascade::ResolveEnvInto(CSSParserTokenRange range, |
| CascadeResolver& resolver, |
| TokenSequence& out) { |
| AtomicString variable_name = ConsumeVariableName(range); |
| DCHECK(range.AtEnd() || (range.Peek().GetType() == kCommaToken)); |
| |
| CSSVariableData* data = GetEnvironmentVariable(variable_name); |
| |
| if (!data) { |
| if (ConsumeComma(range)) |
| return ResolveTokensInto(range, resolver, out); |
| return false; |
| } |
| |
| out.Append(data); |
| |
| return true; |
| } |
| |
| CSSVariableData* StyleCascade::GetVariableData( |
| const CustomProperty& property) const { |
| const AtomicString& name = property.GetPropertyNameAtomicString(); |
| const bool is_inherited = property.IsInherited(); |
| return state_.StyleRef().GetVariableData(name, is_inherited); |
| } |
| |
| CSSVariableData* StyleCascade::GetEnvironmentVariable( |
| const AtomicString& name) const { |
| // If we are in a User Agent Shadow DOM then we should not record metrics. |
| ContainerNode& scope_root = state_.GetElement().GetTreeScope().RootNode(); |
| auto* shadow_root = DynamicTo<ShadowRoot>(&scope_root); |
| bool is_ua_scope = shadow_root && shadow_root->IsUserAgent(); |
| |
| return state_.GetDocument() |
| .GetStyleEngine() |
| .EnsureEnvironmentVariables() |
| .ResolveVariable(name, !is_ua_scope); |
| } |
| |
| const CSSParserContext* StyleCascade::GetParserContext( |
| const CSSVariableReferenceValue& value) { |
| // TODO(crbug.com/985028): CSSVariableReferenceValue should always have a |
| // CSSParserContext. (CSSUnparsedValue violates this). |
| if (value.ParserContext()) |
| return value.ParserContext(); |
| return StrictCSSParserContext( |
| state_.GetDocument().GetExecutionContext()->GetSecureContextMode()); |
| } |
| |
| bool StyleCascade::HasFontSizeDependency(const CustomProperty& property, |
| CSSVariableData* data) const { |
| if (!property.IsRegistered() || !data) |
| return false; |
| if (data->HasFontUnits()) |
| return true; |
| if (data->HasRootFontUnits() && IsRootElement()) |
| return true; |
| return false; |
| } |
| |
| bool StyleCascade::ValidateFallback(const CustomProperty& property, |
| CSSParserTokenRange range) const { |
| if (!property.IsRegistered()) |
| return true; |
| auto context_mode = |
| state_.GetDocument().GetExecutionContext()->GetSecureContextMode(); |
| auto var_mode = CSSParserLocalContext::VariableMode::kTyped; |
| auto* context = StrictCSSParserContext(context_mode); |
| auto local_context = CSSParserLocalContext().WithVariableMode(var_mode); |
| return property.ParseSingleValue(range, *context, local_context); |
| } |
| |
| void StyleCascade::MarkIsReferenced(const CSSProperty& referencer, |
| const CustomProperty& referenced) { |
| if (!referenced.IsRegistered()) |
| return; |
| const AtomicString& name = referenced.GetPropertyNameAtomicString(); |
| state_.GetDocument().EnsurePropertyRegistry().MarkReferenced(name); |
| } |
| |
| void StyleCascade::MarkHasVariableReference(const CSSProperty& property) { |
| if (!property.IsInherited()) |
| state_.Style()->SetHasVariableReferenceFromNonInheritedProperty(); |
| state_.Style()->SetHasVariableReference(); |
| } |
| |
| const Document& StyleCascade::GetDocument() const { |
| return state_.GetDocument(); |
| } |
| |
| const CSSProperty& StyleCascade::ResolveSurrogate(const CSSProperty& property) { |
| if (!property.IsSurrogate()) |
| return property; |
| // This marks the cascade as dependent on cascade-affecting properties |
| // even for simple surrogates like -webkit-writing-mode, but there isn't |
| // currently a flag to distinguish such surrogates from e.g. css-logical |
| // properties. |
| depends_on_cascade_affecting_property_ = true; |
| const CSSProperty* original = property.SurrogateFor( |
| state_.Style()->Direction(), state_.Style()->GetWritingMode()); |
| DCHECK(original); |
| return *original; |
| } |
| |
| void StyleCascade::CountUse(WebFeature feature) { |
| GetDocument().CountUse(feature); |
| } |
| |
| void StyleCascade::MaybeUseCountRevert(const CSSValue& value) { |
| if (IsRevert(value)) |
| CountUse(WebFeature::kCSSKeywordRevert); |
| } |
| |
| void StyleCascade::MaybeUseCountInvalidVariableUnset( |
| const CustomProperty& property) { |
| if (!property.SupportsGuaranteedInvalid()) |
| return; |
| if (!property.IsInherited() && !property.HasInitialValue()) |
| return; |
| const AtomicString& name = property.GetPropertyNameAtomicString(); |
| const ComputedStyle* parent_style = state_.ParentStyle(); |
| if (parent_style && |
| parent_style->GetVariableData(name, property.IsInherited())) { |
| CountUse(WebFeature::kCSSInvalidVariableUnset); |
| } |
| } |
| |
| } // namespace blink |