【问题标题】:How can I call revokeObjectURL only after the URL has loaded?如何仅在 URL 加载后调用 revokeObjectURL?
【发布时间】:2015-11-17 18:49:01
【问题描述】:

我有一个网络应用程序。它在 Google Chrome 中运行,不需要在任何其他浏览器中运行。

我有在服务器上生成并通过 AJAX 请求发送回客户端的 PDF 数据。 我从 PDF 数据创建了一个 blob。 我使用 window.URL.createObjectURL 从 blob 创建一个 URL,然后将其加载到以前创建的用于显示 PDF 的窗口(我的 preview_window)中。

为了加载 URL,我设置了 preview_window.location.href。

我想调用 revokeObjectURL 以避免在窗口中生成和预览新 PDF 时浪费越来越多的资源。 问题是在设置 preview_window.location.href 后立即调用它为时过早,并阻止了 PDF 的显示。因此,我想仅在加载 URL 后调用 revokeObjectURL。 为此,我尝试将 preview_window.onload 设置为回调,但它永远不会被调用。

我想知道:

  1. 是否可以在窗口加载 URL 时触发回调,就像我正在尝试的那样?怎么样?
  2. 是否有其他方法可以确保及时调用 revokeObjectURL?

如果在窗口完成加载 URL 时我无法触发 revokeObjectURL,我可以在生成新 URL 之前立即撤消每个 URL。但如果可能的话,我宁愿在 URL 加载完成后立即撤消它。

我已经准备了一个 html 文件,它很好地展示了这种情况:

<html>
    <head>
        <title>Show PDF Demo</title>
        <script>
            var build_blob = function(mime_type, data) {
                var buf = new ArrayBuffer(data.length);
                var ia = new Uint8Array(buf);
                for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
                var blob = new Blob([ buf ], { type: mime_type });
                return blob;
            };

            window.onload = function(e) {
                document.getElementById('preview_button').onclick = function(e) {

                    // open the window in the onclick handler so we don't trigger popup blocking
                    var preview_window = window.open(null, 'preview_window');

                    // use setTimeout to simulate an asynchronous AJAX request
                    setTimeout(function(e) {
                        var pdf_data = atob(
                            "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZwovT3V0bGluZXMgMiAwIFIKL1BhZ2Vz" +
                            "IDMgMCBSCj4+CmVuZG9iagoyIDAgb2JqCjw8IC9UeXBlIC9PdXRsaW5lcwovQ291bnQgMAo+Pgpl" +
                            "bmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+Pgpl" +
                            "bmRvYmoKNCAwIG9iago8PCAvVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAw" +
                            "IDUwMCAyMDBdCi9Db250ZW50cyA1IDAgUgovUmVzb3VyY2VzIDw8IC9Qcm9jU2V0IDYgMCBSCi9G" +
                            "b250IDw8IC9GMSA3IDAgUiA+Pgo+Pgo+PgplbmRvYmoKNSAwIG9iago8PCAvTGVuZ3RoIDczID4+" +
                            "CnN0cmVhbQpCVAovRjEgMjQgVGYKMTAwIDEwMCBUZAooU01BTEwgVEVTVCBQREYgRklMRSkgVGoK" +
                            "RVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqClsvUERGIC9UZXh0XQplbmRvYmoKNyAwIG9iago8" +
                            "PCAvVHlwZSAvRm9udAovU3VidHlwZSAvVHlwZTEKL05hbWUgL0YxCi9CYXNlRm9udCAvSGVsdmV0" +
                            "aWNhCi9FbmNvZGluZyAvTWFjUm9tYW5FbmNvZGluZwo+PgplbmRvYmoKeHJlZgowIDgKMDAwMDAw" +
                            "MDAwMCA2NTUzNSBmCjAwMDAwMDAwMDkgMDAwMDAgbgowMDAwMDAwMDc0IDAwMDAwIG4KMDAwMDAw" +
                            "MDEyMCAwMDAwMCBuCjAwMDAwMDAxNzkgMDAwMDAgbgowMDAwMDAwMzY0IDAwMDAwIG4KMDAwMDAw" +
                            "MDQ2NiAwMDAwMCBuCjAwMDAwMDA0OTYgMDAwMDAgbgp0cmFpbGVyCjw8IC9TaXplIDgKL1Jvb3Qg" +
                            "MSAwIFIKPj4Kc3RhcnR4cmVmCjYyNQolJUVPRg=="
                        );

                        /*
                            Warning: for my Chrome (Version 44.0.2403.155 m), the in-built PDF viewer doesn't seem 
                            to work with a blob when this html page is loaded from the local filesystem.  I have only 
                            got this to work when fetching this page via HTTP.
                        */

                        var pdf_blob = build_blob('application/pdf', pdf_data);

                        var pdf_url = window.URL.createObjectURL(pdf_blob);

                        preview_window.onload = function(e) {
                            console.log("preview_window.onload called");  // never happens
                            window.URL.revokeObjectURL(pdf_url);
                        };

                        preview_window.location.href = pdf_url;
                        console.log("preview_window.location.href set");

                    }, 500);
                };
            };
        </script>
    </head>
    <body>
        <button id="preview_button">Show Preview</button>
    </body>
</html>

虽然我上面的演示代码避免了它,但我确实为我的应用程序加载了 jQuery,所以如果这让事情变得更容易,我愿意使用它。

