From 6175e4b526541aeacac267f07402c761ef6ab319 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 1 Jun 2012 14:17:09 -0700 Subject: [PATCH] Use already downloaded data for the open with/save as dialog. --- .../firefox/components/PdfStreamConverter.js | 93 ++++++++++++++----- extensions/firefox/tools/l10n.js | 4 +- src/api.js | 15 +++ src/worker.js | 4 + web/viewer.js | 82 ++++++++++++++-- 5 files changed, 165 insertions(+), 33 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 62d22162b..ff63ab52a 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -104,11 +104,18 @@ function ChromeActions(domWindow) { } ChromeActions.prototype = { - download: function(data) { + download: function(data, sendResponse) { + var originalUrl = data.originalUrl; + // The data may not be downloaded so we need just retry getting the pdf with + // the original url. + var blobUrl = data.blobUrl || originalUrl; + + var originalUri = NetUtil.newURI(originalUrl); + var blobUri = NetUtil.newURI(blobUrl); + let mimeService = Cc['@mozilla.org/mime;1'].getService(Ci.nsIMIMEService); var handlerInfo = mimeService. getFromTypeAndExtension('application/pdf', 'pdf'); - var uri = NetUtil.newURI(data); var extHelperAppSvc = Cc['@mozilla.org/uriloader/external-helper-app-service;1']. @@ -116,26 +123,46 @@ ChromeActions.prototype = { var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1']. getService(Ci.nsIWindowWatcher).activeWindow; var ioService = Services.io; - var channel = ioService.newChannel(data, null, null); - var listener = { - extListener: null, - onStartRequest: function(aRequest, aContext) { - this.extListener = extHelperAppSvc.doContent('application/pdf', - aRequest, frontWindow, false); - this.extListener.onStartRequest(aRequest, aContext); - }, - onStopRequest: function(aRequest, aContext, aStatusCode) { - if (this.extListener) - this.extListener.onStopRequest(aRequest, aContext, aStatusCode); - }, - onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, - aCount) { - this.extListener.onDataAvailable(aRequest, aContext, aInputStream, - aOffset, aCount); + var channel = ioService.newChannel(originalUrl, null, null); + + + NetUtil.asyncFetch(blobUri, function(aInputStream, aResult) { + if (!Components.isSuccessCode(aResult)) { + if (sendResponse) + sendResponse(true); + return; } - }; + // Create a nsIInputStreamChannel so we can set the url on the channel + // so the filename will be correct. + let channel = Cc['@mozilla.org/network/input-stream-channel;1']. + createInstance(Ci.nsIInputStreamChannel); + channel.setURI(originalUri); + channel.contentStream = aInputStream; + channel.QueryInterface(Ci.nsIChannel); + + var listener = { + extListener: null, + onStartRequest: function(aRequest, aContext) { + this.extListener = extHelperAppSvc.doContent('application/pdf', + aRequest, frontWindow, false); + this.extListener.onStartRequest(aRequest, aContext); + }, + onStopRequest: function(aRequest, aContext, aStatusCode) { + if (this.extListener) + this.extListener.onStopRequest(aRequest, aContext, aStatusCode); + // Notify the content code we're done downloading. + if (sendResponse) + sendResponse(false); + }, + onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, + aCount) { + this.extListener.onDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount); + } + }; - channel.asyncOpen(listener, null); + channel.asyncOpen(listener, null); + }); }, setDatabase: function(data) { if (inPrivateBrowsing) @@ -199,21 +226,39 @@ ChromeActions.prototype = { function RequestListener(actions) { this.actions = actions; } -// Receive an event and synchronously responds. +// Receive an event and synchronously or asynchronously responds. RequestListener.prototype.receive = function(event) { var message = event.target; + var doc = message.ownerDocument; var action = message.getUserData('action'); var data = message.getUserData('data'); + var sync = message.getUserData('sync'); var actions = this.actions; if (!(action in actions)) { log('Unknown action: ' + action); return; } - var response = actions[action].call(this.actions, data); - message.setUserData('response', response, null); + if (sync) { + var response = actions[action].call(this.actions, data); + message.setUserData('response', response, null); + } else { + var response; + if (!message.getUserData('callback')) { + doc.documentElement.removeChild(message); + response = null; + } else { + response = function sendResponse(response) { + message.setUserData('response', response, null); + + var listener = doc.createEvent('HTMLEvents'); + listener.initEvent('pdf.js.response', true, false); + return message.dispatchEvent(listener); + } + } + actions[action].call(this.actions, data, response); + } }; - function PdfStreamConverter() { } diff --git a/extensions/firefox/tools/l10n.js b/extensions/firefox/tools/l10n.js index b16636e31..df20ff577 100644 --- a/extensions/firefox/tools/l10n.js +++ b/extensions/firefox/tools/l10n.js @@ -9,7 +9,7 @@ // fetch an l10n objects function getL10nData(key) { - var response = FirefoxCom.request('getStrings', key); + var response = FirefoxCom.requestSync('getStrings', key); var data = JSON.parse(response); if (!data) console.warn('[l10n] #' + key + ' missing for [' + gLanguage + ']'); @@ -78,7 +78,7 @@ } window.addEventListener('DOMContentLoaded', function() { - gLanguage = FirefoxCom.request('getLocale', null); + gLanguage = FirefoxCom.requestSync('getLocale', null); translateFragment(); diff --git a/src/api.js b/src/api.js index 59f9661bf..7d65f96b4 100644 --- a/src/api.js +++ b/src/api.js @@ -151,6 +151,15 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { promise.resolve(this.pdfInfo.encrypted); return promise; }, + /** + * @return {Promise} A promise that is resolved with a TypedArray that has + * the raw data from the PDF. + */ + getData: function PDFDocumentProxy_getData() { + var promise = new PDFJS.Promise(); + this.transport.getData(promise); + return promise; + }, destroy: function PDFDocumentProxy_destroy() { this.transport.destroy(); } @@ -616,6 +625,12 @@ var WorkerTransport = (function WorkerTransportClosure() { this.messageHandler.send('GetDocRequest', {data: data, params: params}); }, + getData: function WorkerTransport_sendData(promise) { + this.messageHandler.send('GetData', null, function(data) { + promise.resolve(data); + }); + }, + getPage: function WorkerTransport_getPage(pageNumber, promise) { var pageIndex = pageNumber - 1; if (pageIndex in this.pagePromises) diff --git a/src/worker.js b/src/worker.js index a93c36e95..c1dfa79af 100644 --- a/src/worker.js +++ b/src/worker.js @@ -136,6 +136,10 @@ var WorkerMessageHandler = { handler.send('GetPage', {pageInfo: page}); }); + handler.on('GetData', function wphSetupGetData(data, promise) { + promise.resolve(pdfModel.stream.bytes); + }); + handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) { var pdfPage = pdfModel.getPage(data.pageIndex + 1); handler.send('GetAnnotations', { diff --git a/web/viewer.js b/web/viewer.js index 21a50e96b..f8837ba89 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -116,16 +116,19 @@ var RenderingQueue = (function RenderingQueueClosure() { var FirefoxCom = (function FirefoxComClosure() { return { /** - * Creates an event that hopefully the extension is listening for and will + * Creates an event that the extension is listening for and will * synchronously respond to. + * NOTE: It is reccomended to use request() instead since one day we may not + * be able to synchronously reply. * @param {String} action The action to trigger. * @param {String} data Optional data to send. * @return {*} The response. */ - request: function(action, data) { + requestSync: function(action, data) { var request = document.createTextNode(''); request.setUserData('action', action, null); request.setUserData('data', data, null); + request.setUserData('sync', true, null); document.documentElement.appendChild(request); var sender = document.createEvent('Events'); @@ -134,6 +137,39 @@ var FirefoxCom = (function FirefoxComClosure() { var response = request.getUserData('response'); document.documentElement.removeChild(request); return response; + }, + /** + * Creates an event that the extension is listening for and will + * asynchronously respond by calling the callback. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @param {Function} callback Optional response callback that will be called + * with one data argument. + */ + request: function(action, data, callback) { + var request = document.createTextNode(''); + request.setUserData('action', action, null); + request.setUserData('data', data, null); + request.setUserData('sync', false, null); + if (callback) { + request.setUserData('callback', callback, null); + + document.addEventListener('pdf.js.response', function listener(event) { + var node = event.target, + callback = node.getUserData('callback'), + response = node.getUserData('response'); + + document.documentElement.removeChild(node); + + document.removeEventListener('pdf.js.response', listener, false); + return callback(response); + }, false); + } + document.documentElement.appendChild(request); + + var sender = document.createEvent('HTMLEvents'); + sender.initEvent('pdf.js.message', true, false); + return request.dispatchEvent(sender); } }; })(); @@ -160,7 +196,7 @@ var Settings = (function SettingsClosure() { var database = null; var index; if (isFirefoxExtension) - database = FirefoxCom.request('getDatabase', null) || '{}'; + database = FirefoxCom.requestSync('getDatabase', null) || '{}'; else if (isLocalStorageEnabled) database = localStorage.getItem('database') || '{}'; else @@ -193,7 +229,7 @@ var Settings = (function SettingsClosure() { file[name] = val; var database = JSON.stringify(this.database); if (isFirefoxExtension) - FirefoxCom.request('setDatabase', database); + FirefoxCom.requestSync('setDatabase', database); else if (isLocalStorageEnabled) localStorage.setItem('database', database); }, @@ -224,6 +260,7 @@ var PDFView = { container: null, initialized: false, fellback: false, + pdfDocument: null, // called once when the document is loaded initialize: function pdfViewInitialize() { this.container = document.getElementById('viewerContainer'); @@ -348,6 +385,7 @@ var PDFView = { PDFView.loadingBar = new ProgressBar('#loadingBar', {}); } + this.pdfDocument = null; var self = this; self.loading = true; PDFJS.getDocument(parameters).then( @@ -384,9 +422,37 @@ var PDFView = { }, download: function pdfViewDownload() { + function noData() { + FirefoxCom.request('download', { originalUrl: url }); + } + var url = this.url.split('#')[0]; if (PDFJS.isFirefoxExtension) { - FirefoxCom.request('download', url); + // Document isn't ready just try to download with the url. + if (!this.pdfDocument) { + noData(); + return; + } + this.pdfDocument.getData().then( + function getDataSuccess(data) { + var bb = new MozBlobBuilder(); + bb.append(data.buffer); + var blobUrl = window.URL.createObjectURL( + bb.getBlob('application/pdf')); + + FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url }, + function response(err) { + if (err) { + // This error won't really be helpful because it's likely the + // fallback won't work either (or is already open). + PDFView.error('PDF failed to download.'); + } + window.URL.revokeObjectURL(blobUrl); + } + ); + }, + noData // Error ocurred try downloading with just the url. + ); } else { url += '#pdfjs.action=download', '_parent'; window.open(url, '_parent'); @@ -544,6 +610,8 @@ var PDFView = { }; } + this.pdfDocument = pdfDocument; + var errorWrapper = document.getElementById('errorWrapper'); errorWrapper.setAttribute('hidden', 'true'); @@ -1520,7 +1588,7 @@ window.addEventListener('load', function webViewerLoad(evt) { PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true'); if ('pdfBug' in hashParams && - (!PDFJS.isFirefoxExtension || FirefoxCom.request('pdfBugEnabled'))) { + (!PDFJS.isFirefoxExtension || FirefoxCom.requestSync('pdfBugEnabled'))) { PDFJS.pdfBug = true; var pdfBug = hashParams['pdfBug']; var enabled = pdfBug.split(','); @@ -1529,7 +1597,7 @@ window.addEventListener('load', function webViewerLoad(evt) { } if (!PDFJS.isFirefoxExtension || - (PDFJS.isFirefoxExtension && FirefoxCom.request('searchEnabled'))) { + (PDFJS.isFirefoxExtension && FirefoxCom.requestSync('searchEnabled'))) { document.querySelector('#viewSearch').classList.remove('hidden'); }