fune/testing/web-platform/tests/css/css-variables/variable-cycles.html
Anders Hartvoll Ruud fcca23c04b Bug 1698735 [wpt PR 28035] - Handle secondary edges in custom property cycles, a=testonly
Automatic update from web-platform-tests
Handle secondary edges in custom property cycles

The cycle detection in StyleCascade is not correct. Any secondary edges
coming from a cycle are also detected as in-cycle, even if they aren't.

Explanation of how the new cycle detection works:

- AutoLock is used to "lock" a property that's currently being resolved.
  - It's instantiated at the start of ResolveCustomProperty,
    ResolveVariableReference, and ResolvePendingSubstitution. No other
    places.
  - All "lock" means is that the property we're resolving is pushed to
    CascadeResolver.stack_.
- When we're trying to resolve a var(), a cycle is detected by looking
  for a given property in that stack. If the property is found, the
  stack index is set as the cycle_start_, and the current size of the
  stack is set as the cycle_end_. The idea is that all the properties
  in the range [cycle_start_, cycle_end_) are in a cycle, and are
  therefore invalid.
- As we come back up the regular call stack, we check the InCycle flag
  to see if a (nested) variable resolution has detected a cycle, and if
  we're still inside it.

Where the old code went wrong, was that we assumed that once a cycle was
detected, we would not push anything new onto CascadeResolver.stack_
until we were out of the cycle. Hence, once a cycle was detected,
anything reachable from the start of the cycle would be detected as
in-cycle.

This is regression from the StyleCascade project (since M82), although
nobody reported the issue.

Unfortunately this exposed that two tests relied on the buggy cycle
detection. These tests have been temporarily disabled/rebaselined.

Change-Id: If7bbaad084c001c4f3349a7a4050b0a5bd33139b
Fixed: 1187282
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2749195
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Reviewed-by: Xiaocheng Hu <xiaochengh@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#862369}

--

wpt-commits: 678cbc9eb44fcc4b303b076cf758e96295d8fee3
wpt-pr: 28035
2021-03-19 10:35:10 +00:00

419 lines
9.9 KiB
HTML

