/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* Copyright 2012 Mozilla Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* globals chrome */ 'use strict'; /** * @param {Object} details First argument of the webRequest.onHeadersReceived * event. The property "url" is read. * @return {boolean} True if the PDF download was initiated by PDF.js */ function isPdfDownloadable(details) { return details.url.indexOf('pdfjs.action=download') >= 0; } /** * Insert the content script in a tab which renders the PDF viewer. * @param {number} tabId ID of the tab used by the Chrome APIs. * @param {string} url URL of the PDF file. Used to detect whether the viewer * should be activated in a specific (i)frame. */ function insertPDFJSForTab(tabId, url) { chrome.tabs.executeScript(tabId, { file: 'insertviewer.js', allFrames: true, runAt: 'document_start' }, function() { chrome.tabs.sendMessage(tabId, { type: 'showPDFViewer', url: url }); }); } /** * Try to render the PDF viewer when (a frame within) a tab unloads. * This indicates that a PDF file may be loading. * @param {number} tabId ID of the tab used by the Chrome APIs. * @param {string} url The URL of the pdf file. */ function activatePDFJSForTab(tabId, url) { chrome.tabs.onUpdated.addListener(function listener(_tabId) { if (tabId === _tabId) { insertPDFJSForTab(tabId, url); chrome.tabs.onUpdated.removeListener(listener); } }); } /** * Get the header from the list of headers for a given name. * @param {Array} headers responseHeaders of webRequest.onHeadersReceived * @return {undefined|{name: string, value: string}} The header, if found. */ function getHeaderFromHeaders(headers, headerName) { for (var i=0; i 0; } } /** * Takes a set of headers, and set "Content-Disposition: attachment". * @param {Object} details First argument of the webRequest.onHeadersReceived * event. The property "responseHeaders" is read and * modified if needed. * @return {Object|undefined} The return value for the onHeadersReceived event. * Object with key "responseHeaders" if the headers * have been modified, undefined otherwise. */ function getHeadersWithContentDispositionAttachment(details) { var headers = details.responseHeaders; var cdHeader = getHeaderFromHeaders(headers, 'content-disposition'); if (!cdHeader) { cdHeader = {name: 'Content-Disposition'}; headers.push(cdHeader); } if (!/^attachment/i.test(cdHeader.value)) { cdHeader.value = 'attachment' + cdHeader.value.replace(/^[^;]+/i, ''); return { responseHeaders: headers }; } } chrome.webRequest.onHeadersReceived.addListener( function(details) { if (!isPdfFile(details)) return; if (isPdfDownloadable(details)) { // Force download by ensuring that Content-Disposition: attachment is set return getHeadersWithContentDispositionAttachment(details); } // Replace frame's content with the PDF viewer. // This approach maintains the friendly URL in the location bar. activatePDFJSForTab(details.tabId, details.url); return { responseHeaders: [ // Set Cache-Control header to avoid downloading a file twice // NOTE: This does not behave as desired, Chrome's network stack is // oblivious for Cache control header modifications. {name:'Cache-Control',value:'max-age=600'}, // Temporary render response as XHTML. // Since PDFs are never valid XHTML, the garbage is not going to be // rendered. insertviewer.js will quickly replace the document with // the PDF.js viewer. {name:'Content-Type',value:'application/xhtml+xml; charset=US-ASCII'}, ] }; }, { urls: [ '' ], types: ['main_frame', 'sub_frame'] }, ['blocking','responseHeaders']);