我确实在搜索中找到了this question,但在这种情况下,主窗口(“窗口”)指向一个新的 URL,并且当在 cmets 中询问是否有区别时,OP 从未得到响应,如果window 来自 window.open。

【问题讨论】:

  • 为什么不在每次新通话时调用它?
  • @Kaiido,如果我找不到更好的方法,我会这样做。它肯定会停止浪费内存的积累。但似乎应该可以知道什么时候我不再需要为浏览器保留链接,并更快地释放内存。
  • 好吧,我认为检测open()ed 窗口是否已加载的唯一方法是在其中注入一些代码,使其referer 知道它。由于您正在打开一个 PDF,它将完全由浏览器重新解析,我会说这是您唯一的选择。但也许这里有人会让我撒谎。
  • 是否可以在窗口中放置一个 iframe,在 iframe 中加载 PDF,并在 iframe 上放置一个回调,以便在加载 PDF 时实际调用?
  • @Kaiido,这种方法对我来说听起来太雄心勃勃了。如果有人能写出一个听起来足够权威的答案,说明 Chrome 无法在将 PDF 加载到另一个窗口时触发回调,我应该能够接受它并在创建新 URL 之前撤销每个 URL。

标签: javascript google-chrome pdf onload revokeobjecturl


【解决方案1】:

正如您所发现的,您无法从开启程序设置open()ed windows 的 onload 事件。 您必须在第二页中注入一些脚本,该脚本将调用其window.opener 函数。

但由于您打开的是 pdf 文件,浏览器将重新解析整个页面,您注入的代码将消失。

正如您在 cmets 中发现的那样,解决方案是将 blob 的 url 注入 iframe,然后等待该 iframe 的加载事件。

方法如下:

index.html

    <script>
        // The callback that our pop-up will call when loaded
        function imDone(url){
            window.URL.revokeObjectURL(url);
            }

        var build_blob = function(mime_type, data) {
            var buf = new ArrayBuffer(data.length);
            var ia = new Uint8Array(buf);
            for (var i = 0; i < data.length; i++) ia[i] = data.charCodeAt(i);
            var blob = new Blob([ buf ], { type: mime_type });
            return blob;
        };
        var preview_window=null;
        window.onload = function(e) {
            document.getElementById('preview_button').onclick = function(e) {
              if(preview_window===null || preview_window.closed){
                // open the window in the onclick handler so we don't trigger popup blocking
                preview_window = window.open('html2.html', 'preview_window');
              }
               // avoid reopening the window since it may cache our last blob
              else preview_window.focus();

                // use setTimeout to simulate an asynchronous AJAX request
                setTimeout(function(e) {
                    var pdf_data = /* Your pdf data */

                    var pdf_blob = build_blob('application/pdf', pdf_data);

                    var pdf_url = window.URL.createObjectURL(pdf_blob);
                    // Simple loop if our target document is not ready yet
                    var loopLoad = function(url){
                      var doc = preview_window.document;
                      if(doc){
                        var iframe = doc.querySelector('iframe');
                        if(iframe)iframe.src = url;
                        else setTimeout(function(){loopLoad(url);},200);
                      }
                      else setTimeout(function(){loopLoad(url);},200)
                    };
                    loopLoad(pdf_url);

                }, 0);
            };
        };
    </script>

html2.html

<html>
    <head>
        <title>Iframe PDF Demo</title>
        <style>
            body, html, iframe{margin:0; border:0}
        </style>
    </head>
    <body>
    <iframe width="100%" height="100%"></iframe>
    <script>
        document.querySelector('iframe').onload = function(){
            //first check that our src is set
            if(this.src.indexOf('blob')===0)
                // then call index.html's callback
                window.opener.imDone(this.src);
            }
    </script>
    </body>
</html>

Live Demo

【讨论】:

  • 当我在我的 Chrome 上测试它时,它的行为有点狡猾,显然是由于竞争条件。我在实践中通过仅在第一次打开窗口时加载 html2 (updated demo) 使其表现良好,但那里仍然存在竞争条件,可以通过将超时从 500 减少到 0 来看出。避免竞争条件是...从 preview_window.onload 处理程序设置 iframe src,但这当然行不通。
  • 我没有这个错误,你的pdf根本没有加载吗?通常,如何调用 html2 并不重要,因为回调只会在 iframe 加载时触发。
  • 症状是第二次和随后的预览按钮很少显示PDF。大多数情况下,他们只是给出一个空白窗口。推后我在等待几秒钟。我很确定这是因为 old html2 的 iframe src 是在新的 html2 加载到窗口中之前设置的。对我来说,延迟是 500,但我很确定如果你把它减少到 0,你会看到和我一样的结果。
  • 好的,我能够重现该错误,我认为这是因为在我们尝试设置 iframe src(使用循环功能修复)之前目标文档尚未加载因为对window.open() 的第二次调用将使目标窗口从缓存中加载,因此在我们告诉它将iframe src 设置为新的url 之前触发回调。编辑应该修复所有这些
  • 好的,现在我们正在轮询捕获 html2 何时加载,以便我们可以设置 iframe src。完成工作并按预期行事,所以我将接受这个答案。但是你也帮助我意识到(带着你最初的问题)在 createObjectURL 之前调用 revokeObjectURL 并不是一个糟糕的解决方案,所以我想我会在我的应用程序中这样做。
猜你喜欢
  • 2019-03-12
  • 2013-01-08
  • 1970-01-01
  • 2016-09-11
  • 2017-03-08
  • 1970-01-01
  • 2011-12-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多