Skip to content

Panic in resolve_stylist: "invalid key" when animation node tracked but removed #407

@yes1688

Description

@yes1688

Version: blitz 0.3.0-alpha.2 (blitz-dom)
Platform: Linux (Vulkan/Wayland), likely cross-platform — bug is in Slab indexing logic.

Repro

use blitz_dom::{DocumentConfig, Document as _};
use blitz_html::HtmlDocument;

fn main() {
    let html = r#"
        <style>
          @keyframes pulse { 0% { opacity: 1; } 100% { opacity: 0.5; } }
          .pulse { animation: pulse 2s infinite; }
        </style>
        <div id="pulse-node" class="pulse">animated</div>
    "#;

    let mut doc = HtmlDocument::from_html(html, DocumentConfig::default());

    // First resolve — animation entry is registered for #pulse-node in animations.sets.
    doc.as_mut().resolve(0.0);

    // Remove the animated element (its slab key is now freed).
    let pulse_id = doc.inner().get_element_by_id("pulse-node");
    if let Some(id) = pulse_id {
        doc.inner_mut().mutate().remove_and_drop_node(id);
    }

    // Second resolve — panics: animations.sets still references the freed slab key.
    doc.as_mut().resolve(0.1); // panics here
}

Any DOM removal of an element with an active CSS animation: rule followed by a second resolve() reproduces it. The same panic also occurs in the GUI event loop when blitz internally recreates nodes between frames.

Panic

thread 'main' panicked at blitz-dom-0.3.0-alpha.2/src/stylo.rs:83:23:
invalid key

Root Cause

In resolve_stylist() (around stylo.rs:83):

self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);

Flow:

  1. First resolvetraverse_dom matches CSS animations against the DOM,
    writes node_ids into animations.sets.
  2. The animated node is removed from the DOM (slab key freed).
  3. Second resolve — iterates animations.sets and indexes self.nodes directly.
    The slab key is stale, so the bracket index panics.

Proposed Fix

Use get_mut and skip stale entries:

let Some(node) = self.nodes.get_mut(node_id) else {
    continue; // stale animation entry — skip safely
};
node.set_restyle_hint(RestyleHint::RESTYLE_SELF);

Vendored locally with this patch — reproducer passes cleanly, GUI event loop runs cleanly through hundreds of frames.

PR

Happy to submit a PR with the 1-line fix + regression test if it matches your design intent.


Drafted with Claude, fix and reproduction hand-verified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions