tooltip: SetOwner hides the plate, so a rebuild gives the addon its OnShow back
Reported from play with a video: hovering a bag item draws the enhanced tooltip — MasterTradeSkills' recipe block, AdvancedTradeSkillWindow2's Bags/Bank/Total — for about nine frames of sixty, then the plate falls back to the bare one and stays bare while the cursor sits still. The loop is stock. `ContainerFrameItemButton_OnUpdate` re-enters `..._OnEnter` every `TOOLTIP_UPDATE_TIME` (`ContainerFrame.lua:693` in his own patch-4.mpq; pfUI's bag slots are `ContainerFrameItemButtonTemplate`, so they run it too), and each turn is `SetOwner` + `SetBagItem` — a clear that wipes every line an addon appended. What puts them back in the reference is a hide/show pair, and we had neither half: the `SetOwner` binding hides the plate before it even reads the owner argument (`0x531179` zeroes the content gate at `+0xd0`, `0x531183` calls `[vtable+0x84]` = `0x530a60`, which is the `SetOwner(NULL)` core plus the base hide `0x76ad50`), and the content `Set*` re-shows it through the tooltip's own Show override (`0x530a80`). So every rebuild raises `OnHide` then `OnShow`. Ours cleared in place, and the base show early-outs on an already-visible frame (`0x76ae3b`), so the plate never hid and the `OnShow` never came. That is why exactly two addons lost their lines while others kept theirs: `LibItemTooltip-1.0`, the tooltip contract of MasterTradeSkills and Gromissingcrafts, hangs entirely on a scratch child frame of `GameTooltip` listening for `OnShow`, where pfUI's ItemID and sell-price lines hook the `Set*` methods themselves and so re-run on every refresh. The reference also clears twice per `SetOwner` (once inside the hide, once in the core) and so fires `OnTooltipCleared` twice; that falls out of calling the same hide, and the existing lifecycle test already allowed for it. Addresses read off the player's own `WoW.exe` with objdump, not from memory. The new test is the hover loop in miniature — three turns of `SetOwner` + a content `Set*` under a `LibItemTooltip`-shaped child probe — and fails on the old behaviour with one `OnShow` instead of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -162,6 +162,56 @@ fn owner_clear_and_hide_lifecycle() {
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// **A hover rebuild gives the addon its `OnShow` back.** The bag loop re-enters
|
||||
/// `ContainerFrameItemButton_OnEnter` every `TOOLTIP_UPDATE_TIME` (`ContainerFrame.lua:693`; pfUI's
|
||||
/// bag slots inherit `ContainerFrameItemButtonTemplate`, so they run it too), and that is
|
||||
/// `SetOwner` + a content `Set*` — the clear wipes every line an addon appended. The reference
|
||||
/// hides the plate inside `SetOwner` itself (`0x531183` calls `[vtable+0x84]` = `0x530a60`, the
|
||||
/// `SetOwner(NULL)` core plus the base hide) and the content `Set*` re-shows it (`0x530a80`), so
|
||||
/// every turn of the loop raises `OnHide` then `OnShow`.
|
||||
///
|
||||
/// `LibItemTooltip-1.0` — MasterTradeSkills' and Gromissingcrafts' whole tooltip contract — listens
|
||||
/// on a scratch child frame of `GameTooltip`, so without that pair it is told once: the enhanced
|
||||
/// tooltip lasts 0.2 s and reverts to the bare one for as long as the cursor stays.
|
||||
#[test]
|
||||
fn every_hover_rebuild_raises_onshow_for_the_addon_that_annotates_it() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
shows = 0
|
||||
hides = 0
|
||||
local slot = CreateFrame("Button", "BagSlot")
|
||||
slot:SetPoint("CENTER", 0, 0); slot:SetWidth(10); slot:SetHeight(10)
|
||||
local tt = CreateFrame("GameTooltip", "TTHover")
|
||||
-- LibItemTooltip's mechanism verbatim: a scratch child of the tooltip, on OnShow.
|
||||
local probe = CreateFrame("Frame", nil, tt)
|
||||
probe:SetScript("OnShow", function()
|
||||
shows = shows + 1
|
||||
tt:AddLine("Recipes")
|
||||
tt:Show()
|
||||
end)
|
||||
probe:SetScript("OnHide", function() hides = hides + 1 end)
|
||||
-- Start where a real hover starts: the plate is down (a `CreateFrame`d one is born shown).
|
||||
tt:Hide()
|
||||
shows = 0
|
||||
hides = 0
|
||||
-- Three turns of the hover loop, in the shape ContainerFrameItemButton_OnEnter has.
|
||||
for i = 1, 3 do
|
||||
tt:SetOwner(slot, "ANCHOR_RIGHT")
|
||||
tt:SetText("Copper Ore")
|
||||
end
|
||||
assert(shows == 3, "every rebuild raises OnShow, got " .. tostring(shows))
|
||||
-- The first SetOwner found the plate hidden; the other two hid it.
|
||||
assert(hides == 2, "a rebuild over a shown plate raises OnHide, got " .. tostring(hides))
|
||||
assert(tt:NumLines() == 2, "the addon's line survives the rebuild, got "
|
||||
.. tostring(tt:NumLines()))
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
/// `SetText` shows the tooltip and `AddLine` does not. `r, g, b` apply only when the r slot is a
|
||||
/// number, so `AddLine(text, "", r, g, b)` draws in the default gold, as the reference's zone
|
||||
/// tooltip does.
|
||||
|
||||
@@ -60,6 +60,25 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.create_function(
|
||||
|lua, (this, owner, anchor, x, y): (Table, Table, Value, Option<f32>, Option<f32>)| {
|
||||
let h = frame_handle_of(lua, &this)?;
|
||||
// **The plate hides first.** The binding zeroes the content gate (`+0xd0`,
|
||||
// `0x531179`) and calls the tooltip's own virtual `Hide` (`0x531183`,
|
||||
// `[vtable+0x84]` = `0x530a60`) on *itself* before it even reads the owner
|
||||
// argument; that `Hide` is the `SetOwner(NULL)` core (`0x52ffe0`: clear, so
|
||||
// `OnTooltipCleared`) followed by the base hide (`0x76ad50`, so `OnHide`). The
|
||||
// content `Set*` that follows then re-shows it through `0x530a80`, and the base
|
||||
// show raises `OnShow` because the frame really was hidden (`0x76ae3b` early-outs
|
||||
// on an already-visible one).
|
||||
//
|
||||
// That hide/show pair is the **only** signal an addon gets that the plate was
|
||||
// wiped and wants its lines back, and the hover loop wipes it five times a second:
|
||||
// `ContainerFrameItemButton_OnUpdate` re-enters `..._OnEnter` every
|
||||
// `TOOLTIP_UPDATE_TIME` (`ContainerFrame.lua:693`, and pfUI's bag slots inherit
|
||||
// `ContainerFrameItemButtonTemplate`), which is `SetOwner` + `SetBagItem`. Without
|
||||
// the hide the rebuild is silent, so an addon's lines live 0.2 s and never come
|
||||
// back: `LibItemTooltip-1.0` (MasterTradeSkills, Gromissingcrafts) hangs its whole
|
||||
// contract on a scratch child frame's `OnShow`, and pfQuest and pfUI read the
|
||||
// tooltip from `OnShow` too.
|
||||
super::hide_tooltip(lua, h);
|
||||
let owner_h = frame_handle_of(lua, &owner)?;
|
||||
// The gate is `lua_isstring` (`0x6f3510`): a string or number is read, and any
|
||||
// other type counts as absent rather than raising.
|
||||
|
||||
Reference in New Issue
Block a user