Skip to content

Hook bodies run synchronously inside Blizzard panel dispatch chains, causing ADDON_ACTION_BLOCKED and secret-value layout storms (12.x) #38

Description

@ItalistAddons

Environment

  • WoW retail 12.x ("Midnight"), Interface 120001
  • BugGrabber captures across 2026-08-18 → 2026-08-21 sessions
  • AutoTurnIn 10.0.14

Observed errors (all attributed to AutoTurnIn, all Blizzard frames in the stacks)

Count Error
×6 ADDON_ACTION_BLOCKED — protected PerformEmote() via WorldMapFrame:Show chain
×42 ADDON_ACTION_BLOCKED — protected Button:SetPassThroughButtons() (map pin acquisition)
×16 ADDON_ACTION_BLOCKED — protected Frame:SetPropagateMouseClicks()
×1644 Blizzard_SharedXML\LayoutFrame.lua:491: attempt to compare a secret number value
×111 Blizzard_UIWidgets\...TextWithState.lua:35/:31 secret arithmetic / SetWidth
×15 Blizzard_UIWidgetTemplateBase.lua:1638/:1694 secret arithmetic
×3 SharedTooltipTemplates.lua:202 secret arithmetic

Root cause (one shared mechanism)

AutoTurnIn's five hooksecurefunc thunks (QuestFrame/GossipFrame Show+Hide, MerchantFrame Show) run add-on code synchronously inside Blizzard's panel Show/Hide dispatch chains. On this client that taints everything Blizzard executes afterwards in the same dispatch:

  • Blizzard's own protected calls (the panel-show emote, map-pin SetPassThroughButtons) become blocked and are blamed on AutoTurnIn as the taint owner. Notably there is no PerformEmote/DoEmote call anywhere in AutoTurnIn — the blocked call is Blizzard's own.
  • Metrics Blizzard reads during that window come back as secret values and stay cached; later perfectly clean layout/widget code then raises repeatedly (hence four-digit counts).

Proposed remediation (validated locally over several sessions)

Defer hook bodies out of the dispatch; while in combat, queue and flush on PLAYER_REGEN_ENABLED:

function AutoTurnIn:DeferUnguarded(fn)
    if InCombatLockdown() then
        self.unguardedQueue[#self.unguardedQueue + 1] = fn
        self:RegisterEvent("PLAYER_REGEN_ENABLED", "FlushUnguardedQueue")
        return
    end
    C_Timer.After(0, fn)          -- runs after Blizzard's dispatch completes
end

function AutoTurnIn:FlushUnguardedQueue()
    if InCombatLockdown() then return end
    local q = self.unguardedQueue; self.unguardedQueue = {}
    self:UnregisterEvent("PLAYER_REGEN_ENABLED")
    for _, fn in ipairs(q) do fn() end
end

hooksecurefunc(QuestFrame, "Hide",  function() AutoTurnIn:DeferUnguarded(restoreToggleKey) end)
hooksecurefunc(QuestFrame, "Show",  function() AutoTurnIn:DeferUnguarded(function() AutoTurnIn:ShowIgnoreButton("quest") end) end)
-- (same for GossipFrame Hide/Show, MerchantFrame Show sell/repair path)

Additionally guard map-coordinate reads in MapCoords.lua with issecretvalue before any use of WorldMapFrame:GetNormalizedCursorPosition() / pos:GetXY().

Result after patching: zero blocked-function errors and zero widget/layout secret errors in subsequent sessions; all features (auto turn-in, ignore buttons, sell-junk, auto-repair, coordinates) preserved with one-tick latency.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions