// webAPP 顶栏:回退/前进/重载 + 标题 + 标签面板(切换/关闭) // webapp topbar: back/forward/reload + title + tab panel (switch/close) // 原生方法(webGoBack/webGoForward/webReload/switchTab/closeWebApp)Task 13 才实现, // 此处仅作为调用方,点击会静默 reject(符合预期)。 // Native methods (webGoBack/webGoForward/webReload/switchTab/closeWebApp) land in Task 13; // this topbar is only a caller, clicks silently reject (expected). // 展示/刷新顶栏:tabs 为标签数组(含 active 标记) // Show/refresh the topbar: tabs is the tab array (with active flag) window.showTopbar = function (tabs) { let bar = document.getElementById('web-topbar'); if (!bar) { bar = document.createElement('div'); bar.id = 'web-topbar'; 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); back.onclick = () => bridge.call('webGoBack'); fwd.onclick = () => bridge.call('webGoForward'); reload.onclick = () => bridge.call('webReload'); } // 标签按钮:调原生弹出标签面板(PopupWindow 在内容 WebView 之上) // Tab button: show the native tab panel (PopupWindow above the content WebView) document.getElementById('tb-tabs').onclick = () => bridge.call('showTabPanel'); document.getElementById('tb-tabs').textContent = `标签 (${tabs.length})`; const active = tabs.find(t => t.active); document.getElementById('tb-title').textContent = active ? active.name : ''; // 无标签时隐藏顶栏与标签面板(回到桌面) // Hide the topbar and tab panel when no tabs remain (back to desktop) bar.style.display = tabs.length ? 'flex' : 'none'; const panel = document.getElementById('tab-panel'); if (panel && !tabs.length) panel.style.display = 'none'; // 有 webapp 时隐藏 webapplist 页面内容(避免半透明顶栏透出列表控件),无 webapp 时恢复 // Hide the webapp list content while a webapp is open (so the translucent topbar // doesn't show the list controls through it); restore when none are open document.body.classList.toggle('webapp-active', tabs.length > 0); }; // 标签面板:列出所有标签,点击切换,× 关闭 // 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) { panel = document.createElement('div'); panel.id = 'tab-panel'; document.body.appendChild(panel); } 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); }); // 横向展开(CSS flex-direction: row) // Expand horizontally (CSS flex-direction: row) panel.style.display = 'flex'; } // 收起标签面板 // Collapse the tab panel function closeTabPanel() { const panel = document.getElementById('tab-panel'); if (panel) panel.style.display = 'none'; }