0

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:
Munira Tursunova
2025-02-17 05:43:40 -08:00
committed by Chromium LUCI CQ
parent 95a5bbf538
commit 2b59535eff
3 changed files with 258 additions and 22 deletions
third_party/blink
renderer
web_tests
external

@ -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,

@ -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>