From 4ffc408512590369ae965b86c9d4bb132fe69de4 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 2 Jul 2013 17:15:32 +0200 Subject: [PATCH 1/4] Download PDF from cache for non-Firefox add-on This feature relies on URL.createObjectURL, which is supported by - Firefox 4 - Chrome 8 - Opera 15 - Internet Explorer 10 If the feature is missing, it falls back to downloading from the server. The environment-specific code are put in ifdef's. Two methods are defined: - noData This function is used as a fallback in case of failure, it triggers a download directly from the server. - triggerSaveAs(String url, optional String blob) This function attempts to show a Save As dialog for a given URL. It attempts to use the a.download attribute, if available, and falls back to window.open(, '_parent') if unavailable. See also http://caniuse.com/download --- web/viewer.js | 105 +++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index 9ac2f5a95..106164e19 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -914,59 +914,55 @@ var PDFView = { }, download: function pdfViewDownload() { - function noData() { - FirefoxCom.request('download', { originalUrl: url }); - } var url = this.url.split('#')[0]; //#if !(FIREFOX || MOZCENTRAL) - - var a = document.createElement('a'); - - // If _parent == self, then opening an identical URL with different - // location hash will only cause a navigation, not a download. - if (window.top === window && !('download' in a) && - url === window.location.href.split('#')[0]) { - url += url.indexOf('?') === -1 ? '?' : '&'; + function noData() { + triggerSaveAs(url + '#pdfjs.action=download'); } + function triggerSaveAs(url, blobUrl) { + // If blobUrl is not specified, fall back to non-blob url. + if (!blobUrl) blobUrl = url; - url += '#pdfjs.action=download'; - if (a.click) { - // Use a.click() if available. Otherwise, Chrome might show - // "Unsafe JavaScript attempt to initiate a navigation change - // for frame with URL" and not open the PDF at all. - // Supported by (not mentioned = untested): - // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click) - // - Chrome 19 - 26 (18- does not support a.click) - // - Opera 9 - 12.15 - // - Internet Explorer 6 - 10 - // - Safari 6 (5.1- does not support a.click) - a.href = url; - a.target = '_parent'; - // Use a.download if available. This increases the likelihood that - // the file is downloaded instead of opened by another PDF plugin. - if ('download' in a) { - var filename = url.match(/([^\/?#=]+\.pdf)/i); - a.download = filename ? filename[1] : 'file.pdf'; + var a = document.createElement('a'); + if (a.click) { + // Use a.click() if available. Otherwise, Chrome might show + // "Unsafe JavaScript attempt to initiate a navigation change + // for frame with URL" and not open the PDF at all. + // Supported by (not mentioned = untested): + // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click) + // - Chrome 19 - 26 (18- does not support a.click) + // - Opera 9 - 12.15 + // - Internet Explorer 6 - 10 + // - Safari 6 (5.1- does not support a.click) + a.href = blobUrl; + a.target = '_parent'; + // Use a.download if available. This increases the likelihood that + // the file is downloaded instead of opened by another PDF plugin. + if ('download' in a) { + var filename = url.match(/([^\/?#=]+\.pdf\b)(?!.*\.pdf\b)/i); + a.download = filename ? filename[1] : 'document.pdf'; + } + // must be in the document for IE and recent Firefox versions. + // (otherwise .click() is ignored) + (document.body || document.documentElement).appendChild(a); + a.click(); + a.parentNode.removeChild(a); + } else { + if (window.top === window && + blobUrl.split('#')[0] === window.location.href.split('#')[0]) { + // If _parent == self, then opening an identical URL with different + // location hash will only cause a navigation, not a download. + var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&'; + blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&'); + } + window.open(blobUrl, '_parent'); } - // must be in the document for IE and recent Firefox versions. - // (otherwise .click() is ignored) - (document.body || document.documentElement).appendChild(a); - a.click(); - a.parentNode.removeChild(a); - } else { - window.open(url, '_parent'); } //#else -// // Document isn't ready just try to download with the url. -// if (!this.pdfDocument) { -// noData(); -// return; +// function noData() { +// FirefoxCom.request('download', { originalUrl: url }); // } -// this.pdfDocument.getData().then( -// function getDataSuccess(data) { -// var blob = PDFJS.createBlob(data.buffer, 'application/pdf'); -// var blobUrl = window.URL.createObjectURL(blob); -// +// function triggerSaveAs(url, blobUrl) { // FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url }, // function response(err) { // if (err) { @@ -977,10 +973,23 @@ var PDFView = { // window.URL.revokeObjectURL(blobUrl); // } // ); -// }, -// noData // Error occurred try downloading with just the url. -// ); +// } //#endif + var URL = window.URL || window.webkitURL; + // If the PDF is not ready yet, or if createObjectURL is not supported, + // just try to download with the url. + if (!this.pdfDocument || !URL) { + noData(); + return; + } + this.pdfDocument.getData().then( + function getDataSuccess(data) { + var blob = PDFJS.createBlob(data.buffer, 'application/pdf'); + var blobUrl = URL.createObjectURL(blob); + triggerSaveAs(url, blobUrl); + }, + noData // Error occurred try downloading with just the url. + ); }, fallback: function pdfViewFallback() { From 69a64d45bc8a76ac724864dcbe59bc89c1b02363 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Tue, 2 Jul 2013 20:39:10 +0200 Subject: [PATCH 2/4] Put URL = URL || webkitURL in compatibility.js Declares the URL variable globally. If the feature is not supported, the variable will still be declared, but have the "undefined" value. Supported by: - Firefox 4 Firefox 21 in Web worker - Chrome 8 (prefixed as webkitURL), 23+ unprefixed Chrome 10 (prefixed as webkitURL) in Web Worker, 23+ unprefixed - Opera 15 Opera 15 in Web Worker - Internet Explorer 10 Internet Explorer 11 in Web Worker - Safari 6 (prefixed as webkitURL) Safari 6 (prefixed as webkitURL) in Web Worker --- web/compatibility.js | 7 +++++++ web/viewer.js | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/web/compatibility.js b/web/compatibility.js index e9b69ed07..0d12c64a9 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -92,6 +92,13 @@ if (typeof PDFJS === 'undefined') { window.Float64Array = TypedArray; })(); +// URL = URL || webkitURL +(function normalizeURLObject() { + if (!window.URL && window.webkitURL) { + window.URL = window.webkitURL; + } +})(); + // Object.create() ? (function checkObjectCreateCompatibility() { if (typeof Object.create !== 'undefined') diff --git a/web/viewer.js b/web/viewer.js index 106164e19..d28afcc00 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* globals URL*/ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar */ /* globals PDFFindController, ProgressBar, getFileName, CustomStyle */ /* globals getOutputScale, TextLayerBuilder */ @@ -975,8 +976,7 @@ var PDFView = { // ); // } //#endif - var URL = window.URL || window.webkitURL; - // If the PDF is not ready yet, or if createObjectURL is not supported, + // If the PDF is not ready yet, or if URL.createObjectURL is not supported, // just try to download with the url. if (!this.pdfDocument || !URL) { noData(); From 84ae29c21d0bcdad09cb8ff71d53d4a149100fac Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 10 Jul 2013 20:14:00 +0200 Subject: [PATCH 3/4] Improve suggested filename on download For all of the following URLs, "file.pdf" will be suggested: http://.../file.pdf http://.../download.aspx?file=file.pdf&whatever http://.../get.pdf?name=file.pdf&whatever http://.../single-page-app#view=file.pdf http://.../download.aspx?file=%2Fpath%2Fto%2Ffile.pdf&whatever Fixes #3161 --- .../firefox/components/PdfStreamConverter.js | 8 +++- web/viewer.js | 38 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 84430a658..c89731b59 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -206,6 +206,10 @@ ChromeActions.prototype = { // The data may not be downloaded so we need just retry getting the pdf with // the original url. var originalUri = NetUtil.newURI(data.originalUrl); + var filename = data.filename; + if (typeof filename !== 'string' || !/\.pdf$/i.test(filename)) { + filename = 'document.pdf'; + } var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri; var extHelperAppSvc = Cc['@mozilla.org/uriloader/external-helper-app-service;1']. @@ -234,7 +238,9 @@ ChromeActions.prototype = { // contentDisposition/contentDispositionFilename is readonly before FF18 channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT; if (self.contentDispositionFilename) { - channel.contentDispositionFilename = self.contentDispositionFilename; + channel.contentDispositionFilename = self.contentDispositionFilename; + } else { + channel.contentDispositionFilename = filename; } } catch (e) {} channel.setURI(originalUri); diff --git a/web/viewer.js b/web/viewer.js index d28afcc00..645201e19 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -916,6 +916,30 @@ var PDFView = { download: function pdfViewDownload() { var url = this.url.split('#')[0]; + function getPDFFileNameFromURL(url) { + var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; + // SCHEME HOST 1.PATH 2.QUERY 3.REF + // Pattern to get last matching NAME.pdf + var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i; + var splitURI = reURI.exec(url); + var suggestedFilename = reFilename.exec(splitURI[1]) || + reFilename.exec(splitURI[2]) || + reFilename.exec(splitURI[3]); + if (suggestedFilename) { + suggestedFilename = suggestedFilename[0]; + if (suggestedFilename.indexOf('%') != -1) { + // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf + try { + suggestedFilename = + reFilename.exec(decodeURIComponent(suggestedFilename))[0]; + } catch(e) { // Possible (extremely rare) errors: + // URIError "Malformed URI", e.g. for "%AA.pdf" + // TypeError "null has no properties", e.g. for "%2F.pdf" + } + } + } + return suggestedFilename || 'document.pdf'; + } //#if !(FIREFOX || MOZCENTRAL) function noData() { triggerSaveAs(url + '#pdfjs.action=download'); @@ -940,8 +964,7 @@ var PDFView = { // Use a.download if available. This increases the likelihood that // the file is downloaded instead of opened by another PDF plugin. if ('download' in a) { - var filename = url.match(/([^\/?#=]+\.pdf\b)(?!.*\.pdf\b)/i); - a.download = filename ? filename[1] : 'document.pdf'; + a.download = getPDFFileNameFromURL(url); } // must be in the document for IE and recent Firefox versions. // (otherwise .click() is ignored) @@ -961,10 +984,17 @@ var PDFView = { } //#else // function noData() { -// FirefoxCom.request('download', { originalUrl: url }); +// FirefoxCom.request('download', { +// originalUrl: url, +// filename: getPDFFileNameFromURL(url) +// }); // } // function triggerSaveAs(url, blobUrl) { -// FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url }, +// FirefoxCom.request('download', { +// blobUrl: blobUrl, +// originalUrl: url, +// filename: getPDFFileNameFromURL(url) +// }, // function response(err) { // if (err) { // // This error won't really be helpful because it's likely the From e583070deb73113f13e7a5244138582b7a86140a Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 10 Jul 2013 22:07:23 +0200 Subject: [PATCH 4/4] Download PDF from cache for IE10 / IE11 IE9 falls back to downloading from the original URL. --- web/viewer.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/viewer.js b/web/viewer.js index 645201e19..a21cde03a 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1015,11 +1015,20 @@ var PDFView = { this.pdfDocument.getData().then( function getDataSuccess(data) { var blob = PDFJS.createBlob(data.buffer, 'application/pdf'); +//#if GENERIC + if (navigator.msSaveBlob) { + // IE10 / IE11 + if (!navigator.msSaveBlob(blob, getPDFFileNameFromURL(url))) { + noData(); + } + return; + } +//#endif var blobUrl = URL.createObjectURL(blob); triggerSaveAs(url, blobUrl); }, noData // Error occurred try downloading with just the url. - ); + ).then(null, noData); }, fallback: function pdfViewFallback() {