Both modules land together because `classic_api/mod.rs` and `widget_surface.rs` carry rows for
each: split, the first commit named a module the second added, and would not have compiled.
## The secure-handler family, because 1.12 has nothing to be secure from
`frame.rs` declined it with a stated reason — "modern restricted execution, which 1.12 has no
equivalent of" — and that is right about the *sandbox* and is the whole of why it is now built.
A secure handler exists so an untainted snippet may call **protected** functions; 1.12 has
neither taint nor protected functions, so the restriction has no counterpart here, while the
plumbing underneath (a named frame store, a snippet on a child's handler) is ordinary
behaviour the snippets need. The body runs as plain Lua with the names the secure environment
would bind.
**The per-header shared table is the part worth getting right.** `MenuWheel.lua:484` writes a
bare `held = false` inside a snippet, and in the real secure environment that lands in a table
per header, not `_G`. `setfenv` onto one table per frame, `__index` to `_G` so the rest of the
API resolves; the test asserts `held` is **not** a global afterwards. My first test asserted
through `_G` and failed — the implementation was right and the test measured the wrong thing.
**`SecureActionButtonTemplate` is not empty**: reading your own attributes on click is plain
dispatch. `type="click"` clicks the frame in `clickbutton` (how the wheels reach the game's
micro buttons), and the lookup follows upstream's order (`type-<button>`, `*type*`, `type`).
**That exposed a real bug of ours**: `SetAttribute` silently dropped a frame.
`attribute_value` kept bool/int/number/string and sent everything else to `ScriptValue::Nil`,
so `SetAttribute("clickbutton", SomeFrame)` stored nothing — and upstream's `clickbutton` is
routinely a frame, which `Cinematic/MenuWheel.lua` relies on. A widget table is now kept as
`ScriptValue::Frame(id)`, the variant `NAME_PLATE_CREATED`'s arg1 already uses, which
round-trips through `object::frame_wrapper`. A non-widget table is still nil, documented.
**Not built, named rather than left to be discovered**: the binding half (`ClearBindings`,
`SetBindingClick`, …) are no-ops — no override-binding layer here to write into — so a wheel
opened with the mouse works and hold-a-key-and-release does not; and the `_onshow`/`_onhide`
attribute snippets are not run, since `SetAttribute` is a plain store with nothing to drive
them.
Templates in `assets/ui/CompatTemplates.xml`, listed on that wall with the reason: no stock
file to migrate from, and XML rather than `compat/` only because `inherits=` is loader-resolved.
A trap caught before shipping: `BackdropTemplateMixin.GetBackdropColor` forwarding to
`self:GetBackdropColor()` calls **itself** once `Mixin()` copies it onto the frame — the stock
getters answer, so there are no getter rows, and a test mixes in and reads back.
## `CreateAnimationGroup`: the surface, with nothing animated
1.12 has no animation system and this UI pass has no interpolation to hang one on. The cost is
the **absent constructor**: `Cinematic/Options.lua:7106` builds a rotating spinner and the nil
method aborted the whole options panel. Measured first — that is the addon's only animation in
55,661 lines, so this is a surface for a decoration.
**A non-looping group finishes the moment it plays**, firing `OnFinished`, because nothing
moves and that is the reading that keeps an addon's flow right; one that chains its next step
off `OnFinished` would otherwise wait forever. A looping group plays until stopped. Stated in
the header, since the name implies motion: a spinner does not spin, a fade appears at its
destination, a sequence lands on its last frame. Setters and getters round-trip.
Corpus: all 100 of grom's addons surveyed before and after — 85 ok / 1 fail, rosters
identical. benilla-ui 2081 passed. Full chain green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
176 lines
9.2 KiB
XML
176 lines
9.2 KiB
XML
<Ui>
|
|
<!--
|
|
The ClassicAPI-era FrameXML templates addons on this install inherit by name. Not windows:
|
|
there is nothing to migrate, because 1.12 does not have these templates at all — they are
|
|
the same kind of surface as `script/compat/`'s verbs, declared here because `inherits=` is
|
|
resolved by the XML loader and cannot be answered from Lua.
|
|
|
|
Both are used **unguarded inside Ace3 itself**, so this is not one addon's problem:
|
|
`AceGUIContainer-Frame.lua:179` and `:206` and `AceGUIContainer-DropDownGroup.lua:128`
|
|
name `BackdropTemplate`, and `AceConfigDialog-3.0.lua:573` names
|
|
`DialogBorderOpaqueTemplate`. A missing template makes `CreateFrame` raise
|
|
("Couldn't find inherited node"), which is where OpenMapClassic stopped — on its first
|
|
file, in a library, before a line of its own code ran.
|
|
-->
|
|
|
|
<!--
|
|
9.0 moved the backdrop off the C side and gave addons `BackdropTemplateMixin` to put it
|
|
back; 1.12 has `SetBackdrop`/`GetBackdrop`/`SetBackdropColor`/`SetBackdropBorderColor` as
|
|
real frame methods, so the template is correctly **empty** — a frame inheriting it already
|
|
answers all four. The global the mixin idiom tests for
|
|
(`BackdropTemplateMixin and "BackdropTemplate" or nil`) is set beside it, its methods
|
|
forwarding to those stock ones so `Mixin(f, BackdropTemplateMixin)` is a no-op rather than
|
|
a shadow. `OnBackdropLoaded`/`OnBackdropSizeChanged` are the hooks 9.0's own template
|
|
fires; ours have nothing to recompute, and an addon that calls one gets a function rather
|
|
than a nil.
|
|
-->
|
|
<Frame name="BackdropTemplate" virtual="true"/>
|
|
|
|
<!--
|
|
A dialog border frame, which is all Ace3 wants of it: `local border = CreateFrame("Frame",
|
|
nil, frame, "DialogBorderOpaqueTemplate")` then `border:SetAllPoints(frame)`. Era's own is
|
|
nine textures out of `SharedUIPanelTemplates.xml`; the same shape in 1.12's vocabulary is
|
|
one `<Backdrop>` over this client's dialog art, which is what every stock 1.12 dialog
|
|
draws and what our `GameMenuFrame.xml` already uses. The opaque variant differs only in
|
|
its background alpha, hence the two rows.
|
|
|
|
`enableMouse` stays off: it is a decoration over a frame that handles its own input, and a
|
|
border that ate clicks would make an Ace options window unusable.
|
|
-->
|
|
<Frame name="DialogBorderTemplate" virtual="true">
|
|
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
|
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
|
<TileSize><AbsValue val="32"/></TileSize>
|
|
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
|
<Color r="1" g="1" b="1" a="0.85"/>
|
|
</Backdrop>
|
|
</Frame>
|
|
|
|
<Frame name="DialogBorderOpaqueTemplate" virtual="true">
|
|
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
|
|
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
|
|
<TileSize><AbsValue val="32"/></TileSize>
|
|
<EdgeSize><AbsValue val="32"/></EdgeSize>
|
|
<Color r="1" g="1" b="1" a="1"/>
|
|
</Backdrop>
|
|
</Frame>
|
|
|
|
<!--
|
|
The secure-handler family. On a modern client these exist so an *untainted* snippet may
|
|
call protected functions; 1.12 has neither taint nor protected functions, so what the
|
|
template encodes is a restriction that does not exist here and the frame itself carries no
|
|
behaviour. Empty, therefore — the header methods the snippets need (`SetFrameRef`,
|
|
`WrapScript`, `Execute`) are composed in `script/compat/classic_api/secure.rs`, beside the
|
|
rest of the compat surface.
|
|
|
|
Cinematic's two wheels (`MenuWheel.lua:312`, `MarkerWheel.lua:205`) name the first two.
|
|
-->
|
|
<Frame name="SecureHandlerBaseTemplate" virtual="true"/>
|
|
<Frame name="SecureHandlerShowHideTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerAttributeTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerClickTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerStateTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerEnterLeaveTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerDragTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
<Frame name="SecureHandlerMouseUpDownTemplate" virtual="true" inherits="SecureHandlerBaseTemplate"/>
|
|
|
|
<!--
|
|
`SecureActionButtonTemplate`, and this one is **not** empty: its whole job is to read its
|
|
own attributes on click and act on them, and that dispatch is plain behaviour rather than
|
|
anything protected. `type="click"` clicks the frame named by `clickbutton`, which is how
|
|
Cinematic's wheels reach the game's own menu buttons (`MenuWheel.lua:357`), and the three
|
|
cast-ish types forward to the stock verbs. The taint machinery is what 1.12 has no
|
|
counterpart for, and none of this needs it.
|
|
|
|
`RegisterForClicks` is the caller's to set, as upstream: the wheels ask for `"AnyUp"`.
|
|
-->
|
|
<Button name="SecureActionButtonTemplate" virtual="true">
|
|
<Scripts>
|
|
<OnClick>BenillaSecureAction_OnClick(this, arg1);</OnClick>
|
|
</Scripts>
|
|
</Button>
|
|
|
|
<Script><![CDATA[
|
|
-- `SecureActionButtonTemplate`'s dispatch. `self:GetAttribute(k)` first tries the
|
|
-- button-specific key the real template prefers (`<type>-<button>`, then `*<key>*`), then
|
|
-- the plain one, which is the lookup order upstream documents.
|
|
local function attr(self, key, button)
|
|
local v
|
|
if button then
|
|
v = self:GetAttribute(key .. "-" .. button)
|
|
if v ~= nil then return v end
|
|
v = self:GetAttribute("*" .. key .. "-" .. button)
|
|
if v ~= nil then return v end
|
|
end
|
|
v = self:GetAttribute("*" .. key .. "*")
|
|
if v ~= nil then return v end
|
|
return self:GetAttribute(key)
|
|
end
|
|
|
|
function BenillaSecureAction_OnClick(self, button)
|
|
local kind = attr(self, "type", button)
|
|
if not kind or kind == "none" then return end
|
|
if kind == "click" then
|
|
local target = attr(self, "clickbutton", button)
|
|
if type(target) == "string" then target = getglobal(target) end
|
|
if target and target.Click then target:Click(button) end
|
|
elseif kind == "spell" then
|
|
local spell = attr(self, "spell", button)
|
|
if spell then
|
|
local unit = attr(self, "unit", button)
|
|
if unit then
|
|
CastSpellByName(spell, unit)
|
|
else
|
|
CastSpellByName(spell)
|
|
end
|
|
end
|
|
elseif kind == "macro" then
|
|
local text = attr(self, "macrotext", button)
|
|
if text then
|
|
RunScript(text)
|
|
else
|
|
local name = attr(self, "macro", button)
|
|
if name then RunMacro(name) end
|
|
end
|
|
elseif kind == "item" then
|
|
local item = attr(self, "item", button)
|
|
if item then UseItemByName(item, attr(self, "unit", button)) end
|
|
end
|
|
end
|
|
|
|
-- The mixin 9.0 shipped so addons could call the backdrop methods it had removed. Here
|
|
-- the stock methods are the real ones, so each row forwards; the table exists for the
|
|
-- `BackdropTemplateMixin and "BackdropTemplate"` test and for `Mixin()`.
|
|
BackdropTemplateMixin = {}
|
|
|
|
function BackdropTemplateMixin.OnBackdropLoaded(self) end
|
|
function BackdropTemplateMixin.OnBackdropSizeChanged(self) end
|
|
|
|
function BackdropTemplateMixin.SetupTextureCoordinates(self) end
|
|
|
|
function BackdropTemplateMixin.ApplyBackdrop(self)
|
|
-- 9.0's own ApplyBackdrop re-reads `self.backdropInfo`; ours hands it to the stock
|
|
-- setter, which is the same journey with the C side still present.
|
|
if self.backdropInfo then
|
|
self:SetBackdrop(self.backdropInfo)
|
|
if self.backdropColor then
|
|
local c = self.backdropColor
|
|
self:SetBackdropColor(c.r, c.g, c.b, self.backdropColorAlpha or 1)
|
|
end
|
|
if self.backdropBorderColor then
|
|
local c = self.backdropBorderColor
|
|
self:SetBackdropBorderColor(c.r, c.g, c.b, self.backdropBorderColorAlpha or 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
function BackdropTemplateMixin.ClearBackdrop(self)
|
|
self:SetBackdrop(nil)
|
|
end
|
|
|
|
-- No getter rows: 1.12's own `GetBackdrop`/`GetBackdropColor`/`GetBackdropBorderColor`
|
|
-- already answer, and a forwarding copy would call *itself* once `Mixin()` had written it
|
|
-- onto the frame.
|
|
]]></Script>
|
|
</Ui>
|