Skip to content

Fix(Chat): keep the display bridge off windows Blizzard has not opened yet - #1913

Open
dfrisone wants to merge 1 commit into
EllesmereGaming:mainfrom
dfrisone:fix/chat-popout-newwindow-taint
Open

Fix(Chat): keep the display bridge off windows Blizzard has not opened yet#1913
dfrisone wants to merge 1 commit into
EllesmereGaming:mainfrom
dfrisone:fix/chat-popout-newwindow-taint

Conversation

@dfrisone

@dfrisone dfrisone commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bug: reported by Discord user 265682780711550976 in the EllesmereUI bug channel (2026-09-01, on 9.0.7). LUA error, "secrets".

Issue: right-clicking the [Guild] channel link in chat and picking Move to New Window throws "ChatFrameUtil.lua:711: attempt to compare local 'messageAccessID' (a secret number value, while execution tainted by 'EllesmereUIChat')". The new window opens and docks but stays empty, and the guild lines are still in the old tab, because the error aborts the pop-out before it copies or removes anything.

Root cause: ChatFrameUtil.PopOutChat seeds the new window from the source frame inside one loop: GetMessageInfo(i), compare that line's accessID, AddMessage, next i. The display engine bridges every chat frame with hooksecurefunc(cf, "AddMessage", EngineTail), so the first copied line enters our hook and taints the rest of Blizzard's loop. Iteration two's GetMessageInfo then answers with a secret accessID and the compare throws. The bridge's own note calls AddMessage "the LAST step of Blizzard's message pipeline", which holds for the CHAT_MSG handler it was written against but not for this copy loop.

Fix: InstallBridge now skips frames Blizzard has not opened, so the frame it hands to FCF_OpenNewWindow carries no hook while it copies. The predicate is Blizzard's own FCF_IsChatWindowIndexActive, the one FCF_GetNextOpenChatWindowIndex draws the pop-out target from, with cf.inUse for temporaries. InstallBridge reports whether it installed, and IntegrateChatFrame backfills our window from Blizzard's buffer when a frame is bridged late and our side is still empty, so the copied lines appear a tick after the pop-out. The empty check keeps a late bridge from wiping session history replayed into a window we already render. SetChatWindowShown fires UPDATE_CHAT_WINDOWS, which drives that deferred pass. Side effect: unused chat frames no longer get a bridge, a wheel handler or scroll hooks until they are opened.

Known limitation: the gate only protects a frame's first opening. hooksecurefunc cannot be undone, and restoring cf.AddMessage by hand is worse than the bug, since chat frames get their methods by Mixin copy, so the field has no metatable to fall back to and a hand-written value is read by the secure MessageEventHandler on every line. Once a frame is bridged it therefore taints any later re-seed: closing a popped-out window frees its index while the hook is still attached, and FCF_OpenTemporaryWindow sets inUse before its own identical copy loop, so a pooled whisper window reused for a second conversation is exposed the same way. Blizzard's clearable addMessageObserver slot has the same every-line read problem and is not a way out. Closing those cases means taking the tail off hooksecurefunc(cf, "AddMessage") altogether and driving the engine from chat events plus an incremental sync off cf.historyBuffer, which is what the module's own rule already points at: no EUI code may execute inside a Blizzard chat-state execution. That is an engine rewrite touching every line of live chat rendering, so it is deliberately not in this PR. This change is the containment, and it fixes the reported case.

Not yet verified in game.

Moving a chat type to a new window threw a secret-value error at
ChatFrameUtil.lua:711 and left the pop-out half done. Blizzard seeds the new
window from the source frame inside its own loop -- GetMessageInfo, compare the
line's accessID, AddMessage -- and our AddMessage post-hook tainted the rest of
that loop, so the next GetMessageInfo answered with a secret accessID.

Unopened chat frames now stay unbridged, and the first integrate pass after
Blizzard opens one installs the bridge and backfills our window from its
buffer.
@EllesmereGaming

Copy link
Copy Markdown
Owner

lmk when verified in game going to hold until then!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants