OnShow and OnHide fire children first, and after OnLoad, as in 1.12.1 (#842)

Show and hide propagated a whole subtree first, then fired from a snapshot, parent first: a parent's `OnShow` ran before its children's, and a child hidden by an earlier handler still got its `OnShow`. A frame visible at the end of its load got no `OnShow` at all.

In 1.12.1 show `0x76ae10` and hide `0x76ad50` mark the frame, walk the live child list re-checking each child's gate, and fire the frame's own handler last (`0x76aef5`, `0x76adfd`). The post-load hook fires `OnShow` after `OnLoad` for a visible frame (`0x76a3b3`), from XML and `CreateFrame` alike.

Now handlers run inside a live post-order walk, and the post-load `OnShow` fires. Tests cover the order, a sibling hidden mid-walk, and one or two `OnShow`s after a load; they fail without the fix. Four existing tests' expected orders flip to the reference's.

Fixes #350
Fixes #59

🤖 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-09 13:44:15 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent dc63a06178
commit c2fc3cf304
18 changed files with 633 additions and 204 deletions
@@ -492,16 +492,19 @@ fn toggle_inspect_reopens_after_a_close_and_raises_like_the_reference() {
s.eval::<bool>("return InspectFrame:IsVisible()").unwrap(),
"the window still shows — Show() runs before OnShow raises"
);
// Two raises: `InspectFrame` and `InspectPaperDollFrame` each run their own `OnShow` under
// their own pcall, the first dying at the portrait's gate, the second at `UnitLevel`'s.
// Two raises: `InspectPaperDollFrame` and `InspectFrame` each run their own `OnShow` under
// their own pcall, the page first, since the show cascade notifies children before their
// parent (`0x76aed5`, then `0x76aef5`): the page dies at `UnitLevel`'s gate, the window at
// the portrait's.
let errors = s.errors();
assert_eq!(errors.len(), 2, "one raise per OnShow handler: {errors:?}");
assert!(
errors[0].contains(r#"Usage: SetPortraitTexture(texture, "unit")"#),
"the portrait's gate, before the name line is ever reached: {errors:?}"
errors[0].contains(r#"Usage: UnitLevel("unit")"#),
"the paper doll's own handler, dying at its first getter: {errors:?}"
);
assert!(
errors[1].contains(r#"Usage: UnitLevel("unit")"#),
"and the paper doll's own handler, dying at its first getter: {errors:?}"
errors[1].contains(r#"Usage: SetPortraitTexture(texture, "unit")"#),
"then the window's, at the portrait's gate, before the name line is ever reached: \
{errors:?}"
);
}
+5 -1
View File
@@ -660,6 +660,8 @@ pub(super) const BAG_UI: &[&str] = &[
"Interface\\FrameXML\\UIDropDownMenu.xml",
r"Interface\FrameXML\UIPanelTemplates.lua",
r"Interface\FrameXML\UIPanelTemplates.xml",
// `CharacterFrame.xml`'s tab template, for the hidden parent `PaperDollFrame` needs below.
r"Interface\FrameXML\CharacterFrameTemplates.xml",
// The dialog engine, after the `UIPanelCloseButton` it inherits.
r"Interface\FrameXML\StaticPopup.xml",
// `ContainerFrameItemButton_OnClick` hides `StackSplitFrame` on every plain click
@@ -683,7 +685,9 @@ pub(super) const BAG_UI: &[&str] = &[
"Interface\\FrameXML\\ItemButtonTemplate.xml",
// `BagSlotButtonTemplate` inherits `PaperDollItemSlotButtonTemplate`, and its OnLoad
// (`PaperDollItemSlotButton_OnLoad`) gives each bag button its inventory-slot id, 20..23.
// `CharacterFrame.xml` stays out: a missing `parent=` only warns.
// Its hidden parent `CharacterFrame` comes first: a parentless `PaperDollFrame` would be
// visible at the end of its load and run its OnShow, which reaches for the Honor tab.
"Interface\\FrameXML\\CharacterFrame.xml",
"Interface\\FrameXML\\PaperDollFrame.xml",
// The stock bag bar, with `BagSlotButtonTemplate` and `KEYRING_CONTAINER`.
"Interface\\FrameXML\\MainMenuBarBagButtons.xml",