Add counter-reset, counter-increment properties with scope-based counter tracking during box tree construction. Implement counter(), counters(), and quote keywords (open-quote, close-quote, no-open-quote, no-close-quote) in the content property. Includes code review fixes: removed dead code in counter parser, eliminated wasteful allocation in counter formatting, added counter properties to ComputedStyles::dump(). 4 golden tests added, 2 WPT tests promoted to pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
270 lines
22 KiB
Markdown
270 lines
22 KiB
Markdown
# Story 1.4: Generated Content
|
|
|
|
Status: done
|
|
|
|
## Story
|
|
|
|
As a web user,
|
|
I want CSS-generated content (bullets, quotes, labels) to render correctly,
|
|
so that pages using `::before` and `::after` pseudo-elements display as intended.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. **Given** a CSS rule with `::before` or `::after` and a `content` property with a string value, **When** the page is rendered, **Then** the string content is inserted as an inline box before/after the element's content per CSS 2.1 §12.1
|
|
2. **Given** a `content` value using `attr()`, **When** the page is rendered, **Then** the attribute value from the HTML element is inserted as generated content
|
|
3. **Given** a `content` value using `counter()` with `counter-reset` and `counter-increment`, **When** the page is rendered, **Then** the counter value is computed and displayed per CSS 2.1 §12.4
|
|
4. **Given** generated content with styling (color, font-size, display), **When** the page is rendered, **Then** the pseudo-element is styled independently from its originating element
|
|
5. Golden tests cover string content, attr(), counter(), checklist is updated, and `just ci` passes
|
|
|
|
## Tasks / Subtasks
|
|
|
|
> **NOTE:** AC #1, #2, and #4 are **already implemented** and passing. This story focuses on completing AC #3 (counters) and adding missing content value types.
|
|
|
|
- [x] Task 1: `counter-reset` and `counter-increment` CSS property parsing (AC: #3)
|
|
- [x] 1.1 Add `PropertyId::CounterReset` and `PropertyId::CounterIncrement` variants in `crates/css/src/types.rs` (~line 348-487, PropertyId enum)
|
|
- [x] 1.2 Add `CssValue` variants for counter properties: `CssValue::CounterReset(Vec<(String, i32)>)` and `CssValue::CounterIncrement(Vec<(String, i32)>)` — each is a list of (counter-name, integer) pairs per CSS 2.1 §12.4
|
|
- [x] 1.3 Implement `parse_counter_reset()` in `crates/css/src/parser/mod.rs` — syntax: `counter-reset: <identifier> <integer>?` (default 0), supports multiple counters, `none` keyword
|
|
- [x] 1.4 Implement `parse_counter_increment()` in `crates/css/src/parser/mod.rs` — syntax: `counter-increment: <identifier> <integer>?` (default 1), supports multiple counters, `none` keyword
|
|
- [x] 1.5 Wire both properties into the main property dispatch in parser
|
|
- [x] 1.6 Unit tests for counter property parsing: single counter, counter with value, multiple counters, `none`, invalid values
|
|
|
|
- [x] Task 2: `counter()` and `counters()` in content property parsing (AC: #3)
|
|
- [x] 2.1 Add `ContentItem::Counter(String, ListStyleType)` variant in `crates/css/src/types.rs` — counter name + optional list-style-type (default decimal)
|
|
- [x] 2.2 Add `ContentItem::Counters(String, String, ListStyleType)` variant — counter name + separator string + optional list-style-type
|
|
- [x] 2.3 Extend `parse_content_value()` in `crates/css/src/parser/mod.rs` (~line 1670-1730) to parse `counter(<identifier>)`, `counter(<identifier>, <list-style-type>)`, `counters(<identifier>, <string>)`, `counters(<identifier>, <string>, <list-style-type>)`
|
|
- [x] 2.4 Unit tests for counter/counters parsing in `crates/css/src/tests/content_tests.rs`
|
|
|
|
- [x] Task 3: Counter state tracking in style computation (AC: #3)
|
|
- [x] 3.1 Add `counter_reset: Vec<(String, i32)>` and `counter_increment: Vec<(String, i32)>` fields to `ComputedStyles` in `crates/style/src/types/computed.rs`
|
|
- [x] 3.2 Wire cascade resolution for `counter-reset` and `counter-increment` in `crates/style/src/types/computed.rs` (apply_declaration)
|
|
- [x] 3.3 Implement counter scope tracking: create a `CounterContext` struct in `crates/layout/src/engine/counter.rs` that maintains a stack of counter scopes. Per CSS 2.1 §12.4: `counter-reset` creates a new counter instance in the current scope, `counter-increment` increments the innermost counter with that name
|
|
- [x] 3.4 `CounterContext` lives in layout crate as a `RefCell` field on `LayoutEngine` — counters are evaluated during box tree construction in document order
|
|
- [x] 3.5 Unit tests for counter scope creation, increment, and nesting
|
|
|
|
- [x] Task 4: Counter value resolution in generated content (AC: #3)
|
|
- [x] 4.1 During box tree construction in `crates/layout/src/engine/box_tree.rs`, when processing `ContentItem::Counter(name, style)`, look up the counter value from `CounterContext` and format it using the list-style-type formatter
|
|
- [x] 4.2 For `ContentItem::Counters(name, separator, style)`, collect all counter instances with that name (from outermost to innermost scope), format each, and join with separator
|
|
- [x] 4.3 Reuse existing `ListStyleType` formatting logic from list marker generation via `format_list_marker` in `crates/layout/src/engine/list_marker.rs`
|
|
- [x] 4.4 Wire counter context through `build_box_tree()` and `build_pseudo_element_box()` in `box_tree.rs`
|
|
- [x] 4.5 Unit tests for counter value lookup and formatting
|
|
|
|
- [x] Task 5: Quote keywords in content property (AC: #1, completes content support)
|
|
- [x] 5.1 Add `ContentItem::OpenQuote`, `ContentItem::CloseQuote`, `ContentItem::NoOpenQuote`, `ContentItem::NoCloseQuote` variants in `crates/css/src/types.rs`
|
|
- [x] 5.2 Extend `parse_content_value()` to recognize `open-quote`, `close-quote`, `no-open-quote`, `no-close-quote` keywords
|
|
- [x] 5.3 Implement quote depth tracking (integer depth, starts at 0) — `open-quote` inserts `\u201C` at even depth, `\u2018` at odd depth
|
|
- [x] 5.4 Wire through style computation and box tree building
|
|
- [x] 5.5 Unit tests for quote keyword parsing and depth tracking
|
|
|
|
- [x] Task 6: Golden tests and checklist update (AC: #5)
|
|
- [x] 6.1 Add golden test 227: `counter-reset` + `counter-increment` with `content: counter(name)` — numbered headings
|
|
- [x] 6.2 Add golden test 228: nested counters with `counters()` and separator — "1.1", "1.2" section numbering
|
|
- [x] 6.3 Add golden test 229: counters with `list-style-type: upper-roman` — I, II, III, IV
|
|
- [x] 6.4 Add golden test 230: quote keywords generating Unicode quote characters
|
|
- [x] 6.5 Verify existing golden test 207 (content: attr()) still passes
|
|
- [x] 6.6 Update `docs/CSS2.1_Implementation_Checklist.md` — checked off: counter-reset, counter-increment, counter(), counters(), quote keywords in content
|
|
- [x] 6.7 Run `just ci` and all tests pass (2 WPT tests promoted from known_fail to pass)
|
|
|
|
## Dev Notes
|
|
|
|
### Current Implementation Status
|
|
|
|
Generated content is **substantially implemented**. What works end-to-end:
|
|
- **`::before` and `::after` pseudo-elements** — full pipeline: selector parsing → matching → style computation → box generation → layout → paint
|
|
- **`content: "string"`** — single and multiple strings, concatenation
|
|
- **`content: attr(name)`** — attribute value resolution at style computation time, multiple attr() items
|
|
- **`content: ""`** — empty string generates box with no text child (clearfix pattern)
|
|
- **`content: normal` / `content: none`** — correctly suppress box generation
|
|
- **Pseudo-element styling** — all CSS properties applied independently, cascade/specificity correct
|
|
- **Display types** — pseudo-elements support block, inline, inline-block, flex, table, etc.
|
|
- **Replaced element exclusion** — img, input, etc. properly excluded from pseudo-element generation
|
|
- **`::first-line` and `::first-letter`** — implemented with proper property filtering
|
|
|
|
What is **missing** (this story's scope):
|
|
- **`counter-reset`** — PropertyId not defined, no parsing, no cascade
|
|
- **`counter-increment`** — PropertyId not defined, no parsing, no cascade
|
|
- **`counter()` function** — parser rejects it (drops declaration)
|
|
- **`counters()` function** — parser rejects it
|
|
- **Quote keywords** — `open-quote`, `close-quote`, `no-open-quote`, `no-close-quote` not parsed
|
|
- **`url()` in content** — not parsed (low priority, rarely used in CSS 2.1)
|
|
|
|
### Key Code Locations
|
|
|
|
| Component | File | Key Functions/Lines |
|
|
|---|---|---|
|
|
| PseudoElement enum | `crates/selectors/src/types.rs:162-172` | `Before`, `After`, `FirstLine`, `FirstLetter` |
|
|
| ContentValue/ContentItem types | `crates/css/src/types.rs:230-249` | `ContentValue { Normal, None, String, Items }`, `ContentItem { String, Attr }` |
|
|
| PropertyId enum | `crates/css/src/types.rs:348-487` | Needs `CounterReset`, `CounterIncrement` added |
|
|
| Content parsing | `crates/css/src/parser/mod.rs:1670-1730` | `parse_content_value()` — extend for counter()/counters()/quotes |
|
|
| Content tests | `crates/css/src/tests/content_tests.rs` | 610 lines, comprehensive string/attr tests |
|
|
| ComputedStyles struct | `crates/style/src/types/computed.rs:154-162` | `content`, `before_styles`, `after_styles` fields |
|
|
| Pseudo-element style computation | `crates/style/src/context.rs:1100-1120` | `compute_pseudo_element_styles()` — attr() resolution here |
|
|
| Style tests | `crates/style/src/tests/pseudo_element.rs` | 1011 lines of cascade/inheritance/attr tests |
|
|
| Box tree pseudo-element insertion | `crates/layout/src/engine/box_tree.rs:160-200` | ::before as first child, ::after as last child |
|
|
| `build_pseudo_element_box()` | `crates/layout/src/engine/box_tree.rs:435-506` | Display type conversion, text content creation, style copying |
|
|
| Layout pseudo-element tests | `crates/layout/src/tests/pseudo_element_tests.rs` | 729 lines |
|
|
| ListStyleType enum | `crates/style/src/types/primitives.rs` | Existing enum for list markers — reuse for counter formatting |
|
|
| List marker generation | `crates/layout/src/engine/box_tree.rs` | Existing list-item marker box generation — pattern for counter rendering |
|
|
| CSS 2.1 Checklist | `docs/CSS2.1_Implementation_Checklist.md:184-192` | Phase 14 (Lists & Counters) — counter items unchecked |
|
|
| Golden test 207 | `tests/goldens/fixtures/207-content-attr.html` | Existing ::after with attr() test |
|
|
|
|
### Implementation Approach
|
|
|
|
**Task 1+2 (Counter property + content function parsing):**
|
|
Follow the CSS Property Implementation Order: parse first. Add `PropertyId::CounterReset` and `PropertyId::CounterIncrement` to the PropertyId enum. Add parsing functions similar to existing property parsers in `crates/css/src/parser/mod.rs`. The syntax is: `counter-reset: <ident> <integer>? [, <ident> <integer>?]*` where default reset value is 0 and default increment is 1. For content parsing, extend `parse_content_value()` to handle `counter(<ident>[, <list-style-type>])` and `counters(<ident>, <string>[, <list-style-type>])`.
|
|
|
|
**Task 3 (Counter state tracking):**
|
|
This is the most architecturally complex part. CSS counters have scope rules (§12.4):
|
|
- `counter-reset` creates a new counter in the element's scope
|
|
- `counter-increment` increments the innermost counter with that name
|
|
- Counters are inherited — children see parent counters
|
|
- The tree traversal order matters (document order)
|
|
|
|
Best approach: Create a `CounterContext` that is passed through box tree construction. It maintains a `HashMap<String, Vec<i32>>` where the Vec represents nested scopes (stack). When processing an element:
|
|
1. If it has `counter-reset: foo 0`, push a new scope for "foo" with value 0
|
|
2. If it has `counter-increment: foo 1`, add 1 to the innermost "foo"
|
|
3. When leaving the element's subtree, pop its counter scopes
|
|
|
|
This should live in `crates/layout/src/engine/box_tree.rs` since it's needed during box construction where content values are resolved.
|
|
|
|
**Task 4 (Counter value resolution):**
|
|
When `build_pseudo_element_box()` encounters `ContentItem::Counter(name, style)`, look up the current value of counter `name` from the `CounterContext`. Format using the `ListStyleType` — the codebase already has list marker formatting. For `ContentItem::Counters(name, sep, style)`, collect all scope values for that counter name and join with the separator string.
|
|
|
|
**Task 5 (Quote keywords):**
|
|
Simpler than counters. Track a `quote_depth: i32` in the context. `open-quote` inserts `"` (depth 0, 2, 4...) or `'` (depth 1, 3, 5...) and increments depth. `close-quote` decrements depth and inserts the closing character. `no-open-quote` / `no-close-quote` affect depth without inserting text.
|
|
|
|
### Architecture Constraints
|
|
|
|
- **Layer rule:** Changes span `css` (Layer 1), `style` (Layer 1), `layout` (Layer 1) — all horizontal Layer 1 deps
|
|
- **No unsafe:** All affected crates have `unsafe_code = "forbid"`
|
|
- **Pipeline order:** CSS parse → style compute → layout (box tree build) → display list → render. Counter values resolve during box tree construction (layout phase), not during style computation.
|
|
- **CSS Property Implementation Order:** Parse `counter-reset`/`counter-increment` in `css/` → add to `ComputedStyles` in `style/` → resolve counter values during box tree build in `layout/` → golden tests → checklist → `just ci`
|
|
- **Reuse existing infrastructure:** Use `ListStyleType` enum and formatting from list marker code for counter value formatting. Do NOT create a parallel formatting system.
|
|
|
|
### Previous Story Intelligence
|
|
|
|
**From Story 1.1 (Margin Collapsing):**
|
|
- Golden test infrastructure: fixtures in `tests/goldens/fixtures/`, expected in `tests/goldens/expected/`
|
|
- Unit tests pattern: construct DOM + style manually, run layout, assert positions
|
|
- Checklist update at `docs/CSS2.1_Implementation_Checklist.md` is mandatory
|
|
- `just ci` is the single validation gate
|
|
|
|
**From Story 1.2 (Stacking Contexts & Z-Index):**
|
|
- Display list builder is paint order engine — generated content boxes participate normally in paint
|
|
- Pseudo-element boxes are regular LayoutBox children — they follow all normal paint rules
|
|
|
|
**From Story 1.3 (Positioning):**
|
|
- Positioned pseudo-elements (`::before { position: absolute; }`) follow normal positioning rules
|
|
- Pseudo-element boxes go through the same layout dispatch as regular boxes
|
|
|
|
### Counter Scope Rules (CSS 2.1 §12.4 — Critical)
|
|
|
|
These rules MUST be implemented correctly:
|
|
|
|
1. **Scope creation:** `counter-reset` on an element creates a new counter instance. If a counter with that name already exists in the current scope, a NEW instance is created (nesting).
|
|
2. **Incrementing:** `counter-increment` affects the innermost counter with that name. If no counter exists, one is implicitly created at the element with `counter-reset: name 0` and then incremented.
|
|
3. **Inheritance:** Child elements see parent counters. The counter value at any point is the sum of all increments since the last reset in the current scope.
|
|
4. **Self-nesting:** `<ol>` elements automatically nest counters — each `<ol>` resets, each `<li>` increments. This produces "1, 2, 3" at level 1 and "1, 2" at level 2, not "4, 5".
|
|
5. **`counters()` function:** Collects ALL instances of a named counter from outermost to innermost scope. With separator ".", produces "1.2.3" for nested lists.
|
|
6. **Processing order:** Elements are processed in document order (depth-first tree traversal). The counter-reset and counter-increment on an element are processed BEFORE the element's content (::before sees the incremented value).
|
|
|
|
### Testing Strategy
|
|
|
|
1. **CSS parser tests** in `crates/css/src/tests/content_tests.rs` — extend existing test file with counter()/counters() parsing tests
|
|
2. **Counter property tests** — new tests for `counter-reset` and `counter-increment` parsing
|
|
3. **Counter scope tests** — unit tests for `CounterContext` scope creation, increment, nesting, popping
|
|
4. **Integration golden tests** — key scenarios:
|
|
- Simple numbered headings: `h2 { counter-increment: section; } h2::before { content: counter(section) ". "; }`
|
|
- Nested counters: `ol { counter-reset: item; } li { counter-increment: item; } li::before { content: counters(item, ".") " "; }`
|
|
- Counter with non-decimal format: `content: counter(section, upper-roman)`
|
|
- Quote characters: `q::before { content: open-quote; } q::after { content: close-quote; }`
|
|
5. **Regression check:** Verify golden test 207 (attr()) and all existing pseudo-element tests still pass
|
|
6. Run `just ci` at the end
|
|
|
|
### CSS 2.1 Spec References
|
|
|
|
- **§12.1** — The `content` property: values, applicability to ::before/::after
|
|
- **§12.2** — The `quotes` property: specifying quote pairs for each nesting level
|
|
- **§12.3** — Inserting quotes with `content`: open-quote, close-quote, no-open-quote, no-close-quote
|
|
- **§12.4** — Automatic counters and numbering: counter-reset, counter-increment, counter(), counters()
|
|
- **§12.4.1** — Nested counters and scope
|
|
- **§12.4.2** — Counter styles (list-style-type values for formatting)
|
|
- **§12.5** — Lists: list-style-type, list-style-position, list-style-image (related but separate story 1.6)
|
|
|
|
### Project Structure Notes
|
|
|
|
- New CSS properties (`counter-reset`, `counter-increment`) follow: parse in `css/` → computed in `style/` → resolve in `layout/`
|
|
- `CounterContext` struct created in `crates/layout/` (not `style/`) because counter values resolve during box tree construction
|
|
- New `ContentItem` variants added to `crates/css/src/types.rs`
|
|
- Reuse `ListStyleType` from `crates/style/src/types/primitives.rs` for counter formatting
|
|
- New golden test fixtures go in `tests/goldens/fixtures/` with next available number
|
|
- Checklist update in `docs/CSS2.1_Implementation_Checklist.md` (Phase 14 counter items + Phase 15 content items)
|
|
|
|
### References
|
|
|
|
- [Source: crates/css/src/types.rs#ContentValue] — Content property value types (extend with counter/quote variants)
|
|
- [Source: crates/css/src/parser/mod.rs#parse_content_value] — Content property parser (~L1670-1730)
|
|
- [Source: crates/css/src/tests/content_tests.rs] — Existing content parsing tests (610 lines)
|
|
- [Source: crates/style/src/types/computed.rs#ComputedStyles] — Computed styles struct (add counter fields)
|
|
- [Source: crates/style/src/context.rs#compute_pseudo_element_styles] — Pseudo-element style computation
|
|
- [Source: crates/style/src/tests/pseudo_element.rs] — Pseudo-element style tests (1011 lines)
|
|
- [Source: crates/layout/src/engine/box_tree.rs#build_pseudo_element_box] — Pseudo-element box generation (~L435-506)
|
|
- [Source: crates/layout/src/tests/pseudo_element_tests.rs] — Layout pseudo-element tests (729 lines)
|
|
- [Source: crates/selectors/src/types.rs#PseudoElement] — PseudoElement enum definition
|
|
- [Source: docs/CSS2.1_Implementation_Checklist.md#Phase-14] — Lists & Counters checklist items
|
|
- [Source: tests/goldens/fixtures/207-content-attr.html] — Existing generated content golden test
|
|
|
|
## Dev Agent Record
|
|
|
|
### Agent Model Used
|
|
|
|
Claude Opus 4.6 (1M context)
|
|
|
|
### Debug Log References
|
|
|
|
None — clean implementation with no blocking issues.
|
|
|
|
### Completion Notes List
|
|
|
|
- Implemented full CSS 2.1 §12.4 counter support: `counter-reset`, `counter-increment`, `counter()`, `counters()` functions
|
|
- Implemented CSS 2.1 §12.3 quote keywords: `open-quote`, `close-quote`, `no-open-quote`, `no-close-quote`
|
|
- Created `CounterContext` struct in layout crate for scope-based counter tracking during document-order traversal
|
|
- Counter values resolve during box tree construction (layout phase), not style computation
|
|
- Reused existing `format_list_marker()` for counter value formatting (decimal, lower-alpha, upper-alpha, lower-roman, upper-roman)
|
|
- Quote depth tracking uses Unicode quotation marks: `\u201C`/`\u201D` for outer, `\u2018`/`\u2019` for inner
|
|
- Modified style computation to preserve `ContentValue::Items` through to layout when items contain dynamic content (counters/quotes)
|
|
- Re-exported `ContentItem` from style crate for layout crate access
|
|
- 2 WPT tests promoted from `known_fail` to `pass` (counter-increment-applies-to-011, counter-reset-applies-to-011)
|
|
- All 744 CSS tests pass, 672 layout tests pass, 4 new golden tests pass, existing golden test 207 passes
|
|
- `just ci` passes fully
|
|
|
|
### Change Log
|
|
|
|
- 2026-03-13: Implemented CSS counters, quote keywords, and generated content enhancements (Story 1.4)
|
|
|
|
### File List
|
|
|
|
- `crates/css/src/types.rs` — Added `PropertyId::CounterReset`, `PropertyId::CounterIncrement`, `CssValue::CounterReset`, `CssValue::CounterIncrement`, `ContentItem::Counter`, `ContentItem::Counters`, `ContentItem::OpenQuote`, `ContentItem::CloseQuote`, `ContentItem::NoOpenQuote`, `ContentItem::NoCloseQuote`
|
|
- `crates/css/src/parser/mod.rs` — Added `parse_counter_reset()`, `parse_counter_increment()`, `parse_counter_property()`, `parse_content_counter()`, `parse_content_counters()`, extended `parse_content_value()` for counter/quote keywords, wired counter properties into `parse_value_for_property()`
|
|
- `crates/css/src/tests/content_tests.rs` — Added 18 new tests for counter/counters/quote parsing; updated test_parse_content_unknown_ident_ignored → test_parse_content_counter_produces_declaration
|
|
- `crates/style/src/types/computed.rs` — Added `counter_reset` and `counter_increment` fields to `ComputedStyles`, wired cascade in `apply_declaration()`
|
|
- `crates/style/src/context.rs` — Updated `compute_pseudo_element_styles()` to handle `ContentValue::Items` with dynamic content, return pseudo-element styles for Items
|
|
- `crates/style/src/lib.rs` — Re-exported `ContentItem` from css crate
|
|
- `crates/layout/src/engine/counter.rs` — New file: `CounterContext` struct with scope tracking, `format_counter_value()`, `quote_char()`, unit tests
|
|
- `crates/layout/src/engine/mod.rs` — Added `counter_context: RefCell<CounterContext>` to `LayoutEngine`, reset on each layout pass
|
|
- `crates/layout/src/engine/box_tree.rs` — Process counter-reset/increment during box tree construction, resolve counter/quote content items in `resolve_content_items()`, handle `ContentValue::Items` in `build_pseudo_element_box()`
|
|
- `tests/goldens.rs` — Added golden tests 227-230
|
|
- `tests/goldens/fixtures/227-counter-basic.html` — New fixture
|
|
- `tests/goldens/fixtures/228-counters-nested.html` — New fixture
|
|
- `tests/goldens/fixtures/229-counter-upper-roman.html` — New fixture
|
|
- `tests/goldens/fixtures/230-quote-keywords.html` — New fixture
|
|
- `tests/goldens/expected/227-counter-basic.layout.txt` — New expected output
|
|
- `tests/goldens/expected/227-counter-basic.dl.txt` — New expected output
|
|
- `tests/goldens/expected/228-counters-nested.layout.txt` — New expected output
|
|
- `tests/goldens/expected/228-counters-nested.dl.txt` — New expected output
|
|
- `tests/goldens/expected/229-counter-upper-roman.layout.txt` — New expected output
|
|
- `tests/goldens/expected/229-counter-upper-roman.dl.txt` — New expected output
|
|
- `tests/goldens/expected/230-quote-keywords.layout.txt` — New expected output
|
|
- `tests/goldens/expected/230-quote-keywords.dl.txt` — New expected output
|
|
- `docs/CSS2.1_Implementation_Checklist.md` — Checked off counter and generated content items
|
|
- `tests/external/wpt/wpt_manifest.toml` — Promoted 2 counter WPT tests from known_fail to pass
|