Respect FunctionContext when substituting if()
When substituting condition property, query specified value or if()'s declaration value, the function context must be respected. It can either refer to a local variable, and argument, or a custom property. Bug: 346977961, 325504770 Change-Id: Id40053515d512f4ed7ce99c83d277f9ce22cbd9b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6271544 Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org> Commit-Queue: Munira Tursunova <moonira@google.com> Cr-Commit-Position: refs/heads/main@{#1421042}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
95a5bbf538
commit
2b59535eff
third_party/blink
renderer
core
css
resolver
web_tests
external
wpt
css
css-mixins
@ -1420,7 +1420,8 @@ bool StyleCascade::ResolveTokensInto(CSSParserTokenStream& stream,
|
||||
} else if (token.FunctionId() == CSSValueID::kIf &&
|
||||
RuntimeEnabledFeatures::CSSInlineIfForStyleQueriesEnabled()) {
|
||||
CSSParserTokenStream::BlockGuard guard(stream);
|
||||
success &= ResolveIfInto(stream, resolver, context, out);
|
||||
success &=
|
||||
ResolveIfInto(stream, resolver, context, function_context, out);
|
||||
} else if (token.GetType() == kFunctionToken &&
|
||||
CSSVariableParser::IsValidVariableName(token.Value()) &&
|
||||
RuntimeEnabledFeatures::CSSFunctionsEnabled()) {
|
||||
@ -2083,6 +2084,7 @@ KleeneValue StyleCascade::EvalIfStyleFeature(
|
||||
const MediaQueryFeatureExpNode& feature,
|
||||
CascadeResolver& resolver,
|
||||
const CSSParserContext& context,
|
||||
FunctionContext* function_context,
|
||||
bool& is_attr_tainted) {
|
||||
const MediaQueryExpBounds& bounds = feature.Bounds();
|
||||
|
||||
@ -2090,35 +2092,42 @@ KleeneValue StyleCascade::EvalIfStyleFeature(
|
||||
DCHECK(!bounds.IsRange());
|
||||
DCHECK(bounds.right.op == MediaQueryOperator::kNone);
|
||||
|
||||
// TODO(crbug.com/325504770): Take function context into account.
|
||||
AtomicString property_name(feature.Name());
|
||||
CustomProperty property(property_name, GetDocument());
|
||||
|
||||
// Check for a cycle with custom property in the style condition.
|
||||
if (resolver.DetectCycle(property)) {
|
||||
CSSVariableData* computed_data = nullptr;
|
||||
CSSParserTokenStream property_name_stream(property_name);
|
||||
TokenSequence computed_data_sequence;
|
||||
// To avoid duplicating lookup logic, we pretend that we're resolving
|
||||
// a var() with `property_name`. This will resolve to the appropriate
|
||||
// custom property, local variable, or function argument. We also get
|
||||
// cycle handling for free.
|
||||
if (ResolveVarInto(property_name_stream, resolver, context, function_context,
|
||||
computed_data_sequence)) {
|
||||
computed_data = computed_data_sequence.BuildVariableData();
|
||||
}
|
||||
|
||||
if (resolver.InCycle()) {
|
||||
return KleeneValue::kFalse;
|
||||
}
|
||||
|
||||
LookupAndApply(property, resolver);
|
||||
CSSVariableData* computed = GetVariableData(property);
|
||||
|
||||
if (computed && computed->IsAttrTainted()) {
|
||||
if (computed_data && computed_data->IsAttrTainted()) {
|
||||
is_attr_tainted = true;
|
||||
}
|
||||
|
||||
if (!bounds.right.value.IsValid()) {
|
||||
return computed ? KleeneValue::kTrue : KleeneValue::kFalse;
|
||||
return computed_data ? KleeneValue::kTrue : KleeneValue::kFalse;
|
||||
}
|
||||
|
||||
const CSSValue& query_specified = bounds.right.value.GetCSSValue();
|
||||
|
||||
if (query_specified.IsCSSWideKeyword()) {
|
||||
return EvalIfKeyword(query_specified, computed, property)
|
||||
return EvalIfKeyword(query_specified, computed_data, property)
|
||||
? KleeneValue::kTrue
|
||||
: KleeneValue::kFalse;
|
||||
}
|
||||
|
||||
if (!computed) {
|
||||
if (!computed_data) {
|
||||
return KleeneValue::kFalse;
|
||||
}
|
||||
|
||||
@ -2127,9 +2136,7 @@ KleeneValue StyleCascade::EvalIfStyleFeature(
|
||||
CSSParserTokenStream decl_value_stream(
|
||||
decl_value.VariableDataValue()->OriginalText());
|
||||
TokenSequence substituted_token_sequence;
|
||||
// TODO(crbug.com/325504770): Take function context into account.
|
||||
if (!ResolveTokensInto(decl_value_stream, resolver, context,
|
||||
/* function_context */ nullptr,
|
||||
if (!ResolveTokensInto(decl_value_stream, resolver, context, function_context,
|
||||
/* stop_type */ kEOFToken,
|
||||
substituted_token_sequence)) {
|
||||
return KleeneValue::kFalse;
|
||||
@ -2158,7 +2165,7 @@ KleeneValue StyleCascade::EvalIfStyleFeature(
|
||||
is_attr_tainted = true;
|
||||
}
|
||||
|
||||
if (computed->EqualsIgnoringAttrTainting(*computed_query_data)) {
|
||||
if (computed_data->EqualsIgnoringAttrTainting(*computed_query_data)) {
|
||||
return KleeneValue::kTrue;
|
||||
}
|
||||
|
||||
@ -2168,6 +2175,7 @@ KleeneValue StyleCascade::EvalIfStyleFeature(
|
||||
bool StyleCascade::EvalIfCondition(CSSParserTokenStream& stream,
|
||||
CascadeResolver& resolver,
|
||||
const CSSParserContext& context,
|
||||
FunctionContext* function_context,
|
||||
bool& is_attr_tainted) {
|
||||
if (stream.Peek().Id() == CSSValueID::kElse) {
|
||||
stream.ConsumeIncludingWhitespace();
|
||||
@ -2185,21 +2193,23 @@ bool StyleCascade::EvalIfCondition(CSSParserTokenStream& stream,
|
||||
DCHECK_EQ(stream.Peek().GetType(), kColonToken);
|
||||
stream.ConsumeIncludingWhitespace();
|
||||
|
||||
return MediaEval(*exp_node, [this, &resolver, &context, &is_attr_tainted](
|
||||
return MediaEval(*exp_node, [this, &resolver, &context, &function_context,
|
||||
&is_attr_tainted](
|
||||
const MediaQueryFeatureExpNode& feature) {
|
||||
return EvalIfStyleFeature(feature, resolver, context,
|
||||
is_attr_tainted);
|
||||
function_context, is_attr_tainted);
|
||||
}) == KleeneValue::kTrue;
|
||||
}
|
||||
|
||||
bool StyleCascade::ResolveIfInto(CSSParserTokenStream& stream,
|
||||
CascadeResolver& resolver,
|
||||
const CSSParserContext& context,
|
||||
FunctionContext* function_context,
|
||||
TokenSequence& out) {
|
||||
stream.ConsumeWhitespace();
|
||||
bool is_attr_tainted = false;
|
||||
bool eval_result =
|
||||
EvalIfCondition(stream, resolver, context, is_attr_tainted);
|
||||
bool eval_result = EvalIfCondition(stream, resolver, context,
|
||||
function_context, is_attr_tainted);
|
||||
while (!eval_result) {
|
||||
stream.SkipUntilPeekedTypeIs<kSemicolonToken>();
|
||||
if (stream.AtEnd()) {
|
||||
@ -2211,11 +2221,11 @@ bool StyleCascade::ResolveIfInto(CSSParserTokenStream& stream,
|
||||
// None of the conditions matched, so should be IACVT.
|
||||
return false;
|
||||
}
|
||||
eval_result = EvalIfCondition(stream, resolver, context, is_attr_tainted);
|
||||
eval_result = EvalIfCondition(stream, resolver, context, function_context,
|
||||
is_attr_tainted);
|
||||
}
|
||||
TokenSequence if_result;
|
||||
if (!ResolveTokensInto(stream, resolver, context,
|
||||
/* function_context */ nullptr,
|
||||
if (!ResolveTokensInto(stream, resolver, context, function_context,
|
||||
/* stop_type */ kSemicolonToken, if_result)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -469,15 +469,18 @@ class CORE_EXPORT StyleCascade {
|
||||
bool ResolveIfInto(CSSParserTokenStream&,
|
||||
CascadeResolver&,
|
||||
const CSSParserContext&,
|
||||
FunctionContext*,
|
||||
TokenSequence&);
|
||||
|
||||
bool EvalIfCondition(CSSParserTokenStream& stream,
|
||||
CascadeResolver& resolver,
|
||||
const CSSParserContext& context,
|
||||
FunctionContext*,
|
||||
bool& is_attr_tainted);
|
||||
KleeneValue EvalIfStyleFeature(const MediaQueryFeatureExpNode&,
|
||||
CascadeResolver&,
|
||||
const CSSParserContext& context,
|
||||
FunctionContext*,
|
||||
bool& is_attr_tainted);
|
||||
bool EvalIfKeyword(const CSSValue& value,
|
||||
CSSVariableData* query_value,
|
||||
|
223
third_party/blink/web_tests/external/wpt/css/css-mixins/local-if-substitution.html
vendored
Normal file
223
third_party/blink/web_tests/external/wpt/css/css-mixins/local-if-substitution.html
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<title>Custom Functions: Local substitution of var() in if()</title>
|
||||
<link rel="help" href="https://drafts.csswg.org/css-mixins-1/#locally-substitute-a-var">
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="resources/utils.js"></script>
|
||||
|
||||
<div id=target data-x="var(--x)" data-f="--f()"></div>
|
||||
<div id=main></div>
|
||||
|
||||
<!-- To pass, a test must produce matching computed values for --actual and
|
||||
--expected on #target. -->
|
||||
|
||||
<template data-name="var() in if() condition's custom property value substitutes locally">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: 3px;
|
||||
result: if(style(--x: 3px): PASS; else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--x: 1px;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="var() in if() condition's specified value substitutes locally">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: 3px;
|
||||
result: if(style(--y: var(--x)): PASS; else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--x: 1px;
|
||||
--y: 3px;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="var() in if() declaration value substitutes locally">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: PASS;
|
||||
result: if(style(--true): var(--x); else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--true: true;
|
||||
--x: FAIL;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="var() in if() condition's custom property value substitutes locally, argument">
|
||||
<style>
|
||||
@function --f(--x) {
|
||||
result: if(style(--x: 3px): PASS; else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--x: 1px;
|
||||
--actual: --f(3px);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="var() in if() condition's specified value substitutes locally, argument">
|
||||
<style>
|
||||
@function --f(--x) {
|
||||
result: if(style(--y: var(--x)): PASS; else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--x: 1px;
|
||||
--y: 3px;
|
||||
--actual: --f(3px);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="var() in if() declaration value substitutes locally, argument">
|
||||
<style>
|
||||
@function --f(--x) {
|
||||
result: if(style(--true): var(--x); else: FAIL;);
|
||||
}
|
||||
#target {
|
||||
--true: true;
|
||||
--x: FAIL;
|
||||
--actual: --f(PASS);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="dashed function in if() declaration value">
|
||||
<style>
|
||||
@function --f() {
|
||||
result: if(style(--true): --g(); else: FAIL;);
|
||||
}
|
||||
@function --g() {
|
||||
result: PASS;
|
||||
}
|
||||
#target {
|
||||
--true: true;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="dashed function with argument in if() declaration value">
|
||||
<style>
|
||||
@function --f(--x) {
|
||||
--true: true;
|
||||
result: if(style(--true): --g(var(--x)); else: FAIL;);
|
||||
}
|
||||
@function --g(--x) {
|
||||
result: var(--x, FAIL);
|
||||
}
|
||||
#target {
|
||||
--actual: --f(PASS);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() cycle through local">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: if(style(--true): var(--x); else: FAIL;);
|
||||
result: var(--x, PASS);
|
||||
}
|
||||
#target {
|
||||
--true: true;
|
||||
--x: FAIL;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() cycle in condition custom property through local">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: if(style(--x): FAIL1; else: FAIL2;);
|
||||
result: var(--x, PASS);
|
||||
}
|
||||
#target {
|
||||
--x: 1px;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() cycle in condition specified value through local">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: if(style(--y: var(--x)): FAIL1; else: FAIL2;);
|
||||
result: var(--x, PASS);
|
||||
}
|
||||
#target {
|
||||
--y: 1px;
|
||||
--x: 1px;
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() cycle through function">
|
||||
<style>
|
||||
@function --f() {
|
||||
--local: --g();
|
||||
result: var(--local);
|
||||
}
|
||||
@function --g() {
|
||||
result: if(style(--true): --f());
|
||||
}
|
||||
#target {
|
||||
--true: true;
|
||||
--local: FAIL;
|
||||
--tmp: --f();
|
||||
--actual: var(--tmp, PASS);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() no cycle in overridden local">
|
||||
<style>
|
||||
@function --f() {
|
||||
--x: 3px;
|
||||
result: if(style(--x): PASS; else: FAIL);
|
||||
}
|
||||
#target {
|
||||
--x: var(--x);
|
||||
--actual: --f();
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<template data-name="if() no cycle in overridden argument">
|
||||
<style>
|
||||
@function --f(--x) {
|
||||
result: if(style(--x): PASS; else: FAIL);
|
||||
}
|
||||
#target {
|
||||
--x: var(--x);
|
||||
--actual: --f(3px);
|
||||
--expected: PASS;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
test_all_templates();
|
||||
</script>
|
Reference in New Issue
Block a user