From 065625843df38035093b4dceb96ec45034f6026f Mon Sep 17 00:00:00 2001 From: AskaEth Date: Sun, 16 Aug 2026 16:37:15 +0800 Subject: [PATCH] fix: topbar xss and stale closure --- app/src/main/assets/h5/js/webapp/topbar.js | 54 ++++++++++++++-------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/app/src/main/assets/h5/js/webapp/topbar.js b/app/src/main/assets/h5/js/webapp/topbar.js index 32ab714..e48d09f 100644 --- a/app/src/main/assets/h5/js/webapp/topbar.js +++ b/app/src/main/assets/h5/js/webapp/topbar.js @@ -12,18 +12,25 @@ window.showTopbar = function (tabs) { if (!bar) { bar = document.createElement('div'); bar.id = 'web-topbar'; - bar.innerHTML = ` - - - - - `; + const back = document.createElement('button'); + back.id = 'tb-back'; back.title = '回退'; back.textContent = '←'; + const fwd = document.createElement('button'); + fwd.id = 'tb-fwd'; fwd.title = '前进'; fwd.textContent = '→'; + const reload = document.createElement('button'); + reload.id = 'tb-reload'; reload.title = '重载'; reload.textContent = '⟳'; + const title = document.createElement('span'); + title.className = 'tb-title'; title.id = 'tb-title'; + const tabsBtn = document.createElement('button'); + tabsBtn.id = 'tb-tabs'; + bar.append(back, fwd, reload, title, tabsBtn); document.body.appendChild(bar); - document.getElementById('tb-back').onclick = () => bridge.call('webGoBack'); - document.getElementById('tb-fwd').onclick = () => bridge.call('webGoForward'); - document.getElementById('tb-reload').onclick = () => bridge.call('webReload'); - document.getElementById('tb-tabs').onclick = () => showTabPanel(tabs); + back.onclick = () => bridge.call('webGoBack'); + fwd.onclick = () => bridge.call('webGoForward'); + reload.onclick = () => bridge.call('webReload'); } + // 每次刷新都重绑,避免捕获旧 tabs(陈旧闭包) + // Rebind on every refresh to avoid capturing stale tabs (stale closure) + document.getElementById('tb-tabs').onclick = () => showTabPanel(tabs); document.getElementById('tb-tabs').textContent = `标签 (${tabs.length})`; const active = tabs.find(t => t.active); document.getElementById('tb-title').textContent = active ? active.name : ''; @@ -31,6 +38,8 @@ window.showTopbar = function (tabs) { // 标签面板:列出所有标签,点击切换,× 关闭 // Tab panel: list all tabs, click to switch, × to close +// 用 createElement + textContent/dataset 渲染,避免 innerHTML 拼接用户数据(XSS) +// Render with createElement + textContent/dataset to avoid innerHTML user data (XSS) function showTabPanel(tabs) { let panel = document.getElementById('tab-panel'); if (!panel) { @@ -38,15 +47,22 @@ function showTabPanel(tabs) { panel.id = 'tab-panel'; document.body.appendChild(panel); } - panel.innerHTML = tabs.map(t => ` -
- ${t.name} - -
`).join(''); - panel.querySelectorAll('.tab-row').forEach(row => { - const id = row.dataset.id; - row.querySelector('.tab-name').onclick = () => { bridge.call('switchTab', id); closeTabPanel(); }; - row.querySelector('.tab-close').onclick = () => bridge.call('closeWebApp', id); + panel.textContent = ''; + tabs.forEach(t => { + const row = document.createElement('div'); + row.className = 'tab-row' + (t.active ? ' active' : ''); + row.dataset.id = t.id; + const name = document.createElement('span'); + name.className = 'tab-name'; + name.textContent = t.name; + const close = document.createElement('button'); + close.className = 'tab-close'; + close.title = '关闭'; + close.textContent = '×'; + name.onclick = () => { bridge.call('switchTab', t.id); closeTabPanel(); }; + close.onclick = () => bridge.call('closeWebApp', t.id); + row.append(name, close); + panel.appendChild(row); }); panel.style.display = 'block'; }