| // Copyright 2015 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/css_variable_resolver.h" |
| |
| #include "third_party/blink/renderer/core/css/css_custom_property_declaration.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_token.h" |
| #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h" |
| #include "third_party/blink/renderer/core/css/parser/css_property_parser.h" |
| #include "third_party/blink/renderer/core/css/property_registry.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_builder.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_builder_converter.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_stats.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/css_property_names.h" |
| #include "third_party/blink/renderer/core/css_value_keywords.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/core/style/style_inherited_variables.h" |
| #include "third_party/blink/renderer/core/style/style_non_inherited_variables.h" |
| #include "third_party/blink/renderer/core/style_property_shorthand.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| CSSParserToken ResolveUrl(const CSSParserToken& token, |
| Vector<String>& backing_strings, |
| const KURL& base_url, |
| WTF::TextEncoding charset) { |
| DCHECK(token.GetType() == kUrlToken || token.GetType() == kStringToken); |
| |
| StringView string_view = token.Value(); |
| |
| if (string_view.IsNull()) |
| return token; |
| |
| String relative_url = string_view.ToString(); |
| KURL absolute_url = charset.IsValid() ? KURL(base_url, relative_url, charset) |
| : KURL(base_url, relative_url); |
| |
| backing_strings.push_back(absolute_url.GetString()); |
| |
| return token.CopyWithUpdatedString(StringView(backing_strings.back())); |
| } |
| |
| } // namespace |
| |
| bool CSSVariableResolver::ResolveFallback(CSSParserTokenRange range, |
| const Options& options, |
| Result& result) { |
| if (range.AtEnd()) |
| return false; |
| DCHECK_EQ(range.Peek().GetType(), kCommaToken); |
| range.Consume(); |
| return ResolveTokenRange(range, options, result); |
| } |
| |
| scoped_refptr<CSSVariableData> CSSVariableResolver::ValueForCustomProperty( |
| AtomicString name, |
| const Options& options) { |
| if (variables_seen_.Contains(name)) { |
| cycle_start_points_.insert(name); |
| return nullptr; |
| } |
| |
| DCHECK(registry_ || !RuntimeEnabledFeatures::CSSVariables2Enabled()); |
| const PropertyRegistration* registration = |
| registry_ ? registry_->Registration(name) : nullptr; |
| |
| CSSVariableData* variable_data = GetVariable(name, registration); |
| |
| if (!variable_data) |
| return registration ? registration->InitialVariableData() : nullptr; |
| |
| bool resolve_urls = ShouldResolveRelativeUrls(name, *variable_data); |
| |
| if (!variable_data->NeedsVariableResolution()) { |
| if (IsVariableDisallowed(*variable_data, options, registration)) |
| return nullptr; |
| if (!resolve_urls) |
| return variable_data; |
| } |
| |
| bool unused_cycle_detected; |
| scoped_refptr<CSSVariableData> new_variable_data = ResolveCustomProperty( |
| name, *variable_data, options, resolve_urls, unused_cycle_detected); |
| if (!registration) { |
| SetVariable(name, registration, new_variable_data); |
| return new_variable_data; |
| } |
| |
| const CSSValue* parsed_value = nullptr; |
| if (new_variable_data) { |
| parsed_value = new_variable_data->ParseForSyntax( |
| registration->Syntax(), state_.GetDocument().GetSecureContextMode()); |
| if (!parsed_value) |
| new_variable_data = nullptr; |
| } |
| SetVariable(name, registration, new_variable_data); |
| SetRegisteredVariable(name, *registration, parsed_value); |
| if (!new_variable_data) |
| return registration->InitialVariableData(); |
| return new_variable_data; |
| } |
| |
| scoped_refptr<CSSVariableData> CSSVariableResolver::ResolveCustomProperty( |
| AtomicString name, |
| const CSSVariableData& variable_data, |
| const Options& options, |
| bool resolve_urls, |
| bool& cycle_detected) { |
| DCHECK(variable_data.NeedsVariableResolution() || resolve_urls); |
| |
| Result result; |
| result.is_animation_tainted = variable_data.IsAnimationTainted(); |
| result.has_font_units = variable_data.HasFontUnits(); |
| result.has_root_font_units = variable_data.HasRootFontUnits(); |
| result.backing_strings.AppendVector(variable_data.BackingStrings()); |
| DCHECK(!variables_seen_.Contains(name)); |
| variables_seen_.insert(name); |
| bool success = ResolveTokenRange(variable_data.Tokens(), options, result); |
| variables_seen_.erase(name); |
| |
| if (!success || !cycle_start_points_.IsEmpty()) { |
| cycle_start_points_.erase(name); |
| cycle_detected = true; |
| return nullptr; |
| } |
| cycle_detected = false; |
| |
| if (resolve_urls) { |
| ResolveRelativeUrls(result.tokens, result.backing_strings, |
| variable_data.BaseURL(), variable_data.Charset()); |
| } |
| |
| return CSSVariableData::CreateResolved( |
| result.tokens, std::move(result.backing_strings), |
| result.is_animation_tainted, result.has_font_units, |
| result.has_root_font_units); |
| } |
| |
| void CSSVariableResolver::ResolveRelativeUrls( |
| Vector<CSSParserToken>& tokens, |
| Vector<String>& backing_strings, |
| const KURL& base_url, |
| const WTF::TextEncoding& charset) { |
| CSSParserToken* token = tokens.begin(); |
| CSSParserToken* end = tokens.end(); |
| |
| while (token < end) { |
| if (token->GetType() == kUrlToken) { |
| *token = ResolveUrl(*token, backing_strings, base_url, charset); |
| } else if (token->FunctionId() == CSSValueUrl) { |
| if (token + 1 < end && token[1].GetType() == kStringToken) |
| token[1] = ResolveUrl(token[1], backing_strings, base_url, charset); |
| } |
| ++token; |
| } |
| } |
| |
| bool CSSVariableResolver::ShouldResolveRelativeUrls( |
| const AtomicString& name, |
| const CSSVariableData& variable_data) { |
| if (!variable_data.NeedsUrlResolution()) |
| return false; |
| const PropertyRegistration* registration = |
| registry_ ? registry_->Registration(name) : nullptr; |
| return registration ? registration->Syntax().HasUrlSyntax() : false; |
| } |
| |
| bool CSSVariableResolver::IsVariableDisallowed( |
| const CSSVariableData& variable_data, |
| const Options& options, |
| const PropertyRegistration* registration) { |
| return (options.disallow_animation_tainted && |
| variable_data.IsAnimationTainted()) || |
| (registration && options.disallow_registered_font_units && |
| variable_data.HasFontUnits()) || |
| (registration && options.disallow_registered_root_font_units && |
| variable_data.HasRootFontUnits()); |
| } |
| |
| CSSVariableData* CSSVariableResolver::GetVariable( |
| const AtomicString& name, |
| const PropertyRegistration* registration) { |
| if (!registration || registration->Inherits()) { |
| return inherited_variables_ ? inherited_variables_->GetVariable(name) |
| : nullptr; |
| } |
| return non_inherited_variables_ ? non_inherited_variables_->GetVariable(name) |
| : nullptr; |
| } |
| |
| const CSSValue* CSSVariableResolver::GetRegisteredVariable( |
| const AtomicString& name, |
| const PropertyRegistration& registration) { |
| if (registration.Inherits()) { |
| return inherited_variables_ ? inherited_variables_->RegisteredVariable(name) |
| : nullptr; |
| } |
| return non_inherited_variables_ |
| ? non_inherited_variables_->RegisteredVariable(name) |
| : nullptr; |
| } |
| |
| void CSSVariableResolver::SetVariable( |
| const AtomicString& name, |
| const PropertyRegistration* registration, |
| scoped_refptr<CSSVariableData> variable_data) { |
| if (!registration || registration->Inherits()) { |
| DCHECK(inherited_variables_); |
| inherited_variables_->SetVariable(name, std::move(variable_data)); |
| } else { |
| DCHECK(non_inherited_variables_); |
| non_inherited_variables_->SetVariable(name, std::move(variable_data)); |
| } |
| } |
| |
| void CSSVariableResolver::SetRegisteredVariable( |
| const AtomicString& name, |
| const PropertyRegistration& registration, |
| const CSSValue* value) { |
| if (registration.Inherits()) { |
| DCHECK(inherited_variables_); |
| inherited_variables_->SetRegisteredVariable(name, value); |
| } else { |
| DCHECK(non_inherited_variables_); |
| non_inherited_variables_->SetRegisteredVariable(name, value); |
| } |
| } |
| |
| bool CSSVariableResolver::ResolveVariableReference(CSSParserTokenRange range, |
| const Options& options, |
| bool is_env_variable, |
| Result& result) { |
| range.ConsumeWhitespace(); |
| DCHECK_EQ(range.Peek().GetType(), kIdentToken); |
| AtomicString variable_name = |
| range.ConsumeIncludingWhitespace().Value().ToAtomicString(); |
| DCHECK(range.AtEnd() || (range.Peek().GetType() == kCommaToken)); |
| |
| PropertyHandle property(variable_name); |
| if (state_.AnimationPendingCustomProperties().Contains(property) && |
| !variables_seen_.Contains(variable_name)) { |
| // We make the StyleResolverState mutable for animated custom properties as |
| // an optimisation. Without this we would need to compute animated values on |
| // the stack without saving the result or perform an expensive and complex |
| // value dependency graph analysis to compute them in the required order. |
| StyleResolver::ApplyAnimatedCustomProperty( |
| const_cast<StyleResolverState&>(state_), *this, property); |
| // Null custom property storage may become non-null after application, we |
| // must refresh these cached values. |
| inherited_variables_ = state_.Style()->InheritedVariables(); |
| non_inherited_variables_ = state_.Style()->NonInheritedVariables(); |
| } |
| scoped_refptr<CSSVariableData> variable_data = |
| is_env_variable ? ValueForEnvironmentVariable(variable_name) |
| : ValueForCustomProperty(variable_name, options); |
| |
| if (!variable_data) { |
| // TODO(alancutter): Append the registered initial custom property value if |
| // we are disallowing an animation tainted value. |
| return ResolveFallback(range, options, result); |
| } |
| |
| result.tokens.AppendVector(variable_data->Tokens()); |
| // TODO(alancutter): Avoid adding backing strings multiple times in a row. |
| result.backing_strings.AppendVector(variable_data->BackingStrings()); |
| result.is_animation_tainted |= variable_data->IsAnimationTainted(); |
| result.has_font_units |= variable_data->HasFontUnits(); |
| result.has_root_font_units |= variable_data->HasRootFontUnits(); |
| |
| Result trash; |
| ResolveFallback(range, options, trash); |
| return true; |
| } |
| |
| scoped_refptr<CSSVariableData> CSSVariableResolver::ValueForEnvironmentVariable( |
| const AtomicString& name) { |
| // If we are in a User Agent Shadow DOM then we should not record metrics. |
| ContainerNode& scope_root = state_.GetTreeScope().RootNode(); |
| bool is_ua_scope = |
| scope_root.IsShadowRoot() && ToShadowRoot(scope_root).IsUserAgent(); |
| |
| return state_.GetDocument() |
| .GetStyleEngine() |
| .EnsureEnvironmentVariables() |
| .ResolveVariable(name, !is_ua_scope); |
| } |
| |
| bool CSSVariableResolver::ResolveTokenRange(CSSParserTokenRange range, |
| const Options& options, |
| Result& result) { |
| bool success = true; |
| while (!range.AtEnd()) { |
| const CSSParserToken& token = range.Peek(); |
| if (token.FunctionId() == CSSValueVar || |
| token.FunctionId() == CSSValueEnv) { |
| success &= |
| ResolveVariableReference(range.ConsumeBlock(), options, |
| token.FunctionId() == CSSValueEnv, result); |
| } else { |
| result.tokens.push_back(range.Consume()); |
| } |
| } |
| return success; |
| } |
| |
| const CSSValue* CSSVariableResolver::ResolveVariableReferences( |
| CSSPropertyID id, |
| const CSSValue& value, |
| bool disallow_animation_tainted) { |
| DCHECK(!CSSProperty::Get(id).IsShorthand()); |
| |
| Options options; |
| options.disallow_animation_tainted = disallow_animation_tainted; |
| |
| if (id == CSSPropertyFontSize) { |
| bool is_root = |
| state_.GetElement() && |
| state_.GetElement() == state_.GetDocument().documentElement(); |
| options.disallow_registered_font_units = true; |
| options.disallow_registered_root_font_units = is_root; |
| } |
| |
| if (value.IsPendingSubstitutionValue()) { |
| return ResolvePendingSubstitutions(id, ToCSSPendingSubstitutionValue(value), |
| options); |
| } |
| |
| if (value.IsVariableReferenceValue()) { |
| return ResolveVariableReferences(id, ToCSSVariableReferenceValue(value), |
| options); |
| } |
| |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| const CSSValue* CSSVariableResolver::ResolveVariableReferences( |
| CSSPropertyID id, |
| const CSSVariableReferenceValue& value, |
| const Options& options) { |
| Result result; |
| |
| if (!ResolveTokenRange(value.VariableDataValue()->Tokens(), options, |
| result)) { |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| const CSSValue* resolved_value = CSSPropertyParser::ParseSingleValue( |
| id, result.tokens, value.ParserContext()); |
| if (!resolved_value) |
| return cssvalue::CSSUnsetValue::Create(); |
| return resolved_value; |
| } |
| |
| const CSSValue* CSSVariableResolver::ResolvePendingSubstitutions( |
| CSSPropertyID id, |
| const CSSPendingSubstitutionValue& pending_value, |
| const Options& options) { |
| // Longhands from shorthand references follow this path. |
| HeapHashMap<CSSPropertyID, Member<const CSSValue>>& property_cache = |
| state_.ParsedPropertiesForPendingSubstitutionCache(pending_value); |
| |
| const CSSValue* value = property_cache.at(id); |
| if (!value) { |
| // TODO(timloh): We shouldn't retry this for all longhands if the shorthand |
| // ends up invalid. |
| CSSVariableReferenceValue* shorthand_value = pending_value.ShorthandValue(); |
| CSSPropertyID shorthand_property_id = pending_value.ShorthandPropertyId(); |
| |
| Result result; |
| if (ResolveTokenRange(shorthand_value->VariableDataValue()->Tokens(), |
| options, result)) { |
| HeapVector<CSSPropertyValue, 256> parsed_properties; |
| |
| if (CSSPropertyParser::ParseValue( |
| shorthand_property_id, false, CSSParserTokenRange(result.tokens), |
| shorthand_value->ParserContext(), parsed_properties, |
| StyleRule::RuleType::kStyle)) { |
| unsigned parsed_properties_count = parsed_properties.size(); |
| for (unsigned i = 0; i < parsed_properties_count; ++i) { |
| property_cache.Set(parsed_properties[i].Id(), |
| parsed_properties[i].Value()); |
| } |
| } |
| } |
| value = property_cache.at(id); |
| } |
| |
| if (value) |
| return value; |
| |
| return cssvalue::CSSUnsetValue::Create(); |
| } |
| |
| scoped_refptr<CSSVariableData> |
| CSSVariableResolver::ResolveCustomPropertyAnimationKeyframe( |
| const CSSCustomPropertyDeclaration& keyframe, |
| bool& cycle_detected) { |
| DCHECK(keyframe.Value()); |
| DCHECK(keyframe.Value()->NeedsVariableResolution()); |
| const AtomicString& name = keyframe.GetName(); |
| |
| if (variables_seen_.Contains(name)) { |
| cycle_start_points_.insert(name); |
| cycle_detected = true; |
| return nullptr; |
| } |
| |
| bool resolve_urls = false; |
| return ResolveCustomProperty(name, *keyframe.Value(), Options(), resolve_urls, |
| cycle_detected); |
| } |
| |
| void CSSVariableResolver::ResolveVariableDefinitions() { |
| if (!inherited_variables_ && !non_inherited_variables_) |
| return; |
| |
| Options options; |
| |
| int variable_count = 0; |
| if (inherited_variables_ && inherited_variables_->NeedsResolution()) { |
| for (auto& variable : inherited_variables_->data_) |
| ValueForCustomProperty(variable.key, options); |
| inherited_variables_->ClearNeedsResolution(); |
| variable_count += inherited_variables_->data_.size(); |
| } |
| if (non_inherited_variables_ && non_inherited_variables_->NeedsResolution()) { |
| for (auto& variable : non_inherited_variables_->data_) |
| ValueForCustomProperty(variable.key, options); |
| non_inherited_variables_->ClearNeedsResolution(); |
| variable_count += non_inherited_variables_->data_.size(); |
| } |
| INCREMENT_STYLE_STATS_COUNTER(state_.GetDocument().GetStyleEngine(), |
| custom_properties_applied, variable_count); |
| } |
| |
| void CSSVariableResolver::ComputeRegisteredVariables() { |
| // const_cast is needed because Persistent<const ...> doesn't work properly. |
| |
| if (inherited_variables_) { |
| for (auto& variable : inherited_variables_->registered_data_) { |
| if (variable.value) { |
| variable.value = const_cast<CSSValue*>( |
| &StyleBuilderConverter::ConvertRegisteredPropertyValue( |
| state_, *variable.value)); |
| } |
| } |
| } |
| |
| if (non_inherited_variables_) { |
| for (auto& variable : non_inherited_variables_->registered_data_) { |
| if (variable.value) { |
| variable.value = const_cast<CSSValue*>( |
| &StyleBuilderConverter::ConvertRegisteredPropertyValue( |
| state_, *variable.value)); |
| } |
| } |
| } |
| } |
| |
| CSSVariableResolver::CSSVariableResolver(const StyleResolverState& state) |
| : state_(state), |
| inherited_variables_(state.Style()->InheritedVariables()), |
| non_inherited_variables_(state.Style()->NonInheritedVariables()), |
| registry_(state.GetDocument().GetPropertyRegistry()) {} |
| |
| } // namespace blink |