【问题标题】:Chrome extension: How to show custom UI for a PDF file?Chrome 扩展:如何显示 PDF 文件的自定义 UI?
【发布时间】:2015-03-02 11:14:21
【问题描述】:

我正在尝试编写一个用于显示 PDF 文件的 Google Chrome 扩展程序。一旦我检测到浏览器正在重定向到指向 PDF 文件的 URL,我希望它停止加载默认的 PDF 查看器,而是开始显示我的 UI。 UI 将使用 PDF.JS 来呈现 PDF,并使用 jQuery-ui 来显示其他内容。

问题:我怎么做这个?阻止原始 PDF 查看器非常重要,因为我不想通过显示文档的两个实例来增加内存消耗。因此,我应该以某种方式将选项卡导航到我自己的视图。

【问题讨论】:

    标签: javascript google-chrome google-chrome-extension pdf.js


    【解决方案1】:

    作为PDF.js Chrome extension 的主要作者,我可以分享一些关于为 Chrome 构建 PDF 查看器扩展程序背后的逻辑的见解。

    如何检测PDF文件?

    在一个完美的世界中,每个网站都将提供具有标准application/pdf MIME 类型的 PDF 文件。不幸的是,现实世界并不完美,实际上有许多网站使用了错误的 MIME 类型。您将通过选择满足以下任一条件的请求来捕获大多数情况:

    • 资源使用 Content-Type: application/pdf 响应标头提供服务。
    • 资源使用 Content-Type: application/octet-stream 响应标头提供服务,其 URL 包含“.pdf”(不区分大小写)。

    除此之外,您还必须检测用户是要查看 PDF 文件还是下载 PDF 文件。如果您不关心区别,这很容易:如果请求与前面的任何条件匹配,只需拦截请求即可。
    否则(这是我采用的方法),您需要检查 Content-Disposition 响应标头是否存在,其值是否以“attachment”开头。

    如果您想支持 PDF 下载(例如通过您的 UI),那么您需要添加 Content-Disposition: attachment 响应标头。如果标头已存在,则必须将现有处置类型(例如inline)替换为“附件”。不要费心尝试解析完整的标头值,只需将第一部分剥离到第一个分号,然后将“附件”放在它前面。 (如果你真的想解析头部,阅读RFC 2616 (section 19.5.1)RFC 6266)。

    我应该使用哪些 Chrome(扩展)API 来拦截 PDF 文件?

    chrome.webRequest API 可用于拦截和重定向请求。使用以下逻辑,您可以拦截 PDF 并将其重定向到从给定 URL 请求 PDF 文件的自定义查看器。

    chrome.webRequest.onHeadersReceived.addListener(function(details) {
        if (/* TODO: Detect if it is not a PDF file*/)
            return; // Nope, not a PDF file. Ignore this request.
    
        var viewerUrl = chrome.extension.getURL('viewer.html') +
          '?file=' + encodeURIComponent(details.url);
        return { redirectUrl: viewerUrl };
    }, {
        urls: ["<all_urls>"],
        types: ["main_frame", "sub_frame"]
    }, ["responseHeaders", "blocking"]);
    

    (请参阅https://github.com/mozilla/pdf.js/blob/master/extensions/chromium/pdfHandler.js,了解使用此答案顶部描述的逻辑的 PDF 检测的实际实现)

    使用上面的代码,你可以截取 http 和 https URL 上的任何 PDF 文件。 如果要从本地文件系统和/或 ftp 查看 PDF 文件,则需要使用 chrome.webRequest.onBeforeRequest 事件而不是 onHeadersReceived。幸运的是,您可以假设如果文件以“.pdf”结尾,那么资源很可能是 PDF 文件。但是,想要使用扩展程序查看本地 PDF 文件的用户必须在扩展程序设置页面上明确允许这样做。

    在 Chrome 操作系统上,使用 chrome.fileBrowserHandler API 将您的扩展程序注册为 PDF 查看器 (https://github.com/mozilla/pdf.js/blob/master/extensions/chromium/pdfHandler-vcros.js)。

    基于 webRequest API 的方法仅适用于顶级文档和框架中的 PDF,不适用于通过 &lt;object&gt;&lt;embed&gt; 嵌入的 PDF。尽管它们很少见,但我仍然想支持它们,因此我想出了一种非常规的方法来在这些上下文中检测和加载 PDF 查看器。可以在https://github.com/mozilla/pdf.js/pull/4549/files查看实现。这种方法依赖于这样一个事实,即当一个元素被放入文档时,它最终必须被渲染。渲染时,将应用 CSS 样式。当我在 CSS 中为嵌入/对象元素声明动画时,将触发动画事件。这些事件在文档中冒泡。然后我可以为此事件添加一个侦听器,并将对象/嵌入元素的内容替换为加载我的 PDF 查看器的 iframe。
    有几种方法可以替换元素或内容,但我使用Shadow DOM 来更改显示的内容而不影响页面中的 DOM。

    限制和注意事项

    这里描述的方法有一些限制:

    • 从服务器请求 PDF 文件至少两次:第一次是获取标题的通常请求,当扩展重定向到 PDF 查看器时该请求被中止。然后另一个请求请求实际数据。
      因此,如果一个文件只有效一次,则无法显示 PDF(第一个请求使 URL 无效,第二个请求失败)。

    • 此方法仅适用于 GET 请求。没有公共 API 可以直接从 Chrome 扩展 (crbug.com/104058) 中的请求中获取响应正文。

    • 让 PDF 为 &lt;object&gt;&lt;embed&gt; 元素工作的方法需要在每个页面上运行一个脚本。我对代码进行了剖析,发现对性能的影响可以忽略不计,但是如果要更改逻辑,仍然需要小心。
      (我首先尝试使用Mutation Observers 进行检测,这会使大型文档的页面加载速度降低 3-20%,并在复杂的 DOM 基准测试中导致额外的 1.5 GB 内存使用峰值。

    • 检测&lt;object&gt;/&lt;embed&gt;标签的方法可能仍然会导致任何基于NPAPI/PPAPI的PDF插件加载,因为它只替换了&lt;embed&gt;/&lt;object&gt;标签的内容,当它已经被插入并且呈现。 When a tab is inactive, animations are not scheduled, and hence the dispatch of the animation event will significantly be delayed.

    后记

    PDF.js 是开源的,您可以在https://github.com/mozilla/pdf.js/tree/master/extensions/chromium 查看 Chrome 扩展程序的代码。如果您浏览源代码,您会注意到代码比我在此处解释的要复杂一些。这是因为扩展程序无法在 onHeadersReceived 事件中重定向请求,直到我在几个月前实现它(crbug.com/280464,Chrome 35)。

    还有一些逻辑可以让多功能框中的 URL 看起来更好一些。

    PDF.js 扩展在不断发展,所以除非你想显着改变 PDF 查看器的 UI,我建议要求用户安装 PDF.js 的官方PDF Viewer in the Chrome Web Store,和/或在@987654337 上打开问题@ 用于合理的功能请求。

    【讨论】:

    • 感谢您的帮助。我能够编写一个示例来拦截指向 PDF 文件的所有 URL。但是,我有一个问题。不确定是否是 PDF.js 和/或我的 XHR 代码的问题。当我使用 PDFJS.getDocument(pdfUrl) 显示 pdf 文件的内容时,我在日志中看到很多错误:gist.github.com/another-guy/f750b2db3811c0ee7df8 我尝试使用 XHR 将 PDF 作为 blob 加载,然后输入它到 PDF.js。但是,即使我的 XHR 请求成功(HTTP 代码 200),我也会得到运行我的脚本而不是 PDF 内容的相同 html 页面。我做错了什么?
    • 哦,看起来我的 XHR 和 PDF.js 请求也被过滤了。 :) 现在我还需要将“常规”浏览器请求与我的扩展程序发出的请求区分开来。
    • @keykeeper 您应该确保添加 types: ["main_frame", "sub_frame"]` 过滤器,如我的回答所示。 Marco 的回答中省略了它,但这是错误的,您应该只重定向帧请求。
    • Спасибо!这有帮助!耶!
    • @JérômeMével 您所指的“pdf.js”与 Google Chrome 捆绑在一起,并且是 Chrome 的 PDF 嵌入实现的一部分。 This pdf.js file 与我的回答中的 PDF.js library 无关。请注意,我在之前的评论中说“~100% C++”,因为 PDFium 的 PDF 渲染逻辑是用 C++ 编写的,只有一小部分是用其他东西 (JavaScript) 编写的,以充当网页和PDFium。
    【解决方案2】:

    自定义 PDF 查看器

    基本上,要完成您想做的事情,您需要:

    1. 在加载 PDF 时插入其 URL;
    2. 停止加载 PDF;
    3. 启动您自己的 PDF 查看器并在其中加载 PDF。

    如何

    1. 使用chrome.webRequest API,您可以轻松侦听Chrome 发出的网络请求,更具体地说,是那些将加载.pdf 文件的请求。使用chrome.webRequest.onBeforeRequest事件可以监听所有以“.pdf”结尾的请求,并获取请求资源的URL。

    2. 创建一个页面,例如display_pdf.html,您将在其中显示 PDF 并对其进行任何操作。

    3. chrome.webRequest.onBeforeRequest 监听器中,防止资源被加载返回{redirectUrl: ...} 以重定向到您的display_pdf.html 页面。

    4. 将 PDF 的 URL 传递到您的页面。这可以通过多种方式完成,但对我来说,最简单的一种是在页面 url 的末尾添加编码的 PDF URL,就像编码的查询字符串一样,比如display_pdf.html?url=http%3A%2F%2Fwww.example.com%2Fexample.pdf

    5. 在页面内部,使用 JavaScript 获取 URL 并使用您想要的任何库处理和呈现 PDF,例如 PDF.js

    代码

    按照上述步骤,您的扩展程序将如下所示:

    <root>/
        /background.js
        /display_pdf.html
        /display_pdf.js
        /manifest.json
    

    首先,让我们看一下manifest.json文件:您需要声明webRequestwebRequestBlocking的权限,所以它应该是这样的: p>

    {
        "manifest_version": 2,
    
        "name": "PDF Test",
        "version": "0.0.1",
    
        "background": {
            "scripts": ["/background.js"] 
        },
    
        "permissions": ["webRequest", "webRequestBlocking", "<all_urls>"],
    }
    

    然后,在您的 background.js 中,您将监听 chrome.webRequest.onBeforeRequest 事件并使用您的自定义 display_pdv.html 页面的 URL 更新正在加载 PDF 的选项卡,如下所示:

    chrome.webRequest.onBeforeRequest.addListener(function(details) {
        var displayURL;
    
        if (/\.pdf$/i.test(details.url)) { // if the resource is a PDF file ends with ".pdf"
            displayURL = chrome.runtime.getURL('/display_pdf.html') + '?url=' + encodeURIComponent(details.url);
    
            return {redirectUrl: displayURL};
            // stop the request and proceed to your custom display page
        }   
    }, {urls: ['*://*/*.pdf']}, ['blocking']);
    

    最后,在您的 display_pdf.js 文件中,您将从查询字符串中提取 PDF 的 url 并使用它来做任何您想做的事情:

    var PDF_URL = decodeURIComponent(location.href.split('?url=')[1]);
    // this will be something like http://www.somesite.com/path/to/example.pdf
    
    alert('The PDF url is: ' + PDF_URL);
    // do something with the pdf... like processing it with PDF.js
    

    工作示例

    可以在 HERE 找到我上面所说的一个工作示例。

    文档链接

    我建议您查看上述指定 API 的官方文档,您可以通过以下链接找到:

    【讨论】:

    • PDF.js Chrome 扩展的第一个原型是按照您在此处描述的那样实现的,但它遇到了两个问题:1) 具有正确 MIME 类型 (application/pdf) 但没有 @不会显示 987654351@ 后缀(甚至像 http://example.com/file.pdf?whatever 这样的 URL)。 2) 巧合以.pdf 结尾但不是PDF 的资源也会被重定向。
    • @RobW 我期待这个确切的评论,啊,但是既然你提供了一个更完整的答案,而且解释 PDF 查看器背后的完整逻辑需要几页,我留下了我的答案“不完整"。
    • @RobW 顺便说一句,你确定检查application/octet-stream MIME-type 和~details.url.indexOf(".pdf") 是正确的吗?如果你加载一些"/file.pdf.zip" 怎么办?你不应该使用更完整的 RegExp 或其他东西来检查 url 吗?
    • 我在实践中没有见过 file.pdf.zip + application/octet-stream(但我见过这种 MIME 类型的 file.pdf)。即使扩展程序错误地将可下载文件检测为 PDF,用户也可以通过 UI 下载文件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-16
    • 1970-01-01
    • 2015-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多