Commit 47c079ef by luoqi

fix(web): 新标签页改一步 open,别再"先开 about:blank 再导航"

宿主加上 allow-popups 后炸了两条,都是上一版那个两步走导致的:
  Unsafe attempt to initiate navigation for frame with URL 'about:blank' … The frame
  attempting navigation is sandboxed and is trying to navigate a popup, but is not the
  popup's opener and is not set to propagate sandboxing to popups.
  Uncaught SecurityError: Failed to execute 'replace' on 'Location': The current window
  does not have permission to navigate the target frame to '<host url>'.

因果:**opener 一断,我们就不再是那个弹窗的 opener**,而沙箱化的 frame 只有作为 opener 才有权
导航它 → 第二步 location.replace 被拒,新标签页停在空白页。顺序反过来也不行(导航到跨源之后
再设 opener 会抛)。上一版之所以两步走,是想"手工断 opener 以等效 noopener、同时保留可检测性"
—— 这个组合在沙箱下不成立。

改成一步 `window.open(url, '_blank')`:
  · 可检测性仍在 —— 被拦时返回 null(features 里**不写** noopener;写了成功也返回 null,
    那才是分不清被拦和成功的写法)
  · opener 只做尽力而为的切断(跨源会抛,catch 掉)。可接受:目标是宿主管理页配好的自家地址,
    不是用户输入的任意站点,tabnabbing 面本来就很窄。
三级兜底(新页 → 顶层 → 本 frame)保持不变。

交付文档补上:只给 allow-popups 时新标签页会**继承沙箱**,宿主自己的页面在里面可能功能不全,
建议连 allow-popups-to-escape-sandbox 一起加,并给出完整推荐的 sandbox 串。

️ 内嵌浏览器面板自身拦弹窗,"正常开出新标签页"这一支我这边仍证不了;
但两步导航已从结构上去掉,SecurityError 那类报错不会再有。web typecheck 通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 5abea1b8
Pipeline #3487 failed in 0 seconds
......@@ -144,6 +144,12 @@ PAC 侧已经用 `targetOrigin` 定向发送(不广播),但那只防"发错人",
- **iframe sandbox 别漏权限**。若你们给 iframe 加了 `sandbox`,至少要有
`allow-scripts allow-same-origin`;**动作要开新标签页则必须加 `allow-popups`**
(否则浏览器直接拦掉,控制台报 `Blocked opening ... 'allow-popups' permission is not set`,
PAC 会退化成整页跳转)。想让新页不继承沙箱再加 `allow-popups-to-escape-sandbox`。
PAC 会退化成整页跳转)。
**建议再加 `allow-popups-to-escape-sandbox`** —— 只给 `allow-popups` 时新标签页会**继承沙箱**,
你们自己的页面在里面可能功能不全(存储受限、表单提交被挡等)。推荐:
```html
sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation"
```
- **认领前点不到**。这些按钮在 PAC 侧过「认领闸」——工单没被客服认领时点了会被拦,
是有意为之(没有归属人后面对不上账),不是 bug。
......@@ -69,22 +69,35 @@ export const HOST_LINK_PROPS = { target: '_blank', rel: 'noopener noreferrer' }
* sandbox 若也没给 allow-top-navigation,赋值会抛 SecurityError,继续降级
* ③ 本 frame 内跳转 —— 一定成立,PAC 自己被替换掉(客服可用宿主的返回回来)
*
* ⚠️ 检测被拦**不能靠 window.open 的返回值配 noopener**:features 里带 noopener 时
* 规范规定**成功也返回 null**,那样永远分不清是被拦还是开成功了。
* 所以改成先开同源 about:blank、拿到句柄后手工断 opener 再导航 —— 效果同 noopener,但可检测。
* ⚠️ **必须一步 open 带上 URL,不能"先开 about:blank 再导航"**(2026-07-30 踩过):
* 那一版是为了"开完手工断 opener 以等效 noopener,同时保留可检测性"。宿主加上 allow-popups 之后
* 立刻炸了两条:
* `Unsafe attempt to initiate navigation for frame with URL 'about:blank' … The frame
* attempting navigation is sandboxed and is trying to navigate a popup, but is not the
* popup's opener and is not set to propagate sandboxing to popups.`
* `Uncaught SecurityError: Failed to execute 'replace' on 'Location': The current window
* does not have permission to navigate the target frame to '<host url>'.`
* 因果很直白:**opener 一断,我们就不再是那个弹窗的 opener**,而沙箱化的 frame 只有作为 opener
* 才有权导航它 —— 于是第二步 `location.replace` 被拒,新标签页停在空白页。
* 顺序反过来(先导航后断 opener)也不行:导航到跨源之后再设 opener 会抛。
*
* 根治仍在宿主侧:请对接方给 iframe 的 sandbox 加上 `allow-popups`(想保留新页则再加
* `allow-popups-to-escape-sandbox`,否则新页会继承沙箱限制)。这里只是让 PAC 别装死。
* 所以结论是:**一步到位 `window.open(url, '_blank')`**。
* · 可检测性还在 —— 被弹窗拦时返回 null(features 里**不写** noopener;写了成功也返回 null,
* 那才是分不清被拦和成功的那种写法)
* · opener 只做**尽力而为**的切断(跨源会抛,catch 掉即可)。这里能接受:目标 URL 是宿主管理页
* 配好的自家地址,不是用户输入的任意站点,tabnabbing 面本来就很窄。
*
* 根治仍在宿主侧:`sandbox` 要有 `allow-popups`;**并且建议加 `allow-popups-to-escape-sandbox`**
* —— 只给 allow-popups 时新标签页会继承沙箱,宿主自己的页面在里面可能功能不全。
*/
export function openHostUrl(url: string): void {
const opened = window.open('', '_blank');
const opened = window.open(url, '_blank');
if (opened) {
try {
opened.opener = null; // about:blank 同源,能设;等价于 noopener,防 tabnabbing
opened.opener = null; // 尽力而为:同源能断,跨源会抛(见上方注释,不影响已经开出来的页)
} catch {
/* 设不上就算了 —— 比开不出页面轻得多 */
/* 断不掉就算了 —— 目标是宿主自家配置的地址 */
}
opened.location.replace(url);
return;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment