RegisterEvent ignores case; a frame's first resize precedes OnLoad (#1001)

- `RegisterEvent` and `UnregisterEvent` match a name against the client's event table case-insensitively and drop a name that isn't in it (`0x702140`). A new gate checks that every event name the code fires is in the table.
- A visible XML frame resolves its layout at the post-load hook (`0x76a310` → `0x7680e0(1)`). So its first `OnSizeChanged` fires before its `<Frames>`, `OnLoad` and `OnShow`, as in 1.12.1. A hidden frame still waits for its show.
- `pending_events`' doc now says what the queue is: `SignalEvent` (`0x703e50`) runs handlers in place, and the Lua verbs that still queue are a gap.
- #890 was already fixed by #907; two stale comments around it are corrected.

Fixes #959
Fixes #972
Fixes #560
Closes #890

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-10-12 15:08:07 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 720dcf8188
commit 0f505a2ebf
11 changed files with 279 additions and 65 deletions
@@ -90,9 +90,9 @@ fn error_message_is_the_installed_handler_and_the_host_channel_stays_sighted() {
"reported"
);
// An engine-caught error still lands in `errors()`; the dispatch adds the dialog on top.
s.run("BrokenProbe = CreateFrame('Frame') BrokenProbe:RegisterEvent('ERROR_PROBE') BrokenProbe:SetScript('OnEvent', function() error('fault') end)")
s.run("BrokenProbe = CreateFrame('Frame') BrokenProbe:RegisterEvent('PLAYER_ALIVE') BrokenProbe:SetScript('OnEvent', function() error('fault') end)")
.unwrap();
s.fire_event("ERROR_PROBE", vec![]);
s.fire_event("PLAYER_ALIVE", vec![]);
assert!(
s.errors().iter().any(|e| e.contains("fault")),
"engine-caught errors stay on the host channel: {:?}",
@@ -253,6 +253,38 @@ fn every_event_we_fire_carries_the_reference_s_arguments() {
);
}
/// The in-game event table (`0x703d90`, name array `0xbe1198`) holds the names of
/// `reference/1.12-event-catalog.tsv` and the glue table the 26 of `GLUE_EVENTS`; `RegisterEvent`
/// drops any other name (`0x702140`), so an event fired under a name off both reaches no frame
/// that registered it by name.
#[test]
fn every_event_we_fire_is_in_the_reference_table() {
let tsv = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../reference/1.12-event-catalog.tsv"
);
let text = std::fs::read_to_string(tsv).expect("reference/1.12-event-catalog.tsv");
let names: std::collections::BTreeSet<&str> = text
.lines()
.filter(|l| !l.starts_with('#'))
.filter_map(|l| l.split_once('\t').map(|(_, n)| n))
.chain(benilla_ui::script::GLUE_EVENTS)
.collect();
assert!(names.len() > 390, "the catalog stopped being read");
let mut off: Vec<String> = fire_sites()
.into_iter()
.filter(|(name, _, _)| !names.contains(name.as_str()))
.map(|(name, _, at)| format!("{name} from {at}"))
.collect();
off.sort();
off.dedup();
assert!(
off.is_empty(),
"fired under a name the reference's event table lacks:\n {}",
off.join("\n ")
);
}
/// `0x51bbb0` registers one watch per named unit-window field, so each of the 25 named event ids
/// below `0xb6` comes from that bridge with the unit token, and each needs a producer here.
#[test]