兼容浏览器的iframe加载完成事件

在网页中点击某个按钮或链接下载文件的时候,从点击到文件开始下载的过程中有一个过程,如果没有对这个过程进行判断的话,用户会一直不停地进行点击,体验非常差。浏览器没有办法直接获取到下载文件的事件,一个讨巧的方法是可以使用iframe的onload事件,代码如下:

var url = "http://www.example.com/file.zip";
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
iframe.onload = function() {
console.debug('start downloading...');
document.body.removeAttribute(iframe);
}
document.body.appendChild(iframe);

这段代码在Firefox下面可以正常获取到onload的事件,但在Google Chrome和IE下面,如果iframe src属性的链接是一个网页的话,可以触发onload事件,但如果HTTP文件头中包含Content-disposition: attachment...即下载文件的链接的话,不会触发这个事件。浏览器对iframe具体的支持情况可见:Events to expect when dynamically loading iframes in javascript

这一点有点可惜,有人提出了一个利用Cookies的思路,即在服务器端捕获文件下载的进度,然后随时将进度写入到Cookies中,前台javascript轮询Cookies,从而得到文件是否开始进行下载。这种方案详细的可以参考:Detecting the File Download Dialog In the Browser,后台如果使用Spring的话可以通过过滤器来判断文件下载的情况,详细情况见 http://xiaoz5919.iteye.com/blog/1259561

但这种方案实施起来需要后台进行配合,前后台的代码都过于复杂,另外如果浏览器将Cookies功能关闭的话就会出错,后来无意中在Google Chrome下面运行这个Plunker代码,发现可以正常捕获iframe加载后的时间。原始代码是使用的AngularJS,代码如下:

var url = 'https://github.com/blueimp/jQuery-File-Upload/archive/master.zip';
var e = angular.element("");
e.attr('src', url);
e.load(function() {
console.debug('start downloading...');
});

angular.element('body').append(e);

这里使用了element的load事件,即页面实际加载DOM元素的事件。但非常奇怪的是,如果我将上述代码中的url换成其他的下载文件之后依然无法激活这个事件。后来仔细分析对比了二者中的HTTP Response Header的差异,发现需要在服务器端增加下面两个HTTP头文件内容即可:

// 不让浏览器自动检测文件类型,说明资料:http://drops.wooyun.org/tips/1166
response.addHeader("X-Content-Type-Options", "nosniff");
// 提示浏览器不让其在frame或iframe中加载资源的文件内容 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options
response.addHeader("X-Frame-Options", "deny");

如果使用jQuery的话,javascript代码如下:

var url = 'https://github.com/blueimp/jQuery-File-Upload/archive/master.zip';
var e = $("");
e.attr('src', url);
e.load(function() {
console.debug('start downloading...');
});

$('body').append(e);

PS(2017-5-4):最新版本Google Chrome(本地使用的是v58)开始如果将X-Frame-Options设置成deny的话会出现下面的异常信息:

Refused to display 'https://github.com/blueimp/jQuery-File-Upload/archive/master.zip' in a frame because it set 'X-Frame-Options' to 'deny'.

并且下载的时候网络连接会出现中断。

参考 http://stackoverflow.com/a/30065720/1805493 所提供的思路,可以设置一个定时器检测readyState的状态,如果为complete或interactive的话说明文件加载完成,并且这种方法不需要在服务器端进行设置,代码如下:

var url = 'https://github.com/blueimp/jQuery-File-Upload/archive/master.zip';
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.style.display = 'none';
document.body.appendChild(iframe);
var timer = setInterval(function() {
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc.readyState == 'complete' || iframeDoc.readyState == 'interactive') {
console.debug('start downloading...');
document.body.removeAttribute(iframe);
clearInterval(timer);
return;
}
}, 1000);

当然,需要在服务器端将X-Frame-Options的头信息去除。

参考资料:
'load' event not firing when iframe is loaded in Chrome

2 thoughts to “兼容浏览器的iframe加载完成事件”

  1. 你好,请问电脑浏览器打开客户端怎么做 客户端启动链接是这样的"UpdateClient://",怎么在她打开之后执行onload

发表评论

电子邮件地址不会被公开。 必填项已用*标注