JS函数两次调用window.open非Firefox浏览器仅打开单窗口问题
问题描述
现有一个表单,通过带间隔暂停逻辑的JS函数实现勾选选项后打开两个新窗口的功能。该脚本在Firefox中可正常运行,但在Chrome、Edge、Safari等其他浏览器中仅能打开第一个窗口,第二个窗口无法正常弹出。
原始JS代码如下:
function win_open1() { var form = document.getElementById('form'); form.addEventListener('submit', async function(e) { e.preventDefault(); if (document.getElementById('vl').checked == true) { var dok = document.getElementById('vl').value; } else { var dok = ''; } if (document.getElementById('vb01').checked == true) { let href = document.getElementById('va').value + document.getElementById('vb01').value + dok; window.open(href, '_blank'); await new Promise(r => setTimeout(r, 3000)); } if (document.getElementById('vb02').checked == true) { let href = document.getElementById('va').value + document.getElementById('vb02').value + dok; window.open(href, '_blank'); await new Promise(r => setTimeout(r, 3000)); } }); }
对应HTML表单代码:
<form id="form"> <input name="va" id="va" type="hidden" value="https://domain/index.php?op=gt&lang=pol&bdm="> Type: A: <input type="checkbox" id="vb01" name="vb" value="B&w=01ds&rid=B"> B: <input type="checkbox" id="vb02" name="vb" value="S&w=01ds&rid=S"> <button type="submit" onclick="win_open1()">--- search ---</button> </form>
故障原因
- 除Firefox外,所有主流浏览器都有统一的弹窗拦截规则:只有用户直接交互触发的同步执行流中调用的
window.open,才会被判定为用户主动发起的合法弹窗,其余异步回调中触发的window.open会被直接判定为广告弹窗拦截 - 原代码第一次调用
window.open时,还在用户点击提交的同步执行上下文里,所以可以正常打开;但await加3秒延时之后,执行上下文已经脱离了用户原始交互的调用栈,第二次window.open直接被浏览器拦截,无法弹出 - 原代码还有隐藏bug:每次点击提交按钮都会调用
win_open1()给表单重复绑定submit事件,点击次数越多,绑定的监听函数越多,后续会出现一次点击触发多次弹窗的问题
修复方案
核心思路:在用户点击的同步执行阶段,提前创建好所有需要的窗口拿到引用,后续延迟跳转只需要修改已有窗口的地址即可,不需要再次调用window.open,就不会触发拦截规则。
修复后的JS代码:
// 页面加载完成后只绑定一次表单提交事件,不要放在按钮点击回调里重复绑定 document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('form'); form.addEventListener('submit', e => { e.preventDefault(); const dok = document.getElementById('vl').checked ? document.getElementById('vl').value : ''; const baseUrl = document.getElementById('va').value; // 第一个勾选的窗口直接打开 if (document.getElementById('vb01').checked) { const url1 = baseUrl + document.getElementById('vb01').value + dok; window.open(url1, '_blank'); } // 第二个需要延迟打开的窗口,同步阶段先开空白页拿到合法窗口引用 if (document.getElementById('vb02').checked) { const url2 = baseUrl + document.getElementById('vb02').value + dok; const delayWin = window.open('about:blank', '_blank'); // 延时后给提前拿到的窗口设置跳转地址,不会触发拦截 setTimeout(() => { delayWin.location.href = url2; }, 3000); } }); });
修复后的HTML代码,去掉按钮上冗余的onclick属性,避免重复绑定事件:
<form id="form"> <input name="va" id="va" type="hidden" value="https://domain/index.php?op=gt&lang=pol&bdm="> Type: A: <input type="checkbox" id="vb01" name="vb" value="B&w=01ds&rid=B"> B: <input type="checkbox" id="vb02" name="vb" value="S&w=01ds&rid=S"> <button type="submit">--- search ---</button> </form>
注意事项
- 永远不要在
setTimeout、Promise回调、接口请求回调等异步逻辑里直接调用window.open,必然会被大部分浏览器拦截 - 如果需要延迟跳转/打开页面,必须在用户交互的同步阶段提前拿到窗口对象引用,后续通过修改引用的
location属性实现跳转 - 事件监听只需要绑定一次,不要在触发元素的回调里重复给父元素绑定监听,会导致逻辑重复执行
内容的提问来源于stack exchange,提问作者Marcin
相关产品推荐
相关产品推荐

