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

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

Demo 运行方式

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

  1. 安装依赖
npm install
复制代码
  1. 启动 Demo
npm start
复制代码

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

因为谷歌浏览器无此问题,请在**火狐浏览器**中,打开http://localhost:8080/进行测试。git

问题描述

出现问题的场景

  1. 用户点击支付按钮。github

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

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

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

在火狐浏览器中,该操做会被浏览器拦截,以下图所示:bash

被火狐浏览器拦截

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

可以正常打开的场景

固然,火狐并不是对全部场景都进行了拦截,例如这些操做:koa

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

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

前端示例代码

// 经过直接点击,能够在新窗口打开。
// 经过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 表单,打开新窗口提交参数。

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

前端示例代码:

  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. 如今前端的能力愈来愈强,但始终仍是有其局限性,解决问题的时候不该当把思路局限在前端领域,要充分利用服务端的资源。

  2. Entity 虽然不多使用,但遇到的时候能够经过he库进行转换。

相关文章
相关标签/搜索