写点什么

解决火狐新窗口打开网页被拦截问题

用户头像
Lee Chen
关注
发布于: 2020 年 07 月 19 日
解决火狐新窗口打开网页被拦截问题

阅读更多系列文章请访问我的GitHub 博客,本文示例代码请访问这里



Demo 运行方式



请在这里下载示例代码,并在终端中运行如下命令启动 Demo:



  1. 安装依赖



npm install



  1. 启动 Demo



npm start



示例代码的服务端,由Koa实现。



由于谷歌浏览器无此问题,请在**火狐浏览器**中,打开[http://localhost:8080/](http://localhost:8080/)进行测试。



问题描述



出现问题的场景



  1. 用户点击支付按钮。



  1. 前端发起一个 AJAX 请求到服务端。



  1. 服务端返回一个由支付宝生成的表单,其中带有支付页地址,以及相关参数。



  1. 前端使用表单的参数,在新窗口打开支付宝的支付页面。



在火狐浏览器中,该操作会被浏览器拦截,如下图所示:





结论是,只要在 AJAX 请求后,用 JavaScript 触发的跳转,都会被火狐拦截。



能够正常打开的场景



当然,火狐并非对所有场景都进行了拦截,例如这些操作:



  1. 进行 AJAX 请求后,在当前窗口打开链接。



  1. 由用户直接点击后,在新窗口打开链接。



前端示例代码



// 通过直接点击,可以在新窗口打开。
// 通过a标签打开和表单提交同样可以。
document
.querySelector('#open')
.addEventListener('click', function () {
window.open(action);
});

// 通过计时器延迟之后,依然可以在新窗口打开。
document
.querySelector('#open')
.addEventListener('click', function () {
setTimeout(() => {
window.open(action);
}, 100);
});



失败的解决方案



列举几个失败的方案,可以在http://localhost:8080/中点击相应按钮查看效果。



前端示例代码



  1. window.open



// 获取支付宝支付的表单
async function getFormStr() {
const response = await fetch('/getFormStr');
const result = await response.json();

document.querySelector('#formWrapper').innerHTML = result.formStr;

const action = document
.querySelector('#formWrapper form')
.getAttribute('action');

return action;
}

// 方法1:window.open
document
.querySelector('#method1')
.addEventListener('click', async function () {
const action = await getFormStr();

window.open(action);
});



  1. form.submit



// 方法2:form.submit
document
.querySelector('#method2')
.addEventListener('click', async function () {
const action = await getFormStr();
let formEle = document.createElement('form');

formEle.style.display = 'none';
formEle.method = 'post';
formEle.target = '_blank';
formEle.action = action;

document.body.appendChild(formEle);

formEle.submit();
});



  1. a 标签打开



// 方法3:a标签打开
document
.querySelector('#method3')
.addEventListener('click', async function () {
const action = await getFormStr();
let anchorEle = document.createElement('a');

anchorEle.href = action;
anchorEle.target = '_blank';

document.body.appendChild(anchorEle);

anchorEle.click();
});



服务端示例代码



// 获取支付宝支付的表单
router.get('/getFormStr', async (ctx, next) => {
const response = await fetch(
'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
);
const result = await response.json();

ctx.body = result;

await next();
});



成功的解决方案



该问题的解决思路如下:



  1. 在前端用 form 表单,打开新窗口提交参数。



  1. 由服务端进行处理之后,直接重定向到支付宝的支付页。



前端示例代码:



  1. 在页面中创建一个表单,存储相应数据。



<button id="method4">方法4:服务端重定向</button>
<form id="redirectForm" action="/redirectForm" target="_blank"></form>



  1. 点击按钮之后,打开新窗口,并提交表单。



// 方法4:服务端重定向
document
.querySelector('#method4')
.addEventListener('click', async function () {
document.querySelector('#redirectForm').submit();
});



服务端示例代码如下:



// 获取支付宝支付的表单,之后重定向到支付宝支付页
router.get('/redirectForm', async (ctx, next) => {
const response = await fetch(
'http://rap2.taobao.org:38080/app/mock/250475/getFormStr',
);
const result = await response.json();

// 进行Entity转换
const decodedFormStr = he.decode(result.formStr, {
isAttributeValue: true,
});
const decodedFormStrArr = decodedFormStr.split('\n');
// 截取表单中的有用参数,并拼接成跳转支付宝的URL
const aliPayUrl = `${decodedFormStrArr[0].match(/action="([\s|\S]*)">/)[1]}&${
decodedFormStrArr[1].match(/name="([\s|\S]*)" value="/)[1]
}=${decodedFormStrArr[1].match(/value="([\s|\S]*)">/)[1]}`;

ctx.response.redirect(aliPayUrl);
await next();
});



Entity 的转换



由于支付宝返回的表单中,有 &quot; 字符,即为 Entity(实体),如果直接表单写入页面,浏览器能够正确将其识别成 "



如果你想要查看完整的 Entity 列表,可以点击这里



但在服务端只能将其当做字符串处理,因此需要用he库进行转换,避免参数拼接出错,如下:



// 进行Entity转换
const decodedFormStr = he.decode(result.formStr, {
isAttributeValue: true,
});



小结



  1. 现在前端的能力越来越强,但始终还是有其局限性,解决问题的时候不应当把思路局限在前端领域,要充分利用服务端的资源。



  1. Entity 虽然很少使用,但遇到的时候可以通过he库进行转换。



发布于: 2020 年 07 月 19 日阅读数: 71
用户头像

Lee Chen

关注

还未添加个人签名 2018.08.29 加入

还未添加个人简介

评论

发布
暂无评论
解决火狐新窗口打开网页被拦截问题