<!DOCTYPE html>
<meta charset="utf8">
<title>Test that custom property cycles behave correctly</title>
<link rel="help" href="https://drafts.csswg.org/css-variables/#cycles">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<main id=main></main>
<script>
// Test that, for the given list of |declarations|, the computed values
// of properties listed in |expected_invalid| are invalid (i.e. empty string),
// and the computed values listed in |expected_valid| are *not* invalid
// (i.e. not the empty string).
function test_cycles(declarations, expected_invalid, expected_valid, description) {
test(() => {
let element = document.createElement('div');
try {
declarations.push('--sanity:valid');
element.style = declarations.join(';');
main.append(element);
let cs = getComputedStyle(element);
for (let e of expected_invalid)
assert_equals(cs.getPropertyValue(e), '', `${e}`);
for (let e of expected_valid)
assert_not_equals(cs.getPropertyValue(e), '', `${e}`);
assert_equals(cs.getPropertyValue('--sanity'), 'valid', '--sanity');
} finally {
element.remove();
}
}, description);
}
// (Diagrams produced with graph-easy).
// ┌───┐
// │ │ ───┐
// │ a │ │
// │ │ ◀──┘
// └───┘
test_cycles(
['--a:var(--a)'],
['--a'],
[],
'Self-cycle');
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ b │ ─┘
// └───┘
test_cycles(
[
'--a:var(--b)',
'--b:var(--a)',
],
['--a', '--b'],
[],
'Simple a/b cycle');
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ b │ │
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ c │ ─┘
// └───┘
test_cycles(
[
'--a:var(--b, cycle)',
'--b:var(--c, cycle)',
'--c:var(--a, cycle)',
],
['--a', '--b', '--c'],
[],
'Three-var cycle');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ y │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ b │ │
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ c │ ─┘
// └───┘
test_cycles(
[
'--x:var(--y, valid)',
'--y:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle)',
'--c:var(--a, cycle)',
],
['--a', '--b', '--c'],
['--x', '--y'],
'Cycle that starts in the middle of a chain');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ b │ │
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ c │ ─┘
// └───┘
// │
// │
// ▼
// ┌───┐
// │ y │
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle)',
'--c:var(--a, cycle) var(--y)',
'--y:valid'
],
['--a', '--b', '--c'],
['--x', '--y'],
'Cycle with extra edge');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ ┌───┐ │
// │ y │ ◀── │ b │ │
// └───┘ └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ c │ ─┘
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle) var(--y)',
'--c:var(--a, cycle)',
'--y:valid'
],
['--a', '--b', '--c'],
['--x', '--y'],
'Cycle with extra edge (2)');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ b │ │
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ c │ ─┘
// └───┘
// │
// │
// ▼
// ┌───┐
// │ y │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ z │
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle)',
'--c:var(--a, cycle) var(--y)',
'--y:var(--z)',
'--z:valid'
],
['--a', '--b', '--c'],
['--x', '--y', '--z'],
'Cycle with extra edge (3)');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// ┌▶ │ b │ ─┘
// │ └───┘
// │ │
// │ │
// │ ▼
// │ ┌───┐
// │ │ c │
// │ └───┘
// │ │
// │ │
// │ ▼
// │ ┌───┐
// └─ │ d │
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle) var(--a, cycle)',
'--c:var(--d, cycle)',
'--d:var(--b, cycle)',
],
['--a', '--b', '--c', '--d'],
['--x'],
'Cycle with secondary cycle');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ a │ ◀┐
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// ┌▶ │ b │ │
// │ └───┘ │
// │ │ │
// │ │ │
// │ ▼ │
// │ ┌───┐ │
// │ │ c │ ─┘
// │ └───┘
// │ │
// │ │
// │ ▼
// │ ┌───┐
// └─ │ d │
// └───┘
// │
// │
// ▼
// ┌───┐
// │ y │
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle)',
'--b:var(--c, cycle)',
'--c:var(--d, cycle) var(--a, cycle)',
'--d:var(--b, cycle) var(--y)',
'--y:valid'
],
['--a', '--b', '--c', '--d'],
['--x', '--y'],
'Cycle with overlapping secondary cycle');
// ┌──────────────┐
// │ │
// │ ┌───┐ │
// │ │ x │ │
// │ └───┘ │
// │ │ │
// │ │ │
// │ ▼ ▼
// ┌───┐ ┌────────┐ ┌───┐
// │ b │ ◀── │ a │ ──▶ │ y │
// └───┘ └────────┘ └───┘
// │ ▲
// │ │
// ▼ │
// ┌───┐ │
// │ c │ │
// └───┘ │
// │ │
// │ │
// ▼ │
// ┌───┐ │
// │ d │ ─┘
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--b, cycle) var(--y, valid) var(--c, cycle)',
'--b:var(--a, cycle) ',
'--c:var(--d, cycle)',
'--d:var(--a, cycle)',
'--y:valid',
],
['--a', '--b', '--c', '--d'],
['--x', '--y'],
'Cycle with deeper secondary cycle');
// ┌───┐
// │ x │
// └───┘
// │
// │
// ▼
// ┌───┐
// ┌─────▶ │ a │ ─┐
// │ └───┘ │
// │ │ │
// │ │ │
// │ ▼ │
// │ ┌───┐ │
// │ ┌─ │ b │ │
// │ │ └───┘ │
// │ │ │ │
// │ │ │ │
// │ │ ▼ │
// │ │ ┌───┐ │
// └────┼─ │ c │ │
// │ └───┘ │
// │ │ │
// │ │ │
// │ ▼ │
// │ ┌───┐ │
// └▶ │ y │ ◀┘
// └───┘
test_cycles(
[
'--x:var(--a, valid)',
'--a:var(--y, var(--b, cycle))',
'--b:var(--y, var(--c, cycle))',
'--c:var(--y, var(--a, cycle))',
'--y:valid'
],
['--a', '--b', '--c'],
['--x', '--y'],
'Cycle via fallback');
</script>