- html viewer:.html/.htm 沙箱 iframe 预览(相对资源经 /sidebar/html 路径编码路由加载,CSP sandbox + 信任围栏 + cwd 边界),预览/编辑切换 - browser tab:+ 菜单多开(browser:<n>),地址栏后退/前进/刷新/在浏览器中打开(15px 图标),URL 与标题按会话持久化(patchTab + nextBrowser) - 安全:iframe 一律不透明源沙箱(无 allow-same-origin/top-navigation、no-referrer、权限策略全禁、禁 srcdoc);地址栏仅 http(s)、拒绝 localhost 等本机地址;界面实时沙箱状态(关闭红色警示)+ 临时解锁/恢复 - 设置:htmlViewerNoSandbox / htmlViewerDefaultUnsafe / browserNoSandbox / browserInterceptLinks 四个声明式二级设置(默认安全) - 外链拦截:聊天/界面 http(s) 外链默认在侧边栏打开(openTab seed.url),Ctrl/Cmd 点击放行 - 嵌入拒绝面板:browser.probe 路由探测 X-Frame-Options/frame-ancestors,被拒站点显示原因 + 在浏览器中打开/仍然加载 - 设置页可滚动;内置 7 tab + 9 viewer;新增设计文档;268 个测试全绿
58 lines
2.6 KiB
TypeScript
58 lines
2.6 KiB
TypeScript
/**
|
|
* External-link interception policy tests: the pure decision (which http(s)
|
|
* external links are taken over into the sidebar browser) and the click
|
|
* shape (plain left clicks only — modified clicks always bypass).
|
|
*/
|
|
import { describe, expect, it } from 'vitest'
|
|
import { isPlainLeftClick, shouldInterceptLink } from '../src/client/link-intercept.ts'
|
|
|
|
const SELF = 'http://127.0.0.1:3080'
|
|
|
|
describe('shouldInterceptLink', () => {
|
|
it('takes over http(s) external links', () => {
|
|
expect(shouldInterceptLink('https://example.com/page?a=1', SELF)).toBe('https://example.com/page?a=1')
|
|
expect(shouldInterceptLink('http://example.com/', SELF)).toBe('http://example.com/')
|
|
})
|
|
|
|
it('ignores non-http(s) links (mailto:, javascript:, file:)', () => {
|
|
expect(shouldInterceptLink('mailto:a@b.c', SELF)).toBeNull()
|
|
expect(shouldInterceptLink('javascript:void(0)', SELF)).toBeNull()
|
|
expect(shouldInterceptLink('file:///tmp/x.html', SELF)).toBeNull()
|
|
})
|
|
|
|
it('ignores unparsable hrefs', () => {
|
|
expect(shouldInterceptLink('not a url', SELF)).toBeNull()
|
|
})
|
|
|
|
it('never takes over same-origin (GUI-internal) links', () => {
|
|
expect(shouldInterceptLink('http://127.0.0.1:3080/settings', SELF)).toBeNull()
|
|
expect(shouldInterceptLink('http://127.0.0.1:3080/chat/session-x', SELF)).toBeNull()
|
|
// A different port of the same host is external.
|
|
expect(shouldInterceptLink('http://127.0.0.1:9999/', SELF)).toBe('http://127.0.0.1:9999/')
|
|
})
|
|
|
|
it("takes over LAN hosts (the browser's own blocklist, not the link policy)", () => {
|
|
// The link takeover funnels into the sidebar browser, which then applies
|
|
// its own loopback/self address-bar policy on render — the interception
|
|
// itself must not silently drop links the browser will surface as blocked.
|
|
expect(shouldInterceptLink('http://192.168.1.1/', SELF)).toBe('http://192.168.1.1/')
|
|
})
|
|
})
|
|
|
|
describe('isPlainLeftClick', () => {
|
|
const plain = { button: 0, metaKey: false, ctrlKey: false, shiftKey: false, altKey: false }
|
|
|
|
it('accepts a plain left click', () => {
|
|
expect(isPlainLeftClick(plain)).toBe(true)
|
|
})
|
|
|
|
it('bypasses modified clicks (Ctrl/Cmd/Shift/Alt) and non-left buttons', () => {
|
|
expect(isPlainLeftClick({ ...plain, metaKey: true })).toBe(false)
|
|
expect(isPlainLeftClick({ ...plain, ctrlKey: true })).toBe(false)
|
|
expect(isPlainLeftClick({ ...plain, shiftKey: true })).toBe(false)
|
|
expect(isPlainLeftClick({ ...plain, altKey: true })).toBe(false)
|
|
expect(isPlainLeftClick({ ...plain, button: 1 })).toBe(false)
|
|
expect(isPlainLeftClick({ ...plain, button: 2 })).toBe(false)
|
|
})
|
|
})
|