fix: topbar xss and stale closure
This commit is contained in:
@@ -12,18 +12,25 @@ window.showTopbar = function (tabs) {
|
|||||||
if (!bar) {
|
if (!bar) {
|
||||||
bar = document.createElement('div');
|
bar = document.createElement('div');
|
||||||
bar.id = 'web-topbar';
|
bar.id = 'web-topbar';
|
||||||
bar.innerHTML = `
|
const back = document.createElement('button');
|
||||||
<button id="tb-back" title="回退">←</button>
|
back.id = 'tb-back'; back.title = '回退'; back.textContent = '←';
|
||||||
<button id="tb-fwd" title="前进">→</button>
|
const fwd = document.createElement('button');
|
||||||
<button id="tb-reload" title="重载">⟳</button>
|
fwd.id = 'tb-fwd'; fwd.title = '前进'; fwd.textContent = '→';
|
||||||
<span class="tb-title" id="tb-title"></span>
|
const reload = document.createElement('button');
|
||||||
<button id="tb-tabs"></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.body.appendChild(bar);
|
||||||
document.getElementById('tb-back').onclick = () => bridge.call('webGoBack');
|
back.onclick = () => bridge.call('webGoBack');
|
||||||
document.getElementById('tb-fwd').onclick = () => bridge.call('webGoForward');
|
fwd.onclick = () => bridge.call('webGoForward');
|
||||||
document.getElementById('tb-reload').onclick = () => bridge.call('webReload');
|
reload.onclick = () => bridge.call('webReload');
|
||||||
document.getElementById('tb-tabs').onclick = () => showTabPanel(tabs);
|
|
||||||
}
|
}
|
||||||
|
// 每次刷新都重绑,避免捕获旧 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})`;
|
document.getElementById('tb-tabs').textContent = `标签 (${tabs.length})`;
|
||||||
const active = tabs.find(t => t.active);
|
const active = tabs.find(t => t.active);
|
||||||
document.getElementById('tb-title').textContent = active ? active.name : '';
|
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
|
// 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) {
|
function showTabPanel(tabs) {
|
||||||
let panel = document.getElementById('tab-panel');
|
let panel = document.getElementById('tab-panel');
|
||||||
if (!panel) {
|
if (!panel) {
|
||||||
@@ -38,15 +47,22 @@ function showTabPanel(tabs) {
|
|||||||
panel.id = 'tab-panel';
|
panel.id = 'tab-panel';
|
||||||
document.body.appendChild(panel);
|
document.body.appendChild(panel);
|
||||||
}
|
}
|
||||||
panel.innerHTML = tabs.map(t => `
|
panel.textContent = '';
|
||||||
<div class="tab-row ${t.active ? 'active' : ''}" data-id="${t.id}">
|
tabs.forEach(t => {
|
||||||
<span class="tab-name">${t.name}</span>
|
const row = document.createElement('div');
|
||||||
<button class="tab-close" title="关闭">×</button>
|
row.className = 'tab-row' + (t.active ? ' active' : '');
|
||||||
</div>`).join('');
|
row.dataset.id = t.id;
|
||||||
panel.querySelectorAll('.tab-row').forEach(row => {
|
const name = document.createElement('span');
|
||||||
const id = row.dataset.id;
|
name.className = 'tab-name';
|
||||||
row.querySelector('.tab-name').onclick = () => { bridge.call('switchTab', id); closeTabPanel(); };
|
name.textContent = t.name;
|
||||||
row.querySelector('.tab-close').onclick = () => bridge.call('closeWebApp', id);
|
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';
|
panel.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user